Add wrapper DEVICE_API macro to all tcpc_driver_api instances. Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
2554 lines
78 KiB
C
2554 lines
78 KiB
C
/*
|
|
* 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);
|