driver: PECI: npcx: add driver support for Nuvoton npcx family

This commit add the PECI driver for Nuvoton npcx family to support PECI
APIs.

Signed-off-by: Jun Lin <CHLin56@nuvoton.com>
This commit is contained in:
Jun Lin 2022-07-20 13:45:56 +08:00 committed by Carles Cufí
commit ec4f700202
10 changed files with 387 additions and 0 deletions

View file

@ -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 */

View file

@ -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)

View file

@ -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

12
drivers/peci/Kconfig.npcx Normal file
View file

@ -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.

288
drivers/peci/peci_npcx.c Normal file
View file

@ -0,0 +1,288 @@
/*
* 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/logging/log.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");

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -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 */

View file

@ -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);