fw: initial controller fw

This commit is contained in:
Wenting Zhang 2022-09-04 15:16:18 -04:00
parent c42a54cb26
commit 6af6ed6ebf
19 changed files with 9994 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.*/
build/

50
fw/CMakeLists.txt Normal file
View file

@ -0,0 +1,50 @@
# Generated Cmake Pico project file
cmake_minimum_required(VERSION 3.13)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
# initalize pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/wenting/pico/pico-sdk")
# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)
project(fw C CXX ASM)
# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()
# Add executable. Default name is the project name, version 0.1
add_executable(fw
fw.c
utils.c
fusb302.c
ptn3460.c
tcpm_driver.c
usb_pd_driver.c
usb_pd_policy.c
usb_pd_protocol.c
)
pico_set_program_name(fw "fw")
pico_set_program_version(fw "0.1")
pico_enable_stdio_uart(fw 0)
pico_enable_stdio_usb(fw 1)
# Add the standard library to the build
target_link_libraries(fw pico_stdlib)
# Add any user requested libraries
target_link_libraries(fw
hardware_spi
hardware_dma
hardware_i2c
)
pico_add_extra_outputs(fw)

955
fw/fusb302.c Normal file
View file

@ -0,0 +1,955 @@
/*
fusb302.c - Library for interacting with the FUSB302B chip.
Copyright 2015 The Chromium OS Authors
Copyright 2017 Jason Cerundolo
Copyright 2022 Wenting Zhang
Released under an MIT license. See LICENSE file.
*/
#include <string.h>
#include "pico/stdlib.h"
#include "fusb302.h"
#include "usb_pd_tcpm.h"
#include "tcpm.h"
#include "usb_pd.h"
#define PACKET_IS_GOOD_CRC(head) (PD_HEADER_TYPE(head) == PD_CTRL_GOOD_CRC && \
PD_HEADER_CNT(head) == 0)
static struct fusb302_chip_state {
int cc_polarity;
int vconn_enabled;
/* 1 = pulling up (DFP) 0 = pulling down (UFP) */
int pulling_up;
int rx_enable;
uint8_t mdac_vnc;
uint8_t mdac_rd;
} state[CONFIG_USB_PD_PORT_COUNT];
/*
* Bring the FUSB302 out of reset after Hard Reset signaling. This will
* automatically flush both the Rx and Tx FIFOs.
*/
static void fusb302_pd_reset(int port)
{
tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_PD_RESET);
}
/*
* Flush our Rx FIFO. To prevent packet framing issues, this function should
* only be called when Rx is disabled.
*/
static void fusb302_flush_rx_fifo(int port)
{
/*
* other bits in the register _should_ be 0
* until the day we support other SOP* types...
* then we'll have to keep a shadow of what this register
* value should be so we don't clobber it here!
*/
tcpc_write(port, TCPC_REG_CONTROL1, TCPC_REG_CONTROL1_RX_FLUSH);
}
static void fusb302_flush_tx_fifo(int port)
{
int reg;
tcpc_read(port, TCPC_REG_CONTROL0, &reg);
reg |= TCPC_REG_CONTROL0_TX_FLUSH;
tcpc_write(port, TCPC_REG_CONTROL0, reg);
}
static void fusb302_auto_goodcrc_enable(int port, int enable)
{
int reg;
tcpc_read(port, TCPC_REG_SWITCHES1, &reg);
if (enable)
reg |= TCPC_REG_SWITCHES1_AUTO_GCRC;
else
reg &= ~TCPC_REG_SWITCHES1_AUTO_GCRC;
tcpc_write(port, TCPC_REG_SWITCHES1, reg);
}
/* Convert BC LVL values (in FUSB302) to Type-C CC Voltage Status */
static int convert_bc_lvl(int port, int bc_lvl)
{
/* assume OPEN unless one of the following conditions is true... */
int ret = TYPEC_CC_VOLT_OPEN;
if (state[port].pulling_up) {
if (bc_lvl == 0x00)
ret = TYPEC_CC_VOLT_RA;
else if (bc_lvl < 0x3)
ret = TYPEC_CC_VOLT_RD;
} else {
if (bc_lvl == 0x1)
ret = TYPEC_CC_VOLT_SNK_DEF;
else if (bc_lvl == 0x2)
ret = TYPEC_CC_VOLT_SNK_1_5;
else if (bc_lvl == 0x3)
ret = TYPEC_CC_VOLT_SNK_3_0;
}
return ret;
}
static int measure_cc_pin_source(int port, int cc_measure)
{
int switches0_reg;
int reg;
int cc_lvl;
/* Read status register */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* Save current value */
switches0_reg = reg;
/* Clear pull-up register settings and measure bits */
reg &= ~(TCPC_REG_SWITCHES0_MEAS_CC1 | TCPC_REG_SWITCHES0_MEAS_CC2);
/* Set desired pullup register bit */
if (cc_measure == TCPC_REG_SWITCHES0_MEAS_CC1)
reg |= TCPC_REG_SWITCHES0_CC1_PU_EN;
else
reg |= TCPC_REG_SWITCHES0_CC2_PU_EN;
/* Set CC measure bit */
reg |= cc_measure;
/* Set measurement switch */
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
/* Set MDAC for Open vs Rd/Ra comparison */
tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_vnc);
/* Wait on measurement */
sleep_us(250);
/* Read status register */
tcpc_read(port, TCPC_REG_STATUS0, &reg);
/* Assume open */
cc_lvl = TYPEC_CC_VOLT_OPEN;
/* CC level is below the 'no connect' threshold (vOpen) */
if ((reg & TCPC_REG_STATUS0_COMP) == 0) {
/* Set MDAC for Rd vs Ra comparison */
tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_rd);
/* Wait on measurement */
sleep_us(250);
/* Read status register */
tcpc_read(port, TCPC_REG_STATUS0, &reg);
cc_lvl = (reg & TCPC_REG_STATUS0_COMP) ? TYPEC_CC_VOLT_RD
: TYPEC_CC_VOLT_RA;
}
/* Restore SWITCHES0 register to its value prior */
tcpc_write(port, TCPC_REG_SWITCHES0, switches0_reg);
return cc_lvl;
}
/* Determine cc pin state for source when in manual detect mode */
static void detect_cc_pin_source_manual(int port, int *cc1_lvl, int *cc2_lvl)
{
int cc1_measure = TCPC_REG_SWITCHES0_MEAS_CC1;
int cc2_measure = TCPC_REG_SWITCHES0_MEAS_CC2;
if (state[port].vconn_enabled) {
/* If VCONN enabled, measure cc_pin that matches polarity */
if (state[port].cc_polarity)
*cc2_lvl = measure_cc_pin_source(port, cc2_measure);
else
*cc1_lvl = measure_cc_pin_source(port, cc1_measure);
} else {
/* If VCONN not enabled, measure both cc1 and cc2 */
*cc1_lvl = measure_cc_pin_source(port, cc1_measure);
*cc2_lvl = measure_cc_pin_source(port, cc2_measure);
}
}
/* Determine cc pin state for sink */
static void detect_cc_pin_sink(int port, int *cc1, int *cc2)
{
int reg;
int orig_meas_cc1;
int orig_meas_cc2;
int bc_lvl_cc1;
int bc_lvl_cc2;
/*
* Measure CC1 first.
*/
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* save original state to be returned to later... */
if (reg & TCPC_REG_SWITCHES0_MEAS_CC1)
orig_meas_cc1 = 1;
else
orig_meas_cc1 = 0;
if (reg & TCPC_REG_SWITCHES0_MEAS_CC2)
orig_meas_cc2 = 1;
else
orig_meas_cc2 = 0;
/* Disable CC2 measurement switch, enable CC1 measurement switch */
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2;
reg |= TCPC_REG_SWITCHES0_MEAS_CC1;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
/* CC1 is now being measured by FUSB302. */
/* Wait on measurement */
sleep_us(250);
tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc1);
/* mask away unwanted bits */
bc_lvl_cc1 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1);
/*
* Measure CC2 next.
*/
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* Disable CC1 measurement switch, enable CC2 measurement switch */
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1;
reg |= TCPC_REG_SWITCHES0_MEAS_CC2;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
/* CC2 is now being measured by FUSB302. */
/* Wait on measurement */
sleep_us(250);
tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc2);
/* mask away unwanted bits */
bc_lvl_cc2 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1);
*cc1 = convert_bc_lvl(port, bc_lvl_cc1);
*cc2 = convert_bc_lvl(port, bc_lvl_cc2);
/* return MEAS_CC1/2 switches to original state */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
if (orig_meas_cc1)
reg |= TCPC_REG_SWITCHES0_MEAS_CC1;
else
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1;
if (orig_meas_cc2)
reg |= TCPC_REG_SWITCHES0_MEAS_CC2;
else
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
}
/* Parse header bytes for the size of packet */
static int get_num_bytes(uint16_t header)
{
int rv;
/* Grab the Number of Data Objects field.*/
rv = PD_HEADER_CNT(header);
/* Multiply by four to go from 32-bit words -> bytes */
rv *= 4;
/* Plus 2 for header */
rv += 2;
return rv;
}
static int fusb302_send_message(int port, uint16_t header, const uint32_t *data,
uint8_t *buf, int buf_pos)
{
int rv;
int reg;
int len;
len = get_num_bytes(header);
/*
* packsym tells the TXFIFO that the next X bytes are payload,
* and should not be interpreted as special tokens.
* The 5 LSBs represent X, the number of bytes.
*/
reg = FUSB302_TKN_PACKSYM;
reg |= (len & 0x1F);
buf[buf_pos++] = reg;
/* write in the header */
reg = header;
buf[buf_pos++] = reg & 0xFF;
reg >>= 8;
buf[buf_pos++] = reg & 0xFF;
/* header is done, subtract from length to make this for-loop simpler */
len -= 2;
/* write data objects, if present */
memcpy(&buf[buf_pos], data, len);
buf_pos += len;
/* put in the CRC */
buf[buf_pos++] = FUSB302_TKN_JAMCRC;
/* put in EOP */
buf[buf_pos++] = FUSB302_TKN_EOP;
/* Turn transmitter off after sending message */
buf[buf_pos++] = FUSB302_TKN_TXOFF;
/* Start transmission */
reg = FUSB302_TKN_TXON;
buf[buf_pos++] = FUSB302_TKN_TXON;
/* burst write for speed! */
rv = tcpc_xfer(port, buf, buf_pos, 0, 0, I2C_XFER_SINGLE);
return rv;
}
static int fusb302_tcpm_select_rp_value(int port, int rp)
{
int reg;
int rv;
uint8_t vnc, rd;
rv = tcpc_read(port, TCPC_REG_CONTROL0, &reg);
if (rv)
return rv;
/* Set the current source for Rp value */
reg &= ~TCPC_REG_CONTROL0_HOST_CUR_MASK;
switch (rp) {
case TYPEC_RP_1A5:
reg |= TCPC_REG_CONTROL0_HOST_CUR_1A5;
vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_VNC_MV);
rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_RD_THRESH_MV);
break;
case TYPEC_RP_3A0:
reg |= TCPC_REG_CONTROL0_HOST_CUR_3A0;
vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_VNC_MV);
rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_RD_THRESH_MV);
break;
case TYPEC_RP_USB:
default:
reg |= TCPC_REG_CONTROL0_HOST_CUR_USB;
vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV);
rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV);
}
state[port].mdac_vnc = vnc;
state[port].mdac_rd = rd;
rv = tcpc_write(port, TCPC_REG_CONTROL0, reg);
return rv;
}
static int fusb302_tcpm_init(int port)
{
int reg;
tcpc_i2c_init();
/* set default */
state[port].cc_polarity = -1;
/* set the voltage threshold for no connect detection (vOpen) */
state[port].mdac_vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV);
/* set the voltage threshold for Rd vs Ra detection */
state[port].mdac_rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV);
/* all other variables assumed to default to 0 */
/* Restore default settings */
tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_SW_RESET);
/* Turn on retries and set number of retries */
tcpc_read(port, TCPC_REG_CONTROL3, &reg);
reg |= TCPC_REG_CONTROL3_AUTO_RETRY;
reg |= (PD_RETRY_COUNT & 0x3) <<
TCPC_REG_CONTROL3_N_RETRIES_POS;
tcpc_write(port, TCPC_REG_CONTROL3, reg);
/* Create interrupt masks */
reg = 0xFF;
/* CC level changes */
reg &= ~TCPC_REG_MASK_BC_LVL;
/* collisions */
reg &= ~TCPC_REG_MASK_COLLISION;
/* misc alert */
reg &= ~TCPC_REG_MASK_ALERT;
/* packet received with correct CRC */
reg &= ~TCPC_REG_MASK_CRC_CHK;
tcpc_write(port, TCPC_REG_MASK, reg);
reg = 0xFF;
/* when all pd message retries fail... */
reg &= ~TCPC_REG_MASKA_RETRYFAIL;
/* when fusb302 send a hard reset. */
reg &= ~TCPC_REG_MASKA_HARDSENT;
/* when fusb302 receives GoodCRC ack for a pd message */
reg &= ~TCPC_REG_MASKA_TX_SUCCESS;
/* when fusb302 receives a hard reset */
reg &= ~TCPC_REG_MASKA_HARDRESET;
tcpc_write(port, TCPC_REG_MASKA, reg);
reg = 0xFF;
/* when fusb302 sends GoodCRC to ack a pd message */
reg &= ~TCPC_REG_MASKB_GCRCSENT;
tcpc_write(port, TCPC_REG_MASKB, reg);
/* Interrupt Enable */
tcpc_read(port, TCPC_REG_CONTROL0, &reg);
reg &= ~TCPC_REG_CONTROL0_INT_MASK;
tcpc_write(port, TCPC_REG_CONTROL0, reg);
/* Set VCONN switch defaults */
tcpm_set_polarity(port, 0);
tcpm_set_vconn(port, 0);
fusb302_auto_goodcrc_enable(port, 0);
/* Turn on the power! */
/* TODO: Reduce power consumption */
tcpc_write(port, TCPC_REG_POWER, TCPC_REG_POWER_PWR_ALL);
return 0;
}
static int fusb302_tcpm_release(int port)
{
return EC_ERROR_UNIMPLEMENTED;
}
static int fusb302_tcpm_get_cc(int port, int *cc1, int *cc2)
{
if (state[port].pulling_up) {
/* Source mode? */
detect_cc_pin_source_manual(port, cc1, cc2);
} else {
/* Sink mode? */
detect_cc_pin_sink(port, cc1, cc2);
}
return 0;
}
static int fusb302_tcpm_set_cc(int port, int pull)
{
int reg;
/* NOTE: FUSB302 toggles a single pull-up between CC1 and CC2 */
/* NOTE: FUSB302 Does not support Ra. */
switch (pull) {
case TYPEC_CC_RP:
/* enable the pull-up we know to be necessary */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN |
TCPC_REG_SWITCHES0_CC1_PU_EN |
TCPC_REG_SWITCHES0_CC1_PD_EN |
TCPC_REG_SWITCHES0_CC2_PD_EN |
TCPC_REG_SWITCHES0_VCONN_CC1 |
TCPC_REG_SWITCHES0_VCONN_CC2);
reg |= TCPC_REG_SWITCHES0_CC1_PU_EN |
TCPC_REG_SWITCHES0_CC2_PU_EN;
if (state[port].vconn_enabled)
reg |= state[port].cc_polarity ?
TCPC_REG_SWITCHES0_VCONN_CC1 :
TCPC_REG_SWITCHES0_VCONN_CC2;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
state[port].pulling_up = 1;
break;
case TYPEC_CC_RD:
/* Enable UFP Mode */
/* turn off toggle */
tcpc_read(port, TCPC_REG_CONTROL2, &reg);
reg &= ~TCPC_REG_CONTROL2_TOGGLE;
tcpc_write(port, TCPC_REG_CONTROL2, reg);
/* enable pull-downs, disable pullups */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN);
reg &= ~(TCPC_REG_SWITCHES0_CC1_PU_EN);
reg |= (TCPC_REG_SWITCHES0_CC1_PD_EN);
reg |= (TCPC_REG_SWITCHES0_CC2_PD_EN);
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
state[port].pulling_up = 0;
break;
case TYPEC_CC_OPEN:
/* Disable toggling */
tcpc_read(port, TCPC_REG_CONTROL2, &reg);
reg &= ~TCPC_REG_CONTROL2_TOGGLE;
tcpc_write(port, TCPC_REG_CONTROL2, reg);
/* Ensure manual switches are opened */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
reg &= ~TCPC_REG_SWITCHES0_CC1_PU_EN;
reg &= ~TCPC_REG_SWITCHES0_CC2_PU_EN;
reg &= ~TCPC_REG_SWITCHES0_CC1_PD_EN;
reg &= ~TCPC_REG_SWITCHES0_CC2_PD_EN;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
state[port].pulling_up = 0;
break;
default:
/* Unsupported... */
return EC_ERROR_UNIMPLEMENTED;
}
return 0;
}
static int fusb302_tcpm_set_polarity(int port, int polarity)
{
/* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */
int reg;
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* clear VCONN switch bits */
reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1;
reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2;
if (state[port].vconn_enabled) {
/* set VCONN switch to be non-CC line */
if (polarity)
reg |= TCPC_REG_SWITCHES0_VCONN_CC1;
else
reg |= TCPC_REG_SWITCHES0_VCONN_CC2;
}
/* clear meas_cc bits (RX line select) */
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1;
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2;
/* set rx polarity */
if (polarity)
reg |= TCPC_REG_SWITCHES0_MEAS_CC2;
else
reg |= TCPC_REG_SWITCHES0_MEAS_CC1;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
tcpc_read(port, TCPC_REG_SWITCHES1, &reg);
/* clear tx_cc bits */
reg &= ~TCPC_REG_SWITCHES1_TXCC1_EN;
reg &= ~TCPC_REG_SWITCHES1_TXCC2_EN;
/* set tx polarity */
if (polarity)
reg |= TCPC_REG_SWITCHES1_TXCC2_EN;
else
reg |= TCPC_REG_SWITCHES1_TXCC1_EN;
tcpc_write(port, TCPC_REG_SWITCHES1, reg);
/* Save the polarity for later */
state[port].cc_polarity = polarity;
return 0;
}
static int fusb302_tcpm_set_vconn(int port, int enable)
{
/*
* FUSB302 does not have dedicated VCONN Enable switch.
* We'll get through this by disabling both of the
* VCONN - CC* switches to disable, and enabling the
* saved polarity when enabling.
* Therefore at startup, tcpm_set_polarity should be called first,
* or else live with the default put into tcpm_init.
*/
int reg;
/* save enable state for later use */
state[port].vconn_enabled = enable;
if (enable) {
/* set to saved polarity */
tcpm_set_polarity(port, state[port].cc_polarity);
} else {
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* clear VCONN switch bits */
reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1;
reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2;
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
}
return 0;
}
static int fusb302_tcpm_set_msg_header(int port, int power_role, int data_role)
{
int reg;
tcpc_read(port, TCPC_REG_SWITCHES1, &reg);
reg &= ~TCPC_REG_SWITCHES1_POWERROLE;
reg &= ~TCPC_REG_SWITCHES1_DATAROLE;
if (power_role)
reg |= TCPC_REG_SWITCHES1_POWERROLE;
if (data_role)
reg |= TCPC_REG_SWITCHES1_DATAROLE;
tcpc_write(port, TCPC_REG_SWITCHES1, reg);
return 0;
}
static int fusb302_tcpm_set_rx_enable(int port, int enable)
{
int reg;
state[port].rx_enable = enable;
/* Get current switch state */
tcpc_read(port, TCPC_REG_SWITCHES0, &reg);
/* Clear CC1/CC2 measure bits */
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1;
reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2;
if (enable) {
switch (state[port].cc_polarity) {
/* if CC polarity hasnt been determined, can't enable */
case -1:
return EC_ERROR_UNKNOWN;
case 0:
reg |= TCPC_REG_SWITCHES0_MEAS_CC1;
break;
case 1:
reg |= TCPC_REG_SWITCHES0_MEAS_CC2;
break;
default:
/* "shouldn't get here" */
return EC_ERROR_UNKNOWN;
}
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
/* Disable BC_LVL interrupt when enabling PD comm */
if (!tcpc_read(port, TCPC_REG_MASK, &reg))
tcpc_write(port, TCPC_REG_MASK,
reg | TCPC_REG_MASK_BC_LVL);
/* flush rx fifo in case messages have been coming our way */
fusb302_flush_rx_fifo(port);
} else {
tcpc_write(port, TCPC_REG_SWITCHES0, reg);
/* Enable BC_LVL interrupt when disabling PD comm */
if (!tcpc_read(port, TCPC_REG_MASK, &reg))
tcpc_write(port, TCPC_REG_MASK,
reg & ~TCPC_REG_MASK_BC_LVL);
}
fusb302_auto_goodcrc_enable(port, enable);
return 0;
}
/* Return true if our Rx FIFO is empty */
static int fusb302_rx_fifo_is_empty(int port)
{
int reg, ret;
ret = (!tcpc_read(port, TCPC_REG_STATUS1, &reg)) &&
(reg & TCPC_REG_STATUS1_RX_EMPTY);
return ret;
}
static int fusb302_tcpm_get_message(int port, uint32_t *payload, int *head)
{
/*
* This is the buffer that will get the burst-read data
* from the fusb302.
*
* It's re-used in a couple different spots, the worst of which
* is the PD packet (not header) and CRC.
* maximum size necessary = 28 + 4 = 32
*/
uint8_t buf[32];
int rv, len;
/* If our FIFO is empty then we have no packet */
if (fusb302_rx_fifo_is_empty(port))
return EC_ERROR_UNKNOWN;
/* Read until we have a non-GoodCRC packet or an empty FIFO */
do {
buf[0] = TCPC_REG_FIFOS;
/*
* PART 1 OF BURST READ: Write in register address.
* Issue a START, no STOP.
*/
rv = tcpc_xfer(port, buf, 1, 0, 0, I2C_XFER_START);
/*
* PART 2 OF BURST READ: Read up to the header.
* Issue a repeated START, no STOP.
* only grab three bytes so we can get the header
* and determine how many more bytes we need to read.
* TODO: Check token to ensure valid packet.
*/
rv |= tcpc_xfer(port, 0, 0, buf, 3, I2C_XFER_START);
/* Grab the header */
*head = (buf[1] & 0xFF);
*head |= ((buf[2] << 8) & 0xFF00);
/* figure out packet length, subtract header bytes */
len = get_num_bytes(*head) - 2;
/*
* PART 3 OF BURST READ: Read everything else.
* No START, but do issue a STOP at the end.
* add 4 to len to read CRC out
*/
rv |= tcpc_xfer(port, 0, 0, buf, len+4, I2C_XFER_STOP);
} while (!rv && PACKET_IS_GOOD_CRC(*head) &&
!fusb302_rx_fifo_is_empty(port));
if (!rv) {
/* Discard GoodCRC packets */
if (PACKET_IS_GOOD_CRC(*head))
rv = EC_ERROR_UNKNOWN;
else
memcpy(payload, buf, len);
}
/*
* If our FIFO is non-empty then we may have a packet, we may get
* fewer interrupts than packets due to interrupt latency.
*/
//if (!fusb302_rx_fifo_is_empty(port))
// task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_RX, 0);
return rv;
}
static int fusb302_tcpm_transmit(int port, enum tcpm_transmit_type type,
uint16_t header, const uint32_t *data)
{
/*
* this is the buffer that will be burst-written into the fusb302
* maximum size necessary =
* 1: FIFO register address
* 4: SOP* tokens
* 1: Token that signifies "next X bytes are not tokens"
* 30: 2 for header and up to 7*4 = 28 for rest of message
* 1: "Insert CRC" Token
* 1: EOP Token
* 1: "Turn transmitter off" token
* 1: "Star Transmission" Command
* -
* 40: 40 bytes worst-case
*/
uint8_t buf[40];
int buf_pos = 0;
int reg;
/* Flush the TXFIFO */
fusb302_flush_tx_fifo(port);
switch (type) {
case TCPC_TX_SOP:
/* put register address first for of burst tcpc write */
buf[buf_pos++] = TCPC_REG_FIFOS;
/* Write the SOP Ordered Set into TX FIFO */
buf[buf_pos++] = FUSB302_TKN_SYNC1;
buf[buf_pos++] = FUSB302_TKN_SYNC1;
buf[buf_pos++] = FUSB302_TKN_SYNC1;
buf[buf_pos++] = FUSB302_TKN_SYNC2;
fusb302_send_message(port, header, data, buf, buf_pos);
// wait for the GoodCRC to come back before we let the rest
// of the code do stuff like change polarity and miss it
sleep_us(1200);
return 0;
case TCPC_TX_HARD_RESET:
/* Simply hit the SEND_HARD_RESET bit */
tcpc_read(port, TCPC_REG_CONTROL3, &reg);
reg |= TCPC_REG_CONTROL3_SEND_HARDRESET;
tcpc_write(port, TCPC_REG_CONTROL3, reg);
break;
case TCPC_TX_BIST_MODE_2:
/* Hit the BIST_MODE2 bit and start TX */
tcpc_read(port, TCPC_REG_CONTROL1, &reg);
reg |= TCPC_REG_CONTROL1_BIST_MODE2;
tcpc_write(port, TCPC_REG_CONTROL1, reg);
tcpc_read(port, TCPC_REG_CONTROL0, &reg);
reg |= TCPC_REG_CONTROL0_TX_START;
tcpc_write(port, TCPC_REG_CONTROL0, reg);
//task_wait_event(PD_T_BIST_TRANSMIT);
/* Clear BIST mode bit, TX_START is self-clearing */
tcpc_read(port, TCPC_REG_CONTROL1, &reg);
reg &= ~TCPC_REG_CONTROL1_BIST_MODE2;
tcpc_write(port, TCPC_REG_CONTROL1, reg);
break;
default:
return EC_ERROR_UNIMPLEMENTED;
}
return 0;
}
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
static int fusb302_tcpm_get_vbus_level(int port)
{
int reg;
/* Read status register */
tcpc_read(port, TCPC_REG_STATUS0, &reg);
return (reg & TCPC_REG_STATUS0_VBUSOK) ? 1 : 0;
}
#endif
void fusb302_tcpc_alert(int port)
{
/* interrupt has been received */
int interrupt;
int interrupta;
int interruptb;
/* reading interrupt registers clears them */
tcpc_read(port, TCPC_REG_INTERRUPT, &interrupt);
tcpc_read(port, TCPC_REG_INTERRUPTA, &interrupta);
tcpc_read(port, TCPC_REG_INTERRUPTB, &interruptb);
/*
* Ignore BC_LVL changes when transmitting / receiving PD,
* since CC level will constantly change.
*/
if (state[port].rx_enable)
interrupt &= ~TCPC_REG_INTERRUPT_BC_LVL;
if (interrupt & TCPC_REG_INTERRUPT_BC_LVL) {
/* CC Status change */
//task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0);
}
if (interrupt & TCPC_REG_INTERRUPT_COLLISION) {
/* packet sending collided */
pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED);
}
/* GoodCRC was received, our FIFO is now non-empty */
if (interrupta & TCPC_REG_INTERRUPTA_TX_SUCCESS) {
//task_set_event(PD_PORT_TO_TASK_ID(port),
// PD_EVENT_RX, 0);
pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS);
}
if (interrupta & TCPC_REG_INTERRUPTA_RETRYFAIL) {
/* all retries have failed to get a GoodCRC */
pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED);
}
if (interrupta & TCPC_REG_INTERRUPTA_HARDSENT) {
/* hard reset has been sent */
/* bring FUSB302 out of reset */
fusb302_pd_reset(port);
pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS);
}
if (interrupta & TCPC_REG_INTERRUPTA_HARDRESET) {
/* hard reset has been received */
/* bring FUSB302 out of reset */
fusb302_pd_reset(port);
pd_execute_hard_reset(port);
//task_wake(PD_PORT_TO_TASK_ID(port));
}
if (interruptb & TCPC_REG_INTERRUPTB_GCRCSENT) {
/* Packet received and GoodCRC sent */
/* (this interrupt fires after the GoodCRC finishes) */
if (state[port].rx_enable) {
//task_set_event(PD_PORT_TO_TASK_ID(port),
// PD_EVENT_RX, 0);
} else {
/* flush rx fifo if rx isn't enabled */
fusb302_flush_rx_fifo(port);
}
}
}
/* For BIST receiving */
void tcpm_set_bist_test_data(int port)
{
int reg;
/* Read control3 register */
tcpc_read(port, TCPC_REG_CONTROL3, &reg);
/* Set the BIST_TMODE bit (Clears on Hard Reset) */
reg |= TCPC_REG_CONTROL3_BIST_TMODE;
/* Write the updated value */
tcpc_write(port, TCPC_REG_CONTROL3, reg);
}
const struct tcpm_drv fusb302_tcpm_drv = {
.init = &fusb302_tcpm_init,
.release = &fusb302_tcpm_release,
.get_cc = &fusb302_tcpm_get_cc,
#ifdef CONFIG_USB_PD_VBUS_DETECT_TCPC
.get_vbus_level = &fusb302_tcpm_get_vbus_level,
#endif
.select_rp_value = &fusb302_tcpm_select_rp_value,
.set_cc = &fusb302_tcpm_set_cc,
.set_polarity = &fusb302_tcpm_set_polarity,
.set_vconn = &fusb302_tcpm_set_vconn,
.set_msg_header = &fusb302_tcpm_set_msg_header,
.set_rx_enable = &fusb302_tcpm_set_rx_enable,
.get_message = &fusb302_tcpm_get_message,
.transmit = &fusb302_tcpm_transmit,
.tcpc_alert = &fusb302_tcpc_alert,
};

