Browse Source

drivers: tcpc: Add TCPC driver for FUSB307

Add support for FUSB307.

Signed-off-by: Jianxiong Gu <jianxiong.gu@outlook.com>
pull/90656/head
Jianxiong Gu 5 months ago committed by Benjamin Cabé
parent
commit
d9486cd3f0
  1. 1
      drivers/usb_c/tcpc/CMakeLists.txt
  2. 1
      drivers/usb_c/tcpc/Kconfig
  3. 28
      drivers/usb_c/tcpc/Kconfig.tcpc_fusb307
  4. 742
      drivers/usb_c/tcpc/fusb307.c
  5. 61
      drivers/usb_c/tcpc/fusb307.h
  6. 64
      dts/bindings/tcpc/onnn,fusb307.yaml

1
drivers/usb_c/tcpc/CMakeLists.txt

@ -8,3 +8,4 @@ zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_NUMAKER ucpd_numaker.c)
zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_TCPCI tcpci.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_TCPCI tcpci.c)
zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_PS8XXX ps8xxx.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_PS8XXX ps8xxx.c)
zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_RT1715 rt1715.c) zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_RT1715 rt1715.c)
zephyr_library_sources_ifdef(CONFIG_USBC_TCPC_FUSB307 fusb307.c)

1
drivers/usb_c/tcpc/Kconfig

@ -30,6 +30,7 @@ source "drivers/usb_c/tcpc/Kconfig.tcpc_numaker"
source "drivers/usb_c/tcpc/Kconfig.tcpc_tcpci" source "drivers/usb_c/tcpc/Kconfig.tcpc_tcpci"
source "drivers/usb_c/tcpc/Kconfig.tcpc_ps8xxx" source "drivers/usb_c/tcpc/Kconfig.tcpc_ps8xxx"
source "drivers/usb_c/tcpc/Kconfig.tcpc_rt1715" source "drivers/usb_c/tcpc/Kconfig.tcpc_rt1715"
source "drivers/usb_c/tcpc/Kconfig.tcpc_fusb307"
module = USBC module = USBC
module-str = usbc module-str = usbc

28
drivers/usb_c/tcpc/Kconfig.tcpc_fusb307

@ -0,0 +1,28 @@
# ON Semiconductor FUSB307 TCPC configuration options
# Copyright (c) 2025 Jianxiong Gu <jianxiong.gu@outlook.com>
# SPDX-License-Identifier: Apache-2.0
config USBC_TCPC_FUSB307
bool "USB-C TCPC device controller driver"
select USBC_TCPC_TCPCI
default y
depends on DT_HAS_ONNN_FUSB307_TCPC_ENABLED
help
Enable Type-C Port Controller driver for FUSB307.
if USBC_TCPC_FUSB307
config USBC_TCPC_FUSB307_INIT_DELAY
int "FUSB307 init delay"
default 5
help
Delay between each try of the TCPC initialization
config USBC_TCPC_FUSB307_INIT_RETRIES
int "FUSB307 init retries"
default 10
help
Number of initialization tries that will be performed
endif

742
drivers/usb_c/tcpc/fusb307.c

