diff --git a/drivers/usb_c/ppc/CMakeLists.txt b/drivers/usb_c/ppc/CMakeLists.txt index dc3fc83988c..2c109a48a6f 100644 --- a/drivers/usb_c/ppc/CMakeLists.txt +++ b/drivers/usb_c/ppc/CMakeLists.txt @@ -4,3 +4,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USBC_PPC_SHELL shell.c) zephyr_library_sources_ifdef(CONFIG_USBC_PPC_NX20P3483 nxp_nx20p3483.c) +zephyr_library_sources_ifdef(CONFIG_USBC_PPC_NUMAKER usbc_ppc_numaker.c) diff --git a/drivers/usb_c/ppc/Kconfig b/drivers/usb_c/ppc/Kconfig index c3fc93e1f4e..774b97db829 100644 --- a/drivers/usb_c/ppc/Kconfig +++ b/drivers/usb_c/ppc/Kconfig @@ -22,6 +22,7 @@ config USBC_PPC_SHELL Add useful shell commands to manipulate and debug the PPCs source "drivers/usb_c/ppc/Kconfig.nxp" +source "drivers/usb_c/ppc/Kconfig.numaker" module = USBC_PPC module-str = usbc-ppc diff --git a/drivers/usb_c/ppc/Kconfig.numaker b/drivers/usb_c/ppc/Kconfig.numaker new file mode 100644 index 00000000000..4edec3a569a --- /dev/null +++ b/drivers/usb_c/ppc/Kconfig.numaker @@ -0,0 +1,11 @@ +# Nuvoton NuMaker USB-C PPC device configuration options + +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +config USBC_PPC_NUMAKER + bool "Nuvoton NuMaker USB-C PPC" + default y + depends on DT_HAS_NUVOTON_NUMAKER_PPC_ENABLED && USBC_TCPC_NUMAKER + help + Enable USB-C PPC support for Nuvoton NuMaker chip with UTCPD. diff --git a/drivers/usb_c/ppc/usbc_ppc_numaker.c b/drivers/usb_c/ppc/usbc_ppc_numaker.c new file mode 100644 index 00000000000..a55327c639e --- /dev/null +++ b/drivers/usb_c/ppc/usbc_ppc_numaker.c @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_numaker_ppc + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(ppc_numaker, CONFIG_USBC_LOG_LEVEL); + +#include +#include + +#include "../tcpc/ucpd_numaker.h" + +/* Implementation notes on NuMaker TCPC/PPC/VBUS + * + * PPC and VBUS rely on TCPC/UTCPD and are just pseudo. They are completely + * implemented in TCPC/UTCPD. + */ + +/** + * @brief Immutable device context + */ +struct numaker_ppc_config { + const struct device *tcpc_dev; +}; + +/** + * @brief Initializes the usb-c ppc driver + * + * @retval 0 on success + * @retval -ENODEV if dependent TCPC device is not ready + */ +static int numaker_ppc_init(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + /* Rely on TCPC */ + if (!device_is_ready(tcpc_dev)) { + LOG_ERR("TCPC device not ready"); + return -ENODEV; + } + + return 0; +} + +/** + * @brief Check if PPC is in the dead battery mode + * + * @retval 1 if PPC is in the dead battery mode + * @retval 0 if PPC is not in the dead battery mode + * @retval -EIO if on failure + */ +static int numaker_ppc_is_dead_battery_mode(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_is_dead_battery_mode(tcpc_dev); +} + +/** + * @brief Request the PPC to exit from the dead battery mode + * + * @retval 0 if request was successfully sent + * @retval -EIO if on failure + */ +static int numaker_ppc_exit_dead_battery_mode(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_exit_dead_battery_mode(tcpc_dev); +} + +/** + * @brief Check if the PPC is sourcing the VBUS + * + * @retval 1 if the PPC is sourcing the VBUS + * @retval 0 if the PPC is not sourcing the VBUS + * @retval -EIO on failure + */ +static int numaker_ppc_is_vbus_source(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_is_vbus_source(tcpc_dev); +} + +/** + * @brief Check if the PPC is sinking the VBUS + * + * @retval 1 if the PPC is sinking the VBUS + * @retval 0 if the PPC is not sinking the VBUS + * @retval -EIO on failure + */ +static int numaker_ppc_is_vbus_sink(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_is_vbus_sink(tcpc_dev); +} + +/** + * @brief Set the state of VBUS sinking + * + * @retval 0 if success + * @retval -EIO on failure + */ +static int numaker_ppc_set_snk_ctrl(const struct device *dev, bool enable) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_set_snk_ctrl(tcpc_dev, enable); +} + +/** + * @brief Set the state of VBUS sourcing + * + * @retval 0 if success + * @retval -EIO on failure + */ +static int numaker_ppc_set_src_ctrl(const struct device *dev, bool enable) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_set_src_ctrl(tcpc_dev, enable); +} + +/** + * @brief Set the state of VBUS discharging + * + * @retval 0 if success + * @retval -EIO on failure + */ +static int numaker_ppc_set_vbus_discharge(const struct device *dev, bool enable) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_set_vbus_discharge(tcpc_dev, enable); +} + +/** + * @brief Check if VBUS is present + * + * @retval 1 if VBUS voltage is present + * @retval 0 if no VBUS voltage is detected + * @retval -EIO on failure + */ +static int numaker_ppc_is_vbus_present(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_is_vbus_present(tcpc_dev); +} + +/** + * @brief Set the callback used to notify about PPC events + * + * @retval 0 if success + */ +static int numaker_ppc_set_event_handler(const struct device *dev, usbc_ppc_event_cb_t handler, + void *data) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_set_event_handler(tcpc_dev, handler, data); +} + +/** + * @brief Print the values or PPC registers + * + * @retval 0 if success + * @retval -EIO on failure + */ +static int numaker_ppc_dump_regs(const struct device *dev) +{ + const struct numaker_ppc_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_ppc_dump_regs(tcpc_dev); +} + +static const struct usbc_ppc_driver_api numaker_ppc_driver_api = { + .is_dead_battery_mode = numaker_ppc_is_dead_battery_mode, + .exit_dead_battery_mode = numaker_ppc_exit_dead_battery_mode, + .is_vbus_source = numaker_ppc_is_vbus_source, + .is_vbus_sink = numaker_ppc_is_vbus_sink, + .set_snk_ctrl = numaker_ppc_set_snk_ctrl, + .set_src_ctrl = numaker_ppc_set_src_ctrl, + .set_vbus_discharge = numaker_ppc_set_vbus_discharge, + .is_vbus_present = numaker_ppc_is_vbus_present, + .set_event_handler = numaker_ppc_set_event_handler, + .dump_regs = numaker_ppc_dump_regs, +}; + +#define NUMAKER_TCPC(inst) DT_INST_PARENT(inst) + +#define PPC_NUMAKER_INIT(inst) \ + static const struct numaker_ppc_config numaker_ppc_config_##inst = { \ + .tcpc_dev = DEVICE_DT_GET(NUMAKER_TCPC(inst)), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, numaker_ppc_init, NULL, NULL, &numaker_ppc_config_##inst, \ + POST_KERNEL, CONFIG_USBC_PPC_INIT_PRIORITY, \ + &numaker_ppc_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(PPC_NUMAKER_INIT); diff --git a/drivers/usb_c/tcpc/CMakeLists.txt b/drivers/usb_c/tcpc/CMakeLists.txt index cc5eaf71ea6..eaba920e1a5 100644 --- a/drivers/usb_c/tcpc/CMakeLists.txt +++ b/drivers/usb_c/tcpc/CMakeLists.txt @@ -4,3 +4,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_SHELL shell.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_STM32 ucpd_stm32.c) +zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_NUMAKER ucpd_numaker.c) diff --git a/drivers/usb_c/tcpc/Kconfig b/drivers/usb_c/tcpc/Kconfig index 8d0e2ad1017..d5b0e9a65ab 100644 --- a/drivers/usb_c/tcpc/Kconfig +++ b/drivers/usb_c/tcpc/Kconfig @@ -26,6 +26,7 @@ config USBC_TCPC_SHELL Example functions are printing vbus, chip information and dumping registers. source "drivers/usb_c/tcpc/Kconfig.tcpc_stm32" +source "drivers/usb_c/tcpc/Kconfig.tcpc_numaker" module = USBC module-str = usbc diff --git a/drivers/usb_c/tcpc/Kconfig.tcpc_numaker b/drivers/usb_c/tcpc/Kconfig.tcpc_numaker new file mode 100644 index 00000000000..042a2e5e197 --- /dev/null +++ b/drivers/usb_c/tcpc/Kconfig.tcpc_numaker @@ -0,0 +1,13 @@ +# Nuvoton NuMaker USB-C TCPC device configuration options + +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +config USBC_TCPC_NUMAKER + bool "Nuvoton NuMaker USB-C TCPC device controller driver" + default y + select HAS_NUMAKER_ADC + select HAS_NUMAKER_TMR + depends on DT_HAS_NUVOTON_NUMAKER_TCPC_ENABLED + help + Enable USB-C TCPC support for Nuvoton NuMaker chip with UTCPD. diff --git a/drivers/usb_c/tcpc/ucpd_numaker.c b/drivers/usb_c/tcpc/ucpd_numaker.c new file mode 100644 index 00000000000..e50c3acf2e3 --- /dev/null +++ b/drivers/usb_c/tcpc/ucpd_numaker.c @@ -0,0 +1,2558 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_numaker_tcpc + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(tcpc_numaker, CONFIG_USBC_LOG_LEVEL); + +#include +#include + +#include "ucpd_numaker.h" + +/* Implementation notes on NuMaker TCPC/PPC/VBUS + * + * 1. UTCPD, interfacing to external circuit on VBUS/VCONN voltage measurement, + * VBUS/VCONN overcurrent protection, VBUS overvoltage protection, etc., + * can implement all functions defined in TCPC, PPC, and VBUS. For this, + * TCPC is implemented in UTCPD majorly; PPC and VBUS rely on TCPC for + * their implementation. + * 2. For VBUS/VCONN voltage measurement, UTCPD is updated periodically + * by Timer-trigger EADC. To implement this interconnection, TCPC node_id + * will cover UTCPD, EADC, and Timer H/W characteristics of registers, + * interrupts, resets, and clocks. + * NOTE: EADC and Timer interrupts needn't enable for Timer-triggered EADC. + * In BSP sample, they are enabled just for development/debug purpose. + * 3. About VCONN per PCB + * (1) Support only VCONN source, no VCONN sink (like Plug Cable) + * (2) Separate pins for VCONN enable on CC1/CC2 (VCNEN1/VCNEN2) + * (3) Single pin for VCONN discharge (DISCHG) + * 4. VBUS discharge precedence + * (1) GPIO + * (2) UTCPD + * 5. VCONN discharge precedence + * (1) DPM-supplied callback + * (2) GPIO + * (3) UTCPD + */ + +/** + * @brief Invalid or missing value + */ +#define NUMAKER_INVALID_VALUE UINT32_MAX + +/** + * @brief UTCPD VBUS threshold default in mV + * + * These are default values of UTCPD VBUS threshold registers. They need + * to be reconfigured by taking the following factors into consideration: + * 1. Analog Vref + * 2. UTCPD VBVOL.VBSCALE + */ +#define NUMAKER_UTCPD_VBUS_THRESHOLD_OVERVOLTAGE_MV 25000 +#define NUMAKER_UTCPD_VBUS_THRESHOLD_VSAFE5V_MV 5000 +#define NUMAKER_UTCPD_VBUS_THRESHOLD_VSAFE0V_MV 0 +#define NUMAKER_UTCPD_VBUS_THRESHOLD_STOP_FORCE_DISCHARGE_MV 800 +#define NUMAKER_UTCPD_VBUS_THRESHOLD_SINK_DISCONNECT_MV 3500 + +/** + * @brief SYS register dump + */ +#define NUMAKER_SYS_REG_DUMP(dev, reg_name) LOG_INF("SYS: %8s: 0x%08x", #reg_name, SYS->reg_name); + +/** + * @brief GPIO register dump + */ +#define NUMAKER_GPIO_REG_DUMP(dev, port, reg_name) \ + LOG_INF("%s: %8s: 0x%08x", #port, #reg_name, port->reg_name); + +/** + * @brief UTCPD register write timeout in microseconds + */ +#define NUMAKER_UTCPD_REG_WRITE_BY_NAME_TIMEOUT_US 20000 + +/** + * @brief UTCPD register write by name + */ +#define NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, reg_name, val) \ + ({ \ + int rc_intern = numaker_utcpd_reg_write_wait_ready(dev); \ + if (rc_intern < 0) { \ + LOG_ERR("UTCPD register (%s) write timeout", #reg_name); \ + } else { \ + utcpd_base->reg_name = (val); \ + } \ + rc_intern; \ + }) + +/** + * @brief UTCPD register force write by name + */ +#define NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, reg_name, val) \ + ({ \ + int rc_intern = numaker_utcpd_reg_write_wait_ready(dev); \ + if (rc_intern < 0) { \ + LOG_ERR("UTCPD register (%s) write timeout, force-write", #reg_name); \ + } \ + utcpd_base->reg_name = (val); \ + rc_intern; \ + }) + +/** + * @brief UTCPD register write by offset + */ +#define NUMAKER_UTCPD_REG_WRITE_BY_OFFSET(dev, reg_offset, val) \ + ({ \ + int rc_intern = numaker_utcpd_reg_write_wait_ready(dev); \ + if (rc_intern < 0) { \ + LOG_ERR("UTCPD register (0x%04x) write timeout", reg_offset); \ + } else { \ + sys_write32((val), ((uintptr_t)utcpd_base) + reg_offset); \ + } \ + rc_intern; \ + }) + +/** + * @brief UTCPD register force write by offset + */ +#define NUMAKER_UTCPD_REG_FORCE_WRITE_BY_OFFSET(dev, reg_offset, val) \ + ({ \ + int rc_intern = numaker_utcpd_reg_write_wait_ready(dev); \ + if (rc_intern < 0) { \ + LOG_ERR("UTCPD register (0x%04x) write timeout, force-write", reg_offset); \ + } \ + sys_write32((val), ((uintptr_t)utcpd_base) + reg_offset); \ + rc_intern; \ + }) + +/** + * @brief UTCPD register read by name + */ +#define NUMAKER_UTCPD_REG_READ_BY_NAME(dev, reg_name) ({ utcpd_base->reg_name; }) + +/** + * @brief UTCPD register read by offset + */ +#define NUMAKER_UTCPD_REG_READ_BY_OFFSET(dev, reg_offset) \ + ({ sys_read32(((uintptr_t)utcpd_base) + reg_offset); }) + +/** + * @brief UTCPD register dump + */ +#define NUMAKER_UTCPD_REG_DUMP(dev, reg_name) \ + LOG_INF("UTCPD: %8s: 0x%08x", #reg_name, NUMAKER_UTCPD_REG_READ_BY_NAME(dev, reg_name)); + +/** + * @brief Helper to write UTCPD VBUS threshold + */ +#define NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE(dev, reg_name, mv_norm) \ + ({ \ + uint32_t mv_bit; \ + mv_bit = numaker_utcpd_vbus_volt_mv2bit(dev, mv_norm); \ + mv_bit <<= UTCPD_##reg_name##_##reg_name##_Pos; \ + mv_bit &= UTCPD_##reg_name##_##reg_name##_Msk; \ + NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, reg_name, mv_bit); \ + }) + +/** + * @brief Helper to read UTCPD VBUS threshold + */ +#define NUMAKER_UTCPD_VBUS_THRESHOLD_READ(dev, reg_name) \ + ({ \ + uint32_t mv_bit; \ + mv_bit = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, reg_name); \ + mv_bit &= UTCPD_##reg_name##_##reg_name##_Msk; \ + mv_bit >>= UTCPD_##reg_name##_##reg_name##_Pos; \ + numaker_utcpd_vbus_volt_bit2mv(dev, mv_bit); \ + }) + +/** + * @brief Immutable device context + */ +struct numaker_tcpc_config { + UTCPD_T *utcpd_base; + EADC_T *eadc_base; + TIMER_T *timer_base; + const struct device *clkctrl_dev; + struct numaker_scc_subsys pcc_utcpd; + struct numaker_scc_subsys pcc_timer; + struct reset_dt_spec reset_utcpd; + struct reset_dt_spec reset_timer; + void (*irq_config_func_utcpd)(const struct device *dev); + void (*irq_unconfig_func_utcpd)(const struct device *dev); + const struct pinctrl_dev_config *pincfg; + struct { + struct { + struct gpio_dt_spec vbus_detect; + struct gpio_dt_spec vbus_discharge; + struct gpio_dt_spec vconn_discharge; + } gpios; + + bool dead_battery; + struct { + uint32_t bit; + } pinpl; + struct { + struct { + uint32_t bit; + uint32_t value; + } vbscale; + } vbvol; + } utcpd; + struct { + const struct adc_dt_spec *spec_vbus; + const struct adc_dt_spec *spec_vconn; + /* Rate of timer-triggered voltage measurement (Hz) */ + uint32_t timer_trigger_rate; + /* Trigger source for measuring VBUS/VCONN voltage */ + uint32_t trgsel_vbus; + uint32_t trgsel_vconn; + } eadc; +}; + +/** + * @brief Mutable device context + */ +struct numaker_tcpc_data { + enum tc_rp_value rp; + bool rx_sop_prime_enabled; + + /* One-slot Rx FIFO */ + bool rx_msg_ready; + struct pd_msg rx_msg; + + /* The fields below must persist across tcpc_init(). */ + + uint32_t vref_mv; + + /* TCPC alert */ + struct { + tcpc_alert_handler_cb_t handler; + void *data; + } tcpc_alert; + + /* PPC event */ + struct { + usbc_ppc_event_cb_t handler; + void *data; + } ppc_event; + + /* DPM supplied */ + struct { + /* VCONN callback function */ + tcpc_vconn_control_cb_t vconn_cb; + /* VCONN discharge callback function */ + tcpc_vconn_discharge_cb_t vconn_discharge_cb; + } dpm; +}; + +/** + * @brief Wait ready for next write access to UTCPD register + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_reg_write_wait_ready(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + + if (!WAIT_FOR((utcpd_base->CLKINFO & UTCPD_CLKINFO_ReadyFlag_Msk), + NUMAKER_UTCPD_REG_WRITE_BY_NAME_TIMEOUT_US, NULL)) { + return -EIO; + } + + return 0; +} + +/** + * @brief Convert VBUS voltage format from H/W bit to mV + * + * The following factors are taken into consideration: + * 1. Analog Vref + * 2. UTCPD VBVOL.VBSCALE + * + * @note UTCPD VBVOL.VBVOL = MSB 10-bit of EADC DAT.RESULT[11:0], + * that is, discarding LSB 2-bit. + */ +static uint32_t numaker_utcpd_vbus_volt_bit2mv(const struct device *dev, uint32_t bit) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + + __ASSERT_NO_MSG(data->vref_mv); + return (uint32_t)(((uint64_t)bit) * data->vref_mv * config->utcpd.vbvol.vbscale.value / + BIT_MASK(10)); +} + +/** + * @brief Convert VBUS voltage format from mV to H/W bit + * + * The following factors are taken into consideration: + * 1. Analog Vref + * 2. UTCPD VBVOL.VBSCALE + * + * @note UTCPD VBVOL.VBVOL = MSB 10-bit of EADC DAT.RESULT[11:0], + * that is, discarding LSB 2-bit. + */ +static uint32_t numaker_utcpd_vbus_volt_mv2bit(const struct device *dev, uint32_t mv) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + + __ASSERT_NO_MSG(data->vref_mv); + return mv * BIT_MASK(10) / data->vref_mv / config->utcpd.vbvol.vbscale.value; +} + +/** + * @brief UTCPD register dump + * + * @retval 0 on success + */ +static int numaker_utcpd_dump_regs(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + + /* SYS register */ + NUMAKER_SYS_REG_DUMP(dev, VREFCTL); + NUMAKER_SYS_REG_DUMP(dev, UTCPDCTL); + + /* UTCPD register */ + NUMAKER_UTCPD_REG_DUMP(dev, IS); + NUMAKER_UTCPD_REG_DUMP(dev, IE); + NUMAKER_UTCPD_REG_DUMP(dev, PWRSTSIE); + NUMAKER_UTCPD_REG_DUMP(dev, FUTSTSIE); + NUMAKER_UTCPD_REG_DUMP(dev, CTL); + NUMAKER_UTCPD_REG_DUMP(dev, PINPL); + NUMAKER_UTCPD_REG_DUMP(dev, ROLCTL); + NUMAKER_UTCPD_REG_DUMP(dev, FUTCTL); + NUMAKER_UTCPD_REG_DUMP(dev, PWRCTL); + NUMAKER_UTCPD_REG_DUMP(dev, CCSTS); + NUMAKER_UTCPD_REG_DUMP(dev, PWRSTS); + NUMAKER_UTCPD_REG_DUMP(dev, FUTSTS); + NUMAKER_UTCPD_REG_DUMP(dev, DVCAP1); + NUMAKER_UTCPD_REG_DUMP(dev, DVCAP2); + NUMAKER_UTCPD_REG_DUMP(dev, MSHEAD); + NUMAKER_UTCPD_REG_DUMP(dev, DTRXEVNT); + NUMAKER_UTCPD_REG_DUMP(dev, VBVOL); + NUMAKER_UTCPD_REG_DUMP(dev, SKVBDCTH); + NUMAKER_UTCPD_REG_DUMP(dev, SPDGTH); + NUMAKER_UTCPD_REG_DUMP(dev, VBAMH); + NUMAKER_UTCPD_REG_DUMP(dev, VBAML); + NUMAKER_UTCPD_REG_DUMP(dev, VNDIS); + NUMAKER_UTCPD_REG_DUMP(dev, VNDIE); + NUMAKER_UTCPD_REG_DUMP(dev, MUXSEL); + NUMAKER_UTCPD_REG_DUMP(dev, VCDGCTL); + NUMAKER_UTCPD_REG_DUMP(dev, ADGTM); + NUMAKER_UTCPD_REG_DUMP(dev, VSAFE0V); + NUMAKER_UTCPD_REG_DUMP(dev, VSAFE5V); + NUMAKER_UTCPD_REG_DUMP(dev, VBOVTH); + NUMAKER_UTCPD_REG_DUMP(dev, VCPSVOL); + NUMAKER_UTCPD_REG_DUMP(dev, VCUV); + NUMAKER_UTCPD_REG_DUMP(dev, PHYCTL); + NUMAKER_UTCPD_REG_DUMP(dev, FRSRXCTL); + NUMAKER_UTCPD_REG_DUMP(dev, VCVOL); + NUMAKER_UTCPD_REG_DUMP(dev, CLKINFO); + + return 0; +} + +/** + * @brief Initializes EADC Vref + * + * @retval 0 on success + */ +static int numaker_eadc_vref_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + const struct adc_dt_spec *spec; + enum adc_reference reference; + + if (data->vref_mv) { + return 0; + } + + /* NOTE: Register protection lock will restore automatically. Unlock it again. */ + SYS_UnlockReg(); + + /* Analog reference voltage + * + * NOTE: For Vref being internal, external Vref pin must be floating, + * or it can disturb. + */ + spec = config->eadc.spec_vbus ? config->eadc.spec_vbus : config->eadc.spec_vconn; + if (spec == NULL) { + return 0; + } + /* ADC device ready */ + if (!adc_is_ready_dt(spec)) { + LOG_ERR("ADC device for VBUS/VCONN not ready"); + return -ENODEV; + } + + /* ADC channel configuration ready */ + if (!spec->channel_cfg_dt_node_exists) { + LOG_ERR("ADC channel configuration for VBUS/VCONN not specified"); + return -ENODEV; + } + + reference = spec->channel_cfg.reference; + + SYS->VREFCTL &= ~SYS_VREFCTL_VREFCTL_Msk; + if (reference == ADC_REF_EXTERNAL0 || reference == ADC_REF_EXTERNAL1) { + SYS->VREFCTL |= SYS_VREFCTL_VREF_PIN; + } else if (reference == ADC_REF_INTERNAL) { + switch (spec->vref_mv) { + case 1600: + SYS->VREFCTL |= SYS_VREFCTL_VREF_1_6V; + break; + case 2000: + SYS->VREFCTL |= SYS_VREFCTL_VREF_2_0V; + break; + case 2500: + SYS->VREFCTL |= SYS_VREFCTL_VREF_2_5V; + break; + case 3000: + SYS->VREFCTL |= SYS_VREFCTL_VREF_3_0V; + break; + default: + LOG_ERR("Invalid Vref voltage"); + return -ENOTSUP; + } + } else { + LOG_ERR("Invalid Vref source"); + return -ENOTSUP; + } + + data->vref_mv = spec->vref_mv; + + return 0; +} + +/** + * @brief Reads and returns UTCPD VBUS measured in mV + * + * @retval 0 on success + * @retval -EIO on failure + */ +int numaker_utcpd_vbus_measure(const struct device *dev, uint32_t *mv) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + + if (mv == NULL) { + return -EINVAL; + } + *mv = 0; + + if (config->eadc.spec_vbus == NULL) { + return -ENOTSUP; + } + + /* Vref */ + rc = numaker_eadc_vref_init(dev); + if (rc < 0) { + return rc; + } + + *mv = NUMAKER_UTCPD_VBUS_THRESHOLD_READ(dev, VBVOL); + + return 0; +} + +/** + * @brief Check if the UTCPD VBUS is present + * + * @retval 1 if UTCPD VBUS is present + * @retval 0 if UTCPD VBUS is not present + */ +int numaker_utcpd_vbus_is_present(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + if (pwrsts & UTCPD_PWRSTS_VBPS_Msk) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Check if the UTCPD VBUS is sourcing + * + * @retval 1 if UTCPD VBUS is sourcing + * @retval 0 if UTCPD VBUS is not sourcing + */ +int numaker_utcpd_vbus_is_source(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + if (pwrsts & (UTCPD_PWRSTS_SRHV_Msk | UTCPD_PWRSTS_SRVB_Msk)) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Check if the UTCPD VBUS is sinking + * + * @retval 1 if UTCPD VBUS is sinking + * @retval 0 if UTCPD VBUS is not sinking + */ +int numaker_utcpd_vbus_is_sink(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + if (pwrsts & UTCPD_PWRSTS_SKVB_Msk) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Enable or disable discharge on UTCPD VBUS + * + * @retval 0 on success + * @retval -EIO on failure + */ +int numaker_utcpd_vbus_set_discharge(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + const struct gpio_dt_spec *vbus_discharge_spec = &config->utcpd.gpios.vbus_discharge; + uint32_t pwrctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRCTL); + + /* Use GPIO VBUS discharge */ + if (vbus_discharge_spec->port != NULL) { + return gpio_pin_set_dt(vbus_discharge_spec, enable); + } + + /* Use UTCPD VBUS discharge */ + if (enable) { + pwrctl |= UTCPD_PWRCTL_FDGEN_Msk; + } else { + pwrctl &= ~UTCPD_PWRCTL_FDGEN_Msk; + } + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PWRCTL, pwrctl); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * @brief Enable or disable UTCPD BIST test mode + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_bist_test_mode_set_enable(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t ctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CTL); + + /* Enable or not BIST test mode */ + if (enable) { + ctl |= UTCPD_CTL_BISTEN_Msk; + } else { + ctl &= ~UTCPD_CTL_BISTEN_Msk; + } + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, CTL, ctl); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * @brief Check if UTCPD BIST test mode is enabled + * + * @retval 1 if UTCPD BIST test mode is enabled + * @retval 0 if UTCPD BIST test mode is not enabled + */ +static int numaker_utcpd_bist_test_mode_is_enabled(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t ctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CTL); + + if (ctl & UTCPD_CTL_BISTEN_Msk) { + return 1; + } else { + return 0; + } +} + +/** + * @brief Clears UTCPD Rx message FIFO + * + * @retval 0 on success + */ +static int numaker_utcpd_rx_fifo_clear(const struct device *dev) +{ + struct numaker_tcpc_data *data = dev->data; + + data->rx_msg_ready = false; + + return 0; +} + +/** + * @brief Reads Rx message data from UTCPD + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_rx_read_data(const struct device *dev, uint8_t *rx_data, + uint32_t rx_data_size) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t data_rmn = rx_data_size; + uint8_t *data_pos = rx_data; + uintptr_t data_reg_offset = offsetof(UTCPD_T, RXDA0); + uint32_t data_value; + + /* 32-bit aligned */ + while (data_rmn >= 4) { + data_value = NUMAKER_UTCPD_REG_READ_BY_OFFSET(dev, data_reg_offset); + sys_put_le32(data_value, data_pos); + + /* Next data */ + data_reg_offset += 4; + data_pos += 4; + data_rmn -= 4; + } + + /* Remaining non-32-bit aligned */ + __ASSERT_NO_MSG(data_rmn < 4); + if (data_rmn) { + data_value = NUMAKER_UTCPD_REG_READ_BY_OFFSET(dev, data_reg_offset); + data_reg_offset += 4; + + switch (data_rmn) { + case 3: + sys_put_le24(data_value, data_pos); + data_pos += 3; + data_rmn -= 3; + break; + + case 2: + sys_put_le16(data_value, data_pos); + data_pos += 2; + data_rmn -= 2; + break; + + case 1: + *data_pos = data_value; + data_pos += 1; + data_rmn -= 1; + break; + } + } + + __ASSERT_NO_MSG(data_rmn == 0); + + return 0; +} + +/** + * @brief Writes Tx message data to UTCPD + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_tx_write_data(const struct device *dev, const uint8_t *tx_data, + uint32_t tx_data_size) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t data_rmn = tx_data_size; + const uint8_t *data_pos = tx_data; + uint32_t data_reg_offset = offsetof(UTCPD_T, TXDA0); + uint32_t data_value; + + /* 32-bit aligned */ + while (data_rmn >= 4) { + data_value = sys_get_le32(data_pos); + rc = NUMAKER_UTCPD_REG_WRITE_BY_OFFSET(dev, data_reg_offset, data_value); + if (rc < 0) { + return rc; + } + + /* Next data */ + data_pos += 4; + data_reg_offset += 4; + data_rmn -= 4; + } + + /* Remaining non-32-bit aligned */ + __ASSERT_NO_MSG(data_rmn < 4); + if (data_rmn) { + switch (data_rmn) { + case 3: + data_value = sys_get_le24(data_pos); + data_pos += 3; + data_rmn -= 3; + break; + + case 2: + data_value = sys_get_le16(data_pos); + data_pos += 2; + data_rmn -= 2; + break; + + case 1: + data_value = *data_pos; + data_pos += 1; + data_rmn -= 1; + break; + } + rc = NUMAKER_UTCPD_REG_WRITE_BY_OFFSET(dev, data_reg_offset, data_value); + if (rc < 0) { + return rc; + } + data_reg_offset += 4; + } + + __ASSERT_NO_MSG(data_rmn == 0); + + return 0; +} + +/** + * @brief Enqueues UTCPD Rx message + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_rx_fifo_enqueue(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc = 0; + uint32_t rxbcnt = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, RXBCNT); + uint32_t rxftype = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, RXFTYPE); + uint32_t rxhead = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, RXHEAD); + uint32_t is = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, IS); + uint32_t rx_data_size; + struct pd_msg *msg = &data->rx_msg; + + /* Rx message pending? */ + if (!(is & UTCPD_IS_RXSOPIS_Msk)) { + goto cleanup; + } + + /* rxbcnt = 1 (frame type) + 2 (Message Header) + Rx data byte count */ + if (rxbcnt < 3) { + LOG_ERR("Invalid UTCPD.RXBCNT: %d", rxbcnt); + rc = -EIO; + goto cleanup; + } + rx_data_size = rxbcnt - 3; + + /* Not support Unchunked Extended Message exceeding PD_CONVERT_PD_HEADER_COUNT_TO_BYTES */ + if (rx_data_size > (PD_MAX_EXTENDED_MSG_LEGACY_LEN + 2)) { + LOG_ERR("Not support Unchunked Extended Message exceeding " + "PD_CONVERT_PD_HEADER_COUNT_TO_BYTES: %d", + rx_data_size); + rc = -EIO; + goto cleanup; + } + + /* Rx FIFO has room? */ + if (data->rx_msg_ready) { + LOG_WRN("Rx FIFO overflow"); + } + + /* Rx frame type */ + /* NOTE: Needn't extra cast for UTCPD_RXFTYPE.RXFTYPE aligning with pd_packet_type */ + msg->type = (rxftype & UTCPD_RXFTYPE_RXFTYPE_Msk) >> UTCPD_RXFTYPE_RXFTYPE_Pos; + + /* Rx header */ + msg->header.raw_value = (uint16_t)rxhead; + + /* Rx data size */ + msg->len = rx_data_size; + + /* Rx data */ + rc = numaker_utcpd_rx_read_data(dev, msg->data, rx_data_size); + if (rc < 0) { + goto cleanup; + } + + /* Finish enqueue of this Rx message */ + data->rx_msg_ready = true; + +cleanup: + + /* This has side effect of clearing UTCPD_RXBCNT and friends. */ + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, IS, UTCPD_IS_RXSOPIS_Msk); + + return rc; +} + +/** + * @brief Notify TCPC alert + */ +static void numaker_utcpd_notify_tcpc_alert(const struct device *dev, enum tcpc_alert alert) +{ + struct numaker_tcpc_data *data = dev->data; + tcpc_alert_handler_cb_t alert_handler = data->tcpc_alert.handler; + void *alert_data = data->tcpc_alert.data; + + if (alert_handler) { + alert_handler(dev, alert_data, alert); + } +} + +/** + * @brief Notify PPC event + */ +static void numaker_utcpd_notify_ppc_event(const struct device *dev, enum usbc_ppc_event event) +{ + struct numaker_tcpc_data *data = dev->data; + usbc_ppc_event_cb_t event_handler = data->ppc_event.handler; + void *event_data = data->ppc_event.data; + + if (event_handler) { + event_handler(dev, event_data, event); + } +} + +/** + * @brief UTCPD ISR + * + * @note UTCPD register write cannot be failed, or we may trap in ISR for + * interrupt bits not cleared. To avoid that, we use "force-write" + * version clear interrupt bits for sure. + */ +static void numaker_utcpd_isr(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t is = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, IS); + uint32_t futsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, FUTSTS); + uint32_t vndis = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, VNDIS); + uint32_t ie = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, IE); + uint32_t futstsie = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, FUTSTSIE); + + /* CC status changed */ + if (is & UTCPD_IS_CCSCHIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_CC_STATUS); + } + + /* Power status changed */ + if (is & UTCPD_IS_PWRSCHIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_POWER_STATUS); + } + + /* Received SOP Message */ + if (is & UTCPD_IS_RXSOPIS_Msk) { + numaker_utcpd_rx_fifo_enqueue(dev); + + /* Per TCPCI 4.4.5.1 TCPC_CONTROL, BIST Test Mode + * Incoming messages enabled by RECEIVE_DETECT result + * in GoodCRC response but may not be passed to the TCPM + * via Alert. TCPC may temporarily store incoming messages + * in the Receive Message Buffer, but this may or may not + * result in a Receive SOP* Message Status or a Rx Buffer + * Overflow alert. + */ + if (numaker_utcpd_bist_test_mode_is_enabled(dev) == 1) { + numaker_utcpd_rx_fifo_clear(dev); + } else { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_MSG_STATUS); + } + } + + /* Rx buffer overflow */ + if (is & UTCPD_IS_RXOFIS_Msk) { + LOG_WRN("Rx buffer overflow"); + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_RX_BUFFER_OVERFLOW); + } + + /* Received Hard Reset */ + if (is & UTCPD_IS_RXHRSTIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_HARD_RESET_RECEIVED); + } + + /* SOP* message transmission not successful, no GoodCRC response received on SOP* message + * transmission + */ + if (is & UTCPD_IS_TXFALIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_TRANSMIT_MSG_FAILED); + } + + /* Reset or SOP* message transmission not sent due to incoming receive message */ + if (is & UTCPD_IS_TXDCUDIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_TRANSMIT_MSG_DISCARDED); + } + + /* Reset or SOP* message transmission successful, GoodCRC response received on SOP* message + * transmission + */ + if (is & UTCPD_IS_TXOKIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_TRANSMIT_MSG_SUCCESS); + } + + /* VBUS voltage alarm high */ + if ((is & UTCPD_IS_VBAMHIS_Msk) && (ie & UTCPD_IS_VBAMHIS_Msk)) { + LOG_WRN("UTCPD VBUS voltage alarm high not addressed, disable the alert"); + ie &= ~UTCPD_IS_VBAMHIS_Msk; + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, IE, ie); + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_VBUS_ALARM_HI); + } + + /* VBUS voltage alarm low */ + if ((is & UTCPD_IS_VBAMLIS_Msk) && (ie & UTCPD_IS_VBAMLIS_Msk)) { + LOG_WRN("UTCPD VBUS voltage alarm low not addressed, disable the alert"); + ie &= ~UTCPD_IS_VBAMLIS_Msk; + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, IE, ie); + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_VBUS_ALARM_LO); + } + + /* Fault */ + if ((is & UTCPD_IS_FUTIS_Msk) && (futstsie & futsts)) { + LOG_ERR("UTCPD fault (FUTSTS=0x%08x)", futsts); + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_OFFSET(dev, offsetof(UTCPD_T, FUTSTS), futsts); + /* NOTE: FUTSTSIE will restore to default on Hard Reset. We may re-enter + * here and redo mask. + */ + LOG_WRN("UTCPD fault (FUTSTS=0x%08x) not addressed, disable fault alert (FUTSTSIE)", + futsts); + futstsie &= ~futsts; + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, FUTSTSIE, futstsie); + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_FAULT_STATUS); + + /* VBUS overvoltage */ + if (futsts & UTCPD_FUTSTS_VBOVFUT_Msk) { + if (numaker_utcpd_vbus_is_source(dev)) { + numaker_utcpd_notify_ppc_event(dev, USBC_PPC_EVENT_SRC_OVERVOLTAGE); + } + + if (numaker_utcpd_vbus_is_sink(dev)) { + numaker_utcpd_notify_ppc_event(dev, USBC_PPC_EVENT_SNK_OVERVOLTAGE); + } + } + + /* VBUS overcurrent */ + if (futsts & UTCPD_FUTSTS_VBOCFUT_Msk) { + if (numaker_utcpd_vbus_is_source(dev)) { + numaker_utcpd_notify_ppc_event(dev, USBC_PPC_EVENT_SRC_OVERCURRENT); + } + } + } + + /* VBUS Sink disconnect threshold crossing has been detected */ + if (is & UTCPD_IS_SKDCDTIS_Msk) { + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_VBUS_SNK_DISCONNECT); + } + + /* Vendor defined event detected */ + if (is & UTCPD_IS_VNDIS_Msk) { + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, VNDIS, vndis); + + numaker_utcpd_notify_tcpc_alert(dev, TCPC_ALERT_VENDOR_DEFINED); + } + + NUMAKER_UTCPD_REG_FORCE_WRITE_BY_NAME(dev, IS, is); +} + +/** + * @brief Configures EADC sample module with trigger source, channel, etc. + */ +static int numaker_eadc_smplmod_init(const struct device *dev, const struct adc_dt_spec *spec, + uint32_t trgsel) +{ + const struct numaker_tcpc_config *const config = dev->config; + EADC_T *eadc_base = config->eadc_base; + uint16_t acquisition_time; + uint16_t acq_time_unit; + uint16_t acq_time_value; + + __ASSERT_NO_MSG(spec); + + /* ADC device ready */ + if (!adc_is_ready_dt(spec)) { + LOG_ERR("ADC device for VBUS/VCONN not ready"); + return -ENODEV; + } + + /* ADC channel configuration ready */ + if (!spec->channel_cfg_dt_node_exists) { + LOG_ERR("ADC channel configuration for VBUS/VCONN not specified"); + return -ENODEV; + } + + acquisition_time = spec->channel_cfg.acquisition_time; + acq_time_unit = ADC_ACQ_TIME_UNIT(acquisition_time); + acq_time_value = ADC_ACQ_TIME_VALUE(acquisition_time); + if (acq_time_unit != ADC_ACQ_TIME_TICKS) { + LOG_ERR("Invalid acquisition time unit for VBUS/VCONN"); + return -ENOTSUP; + } + + /* Bind sample module with trigger source and channel */ + EADC_ConfigSampleModule(eadc_base, spec->channel_id, trgsel, spec->channel_id); + /* Extend sampling time */ + EADC_SetExtendSampleTime(eadc_base, spec->channel_id, acq_time_value); + + return 0; +} + +/** + * @brief Initializes VBUS threshold and monitor + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_vbus_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t vbvol = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, VBVOL); + uint32_t pwrctl = 0; + + /* UTCPD VBUS scale factor */ + vbvol &= ~UTCPD_VBVOL_VBSCALE_Msk; + vbvol |= config->utcpd.vbvol.vbscale.bit; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, VBVOL, vbvol); + if (rc < 0) { + return rc; + } + + if (config->eadc.spec_vbus != NULL) { + /* Vref */ + rc = numaker_eadc_vref_init(dev); + if (rc < 0) { + return rc; + } + + /* UTCPD VBUS overvoltage threshold */ + rc = NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE( + dev, VBOVTH, NUMAKER_UTCPD_VBUS_THRESHOLD_OVERVOLTAGE_MV); + if (rc < 0) { + return rc; + } + + /* UTCPD VBUS vSafe5V threshold */ + rc = NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE(dev, VSAFE5V, + NUMAKER_UTCPD_VBUS_THRESHOLD_VSAFE5V_MV); + if (rc < 0) { + return rc; + } + + /* UTCPD VBUS vSafe0V threshold */ + rc = NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE(dev, VSAFE0V, + NUMAKER_UTCPD_VBUS_THRESHOLD_VSAFE0V_MV); + if (rc < 0) { + return rc; + } + + /* UTCPD VBUS stop force discharge threshold */ + rc = NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE( + dev, SPDGTH, NUMAKER_UTCPD_VBUS_THRESHOLD_STOP_FORCE_DISCHARGE_MV); + if (rc < 0) { + return rc; + } + + /* UTCPD VBUS sink disconnect threshold */ + rc = NUMAKER_UTCPD_VBUS_THRESHOLD_WRITE( + dev, SKVBDCTH, NUMAKER_UTCPD_VBUS_THRESHOLD_SINK_DISCONNECT_MV); + if (rc < 0) { + return rc; + } + } + + /* Enable UTCPD VBUS voltage monitor so that UTCPD.VBVOL is available */ + if (config->eadc.spec_vbus != NULL) { + pwrctl &= ~UTCPD_PWRCTL_VBMONI_DIS; + } else { + pwrctl |= UTCPD_PWRCTL_VBMONI_DIS; + } + /* Disable UTCPD VBUS voltage alarms */ + pwrctl |= UTCPD_PWRCTL_DSVBAM_DIS; + /* Disable UTCPD VBUS auto-discharge on disconnect + * NOTE: UTCPD may not integrate with discharge, so this feature is + * disabled and discharge is handled separately. + */ + pwrctl &= ~UTCPD_PWRCTL_ADGDC; + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PWRCTL, pwrctl); +} + +/** + * @brief Initializes UTCPD GPIO pins + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_gpios_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + int rc; + const struct gpio_dt_spec *spec; + + /* Configure VBUS detect pin to INPUT to avoid intervening its power measurement */ + spec = &config->utcpd.gpios.vbus_detect; + if (spec->port == NULL) { + LOG_ERR("VBUS detect pin not specified"); + return -ENODEV; + } + if (!gpio_is_ready_dt(spec)) { + LOG_ERR("VBUS detect pin port device not ready"); + return -ENODEV; + } + rc = gpio_pin_configure_dt(spec, GPIO_INPUT); + if (rc < 0) { + LOG_ERR("VBUS detect pin configured to INPUT failed: %d", rc); + return rc; + } + + /* Configure VBUS discharge pin to OUTPUT INACTIVE */ + spec = &config->utcpd.gpios.vbus_discharge; + if (spec->port != NULL) { + if (!gpio_is_ready_dt(spec)) { + LOG_ERR("VBUS discharge pin port device not ready"); + return -ENODEV; + } + rc = gpio_pin_configure_dt(spec, GPIO_OUTPUT_INACTIVE); + if (rc < 0) { + LOG_ERR("VBUS discharge pin configured to OUTPUT INACTIVE failed: %d", rc); + return rc; + } + } + + /* Configure VCONN discharge pin to OUTPUT INACTIVE */ + spec = &config->utcpd.gpios.vconn_discharge; + if (spec->port != NULL) { + if (!gpio_is_ready_dt(spec)) { + LOG_ERR("VCONN discharge pin port device not ready"); + return -ENODEV; + } + rc = gpio_pin_configure_dt(spec, GPIO_OUTPUT_INACTIVE); + if (rc < 0) { + LOG_ERR("VCONN discharge pin configured to OUTPUT INACTIVE failed: %d", rc); + return rc; + } + } + + return 0; +} + +/** + * @brief Initializes UTCPD PHY + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_phy_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t phyctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PHYCTL); + + /* Enable PHY + * + * NOTE: Only UTCPD0 is supported. + */ + SYS->UTCPDCTL |= SYS_UTCPDCTL_POREN0_Msk; + phyctl |= UTCPD_PHYCTL_PHYPWR_Msk; + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PHYCTL, phyctl); +} + +/** + * @brief Checks if UTCPD Dead Battery mode is enabled + * + * @retval true Dead Battery mode is enabled + * @retval false Dead Battery mode is not enabled + */ +static bool numaker_utcpd_deadbattery_query_enable(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t phyctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PHYCTL); + + /* 0 = Dead Battery circuit controls internal Rd/Rp. + * 1 = Role Control Register controls internal Rd/ + */ + return !(phyctl & UTCPD_PHYCTL_DBCTL_Msk); +} + +/** + * @brief Enables or disables UTCPD Dead Battery mode + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_deadbattery_set_enable(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t phyctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PHYCTL); + + if (enable) { + /* Dead Battery circuit controls internal Rd/Rp */ + phyctl &= ~UTCPD_PHYCTL_DBCTL_Msk; + } else { + /* UTCPD.ROLCTL controls internal Rd/Rp */ + phyctl |= UTCPD_PHYCTL_DBCTL_Msk; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PHYCTL, phyctl); +} + +/** + * @brief Initializes UTCPD Dead Battery mode + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_deadbattery_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + + return numaker_utcpd_deadbattery_set_enable(dev, config->utcpd.dead_battery); +} + +/** + * @brief Initializes UTCPD interrupts + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_interrupts_init(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t ie; + uint32_t pwrstsie; + uint32_t futstsie; + uint32_t vndie; + + ie = UTCPD_IE_VNDIE_Msk | UTCPD_IE_SKDCDTIE_Msk | UTCPD_IE_RXOFIE_Msk | UTCPD_IE_FUTIE_Msk | + UTCPD_IE_VBAMLIE_Msk | UTCPD_IE_VBAMHIE_Msk | UTCPD_IE_TXOKIE_Msk | + UTCPD_IE_TXDCUDIE_Msk | UTCPD_IE_TXFAILIE_Msk | UTCPD_IE_RXHRSTIE_Msk | + UTCPD_IE_RXSOPIE_Msk | UTCPD_IE_PWRSCHIE_Msk | UTCPD_IE_CCSCHIE_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, IE, ie); + if (rc < 0) { + return rc; + } + + pwrstsie = UTCPD_PWRSTSIE_DACONIE_Msk | UTCPD_PWRSTSIE_SRHVIE_Msk | + UTCPD_PWRSTSIE_SRVBIE_Msk | UTCPD_PWRSTSIE_VBDTDGIE_Msk | + UTCPD_PWRSTSIE_VBPSIE_Msk | UTCPD_PWRSTSIE_VCPSIE_Msk | + UTCPD_PWRSTSIE_SKVBIE_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PWRSTSIE, pwrstsie); + if (rc < 0) { + return rc; + } + + futstsie = UTCPD_FUTSTSIE_FOFFVBIE_Msk | UTCPD_FUTSTSIE_ADGFALIE_Msk | + UTCPD_FUTSTSIE_FDGFALIE_Msk | UTCPD_FUTSTSIE_VBOCIE_Msk | + UTCPD_FUTSTSIE_VBOVIE_Msk | UTCPD_FUTSTSIE_VCOCIE_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, FUTSTSIE, futstsie); + if (rc < 0) { + return rc; + } + + vndie = UTCPD_VNDIE_VCDGIE_Msk | UTCPD_VNDIE_CRCERRIE_Msk | UTCPD_VNDIE_TXFRSIE_Msk | + UTCPD_VNDIE_RXFRSIE_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, VNDIE, vndie); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * @brief Initializes UTCPD at stack recycle + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_init_recycle(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t value; + + /* Disable BIST, CC1/CC2 for CC/VCOON */ + value = 0; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, CTL, value); + if (rc < 0) { + return rc; + } + + /* Rp default, CC1/CC2 Rd */ + value = UTCPD_ROLECTL_RPVALUE_DEF | UTCPD_ROLECTL_CC1_RD | UTCPD_ROLECTL_CC2_RD; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, ROLCTL, value); + if (rc < 0) { + return rc; + } + + /* Disable VCONN source */ + value = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRCTL); + value &= ~UTCPD_PWRCTL_VCEN_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PWRCTL, value); + if (rc < 0) { + return rc; + } + + /* Disable detecting Rx events */ + value = 0; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, DTRXEVNT, value); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * @brief Initializes UTCPD at device startup + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_utcpd_init_startup(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t pinpl; + uint32_t futctl; + uint32_t muxsel; + + /* UTCPD GPIO */ + rc = numaker_utcpd_gpios_init(dev); + if (rc < 0) { + return rc; + } + + /* UTCPD PHY */ + rc = numaker_utcpd_phy_init(dev); + if (rc < 0) { + return rc; + } + + /* UTCPD Dead Battery */ + rc = numaker_utcpd_deadbattery_init(dev); + if (rc < 0) { + return rc; + } + + /* UTCPD pin polarity */ + pinpl = config->utcpd.pinpl.bit; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PINPL, pinpl); + if (rc < 0) { + return rc; + } + + /* VBUS voltage and monitor */ + rc = numaker_utcpd_vbus_init(dev); + if (rc < 0) { + return rc; + } + + /* UTCPD fault + * + * Disable the following fault detects which rely on external circuit: + * 1. VBUS force-off + * 2. VBUS overcurrent protection + * 3. VCONN overcurrent protection + */ + futctl = UTCPD_FUTCTL_FOFFVBDS_Msk | UTCPD_FUTCTL_VBOCDTDS_Msk | UTCPD_FUTCTL_VCOCDTDS_Msk; + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, FUTCTL, futctl); + if (rc < 0) { + return rc; + } + + /* UTCPD interconnection select + * + * NOTE: Just configure CC2FRSS/CC2VCENS/CC1FRSS/CC1VCENS to non-merged + * to follow TCPCI + */ + muxsel = UTCPD_MUXSEL_CC2FRSS_Msk | UTCPD_MUXSEL_CC2VCENS_Msk | UTCPD_MUXSEL_CC1FRSS_Msk | + UTCPD_MUXSEL_CC1VCENS_Msk; + /* NOTE: For absence of EADC channel measurement for VCONN, we configure with all-one which + * is supposed to be invalid EADC channel number so that UTCPD won't get updated + * on VCONN by accident. + */ + if (config->eadc.spec_vbus != NULL) { + muxsel |= (config->eadc.spec_vbus->channel_id << UTCPD_MUXSEL_ADCSELVB_Pos); + } else { + muxsel |= UTCPD_MUXSEL_ADCSELVB_Msk; + } + if (config->eadc.spec_vconn != NULL) { + muxsel |= (config->eadc.spec_vconn->channel_id << UTCPD_MUXSEL_ADCSELVC_Pos); + } else { + muxsel |= UTCPD_MUXSEL_ADCSELVC_Msk; + } + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, MUXSEL, muxsel); + if (rc < 0) { + return rc; + } + + /* Interrupts */ + rc = numaker_utcpd_interrupts_init(dev); + if (rc < 0) { + return rc; + } + + /* IRQ */ + config->irq_config_func_utcpd(dev); + + return 0; +} + +/** + * @brief Initializes EADC to be timer-triggered for measuring + * VBUS/VCONN voltage at device startup + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_eadc_init_startup(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + EADC_T *eadc_base = config->eadc_base; + int rc; + const struct adc_dt_spec *spec; + + /* Vref */ + rc = numaker_eadc_vref_init(dev); + if (rc < 0) { + return rc; + } + + /* Set input mode as single-end and enable the A/D converter */ + EADC_Open(eadc_base, EADC_CTL_DIFFEN_SINGLE_END); + + /* Configure sample module for measuring VBUS voltage + * + * NOTE: Make sample module number the same as channel number for + * easy implementation. + * NOTE: EADC measurement channel for VBUS can be absent with PWRSTS.VBPS as fallback + */ + spec = config->eadc.spec_vbus; + if (spec) { + rc = numaker_eadc_smplmod_init(dev, spec, config->eadc.trgsel_vbus); + if (rc < 0) { + return rc; + } + } + + /* Configure sample module for measuring VCONN voltage + * + * NOTE: Make sample module number the same as channel number for + * easy implementation. + * NOTE: EADC measurement channel for VCONN can be absent for VCONN unsupported + */ + spec = config->eadc.spec_vconn; + if (spec) { + rc = numaker_eadc_smplmod_init(dev, spec, config->eadc.trgsel_vconn); + if (rc < 0) { + return rc; + } + } + + return 0; +} + +/** + * @brief Initializes Timer to trigger EADC for measuring VBUS/VCONN + * voltage at device startup + * + * @retval 0 on success + */ +static int numaker_timer_init_startup(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + TIMER_T *timer_base = config->timer_base; + + /* Configure Timer to trigger EADC periodically */ + TIMER_Open(timer_base, TIMER_PERIODIC_MODE, config->eadc.timer_trigger_rate); + TIMER_SetTriggerSource(timer_base, TIMER_TRGSRC_TIMEOUT_EVENT); + TIMER_SetTriggerTarget(timer_base, TIMER_TRG_TO_EADC); + TIMER_Start(timer_base); + + return 0; +} + +/** + * @brief Initializes TCPC at stack recycle + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_init_recycle(const struct device *dev) +{ + struct numaker_tcpc_data *data = dev->data; + int rc; + + /* Initialize UTCPD for attach/detach recycle */ + rc = numaker_utcpd_init_recycle(dev); + if (rc < 0) { + return rc; + } + + /* The fields below must (re-)initialize for tcpc_init(). */ + data->rp = TC_RP_USB; + data->rx_sop_prime_enabled = false; + data->rx_msg_ready = false; + memset(&data->rx_msg, 0x00, sizeof(data->rx_msg)); + + return 0; +} + +/** + * @brief Initializes TCPC at device startup + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_init_startup(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + int rc; + + SYS_UnlockReg(); + + /* Configure pinmux (NuMaker's SYS MFP) */ + rc = pinctrl_apply_state(config->pincfg, PINCTRL_STATE_DEFAULT); + if (rc < 0) { + return rc; + } + + /* Invoke Clock controller to enable module clock */ + + /* Equivalent to CLK_EnableModuleClock() */ + rc = clock_control_on(config->clkctrl_dev, (clock_control_subsys_t)&config->pcc_utcpd); + if (rc < 0) { + return rc; + } + rc = clock_control_on(config->clkctrl_dev, (clock_control_subsys_t)&config->pcc_timer); + if (rc < 0) { + return rc; + } + + /* Equivalent to CLK_SetModuleClock() */ + rc = clock_control_configure(config->clkctrl_dev, + (clock_control_subsys_t)&config->pcc_utcpd, NULL); + if (rc < 0) { + return rc; + } + rc = clock_control_configure(config->clkctrl_dev, + (clock_control_subsys_t)&config->pcc_timer, NULL); + if (rc < 0) { + return rc; + } + + /* Invoke Reset controller to reset module to default state */ + /* Equivalent to SYS_ResetModule() */ + rc = reset_line_toggle_dt(&config->reset_utcpd); + if (rc < 0) { + return rc; + } + rc = reset_line_toggle_dt(&config->reset_timer); + if (rc < 0) { + return rc; + } + + /* Initialize UTCPD */ + rc = numaker_utcpd_init_startup(dev); + if (rc < 0) { + return rc; + } + + if (config->eadc.spec_vbus != NULL || config->eadc.spec_vconn != NULL) { + /* Initialize EADC */ + rc = numaker_eadc_init_startup(dev); + if (rc < 0) { + return rc; + } + + /* Initialize Timer */ + rc = numaker_timer_init_startup(dev); + if (rc < 0) { + return rc; + } + } + + return numaker_tcpc_init_recycle(dev); +} + +/** + * @brief Reads the status of the CC lines + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_get_cc(const struct device *dev, enum tc_cc_voltage_state *cc1, + enum tc_cc_voltage_state *cc2) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t rolctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, ROLCTL); + uint32_t rolctl_cc1 = rolctl & UTCPD_ROLCTL_CC1_Msk; + uint32_t rolctl_cc2 = rolctl & UTCPD_ROLCTL_CC2_Msk; + uint32_t ccsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CCSTS); + uint32_t ccsts_cc1state = ccsts & UTCPD_CCSTS_CC1STATE_Msk; + uint32_t ccsts_cc2state = ccsts & UTCPD_CCSTS_CC2STATE_Msk; + uint32_t ccsts_conrlt = ccsts & UTCPD_CCSTS_CONRLT_Msk; + + /* CC1 */ + if (rolctl_cc1 == UTCPD_ROLECTL_CC1_RP || ccsts_conrlt == UTCPD_CONN_RESULT_RP) { + switch (ccsts_cc1state) { + case UTCPD_CCSTS_CC1STATE_SRC_RA: + *cc1 = TC_CC_VOLT_RA; + break; + case UTCPD_CCSTS_CC1STATE_SRC_RD: + *cc1 = TC_CC_VOLT_RD; + break; + default: + *cc1 = TC_CC_VOLT_OPEN; + } + } else if (rolctl_cc1 == UTCPD_ROLECTL_CC1_RD || ccsts_conrlt == UTCPD_CONN_RESULT_RD) { + switch (ccsts_cc1state) { + case UTCPD_CCSTS_CC1STATE_SNK_DEF: + *cc1 = TC_CC_VOLT_RP_DEF; + break; + case UTCPD_CCSTS_CC1STATE_SNK_1P5A: + *cc1 = TC_CC_VOLT_RP_1A5; + break; + case UTCPD_CCSTS_CC1STATE_SNK_3A: + *cc1 = TC_CC_VOLT_RP_3A0; + break; + default: + *cc1 = TC_CC_VOLT_OPEN; + } + } else { + *cc1 = TC_CC_VOLT_OPEN; + } + + /* CC2 */ + if (rolctl_cc2 == UTCPD_ROLECTL_CC2_RP || ccsts_conrlt == UTCPD_CONN_RESULT_RP) { + switch (ccsts_cc2state) { + case UTCPD_CCSTS_CC2STATE_SRC_RA: + *cc2 = TC_CC_VOLT_RA; + break; + case UTCPD_CCSTS_CC2STATE_SRC_RD: + *cc2 = TC_CC_VOLT_RD; + break; + default: + *cc2 = TC_CC_VOLT_OPEN; + } + } else if (rolctl_cc2 == UTCPD_ROLECTL_CC2_RD || ccsts_conrlt == UTCPD_CONN_RESULT_RD) { + switch (ccsts_cc2state) { + case UTCPD_CCSTS_CC2STATE_SNK_DEF: + *cc2 = TC_CC_VOLT_RP_DEF; + break; + case UTCPD_CCSTS_CC2STATE_SNK_1P5A: + *cc2 = TC_CC_VOLT_RP_1A5; + break; + case UTCPD_CCSTS_CC2STATE_SNK_3A: + *cc2 = TC_CC_VOLT_RP_3A0; + break; + default: + *cc2 = TC_CC_VOLT_OPEN; + } + } else { + *cc2 = TC_CC_VOLT_OPEN; + } + + return 0; +} + +/** + * @brief Sets the value of CC pull up resistor used when operating as a Source + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_select_rp_value(const struct device *dev, enum tc_rp_value rp) +{ + struct numaker_tcpc_data *data = dev->data; + + data->rp = rp; + + return 0; +} + +/** + * @brief Gets the value of the CC pull up resistor used when operating as a Source + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_get_rp_value(const struct device *dev, enum tc_rp_value *rp) +{ + struct numaker_tcpc_data *data = dev->data; + + *rp = data->rp; + + return 0; +} + +/** + * @brief Sets the CC pull resistor and sets the role as either Source or Sink + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_cc(const struct device *dev, enum tc_cc_pull pull) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t rolctl = 0; + + /* Disable Dead Battery mode if it is active, so that + * internal Rd/Rp gets controlled by to UTCPD.ROLCTL + * from Dead Battery circuit. + */ + if (numaker_utcpd_deadbattery_query_enable(dev)) { + rc = numaker_utcpd_deadbattery_set_enable(dev, false); + if (rc < 0) { + return rc; + } + } + + /* Rp value: default, 1.5A, or 3.0A */ + switch (data->rp) { + case TC_RP_USB: + rolctl |= UTCPD_ROLECTL_RPVALUE_DEF; + break; + + case TC_RP_1A5: + rolctl |= UTCPD_ROLECTL_RPVALUE_1P5A; + break; + + case TC_RP_3A0: + rolctl |= UTCPD_ROLECTL_RPVALUE_3A; + break; + + default: + LOG_ERR("Invalid Rp value: %d", data->rp); + return -EINVAL; + } + + /* Pull on both CC1/CC2, determining source/sink role */ + switch (pull) { + case TC_CC_RA: + rolctl |= (UTCPD_ROLECTL_CC1_RA | UTCPD_ROLECTL_CC2_RA); + break; + + case TC_CC_RP: + rolctl |= (UTCPD_ROLECTL_CC1_RP | UTCPD_ROLECTL_CC2_RP); + break; + + case TC_CC_RD: + rolctl |= (UTCPD_ROLECTL_CC1_RD | UTCPD_ROLECTL_CC2_RD); + break; + + case TC_CC_OPEN: + rolctl |= (UTCPD_ROLECTL_CC1_OPEN | UTCPD_ROLECTL_CC2_OPEN); + break; + + default: + LOG_ERR("Invalid pull: %d", pull); + return -EINVAL; + } + + /* Update CC1/CC2 pull values */ + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, ROLCTL, rolctl); + if (rc < 0) { + return rc; + } + + return 0; +} + +/** + * @brief Sets a callback that can enable or discharge VCONN if the TCPC is + * unable to or the system is configured in a way that does not use + * the VCONN control capabilities of the TCPC + */ +static void numaker_tcpc_set_vconn_discharge_cb(const struct device *dev, + tcpc_vconn_discharge_cb_t cb) +{ + struct numaker_tcpc_data *data = dev->data; + + data->dpm.vconn_discharge_cb = cb; +} + +/** + * @brief Sets a callback that can enable or disable VCONN if the TCPC is + * unable to or the system is configured in a way that does not use + * the VCONN control capabilities of the TCPC + */ +static void numaker_tcpc_set_vconn_cb(const struct device *dev, tcpc_vconn_control_cb_t vconn_cb) +{ + struct numaker_tcpc_data *data = dev->data; + + data->dpm.vconn_cb = vconn_cb; +} + +/** + * @brief Discharges VCONN + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_vconn_discharge(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + UTCPD_T *utcpd_base = config->utcpd_base; + const struct gpio_dt_spec *vconn_discharge_spec = &config->utcpd.gpios.vconn_discharge; + uint32_t ctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CTL); + uint32_t vcdgctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, VCDGCTL); + enum tc_cc_polarity polarity = + (ctl & UTCPD_CTL_ORIENT_Msk) ? TC_POLARITY_CC2 : TC_POLARITY_CC1; + + /* Use DPM supplied VCONN discharge */ + if (data->dpm.vconn_discharge_cb) { + return data->dpm.vconn_discharge_cb(dev, polarity, enable); + } + + /* Use GPIO VCONN discharge */ + if (vconn_discharge_spec->port != NULL) { + return gpio_pin_set_dt(vconn_discharge_spec, enable); + } + + /* Use UTCPD VCONN discharge */ + if (enable) { + vcdgctl |= UTCPD_VCDGCTL_VCDGEN_Msk; + } else { + vcdgctl &= ~UTCPD_VCDGCTL_VCDGEN_Msk; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, VCDGCTL, vcdgctl); +} + +/** + * @brief Enables or disables VCONN + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_vconn(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRCTL); + uint32_t ctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CTL); + enum tc_cc_polarity polarity = + (ctl & UTCPD_CTL_ORIENT_Msk) ? TC_POLARITY_CC2 : TC_POLARITY_CC1; + + /* Use DPM supplied VCONN */ + if (data->dpm.vconn_cb) { + return data->dpm.vconn_cb(dev, polarity, enable); + } + + /* Use UTCPD VCONN */ + if (enable) { + pwrctl |= UTCPD_PWRCTL_VCEN_Msk; + } else { + pwrctl &= ~UTCPD_PWRCTL_VCEN_Msk; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, PWRCTL, pwrctl); +} + +/** + * @brief Sets the Power and Data Role of the PD message header + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_roles(const struct device *dev, enum tc_power_role power_role, + enum tc_data_role data_role) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t mshead = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, MSHEAD); + + /* Power role for auto-reply GoodCRC */ + mshead &= ~UTCPD_MSHEAD_PWRROL_Msk; + if (power_role == TC_ROLE_SOURCE) { + mshead |= UTCPD_MHINFO_PROLE_SRC; + } else { + mshead |= UTCPD_MHINFO_PROLE_SNK; + } + + /* Data role for auto-reply GoodCRC */ + mshead &= ~UTCPD_MSHEAD_DAROL_Msk; + if (data_role == TC_ROLE_DFP) { + mshead |= UTCPD_MHINFO_DROLE_DFP; + } else { + mshead |= UTCPD_MHINFO_DROLE_UFP; + } + + /* Message Header for auto-reply GoodCRC */ + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, MSHEAD, mshead); +} + +/** + * @brief Retrieves the Power Delivery message from the TCPC. + * If buf is NULL, then only the status is returned, where 0 means there is a message pending and + * -ENODATA means there is no pending message. + * + * @retval Greater or equal to 0 is the number of bytes received if buf parameter is provided + * @retval 0 if there is a message pending and buf parameter is NULL + * @retval -EIO on failure + * @retval -ENODATA if no message is pending + */ +static int numaker_tcpc_get_rx_pending_msg(const struct device *dev, struct pd_msg *msg) +{ + struct numaker_tcpc_data *data = dev->data; + + /* Rx message pending? */ + if (!data->rx_msg_ready) { + return -ENODATA; + } + + /* Query status only? */ + if (msg == NULL) { + return 0; + } + + /* Dequeue Rx FIFO */ + *msg = data->rx_msg; + data->rx_msg_ready = false; + + /* Indicate Rx message returned */ + return 1; +} + +/** + * @brief Enables the reception of SOP* message types + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_rx_enable(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + struct numaker_tcpc_data *data = dev->data; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t dtrxevnt = 0; + + /* Enable receive */ + if (enable) { + /* Enable receive of SOP messages */ + dtrxevnt |= UTCPD_DTRXEVNT_SOPEN_Msk; + + /* Enable receive of SOP'/SOP'' messages */ + if (data->rx_sop_prime_enabled) { + dtrxevnt |= UTCPD_DTRXEVNT_SOPPEN_Msk | UTCPD_DTRXEVNT_SOPPPEN_Msk; + } + + /* Enable receive of Hard Reset */ + dtrxevnt |= UTCPD_DTRXEVNT_HRSTEN_Msk; + + /* Don't enable receive of Cable Reset for not being Cable Plug */ + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, DTRXEVNT, dtrxevnt); +} + +/** + * @brief Sets the polarity of the CC lines + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_cc_polarity(const struct device *dev, enum tc_cc_polarity polarity) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t ctl = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, CTL); + + /* Update CC polarity */ + switch (polarity) { + case TC_POLARITY_CC1: + ctl &= ~UTCPD_CTL_ORIENT_Msk; + break; + + case TC_POLARITY_CC2: + ctl |= UTCPD_CTL_ORIENT_Msk; + break; + + default: + LOG_ERR("Invalid CC polarity: %d", polarity); + return -EINVAL; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, CTL, ctl); +} + +/** + * @brief Transmits a Power Delivery message + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_transmit_data(const struct device *dev, struct pd_msg *msg) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + int rc; + uint32_t txctl; + uint32_t txctl_retrycnt; + uint32_t txctl_txstype; + + /* Not support Unchunked Extended Message exceeding PD_CONVERT_PD_HEADER_COUNT_TO_BYTES */ + if (msg->len > (PD_MAX_EXTENDED_MSG_LEGACY_LEN + 2)) { + LOG_ERR("Not support Unchunked Extended Message exceeding " + "PD_CONVERT_PD_HEADER_COUNT_TO_BYTES: %d", + msg->len); + return -EIO; + } + + /* txbcnt = 2 (Message Header) + Tx data byte count */ + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, TXBCNT, msg->len + 2); + if (rc < 0) { + return rc; + } + + /* Tx header */ + rc = NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, TXHEAD, msg->header.raw_value); + if (rc < 0) { + return rc; + } + + /* Tx data */ + rc = numaker_utcpd_tx_write_data(dev, msg->data, msg->len); + if (rc < 0) { + return rc; + } + + /* Tx control */ + if (msg->type < PD_PACKET_TX_HARD_RESET) { + /* nRetryCount = 2 for PD REV 3.0 */ + txctl_retrycnt = 2 << UTCPD_TXCTL_RETRYCNT_Pos; + } else if (msg->type <= PD_PACKET_TX_BIST_MODE_2) { + /* Per TCPCI spec, no retry for non-SOP* transmission */ + txctl_retrycnt = 0; + } else { + LOG_ERR("Invalid PD packet type: %d", msg->type); + return -EINVAL; + } + /* NOTE: Needn't extra cast for UTCPD_TXCTL.TXSTYPE aligning with pd_packet_type */ + txctl_txstype = ((uint32_t)msg->type) << UTCPD_TXCTL_TXSTYPE_Pos; + txctl = txctl_retrycnt | txctl_txstype; + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, TXCTL, txctl); +} + +/** + * @brief Dump a set of TCPC registers + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_dump_std_reg(const struct device *dev) +{ + return numaker_utcpd_dump_regs(dev); +} + +/** + * @brief Queries the current sinking state of the TCPC + * + * @retval true if sinking power + * @retval false if not sinking power + */ +static int numaker_tcpc_get_snk_ctrl(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + return (pwrsts & UTCPD_PWRSTS_SKVB_Msk) ? true : false; +} + +/** + * @brief Queries the current sourcing state of the TCPC + * + * @retval true if sourcing power + * @retval false if not sourcing power + */ +static int numaker_tcpc_get_src_ctrl(const struct device *dev) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + return (pwrsts & (UTCPD_PWRSTS_SRVB_Msk | UTCPD_PWRSTS_SRHV_Msk)) ? true : false; +} + +/** + * @brief Enables the reception of SOP Prime messages + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_sop_prime_enable(const struct device *dev, bool enable) +{ + struct numaker_tcpc_data *data = dev->data; + + data->rx_sop_prime_enabled = enable; + + return 0; +} + +/** + * @brief Controls the BIST Mode of the TCPC. It disables RX alerts while the + * mode is active. + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_tcpc_set_bist_test_mode(const struct device *dev, bool enable) +{ + return numaker_utcpd_bist_test_mode_set_enable(dev, enable); +} + +/** + * @brief Sets the alert function that's called when an interrupt is triggered + * due to an alert bit + * + * @retval 0 on success + */ +static int numaker_tcpc_set_alert_handler_cb(const struct device *dev, + tcpc_alert_handler_cb_t alert_handler, + void *alert_data) +{ + struct numaker_tcpc_data *data = dev->data; + + data->tcpc_alert.handler = alert_handler; + data->tcpc_alert.data = alert_data; + + return 0; +} + +/* Functions below with name pattern "*_tcpc_ppc_*" are to invoke by NuMaker PPC driver */ + +int numaker_tcpc_ppc_is_dead_battery_mode(const struct device *dev) +{ + return numaker_utcpd_deadbattery_query_enable(dev); +} + +int numaker_tcpc_ppc_exit_dead_battery_mode(const struct device *dev) +{ + return numaker_utcpd_deadbattery_set_enable(dev, false); +} + +int numaker_tcpc_ppc_is_vbus_source(const struct device *dev) +{ + return numaker_utcpd_vbus_is_source(dev); +} + +int numaker_tcpc_ppc_is_vbus_sink(const struct device *dev) +{ + return numaker_utcpd_vbus_is_sink(dev); +} + +int numaker_tcpc_ppc_set_snk_ctrl(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t cmd; + + if (enable) { + cmd = UTCPD_CMD_SINK_VBUS; + } else { + cmd = UTCPD_CMD_DISABLE_SINK_VBUS; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, CMD, cmd); +} + +int numaker_tcpc_ppc_set_src_ctrl(const struct device *dev, bool enable) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t cmd; + + if (enable) { + /* NOTE: Source VBUS high voltage (UTCPD_CMD_SRC_VBUS_NONDEFAULT) N/A */ + cmd = UTCPD_CMD_SRC_VBUS_DEFAULT; + } else { + cmd = UTCPD_CMD_DISABLE_SRC_VBUS; + } + return NUMAKER_UTCPD_REG_WRITE_BY_NAME(dev, CMD, cmd); +} + +int numaker_tcpc_ppc_set_vbus_discharge(const struct device *dev, bool enable) +{ + return numaker_utcpd_vbus_set_discharge(dev, enable); +} + +int numaker_tcpc_ppc_is_vbus_present(const struct device *dev) +{ + return numaker_utcpd_vbus_is_present(dev); +} + +int numaker_tcpc_ppc_set_event_handler(const struct device *dev, usbc_ppc_event_cb_t event_handler, + void *event_data) +{ + struct numaker_tcpc_data *data = dev->data; + + data->ppc_event.handler = event_handler; + data->ppc_event.data = event_data; + + return 0; +} + +int numaker_tcpc_ppc_dump_regs(const struct device *dev) +{ + return numaker_utcpd_dump_regs(dev); +} + +/* End of "*_tcpc_ppc_*" functions */ + +/* Functions below with name pattern "*_tcpc_vbus_*" are to invoke by NuMaker VBUS driver */ + +bool numaker_tcpc_vbus_check_level(const struct device *dev, enum tc_vbus_level level) +{ + const struct numaker_tcpc_config *const config = dev->config; + UTCPD_T *utcpd_base = config->utcpd_base; + uint32_t mv_norm; + int rc = numaker_utcpd_vbus_measure(dev, &mv_norm); + uint32_t pwrsts = NUMAKER_UTCPD_REG_READ_BY_NAME(dev, PWRSTS); + + /* Fall back to PWRSTS.VBPS if VBUS measurement by EADC is not available */ + switch (level) { + case TC_VBUS_SAFE0V: + return (rc == 0) ? (mv_norm < PD_V_SAFE_0V_MAX_MV) + : !(pwrsts & UTCPD_PWRSTS_VBPS_Msk); + case TC_VBUS_PRESENT: + return (rc == 0) ? (mv_norm >= PD_V_SAFE_5V_MIN_MV) + : (pwrsts & UTCPD_PWRSTS_VBPS_Msk); + case TC_VBUS_REMOVED: + return (rc == 0) ? (mv_norm < TC_V_SINK_DISCONNECT_MAX_MV) + : !(pwrsts & UTCPD_PWRSTS_VBPS_Msk); + } + + return false; +} + +int numaker_tcpc_vbus_measure(const struct device *dev, int *vbus_meas) +{ + int rc; + uint32_t mv; + + if (vbus_meas == NULL) { + return -EINVAL; + } + *vbus_meas = 0; + + rc = numaker_utcpd_vbus_measure(dev, &mv); + if (rc < 0) { + return rc; + } + *vbus_meas = mv; + + return 0; +} + +int numaker_tcpc_vbus_discharge(const struct device *dev, bool enable) +{ + return numaker_utcpd_vbus_set_discharge(dev, enable); +} + +int numaker_tcpc_vbus_enable(const struct device *dev, bool enable) +{ + /* VBUS measurement is made automatic through Timer-triggered EADC. */ + return 0; +} + +/* End of "*_tcpc_vbus_*" functions */ + +static const struct tcpc_driver_api numaker_tcpc_driver_api = { + .init = numaker_tcpc_init_recycle, + .get_cc = numaker_tcpc_get_cc, + .select_rp_value = numaker_tcpc_select_rp_value, + .get_rp_value = numaker_tcpc_get_rp_value, + .set_cc = numaker_tcpc_set_cc, + .set_vconn_discharge_cb = numaker_tcpc_set_vconn_discharge_cb, + .set_vconn_cb = numaker_tcpc_set_vconn_cb, + .vconn_discharge = numaker_tcpc_vconn_discharge, + .set_vconn = numaker_tcpc_set_vconn, + .set_roles = numaker_tcpc_set_roles, + .get_rx_pending_msg = numaker_tcpc_get_rx_pending_msg, + .set_rx_enable = numaker_tcpc_set_rx_enable, + .set_cc_polarity = numaker_tcpc_set_cc_polarity, + .transmit_data = numaker_tcpc_transmit_data, + .dump_std_reg = numaker_tcpc_dump_std_reg, + .get_snk_ctrl = numaker_tcpc_get_snk_ctrl, + .get_src_ctrl = numaker_tcpc_get_src_ctrl, + .sop_prime_enable = numaker_tcpc_sop_prime_enable, + .set_bist_test_mode = numaker_tcpc_set_bist_test_mode, + .set_alert_handler_cb = numaker_tcpc_set_alert_handler_cb, +}; + +/* Same as RESET_DT_SPEC_INST_GET_BY_IDX, except by name */ +#define NUMAKER_RESET_DT_SPEC_INST_GET_BY_NAME(inst, name) \ + { \ + .dev = DEVICE_DT_GET(DT_INST_RESET_CTLR_BY_NAME(inst, name)), \ + .id = DT_INST_RESET_CELL_BY_NAME(inst, name, id), \ + } + +/* Same as GPIO_DT_SPEC_GET_BY_IDX, except by name */ +#define NUMAKER_GPIO_DT_SPEC_GET_BY_NAME(node_id, prop, name) \ + { \ + .port = DEVICE_DT_GET(DT_PHANDLE_BY_NAME(node_id, prop, name)), \ + .pin = DT_PHA_BY_NAME(node_id, prop, name, pin), \ + .dt_flags = DT_PHA_BY_NAME(node_id, prop, name, flags), \ + } + +/* Same as GPIO_DT_SPEC_INST_GET_BY_IDX_OR, except by name */ +#define NUMAKER_GPIO_DT_SPEC_INST_GET_BY_NAME_OR(inst, prop, name, default_value) \ + COND_CODE_1(DT_INST_PROP_HAS_NAME(inst, prop, name), \ + (NUMAKER_GPIO_DT_SPEC_GET_BY_NAME(DT_DRV_INST(inst), prop, name)), \ + (default_value)) + +/* Peripheral Clock Control by name */ +#define NUMAKER_PCC_INST_GET_BY_NAME(inst, name) \ + { \ + .subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC, \ + .pcc.clk_modidx = DT_INST_CLOCKS_CELL_BY_NAME(inst, name, clock_module_index), \ + .pcc.clk_src = DT_INST_CLOCKS_CELL_BY_NAME(inst, name, clock_source), \ + .pcc.clk_div = DT_INST_CLOCKS_CELL_BY_NAME(inst, name, clock_divider), \ + } + +/* UTCPD GPIOs */ +#define NUMAKER_UTCPD_GPIOS_INIT(inst) \ + { \ + .vbus_detect = \ + NUMAKER_GPIO_DT_SPEC_GET_BY_NAME(DT_DRV_INST(inst), gpios, vbus_detect), \ + .vbus_discharge = NUMAKER_GPIO_DT_SPEC_INST_GET_BY_NAME_OR(inst, gpios, \ + vbus_discharge, {0}), \ + .vconn_discharge = NUMAKER_GPIO_DT_SPEC_INST_GET_BY_NAME_OR(inst, gpios, \ + vconn_discharge, {0}), \ + } + +/* UTCPD.PINPL. cast */ +#define NUMAKER_UTCPD_PINPOL_CAST(inst, pin_dt, pin_utcpd) \ + (DT_ENUM_HAS_VALUE(DT_DRV_INST(inst), pin_dt, high_active) ? UTCPD_PINPL_##pin_utcpd##_Msk \ + : 0) + +/* UTCPD.VBVOL.VBSCALE cast */ +#define NUMAKER_UTCPD_VBUS_DIVIDE_CAST(inst) NUMAKER_UTCPD_VBUS_DIVIDE_CAST_NO_DIVIDE(inst) +/* no_divide */ +#define NUMAKER_UTCPD_VBUS_DIVIDE_CAST_NO_DIVIDE(inst) \ + COND_CODE_1(DT_ENUM_HAS_VALUE(DT_DRV_INST(inst), vbus_divide, no_divice), \ + ({.bit = (0 << UTCPD_VBVOL_VBSCALE_Pos), .value = 1}), \ + (NUMAKER_UTCPD_VBUS_DIVIDE_CAST_DIVIDE_10(inst))) +/* divide_10 */ +#define NUMAKER_UTCPD_VBUS_DIVIDE_CAST_DIVIDE_10(inst) \ + COND_CODE_1(DT_ENUM_HAS_VALUE(DT_DRV_INST(inst), vbus_divide, divide_10), \ + ({.bit = (1 << UTCPD_VBVOL_VBSCALE_Pos), .value = 10}), \ + (NUMAKER_UTCPD_VBUS_DIVIDE_CAST_DIVIDE_20(inst))) +/* divide_20 */ +#define NUMAKER_UTCPD_VBUS_DIVIDE_CAST_DIVIDE_20(inst) \ + COND_CODE_1(DT_ENUM_HAS_VALUE(DT_DRV_INST(inst), vbus_divide, divide_20), \ + ({.bit = (2 << UTCPD_VBVOL_VBSCALE_Pos), .value = 20}), (no_divide error)) + +/* UTCPD.PINPL */ +#define NUMAKER_UTCPD_PINPL_INIT(inst) \ + { \ + .bit = NUMAKER_UTCPD_PINPOL_CAST(inst, vconn_overcurrent_event_polarity, VCOCPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vconn_discharge_polarity, VCDGENPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vconn_enable_polarity, VCENPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vbus_overcurrent_event_polarity, VBOCPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vbus_forceoff_event_polarity, FOFFVBPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, frs_tx_polarity, TXFRSPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vbus_discharge_enable_polarity, VBDGENPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vbus_sink_enable_polarity, VBSKENPL) | \ + NUMAKER_UTCPD_PINPOL_CAST(inst, vbus_source_enable_polarity, VBSRENPL) \ + } + +/* UTCPD.VBVOL */ +#define NUMAKER_UTCPD_VBVOL_INIT(inst) \ + { \ + .vbscale = NUMAKER_UTCPD_VBUS_DIVIDE_CAST(inst), \ + } + +#define NUMAKER_UTCPD_INIT(inst) \ + { \ + .gpios = NUMAKER_UTCPD_GPIOS_INIT(inst), \ + .dead_battery = DT_INST_PROP(inst, dead_battery), \ + .pinpl = NUMAKER_UTCPD_PINPL_INIT(inst), .vbvol = NUMAKER_UTCPD_VBVOL_INIT(inst), \ + } + +/* EADC register address is duplicated for easy implementation. + * They must be the same. + */ +#define BUILD_ASSERT_NUMAKER_EADC_REG(inst) \ + IF_ENABLED(DT_NODE_HAS_PROP(DT_DRV_INST(inst), io_channels), \ + (BUILD_ASSERT(DT_INST_REG_ADDR_BY_NAME(inst, eadc) == \ + DT_REG_ADDR(DT_INST_IO_CHANNELS_CTLR(inst)));)) + +#define NUMAKER_EADC_TRGSRC_CAST(inst) \ + ((DT_INST_REG_ADDR_BY_NAME(inst, timer) == TIMER0_BASE) ? EADC_TIMER0_TRIGGER \ + : (DT_INST_REG_ADDR_BY_NAME(inst, timer) == TIMER1_BASE) ? EADC_TIMER1_TRIGGER \ + : (DT_INST_REG_ADDR_BY_NAME(inst, timer) == TIMER2_BASE) ? EADC_TIMER2_TRIGGER \ + : (DT_INST_REG_ADDR_BY_NAME(inst, timer) == TIMER3_BASE) ? EADC_TIMER3_TRIGGER \ + : NUMAKER_INVALID_VALUE) + +#define BUILD_ASSERT_NUMAKER_EADC_TRGSRC_CAST(inst) \ + BUILD_ASSERT(NUMAKER_EADC_TRGSRC_CAST(inst) != NUMAKER_INVALID_VALUE, \ + "NUMAKER_EADC_TRGSRC_CAST error"); + +/* Notes on specifying EADC channels + * + * 1. Must be in order of chn_vbus, chn_vconn, etc. + * 2. The front channel can be absent, e.g. only chn_vconn. + * 3. Build assert will check the above rules. + */ +#define NUMAKER_EADC_SPEC_GET_BY_IDX_COMMA(node_id, prop, idx) ADC_DT_SPEC_GET_BY_IDX(node_id, idx), + +#define NUMAKER_EADC_SPEC_DEFINE(inst) \ + IF_ENABLED( \ + DT_NODE_HAS_PROP(DT_DRV_INST(inst), io_channels), \ + (static const struct adc_dt_spec eadc_specs##inst[] = {DT_FOREACH_PROP_ELEM( \ + DT_DRV_INST(inst), io_channels, NUMAKER_EADC_SPEC_GET_BY_IDX_COMMA)};)) + +/* Note on EADC spec index + * + * These indexes must be integer literal, or meet macro expansion error. + * However, macro expansion just does text replacement, no evaluation. + * To overcome this, UTIL_INC() and friends are invoked to do evaluation + * at preprocess time. + */ +#define NUMAKER_EADC_SPEC_IDX_VBUS(inst) 0 +#define NUMAKER_EADC_SPEC_IDX_VCONN(inst) \ + COND_CODE_1(DT_INST_PROP_HAS_NAME(inst, io_channels, chn_vbus), \ + (UTIL_INC(NUMAKER_EADC_SPEC_IDX_VBUS(inst))), \ + (NUMAKER_EADC_SPEC_IDX_VBUS(inst))) + +#define NUMAKER_EADC_SPEC_PTR_VBUS(inst) \ + COND_CODE_1(DT_INST_PROP_HAS_NAME(inst, io_channels, chn_vbus), \ + (&eadc_specs##inst[NUMAKER_EADC_SPEC_IDX_VBUS(inst)]), (NULL)) +#define NUMAKER_EADC_SPEC_PTR_VCONN(inst) \ + COND_CODE_1(DT_INST_PROP_HAS_NAME(inst, io_channels, chn_vconn), \ + (&eadc_specs##inst[NUMAKER_EADC_SPEC_IDX_VCONN(inst)]), (NULL)) + +#define NUMAKER_EADC_DEVICE_BY_NAME(inst, name) \ + DEVICE_DT_GET(DT_IO_CHANNELS_CTLR_BY_NAME(DT_DRV_INST(inst), name)) +#define NUMAKER_EADC_DEVICE_BY_IDX(inst, idx) \ + DEVICE_DT_GET(DT_IO_CHANNELS_CTLR_BY_IDX(DT_DRV_INST(inst), idx)) + +#define NUMAKER_EADC_INPUT_BY_NAME(inst, name) DT_IO_CHANNELS_INPUT_BY_NAME(DT_DRV_INST(inst), name) +#define NUMAKER_EADC_INPUT_BY_IDX(inst, idx) DT_IO_CHANNELS_INPUT_BY_IDX(DT_DRV_INST(inst), idx) + +#define BUILD_ASSERT_NUMAKER_EADC_SPEC_VBUS(inst) \ + IF_ENABLED(DT_INST_PROP_HAS_NAME(inst, io_channels, chn_vbus), \ + (BUILD_ASSERT(NUMAKER_EADC_DEVICE_BY_NAME(inst, chn_vbus) == \ + NUMAKER_EADC_DEVICE_BY_IDX( \ + inst, NUMAKER_EADC_SPEC_IDX_VBUS(inst)), \ + "EADC device for VBUS error"); \ + BUILD_ASSERT(NUMAKER_EADC_INPUT_BY_NAME(inst, chn_vbus) == \ + NUMAKER_EADC_INPUT_BY_IDX( \ + inst, NUMAKER_EADC_SPEC_IDX_VBUS(inst)), \ + "EADC channel for VBUS error");)) + +#define BUILD_ASSERT_NUMAKER_EADC_SPEC_VCONN(inst) \ + IF_ENABLED(DT_INST_PROP_HAS_NAME(inst, io_channels, chn_vconn), \ + (BUILD_ASSERT(NUMAKER_EADC_DEVICE_BY_NAME(inst, chn_vconn) == \ + NUMAKER_EADC_DEVICE_BY_IDX( \ + inst, NUMAKER_EADC_SPEC_IDX_VCONN(inst)), \ + "EADC device for VCONN error"); \ + BUILD_ASSERT(NUMAKER_EADC_INPUT_BY_NAME(inst, chn_vconn) == \ + NUMAKER_EADC_INPUT_BY_IDX( \ + inst, NUMAKER_EADC_SPEC_IDX_VCONN(inst)), \ + "EADC channel for VCONN error");)) + +#define NUMAKER_EADC_INIT(inst) \ + { \ + .spec_vbus = NUMAKER_EADC_SPEC_PTR_VBUS(inst), \ + .spec_vconn = NUMAKER_EADC_SPEC_PTR_VCONN(inst), \ + .timer_trigger_rate = DT_INST_PROP(inst, adc_measure_timer_trigger_rate), \ + .trgsel_vbus = NUMAKER_EADC_TRGSRC_CAST(inst), \ + .trgsel_vconn = NUMAKER_EADC_TRGSRC_CAST(inst), \ + } + +#define NUMAKER_TCPC_INIT(inst) \ + PINCTRL_DT_INST_DEFINE(inst); \ + \ + NUMAKER_EADC_SPEC_DEFINE(inst); \ + \ + static void numaker_utcpd_irq_config_func_##inst(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQ_BY_NAME(inst, utcpd, irq), \ + DT_INST_IRQ_BY_NAME(inst, utcpd, priority), numaker_utcpd_isr, \ + DEVICE_DT_INST_GET(inst), 0); \ + \ + irq_enable(DT_INST_IRQ_BY_NAME(inst, utcpd, irq)); \ + } \ + \ + static void numaker_utcpd_irq_unconfig_func_##inst(const struct device *dev) \ + { \ + irq_disable(DT_INST_IRQ_BY_NAME(inst, utcpd, irq)); \ + } \ + \ + static const struct numaker_tcpc_config numaker_tcpc_config_##inst = { \ + .utcpd_base = (UTCPD_T *)DT_INST_REG_ADDR_BY_NAME(inst, utcpd), \ + .eadc_base = (EADC_T *)DT_INST_REG_ADDR_BY_NAME(inst, eadc), \ + .timer_base = (TIMER_T *)DT_INST_REG_ADDR_BY_NAME(inst, timer), \ + .pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \ + .clkctrl_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))), \ + .pcc_utcpd = NUMAKER_PCC_INST_GET_BY_NAME(inst, utcpd), \ + .pcc_timer = NUMAKER_PCC_INST_GET_BY_NAME(inst, timer), \ + .reset_utcpd = NUMAKER_RESET_DT_SPEC_INST_GET_BY_NAME(inst, utcpd), \ + .reset_timer = NUMAKER_RESET_DT_SPEC_INST_GET_BY_NAME(inst, timer), \ + .irq_config_func_utcpd = numaker_utcpd_irq_config_func_##inst, \ + .irq_unconfig_func_utcpd = numaker_utcpd_irq_unconfig_func_##inst, \ + .utcpd = NUMAKER_UTCPD_INIT(inst), \ + .eadc = NUMAKER_EADC_INIT(inst), \ + }; \ + \ + BUILD_ASSERT_NUMAKER_EADC_REG(inst); \ + BUILD_ASSERT_NUMAKER_EADC_TRGSRC_CAST(inst); \ + BUILD_ASSERT_NUMAKER_EADC_SPEC_VBUS(inst); \ + BUILD_ASSERT_NUMAKER_EADC_SPEC_VCONN(inst); \ + \ + static struct numaker_tcpc_data numaker_tcpc_data_##inst; \ + \ + DEVICE_DT_INST_DEFINE(inst, numaker_tcpc_init_startup, NULL, &numaker_tcpc_data_##inst, \ + &numaker_tcpc_config_##inst, POST_KERNEL, \ + CONFIG_USBC_TCPC_INIT_PRIORITY, &numaker_tcpc_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(NUMAKER_TCPC_INIT); diff --git a/drivers/usb_c/tcpc/ucpd_numaker.h b/drivers/usb_c/tcpc/ucpd_numaker.h new file mode 100644 index 00000000000..81d6939c51f --- /dev/null +++ b/drivers/usb_c/tcpc/ucpd_numaker.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_USBC_TCPC_UCPD_NUMAKER_H_ +#define ZEPHYR_DRIVERS_USBC_TCPC_UCPD_NUMAKER_H_ + +#include +#include + +/* TCPC exported for PPC */ +int numaker_tcpc_ppc_is_dead_battery_mode(const struct device *dev); +int numaker_tcpc_ppc_exit_dead_battery_mode(const struct device *dev); +int numaker_tcpc_ppc_is_vbus_source(const struct device *dev); +int numaker_tcpc_ppc_is_vbus_sink(const struct device *dev); +int numaker_tcpc_ppc_set_snk_ctrl(const struct device *dev, bool enable); +int numaker_tcpc_ppc_set_src_ctrl(const struct device *dev, bool enable); +int numaker_tcpc_ppc_set_vbus_discharge(const struct device *dev, bool enable); +int numaker_tcpc_ppc_is_vbus_present(const struct device *dev); +int numaker_tcpc_ppc_set_event_handler(const struct device *dev, usbc_ppc_event_cb_t handler, + void *data); +int numaker_tcpc_ppc_dump_regs(const struct device *dev); + +/* TCPC exported for VBUS */ +bool numaker_tcpc_vbus_check_level(const struct device *dev, enum tc_vbus_level level); +int numaker_tcpc_vbus_measure(const struct device *dev, int *vbus_meas); +int numaker_tcpc_vbus_discharge(const struct device *dev, bool enable); +int numaker_tcpc_vbus_enable(const struct device *dev, bool enable); + +#endif /* ZEPHYR_DRIVERS_USBC_TCPC_UCPD_NUMAKER_H_ */ diff --git a/drivers/usb_c/vbus/CMakeLists.txt b/drivers/usb_c/vbus/CMakeLists.txt index 24c9151a41e..0108791ab64 100644 --- a/drivers/usb_c/vbus/CMakeLists.txt +++ b/drivers/usb_c/vbus/CMakeLists.txt @@ -3,3 +3,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_USBC_VBUS_ADC usbc_vbus_adc.c) +zephyr_library_sources_ifdef(CONFIG_USBC_VBUS_NUMAKER usbc_vbus_numaker.c) diff --git a/drivers/usb_c/vbus/Kconfig b/drivers/usb_c/vbus/Kconfig index 5b368a944ae..1168e60683d 100644 --- a/drivers/usb_c/vbus/Kconfig +++ b/drivers/usb_c/vbus/Kconfig @@ -17,6 +17,7 @@ config USBC_VBUS_INIT_PRIORITY Initialization priority of the USB-C VBUS measurement drivers in POST_KERNEL. source "drivers/usb_c/vbus/Kconfig.usbc_vbus_adc" +source "drivers/usb_c/vbus/Kconfig.numaker" endif # USBC_VBUS_DRIVER diff --git a/drivers/usb_c/vbus/Kconfig.numaker b/drivers/usb_c/vbus/Kconfig.numaker new file mode 100644 index 00000000000..0cb85025edb --- /dev/null +++ b/drivers/usb_c/vbus/Kconfig.numaker @@ -0,0 +1,11 @@ +# Nuvoton NuMaker USB-C VBUS device configuration options + +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +config USBC_VBUS_NUMAKER + bool "Nuvoton NuMaker USB-C VBUS" + default y + depends on DT_HAS_NUVOTON_NUMAKER_VBUS_ENABLED && USBC_TCPC_NUMAKER + help + Enable USB-C VBUS support for Nuvoton NuMaker chip with UTCPD. diff --git a/drivers/usb_c/vbus/usbc_vbus_numaker.c b/drivers/usb_c/vbus/usbc_vbus_numaker.c new file mode 100644 index 00000000000..f8e7d15c6c5 --- /dev/null +++ b/drivers/usb_c/vbus/usbc_vbus_numaker.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 Nuvoton Technology Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nuvoton_numaker_vbus + +#include +#include +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(vbus_numaker, CONFIG_USBC_LOG_LEVEL); + +#include +#include + +#include "../tcpc/ucpd_numaker.h" + +/* Implementation notes on NuMaker TCPC/PPC/VBUS + * + * PPC and VBUS rely on TCPC/UTCPD and are just pseudo. They are completely + * implemented in TCPC/UTCPD. + */ + +/** + * @brief Immutable device context + */ +struct numaker_vbus_config { + const struct device *tcpc_dev; +}; + +/** + * @brief Initializes the usb-c vbus driver + * + * @retval 0 on success + * @retval -ENODEV if dependent TCPC device is not ready + */ +static int numaker_vbus_init(const struct device *dev) +{ + const struct numaker_vbus_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + /* Rely on TCPC */ + if (!device_is_ready(tcpc_dev)) { + LOG_ERR("TCPC device not ready"); + return -ENODEV; + } + + return 0; +} + +/** + * @brief Checks if VBUS is at a particular level + * + * @retval true if VBUS is at the level voltage + * @retval false if VBUS is not at that level voltage + */ +static bool numaker_vbus_check_level(const struct device *dev, enum tc_vbus_level level) +{ + const struct numaker_vbus_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_vbus_check_level(tcpc_dev, level); +} + +/** + * @brief Reads and returns VBUS measured in mV + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_vbus_measure(const struct device *dev, int *vbus_meas) +{ + const struct numaker_vbus_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_vbus_measure(tcpc_dev, vbus_meas); +} + +/** + * @brief Controls a pin that discharges VBUS + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_vbus_discharge(const struct device *dev, bool enable) +{ + const struct numaker_vbus_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_vbus_discharge(tcpc_dev, enable); +} + +/** + * @brief Controls a pin that enables VBUS measurments + * + * @retval 0 on success + * @retval -EIO on failure + */ +static int numaker_vbus_enable(const struct device *dev, bool enable) +{ + const struct numaker_vbus_config *const config = dev->config; + const struct device *tcpc_dev = config->tcpc_dev; + + return numaker_tcpc_vbus_enable(tcpc_dev, enable); +} + +static const struct usbc_vbus_driver_api numaker_vbus_driver_api = { + .check_level = numaker_vbus_check_level, + .measure = numaker_vbus_measure, + .discharge = numaker_vbus_discharge, + .enable = numaker_vbus_enable, +}; + +#define NUMAKER_TCPC(inst) DT_INST_PARENT(inst) + +#define VBUS_NUMAKER_INIT(inst) \ + static const struct numaker_vbus_config numaker_vbus_config_##inst = { \ + .tcpc_dev = DEVICE_DT_GET(NUMAKER_TCPC(inst)), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, numaker_vbus_init, NULL, NULL, &numaker_vbus_config_##inst, \ + POST_KERNEL, CONFIG_USBC_VBUS_INIT_PRIORITY, \ + &numaker_vbus_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(VBUS_NUMAKER_INIT); diff --git a/dts/arm/nuvoton/m2l31x.dtsi b/dts/arm/nuvoton/m2l31x.dtsi index b3cbd6c997b..1dd45305500 100644 --- a/dts/arm/nuvoton/m2l31x.dtsi +++ b/dts/arm/nuvoton/m2l31x.dtsi @@ -394,6 +394,33 @@ clocks = <&pcc NUMAKER_WWDT_MODULE NUMAKER_CLK_CLKSEL1_WWDTSEL_LIRC 0>; status = "disabled"; }; + + tcpc0: utcpd@400c6000 { + compatible = "nuvoton,numaker-tcpc"; + reg = <0x400c6000 0x1000>, + <0x40043000 0x1000>, + <0x40050000 0x1000>; + reg-names = "utcpd", "eadc", "timer"; + interrupts = <108 0>; + interrupt-names = "utcpd"; + resets = <&rst NUMAKER_UTCPD0_RST>, + <&rst NUMAKER_TMR0_RST>; + reset-names = "utcpd", "timer"; + clocks = <&pcc NUMAKER_UTCPD0_MODULE 0 0>, + <&pcc NUMAKER_TMR0_MODULE NUMAKER_CLK_CLKSEL1_TMR0SEL_HIRC 0>; + clock-names = "utcpd", "timer"; + status = "disabled"; + + vbus0: vbus0 { + compatible = "nuvoton,numaker-vbus"; + status = "disabled"; + }; + + ppc0: ppc0 { + compatible = "nuvoton,numaker-ppc"; + status = "disabled"; + }; + }; }; }; diff --git a/dts/bindings/ppc/nuvoton,numaker-ppc.yaml b/dts/bindings/ppc/nuvoton,numaker-ppc.yaml new file mode 100644 index 00000000000..91664645352 --- /dev/null +++ b/dts/bindings/ppc/nuvoton,numaker-ppc.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton NuMaker USB Type-C power path controller + +compatible: "nuvoton,numaker-ppc" + +include: [base.yaml] diff --git a/dts/bindings/tcpc/nuvoton,numaker-tcpc.yaml b/dts/bindings/tcpc/nuvoton,numaker-tcpc.yaml new file mode 100644 index 00000000000..e248ae8ddc7 --- /dev/null +++ b/dts/bindings/tcpc/nuvoton,numaker-tcpc.yaml @@ -0,0 +1,149 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton NuMaker USB Type-C port controller + +compatible: "nuvoton,numaker-tcpc" + +include: [base.yaml, reset-device.yaml, pinctrl-device.yaml] + +properties: + reg: + required: true + + interrupts: + required: true + + resets: + required: true + + clocks: + required: true + + vconn-overcurrent-event-polarity: + type: string + description: | + Polarity of VCONN overcurrent event + enum: + - "low-active" + - "high-active" + + vconn-discharge-polarity: + type: string + description: | + Polarity of VCONN discharge + enum: + - "low-active" + - "high-active" + + vconn-enable-polarity: + type: string + description: | + Polarity of VCONN enable + enum: + - "low-active" + - "high-active" + + vbus-overcurrent-event-polarity: + type: string + description: | + Polarity of VBUS overcurrent event + enum: + - "low-active" + - "high-active" + + vbus-forceoff-event-polarity: + type: string + description: | + Polarity of VBUS force-off event + enum: + - "low-active" + - "high-active" + + frs-tx-polarity: + type: string + description: | + Polarity of fast role swap tx + enum: + - "low-active" + - "high-active" + + vbus-discharge-enable-polarity: + type: string + description: | + Polarity of VBUS discharge enable + enum: + - "low-active" + - "high-active" + + vbus-sink-enable-polarity: + type: string + description: | + Polarity of VBUS sink enable + enum: + - "low-active" + - "high-active" + + vbus-source-enable-polarity: + type: string + description: | + Polarity of VBUS source enable + enum: + - "low-active" + - "high-active" + + vbus-divide: + type: string + required: true + description: | + VBUS measurement divider + enum: + - "no-divide" + - "divide-10" + - "divide-20" + + dead-battery: + type: boolean + description: | + Determine if USB-C Dead Battery pull-down resistor should be + applied to the CC lines. + + pinctrl-0: + required: true + + pinctrl-names: + required: true + + gpios: + type: phandle-array + required: true + + gpio-names: + type: string-array + required: true + description: | + Valid names of GPIO: + "vbus-detect": GPIO for VBUS detect (must) + "vbus-discharge": GPIO for VBUS discharge (option) + "vconn-discharge": GPIO for VCONN discharge (option) + + io-channels: + type: phandle-array + description: | + EADC channels for measuring VBUS/VCONN voltage + + io-channel-names: + type: string-array + description: | + Valid names of EADC channels: + "chn-vbus": EADC channel for measuring VBUS voltage (option) + "chn-vconn": EADC channel for measuring VCONN voltage (option) + + adc-measure-timer-trigger-rate: + type: int + description: | + Rate of timer-triggered EADC measurement (Hz). + This is ignored when none of above is specified. + The default is chosen by following BSP sample, + and is to update UTCPD in a proper rate. + default: 100 diff --git a/dts/bindings/usb-c/nuvoton,numaker-vbus.yaml b/dts/bindings/usb-c/nuvoton,numaker-vbus.yaml new file mode 100644 index 00000000000..b66e75f3ff2 --- /dev/null +++ b/dts/bindings/usb-c/nuvoton,numaker-vbus.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2024 Nuvoton Technology Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Nuvoton NuMaker USB Type-C VBUS controller + +compatible: "nuvoton,numaker-vbus" + +include: [base.yaml] diff --git a/modules/Kconfig.nuvoton b/modules/Kconfig.nuvoton index b9765360351..11314843997 100644 --- a/modules/Kconfig.nuvoton +++ b/modules/Kconfig.nuvoton @@ -79,4 +79,8 @@ menu "Nuvoton NuMaker drivers" bool "NuMaker RTC" help Enable Nuvoton RTC HAL module driver + config HAS_NUMAKER_TMR + bool "NuMaker Timer" + help + Enable Nuvoton Timer HAL module driver endmenu