258
fw/fusb302.h Normal file
View file

@ -0,0 +1,258 @@
/*
fusb302.h - Library for interacting with the FUSB302B chip.
Copyright 2010 The Chromium OS Authors
Copyright 2017 Jason Cerundolo
Copyright 2022 Wenting Zhang
Released under an MIT license. See LICENSE file.
*/
#ifndef FUSB302_H
#define FUSB302_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "usb_pd_tcpm.h"
#include "usb_pd.h"
/* Chip Device ID - 302A or 302B */
#define FUSB302_DEVID_302A 0x08
#define FUSB302_DEVID_302B 0x09
/* I2C slave address varies by part number */
/* FUSB302BUCX / FUSB302BMPX */
#define FUSB302_I2C_SLAVE_ADDR 0x22 // 7-bit address for Arduino
/* FUSB302B01MPX */
#define FUSB302_I2C_SLAVE_ADDR_B01 0x23
/* FUSB302B10MPX */
#define FUSB302_I2C_SLAVE_ADDR_B10 0x24
/* FUSB302B11MPX */
#define FUSB302_I2C_SLAVE_ADDR_B11 0x25
/* Default retry count for transmitting */
#define PD_RETRY_COUNT 3
/* Time to wait for TCPC to complete transmit */
#define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US)
#define TCPC_REG_DEVICE_ID 0x01
#define TCPC_REG_SWITCHES0 0x02
#define TCPC_REG_SWITCHES0_CC2_PU_EN (1<<7)
#define TCPC_REG_SWITCHES0_CC1_PU_EN (1<<6)
#define TCPC_REG_SWITCHES0_VCONN_CC2 (1<<5)
#define TCPC_REG_SWITCHES0_VCONN_CC1 (1<<4)
#define TCPC_REG_SWITCHES0_MEAS_CC2 (1<<3)
#define TCPC_REG_SWITCHES0_MEAS_CC1 (1<<2)
#define TCPC_REG_SWITCHES0_CC2_PD_EN (1<<1)
#define TCPC_REG_SWITCHES0_CC1_PD_EN (1<<0)
#define TCPC_REG_SWITCHES1 0x03
#define TCPC_REG_SWITCHES1_POWERROLE (1<<7)
#define TCPC_REG_SWITCHES1_SPECREV1 (1<<6)
#define TCPC_REG_SWITCHES1_SPECREV0 (1<<5)
#define TCPC_REG_SWITCHES1_DATAROLE (1<<4)
#define TCPC_REG_SWITCHES1_AUTO_GCRC (1<<2)
#define TCPC_REG_SWITCHES1_TXCC2_EN (1<<1)
#define TCPC_REG_SWITCHES1_TXCC1_EN (1<<0)
#define TCPC_REG_MEASURE 0x04
#define TCPC_REG_MEASURE_VBUS (1<<6)
#define TCPC_REG_MEASURE_MDAC_MV(mv) (((mv)/42) & 0x3f)
#define TCPC_REG_CONTROL0 0x06
#define TCPC_REG_CONTROL0_TX_FLUSH (1<<6)
#define TCPC_REG_CONTROL0_INT_MASK (1<<5)
#define TCPC_REG_CONTROL0_HOST_CUR_MASK (3<<2)
#define TCPC_REG_CONTROL0_HOST_CUR_3A0 (3<<2)
#define TCPC_REG_CONTROL0_HOST_CUR_1A5 (2<<2)
#define TCPC_REG_CONTROL0_HOST_CUR_USB (1<<2)
#define TCPC_REG_CONTROL0_TX_START (1<<0)
#define TCPC_REG_CONTROL1 0x07
#define TCPC_REG_CONTROL1_ENSOP2DB (1<<6)
#define TCPC_REG_CONTROL1_ENSOP1DB (1<<5)
#define TCPC_REG_CONTROL1_BIST_MODE2 (1<<4)
#define TCPC_REG_CONTROL1_RX_FLUSH (1<<2)
#define TCPC_REG_CONTROL1_ENSOP2 (1<<1)
#define TCPC_REG_CONTROL1_ENSOP1 (1<<0)
#define TCPC_REG_CONTROL2 0x08
/* two-bit field, valid values below */
#define TCPC_REG_CONTROL2_MODE (1<<1)
#define TCPC_REG_CONTROL2_MODE_DFP (0x3)
#define TCPC_REG_CONTROL2_MODE_UFP (0x2)
#define TCPC_REG_CONTROL2_MODE_DRP (0x1)
#define TCPC_REG_CONTROL2_MODE_POS (1)
#define TCPC_REG_CONTROL2_TOGGLE (1<<0)
#define TCPC_REG_CONTROL3 0x09
#define TCPC_REG_CONTROL3_SEND_HARDRESET (1<<6)
#define TCPC_REG_CONTROL3_BIST_TMODE (1<<5) /* 302B Only */
#define TCPC_REG_CONTROL3_AUTO_HARDRESET (1<<4)
#define TCPC_REG_CONTROL3_AUTO_SOFTRESET (1<<3)
/* two-bit field */
#define TCPC_REG_CONTROL3_N_RETRIES (1<<1)
#define TCPC_REG_CONTROL3_N_RETRIES_POS (1)
#define TCPC_REG_CONTROL3_N_RETRIES_SIZE (2)
#define TCPC_REG_CONTROL3_AUTO_RETRY (1<<0)
#define TCPC_REG_MASK 0x0A
#define TCPC_REG_MASK_VBUSOK (1<<7)
#define TCPC_REG_MASK_ACTIVITY (1<<6)
#define TCPC_REG_MASK_COMP_CHNG (1<<5)
#define TCPC_REG_MASK_CRC_CHK (1<<4)
#define TCPC_REG_MASK_ALERT (1<<3)
#define TCPC_REG_MASK_WAKE (1<<2)
#define TCPC_REG_MASK_COLLISION (1<<1)
#define TCPC_REG_MASK_BC_LVL (1<<0)
#define TCPC_REG_POWER 0x0B
#define TCPC_REG_POWER_PWR (1<<0) /* four-bit field */
#define TCPC_REG_POWER_PWR_LOW 0x1 /* Bandgap + Wake circuitry */
#define TCPC_REG_POWER_PWR_MEDIUM 0x3 /* LOW + Receiver + Current refs */
#define TCPC_REG_POWER_PWR_HIGH 0x7 /* MEDIUM + Measure block */
#define TCPC_REG_POWER_PWR_ALL 0xF /* HIGH + Internal Oscillator */
#define TCPC_REG_RESET 0x0C
#define TCPC_REG_RESET_PD_RESET (1<<1)
#define TCPC_REG_RESET_SW_RESET (1<<0)
#define TCPC_REG_MASKA 0x0E
#define TCPC_REG_MASKA_OCP_TEMP (1<<7)
#define TCPC_REG_MASKA_TOGDONE (1<<6)
#define TCPC_REG_MASKA_SOFTFAIL (1<<5)
#define TCPC_REG_MASKA_RETRYFAIL (1<<4)
#define TCPC_REG_MASKA_HARDSENT (1<<3)
#define TCPC_REG_MASKA_TX_SUCCESS (1<<2)
#define TCPC_REG_MASKA_SOFTRESET (1<<1)
#define TCPC_REG_MASKA_HARDRESET (1<<0)
#define TCPC_REG_MASKB 0x0F
#define TCPC_REG_MASKB_GCRCSENT (1<<0)
#define TCPC_REG_STATUS0A 0x3C
#define TCPC_REG_STATUS0A_SOFTFAIL (1<<5)
#define TCPC_REG_STATUS0A_RETRYFAIL (1<<4)
#define TCPC_REG_STATUS0A_POWER (1<<2) /* two-bit field */
#define TCPC_REG_STATUS0A_RX_SOFT_RESET (1<<1)
#define TCPC_REG_STATUS0A_RX_HARD_RESET (1<<0)
#define TCPC_REG_STATUS1A 0x3D
/* three-bit field, valid values below */
#define TCPC_REG_STATUS1A_TOGSS (1<<3)
#define TCPC_REG_STATUS1A_TOGSS_RUNNING 0x0
#define TCPC_REG_STATUS1A_TOGSS_SRC1 0x1
#define TCPC_REG_STATUS1A_TOGSS_SRC2 0x2
#define TCPC_REG_STATUS1A_TOGSS_SNK1 0x5
#define TCPC_REG_STATUS1A_TOGSS_SNK2 0x6
#define TCPC_REG_STATUS1A_TOGSS_AA 0x7
#define TCPC_REG_STATUS1A_TOGSS_POS (3)
#define TCPC_REG_STATUS1A_TOGSS_MASK (0x7)
#define TCPC_REG_STATUS1A_RXSOP2DB (1<<2)
#define TCPC_REG_STATUS1A_RXSOP1DB (1<<1)
#define TCPC_REG_STATUS1A_RXSOP (1<<0)
#define TCPC_REG_INTERRUPTA 0x3E
#define TCPC_REG_INTERRUPTA_OCP_TEMP (1<<7)
#define TCPC_REG_INTERRUPTA_TOGDONE (1<<6)
#define TCPC_REG_INTERRUPTA_SOFTFAIL (1<<5)
#define TCPC_REG_INTERRUPTA_RETRYFAIL (1<<4)
#define TCPC_REG_INTERRUPTA_HARDSENT (1<<3)
#define TCPC_REG_INTERRUPTA_TX_SUCCESS (1<<2)
#define TCPC_REG_INTERRUPTA_SOFTRESET (1<<1)
#define TCPC_REG_INTERRUPTA_HARDRESET (1<<0)
#define TCPC_REG_INTERRUPTB 0x3F
#define TCPC_REG_INTERRUPTB_GCRCSENT (1<<0)
#define TCPC_REG_STATUS0 0x40
#define TCPC_REG_STATUS0_VBUSOK (1<<7)
#define TCPC_REG_STATUS0_ACTIVITY (1<<6)
#define TCPC_REG_STATUS0_COMP (1<<5)
#define TCPC_REG_STATUS0_CRC_CHK (1<<4)
#define TCPC_REG_STATUS0_ALERT (1<<3)
#define TCPC_REG_STATUS0_WAKE (1<<2)
#define TCPC_REG_STATUS0_BC_LVL1 (1<<1) /* two-bit field */
#define TCPC_REG_STATUS0_BC_LVL0 (1<<0) /* two-bit field */
#define TCPC_REG_STATUS1 0x41
#define TCPC_REG_STATUS1_RXSOP2 (1<<7)
#define TCPC_REG_STATUS1_RXSOP1 (1<<6)
#define TCPC_REG_STATUS1_RX_EMPTY (1<<5)
#define TCPC_REG_STATUS1_RX_FULL (1<<4)
#define TCPC_REG_STATUS1_TX_EMPTY (1<<3)
#define TCPC_REG_STATUS1_TX_FULL (1<<2)
#define TCPC_REG_INTERRUPT 0x42
#define TCPC_REG_INTERRUPT_VBUSOK (1<<7)
#define TCPC_REG_INTERRUPT_ACTIVITY (1<<6)
#define TCPC_REG_INTERRUPT_COMP_CHNG (1<<5)
#define TCPC_REG_INTERRUPT_CRC_CHK (1<<4)
#define TCPC_REG_INTERRUPT_ALERT (1<<3)
#define TCPC_REG_INTERRUPT_WAKE (1<<2)
#define TCPC_REG_INTERRUPT_COLLISION (1<<1)
#define TCPC_REG_INTERRUPT_BC_LVL (1<<0)
#define TCPC_REG_FIFOS 0x43
/* Tokens defined for the FUSB302 TX FIFO */
enum fusb302_txfifo_tokens {
FUSB302_TKN_TXON = 0xA1,
FUSB302_TKN_SYNC1 = 0x12,
FUSB302_TKN_SYNC2 = 0x13,
FUSB302_TKN_SYNC3 = 0x1B,
FUSB302_TKN_RST1 = 0x15,
FUSB302_TKN_RST2 = 0x16,
FUSB302_TKN_PACKSYM = 0x80,
FUSB302_TKN_JAMCRC = 0xFF,
FUSB302_TKN_EOP = 0x14,
FUSB302_TKN_TXOFF = 0xFE,
};
extern const struct tcpm_drv fusb302_tcpm_drv;
/*
// Common methods for TCPM implementations
int fusb302_init(void);
int fusb302_get_cc(int *cc1, int *cc2);
int fusb302_get_vbus_level(void);
int fusb302_select_rp_value(int rp);
int fusb302_set_cc(int pull);
int fusb302_set_polarity(int polarity);
int fusb302_set_vconn(int enable);
int fusb302_set_msg_header(int power_role, int data_role);
int fusb302_set_rx_enable(int enable);
int fusb302_get_message(uint32_t *payload, int *head);
int fusb302_transmit(enum tcpm_transmit_type type,
uint16_t header, const uint32_t *data);
//int alert(void);
void fusb302_pd_reset(int port);
void fusb302_auto_goodcrc_enable(int enable);
int fusb302_convert_bc_lvl(int bc_lvl);
void fusb302_detect_cc_pin_source_manual(int *cc1_lvl, int *cc2_lvl);
int fusb302_measure_cc_pin_source(int cc_measure);
void fusb302_detect_cc_pin_sink(int *cc1, int *cc2);
int fusb302_send_message(uint16_t header, const uint32_t *data,
uint8_t *buf, int buf_pos);
void fusb302_flush_rx_fifo(int port);
void fusb302_flush_tx_fifo(int port);
void fusb302_clear_int_pin(void);
void fusb302_set_bist_test_data(void);
int fusb302_get_chip_id(int *id);
uint32_t fusb302_get_interrupt_reason(void);
int fusb302_tcpc_write(int reg, int val);
int fusb302_tcpc_read(int reg, int *val);
int fusb302_tcpc_xfer(const uint8_t *out,
int out_size, uint8_t *in,
int in_size, int flags);
*/
#ifdef __cplusplus
}
#endif
#endif /* FUSB302_H */

