920 lines
22 KiB
C
920 lines
22 KiB
C
/*
|
|
* Copyright (c) 2019 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_xec_i2c
|
|
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
|
|
#include <zephyr/kernel.h>
|
|
#include <soc.h>
|
|
#include <errno.h>
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/irq.h>
|
|
LOG_MODULE_REGISTER(i2c_mchp, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
#define SPEED_100KHZ_BUS 0
|
|
#define SPEED_400KHZ_BUS 1
|
|
#define SPEED_1MHZ_BUS 2
|
|
|
|
#define EC_OWN_I2C_ADDR 0x7F
|
|
#define RESET_WAIT_US 20
|
|
#define BUS_IDLE_US_DFLT 5
|
|
|
|
/* I2C timeout is 10 ms (WAIT_INTERVAL * WAIT_COUNT) */
|
|
#define WAIT_INTERVAL 50
|
|
#define WAIT_COUNT 200
|
|
|
|
/* Line High Timeout is 2.5 ms (WAIT_LINE_HIGH_USEC * WAIT_LINE_HIGH_COUNT) */
|
|
#define WAIT_LINE_HIGH_USEC 25
|
|
#define WAIT_LINE_HIGH_COUNT 100
|
|
|
|
/* I2C Read/Write bit pos */
|
|
#define I2C_READ_WRITE_POS 0
|
|
|
|
struct xec_speed_cfg {
|
|
uint32_t bus_clk;
|
|
uint32_t data_timing;
|
|
uint32_t start_hold_time;
|
|
uint32_t config;
|
|
uint32_t timeout_scale;
|
|
};
|
|
|
|
struct i2c_xec_config {
|
|
uint32_t port_sel;
|
|
uint32_t base_addr;
|
|
uint8_t girq_id;
|
|
uint8_t girq_bit;
|
|
uint8_t pcr_idx;
|
|
uint8_t pcr_bitpos;
|
|
struct gpio_dt_spec sda_gpio;
|
|
struct gpio_dt_spec scl_gpio;
|
|
const struct pinctrl_dev_config *pcfg;
|
|
void (*irq_config_func)(void);
|
|
};
|
|
|
|
struct i2c_xec_data {
|
|
uint32_t pending_stop;
|
|
uint32_t error_seen;
|
|
uint32_t timeout_seen;
|
|
uint32_t previously_in_read;
|
|
uint32_t speed_id;
|
|
struct i2c_target_config *slave_cfg;
|
|
bool slave_attached;
|
|
bool slave_read;
|
|
};
|
|
|
|
/* 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 void i2c_xec_reset_config(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->data);
|
|
uint32_t ba = config->base_addr;
|
|
|
|
/* Assert RESET */
|
|
z_mchp_xec_pcr_periph_reset(config->pcr_idx, config->pcr_bitpos);
|
|
/* Write 0x80. i.e Assert PIN bit, ESO = 0 and Interrupts
|
|
* disabled (ENI)
|
|
*/
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN;
|
|
|
|
/* Enable controller and I2C filters */
|
|
MCHP_I2C_SMB_CFG(ba) = MCHP_I2C_SMB_CFG_GC_EN |
|
|
MCHP_I2C_SMB_CFG_ENAB |
|
|
MCHP_I2C_SMB_CFG_FEN |
|
|
(config->port_sel &
|
|
MCHP_I2C_SMB_CFG_PORT_SEL_MASK);
|
|
|
|
/* Configure bus clock register, Data Timing register,
|
|
* Repeated Start Hold Time register,
|
|
* and Timeout Scaling register
|
|
*/
|
|
MCHP_I2C_SMB_BUS_CLK(ba) = xec_cfg_params[data->speed_id].bus_clk;
|
|
MCHP_I2C_SMB_DATA_TM(ba) = xec_cfg_params[data->speed_id].data_timing;
|
|
MCHP_I2C_SMB_RSHT(ba) =
|
|
xec_cfg_params[data->speed_id].start_hold_time;
|
|
MCHP_I2C_SMB_TMTSC(ba) = xec_cfg_params[data->speed_id].timeout_scale;
|
|
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_ACK;
|
|
|
|
k_busy_wait(RESET_WAIT_US);
|
|
}
|
|
|
|
static int xec_spin_yield(int *counter)
|
|
{
|
|
*counter = *counter + 1;
|
|
|
|
if (*counter > WAIT_COUNT) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
k_busy_wait(WAIT_INTERVAL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cleanup_registers(uint32_t ba)
|
|
{
|
|
uint32_t cfg = MCHP_I2C_SMB_CFG(ba);
|
|
|
|
cfg |= MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO;
|
|
|
|
cfg |= MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO;
|
|
|
|
cfg |= MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO;
|
|
|
|
cfg |= MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO;
|
|
MCHP_I2C_SMB_CFG(ba) = cfg;
|
|
cfg &= ~MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
static void restart_slave(uint32_t ba)
|
|
{
|
|
MCHP_I2C_SMB_CTRL_WO(ba) = MCHP_I2C_SMB_CTRL_PIN |
|
|
MCHP_I2C_SMB_CTRL_ESO |
|
|
MCHP_I2C_SMB_CTRL_ACK |
|
|
MCHP_I2C_SMB_CTRL_ENI;
|
|
}
|
|
#endif
|
|
|
|
static void recover_from_error(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
uint32_t ba = config->base_addr;
|
|
|
|
cleanup_registers(ba);
|
|
i2c_xec_reset_config(dev);
|
|
}
|
|
|
|
static int wait_bus_free(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
int ret;
|
|
int counter = 0;
|
|
uint32_t ba = config->base_addr;
|
|
|
|
while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) {
|
|
ret = xec_spin_yield(&counter);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Check for bus error */
|
|
if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_BER) {
|
|
recover_from_error(dev);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Wait with timeout for I2C controller to finish transmit/receive of one
|
|
* byte(address or data).
|
|
* When transmit/receive operation is started the I2C PIN status is 1. Upon
|
|
* normal completion I2C PIN status asserts(0).
|
|
* We loop checking I2C status for the following events:
|
|
* Bus Error:
|
|
* Reset controller and return -EBUSY
|
|
* Lost Arbitration:
|
|
* Return -EPERM. We lost bus to another controller. No reset.
|
|
* PIN == 0: I2C Status LRB is valid and contains ACK/NACK data on 9th clock.
|
|
* ACK return 0 (success)
|
|
* NACK Issue STOP, wait for bus minimum idle time, return -EIO.
|
|
* Timeout:
|
|
* Reset controller and return -ETIMEDOUT
|
|
*
|
|
* NOTE: After generating a STOP the controller will not generate a START until
|
|
* Bus Minimum Idle time has expired.
|
|
*/
|
|
static int wait_completion(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
int ret;
|
|
int counter = 0;
|
|
uint32_t ba = config->base_addr;
|
|
|
|
while (1) {
|
|
uint8_t status = MCHP_I2C_SMB_STS_RO(ba);
|
|
|
|
/* Is bus error ? */
|
|
if (status & MCHP_I2C_SMB_STS_BER) {
|
|
recover_from_error(dev);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Is Lost arbitration ? */
|
|
status = MCHP_I2C_SMB_STS_RO(ba);
|
|
if (status & MCHP_I2C_SMB_STS_LAB) {
|
|
recover_from_error(dev);
|
|
return -EPERM;
|
|
}
|
|
|
|
status = MCHP_I2C_SMB_STS_RO(ba);
|
|
/* PIN -> 0 indicates I2C is done */
|
|
if (!(status & MCHP_I2C_SMB_STS_PIN)) {
|
|
/* PIN == 0. LRB contains state of 9th bit */
|
|
if (status & MCHP_I2C_SMB_STS_LRB_AD0) { /* NACK? */
|
|
/* Send STOP */
|
|
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;
|
|
k_busy_wait(BUS_IDLE_US_DFLT);
|
|
return -EIO;
|
|
}
|
|
break; /* success: ACK */
|
|
}
|
|
|
|
ret = xec_spin_yield(&counter);
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Call GPIO driver to read state of pins.
|
|
* Return boolean true if both lines HIGH else return boolean false
|
|
*/
|
|
static bool check_lines_high(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const)(dev->config);
|
|
gpio_port_value_t sda = 0, scl = 0;
|
|
|
|
if (gpio_port_get_raw(config->sda_gpio.port, &sda)) {
|
|
LOG_ERR("gpio_port_get_raw for %s SDA failed", dev->name);
|
|
return false;
|
|
}
|
|
|
|
/* both pins could be on same GPIO group */
|
|
if (config->sda_gpio.port == config->scl_gpio.port) {
|
|
scl = sda;
|
|
} else {
|
|
if (gpio_port_get_raw(config->scl_gpio.port, &scl)) {
|
|
LOG_ERR("gpio_port_get_raw for %s SCL failed",
|
|
dev->name);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return (sda & BIT(config->sda_gpio.pin)) && (scl & BIT(config->scl_gpio.pin));
|
|
|
|
}
|
|
|
|
static int i2c_xec_configure(const struct device *dev,
|
|
uint32_t dev_config_raw)
|
|
{
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->data);
|
|
|
|
if (!(dev_config_raw & I2C_MODE_CONTROLLER)) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (dev_config_raw & I2C_ADDR_10_BITS) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (I2C_SPEED_GET(dev_config_raw)) {
|
|
case I2C_SPEED_STANDARD:
|
|
data->speed_id = SPEED_100KHZ_BUS;
|
|
break;
|
|
case I2C_SPEED_FAST:
|
|
data->speed_id = SPEED_400KHZ_BUS;
|
|
break;
|
|
case I2C_SPEED_FAST_PLUS:
|
|
data->speed_id = SPEED_1MHZ_BUS;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
i2c_xec_reset_config(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xec_poll_write(const struct device *dev, struct i2c_msg msg,
|
|
uint16_t addr)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->data);
|
|
uint32_t ba = config->base_addr;
|
|
uint8_t i2c_timer = 0, byte;
|
|
int ret;
|
|
|
|
if (data->timeout_seen == 1) {
|
|
/* Wait to see if the slave has released the CLK */
|
|
ret = wait_completion(dev);
|
|
if (ret) {
|
|
data->timeout_seen = 1;
|
|
LOG_ERR("%s: %s wait_completion failure %d\n",
|
|
__func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
data->timeout_seen = 0;
|
|
|
|
/* If we are here, it means the slave has finally released
|
|
* the CLK. The master needs to end that transaction
|
|
* gracefully by sending a STOP on the bus.
|
|
*/
|
|
LOG_DBG("%s: %s Force Stop", __func__, dev->name);
|
|
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;
|
|
k_busy_wait(BUS_IDLE_US_DFLT);
|
|
data->pending_stop = 0;
|
|
|
|
/* If the timeout had occurred while the master was reading
|
|
* something from the slave, that read needs to be completed
|
|
* to clear the bus.
|
|
*/
|
|
if (data->previously_in_read == 1) {
|
|
data->previously_in_read = 0;
|
|
byte = MCHP_I2C_SMB_DATA(ba);
|
|
}
|
|
return -EBUSY;
|
|
}
|
|
|
|
if ((data->pending_stop == 0) || (data->error_seen == 1)) {
|
|
/* Wait till clock and data lines are HIGH */
|
|
while (check_lines_high(dev) == false) {
|
|
if (i2c_timer >= WAIT_LINE_HIGH_COUNT) {
|
|
LOG_DBG("%s: %s not high",
|
|
__func__, dev->name);
|
|
data->error_seen = 1;
|
|
return -EBUSY;
|
|
}
|
|
k_busy_wait(WAIT_LINE_HIGH_USEC);
|
|
i2c_timer++;
|
|
}
|
|
|
|
if (data->error_seen) {
|
|
LOG_DBG("%s: Recovering %s previously in error",
|
|
__func__, dev->name);
|
|
data->error_seen = 0;
|
|
recover_from_error(dev);
|
|
}
|
|
|
|
/* Wait until bus is free */
|
|
ret = wait_bus_free(dev);
|
|
if (ret) {
|
|
data->error_seen = 1;
|
|
LOG_DBG("%s: %s wait_bus_free failure %d",
|
|
__func__, dev->name, 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(dev);
|
|
switch (ret) {
|
|
case 0: /* Success */
|
|
break;
|
|
|
|
case -EIO:
|
|
LOG_WRN("%s: No Addr ACK from Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
default:
|
|
data->error_seen = 1;
|
|
LOG_ERR("%s: %s wait_comp error %d for addr send",
|
|
__func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Send bytes */
|
|
for (int i = 0U; i < msg.len; i++) {
|
|
MCHP_I2C_SMB_DATA(ba) = msg.buf[i];
|
|
ret = wait_completion(dev);
|
|
|
|
switch (ret) {
|
|
case 0: /* Success */
|
|
break;
|
|
|
|
case -EIO:
|
|
LOG_ERR("%s: No Data ACK from Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
case -ETIMEDOUT:
|
|
data->timeout_seen = 1;
|
|
LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
default:
|
|
data->error_seen = 1;
|
|
LOG_ERR("%s: %s wait_completion error %d for data send",
|
|
__func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Handle stop bit for last byte to write */
|
|
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(const struct device *dev, struct i2c_msg msg,
|
|
uint16_t addr)
|
|
{
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->data);
|
|
uint32_t ba = config->base_addr;
|
|
uint8_t byte, ctrl, i2c_timer = 0;
|
|
int ret;
|
|
|
|
if (data->timeout_seen == 1) {
|
|
/* Wait to see if the slave has released the CLK */
|
|
ret = wait_completion(dev);
|
|
if (ret) {
|
|
data->timeout_seen = 1;
|
|
LOG_ERR("%s: %s wait_completion failure %d\n",
|
|
__func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
data->timeout_seen = 0;
|
|
|
|
/* If we are here, it means the slave has finally released
|
|
* the CLK. The master needs to end that transaction
|
|
* gracefully by sending a STOP on the bus.
|
|
*/
|
|
LOG_DBG("%s: %s Force Stop", __func__, dev->name);
|
|
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;
|
|
k_busy_wait(BUS_IDLE_US_DFLT);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!(msg.flags & I2C_MSG_RESTART) || (data->error_seen == 1)) {
|
|
/* Wait till clock and data lines are HIGH */
|
|
while (check_lines_high(dev) == false) {
|
|
if (i2c_timer >= WAIT_LINE_HIGH_COUNT) {
|
|
LOG_DBG("%s: %s not high",
|
|
__func__, dev->name);
|
|
data->error_seen = 1;
|
|
return -EBUSY;
|
|
}
|
|
k_busy_wait(WAIT_LINE_HIGH_USEC);
|
|
i2c_timer++;
|
|
}
|
|
|
|
if (data->error_seen) {
|
|
LOG_DBG("%s: Recovering %s previously in error",
|
|
__func__, dev->name);
|
|
data->error_seen = 0;
|
|
recover_from_error(dev);
|
|
}
|
|
|
|
/* Wait until bus is free */
|
|
ret = wait_bus_free(dev);
|
|
if (ret) {
|
|
data->error_seen = 1;
|
|
LOG_DBG("%s: %s wait_bus_free failure %d",
|
|
__func__, dev->name, 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(dev);
|
|
switch (ret) {
|
|
case 0: /* Success */
|
|
break;
|
|
|
|
case -EIO:
|
|
data->error_seen = 1;
|
|
LOG_WRN("%s: No Addr ACK from Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
case -ETIMEDOUT:
|
|
data->previously_in_read = 1;
|
|
data->timeout_seen = 1;
|
|
LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
default:
|
|
data->error_seen = 1;
|
|
LOG_ERR("%s: %s wait_completion error %d for address send",
|
|
__func__, dev->name, 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);
|
|
|
|
for (int i = 0U; i < msg.len; i++) {
|
|
ret = wait_completion(dev);
|
|
switch (ret) {
|
|
case 0: /* Success */
|
|
break;
|
|
|
|
case -EIO:
|
|
LOG_ERR("%s: No Data ACK from Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
case -ETIMEDOUT:
|
|
data->previously_in_read = 1;
|
|
data->timeout_seen = 1;
|
|
LOG_ERR("%s: Clk stretch Timeout - Slave 0x%x on %s",
|
|
__func__, addr >> 1, dev->name);
|
|
return ret;
|
|
|
|
default:
|
|
data->error_seen = 1;
|
|
LOG_ERR("%s: %s wait_completion error %d for data send",
|
|
__func__, dev->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
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(const struct device *dev, struct i2c_msg *msgs,
|
|
uint8_t num_msgs, uint16_t addr)
|
|
{
|
|
int ret = 0;
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
struct i2c_xec_data *data = dev->data;
|
|
|
|
if (data->slave_attached) {
|
|
LOG_ERR("%s Device is registered as slave", dev->name);
|
|
return -EBUSY;
|
|
}
|
|
#endif
|
|
|
|
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) {
|
|
LOG_ERR("%s Write error: %d", dev->name, ret);
|
|
return ret;
|
|
}
|
|
} else {
|
|
ret = i2c_xec_poll_read(dev, msgs[i], addr);
|
|
if (ret) {
|
|
LOG_ERR("%s Read error: %d", dev->name, ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_xec_bus_isr(const struct device *dev)
|
|
{
|
|
#ifdef CONFIG_I2C_TARGET
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
struct i2c_xec_data *data = dev->data;
|
|
const struct i2c_target_callbacks *slave_cb = data->slave_cfg->callbacks;
|
|
uint32_t ba = config->base_addr;
|
|
|
|
uint32_t status;
|
|
uint8_t val;
|
|
|
|
uint8_t dummy = 0U;
|
|
|
|
if (!data->slave_attached) {
|
|
return;
|
|
}
|
|
|
|
/* Get current status */
|
|
status = MCHP_I2C_SMB_STS_RO(ba);
|
|
|
|
/* Bus Error */
|
|
if (status & MCHP_I2C_SMB_STS_BER) {
|
|
if (slave_cb->stop) {
|
|
slave_cb->stop(data->slave_cfg);
|
|
}
|
|
restart_slave(ba);
|
|
goto clear_iag;
|
|
}
|
|
|
|
/* External stop */
|
|
if (status & MCHP_I2C_SMB_STS_EXT_STOP) {
|
|
if (slave_cb->stop) {
|
|
slave_cb->stop(data->slave_cfg);
|
|
}
|
|
dummy = MCHP_I2C_SMB_DATA(ba);
|
|
restart_slave(ba);
|
|
goto clear_iag;
|
|
}
|
|
|
|
/* Address byte handling */
|
|
if (status & MCHP_I2C_SMB_STS_AAS) {
|
|
uint8_t slv_data = MCHP_I2C_SMB_DATA(ba);
|
|
|
|
if (!(slv_data & BIT(I2C_READ_WRITE_POS))) {
|
|
/* Slave receive */
|
|
data->slave_read = false;
|
|
if (slave_cb->write_requested) {
|
|
slave_cb->write_requested(data->slave_cfg);
|
|
}
|
|
goto clear_iag;
|
|
} else {
|
|
/* Slave transmit */
|
|
data->slave_read = true;
|
|
if (slave_cb->read_requested) {
|
|
slave_cb->read_requested(data->slave_cfg, &val);
|
|
}
|
|
MCHP_I2C_SMB_DATA(ba) = val;
|
|
goto clear_iag;
|
|
}
|
|
}
|
|
|
|
/* Slave transmit */
|
|
if (data->slave_read) {
|
|
/* Master has Nacked, then just write a dummy byte */
|
|
if (MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_LRB_AD0) {
|
|
MCHP_I2C_SMB_DATA(ba) = dummy;
|
|
} else {
|
|
if (slave_cb->read_processed) {
|
|
slave_cb->read_processed(data->slave_cfg, &val);
|
|
}
|
|
MCHP_I2C_SMB_DATA(ba) = val;
|
|
}
|
|
} else {
|
|
val = MCHP_I2C_SMB_DATA(ba);
|
|
/* TODO NACK Master */
|
|
if (slave_cb->write_received) {
|
|
slave_cb->write_received(data->slave_cfg, val);
|
|
}
|
|
}
|
|
|
|
clear_iag:
|
|
MCHP_GIRQ_SRC(config->girq_id) = BIT(config->girq_bit);
|
|
#endif
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
static int i2c_xec_target_register(const struct device *dev,
|
|
struct i2c_target_config *config)
|
|
{
|
|
const struct i2c_xec_config *cfg = dev->config;
|
|
struct i2c_xec_data *data = dev->data;
|
|
uint32_t ba = cfg->base_addr;
|
|
int ret;
|
|
int counter = 0;
|
|
|
|
if (!config) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (data->slave_attached) {
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Wait for any outstanding transactions to complete so that
|
|
* the bus is free
|
|
*/
|
|
while (!(MCHP_I2C_SMB_STS_RO(ba) & MCHP_I2C_SMB_STS_NBB)) {
|
|
ret = xec_spin_yield(&counter);
|
|
|
|
if (ret < 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
data->slave_cfg = config;
|
|
|
|
/* Set own address */
|
|
MCHP_I2C_SMB_OWN_ADDR(ba) = data->slave_cfg->address;
|
|
restart_slave(ba);
|
|
|
|
data->slave_attached = true;
|
|
|
|
/* Clear before enabling girq bit */
|
|
MCHP_GIRQ_SRC(cfg->girq_id) = BIT(cfg->girq_bit);
|
|
MCHP_GIRQ_ENSET(cfg->girq_id) = BIT(cfg->girq_bit);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_xec_target_unregister(const struct device *dev,
|
|
struct i2c_target_config *config)
|
|
{
|
|
const struct i2c_xec_config *cfg = dev->config;
|
|
struct i2c_xec_data *data = dev->data;
|
|
|
|
if (!data->slave_attached) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->slave_attached = false;
|
|
|
|
MCHP_GIRQ_ENCLR(cfg->girq_id) = BIT(cfg->girq_bit);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct i2c_driver_api i2c_xec_driver_api = {
|
|
.configure = i2c_xec_configure,
|
|
.transfer = i2c_xec_transfer,
|
|
#ifdef CONFIG_I2C_TARGET
|
|
.target_register = i2c_xec_target_register,
|
|
.target_unregister = i2c_xec_target_unregister,
|
|
#endif
|
|
};
|
|
|
|
static int i2c_xec_init(const struct device *dev)
|
|
{
|
|
const struct i2c_xec_config *cfg = dev->config;
|
|
struct i2c_xec_data *data =
|
|
(struct i2c_xec_data *const) (dev->data);
|
|
int ret;
|
|
|
|
data->pending_stop = 0;
|
|
data->slave_attached = false;
|
|
|
|
ret = pinctrl_apply_state(cfg->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (ret != 0) {
|
|
LOG_ERR("XEC I2C pinctrl setup failed (%d)", ret);
|
|
return ret;
|
|
}
|
|
|
|
if (!gpio_is_ready_dt(&cfg->sda_gpio)) {
|
|
LOG_ERR("%s GPIO device is not ready for SDA GPIO", dev->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!gpio_is_ready_dt(&cfg->scl_gpio)) {
|
|
LOG_ERR("%s GPIO device is not ready for SCL GPIO", dev->name);
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Default configuration */
|
|
ret = i2c_xec_configure(dev,
|
|
I2C_MODE_CONTROLLER |
|
|
I2C_SPEED_SET(I2C_SPEED_STANDARD));
|
|
if (ret) {
|
|
LOG_ERR("%s configure failed %d", dev->name, ret);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_TARGET
|
|
const struct i2c_xec_config *config =
|
|
(const struct i2c_xec_config *const) (dev->config);
|
|
|
|
config->irq_config_func();
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
#define I2C_XEC_DEVICE(n) \
|
|
\
|
|
PINCTRL_DT_INST_DEFINE(n); \
|
|
\
|
|
static void i2c_xec_irq_config_func_##n(void); \
|
|
\
|
|
static struct i2c_xec_data i2c_xec_data_##n; \
|
|
static const struct i2c_xec_config i2c_xec_config_##n = { \
|
|
.base_addr = \
|
|
DT_INST_REG_ADDR(n), \
|
|
.port_sel = DT_INST_PROP(n, port_sel), \
|
|
.girq_id = DT_INST_PROP(n, girq), \
|
|
.girq_bit = DT_INST_PROP(n, girq_bit), \
|
|
.pcr_idx = DT_INST_PROP_BY_IDX(n, pcrs, 0), \
|
|
.pcr_bitpos = DT_INST_PROP_BY_IDX(n, pcrs, 1), \
|
|
.sda_gpio = GPIO_DT_SPEC_INST_GET(n, sda_gpios), \
|
|
.scl_gpio = GPIO_DT_SPEC_INST_GET(n, scl_gpios), \
|
|
.irq_config_func = i2c_xec_irq_config_func_##n, \
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \
|
|
}; \
|
|
I2C_DEVICE_DT_INST_DEFINE(n, i2c_xec_init, NULL, \
|
|
&i2c_xec_data_##n, &i2c_xec_config_##n, \
|
|
POST_KERNEL, CONFIG_I2C_INIT_PRIORITY, \
|
|
&i2c_xec_driver_api); \
|
|
\
|
|
static void i2c_xec_irq_config_func_##n(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(n), \
|
|
DT_INST_IRQ(n, priority), \
|
|
i2c_xec_bus_isr, \
|
|
DEVICE_DT_INST_GET(n), 0); \
|
|
irq_enable(DT_INST_IRQN(n)); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_XEC_DEVICE)
|