diff --git a/drivers/peci/CMakeLists.txt b/drivers/peci/CMakeLists.txt index b09185cd007..1a1313194ae 100644 --- a/drivers/peci/CMakeLists.txt +++ b/drivers/peci/CMakeLists.txt @@ -1,3 +1,5 @@ # SPDX-License-Identifier: Apache-2.0 -zephyr_library() \ No newline at end of file +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_PECI_XEC peci_mchp_xec.c) diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig index f241687ffd9..ebaedfaa217 100644 --- a/drivers/peci/Kconfig +++ b/drivers/peci/Kconfig @@ -10,6 +10,8 @@ menuconfig PECI if PECI +source "drivers/peci/Kconfig.xec" + module = PECI module-str = peci source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/peci/Kconfig.xec b/drivers/peci/Kconfig.xec new file mode 100644 index 00000000000..7e6e49d5340 --- /dev/null +++ b/drivers/peci/Kconfig.xec @@ -0,0 +1,10 @@ +# Microchip XEC PECI configuration options + +# Copyright (c) 2020 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +config PECI_XEC + bool "XEC Microchip PECI driver" + depends on SOC_FAMILY_MEC + help + Enable the Microchip XEC PECI IO driver. diff --git a/drivers/peci/peci_mchp_xec.c b/drivers/peci/peci_mchp_xec.c new file mode 100644 index 00000000000..1b009eec9d3 --- /dev/null +++ b/drivers/peci/peci_mchp_xec.c @@ -0,0 +1,355 @@ +/* + * Copyright (c) 2020 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +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 +/* 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; + u8_t irq_num; +}; + +struct peci_xec_data { + struct k_sem tx_lock; +}; + +#ifdef CONFIG_PECI_INTERRUPT_DRIVEN +static struct peci_xec_data peci_data; +#endif + +static const struct peci_xec_config peci_xec_config = { + .base = (PECI_Type *) DT_INST_0_MICROCHIP_XEC_PECI_BASE_ADDRESS, + .irq_num = DT_INST_0_MICROCHIP_XEC_PECI_IRQ_0, +}; + +static int check_bus_idle(PECI_Type *base) +{ + u8_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\n"); + return -EBUSY; + } + } + return 0; +} + +static int peci_xec_configure(struct device *dev, u32_t bitrate) +{ + ARG_UNUSED(dev); + + PECI_Type *base = peci_xec_config.base; + u16_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(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(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 int peci_xec_write(struct device *dev, struct peci_msg *msg) +{ + ARG_UNUSED(dev); + int i; + int ret; + +#ifndef CONFIG_PECI_INTERRUPT_DRIVEN + u8_t wait_timeout; +#endif + 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\n", __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; + + /* PING command doesn't require opcode to be sent */ + if (msg->cmd_code != PECI_CMD_PING) { + base->WR_DATA = msg->cmd_code; + } + + /* Add PECI payload data to FIFO */ + 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 + wait_timeout = tx_buf->len; + while (!(base->STATUS1 & MCHP_PECI_STS1_EOF)) { + k_busy_wait(PECI_IO_DELAY); + + if (!wait_timeout) { + LOG_WRN("Tx timeout\n"); + base->CONTROL = MCHP_PECI_CTRL_FRST; + return -ETIMEDOUT; + } + } +#endif + return 0; +} + +static int peci_xec_read(struct device *dev, struct peci_msg *msg) +{ + ARG_UNUSED(dev); + int i; + int ret; + u8_t tx_fcs; + u8_t bytes_rcvd; + 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++) { + /* Check if data available */ + if (!(base->STATUS2 & MCHP_PECI_STS2_RFE)) { + 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_DBG("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; + } + + /* Reset internal FIFOs for next transaction */ + base->CONTROL = MCHP_PECI_CTRL_FRST; + + return 0; +} + +static int peci_xec_transfer(struct device *dev, struct peci_msg *msg) +{ + ARG_UNUSED(dev); + int ret; + PECI_Type *base = peci_xec_config.base; + + 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 */ + if (base->ERROR) { + if (base->ERROR & MCHP_PECI_ERR_RDOV) { + LOG_WRN("Read buffer is not empty\n"); + } + + if (base->ERROR & MCHP_PECI_ERR_WRUN) { + LOG_WRN("Write buffer is not empty\n"); + } + + if (base->ERROR & MCHP_PECI_ERR_BERR) { + LOG_WRN("Write buffer is not empty\n"); + } + + base->ERROR = base->ERROR; + LOG_WRN("Transaction error %x\n", base->ERROR); + return -EIO; + } + + return 0; +} + +#ifdef CONFIG_PECI_INTERRUPT_DRIVEN +static void peci_xec_isr(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\n", base->STATUS2); + k_sem_give(&peci_data.tx_lock); + } + + if (base->STATUS2 & MCHP_PECI_STS2_RFE) { + LOG_WRN("RX FIFO full ST2:%x\n", 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(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_0_MICROCHIP_XEC_PECI_IRQ_0_PRIORITY, + peci_xec_isr, NULL, 0); +#endif + return 0; +} + +DEVICE_AND_API_INIT(peci_xec, DT_INST_0_MICROCHIP_XEC_PECI_LABEL, + &peci_xec_init, + NULL, NULL, + POST_KERNEL, CONFIG_PECI_INIT_PRIORITY, + &peci_xec_driver_api);