| /* |
| * Copyright (c) 2024 Nuvoton Technology Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT nuvoton_numaker_tcpc |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/drivers/usb_c/usbc_tcpc.h> |
| #include <zephyr/drivers/usb_c/usbc_ppc.h> |
| #include <zephyr/drivers/clock_control.h> |
| #include <zephyr/drivers/clock_control/clock_control_numaker.h> |
| #include <zephyr/drivers/reset.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/adc.h> |
| #include <zephyr/sys/byteorder.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(tcpc_numaker, CONFIG_USBC_LOG_LEVEL); |
| |
| #include <soc.h> |
| #include <NuMicro.h> |
| |
| #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.<PIN> 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_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 = (0 << UTCPD_VBVOL_VBSCALE_Pos), .value = 20}), \ |
| (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}), \ |
| (vbus-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); |