64
fw/fw.c Normal file
View file

@ -0,0 +1,64 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/i2c.h"
#include "utils.h"
#include "tcpm_driver.h"
#include "usb_pd.h"
#include "ptn3460.h"
int main()
{
stdio_init_all();
sleep_ms(1000);
int result = tcpm_init(0);
if (result)
fatal("Failed to initialize TCPC\n");
int cc1, cc2;
tcpc_config[0].drv->get_cc(0, &cc1, &cc2);
printf("CC status %d %d\n", cc1, cc2);
ptn3460_init();
pd_init(0);
sleep_ms(50);
extern int dp_enabled;
bool hpd_sent = false;
while (1) {
// TODO: Implement interrupt
fusb302_tcpc_alert(0);
pd_run_state_machine(0);
if (dp_enabled && !hpd_sent && !pd_is_vdm_busy(0)) {
printf("DP enabled\n");
pd_send_hpd(0, hpd_high);
hpd_sent = true;
}
}
return 0;
}

62
fw/pico_sdk_import.cmake Normal file
View file

@ -0,0 +1,62 @@
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
# This can be dropped into an external project to help locate this SDK
# It should be include()ed prior to project()
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
endif ()
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
endif ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
if (PICO_SDK_FETCH_FROM_GIT_PATH)
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
endif ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
if (NOT pico_sdk)
message("Downloading Raspberry Pi Pico SDK")
FetchContent_Populate(pico_sdk)
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
endif ()
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
else ()
message(FATAL_ERROR
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
)
endif ()
endif ()
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
if (NOT EXISTS ${PICO_SDK_PATH})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
endif ()
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
endif ()
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
include(${PICO_SDK_INIT_CMAKE_FILE})

