/* * 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 DEVICE_API(tcpc, 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_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);