blob: 670ec452900bb8dc7e3384509340c5c7580a0810 [file] [log] [blame]
/*
* 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");