You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
7.4 KiB
290 lines
7.4 KiB
/* |
|
* 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 = 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");
|
|
|