@ -0,0 +1,742 @@
/*
* Copyright (c) 2025 Jianxiong Gu <jianxiong.gu@outlook.com>
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/device.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/usb_c/tcpci_priv.h>
#include <zephyr/usb_c/usbc.h>
#include <zephyr/usb_c/tcpci.h>
#include <zephyr/shell/shell.h>
#include "fusb307.h"
#define DT_DRV_COMPAT onnn_fusb307_tcpc
LOG_MODULE_REGISTER(tcpc_fusb307, CONFIG_USBC_LOG_LEVEL);
struct fusb307_data {
/* Device structure used to retrieve it in k_work functions */
const struct device *const dev;
struct k_work_delayable init_dwork;
int init_retries;
bool initialized;
/* TCPCI Specification Revision */
uint8_t pd_int_rev;
struct gpio_callback alert_cb;
struct k_work alert_work;
bool msg_pending;
/* One-slot Rx FIFO */
struct pd_msg rx_msg;
tcpc_alert_handler_cb_t alert_handler;
void *alert_handler_data;
tcpc_vconn_discharge_cb_t vconn_discharge_cb;
tcpc_vconn_control_cb_t vconn_cb;
bool cc_changed;
enum tc_cc_voltage_state cc1;
enum tc_cc_voltage_state cc2;
enum tc_cc_polarity cc_polarity;
enum tc_power_role power_role;
/* Flag to receive or ignore SOP Prime messages */
bool rx_sop_prime_enable;
};
struct fusb307_cfg {
const struct i2c_dt_spec bus;
const struct gpio_dt_spec alert_gpio;
const struct gpio_dt_spec vconn_disc_gpio;
/* Maximum number of packet retransmissions done by TCPC */
const uint8_t transmit_retries;
};
static int tcpci_init_alert_mask(const struct device *dev)
{
const struct fusb307_cfg *cfg = dev->config;
uint16_t mask = TCPC_REG_ALERT_TX_COMPLETE | TCPC_REG_ALERT_RX_STATUS |
TCPC_REG_ALERT_RX_HARD_RST | TCPC_REG_ALERT_CC_STATUS |
TCPC_REG_ALERT_POWER_STATUS | TCPC_REG_ALERT_FAULT |
TCPC_REG_ALERT_RX_BUF_OVF;
return tcpci_tcpm_mask_status_register(&cfg->bus, TCPC_ALERT_STATUS, mask);
}
static int fusb307_tcpc_init(const struct device *dev)
{
struct fusb307_data *data = dev->data;
if (!data->initialized) {
if (data->init_retries > CONFIG_USBC_TCPC_FUSB307_INIT_RETRIES) {
LOG_ERR("TCPC was not initialized correctly");
return -EIO;
}
return -EAGAIN;
}
data->rx_sop_prime_enable = false;
data->msg_pending = false;
memset(&data->rx_msg, 0x00, sizeof(data->rx_msg));
LOG_INF("FUSB307 TCPC initialized");
return 0;
}
static int fusb307_tcpc_get_cc(const struct device *dev, enum tc_cc_voltage_state *cc1,
enum tc_cc_voltage_state *cc2)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
int ret;
if (!data->initialized) {
return -EIO;
}
if (IS_ENABLED(CONFIG_USBC_CSM_SINK_ONLY) && !data->cc_changed) {
*cc1 = data->cc1;
*cc2 = data->cc2;
return 0;
}
data->cc_changed = false;
ret = tcpci_tcpm_get_cc(&cfg->bus, cc1, cc2);
if (IS_ENABLED(CONFIG_USBC_CSM_SINK_ONLY) || *cc1 != data->cc1 || *cc2 != data->cc2) {
LOG_DBG("CC changed values: %d->%d, %d->%d", data->cc1, *cc1, data->cc2, *cc2);
data->cc1 = *cc1;
data->cc2 = *cc2;
}
return ret;
}
static int fusb307_tcpc_select_rp_value(const struct device *dev, enum tc_rp_value rp)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
data->cc_changed = true;
return tcpci_tcpm_select_rp_value(&cfg->bus, rp);
}
static int fusb307_tcpc_get_rp_value(const struct device *dev, enum tc_rp_value *rp)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_get_rp_value(&cfg->bus, rp);
}
static int fusb307_tcpc_set_cc(const struct device *dev, enum tc_cc_pull pull)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
if (!data->initialized) {
return -EIO;
}
data->cc_changed = true;
return tcpci_tcpm_set_cc(&cfg->bus, pull);
}
static void fusb307_tcpc_set_vconn_discharge_cb(const struct device *dev,
tcpc_vconn_discharge_cb_t cb)
{
struct fusb307_data *data = dev->data;
data->vconn_discharge_cb = cb;
}
static void fusb307_tcpc_set_vconn_cb(const struct device *dev, tcpc_vconn_control_cb_t vconn_cb)
{
struct fusb307_data *data = dev->data;
data->vconn_cb = vconn_cb;
}
static int fusb307_tcpc_vconn_discharge(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
if (cfg->vconn_disc_gpio.port == NULL) {
/* FUSB307 does not have built-in VCONN discharge path */
LOG_ERR("VCONN discharge GPIO is not defined");
return -EIO;
}
return gpio_pin_set_dt(&cfg->vconn_disc_gpio, enable);
}
static int fusb307_tcpc_set_vconn(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
int ret;
if (!data->initialized) {
return -EIO;
}
data->cc_changed = true;
ret = tcpci_tcpm_set_vconn(&cfg->bus, enable);
if (ret != 0) {
return ret;
}
if (data->vconn_cb != NULL) {
ret = data->vconn_cb(dev, data->cc_polarity, enable);
}
return ret;
}
static int fusb307_tcpc_set_roles(const struct device *dev, enum tc_power_role power_role,
enum tc_data_role data_role)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
int ret;
ret = tcpci_tcpm_set_roles(&cfg->bus, PD_REV20, power_role, data_role);
if (ret != 0) {
return ret;
}
data->power_role = power_role;
return 0;
}
static int fusb307_tcpc_get_rx_pending_msg(const struct device *dev, struct pd_msg *msg)
{
struct fusb307_data *data = dev->data;
/* Rx message pending? */
if (!data->msg_pending) {
return -ENODATA;
}
/* Query status only? */
if (msg == NULL) {
return 0;
}
/* Dequeue Rx FIFO */
*msg = data->rx_msg;
data->msg_pending = false;
/* Indicate Rx message returned */
return 1;
}
static int fusb307_tcpc_rx_fifo_enqueue(const struct device *dev)
{
const struct fusb307_cfg *const cfg = dev->config;
struct fusb307_data *data = dev->data;
uint8_t buf[4];
uint8_t rxbcnt;
uint8_t rxftype;
uint16_t rxhead;
uint8_t rx_data_size;
struct pd_msg *msg = &data->rx_msg;
int ret = 0;
ret = i2c_burst_read_dt(&cfg->bus, TCPC_REG_RX_BUFFER, buf, sizeof(buf));
if (ret != 0) {
return ret;
}
rxbcnt = buf[0];
rxftype = buf[1];
rxhead = (buf[3] << 8) | buf[2];
/* rxbcnt = 1 (frame type) + 2 (Message Header) + Rx data byte count */
if (rxbcnt < 3) {
LOG_ERR("Invalid RXBCNT: %d", rxbcnt);
return -EIO;
}
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);
return -EIO;
}
/* Rx frame type */
msg->type = rxftype;
/* Rx header */
msg->header.raw_value = (uint16_t)rxhead;
/* Rx data size */
msg->len = rx_data_size;
/* Rx data */
if (rx_data_size > 0) {
ret = i2c_burst_read_dt(&cfg->bus, TCPC_REG_RX_BUFFER + 4, msg->data, rx_data_size);
if (ret) {
LOG_ERR("Failed to read Rx data: %d", ret);
}
}
return ret;
}
static int fusb307_tcpc_set_rx_enable(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
if (!enable) {
return tcpci_tcpm_set_rx_type(&cfg->bus, 0);
}
if (data->rx_sop_prime_enable) {
return tcpci_tcpm_set_rx_type(&cfg->bus,
TCPC_REG_RX_DETECT_SOP_SOPP_SOPPP_HRST_MASK);
} else {
return tcpci_tcpm_set_rx_type(&cfg->bus, TCPC_REG_RX_DETECT_SOP_HRST_MASK);
}
}
static int fusb307_tcpc_set_cc_polarity(const struct device *dev, enum tc_cc_polarity polarity)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
enum tc_cc_voltage_state cc1, cc2;
int ret;
if (!data->initialized) {
return -EIO;
}
ret = tcpci_tcpm_set_cc_polarity(&cfg->bus, polarity);
if (ret != 0) {
return ret;
}
/* FUSB307 won't respond with GoodCRC on CC2 unless CC1 is set to OPEN. */
tcpci_tcpm_get_cc(&cfg->bus, &cc1, &cc2);
if (cc1) {
tcpci_update_reg8(&cfg->bus, TCPC_REG_ROLE_CTRL, TCPC_REG_ROLE_CTRL_CC2_MASK,
TCPC_REG_ROLE_CTRL_SET(0, 0, 0, TC_CC_OPEN));
} else if (cc2) {
tcpci_update_reg8(&cfg->bus, TCPC_REG_ROLE_CTRL, TCPC_REG_ROLE_CTRL_CC1_MASK,
TCPC_REG_ROLE_CTRL_SET(0, 0, TC_CC_OPEN, 0));
} else {
if (data->power_role == TC_ROLE_SINK) {
tcpci_tcpm_set_cc(&cfg->bus, TC_CC_RD);
} else {
tcpci_tcpm_set_cc(&cfg->bus, TC_CC_RP);
}
}
data->cc_changed = true;
data->cc_polarity = polarity;
return 0;
}
static int fusb307_tcpc_transmit_data(const struct device *dev, struct pd_msg *msg)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_transmit_data(&cfg->bus, msg, cfg->transmit_retries);
}
static int fusb307_tcpc_dump_std_reg(const struct device *dev)
{
const struct fusb307_cfg *cfg = dev->config;
LOG_INF("TCPC %s:%s registers:", cfg->bus.bus->name, dev->name);
return tcpci_tcpm_dump_std_reg(&cfg->bus);
}
void fusb307_tcpc_alert_handler_cb(const struct device *dev, void *data, enum tcpc_alert alert)
{
}
static int fusb307_tcpc_get_status_register(const struct device *dev, enum tcpc_status_reg reg,
uint32_t *status)
{
const struct fusb307_cfg *cfg = dev->config;
if (reg == TCPC_VENDOR_DEFINED_STATUS) {
return tcpci_read_reg8(&cfg->bus, FUSB307_REG_ALERT_VD, (uint8_t *)status);
}
return tcpci_tcpm_get_status_register(&cfg->bus, reg, (uint16_t *)status);
}
static int fusb307_tcpc_clear_status_register(const struct device *dev, enum tcpc_status_reg reg,
uint32_t mask)
{
const struct fusb307_cfg *cfg = dev->config;
if (reg == TCPC_VENDOR_DEFINED_STATUS) {
return tcpci_write_reg8(&cfg->bus, FUSB307_REG_ALERT_VD, (uint8_t)mask);
}
return tcpci_tcpm_clear_status_register(&cfg->bus, reg, (uint16_t)mask);
}
static int fusb307_tcpc_mask_status_register(const struct device *dev, enum tcpc_status_reg reg,
uint32_t mask)
{
const struct fusb307_cfg *cfg = dev->config;
if (reg == TCPC_VENDOR_DEFINED_STATUS) {
return tcpci_write_reg8(&cfg->bus, FUSB307_REG_ALERT_VD_MASK, (uint8_t)mask);
}
return tcpci_tcpm_mask_status_register(&cfg->bus, reg, (uint16_t)mask);
}
static int fusb307_tcpc_set_drp_toggle(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
const struct fusb307_data *data = dev->data;
return tcpci_tcpm_set_drp_toggle(&cfg->bus, data->pd_int_rev, enable);
}
static int fusb307_tcpc_set_debug_accessory(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_set_debug_accessory(&cfg->bus, enable);
}
static int fusb307_tcpc_get_snk_ctrl(const struct device *dev)
{
const struct fusb307_cfg *cfg = dev->config;
bool sinking;
int ret;
ret = tcpci_tcpm_get_snk_ctrl(&cfg->bus, &sinking);
if (ret != 0) {
return ret;
}
return sinking;
}
static int fusb307_tcpc_set_snk_ctrl(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_set_snk_ctrl(&cfg->bus, enable);
}
static int fusb307_tcpc_get_src_ctrl(const struct device *dev)
{
const struct fusb307_cfg *cfg = dev->config;
bool sourcing;
int ret;
ret = tcpci_tcpm_get_src_ctrl(&cfg->bus, &sourcing);
if (ret != 0) {
return ret;
}
return sourcing;
}
static int fusb307_tcpc_set_src_ctrl(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_set_src_ctrl(&cfg->bus, enable);
}
static int fusb307_tcpc_get_chip_info(const struct device *dev, struct tcpc_chip_info *chip_info)
{
const struct fusb307_cfg *cfg = dev->config;
if (chip_info == NULL) {
return -EIO;
}
chip_info->fw_version_number = 0;
chip_info->min_req_fw_version_number = 0;
return tcpci_tcpm_get_chip_info(&cfg->bus, chip_info);
}
static int fusb307_tcpc_set_low_power_mode(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_set_low_power_mode(&cfg->bus, enable);
}
static int fusb307_tcpc_sop_prime_enable(const struct device *dev, bool enable)
{
struct fusb307_data *data = dev->data;
data->rx_sop_prime_enable = enable;
return 0;
}
static int fusb307_tcpc_set_bist_test_mode(const struct device *dev, bool enable)
{
const struct fusb307_cfg *cfg = dev->config;
return tcpci_tcpm_set_bist_test_mode(&cfg->bus, enable);
}
static int fusb307_tcpc_set_alert_handler_cb(const struct device *dev,
tcpc_alert_handler_cb_t handler, void *handler_data)
{
struct fusb307_data *data = dev->data;
if (data->alert_handler == handler && data->alert_handler_data == handler_data) {
return 0;
}
data->alert_handler = handler;
data->alert_handler_data = handler_data;
return 0;
}
static DEVICE_API(tcpc, fusb307_driver_api) = {
.init = fusb307_tcpc_init,
.get_cc = fusb307_tcpc_get_cc,
.select_rp_value = fusb307_tcpc_select_rp_value,
.get_rp_value = fusb307_tcpc_get_rp_value,
.set_cc = fusb307_tcpc_set_cc,
.set_vconn_discharge_cb = fusb307_tcpc_set_vconn_discharge_cb,
.set_vconn_cb = fusb307_tcpc_set_vconn_cb,
.vconn_discharge = fusb307_tcpc_vconn_discharge,
.set_vconn = fusb307_tcpc_set_vconn,
.set_roles = fusb307_tcpc_set_roles,
.get_rx_pending_msg = fusb307_tcpc_get_rx_pending_msg,
.set_rx_enable = fusb307_tcpc_set_rx_enable,
.set_cc_polarity = fusb307_tcpc_set_cc_polarity,
.transmit_data = fusb307_tcpc_transmit_data,
.dump_std_reg = fusb307_tcpc_dump_std_reg,
.alert_handler_cb = fusb307_tcpc_alert_handler_cb,
.get_status_register = fusb307_tcpc_get_status_register,
.clear_status_register = fusb307_tcpc_clear_status_register,
.mask_status_register = fusb307_tcpc_mask_status_register,
.set_debug_accessory = fusb307_tcpc_set_debug_accessory,
.set_debug_detach = NULL,
.set_drp_toggle = fusb307_tcpc_set_drp_toggle,
.get_snk_ctrl = fusb307_tcpc_get_snk_ctrl,
.set_snk_ctrl = fusb307_tcpc_set_snk_ctrl,
.get_src_ctrl = fusb307_tcpc_get_src_ctrl,
.set_src_ctrl = fusb307_tcpc_set_src_ctrl,
.get_chip_info = fusb307_tcpc_get_chip_info,
.set_low_power_mode = fusb307_tcpc_set_low_power_mode,
.sop_prime_enable = fusb307_tcpc_sop_prime_enable,
.set_bist_test_mode = fusb307_tcpc_set_bist_test_mode,
.set_alert_handler_cb = fusb307_tcpc_set_alert_handler_cb,
};
void fusb307_alert_cb(const struct device *port, struct gpio_callback *cb, gpio_port_pins_t pins)
{
struct fusb307_data *data = CONTAINER_OF(cb, struct fusb307_data, alert_cb);
k_work_submit(&data->alert_work);
}
void fusb307_alert_work_cb(struct k_work *work)
{
struct fusb307_data *data = CONTAINER_OF(work, struct fusb307_data, alert_work);
const struct device *dev = data->dev;
const struct fusb307_cfg *cfg = dev->config;
uint16_t alert_reg = 0;
uint16_t clear_flags = 0;
if (!data->initialized) {
return;
}
tcpci_tcpm_get_status_register(&cfg->bus, TCPC_ALERT_STATUS, &alert_reg);
while (alert_reg != 0) {
enum tcpc_alert alert_type = tcpci_alert_reg_to_enum(alert_reg);
if (alert_type == TCPC_ALERT_HARD_RESET_RECEIVED) {
LOG_DBG("hard rst received");
tcpci_init_alert_mask(dev);
data->cc_changed = true;
} else if (alert_type == TCPC_ALERT_FAULT_STATUS) {
uint8_t fault;
tcpci_tcpm_get_status_register(&cfg->bus, TCPC_FAULT_STATUS,
(uint16_t *)&fault);
tcpci_tcpm_clear_status_register(&cfg->bus, TCPC_FAULT_STATUS,
(uint16_t)fault);
LOG_DBG("fault: %02x", fault);
} else if (alert_type == TCPC_ALERT_POWER_STATUS) {
uint8_t pwr_status;
tcpci_tcpm_get_status_register(&cfg->bus, TCPC_POWER_STATUS,
(uint16_t *)&pwr_status);
LOG_DBG("power status: %02x", pwr_status);
} else if (alert_type == TCPC_ALERT_MSG_STATUS) {
LOG_DBG("MSG pending");
if (fusb307_tcpc_rx_fifo_enqueue(dev) == 0) {
data->msg_pending = true;
}
} else if (alert_type == TCPC_ALERT_CC_STATUS) {
data->cc_changed = true;
}
if (data->alert_handler != NULL) {
data->alert_handler(dev, data->alert_handler_data, alert_type);
}
clear_flags |= BIT(alert_type);
alert_reg &= ~BIT(alert_type);
}
tcpci_tcpm_clear_status_register(&cfg->bus, TCPC_ALERT_STATUS, clear_flags);
tcpci_tcpm_get_status_register(&cfg->bus, TCPC_ALERT_STATUS, &alert_reg);
/* If alert_reg is not 0 or the interrupt signal is still active */
if ((alert_reg != 0) || gpio_pin_get_dt(&cfg->alert_gpio)) {
k_work_submit(work);
}
}
void fusb307_init_work_cb(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct fusb307_data *data = CONTAINER_OF(dwork, struct fusb307_data, init_dwork);
const struct fusb307_cfg *cfg = data->dev->config;
uint8_t power_reg = 0;
struct tcpc_chip_info chip_info;
uint16_t tcpci_rev;
int ret;
LOG_INF("Initializing FUSB307 chip: %s", data->dev->name);
ret = tcpci_tcpm_get_status_register(&cfg->bus, TCPC_POWER_STATUS, (uint16_t *)&power_reg);
if (ret != 0 || (power_reg & TCPC_REG_POWER_STATUS_UNINIT)) {
data->init_retries++;
if (data->init_retries > CONFIG_USBC_TCPC_FUSB307_INIT_RETRIES) {
LOG_ERR("Chip didn't respond");
return;
}
LOG_DBG("Postpone chip initialization %d", data->init_retries);
k_work_schedule(&data->init_dwork, K_MSEC(CONFIG_USBC_TCPC_FUSB307_INIT_DELAY));
return;
}
fusb307_tcpc_get_chip_info(data->dev, &chip_info);
LOG_INF("Initialized chip is: %04x:%04x:%04x", chip_info.vendor_id, chip_info.product_id,
chip_info.device_id);
/* get TCPCI Specification Revision */
tcpci_read_reg16(&cfg->bus, TCPC_REG_PD_INT_REV, &tcpci_rev);
data->pd_int_rev = TCPC_REG_PD_INT_REV_REV_MAJOR(tcpci_rev) << 4 |
TCPC_REG_PD_INT_REV_REV_MINOR(tcpci_rev);
/* Clear reset flag */
tcpci_tcpm_clear_status_register(&cfg->bus, TCPC_FAULT_STATUS,
TCPC_REG_FAULT_STATUS_ALL_REGS_RESET);
/* Mask all vendor defined alerts */
tcpci_write_reg8(&cfg->bus, FUSB307_REG_ALERT_VD_MASK, 0);
/* Mask all alerts */
tcpci_tcpm_mask_status_register(&cfg->bus, TCPC_ALERT_STATUS, 0);
/* Clear the current alert register */
tcpci_tcpm_clear_status_register(&cfg->bus, TCPC_ALERT_STATUS, 0xff);
/* Initialize alert interrupt */
gpio_pin_configure_dt(&cfg->alert_gpio, GPIO_INPUT);
gpio_init_callback(&data->alert_cb, fusb307_alert_cb, BIT(cfg->alert_gpio.pin));
gpio_add_callback(cfg->alert_gpio.port, &data->alert_cb);
gpio_pin_interrupt_configure_dt(&cfg->alert_gpio, GPIO_INT_EDGE_TO_ACTIVE);
tcpci_init_alert_mask(data->dev);
data->initialized = true;
/* Disable DRP Toggle */
fusb307_tcpc_set_drp_toggle(data->dev, false);
/* Set DebugAccessoryControl control by TCPM */
tcpci_update_reg8(&cfg->bus, TCPC_REG_TCPC_CTRL,
TCPC_REG_TCPC_CTRL_DEBUG_ACC_CONTROL,
TCPC_REG_TCPC_CTRL_DEBUG_ACC_CONTROL);
/* Check and clear any alert set after initialization */
k_work_submit(&data->alert_work);
}
static int fusb307_dev_init(const struct device *dev)
{
const struct fusb307_cfg *cfg = dev->config;
struct fusb307_data *data = dev->data;
int ret;
if (!device_is_ready(cfg->bus.bus)) {
return -EIO;
}
/* Resets the chip */
ret = tcpci_write_reg8(&cfg->bus, FUSB307_REG_RESET, FUSB307_REG_RESET_SW_RST);
if (ret != 0) {
LOG_ERR("Failed to reset chip: %d", ret);
return ret;
}
k_work_init_delayable(&data->init_dwork, fusb307_init_work_cb);
k_work_schedule(&data->init_dwork, K_MSEC(CONFIG_USBC_TCPC_FUSB307_INIT_DELAY));
k_work_init(&data->alert_work, fusb307_alert_work_cb);
return 0;
}
#define VCONN_DISC_GPIO(node) \
.vconn_disc_gpio = COND_CODE_1(DT_INST_NODE_HAS_PROP(node, vconn_disc_gpios), \
(GPIO_DT_SPEC_INST_GET(node, vconn_disc_gpios)), ({0}))
#define FUSB307_INSTANCE_DEFINE(inst) \
static struct fusb307_data drv_data_fusb307##inst = { \
.dev = DEVICE_DT_GET(DT_DRV_INST(inst)), \
.init_retries = 0, \
.cc_changed = true, \
}; \
static struct fusb307_cfg drv_cfg_fusb307##inst = { \
.bus = I2C_DT_SPEC_GET(DT_DRV_INST(inst)), \
.alert_gpio = GPIO_DT_SPEC_GET(DT_DRV_INST(inst), irq_gpios), \
.transmit_retries = DT_PROP(DT_DRV_INST(inst), transmit_retries), \
VCONN_DISC_GPIO(DT_DRV_INST(inst)), \
}; \
DEVICE_DT_INST_DEFINE(inst, &fusb307_dev_init, NULL, &drv_data_fusb307##inst, \
&drv_cfg_fusb307##inst, POST_KERNEL, CONFIG_USBC_TCPC_INIT_PRIORITY, \
&fusb307_driver_api);
DT_INST_FOREACH_STATUS_OKAY(FUSB307_INSTANCE_DEFINE)

