88ca215eed
Updates the API and types to match updated I2C terminology. Replaces master with controller and slave with target. Updates all drivers to match the changed macros, types, and API signatures. Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
1117 lines
29 KiB
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/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>
|
|
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 and clr others */
|
|
regs->CFG = MCHP_I2C_SMB_CFG_RESET;
|
|
k_busy_wait(RESET_WAIT_US);
|
|
/* clear reset, set filter enable, select port */
|
|
regs->CFG = 0;
|
|
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 */
|
|
regs->CFG = MCHP_I2C_SMB_CFG_RESET;
|
|
k_busy_wait(RESET_WAIT_US);
|
|
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)
|