While running certain peci command, observed when FW attempts to read last byte (Response FCS), PECI host controller returned “Read FIFO” empty. Since “Read FIFO” is empty FW didn’t read the response FCS. Due to this issue, FW getting corrupted response from the PECI controller for all the subsequent PECI commands. To address this issue, FW waits for “Read FIFO” filled up by the PECI controller. Signed-off-by: Diwakar C <diwakar.c@intel.com>
405 lines
9.1 KiB
C
405 lines
9.1 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT microchip_xec_peci
|
|
|
|
#include <errno.h>
|
|
#include <device.h>
|
|
#include <drivers/peci.h>
|
|
#include <soc.h>
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(peci_mchp_xec, CONFIG_PECI_LOG_LEVEL);
|
|
|
|
/* Maximum PECI core clock is the main clock 48Mhz */
|
|
#define MAX_PECI_CORE_CLOCK 48000u
|
|
/* 1 ms */
|
|
#define PECI_RESET_DELAY 1000u
|
|
/* 100 us */
|
|
#define PECI_IDLE_DELAY 100u
|
|
/* 5 ms */
|
|
#define PECI_IDLE_TIMEOUT 50u
|
|
/* Maximum retries */
|
|
#define PECI_TIMEOUT_RETRIES 3u
|
|
/* Maximum read buffer fill wait retries */
|
|
#define PECI_RX_BUF_FILL_WAIT_RETRY 100u
|
|
|
|
/* 10 us */
|
|
#define PECI_IO_DELAY 10
|
|
|
|
#define OPT_BIT_TIME_MSB_OFS 8u
|
|
|
|
#define PECI_FCS_LEN 2
|
|
|
|
struct peci_xec_config {
|
|
PECI_Type *base;
|
|
uint8_t irq_num;
|
|
};
|
|
|
|
struct peci_xec_data {
|
|
struct k_sem tx_lock;
|
|
uint32_t bitrate;
|
|
int timeout_retries;
|
|
};
|
|
|
|
static struct peci_xec_data peci_data;
|
|
|
|
static const struct peci_xec_config peci_xec_config = {
|
|
.base = (PECI_Type *) DT_INST_REG_ADDR(0),
|
|
.irq_num = DT_INST_IRQN(0),
|
|
};
|
|
|
|
static int check_bus_idle(PECI_Type *base)
|
|
{
|
|
uint8_t delay_cnt = PECI_IDLE_TIMEOUT;
|
|
|
|
/* Wait until PECI bus becomes idle.
|
|
* Note that when IDLE bit in the status register changes, HW do not
|
|
* generate an interrupt, so need to poll.
|
|
*/
|
|
while (!(base->STATUS2 & MCHP_PECI_STS2_IDLE)) {
|
|
k_busy_wait(PECI_IDLE_DELAY);
|
|
delay_cnt--;
|
|
|
|
if (!delay_cnt) {
|
|
LOG_WRN("Bus is busy");
|
|
return -EBUSY;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int peci_xec_configure(const struct device *dev, uint32_t bitrate)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
peci_data.bitrate = bitrate;
|
|
PECI_Type *base = peci_xec_config.base;
|
|
uint16_t value;
|
|
|
|
/* Power down PECI interface */
|
|
base->CONTROL = MCHP_PECI_CTRL_PD;
|
|
|
|
/* Adjust bitrate */
|
|
value = MAX_PECI_CORE_CLOCK / bitrate;
|
|
base->OPT_BIT_TIME_LSB = value & MCHP_PECI_OPT_BT_LSB_MASK;
|
|
base->OPT_BIT_TIME_MSB = (value >> OPT_BIT_TIME_MSB_OFS) &
|
|
MCHP_PECI_OPT_BT_MSB_MASK;
|
|
|
|
/* Power up PECI interface */
|
|
base->CONTROL &= ~MCHP_PECI_CTRL_PD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int peci_xec_disable(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
int ret;
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
/* Make sure no transaction is interrupted before disabling the HW */
|
|
ret = check_bus_idle(base);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
NVIC_ClearPendingIRQ(peci_xec_config.irq_num);
|
|
irq_disable(peci_xec_config.irq_num);
|
|
#endif
|
|
base->CONTROL |= MCHP_PECI_CTRL_PD;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int peci_xec_enable(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
base->CONTROL &= ~MCHP_PECI_CTRL_PD;
|
|
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
irq_enable(peci_xec_config.irq_num);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void peci_xec_bus_recovery(const struct device *dev, bool full_reset)
|
|
{
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
LOG_WRN("%s full_reset:%d", __func__, full_reset);
|
|
if (full_reset) {
|
|
base->CONTROL = MCHP_PECI_CTRL_PD | MCHP_PECI_CTRL_RST;
|
|
k_busy_wait(PECI_RESET_DELAY);
|
|
base->CONTROL &= ~MCHP_PECI_CTRL_RST;
|
|
|
|
peci_xec_configure(dev, peci_data.bitrate);
|
|
} else {
|
|
/* Only reset internal FIFOs */
|
|
base->CONTROL |= MCHP_PECI_CTRL_FRST;
|
|
}
|
|
}
|
|
|
|
static int peci_xec_write(const struct device *dev, struct peci_msg *msg)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
int i;
|
|
int ret;
|
|
|
|
struct peci_buf *tx_buf = &msg->tx_buffer;
|
|
struct peci_buf *rx_buf = &msg->rx_buffer;
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
/* Check if FIFO is full */
|
|
if (base->STATUS2 & MCHP_PECI_STS2_WFF) {
|
|
LOG_WRN("%s FIFO is full", __func__);
|
|
return -EIO;
|
|
}
|
|
|
|
base->CONTROL &= ~MCHP_PECI_CTRL_FRST;
|
|
|
|
/* Add PECI transaction header to TX FIFO */
|
|
base->WR_DATA = msg->addr;
|
|
base->WR_DATA = tx_buf->len;
|
|
base->WR_DATA = rx_buf->len;
|
|
|
|
/* Add PECI payload to Tx FIFO only if write length is valid */
|
|
if (tx_buf->len) {
|
|
base->WR_DATA = msg->cmd_code;
|
|
for (i = 0; i < tx_buf->len - 1; i++) {
|
|
if (!(base->STATUS2 & MCHP_PECI_STS2_WFF)) {
|
|
base->WR_DATA = tx_buf->buf[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check bus is idle before starting a new transfer */
|
|
ret = check_bus_idle(base);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
base->CONTROL |= MCHP_PECI_CTRL_TXEN;
|
|
k_busy_wait(PECI_IO_DELAY);
|
|
|
|
/* Wait for transmission to complete */
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
if (k_sem_take(&peci_data.tx_lock, PECI_IO_DELAY * tx_buf->len)) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
#else
|
|
/* In worst case, overall timeout will be 1msec (100 * 10usec) */
|
|
uint8_t wait_timeout_cnt = 100;
|
|
|
|
while (!(base->STATUS1 & MCHP_PECI_STS1_EOF)) {
|
|
k_busy_wait(PECI_IO_DELAY);
|
|
wait_timeout_cnt--;
|
|
if (!wait_timeout_cnt) {
|
|
LOG_WRN("Tx timeout");
|
|
peci_data.timeout_retries++;
|
|
/* Full reset only if multiple consecutive failures */
|
|
if (peci_data.timeout_retries > PECI_TIMEOUT_RETRIES) {
|
|
peci_xec_bus_recovery(dev, true);
|
|
} else {
|
|
peci_xec_bus_recovery(dev, false);
|
|
}
|
|
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
#endif
|
|
peci_data.timeout_retries = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int peci_xec_read(const struct device *dev, struct peci_msg *msg)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
int i;
|
|
int ret;
|
|
uint8_t tx_fcs;
|
|
uint8_t bytes_rcvd;
|
|
uint8_t wait_timeout_cnt;
|
|
struct peci_buf *rx_buf = &msg->rx_buffer;
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
/* Attempt to read data from RX FIFO */
|
|
bytes_rcvd = 0;
|
|
for (i = 0; i < (rx_buf->len + PECI_FCS_LEN); i++) {
|
|
/* Worst case timeout will be 1msec (100 * 10usec) */
|
|
wait_timeout_cnt = PECI_RX_BUF_FILL_WAIT_RETRY;
|
|
/* Wait for read buffer to fill up */
|
|
while (base->STATUS2 & MCHP_PECI_STS2_RFE) {
|
|
k_usleep(PECI_IO_DELAY);
|
|
wait_timeout_cnt--;
|
|
if (!wait_timeout_cnt) {
|
|
LOG_WRN("Rx buffer empty");
|
|
return -ETIMEDOUT;
|
|
}
|
|
}
|
|
|
|
if (i == 0) {
|
|
/* Get write block FCS just for debug */
|
|
tx_fcs = base->RD_DATA;
|
|
LOG_DBG("TX FCS %x", tx_fcs);
|
|
} else if (i == (rx_buf->len + 1)) {
|
|
/* Get read block FCS, but don't count it */
|
|
rx_buf->buf[i-1] = base->RD_DATA;
|
|
} else {
|
|
/* Get response */
|
|
rx_buf->buf[i-1] = base->RD_DATA;
|
|
bytes_rcvd++;
|
|
}
|
|
}
|
|
|
|
/* Check if transaction is as expected */
|
|
if (rx_buf->len != bytes_rcvd) {
|
|
LOG_INF("Incomplete %x vs %x", bytes_rcvd, rx_buf->len);
|
|
}
|
|
|
|
/* Once write-read transaction is complete, ensure bus is idle
|
|
* before resetting the internal FIFOs
|
|
*/
|
|
ret = check_bus_idle(base);
|
|
if (ret) {
|
|
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int peci_xec_transfer(const struct device *dev, struct peci_msg *msg)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
int ret;
|
|
PECI_Type *base = peci_xec_config.base;
|
|
uint8_t err_val;
|
|
|
|
ret = peci_xec_write(dev, msg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
/* If a PECI transmission is successful, it may or not involve
|
|
* a read operation, check if transaction expects a response
|
|
*/
|
|
if (msg->rx_buffer.len) {
|
|
ret = peci_xec_read(dev, msg);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Cleanup */
|
|
if (base->STATUS1 & MCHP_PECI_STS1_EOF) {
|
|
base->STATUS1 |= MCHP_PECI_STS1_EOF;
|
|
}
|
|
|
|
/* Check for error conditions and perform bus recovery if necessary */
|
|
err_val = base->ERROR;
|
|
if (err_val) {
|
|
if (err_val & MCHP_PECI_ERR_RDOV) {
|
|
LOG_ERR("Read buffer is not empty");
|
|
}
|
|
|
|
if (err_val & MCHP_PECI_ERR_WRUN) {
|
|
LOG_ERR("Write buffer is not empty");
|
|
}
|
|
|
|
if (err_val & MCHP_PECI_ERR_BERR) {
|
|
LOG_ERR("PECI bus error");
|
|
}
|
|
|
|
LOG_DBG("PECI err %x", err_val);
|
|
LOG_DBG("PECI sts1 %x", base->STATUS1);
|
|
LOG_DBG("PECI sts2 %x", base->STATUS2);
|
|
|
|
/* ERROR is a clear-on-write register, need to clear errors
|
|
* occurring at the end of a transaction. A temp variable is
|
|
* used to overcome complaints by the static code analyzer
|
|
*/
|
|
base->ERROR = err_val;
|
|
peci_xec_bus_recovery(dev, false);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
static void peci_xec_isr(const void *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
PECI_Type *base = peci_xec_config.base;
|
|
|
|
MCHP_GIRQ_SRC(MCHP_PECI_GIRQ) = MCHP_PECI_GIRQ_VAL;
|
|
|
|
if (base->ERROR) {
|
|
base->ERROR = base->ERROR;
|
|
}
|
|
|
|
if (base->STATUS2 & MCHP_PECI_STS2_WFE) {
|
|
LOG_WRN("TX FIFO empty ST2:%x", base->STATUS2);
|
|
k_sem_give(&peci_data.tx_lock);
|
|
}
|
|
|
|
if (base->STATUS2 & MCHP_PECI_STS2_RFE) {
|
|
LOG_WRN("RX FIFO full ST2:%x", base->STATUS2);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static const struct peci_driver_api peci_xec_driver_api = {
|
|
.config = peci_xec_configure,
|
|
.enable = peci_xec_enable,
|
|
.disable = peci_xec_disable,
|
|
.transfer = peci_xec_transfer,
|
|
};
|
|
|
|
static int peci_xec_init(const struct device *dev)
|
|
{
|
|
ARG_UNUSED(dev);
|
|
|
|
PECI_Type *base = peci_xec_config.base;
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
k_sem_init(&peci_data.tx_lock, 0, 1);
|
|
#endif
|
|
|
|
/* Reset PECI interface */
|
|
base->CONTROL |= MCHP_PECI_CTRL_RST;
|
|
k_busy_wait(PECI_RESET_DELAY);
|
|
base->CONTROL &= ~MCHP_PECI_CTRL_RST;
|
|
|
|
#ifdef CONFIG_PECI_INTERRUPT_DRIVEN
|
|
/* Enable interrupt for errors */
|
|
base->INT_EN1 = (MCHP_PECI_IEN1_EREN | MCHP_PECI_IEN1_EIEN);
|
|
|
|
/* Enable interrupt for Tx FIFO is empty */
|
|
base->INT_EN2 |= MCHP_PECI_IEN2_ENWFE;
|
|
/* Enable interrupt for Rx FIFO is full */
|
|
base->INT_EN2 |= MCHP_PECI_IEN2_ENRFF;
|
|
|
|
base->CONTROL |= MCHP_PECI_CTRL_MIEN;
|
|
|
|
/* Direct NVIC */
|
|
IRQ_CONNECT(peci_xec_config.irq_num,
|
|
DT_INST_IRQ(0, priority),
|
|
peci_xec_isr, NULL, 0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
DEVICE_DT_INST_DEFINE(0,
|
|
&peci_xec_init,
|
|
device_pm_control_nop,
|
|
NULL, NULL,
|
|
POST_KERNEL, CONFIG_PECI_INIT_PRIORITY,
|
|
&peci_xec_driver_api);
|