/*
 * 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 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.<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);