90
fw/ptn3460.c Normal file
View file

@ -0,0 +1,90 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <stdint.h>
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "ptn3460.h"
#include "utils.h"
//#include "edid.h"
#define PTN3460_I2C_ADDRESS (0x60)
#define PTN3460_I2C (i2c0)
#define PTN3460_HPD_PIN (8)
#define PTN3460_PDN_PIN (9)
void ptn3460_select_edid_emulation(uint8_t id) {
uint8_t buf[2];
int result;
buf[0] = (uint8_t)0x84;
buf[1] = (uint8_t)0x01 | (id << 1);
result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS,
buf, 2, false);
if (result != 2) {
fatal("Failed writing data to PTN3460\n");
}
}
void ptn3460_load_edid(uint8_t *edid) {
uint8_t buf[1];
int result;
buf[0] = 0;
result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS,
buf, 1, true);
result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS,
edid, 128, false);
if (result != 128) {
fatal("Failed writing data to PTN3460\n");
}
}
void ptn3460_init() {
gpio_init(PTN3460_HPD_PIN);
gpio_set_dir(PTN3460_HPD_PIN, GPIO_IN);
gpio_pull_down(PTN3460_HPD_PIN);
gpio_init(PTN3460_PDN_PIN);
gpio_put(PTN3460_PDN_PIN, 1);
gpio_set_dir(PTN3460_PDN_PIN, GPIO_OUT);
sleep_ms(100);
// wait for HPD to become high
int ticks = 0;
while (gpio_get(PTN3460_HPD_PIN) != true) {
ticks ++;
if (ticks > 500) {
fatal("PTN3460 boot timeout\n");
}
sleep_ms(1);
}
printf("PTN3460 up after %d ms\n", ticks);
// Enable EDID emulation
ptn3460_select_edid_emulation(0);
//ptn3460_load_edid();
uint8_t buf[2];
int result;
buf[0] = (uint8_t)0x80;
buf[1] = (uint8_t)0x02;
result = i2c_write_blocking(PTN3460_I2C, PTN3460_I2C_ADDRESS,
buf, 2, false);
if (result != 2) {
fatal("Failed writing data to PTN3460\n");
}
}