61
drivers/usb_c/tcpc/fusb307.h

@ -0,0 +1,61 @@
/*
* Copyright (c) 2025 Jianxiong Gu <jianxiong.gu@outlook.com>
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_USBC_TCPC_FUSB307_H_
#define ZEPHYR_DRIVERS_USBC_TCPC_FUSB307_H_
#define FUSB307_VENDOR_ID 0x0779
#define FUSB307_PRODUCT_ID 0x0133
#define FUSB307_REG_VCONN_OCP 0xA0
#define FUSB307_REG_VCONN_OCP_RANGE BIT(3)
#define FUSB307_REG_VCONN_OCP_CUR GENMASK(2, 0)
#define FUSB307_REG_RESET 0xA2
#define FUSB307_REG_RESET_PD_RST BIT(1)
#define FUSB307_REG_RESET_SW_RST BIT(0)
#define FUSB307_REG_GPIO1_CFG 0xA4
#define FUSB307_REG_GPIO2_CFG 0xA5
#define FUSB307_REG_GPIO_CFG_GPO_VAL BIT(2)
#define FUSB307_REG_GPIO_CFG_GPI_EN BIT(1)
#define FUSB307_REG_GPIO_CFG_GPO_EN BIT(0)
#define FUSB307_REG_GPIO2_CFG_FR_SWAP_FN BIT(3)
#define FUSB307_REG_GPIO_STAT 0xA6
#define FUSB307_REG_GPIO_STAT_GPI2_VAL BIT(1)
#define FUSB307_REG_GPIO_STAT_GPI1_VAL BIT(0)
#define FUSB307_REG_DRPTOGGLE 0xA7
#define FUSB307_REG_DRPTOGGLE_DRPTOGGLE BIT(0)
#define FUSB307_REG_SINK_TRANSMIT 0xB0
#define FUSB307_REG_SINK_TRANSMIT_DIS_SNK_TX BIT(6)
#define FUSB307_REG_SINK_TRANSMIT_RETRY_CNT GENMASK(5, 4)
#define FUSB307_REG_SINK_TRANSMIT_TXSOP GENMASK(2, 0)
#define FUSB307_REG_SRC_FRSWAP 0xB1
#define FUSB307_REG_SRC_FRSWAP_FRSWAP_SNK_DELAY GENMASK(3, 2)
#define FUSB307_REG_SRC_FRSWAP_MANUAL_SNK_EN BIT(1)
#define FUSB307_REG_SRC_FRSWAP_FR_SWAP BIT(0)
#define FUSB307_REG_SNK_FRSWAP 0xB2
#define FUSB307_REG_SNK_FRSWAP_EN_FRSWAP_DTCT BIT(0)
#define FUSB307_REG_ALERT_VD 0xB3
#define FUSB307_REG_ALERT_VD_DISCH_SUCC BIT(6)
#define FUSB307_REG_ALERT_VD_GPI2 BIT(5)
#define FUSB307_REG_ALERT_VD_GPI1 BIT(4)
#define FUSB307_REG_ALERT_VD_VDD_DTCT BIT(3)
#define FUSB307_REG_ALERT_VD_OTP BIT(2)
#define FUSB307_REG_ALERT_VD_SWAP_TX BIT(1)
#define FUSB307_REG_ALERT_VD_SWAP_RX BIT(0)
#define FUSB307_REG_ALERT_VD_MASK 0xB4
/* Sampling period of VBUS Measurement, at least greater than 3ms */
#define FUSB307_T_VBUS_SAMPLE 4 /* ms */
#endif /* ZEPHYR_DRIVERS_USBC_TCPC_FUSB307_H_ */

