/* * Copyright (c) 2020 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT microchip_xec_peci #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 /* 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, NULL, NULL, NULL, POST_KERNEL, CONFIG_PECI_INIT_PRIORITY, &peci_xec_driver_api);