24
fw/ptn3460.h Normal file
View file

@ -0,0 +1,24 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#pragma once
void ptn3460_init();

277
fw/tcpm.h Normal file
View file

@ -0,0 +1,277 @@
/* Copyright 2015 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* USB Power delivery port management - common header for TCPM drivers */
#ifndef __CROS_EC_USB_PD_TCPM_TCPM_H
#define __CROS_EC_USB_PD_TCPM_TCPM_H
#ifdef __cplusplus
extern "C" {
#endif
#include "tcpm_driver.h"
#include "usb_pd_tcpm.h"
#if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \
!defined(CONFIG_USB_PD_DUAL_ROLE)
#error "DRP auto toggle requires board to have DRP support"
#error "Please upgrade your board configuration"
#endif
#ifndef CONFIG_USB_PD_TCPC
extern const struct tcpc_config_t tcpc_config[];
/* I2C wrapper functions - get I2C port / slave addr from config struct. */
void tcpc_i2c_init(void);
int tcpc_write(int port, int reg, int val);
int tcpc_write16(int port, int reg, int val);
int tcpc_read(int port, int reg, int *val);
int tcpc_read16(int port, int reg, int *val);
int tcpc_xfer(int port,
const uint8_t *out, int out_size,
uint8_t *in, int in_size,
int flags);
/* TCPM driver wrapper function */
static inline int tcpm_init(int port)
{
int rv;
rv = tcpc_config[port].drv->init(port);
if (rv)
return rv;
/* Board specific post TCPC init */
if (board_tcpc_post_init)
rv = board_tcpc_post_init(port);
return rv;
}
static inline int tcpm_release(int port)
{
return tcpc_config[port].drv->release(port);
}
static inline int tcpm_get_cc(int port, int *cc1, int *cc2)
{
return tcpc_config[port].drv->get_cc(port, cc1, cc2);
}
static inline int tcpm_get_vbus_level(int port)
{
return tcpc_config[port].drv->get_vbus_level(port);
}
static inline int tcpm_select_rp_value(int port, int rp)
{
return tcpc_config[port].drv->select_rp_value(port, rp);
}
static inline int tcpm_set_cc(int port, int pull)
{
return tcpc_config[port].drv->set_cc(port, pull);
}
static inline int tcpm_set_polarity(int port, int polarity)
{
return tcpc_config[port].drv->set_polarity(port, polarity);
}
static inline int tcpm_set_vconn(int port, int enable)
{
return tcpc_config[port].drv->set_vconn(port, enable);
}
static inline int tcpm_set_msg_header(int port, int power_role, int data_role)
{
return tcpc_config[port].drv->set_msg_header(port, power_role,
data_role);
}
static inline int tcpm_set_rx_enable(int port, int enable)
{
return tcpc_config[port].drv->set_rx_enable(port, enable);
}
static inline int tcpm_get_message(int port, uint32_t *payload, int *head)
{
return tcpc_config[port].drv->get_message(port, payload, head);
}
static inline int tcpm_transmit(int port, enum tcpm_transmit_type type,
uint16_t header, const uint32_t *data)
{
return tcpc_config[port].drv->transmit(port, type, header, data);
}
static inline void tcpc_alert(int port)
{
tcpc_config[port].drv->tcpc_alert(port);
}
static inline void tcpc_discharge_vbus(int port, int enable)
{
tcpc_config[port].drv->tcpc_discharge_vbus(port, enable);
}
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
static inline int tcpm_auto_toggle_supported(int port)
{
return !!tcpc_config[port].drv->drp_toggle;
}
static inline int tcpm_set_drp_toggle(int port, int enable)
{
return tcpc_config[port].drv->drp_toggle(port, enable);
}
#endif
#ifdef CONFIG_CMD_I2C_STRESS_TEST_TCPC
static inline int tcpc_i2c_read(const int port, const int addr,
const int reg, int *data)
{
return tcpc_read(port, reg, data);
}
static inline int tcpc_i2c_write(const int port, const int addr,
const int reg, int data)
{
return tcpc_write(port, reg, data);
}
#endif
#else
/**
* Initialize TCPM driver and wait for TCPC readiness.
*
* @param port Type-C port number
*
* @return EC_SUCCESS or error
*/
int tcpm_init(int port);
/**
* Read the CC line status.
*
* @param port Type-C port number
* @param cc1 pointer to CC status for CC1
* @param cc2 pointer to CC status for CC2
*
* @return EC_SUCCESS or error
*/
int tcpm_get_cc(int port, int *cc1, int *cc2);
/**
* Read VBUS
*
* @param port Type-C port number
*
* @return 0 => VBUS not detected, 1 => VBUS detected
*/
int tcpm_get_vbus_level(int port);
/**
* Set the value of the CC pull-up used when we are a source.
*
* @param port Type-C port number
* @param rp One of enum tcpc_rp_value
*
* @return EC_SUCCESS or error
*/
int tcpm_select_rp_value(int port, int rp);
/**
* Set the CC pull resistor. This sets our role as either source or sink.
*
* @param port Type-C port number
* @param pull One of enum tcpc_cc_pull
*
* @return EC_SUCCESS or error
*/
int tcpm_set_cc(int port, int pull);
/**
* Set polarity
*
* @param port Type-C port number
* @param polarity 0=> transmit on CC1, 1=> transmit on CC2
*
* @return EC_SUCCESS or error
*/
int tcpm_set_polarity(int port, int polarity);
/**
* Set Vconn.
*
* @param port Type-C port number
* @param polarity Polarity of the CC line to read
*
* @return EC_SUCCESS or error
*/
int tcpm_set_vconn(int port, int enable);
/**
* Set PD message header to use for goodCRC
*
* @param port Type-C port number
* @param power_role Power role to use in header
* @param data_role Data role to use in header
*
* @return EC_SUCCESS or error
*/
int tcpm_set_msg_header(int port, int power_role, int data_role);
/**
* Set RX enable flag
*
* @param port Type-C port number
* @enable true for enable, false for disable
*
* @return EC_SUCCESS or error
*/
int tcpm_set_rx_enable(int port, int enable);
/**
* Read last received PD message.
*
* @param port Type-C port number
* @param payload Pointer to location to copy payload of message
* @param header of message
*
* @return EC_SUCCESS or error
*/
int tcpm_get_message(int port, uint32_t *payload, int *head);
/**
* Transmit PD message
*
* @param port Type-C port number
* @param type Transmit type
* @param header Packet header
* @param cnt Number of bytes in payload
* @param data Payload
*
* @return EC_SUCCESS or error
*/
int tcpm_transmit(int port, enum tcpm_transmit_type type, uint16_t header,
const uint32_t *data);
/**
* TCPC is asserting alert
*
* @param port Type-C port number
*/
void tcpc_alert(int port);
#endif
#ifdef __cplusplus
}
#endif
#endif