64
dts/bindings/tcpc/onnn,fusb307.yaml

@ -0,0 +1,64 @@
# Copyright (c) 2025 Jianxiong Gu <jianxiong.gu@outlook.com>
# SPDX-License-Identifier: Apache-2.0
description: |
Onsemi FUSB307 USB Type-C port controller
Example:
ports {
#address-cells = <1>;
#size-cells = <0>;
port0: usbc-port@0 {
compatible = "usb-c-connector";
reg = <0>;
tcpc = <&fusb307_tcpc0>;
vbus = <&fusb307_vbus0>;
power-role = "sink";
sink-pdos = <PDO_FIXED(5000, 100, 0)>;
};
};
i2c1 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST_PLUS>;
fusb307_tcpc0: fusb307_tcpc0@50 {
compatible = "onnn,fusb307-tcpc";
reg = <0x50>;
irq-gpios = <&gpioa 8 GPIO_ACTIVE_LOW>;
status = "okay";
fusb307_vbus0: fusb307_vbus0 {
compatible = "zephyr,usb-c-vbus-tcpci";
status = "okay";
};
};
};
compatible: "onnn,fusb307-tcpc"
include: [base.yaml, pinctrl-device.yaml]
properties:
reg:
required: true
irq-gpios:
type: phandle-array
required: true
description: |
Interrupt GPIO pin connected from the chip.
transmit-retries:
type: int
default: 2
description: |
Maximum number of packet retransmissions done by TCPC. Valid values are <0, 3>.
This value is used to fill the Retry Counter part of the TCPCI Transmit register.
vconn-disc-gpios:
type: phandle-array
description: |
GPIO pin for VCONN discharge control. FUSB307 does not have VCONN discharge capability.
If VCONN discharge is not needed, this pin does not need to be defined in the device
tree. Otherwise, this pin and a discharge path are required.
Loading…
Cancel
Save