zephyr/drivers/i2c/i2c_mchp_xec_v2.c

1117 lines
29 KiB
C

/*
* Copyright (c) 2019 Intel Corporation
* Copyright (c) 2021 Microchip Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_xec_i2c_v2
#include <zephyr/kernel.h>
#include <soc.h>
#include <errno.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/mchp_xec_clock_control.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/interrupt_controller/intc_mchp_xec_ecia.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/sys_io.h>
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
LOG_MODULE_REGISTER(i2c_mchp, CONFIG_I2C_LOG_LEVEL);
#include "i2c-priv.h"
#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
/* I2C timeout is 10 ms (WAIT_INTERVAL * WAIT_COUNT) */
#define WAIT_INTERVAL 50
#define WAIT_COUNT 200
#define STOP_WAIT_COUNT 500
#define PIN_CFG_WAIT 50
/* I2C Read/Write bit pos */
#define I2C_READ_WRITE_POS 0
/* I2C recover SCL low retries */
#define I2C_RECOVER_SCL_LOW_RETRIES 10
/* I2C recover SDA low retries */
#define I2C_RECOVER_SDA_LOW_RETRIES 3
/* I2C recovery bit bang delay */
#define I2C_RECOVER_BB_DELAY_US 5
/* I2C recovery SCL sample delay */
#define I2C_RECOVER_SCL_DELAY_US 50
/* I2C SCL and SDA lines(signals) */
#define I2C_LINES_SCL_HI BIT(SOC_I2C_SCL_POS)
#define I2C_LINES_SDA_HI BIT(SOC_I2C_SDA_POS)
#define I2C_LINES_BOTH_HI (I2C_LINES_SCL_HI | I2C_LINES_SDA_HI)
#define I2C_START 0U
#define I2C_RPT_START 1U
#define I2C_ENI_DIS 0U
#define I2C_ENI_EN 1U
#define I2C_WAIT_PIN_DEASSERT 0U
#define I2C_WAIT_PIN_ASSERT 1U
#define I2C_XEC_CTRL_WR_DLY 8
#define I2C_XEC_STATE_STOPPED 1U
#define I2C_XEC_STATE_OPEN 2U
#define I2C_XEC_OK 0
#define I2C_XEC_ERR_LAB 1
#define I2C_XEC_ERR_BUS 2
#define I2C_XEC_ERR_TMOUT 3
#define XEC_GPIO_CTRL_BASE DT_REG_ADDR(DT_NODELABEL(gpio_000_036))
struct xec_speed_cfg {
uint32_t bus_clk;
uint32_t data_timing;
uint32_t start_hold_time;
uint32_t idle_scale;
uint32_t timeout_scale;
};
struct i2c_xec_config {
uint32_t port_sel;
uint32_t base_addr;
uint32_t clock_freq;
uint8_t girq;
uint8_t girq_pos;
uint8_t pcr_idx;
uint8_t pcr_bitpos;
const struct pinctrl_dev_config *pcfg;
void (*irq_config_func)(void);
};
struct i2c_xec_data {
uint8_t state;
uint8_t read_discard;
uint8_t speed_id;
struct i2c_target_config *target_cfg;
bool target_attached;
bool target_read;
uint32_t i2c_compl;
uint8_t i2c_ctrl;
uint8_t i2c_addr;
uint8_t i2c_status;
};
/* 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,
.idle_scale = 0x01FC01ED,
.timeout_scale = 0x4B9CC2C7,
},
[SPEED_400KHZ_BUS] = {
.bus_clk = 0x00000F17,
.data_timing = 0x040A0A06,
.start_hold_time = 0x0000000A,
.idle_scale = 0x01000050,
.timeout_scale = 0x159CC2C7,
},
[SPEED_1MHZ_BUS] = {
.bus_clk = 0x00000509,
.data_timing = 0x04060601,
.start_hold_time = 0x00000006,
.idle_scale = 0x10000050,
.timeout_scale = 0x089CC2C7,
},
};
static void i2c_ctl_wr(const struct device *dev, uint8_t ctrl)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
data->i2c_ctrl = ctrl;
regs->CTRLSTS = ctrl;
for (int i = 0; i < I2C_XEC_CTRL_WR_DLY; i++) {
regs->BLKID = ctrl;
}
}
static int i2c_xec_reset_config(const struct device *dev);
static int wait_bus_free(const struct device *dev, uint32_t nwait)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
uint32_t count = nwait;
uint8_t sts = 0;
while (count--) {
sts = regs->CTRLSTS;
data->i2c_status = sts;
if (sts & MCHP_I2C_SMB_STS_NBB) {
break; /* bus is free */
}
k_busy_wait(WAIT_INTERVAL);
}
/* NBB -> 1 not busy can occur for STOP, BER, or LAB */
if (sts == (MCHP_I2C_SMB_STS_PIN | MCHP_I2C_SMB_STS_NBB)) {
/* No service requested(PIN=1), NotBusy(NBB=1), and no errors */
return 0;
}
if (sts & MCHP_I2C_SMB_STS_BER) {
return I2C_XEC_ERR_BUS;
}
if (sts & MCHP_I2C_SMB_STS_LAB) {
return I2C_XEC_ERR_LAB;
}
return I2C_XEC_ERR_TMOUT;
}
/*
* returns state of I2C SCL and SDA lines.
* b[0] = SCL, b[1] = SDA
* Call soc specific routine to read GPIO pad input.
* Why? We can get the pins from our PINCTRL info but
* we do not know which pin is I2C clock and which pin
* is I2C data. There's no ordering in PINCTRL DT unless
* we impose an order.
*/
static uint32_t get_lines(const struct device *dev)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
uint8_t port = regs->CFG & MCHP_I2C_SMB_CFG_PORT_SEL_MASK;
uint32_t lines = 0u;
soc_i2c_port_lines_get(port, &lines);
return lines;
}
static int i2c_xec_reset_config(const struct device *dev)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
data->state = I2C_XEC_STATE_STOPPED;
data->read_discard = 0;
/* Assert RESET */
z_mchp_xec_pcr_periph_reset(cfg->pcr_idx, cfg->pcr_bitpos);
regs->CFG = MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO;
mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos);
/* PIN=1 to clear all status except NBB and synchronize */
i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN);
/*
* Controller implements two peripheral addresses for itself.
* It always monitors whether an external controller issues START
* plus target address. We should write valid peripheral addresses
* that do not match any peripheral on the bus.
* An alternative is to use the default 0 value which is the
* general call address and disable the general call match
* enable in the configuration register.
*/
regs->OWN_ADDR = EC_OWN_I2C_ADDR | (EC_OWN_I2C_ADDR << 8);
#ifdef CONFIG_I2C_TARGET
if (data->target_cfg) {
regs->OWN_ADDR = data->target_cfg->address;
}
#endif
/* Port number and filter enable MUST be written before enabling */
regs->CFG |= BIT(14); /* disable general call */
regs->CFG |= MCHP_I2C_SMB_CFG_FEN;
regs->CFG |= (cfg->port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK);
/*
* Before enabling the controller program the desired bus clock,
* repeated start hold time, data timing, and timeout scaling
* registers.
*/
regs->BUSCLK = xec_cfg_params[data->speed_id].bus_clk;
regs->RSHTM = xec_cfg_params[data->speed_id].start_hold_time;
regs->DATATM = xec_cfg_params[data->speed_id].data_timing;
regs->TMOUTSC = xec_cfg_params[data->speed_id].timeout_scale;
regs->IDLSC = xec_cfg_params[data->speed_id].idle_scale;
/*
* PIN=1 clears all status except NBB
* ESO=1 enables output drivers
* ACK=1 enable ACK generation when data/address is clocked in.
*/
i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN |
MCHP_I2C_SMB_CTRL_ESO |
MCHP_I2C_SMB_CTRL_ACK);
/* Enable controller */
regs->CFG |= MCHP_I2C_SMB_CFG_ENAB;
k_busy_wait(RESET_WAIT_US);
/* wait for NBB=1, BER, LAB, or timeout */
int rc = wait_bus_free(dev, WAIT_COUNT);
return rc;
}
/*
* If SCL is low sample I2C_RECOVER_SCL_LOW_RETRIES times with a 5 us delay
* between samples. If SCL remains low then return -EBUSY
* If SCL is High and SDA is low then loop up to I2C_RECOVER_SDA_LOW_RETRIES
* times driving the pins:
* Drive SCL high
* delay I2C_RECOVER_BB_DELAY_US
* Generate 9 clock pulses on SCL checking SDA before each falling edge of SCL
* If SDA goes high exit clock loop else to all 9 clocks
* Drive SDA low, delay 5 us, release SDA, delay 5 us
* Both lines are high then exit SDA recovery loop
* Both lines should not be driven
* Check both lines: if any bad return error else return success
* NOTE 1: Bit-bang mode uses a HW MUX to switch the lines away from the I2C
* controller logic to BB logic.
* NOTE 2: Bit-bang mode requires HW timeouts to be disabled.
* NOTE 3: Bit-bang mode requires the controller's configuration enable bit
* to be set.
* NOTE 4: The controller must be reset after using bit-bang mode.
*/
static int i2c_xec_recover_bus(const struct device *dev)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
int i, j, ret;
LOG_ERR("I2C attempt bus recovery\n");
/* reset controller to a known state */
z_mchp_xec_pcr_periph_reset(cfg->pcr_idx, cfg->pcr_bitpos);
regs->CFG = BIT(14) | MCHP_I2C_SMB_CFG_FEN |
(cfg->port_sel & MCHP_I2C_SMB_CFG_PORT_SEL_MASK);
regs->CFG |= MCHP_I2C_SMB_CFG_FLUSH_SXBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_SRBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_MXBUF_WO |
MCHP_I2C_SMB_CFG_FLUSH_MRBUF_WO;
regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN;
regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL |
MCHP_I2C_SMB_BB_DAT;
regs->CFG |= MCHP_I2C_SMB_CFG_ENAB;
if (!(regs->BBCTRL & MCHP_I2C_SMB_BB_CLKI_RO)) {
for (i = 0;; i++) {
if (i >= I2C_RECOVER_SCL_LOW_RETRIES) {
ret = -EBUSY;
goto recov_exit;
}
k_busy_wait(I2C_RECOVER_SCL_DELAY_US);
if (regs->BBCTRL & MCHP_I2C_SMB_BB_CLKI_RO) {
break; /* SCL went High */
}
}
}
if (regs->BBCTRL & MCHP_I2C_SMB_BB_DATI_RO) {
ret = 0;
goto recov_exit;
}
ret = -EBUSY;
/* SDA recovery */
for (i = 0; i < I2C_RECOVER_SDA_LOW_RETRIES; i++) {
/* SCL output mode and tri-stated */
regs->BBCTRL = MCHP_I2C_SMB_BB_EN |
MCHP_I2C_SMB_BB_SCL_DIR_OUT |
MCHP_I2C_SMB_BB_CL |
MCHP_I2C_SMB_BB_DAT;
k_busy_wait(I2C_RECOVER_BB_DELAY_US);
for (j = 0; j < 9; j++) {
if (regs->BBCTRL & MCHP_I2C_SMB_BB_DATI_RO) {
break;
}
/* drive SCL low */
regs->BBCTRL = MCHP_I2C_SMB_BB_EN |
MCHP_I2C_SMB_BB_SCL_DIR_OUT |
MCHP_I2C_SMB_BB_DAT;
k_busy_wait(I2C_RECOVER_BB_DELAY_US);
/* release SCL: pulled high by external pull-up */
regs->BBCTRL = MCHP_I2C_SMB_BB_EN |
MCHP_I2C_SMB_BB_SCL_DIR_OUT |
MCHP_I2C_SMB_BB_CL |
MCHP_I2C_SMB_BB_DAT;
k_busy_wait(I2C_RECOVER_BB_DELAY_US);
}
/* SCL is High. Produce rising edge on SCL for STOP */
regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL |
MCHP_I2C_SMB_BB_SDA_DIR_OUT; /* drive low */
k_busy_wait(I2C_RECOVER_BB_DELAY_US);
regs->BBCTRL = MCHP_I2C_SMB_BB_EN | MCHP_I2C_SMB_BB_CL |
MCHP_I2C_SMB_BB_DAT; /* release SCL */
k_busy_wait(I2C_RECOVER_BB_DELAY_US);
/* check if SCL and SDA are both high */
uint8_t bb = regs->BBCTRL &
(MCHP_I2C_SMB_BB_CLKI_RO | MCHP_I2C_SMB_BB_DATI_RO);
if (bb == (MCHP_I2C_SMB_BB_CLKI_RO | MCHP_I2C_SMB_BB_DATI_RO)) {
ret = 0; /* successful recovery */
goto recov_exit;
}
}
recov_exit:
/* BB mode disable reconnects SCL and SDA to I2C logic. */
regs->BBCTRL = 0;
regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN; /* clear status */
i2c_xec_reset_config(dev); /* reset controller */
return ret;
}
#ifdef CONFIG_I2C_TARGET
/*
* Restart I2C controller as target for ACK of address match.
* Setting PIN clears all status in I2C.Status register except NBB.
*/
static void restart_target(const struct device *dev)
{
i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO |
MCHP_I2C_SMB_CTRL_ACK | MCHP_I2C_SMB_CTRL_ENI);
}
/*
* Configure I2C controller acting as target to NACK the next received byte.
* NOTE: Firmware must re-enable ACK generation before the start of the next
* transaction otherwise the controller will NACK its target addresses.
*/
static void target_config_for_nack(const struct device *dev)
{
i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO |
MCHP_I2C_SMB_CTRL_ENI);
}
#endif
static int wait_pin(const struct device *dev, bool pin_assert, uint32_t nwait)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
for (;;) {
k_busy_wait(WAIT_INTERVAL);
data->i2c_compl = regs->COMPL;
data->i2c_status = regs->CTRLSTS;
if (data->i2c_status & MCHP_I2C_SMB_STS_BER) {
return I2C_XEC_ERR_BUS;
}
if (data->i2c_status & MCHP_I2C_SMB_STS_LAB) {
return I2C_XEC_ERR_LAB;
}
if (!(data->i2c_status & MCHP_I2C_SMB_STS_PIN)) {
if (pin_assert) {
return 0;
}
} else if (!pin_assert) {
return 0;
}
if (nwait) {
--nwait;
} else {
break;
}
}
return I2C_XEC_ERR_TMOUT;
}
static int gen_start(const struct device *dev, uint8_t addr8,
bool is_repeated)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
uint8_t ctrl = MCHP_I2C_SMB_CTRL_ESO | MCHP_I2C_SMB_CTRL_STA |
MCHP_I2C_SMB_CTRL_ACK;
data->i2c_addr = addr8;
if (is_repeated) {
i2c_ctl_wr(dev, ctrl);
regs->I2CDATA = addr8;
} else {
ctrl |= MCHP_I2C_SMB_CTRL_PIN;
regs->I2CDATA = addr8;
i2c_ctl_wr(dev, ctrl);
}
return 0;
}
static int gen_stop(const struct device *dev)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
uint8_t ctrl = MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO |
MCHP_I2C_SMB_CTRL_STO | MCHP_I2C_SMB_CTRL_ACK;
data->i2c_ctrl = ctrl;
regs->CTRLSTS = ctrl;
return 0;
}
static int do_stop(const struct device *dev, uint32_t nwait)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
int ret;
data->state = I2C_XEC_STATE_STOPPED;
data->read_discard = 0;
gen_stop(dev);
ret = wait_bus_free(dev, nwait);
if (ret) {
uint32_t lines = get_lines(dev);
if (lines != I2C_LINES_BOTH_HI) {
i2c_xec_recover_bus(dev);
} else {
ret = i2c_xec_reset_config(dev);
}
}
if (ret == 0) {
/* stop success: prepare for next transaction */
regs->CTRLSTS = MCHP_I2C_SMB_CTRL_PIN | MCHP_I2C_SMB_CTRL_ESO |
MCHP_I2C_SMB_CTRL_ACK;
}
return ret;
}
static int do_start(const struct device *dev, uint8_t addr8, bool is_repeated)
{
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
int ret;
gen_start(dev, addr8, is_repeated);
ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT);
if (ret) {
i2c_xec_reset_config(dev);
return ret;
}
/* PIN 1->0: check for NACK */
if (data->i2c_status & MCHP_I2C_SMB_STS_LRB_AD0) {
gen_stop(dev);
ret = wait_bus_free(dev, WAIT_COUNT);
if (ret) {
i2c_xec_reset_config(dev);
}
return -EIO;
}
return 0;
}
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;
}
int ret = i2c_xec_reset_config(dev);
return ret;
}
/* I2C Controller transmit: polling implementation */
static int ctrl_tx(const struct device *dev, struct i2c_msg *msg, uint16_t addr)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
int ret = 0;
uint8_t mflags = msg->flags;
uint8_t addr8 = (uint8_t)((addr & 0x7FU) << 1);
if (data->state == I2C_XEC_STATE_STOPPED) {
data->i2c_addr = addr8;
/* Is bus free and controller ready? */
ret = wait_bus_free(dev, WAIT_COUNT);
if (ret) {
ret = i2c_xec_recover_bus(dev);
if (ret) {
return ret;
}
}
ret = do_start(dev, addr8, I2C_START);
if (ret) {
return ret;
}
data->state = I2C_XEC_STATE_OPEN;
} else if (mflags & I2C_MSG_RESTART) {
data->i2c_addr = addr8;
ret = do_start(dev, addr8, I2C_RPT_START);
if (ret) {
return ret;
}
}
for (size_t n = 0; n < msg->len; n++) {
regs->I2CDATA = msg->buf[n];
ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT);
if (ret) {
i2c_xec_reset_config(dev);
return ret;
}
if (data->i2c_status & MCHP_I2C_SMB_STS_LRB_AD0) { /* NACK? */
do_stop(dev, STOP_WAIT_COUNT);
return -EIO;
}
}
if (mflags & I2C_MSG_STOP) {
ret = do_stop(dev, STOP_WAIT_COUNT);
}
return ret;
}
/*
* I2C Controller receive: polling implementation
* Transmitting a target address with BIT[0] == 1 causes the controller
* to enter controller-read mode where every read of I2CDATA generates
* clocks for the next byte. When we generate START or Repeated-START
* and transmit an address the address is also clocked in during
* address transmission. The address must read and discarded.
* Read of I2CDATA returns data currently in I2C read buffer, sets
* I2CSTATUS.PIN = 1, and !!generates clocks for the next
* byte!!
* For this controller to NACK the last byte we must clear the
* I2C CTRL register ACK bit BEFORE reading the next to last
* byte. Before reading the last byte we configure I2C CTRL to generate a STOP
* and then read the last byte from I2 DATA.
* When controller is in STOP mode it will not generate clocks when I2CDATA is
* read. UGLY HW DESIGN.
* We will NOT attempt to follow this HW design for Controller read except
* when all information is available: STOP message flag set AND number of
* bytes to read including dummy is >= 2. General usage can result in the
* controller not NACK'ing the last byte.
*/
static int ctrl_rx(const struct device *dev, struct i2c_msg *msg, uint16_t addr)
{
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data =
(struct i2c_xec_data *const) (dev->data);
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
int ret = 0;
size_t data_len = msg->len;
uint8_t mflags = msg->flags;
uint8_t addr8 = (uint8_t)(((addr & 0x7FU) << 1) | BIT(0));
uint8_t temp = 0;
if (data->state == I2C_XEC_STATE_STOPPED) {
data->i2c_addr = addr8;
/* Is bus free and controller ready? */
ret = wait_bus_free(dev, WAIT_COUNT);
if (ret) {
i2c_xec_reset_config(dev);
return ret;
}
ret = do_start(dev, addr8, I2C_START);
if (ret) {
return ret;
}
data->state = I2C_XEC_STATE_OPEN;
/* controller clocked address into I2CDATA */
data->read_discard = 1U;
} else if (mflags & I2C_MSG_RESTART) {
data->i2c_addr = addr8;
ret = do_start(dev, addr8, I2C_RPT_START);
if (ret) {
return ret;
}
/* controller clocked address into I2CDATA */
data->read_discard = 1U;
}
if (!data_len) { /* requested message length is 0 */
ret = 0;
if (mflags & I2C_MSG_STOP) {
data->state = I2C_XEC_STATE_STOPPED;
data->read_discard = 0;
ret = do_stop(dev, STOP_WAIT_COUNT);
}
return ret;
}
if (data->read_discard) {
data_len++;
}
uint8_t *p8 = &msg->buf[0];
while (data_len) {
if (mflags & I2C_MSG_STOP) {
if (data_len == 2) {
i2c_ctl_wr(dev, MCHP_I2C_SMB_CTRL_ESO);
} else if (data_len == 1) {
break;
}
}
temp = regs->I2CDATA; /* generates clocks */
if (data->read_discard) {
data->read_discard = 0;
} else {
*p8++ = temp;
}
ret = wait_pin(dev, I2C_WAIT_PIN_ASSERT, WAIT_COUNT);
if (ret) {
i2c_xec_reset_config(dev);
return ret;
}
data_len--;
}
if (mflags & I2C_MSG_STOP) {
data->state = I2C_XEC_STATE_STOPPED;
data->read_discard = 0;
ret = do_stop(dev, STOP_WAIT_COUNT);
if (ret == 0) {
*p8 = regs->I2CDATA;
}
}
return ret;
}
static int i2c_xec_transfer(const struct device *dev, struct i2c_msg *msgs,
uint8_t num_msgs, uint16_t addr)
{
struct i2c_xec_data *data = dev->data;
int ret = 0;
#ifdef CONFIG_I2C_TARGET
if (data->target_attached) {
LOG_ERR("Device is registered as target");
return -EBUSY;
}
#endif
for (uint8_t i = 0; i < num_msgs; i++) {
struct i2c_msg *m = &msgs[i];
if ((m->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
ret = ctrl_tx(dev, m, addr);
} else {
ret = ctrl_rx(dev, m, addr);
}
if (ret) {
data->state = I2C_XEC_STATE_STOPPED;
data->read_discard = 0;
LOG_ERR("i2x_xfr: flags: %x error: %d", m->flags, ret);
break;
}
}
return ret;
}
static void i2c_xec_bus_isr(const struct device *dev)
{
#ifdef CONFIG_I2C_TARGET
const struct i2c_xec_config *cfg =
(const struct i2c_xec_config *const) (dev->config);
struct i2c_xec_data *data = dev->data;
const struct i2c_target_callbacks *target_cb =
data->target_cfg->callbacks;
struct i2c_smb_regs *regs = (struct i2c_smb_regs *)cfg->base_addr;
int ret;
uint32_t status;
uint32_t compl_status;
uint8_t val;
uint8_t dummy = 0U;
/* Get current status */
status = regs->CTRLSTS;
compl_status = regs->COMPL & MCHP_I2C_SMB_CMPL_RW1C_MASK;
/* Idle interrupt enabled and active? */
if ((regs->CFG & MCHP_I2C_SMB_CFG_ENIDI) &&
(compl_status & MCHP_I2C_SMB_CMPL_IDLE_RWC)) {
regs->CFG &= ~MCHP_I2C_SMB_CFG_ENIDI;
if (status & MCHP_I2C_SMB_STS_NBB) {
restart_target(dev);
goto clear_iag;
}
}
if (!data->target_attached) {
goto clear_iag;
}
/* Bus Error */
if (status & MCHP_I2C_SMB_STS_BER) {
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
}
restart_target(dev);
goto clear_iag;
}
/* External stop */
if (status & MCHP_I2C_SMB_STS_EXT_STOP) {
if (target_cb->stop) {
target_cb->stop(data->target_cfg);
}
restart_target(dev);
goto clear_iag;
}
/* Address byte handling */
if (status & MCHP_I2C_SMB_STS_AAS) {
if (status & MCHP_I2C_SMB_STS_PIN) {
goto clear_iag;
}
uint8_t rx_data = regs->I2CDATA;
if (rx_data & BIT(I2C_READ_WRITE_POS)) {
/* target transmitter mode */
data->target_read = true;
val = dummy;
if (target_cb->read_requested) {
target_cb->read_requested(
data->target_cfg, &val);
/* Application target transmit handler
* does not have data to send. In
* target transmit mode the external
* Controller is ACK's data we send.
* All we can do is keep sending dummy
* data. We assume read_requested does
* not modify the value pointed to by val
* if it has not data(returns error).
*/
}
/*
* Writing I2CData causes this HW to release SCL
* ending clock stretching. The external Controller
* senses SCL released and begins generating clocks
* and capturing data driven by this controller
* on SDA. External Controller ACK's data until it
* wants no more then it will NACK.
*/
regs->I2CDATA = val;
goto clear_iag; /* Exit ISR */
} else {
/* target receiver mode */
data->target_read = false;
if (target_cb->write_requested) {
ret = target_cb->write_requested(
data->target_cfg);
if (ret) {
/*
* Application handler can't accept
* data. Configure HW to NACK next
* data transmitted by external
* Controller.
* !!! TODO We must re-program our HW
* for address ACK before next
* transaction is begun !!!
*/
target_config_for_nack(dev);
}
}
goto clear_iag; /* Exit ISR */
}
}
if (data->target_read) { /* Target transmitter mode */
/* Master has Nacked, then just write a dummy byte */
status = regs->CTRLSTS;
if (status & MCHP_I2C_SMB_STS_LRB_AD0) {
/*
* ISSUE: HW will not detect external STOP in
* target transmit mode. Enable IDLE interrupt
* to catch PIN 0 -> 1 and NBB 0 -> 1.
*/
regs->CFG |= MCHP_I2C_SMB_CFG_ENIDI;
/*
* dummy write causes this controller's PIN status
* to de-assert 0 -> 1. Data is not transmitted.
* SCL is not driven low by this controller.
*/
regs->I2CDATA = dummy;
status = regs->CTRLSTS;
} else {
val = dummy;
if (target_cb->read_processed) {
target_cb->read_processed(
data->target_cfg, &val);
}
regs->I2CDATA = val;
}
} else { /* target receiver mode */
/*
* Reading the I2CData register causes this target to release
* SCL. The external Controller senses SCL released generates
* clocks for transmitting the next data byte.
* Reading I2C Data register causes PIN status 0 -> 1.
*/
val = regs->I2CDATA;
if (target_cb->write_received) {
/*
* Call back returns error if we should NACK
* next byte.
*/
ret = target_cb->write_received(data->target_cfg, val);
if (ret) {
/*
* Configure HW to NACK next byte. It will not
* generate clocks for another byte of data
*/
target_config_for_nack(dev);
}
}
}
clear_iag:
regs->COMPL = compl_status;
mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos);
#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;
int ret;
if (!config) {
return -EINVAL;
}
if (data->target_attached) {
return -EBUSY;
}
/* Wait for any outstanding transactions to complete so that
* the bus is free
*/
ret = wait_bus_free(dev, WAIT_COUNT);
if (ret) {
return ret;
}
data->target_cfg = config;
ret = i2c_xec_reset_config(dev);
if (ret) {
return ret;
}
restart_target(dev);
data->target_attached = true;
/* Clear before enabling girq bit */
mchp_xec_ecia_girq_src_clr(cfg->girq, cfg->girq_pos);
mchp_xec_ecia_girq_src_en(cfg->girq, cfg->girq_pos);
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->target_attached) {
return -EINVAL;
}
data->target_cfg = NULL;
data->target_attached = false;
mchp_xec_ecia_girq_src_dis(cfg->girq, cfg->girq_pos);
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;
uint32_t bitrate_cfg;
data->state = I2C_XEC_STATE_STOPPED;
data->target_cfg = NULL;
data->target_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;
}
bitrate_cfg = i2c_map_dt_bitrate(cfg->clock_freq);
if (!bitrate_cfg) {
return -EINVAL;
}
/* Default configuration */
ret = i2c_xec_configure(dev, I2C_MODE_CONTROLLER | bitrate_cfg);
if (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), \
.clock_freq = DT_INST_PROP(n, clock_frequency), \
.girq = DT_INST_PROP_BY_IDX(n, girqs, 0), \
.girq_pos = DT_INST_PROP_BY_IDX(n, girqs, 1), \
.pcr_idx = DT_INST_PROP_BY_IDX(n, pcrs, 0), \
.pcr_bitpos = DT_INST_PROP_BY_IDX(n, pcrs, 1), \
.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)