You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2554 lines
78 KiB
2554 lines
78 KiB
/* |
|
* 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);
|
|
|