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:
parent
c0317fba1f
commit
ec4f700202
10 changed files with 387 additions and 0 deletions
|
@ -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 */
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
12
drivers/peci/Kconfig.npcx
Normal 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
288
drivers/peci/peci_npcx.c
Normal 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");
|
|
@ -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 {
|
||||
|
|
15
dts/bindings/peci/nuvoton,npcx-peci.yaml
Normal file
15
dts/bindings/peci/nuvoton,npcx-peci.yaml
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue