452 lines
10 KiB
C
452 lines
10 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <drivers/clock_control.h>
|
|
#include <kernel.h>
|
|
#include <soc.h>
|
|
#include <errno.h>
|
|
#include <drivers/i2c.h>
|
|
|
|
#define SPEED_100KHZ_BUS 0
|
|
#define SPEED_400KHZ_BUS 1
|
|
#define SPEED_1MHZ_BUS 2
|
|
|
|
#define WAIT_INTERVAL 5
|
|
/* 250 us */
|
|
#define TIMEOUT 100
|
|
/* 25 us */
|
|
#define MAX_CLK_STRETCHING 5
|
|
|
|
struct xec_speed_cfg {
|
|
u32_t bus_clk;
|
|
u32_t data_timing;
|
|
u32_t start_hold_time;
|
|
u32_t config;
|
|
u32_t timeout_scale;
|
|
};
|
|
|
|
struct i2c_xec_config {
|
|
u32_t port_sel;
|
|
u32_t base_addr;
|
|
};
|
|
|
|
struct i2c_xec_data {
|
|
u32_t pending_stop;
|
|
};
|
|
|
|
/* Recommended programming values based on 16MHz
|
|
* i2c_baud_clk_period/bus_clk_period - 2 = (low_period + hi_period)
|
|
* bus_clk_reg (16MHz/100KHz -2) = 0x4F + 0x4F
|
|
* (16MHz/400KHz -2) = 0x0F + 0x17
|
|
* (16MHz/1MHz -2) = 0x05 + 0x09
|
|
*/
|
|
static const struct xec_speed_cfg xec_cfg_params[] = {
|
|
[SPEED_100KHZ_BUS] = {
|
|
.bus_clk = 0x00004F4F,
|
|
.data_timing = 0x0C4D5006,
|
|
.start_hold_time = 0x0000004D,
|
|
.config = 0x01FC01ED,
|
|
.timeout_scale = 0x4B9CC2C7,
|
|
},
|
|
[SPEED_400KHZ_BUS] = {
|
|
.bus_clk = 0x00000F17,
|
|
.data_timing = 0x040A0A06,
|
|
.start_hold_time = 0x0000000A,
|
|
.config = 0x01000050,
|
|
.timeout_scale = 0x159CC2C7,
|
|
},
|
|
[SPEED_1MHZ_BUS] = {
|
|
.bus_clk = 0x00000509,
|
|
.data_timing = 0x04060601,
|
|
.start_hold_time = 0x00000006,
|
|
.config = 0x10000050,
|
|
.timeout_scale = 0x089CC2C7,
|
|
},
|
|
};
|
|
|
|
static int xec_spin_yield(int *counter)
|
|
{
|
|
*counter = *counter + 1;
|
|
|
|
if (*counter > TIMEOUT) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if (*counter > MAX_CLK_STRETCHING * 2) {
|
|
k_yield();
|
|
} else {
|
|
k_busy_wait(5);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void recover_from_error(u32_t ba)
|
|
{
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_STO |
|
|
MCHP_I2C_SMB_CTRL_ACK;
|
|
}
|
|
|
|
static int wait_bus_free(u32_t ba)
|
|
{
|
|
int ret;
|
|
int counter = 0;
|
|
|
|
while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) {
|
|
ret = xec_spin_yield(&counter);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wait_completion(u32_t ba)
|
|
{
|
|
int ret;
|
|
int counter = 0;
|
|
|
|
/* Wait for transaction to be completed */
|
|
while (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_PIN) {
|
|
ret = xec_spin_yield(&counter);
|
|
|
|
if (ret < 0) {
|
|
recover_from_error(ba);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Check if Slave send ACK/NACK */
|
|
if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_LRB_AD0) {
|
|
recover_from_error(ba);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Check for bus error */
|
|
if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) {
|
|
recover_from_error(ba);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool check_lines(u32_t ba)
|
|
{
|
|
return ((!(MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_CLKI_RO)) ||
|
|
(!(MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_DATI_RO)));
|
|
}
|
|
|
|
static int i2c_xec_configure(struct device *dev, u32_t dev_config_raw)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config->config_info);
|
|
u32_t ba = config->base_addr;
|
|
u8_t port_sel = config->port_sel;
|
|
u32_t speed_id;
|
|
u32_t cfg, bb_ctrl;
|
|
u8_t ctrl;
|
|
|
|
if (!(dev_config_raw & I2C_MODE_MASTER)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (dev_config_raw & I2C_ADDR_10_BITS) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (I2C_SPEED_GET(dev_config_raw)) {
|
|
case I2C_SPEED_STANDARD:
|
|
speed_id = SPEED_100KHZ_BUS;
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
speed_id = SPEED_400KHZ_BUS;
|
|
break;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
speed_id = SPEED_1MHZ_BUS;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
cfg = MCHP_I2C_SMB_CFG(ba) & MCHP_I2C_SMB_CTRL_MASK;
|
|
bb_ctrl = MCHP_I2C_SMB_BB_CTRL(ba) & MCHP_I2C_SMB_BB_MASK;
|
|
ctrl = MCHP_I2C_SMB_CTRL(ba) & MCHP_I2C_SMB_CTRL_MASK;
|
|
|
|
/* Assert RESET and clr others */
|
|
cfg |= MCHP_I2C_SMB_CFG_RESET;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
|
|
/* Enable bit-bang mode, do not enable SMBus HW timeouts */
|
|
bb_ctrl |= MCHP_I2C_SMB_BB_EN;
|
|
bb_ctrl |= MCHP_I2C_SMB_BB_SCL_DIR_OUT;
|
|
bb_ctrl |= MCHP_I2C_SMB_BB_SDA_DIR_OUT;
|
|
bb_ctrl |= (MCHP_I2C_SMB_BB_CL | MCHP_I2C_SMB_BB_DAT);
|
|
MCHP_I2C_SMB_BB_CTRL(ba) = bb_ctrl;
|
|
|
|
/* Bus reset */
|
|
cfg &= ~MCHP_I2C_SMB_CFG_RESET;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
|
|
/* Assert PIN bit, ESO = 0 and disables Interrupts (ENI) */
|
|
ctrl = MCHP_I2C_SMB_CTRL_PIN;
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = ctrl;
|
|
|
|
/* Port selection */
|
|
cfg |= (port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK);
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
|
|
/* Enable controller and I2C filters*/
|
|
cfg |= (MCHP_I2C_SMB_CFG_ENAB | MCHP_I2C_SMB_CFG_FEN);
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
|
|
/* Set own address */
|
|
MCHP_I2C_SMB_OWN_ADDR(ba) = 0x7F;
|
|
|
|
/* Configure speed */
|
|
MCHP_I2C_SMB_BUS_CLK(ba) = xec_cfg_params[speed_id].bus_clk;
|
|
MCHP_I2C_SMB_DATA_TM(ba) = xec_cfg_params[speed_id].data_timing;
|
|
MCHP_I2C_SMB_RSHT(ba) = xec_cfg_params[speed_id].start_hold_time;
|
|
MCHP_I2C_SMB_TMTSC(ba) = xec_cfg_params[speed_id].timeout_scale;
|
|
|
|
ctrl |= (MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_ENI | MCHP_I2C_SMB_CTRL_ACK);
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = ctrl;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xec_poll_write(struct device *dev, struct i2c_msg msg,
|
|
u16_t addr)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config->config_info);
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->driver_data);
|
|
u32_t ba = config->base_addr;
|
|
int ret;
|
|
|
|
if (data->pending_stop == 0) {
|
|
/* Check clock and data lines */
|
|
if (check_lines(ba)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Wait until bus is free */
|
|
ret = wait_bus_free(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Send slave address */
|
|
MCHP_I2C_SMB_DATA(ba) = (addr & ~BIT(0));
|
|
|
|
/* Send start and ack bits */
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO | MCHP_I2C_SMB_CTRL_STA |
|
|
MCHP_I2C_SMB_CTRL_ACK;
|
|
|
|
ret = wait_completion(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Send bytes */
|
|
for (int i = 0U; i < msg.len; i++) {
|
|
MCHP_I2C_SMB_DATA(ba) = msg.buf[i];
|
|
ret = wait_completion(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* Handle stop bit for last byte to write */
|
|
if (i == (msg.len - 1)) {
|
|
if (msg.flags & I2C_MSG_STOP) {
|
|
/* Send stop and ack bits */
|
|
MCHP_I2C_SMB_CTRL_WO(ba) =
|
|
MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_STO |
|
|
MCHP_I2C_SMB_CTRL_ACK;
|
|
data->pending_stop = 0;
|
|
} else {
|
|
data->pending_stop = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xec_poll_read(struct device *dev, struct i2c_msg msg,
|
|
u16_t addr)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config->config_info);
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->driver_data);
|
|
u32_t ba = config->base_addr;
|
|
u8_t byte, ctrl;
|
|
int ret;
|
|
|
|
if (!(msg.flags & I2C_MSG_RESTART)) {
|
|
/* Check clock and data lines */
|
|
if (check_lines(ba)) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Wait until bus is free */
|
|
ret = wait_bus_free(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* MCHP I2C spec recommends that for repeated start to write to control
|
|
* register before writing to data register
|
|
*/
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_STA | MCHP_I2C_SMB_CTRL_ACK;
|
|
|
|
/* Send slave address */
|
|
MCHP_I2C_SMB_DATA(ba) = (addr | BIT(0));
|
|
|
|
ret = wait_completion(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (msg.len == 1) {
|
|
/* Send NACK for last transaction */
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO;
|
|
}
|
|
|
|
/* Read dummy byte */
|
|
byte = MCHP_I2C_SMB_DATA(ba);
|
|
ret = wait_completion(ba);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
for (int i = 0U; i < msg.len; i++) {
|
|
while (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_PIN) {
|
|
if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) {
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
|
|
if (i == (msg.len - 1)) {
|
|
if (msg.flags & I2C_MSG_STOP) {
|
|
/* Send stop and ack bits */
|
|
ctrl = (MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_STO |
|
|
MCHP_I2C_SMB_CTRL_ACK);
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = ctrl;
|
|
data->pending_stop = 0;
|
|
}
|
|
} else if (i == (msg.len - 2)) {
|
|
/* Send NACK for last transaction */
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_ESO;
|
|
}
|
|
msg.buf[i] = MCHP_I2C_SMB_DATA(ba);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xec_transfer(struct device *dev, struct i2c_msg *msgs,
|
|
u8_t num_msgs, u16_t addr)
|
|
{
|
|
int ret = 0;
|
|
|
|
addr <<= 1;
|
|
for (int i = 0U; i < num_msgs; i++) {
|
|
if ((msgs[i].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
|
|
ret = i2c_xec_poll_write(dev, msgs[i], addr);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = i2c_xec_poll_read(dev, msgs[i], addr);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_I2C_SLAVE)
|
|
static int i2c_xec_slave_register(struct device *dev)
|
|
{
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
static int i2c_xec_slave_unregister(struct device *dev)
|
|
{
|
|
return -ENOTSUP;
|
|
}
|
|
#endif
|
|
|
|
static int i2c_xec_init(struct device *dev);
|
|
|
|
static const struct i2c_driver_api i2c_xec_driver_api = {
|
|
.configure = i2c_xec_configure,
|
|
.transfer = i2c_xec_transfer,
|
|
#if defined(CONFIG_I2C_SLAVE)
|
|
.slave_register = i2c_xec_slave_register,
|
|
.slave_unregister = i2c_xec_slave_unregister,
|
|
#endif
|
|
};
|
|
|
|
#ifdef CONFIG_I2C_XEC_0
|
|
static struct i2c_xec_data i2c_xec_data_0;
|
|
static const struct i2c_xec_config i2c_xec_config_0 = {
|
|
.base_addr = DT_INST_0_MICROCHIP_XEC_I2C_BASE_ADDRESS,
|
|
.port_sel = DT_INST_0_MICROCHIP_XEC_I2C_PORT_SEL,
|
|
};
|
|
DEVICE_AND_API_INIT(i2c_xec_0, DT_INST_0_MICROCHIP_XEC_I2C_LABEL,
|
|
&i2c_xec_init, &i2c_xec_data_0, &i2c_xec_config_0,
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,
|
|
&i2c_xec_driver_api);
|
|
#endif /* CONFIG_I2C_XEC_0 */
|
|
|
|
#ifdef CONFIG_I2C_XEC_1
|
|
static struct i2c_xec_data i2c_xec_data_1;
|
|
static const struct i2c_xec_config i2c_xec_config_1 = {
|
|
.base_addr = DT_INST_1_MICROCHIP_XEC_I2C_BASE_ADDRESS,
|
|
.port_sel = DT_INST_1_MICROCHIP_XEC_I2C_PORT_SEL,
|
|
};
|
|
DEVICE_AND_API_INIT(i2c_xec_1, DT_INST_1_MICROCHIP_XEC_I2C_LABEL,
|
|
&i2c_xec_init, &i2c_xec_data_1, &i2c_xec_config_1,
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,
|
|
&i2c_xec_driver_api);
|
|
#endif /* CONFIG_I2C_XEC_1 */
|
|
|
|
#ifdef CONFIG_I2C_XEC_2
|
|
static struct i2c_xec_data i2c_xec_data_2;
|
|
static const struct i2c_xec_config i2c_xec_config_2 = {
|
|
.base_addr = DT_INST_2_MICROCHIP_XEC_I2C_BASE_ADDRESS,
|
|
.port_sel = DT_INST_2_MICROCHIP_XEC_I2C_PORT_SEL,
|
|
};
|
|
DEVICE_AND_API_INIT(i2c_xec_2, DT_INST_2_MICROCHIP_XEC_I2C_LABEL,
|
|
&i2c_xec_init, &i2c_xec_data_2, &i2c_xec_config_2,
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY,
|
|
&i2c_xec_driver_api);
|
|
#endif /* CONFIG_I2C_XEC_2 */
|
|
|
|
static int i2c_xec_init(struct device *dev)
|
|
{
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->driver_data);
|
|
data->pending_stop = 0;
|
|
return 0;
|
|
}
|