diff --git a/drivers/clock_control/clock_control_npcx.c b/drivers/clock_control/clock_control_npcx.c index 047676d53d7..82199d6111d 100644 --- a/drivers/clock_control/clock_control_npcx.c +++ b/drivers/clock_control/clock_control_npcx.c @@ -95,6 +95,9 @@ static int npcx_clock_control_get_subsys_rate(const struct device *dev, case NPCX_CLOCK_BUS_LFCLK: *rate = LFCLK; break; + case NPCX_CLOCK_BUS_FMCLK: + *rate = FMCLK; + break; default: *rate = 0U; /* Invalid parameters */ diff --git a/drivers/peci/CMakeLists.txt b/drivers/peci/CMakeLists.txt index 1f27e9f7cd8..61cfc9deeb9 100644 --- a/drivers/peci/CMakeLists.txt +++ b/drivers/peci/CMakeLists.txt @@ -4,4 +4,5 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_PECI_XEC peci_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_PECI_ITE_IT8XXX2 peci_ite_it8xxx2.c) +zephyr_library_sources_ifdef(CONFIG_PECI_NPCX peci_npcx.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE peci_handlers.c) diff --git a/drivers/peci/Kconfig b/drivers/peci/Kconfig index e69e1cfc3ee..120006ce840 100644 --- a/drivers/peci/Kconfig +++ b/drivers/peci/Kconfig @@ -12,6 +12,7 @@ if PECI source "drivers/peci/Kconfig.xec" source "drivers/peci/Kconfig.it8xxx2" +source "drivers/peci/Kconfig.npcx" module = PECI module-str = peci diff --git a/drivers/peci/Kconfig.npcx b/drivers/peci/Kconfig.npcx new file mode 100644 index 00000000000..4b1e01274b4 --- /dev/null +++ b/drivers/peci/Kconfig.npcx @@ -0,0 +1,12 @@ +# NPCX PECI driver configuration options + +# Copyright (c) 2022 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +config PECI_NPCX + bool "NPCX PECI driver" + default y + depends on DT_HAS_NUVOTON_NPCX_PECI_ENABLED + select PECI_INTERRUPT_DRIVEN + help + Enable the NPCX PECI IO driver. diff --git a/drivers/peci/peci_npcx.c b/drivers/peci/peci_npcx.c new file mode 100644 index 00000000000..7c7a2a45e5a --- /dev/null +++ b/drivers/peci/peci_npcx.c @@ -0,0 +1,288 @@ +/* + * 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 +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"); diff --git a/dts/arm/nuvoton/npcx.dtsi b/dts/arm/nuvoton/npcx.dtsi index 2ba675fc5e8..959acd6e53f 100644 --- a/dts/arm/nuvoton/npcx.dtsi +++ b/dts/arm/nuvoton/npcx.dtsi @@ -505,6 +505,16 @@ reg = <0x40020000 0x2000>; clocks = <&pcc NPCX_CLOCK_BUS_APB3 NPCX_PWDWN_CTL1 2>; }; + + peci0: peci@400d4000 { + compatible = "nuvoton,npcx-peci"; + reg = <0x400d4000 0x1000>; + #address-cells = <1>; + #size-cells = <0>; + interrupts = <4 4>; + clocks = <&pcc NPCX_CLOCK_BUS_FMCLK NPCX_PWDWN_CTL4 5>; + status = "disabled"; + }; }; soc-if { diff --git a/dts/bindings/peci/nuvoton,npcx-peci.yaml b/dts/bindings/peci/nuvoton,npcx-peci.yaml new file mode 100644 index 00000000000..07dfb35391c --- /dev/null +++ b/dts/bindings/peci/nuvoton,npcx-peci.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2022 Nuvoton Technology Corporation. +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton NPCX PECI node + +compatible: "nuvoton,npcx-peci" + +include: [peci.yaml, pinctrl-device.yaml] + +properties: + pinctrl-0: + required: true + + pinctrl-names: + required: true diff --git a/include/zephyr/dt-bindings/clock/npcx_clock.h b/include/zephyr/dt-bindings/clock/npcx_clock.h index 4e1b4ed3abf..adca438f2de 100644 --- a/include/zephyr/dt-bindings/clock/npcx_clock.h +++ b/include/zephyr/dt-bindings/clock/npcx_clock.h @@ -17,6 +17,7 @@ #define NPCX_CLOCK_BUS_APB3 7 #define NPCX_CLOCK_BUS_APB4 8 #define NPCX_CLOCK_BUS_AHB6 9 +#define NPCX_CLOCK_BUS_FMCLK 10 /* clock enable/disable references */ #define NPCX_PWDWN_CTL1 0 diff --git a/soc/arm/nuvoton_npcx/common/reg/reg_def.h b/soc/arm/nuvoton_npcx/common/reg/reg_def.h index 1933aef830e..63114d5539f 100644 --- a/soc/arm/nuvoton_npcx/common/reg/reg_def.h +++ b/soc/arm/nuvoton_npcx/common/reg/reg_def.h @@ -1536,4 +1536,54 @@ struct fiu_reg { #define UMA_CODE_CMD_ADR_WR_BYTE(n) (UMA_FLD_EXEC | UMA_FLD_WRITE | \ UMA_FLD_ADDR | UMA_FIELD_DATA_##n | \ UMA_FLD_SHD_SL) + +/* Platform Environment Control Interface (PECI) device registers */ +struct peci_reg { + /* 0x000: PECI Control Status */ + volatile uint8_t PECI_CTL_STS; + /* 0x001: PECI Read Length */ + volatile uint8_t PECI_RD_LENGTH; + /* 0x002: PECI Address */ + volatile uint8_t PECI_ADDR; + /* 0x003: PECI Command */ + volatile uint8_t PECI_CMD; + /* 0x004: PECI Control 2 */ + volatile uint8_t PECI_CTL2; + /* 0x005: PECI Index */ + volatile uint8_t PECI_INDEX; + /* 0x006: PECI Index Data */ + volatile uint8_t PECI_IDATA; + /* 0x007: PECI Write Length */ + volatile uint8_t PECI_WR_LENGTH; + volatile uint8_t reserved1[3]; + /* 0x00B: PECI Write FCS */ + volatile uint8_t PECI_WR_FCS; + /* 0x00C: PECI Read FCS */ + volatile uint8_t PECI_RD_FCS; + /* 0x00D: PECI Assured Write FCS */ + volatile uint8_t PECI_AW_FCS; + volatile uint8_t reserved2; + /* 0x00F: PECI Transfer Rate */ + volatile uint8_t PECI_RATE; + /* 0x010 - 0x04F: PECI Data In/Out */ + union { + volatile uint8_t PECI_DATA_IN[64]; + volatile uint8_t PECI_DATA_OUT[64]; + }; +}; + +/* PECI register fields */ +#define NPCX_PECI_CTL_STS_START_BUSY 0 +#define NPCX_PECI_CTL_STS_DONE 1 +#define NPCX_PECI_CTL_STS_CRC_ERR 3 +#define NPCX_PECI_CTL_STS_ABRT_ERR 4 +#define NPCX_PECI_CTL_STS_AWFCS_EB 5 +#define NPCX_PECI_CTL_STS_DONE_EN 6 +#define NPCX_PECI_RATE_MAX_BIT_RATE FIELD(0, 5) +#define NPCX_PECI_RATE_MAX_BIT_RATE_MASK 0x1F +/* The minimal valid value of NPCX_PECI_RATE_MAX_BIT_RATE field */ +#define PECI_MAX_BIT_RATE_VALID_MIN 0x05 +#define PECI_HIGH_SPEED_MIN_VAL 0x07 + +#define NPCX_PECI_RATE_EHSP 6 #endif /* _NUVOTON_NPCX_REG_DEF_H */ diff --git a/soc/arm/nuvoton_npcx/common/registers.c b/soc/arm/nuvoton_npcx/common/registers.c index c6633298d52..5e3add647a1 100644 --- a/soc/arm/nuvoton_npcx/common/registers.c +++ b/soc/arm/nuvoton_npcx/common/registers.c @@ -171,3 +171,9 @@ NPCX_REG_OFFSET_CHECK(fiu_reg, UMA_CTS, 0x01e); NPCX_REG_OFFSET_CHECK(fiu_reg, CRCCON, 0x026); NPCX_REG_OFFSET_CHECK(fiu_reg, FIU_RD_CMD, 0x030); NPCX_REG_OFFSET_CHECK(fiu_reg, FIU_EXT_CFG, 0x033); + +/* PECI register structure check */ +NPCX_REG_SIZE_CHECK(peci_reg, 0x050); +NPCX_REG_OFFSET_CHECK(peci_reg, PECI_ADDR, 0x002); +NPCX_REG_OFFSET_CHECK(peci_reg, PECI_WR_LENGTH, 0x007); +NPCX_REG_OFFSET_CHECK(peci_reg, PECI_WR_FCS, 0x00b);