| /* |
| * Copyright (c) 2020 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_npcx_espi |
| |
| #include <assert.h> |
| #include <zephyr/drivers/espi.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/dt-bindings/espi/npcx_espi.h> |
| #include <zephyr/kernel.h> |
| #include <soc.h> |
| #include "espi_utils.h" |
| #include "soc_host.h" |
| #include "soc_miwu.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(espi, CONFIG_ESPI_LOG_LEVEL); |
| |
| struct espi_npcx_config { |
| uintptr_t base; |
| /* clock configuration */ |
| struct npcx_clk_cfg clk_cfg; |
| /* mapping table between eSPI reset signal and wake-up input */ |
| struct npcx_wui espi_rst_wui; |
| /* pinmux configuration */ |
| const struct pinctrl_dev_config *pcfg; |
| }; |
| |
| struct espi_npcx_data { |
| sys_slist_t callbacks; |
| uint8_t plt_rst_asserted; |
| uint8_t espi_rst_asserted; |
| uint8_t sx_state; |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| struct k_sem oob_rx_lock; |
| #endif |
| }; |
| |
| /* Driver convenience defines */ |
| #define HAL_INSTANCE(dev) \ |
| ((struct espi_reg *)((const struct espi_npcx_config *)(dev)->config)->base) |
| |
| /* eSPI channels */ |
| #define NPCX_ESPI_CH_PC 0 |
| #define NPCX_ESPI_CH_VW 1 |
| #define NPCX_ESPI_CH_OOB 2 |
| #define NPCX_ESPI_CH_FLASH 3 |
| #define NPCX_ESPI_CH_COUNT 4 |
| #define NPCX_ESPI_HOST_CH_EN(ch) (ch + 4) |
| |
| /* eSPI max supported frequency */ |
| #define NPCX_ESPI_MAXFREQ_20 0 |
| #define NPCX_ESPI_MAXFREQ_25 1 |
| #define NPCX_ESPI_MAXFREQ_33 2 |
| #define NPCX_ESPI_MAXFREQ_50 3 |
| |
| /* Minimum delay before acknowledging a virtual wire */ |
| #define NPCX_ESPI_VWIRE_ACK_DELAY 10ul /* 10 us */ |
| |
| /* OOB channel maximum payload size */ |
| #define NPCX_ESPI_OOB_MAX_PAYLOAD 64 |
| #define NPCX_OOB_RX_PACKAGE_LEN(hdr) (((hdr & 0xff000000) >> 24) | \ |
| ((hdr & 0xf0000) >> 8)) |
| |
| /* eSPI cycle type field for OOB and FLASH channels */ |
| #define ESPI_FLASH_READ_CYCLE_TYPE 0x00 |
| #define ESPI_FLASH_WRITE_CYCLE_TYPE 0x01 |
| #define ESPI_FLASH_ERASE_CYCLE_TYPE 0x02 |
| #define ESPI_OOB_GET_CYCLE_TYPE 0x21 |
| #define ESPI_OOB_TAG 0x00 |
| #define ESPI_OOB_MAX_TIMEOUT 500ul /* 500 ms */ |
| |
| /* eSPI bus interrupt configuration structure and macro function */ |
| struct espi_bus_isr { |
| uint8_t status_bit; /* bit order in ESPISTS register */ |
| uint8_t int_en_bit; /* bit order in ESPIIE register */ |
| uint8_t wake_en_bit; /* bit order in ESPIWE register */ |
| void (*bus_isr)(const struct device *dev); /* eSPI bus ISR */ |
| }; |
| |
| #define NPCX_ESPI_BUS_INT_ITEM(event, isr) { \ |
| .status_bit = NPCX_ESPISTS_##event, \ |
| .int_en_bit = NPCX_ESPIIE_##event##IE, \ |
| .wake_en_bit = NPCX_ESPIWE_##event##WE, \ |
| .bus_isr = isr } |
| |
| /* eSPI Virtual Wire Input (Master-to-Slave) signals configuration structure */ |
| struct npcx_vw_in_config { |
| enum espi_vwire_signal sig; /* Virtual Wire signal */ |
| uint8_t reg_idx; /* register index for VW signal */ |
| uint8_t bitmask; /* VW signal bits-mask */ |
| struct npcx_wui vw_wui; /* WUI mapping in MIWU modules for VW signal */ |
| }; |
| |
| /* eSPI Virtual Wire Output (Slave-to-Master) signals configuration structure */ |
| struct npcx_vw_out_config { |
| enum espi_vwire_signal sig; /* Virtual Wire signal */ |
| uint8_t reg_idx; /* register index for VW signal */ |
| uint8_t bitmask; /* VW signal bits-mask */ |
| }; |
| |
| /* |
| * eSPI VW input/Output signal configuration tables. Please refer |
| * npcxn-espi-vws-map.dtsi device tree file for more detail. |
| */ |
| static const struct npcx_vw_in_config vw_in_tbl[] = { |
| /* index 02h (In) */ |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_S3, vw_slp_s3), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_S4, vw_slp_s4), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_S5, vw_slp_s5), |
| /* index 03h (In) */ |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SUS_STAT, vw_sus_stat), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_PLTRST, vw_plt_rst), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_OOB_RST_WARN, vw_oob_rst_warn), |
| /* index 07h (In) */ |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_HOST_RST_WARN, vw_host_rst_warn), |
| /* index 41h (In) */ |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SUS_WARN, vw_sus_warn), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SUS_PWRDN_ACK, vw_sus_pwrdn_ack), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_A, vw_slp_a), |
| /* index 42h (In) */ |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_LAN, vw_slp_lan), |
| NPCX_DT_VW_IN_CONF(ESPI_VWIRE_SIGNAL_SLP_WLAN, vw_slp_wlan), |
| }; |
| |
| static const struct npcx_vw_out_config vw_out_tbl[] = { |
| /* index 04h (Out) */ |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_OOB_RST_ACK, vw_oob_rst_ack), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_WAKE, vw_wake), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_PME, vw_pme), |
| /* index 05h (Out) */ |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_SLV_BOOT_DONE, vw_slv_boot_done), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_ERR_FATAL, vw_err_fatal), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_ERR_NON_FATAL, vw_err_non_fatal), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_SLV_BOOT_STS, |
| vw_slv_boot_sts_with_done), |
| /* index 06h (Out) */ |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_SCI, vw_sci), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_SMI, vw_smi), |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_HOST_RST_ACK, vw_host_rst_ack), |
| /* index 40h (Out) */ |
| NPCX_DT_VW_OUT_CONF(ESPI_VWIRE_SIGNAL_SUS_ACK, vw_sus_ack), |
| }; |
| |
| /* Callbacks for eSPI bus reset and Virtual Wire signals. */ |
| static struct miwu_dev_callback espi_rst_callback; |
| static struct miwu_dev_callback vw_in_callback[ARRAY_SIZE(vw_in_tbl)]; |
| |
| /* eSPI VW service function forward declarations */ |
| static int espi_npcx_receive_vwire(const struct device *dev, |
| enum espi_vwire_signal signal, uint8_t *level); |
| static int espi_npcx_send_vwire(const struct device *dev, |
| enum espi_vwire_signal signal, uint8_t level); |
| static void espi_vw_send_bootload_done(const struct device *dev); |
| |
| /* eSPI local initialization functions */ |
| static void espi_init_wui_callback(const struct device *dev, |
| struct miwu_dev_callback *callback, const struct npcx_wui *wui, |
| miwu_dev_callback_handler_t handler) |
| { |
| /* VW signal which has no wake-up input source */ |
| if (wui->table == NPCX_MIWU_TABLE_NONE) |
| return; |
| |
| /* Install callback function */ |
| npcx_miwu_init_dev_callback(callback, wui, handler, dev); |
| npcx_miwu_manage_dev_callback(callback, 1); |
| |
| /* Configure MIWU setting and enable its interrupt */ |
| npcx_miwu_interrupt_configure(wui, NPCX_MIWU_MODE_EDGE, |
| NPCX_MIWU_TRIG_BOTH); |
| npcx_miwu_irq_enable(wui); |
| } |
| |
| /* eSPI local bus interrupt service functions */ |
| static void espi_bus_err_isr(const struct device *dev) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| uint32_t err = inst->ESPIERR; |
| |
| LOG_ERR("eSPI Bus Error %08X", err); |
| /* Clear error status bits */ |
| inst->ESPIERR = err; |
| } |
| |
| static void espi_bus_inband_rst_isr(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| LOG_DBG("%s issued", __func__); |
| } |
| |
| static void espi_bus_reset_isr(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| LOG_DBG("%s issued", __func__); |
| /* Do nothing! This signal is handled in ESPI_RST VW signal ISR */ |
| } |
| |
| static void espi_bus_cfg_update_isr(const struct device *dev) |
| { |
| int chan; |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| struct espi_npcx_data *const data = dev->data; |
| struct espi_event evt = { .evt_type = ESPI_BUS_EVENT_CHANNEL_READY, |
| .evt_details = 0, |
| .evt_data = 0 }; |
| /* If host enable bits are not sync with ready bits on slave side. */ |
| uint8_t chg_mask = GET_FIELD(inst->ESPICFG, NPCX_ESPICFG_HCHANS_FIELD) |
| ^ GET_FIELD(inst->ESPICFG, NPCX_ESPICFG_CHANS_FIELD); |
| chg_mask &= (ESPI_CHANNEL_VWIRE | ESPI_CHANNEL_OOB | |
| ESPI_CHANNEL_FLASH); |
| |
| LOG_DBG("ESPI CFG Change Updated! 0x%02X", chg_mask); |
| /* |
| * If host enable/disable channel for VW/OOB/FLASH, EC should follow |
| * except Peripheral channel. It is handled after receiving PLTRST |
| * event separately. |
| */ |
| for (chan = NPCX_ESPI_CH_VW; chan < NPCX_ESPI_CH_COUNT; chan++) { |
| /* Channel ready bit isn't sync with enabled bit on host side */ |
| if (chg_mask & BIT(chan)) { |
| evt.evt_data = IS_BIT_SET(inst->ESPICFG, |
| NPCX_ESPI_HOST_CH_EN(chan)); |
| evt.evt_details = BIT(chan); |
| |
| if (evt.evt_data) { |
| inst->ESPICFG |= BIT(chan); |
| } else { |
| inst->ESPICFG &= ~BIT(chan); |
| } |
| |
| espi_send_callbacks(&data->callbacks, dev, evt); |
| } |
| } |
| LOG_DBG("ESPI CFG EC Updated! 0x%02X", GET_FIELD(inst->ESPICFG, |
| NPCX_ESPICFG_CHANS_FIELD)); |
| |
| /* If VW channel is enabled and ready, send bootload done VW signal */ |
| if ((chg_mask & BIT(NPCX_ESPI_CH_VW)) && IS_BIT_SET(inst->ESPICFG, |
| NPCX_ESPI_HOST_CH_EN(NPCX_ESPI_CH_VW))) { |
| espi_vw_send_bootload_done(dev); |
| } |
| } |
| |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| static void espi_bus_oob_rx_isr(const struct device *dev) |
| { |
| struct espi_npcx_data *const data = dev->data; |
| |
| LOG_DBG("%s", __func__); |
| k_sem_give(&data->oob_rx_lock); |
| } |
| #endif |
| |
| const struct espi_bus_isr espi_bus_isr_tbl[] = { |
| NPCX_ESPI_BUS_INT_ITEM(BERR, espi_bus_err_isr), |
| NPCX_ESPI_BUS_INT_ITEM(IBRST, espi_bus_inband_rst_isr), |
| NPCX_ESPI_BUS_INT_ITEM(ESPIRST, espi_bus_reset_isr), |
| NPCX_ESPI_BUS_INT_ITEM(CFGUPD, espi_bus_cfg_update_isr), |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| NPCX_ESPI_BUS_INT_ITEM(OOBRX, espi_bus_oob_rx_isr), |
| #endif |
| }; |
| |
| static void espi_bus_generic_isr(const struct device *dev) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| int i; |
| uint32_t mask, status; |
| |
| /* |
| * Bit 17 of ESPIIE is reserved. We need to set the same bit in mask |
| * in case bit 17 in ESPISTS of npcx7 is not cleared in ISR. |
| */ |
| mask = inst->ESPIIE | (1 << NPCX_ESPISTS_VWUPDW); |
| status = inst->ESPISTS & mask; |
| |
| /* Clear pending bits of status register first */ |
| inst->ESPISTS = status; |
| |
| LOG_DBG("%s: 0x%08X", __func__, status); |
| for (i = 0; i < ARRAY_SIZE(espi_bus_isr_tbl); i++) { |
| struct espi_bus_isr entry = espi_bus_isr_tbl[i]; |
| |
| if (status & BIT(entry.status_bit)) { |
| if (entry.bus_isr != NULL) { |
| entry.bus_isr(dev); |
| } |
| } |
| } |
| } |
| |
| /* eSPI local virtual-wire service functions */ |
| static void espi_vw_config_input(const struct device *dev, |
| const struct npcx_vw_in_config *config_in) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| int idx = config_in->reg_idx; |
| |
| /* IE & WE bits are already set? */ |
| if (IS_BIT_SET(inst->VWEVMS[idx], NPCX_VWEVMS_IE) && |
| IS_BIT_SET(inst->VWEVMS[idx], NPCX_VWEVMS_WE)) |
| return; |
| |
| /* Set IE & WE bits in VWEVMS */ |
| inst->VWEVMS[idx] |= BIT(NPCX_VWEVMS_IE) | BIT(NPCX_VWEVMS_WE); |
| LOG_DBG("VWEVMS%d 0x%08X", idx, inst->VWEVMS[idx]); |
| } |
| |
| static void espi_vw_config_output(const struct device *dev, |
| const struct npcx_vw_out_config *config_out) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| int idx = config_out->reg_idx; |
| uint8_t valid = GET_FIELD(inst->VWEVSM[idx], NPCX_VWEVSM_VALID); |
| |
| /* Set valid bits for vw signal which we have declared in table. */ |
| valid |= config_out->bitmask; |
| SET_FIELD(inst->VWEVSM[idx], NPCX_VWEVSM_VALID, valid); |
| |
| /* |
| * Turn off hardware-wire feature which generates VW events that |
| * connected to hardware signals. We will set it manually by software. |
| */ |
| SET_FIELD(inst->VWEVSM[idx], NPCX_VWEVSM_HW_WIRE, 0); |
| |
| LOG_DBG("VWEVSM%d 0x%08X", idx, inst->VWEVSM[idx]); |
| } |
| |
| static void espi_vw_notify_system_state(const struct device *dev, |
| enum espi_vwire_signal signal) |
| { |
| struct espi_npcx_data *const data = dev->data; |
| struct espi_event evt = { ESPI_BUS_EVENT_VWIRE_RECEIVED, 0, 0 }; |
| uint8_t wire = 0; |
| |
| espi_npcx_receive_vwire(dev, signal, &wire); |
| if (!wire) { |
| data->sx_state = signal; |
| } |
| |
| evt.evt_details = signal; |
| evt.evt_data = wire; |
| espi_send_callbacks(&data->callbacks, dev, evt); |
| } |
| |
| static void espi_vw_notify_host_warning(const struct device *dev, |
| enum espi_vwire_signal signal) |
| { |
| uint8_t wire; |
| |
| espi_npcx_receive_vwire(dev, signal, &wire); |
| |
| k_busy_wait(NPCX_ESPI_VWIRE_ACK_DELAY); |
| switch (signal) { |
| case ESPI_VWIRE_SIGNAL_HOST_RST_WARN: |
| espi_npcx_send_vwire(dev, |
| ESPI_VWIRE_SIGNAL_HOST_RST_ACK, |
| wire); |
| break; |
| case ESPI_VWIRE_SIGNAL_SUS_WARN: |
| espi_npcx_send_vwire(dev, ESPI_VWIRE_SIGNAL_SUS_ACK, |
| wire); |
| break; |
| case ESPI_VWIRE_SIGNAL_OOB_RST_WARN: |
| espi_npcx_send_vwire(dev, ESPI_VWIRE_SIGNAL_OOB_RST_ACK, |
| wire); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void espi_vw_notify_plt_rst(const struct device *dev) |
| { |
| struct espi_npcx_data *const data = dev->data; |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| struct espi_event evt = { ESPI_BUS_EVENT_VWIRE_RECEIVED, |
| ESPI_VWIRE_SIGNAL_PLTRST, 0 |
| }; |
| uint8_t wire = 0; |
| |
| espi_npcx_receive_vwire(dev, ESPI_VWIRE_SIGNAL_PLTRST, &wire); |
| LOG_DBG("VW_PLT_RST is %d!", wire); |
| if (wire) { |
| /* Set Peripheral Channel ready when PLTRST is de-asserted */ |
| inst->ESPICFG |= BIT(NPCX_ESPICFG_PCHANEN); |
| /* Configure all host sub-modules in host domain */ |
| npcx_host_init_subs_host_domain(); |
| } |
| |
| /* PLT_RST will be received several times */ |
| if (wire != data->plt_rst_asserted) { |
| data->plt_rst_asserted = wire; |
| evt.evt_data = wire; |
| espi_send_callbacks(&data->callbacks, dev, evt); |
| } |
| } |
| |
| static void espi_vw_send_bootload_done(const struct device *dev) |
| { |
| int ret; |
| uint8_t boot_done; |
| |
| ret = espi_npcx_receive_vwire(dev, |
| ESPI_VWIRE_SIGNAL_SLV_BOOT_DONE, &boot_done); |
| LOG_DBG("%s: %d", __func__, boot_done); |
| if (!ret && !boot_done) { |
| /* Send slave boot status bit with done bit at the same time. */ |
| espi_npcx_send_vwire(dev, ESPI_VWIRE_SIGNAL_SLV_BOOT_STS, 1); |
| } |
| } |
| |
| static void espi_vw_generic_isr(const struct device *dev, struct npcx_wui *wui) |
| { |
| int idx; |
| enum espi_vwire_signal signal; |
| |
| LOG_DBG("%s: WUI %d %d %d", __func__, wui->table, wui->group, wui->bit); |
| for (idx = 0; idx < ARRAY_SIZE(vw_in_tbl); idx++) { |
| if (wui->table == vw_in_tbl[idx].vw_wui.table && |
| wui->group == vw_in_tbl[idx].vw_wui.group && |
| wui->bit == vw_in_tbl[idx].vw_wui.bit) { |
| break; |
| } |
| } |
| |
| if (idx == ARRAY_SIZE(vw_in_tbl)) { |
| LOG_ERR("Unknown VW event! %d %d %d", wui->table, |
| wui->group, wui->bit); |
| return; |
| } |
| |
| signal = vw_in_tbl[idx].sig; |
| if (signal == ESPI_VWIRE_SIGNAL_SLP_S3 |
| || signal == ESPI_VWIRE_SIGNAL_SLP_S4 |
| || signal == ESPI_VWIRE_SIGNAL_SLP_S5 |
| || signal == ESPI_VWIRE_SIGNAL_SLP_A) { |
| espi_vw_notify_system_state(dev, signal); |
| } else if (signal == ESPI_VWIRE_SIGNAL_HOST_RST_WARN |
| || signal == ESPI_VWIRE_SIGNAL_SUS_WARN |
| || signal == ESPI_VWIRE_SIGNAL_OOB_RST_WARN) { |
| espi_vw_notify_host_warning(dev, signal); |
| } else if (signal == ESPI_VWIRE_SIGNAL_PLTRST) { |
| espi_vw_notify_plt_rst(dev); |
| } |
| } |
| |
| static void espi_vw_espi_rst_isr(const struct device *dev, struct npcx_wui *wui) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| struct espi_npcx_data *const data = dev->data; |
| struct espi_event evt = { ESPI_BUS_RESET, 0, 0 }; |
| |
| data->espi_rst_asserted = !IS_BIT_SET(inst->ESPISTS, |
| NPCX_ESPISTS_ESPIRST_LVL); |
| LOG_DBG("eSPI RST asserted is %d!", data->espi_rst_asserted); |
| |
| evt.evt_data = data->espi_rst_asserted; |
| espi_send_callbacks(&data->callbacks, dev, evt); |
| } |
| |
| /* eSPI api functions */ |
| static int espi_npcx_configure(const struct device *dev, struct espi_cfg *cfg) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| |
| uint8_t max_freq = 0; |
| uint8_t cur_io_mode, io_mode = 0; |
| |
| /* Configure eSPI frequency */ |
| switch (cfg->max_freq) { |
| case 20: |
| max_freq = NPCX_ESPI_MAXFREQ_20; |
| break; |
| case 25: |
| max_freq = NPCX_ESPI_MAXFREQ_25; |
| break; |
| case 33: |
| max_freq = NPCX_ESPI_MAXFREQ_33; |
| break; |
| case 50: |
| max_freq = NPCX_ESPI_MAXFREQ_50; |
| break; |
| default: |
| return -EINVAL; |
| } |
| SET_FIELD(inst->ESPICFG, NPCX_ESPICFG_MAXFREQ_FIELD, max_freq); |
| |
| /* Configure eSPI IO mode */ |
| io_mode = (cfg->io_caps >> 1); |
| if (io_mode > 3) { |
| return -EINVAL; |
| } |
| |
| cur_io_mode = GET_FIELD(inst->ESPICFG, NPCX_ESPICFG_IOMODE_FIELD); |
| if (io_mode != cur_io_mode) { |
| SET_FIELD(inst->ESPICFG, NPCX_ESPICFG_IOMODE_FIELD, io_mode); |
| } |
| |
| /* Configure eSPI supported channels */ |
| if (cfg->channel_caps & ESPI_CHANNEL_PERIPHERAL) |
| inst->ESPICFG |= BIT(NPCX_ESPICFG_PCCHN_SUPP); |
| |
| if (cfg->channel_caps & ESPI_CHANNEL_VWIRE) |
| inst->ESPICFG |= BIT(NPCX_ESPICFG_VWCHN_SUPP); |
| |
| if (cfg->channel_caps & ESPI_CHANNEL_OOB) |
| inst->ESPICFG |= BIT(NPCX_ESPICFG_OOBCHN_SUPP); |
| |
| if (cfg->channel_caps & ESPI_CHANNEL_FLASH) |
| inst->ESPICFG |= BIT(NPCX_ESPICFG_FLASHCHN_SUPP); |
| |
| LOG_DBG("%s: %d %d ESPICFG: 0x%08X", __func__, |
| max_freq, io_mode, inst->ESPICFG); |
| |
| return 0; |
| } |
| |
| static bool espi_npcx_channel_ready(const struct device *dev, |
| enum espi_channel ch) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| bool sts; |
| |
| switch (ch) { |
| case ESPI_CHANNEL_PERIPHERAL: |
| sts = IS_BIT_SET(inst->ESPICFG, NPCX_ESPICFG_PCHANEN); |
| break; |
| case ESPI_CHANNEL_VWIRE: |
| sts = IS_BIT_SET(inst->ESPICFG, NPCX_ESPICFG_VWCHANEN); |
| break; |
| case ESPI_CHANNEL_OOB: |
| sts = IS_BIT_SET(inst->ESPICFG, NPCX_ESPICFG_OOBCHANEN); |
| break; |
| case ESPI_CHANNEL_FLASH: |
| sts = IS_BIT_SET(inst->ESPICFG, NPCX_ESPICFG_FLASHCHANEN); |
| break; |
| default: |
| sts = false; |
| break; |
| } |
| |
| return sts; |
| } |
| |
| static int espi_npcx_send_vwire(const struct device *dev, |
| enum espi_vwire_signal signal, uint8_t level) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| uint8_t reg_idx, bitmask, sig_idx, val = 0; |
| |
| /* Find signal in VW output table */ |
| for (sig_idx = 0; sig_idx < ARRAY_SIZE(vw_out_tbl); sig_idx++) |
| if (vw_out_tbl[sig_idx].sig == signal) |
| break; |
| |
| if (sig_idx == ARRAY_SIZE(vw_out_tbl)) { |
| LOG_ERR("%s signal %d is invalid", __func__, signal); |
| return -EIO; |
| } |
| |
| reg_idx = vw_out_tbl[sig_idx].reg_idx; |
| bitmask = vw_out_tbl[sig_idx].bitmask; |
| |
| /* Get wire field and set/clear wire bit */ |
| val = GET_FIELD(inst->VWEVSM[reg_idx], NPCX_VWEVSM_WIRE); |
| if (level) { |
| val |= bitmask; |
| } else { |
| val &= ~bitmask; |
| } |
| |
| SET_FIELD(inst->VWEVSM[reg_idx], NPCX_VWEVSM_WIRE, val); |
| LOG_DBG("Send VW: VWEVSM%d 0x%08X", reg_idx, inst->VWEVSM[reg_idx]); |
| |
| return 0; |
| } |
| |
| static int espi_npcx_receive_vwire(const struct device *dev, |
| enum espi_vwire_signal signal, uint8_t *level) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| uint8_t reg_idx, bitmask, sig_idx, val; |
| |
| /* Find signal in VW input table */ |
| for (sig_idx = 0; sig_idx < ARRAY_SIZE(vw_in_tbl); sig_idx++) |
| if (vw_in_tbl[sig_idx].sig == signal) { |
| reg_idx = vw_in_tbl[sig_idx].reg_idx; |
| bitmask = vw_in_tbl[sig_idx].bitmask; |
| |
| val = GET_FIELD(inst->VWEVMS[reg_idx], |
| NPCX_VWEVMS_WIRE); |
| val &= GET_FIELD(inst->VWEVMS[reg_idx], |
| NPCX_VWEVMS_VALID); |
| |
| *level = !!(val & bitmask); |
| return 0; |
| } |
| |
| /* Find signal in VW output table */ |
| for (sig_idx = 0; sig_idx < ARRAY_SIZE(vw_out_tbl); sig_idx++) |
| if (vw_out_tbl[sig_idx].sig == signal) { |
| reg_idx = vw_out_tbl[sig_idx].reg_idx; |
| bitmask = vw_out_tbl[sig_idx].bitmask; |
| |
| val = GET_FIELD(inst->VWEVSM[reg_idx], |
| NPCX_VWEVSM_WIRE); |
| val &= GET_FIELD(inst->VWEVSM[reg_idx], |
| NPCX_VWEVSM_VALID); |
| *level = !!(val & bitmask); |
| return 0; |
| } |
| |
| LOG_ERR("%s Out of index %d", __func__, signal); |
| return -EIO; |
| } |
| |
| static int espi_npcx_manage_callback(const struct device *dev, |
| struct espi_callback *callback, bool set) |
| { |
| struct espi_npcx_data *const data = dev->data; |
| |
| return espi_manage_callback(&data->callbacks, callback, set); |
| } |
| |
| static int espi_npcx_read_lpc_request(const struct device *dev, |
| enum lpc_peripheral_opcode op, |
| uint32_t *data) |
| { |
| ARG_UNUSED(dev); |
| |
| return npcx_host_periph_read_request(op, data); |
| } |
| |
| static int espi_npcx_write_lpc_request(const struct device *dev, |
| enum lpc_peripheral_opcode op, |
| uint32_t *data) |
| { |
| ARG_UNUSED(dev); |
| |
| return npcx_host_periph_write_request(op, data); |
| } |
| |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| static int espi_npcx_send_oob(const struct device *dev, |
| struct espi_oob_packet *pckt) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| uint8_t *oob_buf = pckt->buf; |
| int sz_oob_tx = pckt->len; |
| int idx_tx_buf; |
| uint32_t oob_data; |
| |
| /* Check out of OOB transmitted buffer size */ |
| if (sz_oob_tx > NPCX_ESPI_OOB_MAX_PAYLOAD) { |
| LOG_ERR("Out of OOB transmitted buffer: %d", sz_oob_tx); |
| return -EINVAL; |
| } |
| |
| /* Check OOB Transmit Queue is empty? */ |
| if (IS_BIT_SET(inst->OOBCTL, NPCX_OOBCTL_OOB_AVAIL)) { |
| LOG_ERR("OOB channel is busy"); |
| return -EBUSY; |
| } |
| |
| /* |
| * GET_OOB header (first 4 bytes) in npcx 32-bits tx buffer |
| * |
| * [24:31] - LEN[0:7] Data length of GET_OOB request package |
| * [20:23] - TAG Tag of GET_OOB |
| * [16:19] - LEN[8:11] Ignore it since max payload is 64 bytes |
| * [8:15] - CYCLE_TYPE Cycle type of GET_OOB |
| * [0:7] - SZ_PACK Package size plus 3 bytes header. (Npcx only) |
| */ |
| inst->OOBTXBUF[0] = (sz_oob_tx + 3) |
| | (ESPI_OOB_GET_CYCLE_TYPE << 8) |
| | (ESPI_OOB_TAG << 16) |
| | (sz_oob_tx << 24); |
| |
| /* Write GET_OOB data into 32-bits tx buffer in little endian */ |
| for (idx_tx_buf = 0; idx_tx_buf < sz_oob_tx/4; idx_tx_buf++, |
| oob_buf += 4) |
| inst->OOBTXBUF[idx_tx_buf + 1] = oob_buf[0] |
| | (oob_buf[1] << 8) |
| | (oob_buf[2] << 16) |
| | (oob_buf[3] << 24); |
| |
| /* Write remaining bytes of package */ |
| if (sz_oob_tx % 4) { |
| int i; |
| |
| oob_data = 0; |
| for (i = 0; i < sz_oob_tx % 4; i++) |
| oob_data |= (oob_buf[i] << (8 * i)); |
| inst->OOBTXBUF[idx_tx_buf + 1] = oob_data; |
| } |
| |
| /* |
| * Notify host a new OOB packet is ready. Please don't write OOB_FREE |
| * to 1 at the same tiem in case clear it unexpectedly. |
| */ |
| oob_data = inst->OOBCTL & ~(BIT(NPCX_OOBCTL_OOB_FREE)); |
| oob_data |= BIT(NPCX_OOBCTL_OOB_AVAIL); |
| inst->OOBCTL = oob_data; |
| |
| while (IS_BIT_SET(inst->OOBCTL, NPCX_OOBCTL_OOB_AVAIL)) |
| ; |
| |
| LOG_DBG("%s issued!!", __func__); |
| return 0; |
| } |
| |
| static int espi_npcx_receive_oob(const struct device *dev, |
| struct espi_oob_packet *pckt) |
| { |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| struct espi_npcx_data *const data = dev->data; |
| uint8_t *oob_buf = pckt->buf; |
| uint32_t oob_data; |
| int idx_rx_buf, sz_oob_rx, ret; |
| |
| /* Check eSPI bus status first */ |
| if (IS_BIT_SET(inst->ESPISTS, NPCX_ESPISTS_BERR)) { |
| LOG_ERR("%s: eSPI Bus Error: 0x%08X", __func__, inst->ESPIERR); |
| return -EIO; |
| } |
| |
| /* Notify host that OOB received buffer is free now. */ |
| inst->OOBCTL |= BIT(NPCX_OOBCTL_OOB_FREE); |
| |
| /* Wait until get oob package or timeout */ |
| ret = k_sem_take(&data->oob_rx_lock, K_MSEC(ESPI_OOB_MAX_TIMEOUT)); |
| if (ret == -EAGAIN) { |
| LOG_ERR("%s: Timeout", __func__); |
| return -ETIMEDOUT; |
| } |
| |
| /* |
| * PUT_OOB header (first 4 bytes) in npcx 32-bits rx buffer |
| * |
| * [24:31] - LEN[0:7] Data length of PUT_OOB request package |
| * [20:23] - TAG Tag of PUT_OOB |
| * [16:19] - LEN[8:11] Data length of PUT_OOB request package |
| * [8:15] - CYCLE_TYPE Cycle type of PUT_OOB |
| * [0:7] - SZ_PACK Reserved. (Npcx only) |
| */ |
| oob_data = inst->OOBRXBUF[0]; |
| /* Get received package length first */ |
| sz_oob_rx = NPCX_OOB_RX_PACKAGE_LEN(oob_data); |
| |
| /* Check OOB received buffer size */ |
| if (sz_oob_rx > NPCX_ESPI_OOB_MAX_PAYLOAD) { |
| LOG_ERR("Out of OOB received buffer: %d", sz_oob_rx); |
| return -EINVAL; |
| } |
| |
| /* Set received size to package structure */ |
| pckt->len = sz_oob_rx; |
| |
| /* Read PUT_OOB data into 32-bits rx buffer in little endian */ |
| for (idx_rx_buf = 0; idx_rx_buf < sz_oob_rx/4; idx_rx_buf++) { |
| oob_data = inst->OOBRXBUF[idx_rx_buf + 1]; |
| |
| *(oob_buf++) = oob_data & 0xFF; |
| *(oob_buf++) = (oob_data >> 8) & 0xFF; |
| *(oob_buf++) = (oob_data >> 16) & 0xFF; |
| *(oob_buf++) = (oob_data >> 24) & 0xFF; |
| } |
| |
| /* Read remaining bytes of package */ |
| if (sz_oob_rx % 4) { |
| int i; |
| |
| oob_data = inst->OOBRXBUF[idx_rx_buf + 1]; |
| for (i = 0; i < sz_oob_rx % 4; i++) |
| *(oob_buf++) = (oob_data >> (8 * i)) & 0xFF; |
| } |
| return 0; |
| } |
| #endif |
| |
| /* Platform specific espi module functions */ |
| void npcx_espi_enable_interrupts(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| /* Enable eSPI bus interrupt */ |
| irq_enable(DT_INST_IRQN(0)); |
| |
| /* Turn on all VW inputs' MIWU interrupts */ |
| for (int idx = 0; idx < ARRAY_SIZE(vw_in_tbl); idx++) { |
| npcx_miwu_irq_enable(&(vw_in_tbl[idx].vw_wui)); |
| } |
| } |
| |
| void npcx_espi_disable_interrupts(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| /* Disable eSPI bus interrupt */ |
| irq_disable(DT_INST_IRQN(0)); |
| |
| /* Turn off all VW inputs' MIWU interrupts */ |
| for (int idx = 0; idx < ARRAY_SIZE(vw_in_tbl); idx++) { |
| npcx_miwu_irq_disable(&(vw_in_tbl[idx].vw_wui)); |
| } |
| } |
| |
| /* eSPI driver registration */ |
| static int espi_npcx_init(const struct device *dev); |
| |
| static const struct espi_driver_api espi_npcx_driver_api = { |
| .config = espi_npcx_configure, |
| .get_channel_status = espi_npcx_channel_ready, |
| .send_vwire = espi_npcx_send_vwire, |
| .receive_vwire = espi_npcx_receive_vwire, |
| .manage_callback = espi_npcx_manage_callback, |
| .read_lpc_request = espi_npcx_read_lpc_request, |
| .write_lpc_request = espi_npcx_write_lpc_request, |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| .send_oob = espi_npcx_send_oob, |
| .receive_oob = espi_npcx_receive_oob, |
| #endif |
| }; |
| |
| static struct espi_npcx_data espi_npcx_data; |
| |
| PINCTRL_DT_INST_DEFINE(0); |
| BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) == 1, |
| "only one 'nuvoton_npcx_espi' compatible node may be present"); |
| |
| static const struct espi_npcx_config espi_npcx_config = { |
| .base = DT_INST_REG_ADDR(0), |
| .espi_rst_wui = NPCX_DT_WUI_ITEM_BY_NAME(0, espi_rst_wui), |
| .clk_cfg = NPCX_DT_CLK_CFG_ITEM(0), |
| .pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(0), |
| }; |
| |
| DEVICE_DT_INST_DEFINE(0, &espi_npcx_init, NULL, |
| &espi_npcx_data, &espi_npcx_config, |
| PRE_KERNEL_2, CONFIG_ESPI_INIT_PRIORITY, |
| &espi_npcx_driver_api); |
| |
| static int espi_npcx_init(const struct device *dev) |
| { |
| const struct espi_npcx_config *const config = dev->config; |
| struct espi_npcx_data *const data = dev->data; |
| struct espi_reg *const inst = HAL_INSTANCE(dev); |
| const struct device *const clk_dev = DEVICE_DT_GET(NPCX_CLK_CTRL_NODE); |
| int i, ret; |
| |
| /* If booter doesn't set the host interface type */ |
| if (!NPCX_BOOTER_IS_HIF_TYPE_SET()) { |
| npcx_host_interface_sel(NPCX_HIF_TYPE_ESPI_SHI); |
| } |
| |
| /* Turn on eSPI device clock first */ |
| ret = clock_control_on(clk_dev, (clock_control_subsys_t *) |
| &config->clk_cfg); |
| if (ret < 0) { |
| LOG_ERR("Turn on eSPI clock fail %d", ret); |
| return ret; |
| } |
| |
| if (IS_ENABLED(CONFIG_ESPI_NPCX_BYPASS_CH_ENABLE_FATAL_ERROR)) { |
| /* Enable the access to the NPCX_ONLY_ESPI_REG2 register */ |
| inst->NPCX_ONLY_ESPI_REG1 = NPCX_ONLY_ESPI_REG1_UNLOCK_REG2; |
| inst->NPCX_ONLY_ESPI_REG2 &= ~BIT(NPCX_ONLY_ESPI_REG2_TRANS_END_CONFIG); |
| /* Disable the access to the NPCX_ONLY_ESPI_REG2 register */ |
| inst->NPCX_ONLY_ESPI_REG1 = NPCX_ONLY_ESPI_REG1_LOCK_REG2; |
| } |
| |
| /* Enable events which share the same espi bus interrupt */ |
| for (i = 0; i < ARRAY_SIZE(espi_bus_isr_tbl); i++) { |
| inst->ESPIIE |= BIT(espi_bus_isr_tbl[i].int_en_bit); |
| inst->ESPIWE |= BIT(espi_bus_isr_tbl[i].wake_en_bit); |
| } |
| |
| #if defined(CONFIG_ESPI_OOB_CHANNEL) |
| k_sem_init(&data->oob_rx_lock, 0, 1); |
| #endif |
| |
| /* Configure Virtual Wire input signals */ |
| for (i = 0; i < ARRAY_SIZE(vw_in_tbl); i++) |
| espi_vw_config_input(dev, &vw_in_tbl[i]); |
| |
| /* Configure Virtual Wire output signals */ |
| for (i = 0; i < ARRAY_SIZE(vw_out_tbl); i++) |
| espi_vw_config_output(dev, &vw_out_tbl[i]); |
| |
| /* Configure wake-up input and callback for eSPI VW input signal */ |
| for (i = 0; i < ARRAY_SIZE(vw_in_tbl); i++) |
| espi_init_wui_callback(dev, &vw_in_callback[i], |
| &vw_in_tbl[i].vw_wui, espi_vw_generic_isr); |
| |
| /* Configure wake-up input and callback for ESPI_RST signal */ |
| espi_init_wui_callback(dev, &espi_rst_callback, |
| &config->espi_rst_wui, espi_vw_espi_rst_isr); |
| |
| /* Configure pin-mux for eSPI bus device */ |
| ret = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| LOG_ERR("eSPI pinctrl setup failed (%d)", ret); |
| return ret; |
| } |
| |
| /* Configure host sub-modules which HW blocks belong to core domain */ |
| npcx_host_init_subs_core_domain(dev, &data->callbacks); |
| |
| /* eSPI Bus interrupt installation */ |
| IRQ_CONNECT(DT_INST_IRQN(0), |
| DT_INST_IRQ(0, priority), |
| espi_bus_generic_isr, |
| DEVICE_DT_INST_GET(0), 0); |
| |
| /* Enable eSPI bus interrupt */ |
| irq_enable(DT_INST_IRQN(0)); |
| |
| return 0; |
| } |