You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1119 lines
29 KiB
1119 lines
29 KiB
/* |
|
* 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 DEVICE_API(i2c, 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 |
|
#ifdef CONFIG_I2C_RTIO |
|
.iodev_submit = i2c_iodev_submit_fallback, |
|
#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)
|
|
|