/* * Copyright (c) 2022 Nuvoton Technology Corporation. * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT nuvoton_npcx_peci #include #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(peci_npcx, CONFIG_PECI_LOG_LEVEL); #define PECI_TIMEOUT K_MSEC(300) #define PECI_NPCX_MAX_TX_BUF_LEN 65 #define PECI_NPCX_MAX_RX_BUF_LEN 64 struct peci_npcx_config { /* peci controller base address */ struct peci_reg *base; struct npcx_clk_cfg clk_cfg; const struct pinctrl_dev_config *pcfg; }; struct peci_npcx_data { struct k_sem trans_sync_sem; struct k_sem lock; uint32_t peci_src_clk_freq; int trans_error; }; enum npcx_peci_error_code { NPCX_PECI_NO_ERROR, NPCX_PECI_WR_ABORT_ERROR, NPCX_PECI_RD_CRC_ERROR, }; static int peci_npcx_check_bus_idle(struct peci_reg *reg) { if (IS_BIT_SET(reg->PECI_CTL_STS, NPCX_PECI_CTL_STS_START_BUSY)) { return -EBUSY; } return 0; } static int peci_npcx_wait_completion(const struct device *dev) { struct peci_npcx_data *const data = dev->data; int ret; ret = k_sem_take(&data->trans_sync_sem, PECI_TIMEOUT); if (ret != 0) { LOG_ERR("%s: Timeout", __func__); return -ETIMEDOUT; } if (data->trans_error != NPCX_PECI_NO_ERROR) { return -EIO; } return 0; } static int peci_npcx_configure(const struct device *dev, uint32_t bitrate) { const struct peci_npcx_config *const config = dev->config; struct peci_npcx_data *const data = dev->data; struct peci_reg *const reg = config->base; uint8_t bit_rate_divider; k_sem_take(&data->lock, K_FOREVER); /* * The unit of the bitrate is in Kbps, need to convert it to bps when * calculate the divider */ bit_rate_divider = DIV_ROUND_UP(data->peci_src_clk_freq, bitrate * 1000 * 4) - 1; /* * Make sure the divider doesn't exceed the max valid value and is not lower than the * minimal valid value. */ bit_rate_divider = CLAMP(bit_rate_divider, PECI_MAX_BIT_RATE_VALID_MIN, NPCX_PECI_RATE_MAX_BIT_RATE_MASK); if (bit_rate_divider < PECI_HIGH_SPEED_MIN_VAL) { reg->PECI_RATE |= BIT(NPCX_PECI_RATE_EHSP); } else { reg->PECI_RATE &= ~BIT(NPCX_PECI_RATE_EHSP); } SET_FIELD(reg->PECI_RATE, NPCX_PECI_RATE_MAX_BIT_RATE, bit_rate_divider); k_sem_give(&data->lock); return 0; } static int peci_npcx_disable(const struct device *dev) { struct peci_npcx_data *const data = dev->data; k_sem_take(&data->lock, K_FOREVER); irq_disable(DT_INST_IRQN(0)); k_sem_give(&data->lock); return 0; } static int peci_npcx_enable(const struct device *dev) { const struct peci_npcx_config *const config = dev->config; struct peci_npcx_data *const data = dev->data; struct peci_reg *const reg = config->base; k_sem_take(&data->lock, K_FOREVER); reg->PECI_CTL_STS = BIT(NPCX_PECI_CTL_STS_DONE) | BIT(NPCX_PECI_CTL_STS_CRC_ERR) | BIT(NPCX_PECI_CTL_STS_ABRT_ERR); NVIC_ClearPendingIRQ(DT_INST_IRQN(0)); irq_enable(DT_INST_IRQN(0)); k_sem_give(&data->lock); return 0; } static int peci_npcx_transfer(const struct device *dev, struct peci_msg *msg) { const struct peci_npcx_config *const config = dev->config; struct peci_npcx_data *const data = dev->data; struct peci_reg *const reg = config->base; struct peci_buf *peci_rx_buf = &msg->rx_buffer; struct peci_buf *peci_tx_buf = &msg->tx_buffer; enum peci_command_code cmd_code = msg->cmd_code; int ret = 0; k_sem_take(&data->lock, K_FOREVER); if (peci_tx_buf->len > PECI_NPCX_MAX_TX_BUF_LEN || peci_rx_buf->len > PECI_NPCX_MAX_RX_BUF_LEN) { ret = -EINVAL; goto out; } ret = peci_npcx_check_bus_idle(reg); if (ret != 0) { goto out; } reg->PECI_ADDR = msg->addr; reg->PECI_WR_LENGTH = peci_tx_buf->len; reg->PECI_RD_LENGTH = peci_rx_buf->len; reg->PECI_CMD = cmd_code; /* * If command = PING command: * Tx buffer length = 0. * Otherwise: * Tx buffer length = N-bytes data + 1 byte command code. */ if (peci_tx_buf->len != 0) { for (int i = 0; i < (peci_tx_buf->len - 1); i++) { reg->PECI_DATA_OUT[i] = peci_tx_buf->buf[i]; } } /* Enable PECI transaction done interrupt */ reg->PECI_CTL_STS |= BIT(NPCX_PECI_CTL_STS_DONE_EN); /* Start PECI transaction */ reg->PECI_CTL_STS |= BIT(NPCX_PECI_CTL_STS_START_BUSY); ret = peci_npcx_wait_completion(dev); if (ret == 0) { int i; for (i = 0; i < peci_rx_buf->len; i++) { peci_rx_buf->buf[i] = reg->PECI_DATA_IN[i]; } /* * The application allocates N+1 bytes for rx_buffer. * The read data block is stored at the offset 0 ~ (N-1). * The read block FCS is stored at offset N. */ peci_rx_buf->buf[i] = reg->PECI_RD_FCS; LOG_DBG("Wr FCS:0x%02x|Rd FCS:0x%02x", reg->PECI_WR_FCS, reg->PECI_RD_FCS); } out: k_sem_give(&data->lock); return ret; } static void peci_npcx_isr(const struct device *dev) { const struct peci_npcx_config *const config = dev->config; struct peci_npcx_data *const data = dev->data; struct peci_reg *const reg = config->base; uint8_t status; status = reg->PECI_CTL_STS; LOG_DBG("PECI ISR status: 0x%02x", status); /* * Disable the transaction done interrupt, also clear the status bits * if they were set. */ reg->PECI_CTL_STS &= ~BIT(NPCX_PECI_CTL_STS_DONE_EN); if (IS_BIT_SET(status, NPCX_PECI_CTL_STS_ABRT_ERR)) { data->trans_error = NPCX_PECI_WR_ABORT_ERROR; LOG_ERR("PECI Nego or Wr FCS(0x%02x) error", reg->PECI_WR_FCS); } else if (IS_BIT_SET(status, NPCX_PECI_CTL_STS_CRC_ERR)) { data->trans_error = NPCX_PECI_RD_CRC_ERROR; LOG_ERR("PECI Rd FCS(0x%02x) error", reg->PECI_WR_FCS); } else { data->trans_error = NPCX_PECI_NO_ERROR; } k_sem_give(&data->trans_sync_sem); } static DEVICE_API(peci, peci_npcx_driver_api) = { .config = peci_npcx_configure, .enable = peci_npcx_enable, .disable = peci_npcx_disable, .transfer = peci_npcx_transfer, }; static int peci_npcx_init(const struct device *dev) { const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); const struct peci_npcx_config *const config = dev->config; struct peci_npcx_data *const data = dev->data; int ret; if (!device_is_ready(clk_dev)) { LOG_ERR("%s device not ready", clk_dev->name); return -ENODEV; } ret = clock_control_on(clk_dev, (clock_control_subsys_t)&config->clk_cfg); if (ret < 0) { LOG_ERR("Turn on PECI clock fail %d", ret); return ret; } ret = clock_control_get_rate(clk_dev, (clock_control_subsys_t)&config->clk_cfg, &data->peci_src_clk_freq); if (ret < 0) { LOG_ERR("Get PECI source clock rate error %d", ret); return ret; } ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); if (ret != 0) { LOG_ERR("NPCX PECI pinctrl init failed (%d)", ret); return ret; } k_sem_init(&data->trans_sync_sem, 0, 1); k_sem_init(&data->lock, 1, 1); IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), peci_npcx_isr, DEVICE_DT_INST_GET(0), 0); return 0; } static struct peci_npcx_data peci_npcx_data0; PINCTRL_DT_INST_DEFINE(0); static const struct peci_npcx_config peci_npcx_config0 = { .base = (struct peci_reg *)DT_INST_REG_ADDR(0), .clk_cfg = NPCX_DT_CLK_CFG_ITEM(0), .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), }; DEVICE_DT_INST_DEFINE(0, &peci_npcx_init, NULL, &peci_npcx_data0, &peci_npcx_config0, POST_KERNEL, CONFIG_PECI_INIT_PRIORITY, &peci_npcx_driver_api); BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, "only one 'nuvoton_npcx_peci' compatible node can be supported");