129
fw/tcpm_driver.c Normal file
View file

@ -0,0 +1,129 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
// Copyright 2017 Jason Cerundolo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "tcpm_driver.h"
#include "pico/stdlib.h"
#include "hardware/i2c.h"
#include "utils.h"
const struct tcpc_config_t tcpc_config[CONFIG_USB_PD_PORT_COUNT] = {
{0, FUSB302_I2C_SLAVE_ADDR, &fusb302_tcpm_drv},
};
#define TCPC_I2C i2c0
#define TCPC_I2C_SDA 0
#define TCPC_I2C_SCL 1
void tcpc_i2c_init(void) {
i2c_init(TCPC_I2C, 100*1000);
gpio_set_function(TCPC_I2C_SDA, GPIO_FUNC_I2C);
gpio_set_function(TCPC_I2C_SCL, GPIO_FUNC_I2C);
gpio_pull_up(TCPC_I2C_SDA);
gpio_pull_up(TCPC_I2C_SCL);
}
/* I2C wrapper functions - get I2C port / slave addr from config struct. */
int tcpc_write(int port, int reg, int val) {
uint8_t buf[2];
int result;
buf[0] = (uint8_t)reg;
buf[1] = (uint8_t)val;
result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 2, false);
if (result != 2) {
fatal("Failed writing data to TCPC");
}
return 0;
}
int tcpc_write16(int port, int reg, int val) {
uint8_t buf[3];
int result;
buf[0] = (uint8_t)reg;
buf[1] = (uint8_t)(val & 0xff);
buf[2] = (uint8_t)((val >> 8) & 0xff);
result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 3, false);
if (result != 3) {
fatal("Failed writing data to TCPC");
}
return 0;
}
int tcpc_read(int port, int reg, int *val) {
int result;
uint8_t buf[1];
buf[0] = reg;
result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, true);
if (result != 1) {
fatal("Failed writing data to TCPC");
}
result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, false);
if (result != 1) {
fatal("Failed reading data from TCPC");
}
*val = (int)buf[0];
return 0;
}
int tcpc_read16(int port, int reg, int *val) {
uint8_t buf[2];
int result;
buf[0] = reg;
result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 1, true);
if (result != 1) {
fatal("Failed writing data to TCPC");
}
result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, buf, 2, false);
if (result != 2) {
fatal("Failed reading data from TCPC");
}
*val = (int)buf[1] << 8 | buf[0];
return 0;
}
int tcpc_xfer(int port,
const uint8_t *out, int out_size,
uint8_t *in, int in_size,
int flags) {
int result;
if (out_size) {
result = i2c_write_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, out,
out_size, !!(flags & I2C_XFER_STOP));
if (result != out_size) {
fatal("Failed writing data to TCPC");
}
}
if (in_size) {
result = i2c_read_blocking(TCPC_I2C, FUSB302_I2C_SLAVE_ADDR, in,
in_size, !!(flags & I2C_XFER_STOP));
if (result != in_size) {
fatal("Failed reading data from TCPC");
}
}
return 0;
}

41
fw/tcpm_driver.h Normal file
View file

@ -0,0 +1,41 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
// Copyright 2017 Jason Cerundolo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#ifndef TCPM_DRIVER_H_
#define TCPM_DRIVER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
// USB-C Stuff
#include "tcpm.h"
#include "fusb302.h"
#define CONFIG_USB_PD_PORT_COUNT 1
#ifdef __cplusplus
}
#endif
#endif /* TCPM_DRIVER_H_ */

1807
fw/usb_pd.h Normal file

File diff suppressed because it is too large Load diff

261
fw/usb_pd_driver.c Normal file
View file

