| /* |
| * Copyright (c) 2022 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_npcx_peci |
| |
| #include <errno.h> |
| #include <soc.h> |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/peci.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/kernel.h> |
| |
| #include <zephyr/logging/log.h> |
| #include <zephyr/irq.h> |
| 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 = ceiling_fraction(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 const struct peci_driver_api 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"); |