@ -0,0 +1,261 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
// Copyright 2017 Jason Cerundolo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include "pico/stdlib.h"
#include "usb_pd_driver.h"
#include "usb_pd.h"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(t) (sizeof(t) / sizeof(t[0]))
#endif
extern struct tc_module tc_instance;
extern uint32_t g_us_timestamp_upper_32bit;
uint32_t pd_task_set_event(uint32_t event, int wait_for_reply)
{
switch (event)
{
case PD_EVENT_TX:
break;
default:
break;
}
return 0;
}
const uint32_t pd_src_pdo[] = {
PDO_FIXED(5000, 1500, PDO_FIXED_FLAGS),
};
const int pd_src_pdo_cnt = ARRAY_SIZE(pd_src_pdo);
const uint32_t pd_snk_pdo[] = {
PDO_FIXED(5000, 500, PDO_FIXED_FLAGS),
};
const int pd_snk_pdo_cnt = ARRAY_SIZE(pd_snk_pdo);
void pd_set_input_current_limit(int port, uint32_t max_ma,
uint32_t supply_voltage)
{
}
int pd_is_valid_input_voltage(int mv)
{
return 1;
}
int pd_snk_is_vbus_provided(int port)
{
return 1;
}
timestamp_t get_time(void)
{
absolute_time_t t = get_absolute_time();
#ifdef NDEBUG
return (timestamp_t)t;
#else
return (timestamp_t)t._private_us_since_boot;
#endif
}
void pd_power_supply_reset(int port)
{
return;
}
int pd_custom_vdm(int port, int cnt, uint32_t *payload,
uint32_t **rpayload)
{
#if 0
int cmd = PD_VDO_CMD(payload[0]);
uint16_t dev_id = 0;
int is_rw, is_latest;
/* make sure we have some payload */
if (cnt == 0)
return 0;
switch (cmd) {
case VDO_CMD_VERSION:
/* guarantee last byte of payload is null character */
*(payload + cnt - 1) = 0;
//CPRINTF("version: %s\n", (char *)(payload+1));
break;
case VDO_CMD_READ_INFO:
case VDO_CMD_SEND_INFO:
/* copy hash */
if (cnt == 7) {
dev_id = VDO_INFO_HW_DEV_ID(payload[6]);
is_rw = VDO_INFO_IS_RW(payload[6]);
is_latest = pd_dev_store_rw_hash(port,
dev_id,
payload + 1,
is_rw ?
SYSTEM_IMAGE_RW :
SYSTEM_IMAGE_RO);
/*
* Send update host event unless our RW hash is
* already known to be the latest update RW.
*/
if (!is_rw || !is_latest)
pd_send_host_event(PD_EVENT_UPDATE_DEVICE);
//CPRINTF("DevId:%d.%d SW:%d RW:%d\n",
// HW_DEV_ID_MAJ(dev_id),
// HW_DEV_ID_MIN(dev_id),
// VDO_INFO_SW_DBG_VER(payload[6]),
// is_rw);
} else if (cnt == 6) {
/* really old devices don't have last byte */
pd_dev_store_rw_hash(port, dev_id, payload + 1,
SYSTEM_IMAGE_UNKNOWN);
}
break;
case VDO_CMD_CURRENT:
CPRINTF("Current: %dmA\n", payload[1]);
break;
case VDO_CMD_FLIP:
/* TODO: usb_mux_flip(port); */
break;
#ifdef CONFIG_USB_PD_LOGGING
case VDO_CMD_GET_LOG:
pd_log_recv_vdm(port, cnt, payload);
break;
#endif /* CONFIG_USB_PD_LOGGING */
}
#endif // if 0
return 0;
}
void pd_execute_data_swap(int port, int data_role)
{
/* Do nothing */
}
int pd_check_data_swap(int port, int data_role)
{
// Never allow data swap
return 0;
}
int pd_check_power_swap(int port)
{
/* Always refuse power swap */
return 0;
}
int pd_board_checks(void)
{
return EC_SUCCESS;
}
int pd_set_power_supply_ready(int port)
{
#if 0
/* Disable charging */
gpio_set_level(GPIO_USB_C0_CHARGE_L, 1);
/* Enable VBUS source */
gpio_set_level(GPIO_USB_C0_5V_EN, 1);
/* notify host of power info change */
pd_send_host_event(PD_EVENT_POWER_CHANGE);
#endif // if 0
return EC_SUCCESS; /* we are ready */
}
void pd_transition_voltage(int idx)
{
/* No-operation: we are always 5V */
#if 0
timestamp_t deadline;
uint32_t mv = src_pdo_charge[idx - 1].mv;
/* Is this a transition to a new voltage? */
if (charge_port_is_active() && vbus[CHG].mv != mv) {
/*
* Alter voltage limit on charge port, this should cause
* the port to select the desired PDO.
*/
pd_set_external_voltage_limit(CHG, mv);
/* Wait for CHG transition */
deadline.val = get_time().val + PD_T_PS_TRANSITION;
CPRINTS("Waiting for CHG port transition");
while (charge_port_is_active() &&
vbus[CHG].mv != mv &&
get_time().val < deadline.val)
msleep(10);
if (vbus[CHG].mv != mv) {
CPRINTS("Missed CHG transition, resetting DUT");
pd_power_supply_reset(DUT);
return;
}
CPRINTS("CHG transitioned");
}
vbus[DUT].mv = vbus[CHG].mv;
vbus[DUT].ma = vbus[CHG].ma;
#endif // if 0
}
void pd_check_dr_role(int port, int dr_role, int flags)
{
#if 0
/* If UFP, try to switch to DFP */
if ((flags & PD_FLAGS_PARTNER_DR_DATA) && dr_role == PD_ROLE_UFP)
pd_request_data_swap(port);
#endif
}
void pd_check_pr_role(int port, int pr_role, int flags)
{
#if 0
/*
* If partner is dual-role power and dualrole toggling is on, consider
* if a power swap is necessary.
*/
if ((flags & PD_FLAGS_PARTNER_DR_POWER) &&
pd_get_dual_role() == PD_DRP_TOGGLE_ON) {
/*
* If we are a sink and partner is not externally powered, then
* swap to become a source. If we are source and partner is
* externally powered, swap to become a sink.
*/
int partner_extpower = flags & PD_FLAGS_PARTNER_EXTPOWER;
if ((!partner_extpower && pr_role == PD_ROLE_SINK) ||
(partner_extpower && pr_role == PD_ROLE_SOURCE))
pd_request_power_swap(port);
}
#endif // if 0
}

119
fw/usb_pd_driver.h Normal file
View file

@ -0,0 +1,119 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
// Copyright 2017 Jason Cerundolo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#ifndef USB_PD_DRIVER_H_
#define USB_PD_DRIVER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "usb_pd.h"
#include <stdint.h>
//#define CONFIG_BBRAM
//#define CONFIG_CHARGE_MANAGER
//#define CONFIG_USBC_BACKWARDS_COMPATIBLE_DFP
//#define CONFIG_USBC_VCONN_SWAP
#define CONFIG_USB_PD_ALT_MODE
//#define CONFIG_USB_PD_CHROMEOS
#define CONFIG_USB_PD_DUAL_ROLE
//#define CONFIG_USB_PD_GIVE_BACK
//#define CONFIG_USB_PD_SIMPLE_DFP
//#define CONFIG_USB_PD_TCPM_TCPCI
/* USB configuration */
#define CONFIG_USB_PID 0x500c
#define CONFIG_USB_BCD_DEV 0x0001 /* v 0.01 */
#define CONFIG_USB_PD_IDENTITY_HW_VERS 1
#define CONFIG_USB_PD_IDENTITY_SW_VERS 1
/* Default pull-up value on the USB-C ports when they are used as source. */
#define CONFIG_USB_PD_PULLUP TYPEC_RP_USB
/* Override PD_ROLE_DEFAULT in usb_pd.h */
#define PD_ROLE_DEFAULT(port) (PD_ROLE_SINK)
/* Don't automatically change roles */
#undef CONFIG_USB_PD_INITIAL_DRP_STATE
#define CONFIG_USB_PD_INITIAL_DRP_STATE PD_DRP_FREEZE
/* board specific type-C power constants */
/*
* delay to turn on the power supply max is ~16ms.
* delay to turn off the power supply max is about ~180ms.
*/
#define PD_POWER_SUPPLY_TURN_ON_DELAY 10000 /* us */
#define PD_POWER_SUPPLY_TURN_OFF_DELAY 20000 /* us */
/* Define typical operating power and max power */
#define PD_OPERATING_POWER_MW (2250ull)
#define PD_MAX_POWER_MW (15000ull)
#define PD_MAX_CURRENT_MA (3000ull)
#define PD_MAX_VOLTAGE_MV (5000ull)
#define PDO_FIXED_FLAGS (PDO_FIXED_COMM_CAP)
#define usleep(us) (delay_us(us))
#define msleep(ms) (delay_ms(ms))
typedef union {
uint64_t val;
struct {
uint32_t lo;
uint32_t hi;
} le /* little endian words */;
} timestamp_t;
uint32_t pd_task_set_event(uint32_t event, int wait_for_reply);
void pd_power_supply_reset(int port);
// Get the current timestamp from the system timer.
timestamp_t get_time(void);
/* Standard macros / definitions */
#ifndef MAX
#define MAX(a, b) \
({ \
__typeof__(a) temp_a = (a); \
__typeof__(b) temp_b = (b); \
\
temp_a > temp_b ? temp_a : temp_b; \
})
#endif
#ifndef MIN
#define MIN(a, b) \
({ \
__typeof__(a) temp_a = (a); \
__typeof__(b) temp_b = (b); \
\
temp_a < temp_b ? temp_a : temp_b; \
})
#endif
#ifdef __cplusplus
}
#endif
#endif /* USB_PD_DRIVER_H_ */

1163
fw/usb_pd_policy.c Normal file

File diff suppressed because it is too large Load diff

4278
fw/usb_pd_protocol.c Normal file

File diff suppressed because it is too large Load diff

351
fw/usb_pd_tcpm.h Normal file
View file

@ -0,0 +1,351 @@
/* Copyright 2015 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/* USB Power delivery port management */
#ifndef __CROS_EC_USB_PD_TCPM_H
#define __CROS_EC_USB_PD_TCPM_H
#ifdef __cplusplus
extern "C" {
#endif
/* List of common error codes that can be returned */
enum ec_error_list {
/* Success - no error */
EC_SUCCESS = 0,
/* Unknown error */
EC_ERROR_UNKNOWN = 1,
/* Function not implemented yet */
EC_ERROR_UNIMPLEMENTED = 2,
/* Overflow error; too much input provided. */
EC_ERROR_OVERFLOW = 3,
/* Timeout */
EC_ERROR_TIMEOUT = 4,
/* Invalid argument */
EC_ERROR_INVAL = 5,
/* Already in use, or not ready yet */
EC_ERROR_BUSY = 6,
/* Access denied */
EC_ERROR_ACCESS_DENIED = 7,
/* Failed because component does not have power */
EC_ERROR_NOT_POWERED = 8,
/* Failed because component is not calibrated */
EC_ERROR_NOT_CALIBRATED = 9,
/* Failed because CRC error */
EC_ERROR_CRC = 10,
/* Invalid console command param (PARAMn means parameter n is bad) */
EC_ERROR_PARAM1 = 11,
EC_ERROR_PARAM2 = 12,
EC_ERROR_PARAM3 = 13,
EC_ERROR_PARAM4 = 14,
EC_ERROR_PARAM5 = 15,
EC_ERROR_PARAM6 = 16,
EC_ERROR_PARAM7 = 17,
EC_ERROR_PARAM8 = 18,
EC_ERROR_PARAM9 = 19,
/* Wrong number of params */
EC_ERROR_PARAM_COUNT = 20,
/* Interrupt event not handled */
EC_ERROR_NOT_HANDLED = 21,
/* Data has not changed */
EC_ERROR_UNCHANGED = 22,
/* Memory allocation */
EC_ERROR_MEMORY_ALLOCATION = 23,
/* Verified boot errors */
EC_ERROR_VBOOT_SIGNATURE = 0x1000, /* 4096 */
EC_ERROR_VBOOT_SIG_MAGIC = 0x1001,
EC_ERROR_VBOOT_SIG_SIZE = 0x1002,
EC_ERROR_VBOOT_SIG_ALGORITHM = 0x1003,
EC_ERROR_VBOOT_HASH_ALGORITHM = 0x1004,
EC_ERROR_VBOOT_SIG_OFFSET = 0x1005,
EC_ERROR_VBOOT_DATA_SIZE = 0x1006,
/* Verified boot key errors */
EC_ERROR_VBOOT_KEY = 0x1100,
EC_ERROR_VBOOT_KEY_MAGIC = 0x1101,
EC_ERROR_VBOOT_KEY_SIZE = 0x1102,
/* Verified boot data errors */
EC_ERROR_VBOOT_DATA = 0x1200,
EC_ERROR_VBOOT_DATA_VERIFY = 0x1201,
/* Module-internal error codes may use this range. */
EC_ERROR_INTERNAL_FIRST = 0x10000,
EC_ERROR_INTERNAL_LAST = 0x1FFFF
};
/* Flags for i2c_xfer() */
#define I2C_XFER_START (1 << 0) /* Start smbus session from idle state */
#define I2C_XFER_STOP (1 << 1) /* Terminate smbus session with stop bit */
#define I2C_XFER_SINGLE (I2C_XFER_START | I2C_XFER_STOP) /* One transaction */
/* Default retry count for transmitting */
#define PD_RETRY_COUNT 3
/* Time to wait for TCPC to complete transmit */
#define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US)
enum tcpc_cc_voltage_status {
TYPEC_CC_VOLT_OPEN = 0,
TYPEC_CC_VOLT_RA = 1,
TYPEC_CC_VOLT_RD = 2,
TYPEC_CC_VOLT_SNK_DEF = 5,
TYPEC_CC_VOLT_SNK_1_5 = 6,
TYPEC_CC_VOLT_SNK_3_0 = 7,
};
enum tcpc_cc_pull {
TYPEC_CC_RA = 0,
TYPEC_CC_RP = 1,
TYPEC_CC_RD = 2,
TYPEC_CC_OPEN = 3,
};
enum tcpc_rp_value {
TYPEC_RP_USB = 0,
TYPEC_RP_1A5 = 1,
TYPEC_RP_3A0 = 2,
TYPEC_RP_RESERVED = 3,
};
enum tcpm_transmit_type {
TCPC_TX_SOP = 0,
TCPC_TX_SOP_PRIME = 1,
TCPC_TX_SOP_PRIME_PRIME = 2,
TCPC_TX_SOP_DEBUG_PRIME = 3,
TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4,
TCPC_TX_HARD_RESET = 5,
TCPC_TX_CABLE_RESET = 6,
TCPC_TX_BIST_MODE_2 = 7
};
enum tcpc_transmit_complete {
TCPC_TX_COMPLETE_SUCCESS = 0,
TCPC_TX_COMPLETE_DISCARDED = 1,
TCPC_TX_COMPLETE_FAILED = 2,
};
struct tcpm_drv {
/**
* Initialize TCPM driver and wait for TCPC readiness.
*
* @param port Type-C port number
*
* @return EC_SUCCESS or error
*/
int (*init)(int port);
/**
* Release the TCPM hardware and disconnect the driver.
* Only .init() can be called after .release().
*
* @param port Type-C port number
*
* @return EC_SUCCESS or error
*/
int (*release)(int port);
/**
* Read the CC line status.
*
* @param port Type-C port number
* @param cc1 pointer to CC status for CC1
* @param cc2 pointer to CC status for CC2
*
* @return EC_SUCCESS or error
*/
int (*get_cc)(int port, int *cc1, int *cc2);
/**
* Read VBUS
*
* @param port Type-C port number
*
* @return 0 => VBUS not detected, 1 => VBUS detected
*/
int (*get_vbus_level)(int port);
/**
* Set the value of the CC pull-up used when we are a source.
*
* @param port Type-C port number
* @param rp One of enum tcpc_rp_value
*
* @return EC_SUCCESS or error
*/
int (*select_rp_value)(int port, int rp);
/**
* Set the CC pull resistor. This sets our role as either source or sink.
*
* @param port Type-C port number
* @param pull One of enum tcpc_cc_pull
*
* @return EC_SUCCESS or error
*/
int (*set_cc)(int port, int pull);
/**
* Set polarity
*
* @param port Type-C port number
* @param polarity 0=> transmit on CC1, 1=> transmit on CC2
*
* @return EC_SUCCESS or error
*/
int (*set_polarity)(int port, int polarity);
/**
* Set Vconn.
*
* @param port Type-C port number
* @param polarity Polarity of the CC line to read
*
* @return EC_SUCCESS or error
*/
int (*set_vconn)(int port, int enable);
/**
* Set PD message header to use for goodCRC
*
* @param port Type-C port number
* @param power_role Power role to use in header
* @param data_role Data role to use in header
*
* @return EC_SUCCESS or error
*/
int (*set_msg_header)(int port, int power_role, int data_role);
/**
* Set RX enable flag
*
* @param port Type-C port number
* @enable true for enable, false for disable
*
* @return EC_SUCCESS or error
*/
int (*set_rx_enable)(int port, int enable);
/**
* Read last received PD message.
*
* @param port Type-C port number
* @param payload Pointer to location to copy payload of message
* @param header of message
*
* @return EC_SUCCESS or error
*/
int (*get_message)(int port, uint32_t *payload, int *head);
/**
* Transmit PD message
*
* @param port Type-C port number
* @param type Transmit type
* @param header Packet header
* @param cnt Number of bytes in payload
* @param data Payload
*
* @return EC_SUCCESS or error
*/
int (*transmit)(int port, enum tcpm_transmit_type type, uint16_t header,
const uint32_t *data);
/**
* TCPC is asserting alert
*
* @param port Type-C port number
*/
void (*tcpc_alert)(int port);
/**
* Discharge PD VBUS on src/sink disconnect & power role swap
*
* @param port Type-C port number
* @param enable Discharge enable or disable
*/
void (*tcpc_discharge_vbus)(int port, int enable);
#ifdef CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE
/**
* Enable TCPC auto DRP toggling.
*
* @param port Type-C port number
* @param enable 1: Enable 0: Disable
*
* @return EC_SUCCESS or error
*/
int (*drp_toggle)(int port, int enable);
#endif
};
enum tcpc_alert_polarity {
TCPC_ALERT_ACTIVE_LOW,
TCPC_ALERT_ACTIVE_HIGH,
};
struct tcpc_config_t {
int i2c_host_port;
int i2c_slave_addr;
const struct tcpm_drv *drv;
enum tcpc_alert_polarity pol;
};
/**
* Returns the PD_STATUS_TCPC_ALERT_* mask corresponding to the TCPC ports
* that are currently asserting ALERT.
*
* @return PD_STATUS_TCPC_ALERT_* mask.
*/
uint16_t tcpc_get_alert_status(void);
/**
* Optional, set the TCPC power mode.
*
* @param port Type-C port number
* @param mode 0: off/sleep, 1: on/awake
*/
void board_set_tcpc_power_mode(int port, int mode) __attribute__((weak));
/**
* Initialize TCPC.
*
* @param port Type-C port number
*/
void tcpc_init(int port);
/**
* TCPC is asserting alert
*
* @param port Type-C port number
*/
void tcpc_alert_clear(int port);
/**
* Run TCPC task once. This checks for incoming messages, processes
* any outgoing messages, and reads CC lines.
*
* @param port Type-C port number
* @param evt Event type that woke up this task
*/
int tcpc_run(int port, int evt);
/**
* Initialize board specific TCPC functions post TCPC initialization.
*
* @param port Type-C port number
*
* @return EC_SUCCESS or error
*/
int board_tcpc_post_init(int port) __attribute__((weak));
#ifdef __cplusplus
}
#endif
#endif /* __CROS_EC_USB_PD_TCPM_H */

39
fw/utils.c Normal file
View file

@ -0,0 +1,39 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>
#include "utils.h"
void fatal(const char *msg, ...) {
va_list params;
va_start(params, msg);
printf("[FATAL] ");
vprintf(msg, params);
va_end(params);
while(1);
}

24
fw/utils.h Normal file
View file

@ -0,0 +1,24 @@
//
// Copyright 2022 Wenting Zhang <zephray@outlook.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#pragma once
void fatal(const char *msg, ...);