From 77a1995169cdfb679225ec88694d54abe4d266b2 Mon Sep 17 00:00:00 2001 From: Whinis <whinis@whinis.com> Date: Mon, 11 Mar 2024 22:31:03 -0400 Subject: [PATCH] Serial Rewrite with DMA rather than FIFO in progress, GPIO management to prevent SFP issues. --- inc/FUSB302_UFP.h | 76 ++++++ inc/PD_UFP.h | 190 ++++++++++++++ inc/PD_UFP_Protocol.h | 150 +++++++++++ pio/uart_rx.pio | 25 ++ src/FUSB302_UFP.c | 596 +++++++++++++++++++++++++++++++++++++++++ src/PD_UFP.cpp | 557 +++++++++++++++++++++++++++++++++++++++ src/PD_UFP_Protocol.c | 599 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 2193 insertions(+) create mode 100644 inc/FUSB302_UFP.h create mode 100644 inc/PD_UFP.h create mode 100644 inc/PD_UFP_Protocol.h create mode 100644 pio/uart_rx.pio create mode 100644 src/FUSB302_UFP.c create mode 100644 src/PD_UFP.cpp create mode 100644 src/PD_UFP_Protocol.c diff --git a/inc/FUSB302_UFP.h b/inc/FUSB302_UFP.h new file mode 100644 index 0000000..77beafb --- /dev/null +++ b/inc/FUSB302_UFP.h @@ -0,0 +1,76 @@ + +/** + * FUSB302_UFP.h + * + * Updated on: Jan 4, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD implement with only UFP(device) functionality + * Requires only stdint.h and string.h + * No use of bit-field for better cross-platform compatibility + * + * FUSB302 can support PD3.0 with limitations and workarounds + * - Do not have enough FIFO for unchunked message, use chunked message instead + * - VBUS sense low threshold at 4V, disable vbus_sense if request PPS below 4V + * + */ + +#ifndef FUSB302_UFP_H +#define FUSB302_UFP_H + +#include <stdint.h> + +enum { + FUSB302_SUCCESS = 0, + FUSB302_BUSY = (1 << 0), + FUSB302_ERR_PARAM = (1 << 1), + FUSB302_ERR_DEVICE_ID = (1 << 2), + FUSB302_ERR_READ_DEVICE = (1 << 3), + FUSB302_ERR_WRITE_DEVICE = (1 << 4) +}; +typedef uint8_t FUSB302_ret_t; + +#define FUSB302_EVENT_ATTACHED (1 << 0) +#define FUSB302_EVENT_DETACHED (1 << 1) +#define FUSB302_EVENT_RX_SOP (1 << 2) +#define FUSB302_EVENT_GOOD_CRC_SENT (1 << 3) +typedef uint8_t FUSB302_event_t; + +typedef struct { + /* setup by user */ + uint8_t i2c_address; + FUSB302_ret_t (*i2c_read)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count); + FUSB302_ret_t (*i2c_write)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count); + FUSB302_ret_t (*delay_ms)(uint32_t t); + + /* used by this library */ + const char * err_msg; + uint16_t rx_header; + uint8_t rx_buffer[32]; + uint8_t reg_control[15]; + uint8_t reg_status[7]; + + uint8_t interrupta; + uint8_t interruptb; + uint8_t cc1; + uint8_t cc2; + uint8_t state; + uint8_t vbus_sense; +} FUSB302_dev_t; + +static inline const char * FUSB302_get_last_err_msg(FUSB302_dev_t *dev) { return dev->err_msg; } + +FUSB302_ret_t FUSB302_init (FUSB302_dev_t *dev); +FUSB302_ret_t FUSB302_pd_reset (FUSB302_dev_t *dev); +FUSB302_ret_t FUSB302_pdwn_cc (FUSB302_dev_t *dev, uint8_t enable); +FUSB302_ret_t FUSB302_set_vbus_sense (FUSB302_dev_t *dev, uint8_t enable); +FUSB302_ret_t FUSB302_get_ID (FUSB302_dev_t *dev, uint8_t *version_ID, uint8_t *revision_ID); +FUSB302_ret_t FUSB302_get_cc (FUSB302_dev_t *dev, uint8_t *cc1, uint8_t *cc2); +FUSB302_ret_t FUSB302_get_vbus_level (FUSB302_dev_t *dev, uint8_t *vbus); +FUSB302_ret_t FUSB302_get_message (FUSB302_dev_t *dev, uint16_t *header, uint32_t *data); +FUSB302_ret_t FUSB302_tx_sop (FUSB302_dev_t *dev, uint16_t header, const uint32_t *data); +FUSB302_ret_t FUSB302_tx_hard_reset (FUSB302_dev_t *dev); +FUSB302_ret_t FUSB302_alert (FUSB302_dev_t *dev, FUSB302_event_t *events); + +#endif /* FUSB302_H */ + diff --git a/inc/PD_UFP.h b/inc/PD_UFP.h new file mode 100644 index 0000000..8da8aa5 --- /dev/null +++ b/inc/PD_UFP.h @@ -0,0 +1,190 @@ + +/** + * PD_UFP.h + * + * Updated on: Jan 25, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD Ardunio Library for PD Micro board + * Only support UFP(device) sink only functionality + * Requires FUSB302_UFP.h, PD_UFP_Protocol.h and Standard Arduino Library + * + * Support PD3.0 PPS + * + */ + +#ifndef PD_UFP_H +#define PD_UFP_H + +#include <stdint.h> +#include "hardware/irq.h" +#include "hardware/structs/sio.h" +#include "hardware/gpio.h" +#include "hardware/i2c.h" +#include "config.h" + +extern "C" { + #include "FUSB302_UFP.h" + #include "PD_UFP_Protocol.h" +} + +enum { + PD_UFP_VOLTAGE_LED_OFF = 0, + PD_UFP_VOLTAGE_LED_5V = 1, + PD_UFP_VOLTAGE_LED_9V = 2, + PD_UFP_VOLTAGE_LED_12V = 3, + PD_UFP_VOLTAGE_LED_15V = 4, + PD_UFP_VOLTAGE_LED_20V = 5, + PD_UFP_VOLTAGE_LED_AUTO = 6 +}; +typedef uint8_t PD_UFP_VOLTAGE_LED_t; + +enum { + PD_UFP_CURRENT_LED_OFF = 0, + PD_UFP_CURRENT_LED_LE_1V = 1, + PD_UFP_CURRENT_LED_LE_3V = 2, + PD_UFP_CURRENT_LED_GT_3V = 3, + PD_UFP_CURRENT_LED_AUTO = 4 +}; +typedef uint8_t PD_UFP_CURRENT_LED_t; + +enum { + STATUS_POWER_NA = 0, + STATUS_POWER_TYP, + STATUS_POWER_PPS +}; +typedef uint8_t status_power_t; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PD_UFP_core_c +/////////////////////////////////////////////////////////////////////////////////////////////////// +class PD_UFP_core_c +{ + public: + PD_UFP_core_c(); + // Init + void init(enum PD_power_option_t power_option = PD_POWER_OPTION_MAX_5V); + void init_PPS(uint16_t PPS_voltage, uint8_t PPS_current, enum PD_power_option_t power_option = PD_POWER_OPTION_MAX_5V); + // Task + void run(void); + // Status + bool is_power_ready(void) { return status_power == STATUS_POWER_TYP; } + bool is_PPS_ready(void) { return status_power == STATUS_POWER_PPS; } + bool is_ps_transition(void) { return send_request || wait_ps_rdy; } + // Get + uint16_t get_voltage(void) { return ready_voltage; } // Voltage in 50mV units, 20mV(PPS) + uint16_t get_current(void) { return ready_current; } // Current in 10mA units, 50mA(PPS) + // Set + bool set_PPS(uint16_t PPS_voltage, uint8_t PPS_current); + void set_power_option(enum PD_power_option_t power_option); + // Clock + static void clock_prescale_set(uint8_t prescaler); + + protected: + static FUSB302_ret_t FUSB302_i2c_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count); + static FUSB302_ret_t FUSB302_i2c_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count); + static FUSB302_ret_t FUSB302_delay_ms(uint32_t t); + void handle_protocol_event(PD_protocol_event_t events); + void handle_FUSB302_event(FUSB302_event_t events); + bool timer(void); + void set_default_power(void); + // Device + FUSB302_dev_t FUSB302{}; + PD_protocol_t protocol{}; + // Power ready power + uint16_t ready_voltage; + uint16_t ready_current; + // PPS setup + uint16_t PPS_voltage_next; + uint8_t PPS_current_next; + // Status + virtual void status_power_ready(status_power_t status, uint16_t voltage, uint16_t current); + uint8_t status_initialized; + uint8_t status_src_cap_received; + status_power_t status_power; + // Timer and counter for PD Policy + uint16_t time_polling; + uint16_t time_wait_src_cap; + uint16_t time_wait_ps_rdy; + uint16_t time_PPS_request; + uint8_t get_src_cap_retry_count; + uint8_t wait_src_cap; + uint8_t wait_ps_rdy; + uint8_t send_request; + static uint8_t clock_prescaler; + // Time functions + static void delay_ms(uint16_t ms); + static uint16_t clock_ms(void); + // Status logging + virtual void status_log_event(uint8_t status, uint32_t * obj = 0) {} +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PD_UFP_c, extended from PD_UFP_core_c, Add LED and Load switch functions +/////////////////////////////////////////////////////////////////////////////////////////////////// +class PD_UFP_c : public PD_UFP_core_c +{ + public: + PD_UFP_c(); + // Set Load Switch + void set_output(uint8_t enable); + // Task + void run(void); + + protected: + // Status + virtual void status_power_ready(status_power_t status, uint16_t voltage, uint16_t current); + // Load Switch + uint8_t status_load_sw; +}; + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Optional: PD_UFP_log_c, extended from PD_UFP_c to provide logging function. +// Asynchronous, minimal impact on PD timing. +/////////////////////////////////////////////////////////////////////////////////////////////////// +struct status_log_t { + uint16_t time; + uint16_t msg_header; + uint8_t obj_count; + uint8_t status; +}; + +enum pd_log_level_t { + PD_LOG_LEVEL_INFO, + PD_LOG_LEVEL_VERBOSE +}; + +class PD_UFP_log_c : public PD_UFP_c +{ + public: + PD_UFP_log_c(pd_log_level_t log_level = PD_LOG_LEVEL_INFO); + // Task +// void print_status(Serial_ & serial); +// void print_status(HardwareSerial & serial); + // Get + int status_log_readline(char * buffer, int maxlen); + + protected: + int status_log_readline_msg(char * buffer, int maxlen, status_log_t * log); + int status_log_readline_src_cap(char * buffer, int maxlen); + // Status log functions + uint8_t status_log_obj_add(uint16_t header, const uint32_t * obj); + virtual void status_log_event(uint8_t status, uint32_t * obj); + // status log event queue + status_log_t status_log[16]{}; // array size must be power of 2 and <=256 + uint8_t status_log_read; + uint8_t status_log_write; + // status log object queue + uint32_t status_log_obj[16]{}; // array size must be power of 2 and <=256 + uint8_t status_log_obj_read; + uint8_t status_log_obj_write; + // state variables + pd_log_level_t status_log_level; + uint8_t status_log_counter; + char status_log_time[8]{}; +}; + +#endif + diff --git a/inc/PD_UFP_Protocol.h b/inc/PD_UFP_Protocol.h new file mode 100644 index 0000000..973f23a --- /dev/null +++ b/inc/PD_UFP_Protocol.h @@ -0,0 +1,150 @@ + +/** + * PD_UFP_Protocol.c + * + * Updated on: Aug 25, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD implement with only UFP(device) sink only functionality + * Requires PD PHY to do automatic GoodCRC response on valid SOP messages. + * Requires only stdint.h, stdbool.h and string.h + * No use of bit-field for better cross-platform compatibility + * + * Support PD3.0 PPS + * Do not support extended message. Not necessary for PD trigger and PPS. + * + * Reference: USB_PD_R2_0 V1.3 - 20170112 + * USB_PD_R3_0 V2.0 20190829 + ECNs 2020-12-10 + * - Chapter 6. Protocol Layer + * + */ + +#ifndef PD_UFP_PROTOCOL_H +#define PD_UFP_PROTOCOL_H + +#include <stdbool.h> +#include <stdint.h> + +/* For use in PD_protocol_get_power_info() */ +#define PD_V(v) ((uint16_t)(v * 20 + 0.01)) +#define PD_A(a) ((uint16_t)(a * 100 + 0.01)) + +/* For use in PD_protocol_set_PPS_option() */ +#define PPS_V(v) ((uint16_t)(v * 50 + 0.01)) +#define PPS_A(a) ((uint8_t)(a * 20 + 0.01)) + +#define PD_PROTOCOL_MAX_NUM_OF_PDO 7 + +#define PD_PROTOCOL_EVENT_SRC_CAP (1 << 0) +#define PD_PROTOCOL_EVENT_PS_RDY (1 << 1) +#define PD_PROTOCOL_EVENT_ACCEPT (1 << 2) +#define PD_PROTOCOL_EVENT_REJECT (1 << 3) +#define PD_PROTOCOL_EVENT_PPS_STATUS (1 << 4) + +typedef uint8_t PD_protocol_event_t; + +enum PD_power_option_t { + PD_POWER_OPTION_MAX_5V = 0, + PD_POWER_OPTION_MAX_9V = 1, + PD_POWER_OPTION_MAX_12V = 2, + PD_POWER_OPTION_MAX_15V = 3, + PD_POWER_OPTION_MAX_20V = 4, + PD_POWER_OPTION_MAX_VOLTAGE = 5, + PD_POWER_OPTION_MAX_CURRENT = 6, + PD_POWER_OPTION_MAX_POWER = 7, +}; + +enum PD_power_data_obj_type_t { /* Power data object type */ + PD_PDO_TYPE_FIXED_SUPPLY = 0, + PD_PDO_TYPE_BATTERY = 1, + PD_PDO_TYPE_VARIABLE_SUPPLY = 2, + PD_PDO_TYPE_AUGMENTED_PDO = 3 /* USB PD 3.0 */ +}; + +enum PPS_PTF_t { + PPS_PTF_NOT_SUPPORT = 0, + PPS_PTF_NORMAL = 1, + PPS_PTF_WARNING = 2, + PPS_PTF_OVER_TEMPERATURE = 3 +}; + +enum PPS_OMF_t { + PPS_OMF_VOLTAGE_MODE = 0, + PPS_OMF_CURRENT_LIMIT_MODE = 1 +}; + +typedef struct { + uint16_t output_voltage; /* Voltage in 20mV units, 0xFFFF if not supported */ + uint8_t output_current; /* Current in 50mV units, 0xFF if not supported */ + enum PPS_PTF_t flag_PTF; + enum PPS_OMF_t flag_OMF; +} PPS_status_t; + +typedef struct { + const char * name; + uint8_t id; + uint8_t spec_rev; + uint8_t num_of_obj; + uint8_t extended; +} PD_msg_info_t; + +typedef struct { + enum PD_power_data_obj_type_t type; + uint16_t min_v; /* Voltage in 50mV units */ + uint16_t max_v; /* Voltage in 50mV units */ + uint16_t max_i; /* Current in 10mA units */ + uint16_t max_p; /* Power in 250mW units */ +} PD_power_info_t; + +struct PD_msg_state_t; +typedef struct { + const struct PD_msg_state_t *msg_state; + uint16_t tx_msg_header; + uint16_t rx_msg_header; + uint8_t message_id; + + uint16_t PPS_voltage; + uint8_t PPS_current; + uint8_t PPSSDB[4]; /* PPS Status Data Block */ + + enum PD_power_option_t power_option; + uint32_t power_data_obj[PD_PROTOCOL_MAX_NUM_OF_PDO]; + uint8_t power_data_obj_count; + uint8_t power_data_obj_selected; +} PD_protocol_t; + +/* Message handler */ +void PD_protocol_handle_msg(PD_protocol_t *p, uint16_t header, uint32_t *obj, PD_protocol_event_t *events); +bool PD_protocol_respond(PD_protocol_t *p, uint16_t *h, uint32_t *obj); + +/* PD Message creation */ +void PD_protocol_create_get_src_cap(PD_protocol_t *p, uint16_t *header); +void PD_protocol_create_get_PPS_status(PD_protocol_t *p, uint16_t *header); +void PD_protocol_create_request(PD_protocol_t *p, uint16_t *header, uint32_t *obj); + +/* Get functions */ +static inline uint8_t PD_protocol_get_selected_power(PD_protocol_t *p) { return p->power_data_obj_selected; } +static inline uint16_t PD_protocol_get_PPS_voltage(PD_protocol_t *p) { return p->PPS_voltage; } /* Voltage in 20mV units */ +static inline uint8_t PD_protocol_get_PPS_current(PD_protocol_t *p) { return p->PPS_current; } /* Current in 50mA units */ + +static inline uint16_t PD_protocol_get_tx_msg_header(PD_protocol_t *p) { return p->tx_msg_header; } +static inline uint16_t PD_protocol_get_rx_msg_header(PD_protocol_t *p) { return p->rx_msg_header; } + +bool PD_protocol_get_msg_info(uint16_t header, PD_msg_info_t * msg_info); + +bool PD_protocol_get_power_info(PD_protocol_t *p, uint8_t index, PD_power_info_t *power_info); +bool PD_protocol_get_PPS_status(PD_protocol_t *p, PPS_status_t * PPS_status); + +/* Set Fixed and Variable power option */ +bool PD_protocol_set_power_option(PD_protocol_t *p, enum PD_power_option_t option); +bool PD_protocol_select_power(PD_protocol_t *p, uint8_t index); + +/* Set PPS Voltage in 20mV units, Current in 50mA units. return true if re-send request is needed + strict=true, If PPS setting is not qualified, return false, nothing is changed. + strict=false, if PPS setting is not qualified, fall back to regular power option */ +bool PD_protocol_set_PPS(PD_protocol_t * p, uint16_t PPS_voltage, uint8_t PPS_current, bool strict); + +void PD_protocol_reset(PD_protocol_t *p); +void PD_protocol_init(PD_protocol_t *p); + +#endif diff --git a/pio/uart_rx.pio b/pio/uart_rx.pio new file mode 100644 index 0000000..03fcf05 --- /dev/null +++ b/pio/uart_rx.pio @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022, Daniel Gorbea + * All rights reserved. + * + * This source code is licensed under the MIT-style license found in the + * LICENSE file in the root directory of this source tree. + * + * Pio program for UART RX protocol + */ + +.define PUBLIC UART_RX_IRQ_NUM 0 // use 0 to 3 + +.program uart_rx // 1 bit every 8 cycles +start: + wait 0 pin 0 + set x 7 [10] +bit_loop: + in pins 1 + jmp x-- bit_loop [6] + jmp pin good_stop + wait 1 pin 0 [2] + jmp start +good_stop: + push + irq UART_RX_IRQ_NUM \ No newline at end of file diff --git a/src/FUSB302_UFP.c b/src/FUSB302_UFP.c new file mode 100644 index 0000000..37ef891 --- /dev/null +++ b/src/FUSB302_UFP.c @@ -0,0 +1,596 @@ + +/** + * FUSB302_UFP.c + * + * Updated on: Jan 4, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD implement with only UFP(device) functionality + * Requires only stdint.h and string.h + * No use of bit-field for better cross-platform compatibility + * + * FUSB302 can support PD3.0 with limitations and workarounds + * - Do not have enough FIFO for unchunked message, use chunked message instead + * - VBUS sense low threshold at 4V, disable vbus_sense if request PPS below 4V + * + */ + +#include <string.h> +#include "FUSB302_UFP.h" + +/* Switches0 : 02h */ +#define PU_EN2 (0x01 << 7) +#define PU_EN1 (0x01 << 6) +#define VCONN_CC2 (0x01 << 5) +#define VCONN_CC1 (0x01 << 4) +#define MEAS_CC2 (0x01 << 3) +#define MEAS_CC1 (0x01 << 2) +#define PDWN2 (0x01 << 1) +#define PDWN1 (0x01 << 0) + +/* Switches1 : 03h */ +#define POWERROLE (0x01 << 7) +#define SPECREV1 (0x01 << 6) +#define SPECREV0 (0x01 << 5) +#define DATAROLE (0x01 << 4) +#define AUTO_CRC (0x01 << 2) +#define TXCC2 (0x01 << 1) +#define TXCC1 (0x01 << 0) + +/* Measure : 04h */ +#define MEAS_VBUS (0x01 << 6) + +/* Control0 : 06h */ +#define TX_FLUSH (0x01 << 6) +#define INT_MASK (0x01 << 5) +#define HOST_CUR_MASK (0x03 << 2) +#define HOST_CUR_3A0 (0x03 << 2) +#define HOST_CUR_1A5 (0x02 << 2) +#define HOST_CUR_USB (0x01 << 2) +#define AUTO_PRE (0x01 << 1) +#define TX_START (0x01 << 0) + +/* Control1 : 07h */ +#define ENSOP2DB (0x01 << 6) +#define ENSOP1DB (0x01 << 5) +#define BIST_MODE2 (0x01 << 4) +#define RX_FLUSH (0x01 << 2) +#define ENSOP2 (0x01 << 1) +#define ENSOP1 (0x01 << 0) + +/* Control2 : 08h */ +#define WAKE_EN (0x01 << 3) +#define MODE_MASK (0x03 << 1) +#define MODE_DFP (0x03 << 1) +#define MODE_UFP (0x02 << 1) +#define MODE_DRP (0x01 << 1) +#define TOGGLE (0x01 << 0) + +/* Control3 : 09h */ +#define SEND_HARDRESET (0x01 << 6) +#define BIST_TMODE (0x01 << 5) /* on FUSB302B only */ +#define AUTO_HARDRESET (0x01 << 4) +#define AUTO_SOFTRESET (0x01 << 3) +#define N_RETRIES_MASK (0x03 << 1) +#define N_RETRIES(n) ((n) << 1) +#define AUTO_RETRY (0x01 << 0) + +/* Mask : 0Ah */ +#define M_VBUSOK (0x01 << 7) +#define M_ACTIVITY (0x01 << 6) +#define M_COMP_CHNG (0x01 << 5) +#define M_CRC_CHK (0x01 << 4) +#define M_ALERT (0x01 << 3) +#define M_WAKE (0x01 << 2) +#define M_COLLISION (0x01 << 1) +#define M_BC_LVL (0x01 << 0) + +/* Power : 0Bh */ +#define PWR_INT_OSC (0x01 << 3) /* Enable internal oscillator */ +#define PWR_MEASURE (0x01 << 2) /* Measure block powered */ +#define PWR_RECEIVER (0x01 << 1) /* Receiver powered and current reference for Measure block */ +#define PWR_BANDGAP (0x01 << 0) /* Bandgap and wake circuitry */ + +/* Reset : 0Ch */ +#define PD_RESET (0x01 << 1) +#define SW_RES (0x01 << 0) + +/* Maska : 0Eh */ +#define M_OCP_TEMP (0x01 << 7) +#define M_TOGDONE (0x01 << 6) +#define M_SOFTFAIL (0x01 << 5) +#define M_RETRYFAIL (0x01 << 4) +#define M_HARDSENT (0x01 << 3) +#define M_TXSENT (0x01 << 2) +#define M_SOFTRST (0x01 << 1) +#define M_HARDRST (0x01 << 0) + +/* Maskb : 0Fh */ +#define M_GCRCSENT (0x01 << 0) + +/* Status0a : 3Ch */ +#define SOFTFAIL (0x01 << 5) +#define RETRYFAIL (0x01 << 4) +#define POWER3_2 (0x01 << 2) +#define SOFTRST (0x01 << 1) +#define HARDRST (0x01 << 0) + +/* Status1a : 3Dh */ +#define TOGSS_MASK (0x07 << 3) +#define TOGSS_RUNNING (0x00 << 3) +#define TOGSS_SRC1 (0x01 << 3) +#define TOGSS_SRC2 (0x02 << 3) +#define TOGSS_SNK1 (0x05 << 3) +#define TOGSS_SNK2 (0x06 << 3) +#define TOGSS_AUDIOA (0x07 << 3) +#define RXSOP2DB (0x01 << 2) +#define RXSOP1DB (0x01 << 1) +#define RXSOP (0x01 << 0) + +/* Interrupta : 3Eh */ +#define I_OCP_TEMP (0x01 << 7) +#define I_TOGDONE (0x01 << 6) +#define I_SOFTFAIL (0x01 << 5) +#define I_RETRYFAIL (0x01 << 4) +#define I_HARDSENT (0x01 << 3) +#define I_TXSENT (0x01 << 2) +#define I_SOFTRST (0x01 << 1) +#define I_HARDRST (0x01 << 0) + +/* Interruptb : 3Fh */ +#define I_GCRCSENT (0x01 << 0) + +/* Status0 : 40h */ +#define VBUSOK (0x01 << 7) +#define ACTIVITY (0x01 << 6) +#define COMP (0x01 << 5) +#define CRC_CHK (0x01 << 4) +#define ALERT (0x01 << 3) +#define WAKE (0x01 << 2) +#define BC_LVL_MASK (0x03 << 0) +#define BC_LVL_LT200 (0x00 << 0) +#define BC_LVL_200_660 (0x01 << 0) +#define BC_LVL_660_1230 (0x02 << 0) +#define BC_LVL_GT1230 (0x03 << 0) + +/* Status1 : 41h */ +#define RXSOP2 (0x01 << 7) +#define RXSOP1 (0x01 << 6) +#define RX_EMPTY (0x01 << 5) +#define RX_FULL (0x01 << 4) +#define TX_EMPTY (0x01 << 3) +#define TX_FULL (0x01 << 2) +#define OVRTEMP (0x01 << 1) +#define OCP (0x01 << 0) + +/* Interrupt : 42h */ +#define I_VBUSOK (0x01 << 7) +#define I_ACTIVITY (0x01 << 6) +#define I_COMP_CHNG (0x01 << 5) +#define I_CRC_CHK (0x01 << 4) +#define I_ALERT (0x01 << 3) +#define I_WAKE (0x01 << 2) +#define I_COLLISION (0x01 << 1) +#define I_BC_LVL (0x01 << 0) + +#define ADDRESS_DEVICE_ID 0x01 +#define ADDRESS_SWITCHES0 0x02 +#define ADDRESS_SWITCHES1 0x03 +#define ADDRESS_MEASURE 0x04 +#define ADDRESS_SLICE 0x05 +#define ADDRESS_CONTROL0 0x06 +#define ADDRESS_CONTROL1 0x07 +#define ADDRESS_CONTROL2 0x08 +#define ADDRESS_CONTROL3 0x09 +#define ADDRESS_MASK 0x0A +#define ADDRESS_POWER 0x0B +#define ADDRESS_RESET 0x0C +#define ADDRESS_MASKA 0x0E +#define ADDRESS_MASKB 0x0F +#define ADDRESS_STATUS0A 0x3C +#define ADDRESS_STATUS1A 0x3D +#define ADDRESS_INTERRUPTA 0x3E +#define ADDRESS_INTERRUPTB 0x3F +#define ADDRESS_STATUS0 0x40 +#define ADDRESS_STATUS1 0x41 +#define ADDRESS_INTERRUPT 0x42 +#define ADDRESS_FIFOS 0x43 + +#define REG_DEVICE_ID dev->reg_control[ADDRESS_DEVICE_ID - ADDRESS_DEVICE_ID] +#define REG_SWITCHES0 dev->reg_control[ADDRESS_SWITCHES0 - ADDRESS_DEVICE_ID] +#define REG_SWITCHES1 dev->reg_control[ADDRESS_SWITCHES1 - ADDRESS_DEVICE_ID] +#define REG_MEASURE dev->reg_control[ADDRESS_MEASURE - ADDRESS_DEVICE_ID] +#define REG_SLICE dev->reg_control[ADDRESS_SLICE - ADDRESS_DEVICE_ID] +#define REG_CONTROL0 dev->reg_control[ADDRESS_CONTROL0 - ADDRESS_DEVICE_ID] +#define REG_CONTROL1 dev->reg_control[ADDRESS_CONTROL1 - ADDRESS_DEVICE_ID] +#define REG_CONTROL2 dev->reg_control[ADDRESS_CONTROL2 - ADDRESS_DEVICE_ID] +#define REG_CONTROL3 dev->reg_control[ADDRESS_CONTROL3 - ADDRESS_DEVICE_ID] +#define REG_MASK dev->reg_control[ADDRESS_MASK - ADDRESS_DEVICE_ID] +#define REG_POWER dev->reg_control[ADDRESS_POWER - ADDRESS_DEVICE_ID] +#define REG_RESET dev->reg_control[ADDRESS_RESET - ADDRESS_DEVICE_ID] +#define REG_MASKA dev->reg_control[ADDRESS_MASKA - ADDRESS_DEVICE_ID] +#define REG_MASKB dev->reg_control[ADDRESS_MASKB - ADDRESS_DEVICE_ID] +#define REG_STATUS0A dev->reg_status[ADDRESS_STATUS0A - ADDRESS_STATUS0A] +#define REG_STATUS1A dev->reg_status[ADDRESS_STATUS1A - ADDRESS_STATUS0A] +#define REG_INTERRUPTA dev->reg_status[ADDRESS_INTERRUPTA - ADDRESS_STATUS0A] +#define REG_INTERRUPTB dev->reg_status[ADDRESS_INTERRUPTB - ADDRESS_STATUS0A] +#define REG_STATUS0 dev->reg_status[ADDRESS_STATUS0 - ADDRESS_STATUS0A] +#define REG_STATUS1 dev->reg_status[ADDRESS_STATUS1 - ADDRESS_STATUS0A] +#define REG_INTERRUPT dev->reg_status[ADDRESS_INTERRUPT - ADDRESS_STATUS0A] + +enum FUSB302_transmit_data_tokens_t { + TX_TOKEN_TXON = 0xA1, + TX_TOKEN_SOP1 = 0x12, + TX_TOKEN_SOP2 = 0x13, + TX_TOKEN_SOP3 = 0x1B, + TX_TOKEN_RESET1 = 0x15, + TX_TOKEN_RESET2 = 0x16, + TX_TOKEN_PACKSYM = 0x80, + TX_TOKEN_JAM_CRC = 0xFF, + TX_TOKEN_EOP = 0x14, + TX_TOKEN_TXOFF = 0xFE, +}; + +enum FUSB302_state_t { + FUSB302_STATE_UNATTACHED = 0, + FUSB302_STATE_ATTACHED +}; + +#define FUSB302_ERR_MSG(s) s + +#define REG_READ(addr, data, count) do { \ + if (reg_read(dev, addr, data, count) != FUSB302_SUCCESS) { return FUSB302_ERR_READ_DEVICE; } \ +} while(0) + +#define REG_WRITE(addr, data, count) do { \ + if (reg_write(dev, addr, data, count) != FUSB302_SUCCESS) { return FUSB302_ERR_WRITE_DEVICE; } \ +} while(0) + +static inline FUSB302_ret_t reg_read(FUSB302_dev_t *dev, uint8_t address, uint8_t *data, uint8_t count) +{ + FUSB302_ret_t ret = dev->i2c_read(dev->i2c_address, address, data, count); + if (ret != FUSB302_SUCCESS) { + dev->err_msg = FUSB302_ERR_MSG("Fail to read register"); + } + return ret; +} + +static inline FUSB302_ret_t reg_write(FUSB302_dev_t *dev, uint8_t address, uint8_t *data, uint8_t count) +{ + FUSB302_ret_t ret = dev->i2c_write(dev->i2c_address, address, data, count); + if (ret != FUSB302_SUCCESS) { + dev->err_msg = FUSB302_ERR_MSG("Fail to write register"); + } + return ret; +} + +static FUSB302_ret_t FUSB302_read_cc_lvl(FUSB302_dev_t *dev, uint8_t * cc_value) +{ + /* 00: < 200 mV : vRa + 01: >200 mV, <660 mV : vRd-USB + 10: >660 mV, <1.23 V : vRd-1.5 + 11: >1.23 V : vRd-3.0 */ + uint8_t cc, cc_verify; + REG_READ(ADDRESS_STATUS0, ®_STATUS0, 1); + cc = REG_STATUS0 & BC_LVL_MASK; + for (uint8_t i = 0; i < 5; i++) { + REG_READ(ADDRESS_STATUS0, ®_STATUS0, 1); + cc_verify = REG_STATUS0 & BC_LVL_MASK; + if (cc != cc_verify) { + return FUSB302_BUSY; + } + } + *cc_value = cc; + return FUSB302_SUCCESS; +} + +static FUSB302_ret_t FUSB302_read_incoming_packet(FUSB302_dev_t *dev, FUSB302_event_t * events) +{ + uint8_t len, b[3]; + REG_READ(ADDRESS_FIFOS, b, 3); + dev->rx_header = ((uint16_t)b[2] << 8) | b[1]; + len = (dev->rx_header >> 12) & 0x7; + REG_READ(ADDRESS_FIFOS, dev->rx_buffer, len * 4 + 4); /* add 4 to len to read CRC out */ + + if (events) { + *events |= FUSB302_EVENT_RX_SOP; + } + return FUSB302_SUCCESS; +} + +static FUSB302_ret_t FUSB302_state_unattached(FUSB302_dev_t *dev, FUSB302_event_t * events) +{ + REG_READ(ADDRESS_STATUS0, ®_STATUS0, 1); + if (REG_STATUS0 & VBUSOK) { + /* enable internal oscillator */ + REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE | PWR_INT_OSC; + REG_WRITE(ADDRESS_POWER, ®_POWER, 1); + dev->delay_ms(1); + + /* read cc1 */ + REG_SWITCHES0 = PDWN1 | PDWN2 | MEAS_CC1; + REG_SWITCHES1 = SPECREV0; + REG_MEASURE = 49; + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 3); + dev->delay_ms(1); + while (FUSB302_read_cc_lvl(dev, &dev->cc1) != FUSB302_SUCCESS) { + dev->delay_ms(1); + } + + /* read cc2 */ + REG_SWITCHES0 = PDWN1 | PDWN2 | MEAS_CC2; + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 1); + dev->delay_ms(1); + while (FUSB302_read_cc_lvl(dev, &dev->cc2) != FUSB302_SUCCESS) { + dev->delay_ms(1); + } + + /* clear interrupt */ + REG_READ(ADDRESS_INTERRUPTA, ®_INTERRUPTA, 2); + dev->interrupta = 0; + dev->interruptb = 0; + + /* enable tx on cc pin */ + if (dev->cc1 > 0) { + REG_SWITCHES0 = PDWN1 | PDWN2 | MEAS_CC1; + REG_SWITCHES1 = SPECREV0 | AUTO_CRC | TXCC1; + //REG_SWITCHES1 = SPECREV0 | TXCC1; + } else if (dev->cc2 > 0) { + REG_SWITCHES0 = PDWN1 | PDWN2 | MEAS_CC2; + REG_SWITCHES1 = SPECREV0 | AUTO_CRC | TXCC2; + //REG_SWITCHES1 = SPECREV0 | TXCC2; + } else { + REG_SWITCHES0 = PDWN1 | PDWN2; + REG_SWITCHES1 = SPECREV0; + } + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 2); + + /* update state */ + dev->state = FUSB302_STATE_ATTACHED; + if (events) { + *events |= FUSB302_EVENT_ATTACHED; + } + } + return FUSB302_SUCCESS; +} + +static FUSB302_ret_t FUSB302_state_attached(FUSB302_dev_t *dev, FUSB302_event_t * events) +{ + REG_READ(ADDRESS_STATUS0A, ®_STATUS0A, 7); + dev->interrupta |= REG_INTERRUPTA; + dev->interruptb |= REG_INTERRUPTB; + if (dev->vbus_sense && ((REG_STATUS0 & VBUSOK) == 0)) { + /* reset cc pins to pull down */ + REG_SWITCHES0 = PDWN1 | PDWN2; + REG_SWITCHES1 = SPECREV0; + REG_MEASURE = 49; + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 3); + + /* turn off internal oscillator */ + REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE; + REG_WRITE(ADDRESS_POWER, ®_POWER, 1); + + /* update state */ + dev->state = FUSB302_STATE_UNATTACHED; + if (events) { + *events |= FUSB302_EVENT_DETACHED; + } + return FUSB302_SUCCESS; + } + if (REG_STATUS0A & HARDRST) { + uint8_t reg_control = PD_RESET; + REG_WRITE(ADDRESS_RESET, ®_control, 1); + return FUSB302_SUCCESS; + } + if (dev->interruptb & I_GCRCSENT) { + dev->interruptb &= ~I_GCRCSENT; + if (events) { + *events |= FUSB302_EVENT_GOOD_CRC_SENT; + } + } + if ((REG_STATUS1 & RX_EMPTY) == 0) { + if (FUSB302_read_incoming_packet(dev, events) != FUSB302_SUCCESS) { + uint8_t rx_flush = REG_CONTROL1 | RX_FLUSH; + reg_write(dev, ADDRESS_CONTROL1, &rx_flush, 1); + } + } + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_init(FUSB302_dev_t *dev) +{ + if (dev->i2c_address == 0) { + dev->err_msg = FUSB302_ERR_MSG("Invalid i2c address"); + return FUSB302_ERR_PARAM; + } + if (dev->i2c_read == 0) { + dev->err_msg = FUSB302_ERR_MSG("Invalid i2c_read function"); + return FUSB302_ERR_PARAM; + } + if (dev->i2c_write == 0) { + dev->err_msg = FUSB302_ERR_MSG("Invalid i2c_write function"); + return FUSB302_ERR_PARAM; + } + + if (reg_read(dev, ADDRESS_DEVICE_ID, &dev->reg_control[1], 1) != FUSB302_SUCCESS) { + dev->err_msg = FUSB302_ERR_MSG("Device not found"); + return FUSB302_ERR_READ_DEVICE; + } + + if ((dev->reg_control[1] & 0x80) == 0) { + dev->err_msg = FUSB302_ERR_MSG("Invalid device version"); + return FUSB302_ERR_DEVICE_ID; + } + + dev->state = FUSB302_STATE_UNATTACHED; + dev->rx_header = 0; + memset(dev->rx_buffer, 0, sizeof(dev->rx_buffer)); + + /* restore default settings */ + REG_RESET = SW_RES; + REG_WRITE(ADDRESS_RESET, ®_RESET, 1); + + /* fetch all R/W registers */ + REG_READ(ADDRESS_DEVICE_ID, ®_DEVICE_ID, 15); + + /* configure switchs and comparators */ + REG_SWITCHES0 = PDWN1 | PDWN2; + REG_SWITCHES1 = SPECREV0; + REG_MEASURE = 49; + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 3); + + /* configure auto retries */ + REG_CONTROL3 &= ~N_RETRIES_MASK; + REG_CONTROL3 |= N_RETRIES(3) | AUTO_RETRY; + REG_WRITE(ADDRESS_CONTROL3, ®_CONTROL3, 1); + + /* configure interrupt mask */ + REG_MASK = 0xFF; + REG_MASK &= ~(M_VBUSOK | M_ACTIVITY | M_COLLISION | M_ALERT | M_CRC_CHK); + REG_WRITE(ADDRESS_MASK, ®_MASK, 1); + + /* configure interrupt maska/maskb */ + REG_MASKA = 0xFF; + REG_MASKA &= ~(M_RETRYFAIL | M_HARDSENT | M_TXSENT | M_HARDRST); + REG_WRITE(ADDRESS_MASKA, ®_MASKA, 1); + REG_MASKB = 0xFF; + REG_MASKB &= ~(M_GCRCSENT); + REG_WRITE(ADDRESS_MASKB, ®_MASKB, 1); + + /* enable interrupt */ + REG_CONTROL0 &= ~INT_MASK; + REG_WRITE(ADDRESS_CONTROL0, ®_CONTROL0, 1); + + /* Power on, enable VUSB detection */ + REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE; + REG_WRITE(ADDRESS_POWER, ®_POWER, 1); + + dev->vbus_sense = 1; + dev->err_msg = FUSB302_ERR_MSG(""); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_pd_reset(FUSB302_dev_t *dev) +{ + uint8_t reg = PD_RESET; + REG_WRITE(ADDRESS_RESET, ®, 1); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_pdwn_cc(FUSB302_dev_t *dev, uint8_t enable) +{ + REG_SWITCHES0 = enable ? (PDWN1 | PDWN2) : 0; + REG_WRITE(ADDRESS_SWITCHES0, ®_SWITCHES0, 1); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_set_vbus_sense(FUSB302_dev_t *dev, uint8_t enable) +{ + if (dev->vbus_sense != enable) { + if (enable) { + REG_MASK &= ~M_VBUSOK; /* enable VBUSOK interrupt */ + } else { + REG_MASK |= M_VBUSOK; /* disable VBUSOK interrupt */ + } + REG_WRITE(ADDRESS_MASK, ®_MASK, 1); + dev->vbus_sense = enable; + } + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_get_ID(FUSB302_dev_t *dev, uint8_t * version_ID, uint8_t * revision_ID) +{ + if (dev && (REG_DEVICE_ID & 0x80)) { + if (version_ID) { + *version_ID = (REG_DEVICE_ID >> 4) & 0x7; + } + if (revision_ID) { + *revision_ID = (REG_DEVICE_ID >> 0) & 0xF; + } + return FUSB302_SUCCESS; + } + return FUSB302_ERR_PARAM; +} + +FUSB302_ret_t FUSB302_get_cc(FUSB302_dev_t *dev, uint8_t *cc1, uint8_t *cc2) +{ + if (cc1) { + *cc1 = dev->cc1; + } + if (cc2) { + *cc2 = dev->cc2; + } + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_get_vbus_level(FUSB302_dev_t *dev, uint8_t *vbus) +{ + uint8_t reg_control; + REG_READ(ADDRESS_STATUS0, ®_control, 1); + *vbus = reg_control & VBUSOK ? 1 : 0; + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_get_message(FUSB302_dev_t *dev, uint16_t * header, uint32_t * data) +{ + if (header) { + *header = dev->rx_header; + } + if (data) { + uint8_t len = (dev->rx_header >> 12) & 0x7; + memcpy(data, dev->rx_buffer, len * 4); + } + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_tx_sop(FUSB302_dev_t *dev, uint16_t header, const uint32_t *data) +{ + uint8_t buf[40]; + uint8_t * pbuf = buf; + uint8_t obj_count = ((header >> 12) & 7); + *pbuf++ = (uint8_t)TX_TOKEN_SOP1; + *pbuf++ = (uint8_t)TX_TOKEN_SOP1; + *pbuf++ = (uint8_t)TX_TOKEN_SOP1; + *pbuf++ = (uint8_t)TX_TOKEN_SOP2; + *pbuf++ = (uint8_t)TX_TOKEN_PACKSYM | ((obj_count << 2) + 2); + *pbuf++ = header & 0xFF; header >>= 8; + *pbuf++ = header & 0xFF; + for (uint8_t i = 0; i < obj_count; i++) { + uint32_t d = *data++; + *pbuf++ = d & 0xFF; d >>= 8; + *pbuf++ = d & 0xFF; d >>= 8; + *pbuf++ = d & 0xFF; d >>= 8; + *pbuf++ = d & 0xFF; + } + *pbuf++ = (uint8_t)TX_TOKEN_JAM_CRC; + *pbuf++ = (uint8_t)TX_TOKEN_EOP; + *pbuf++ = (uint8_t)TX_TOKEN_TXOFF; + *pbuf++ = (uint8_t)TX_TOKEN_TXON; + REG_WRITE(ADDRESS_FIFOS, buf, pbuf - buf); + dev->delay_ms(1); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_tx_hard_reset(FUSB302_dev_t *dev) +{ + uint8_t reg_control = REG_CONTROL3; + reg_control |= SEND_HARDRESET; + REG_WRITE(ADDRESS_CONTROL3, ®_control, 1); + dev->delay_ms(5); + reg_control = PD_RESET; + REG_WRITE(ADDRESS_RESET, ®_control, 1); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t FUSB302_alert(FUSB302_dev_t *dev, FUSB302_event_t * events) +{ + FUSB302_ret_t (* const handler[]) (FUSB302_dev_t *, FUSB302_event_t *) = { + FUSB302_state_unattached, + FUSB302_state_attached + }; + if (dev->state < sizeof(handler) / sizeof(handler[0])) { + return handler[dev->state](dev, events); + } + dev->state = FUSB302_STATE_UNATTACHED; + return FUSB302_SUCCESS; +} diff --git a/src/PD_UFP.cpp b/src/PD_UFP.cpp new file mode 100644 index 0000000..65ca074 --- /dev/null +++ b/src/PD_UFP.cpp @@ -0,0 +1,557 @@ + +/** + * PD_UFP.h + * + * Updated on: Jan 28, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD Ardunio Library for PD Micro board + * Only support UFP(device) sink only functionality + * Requires FUSB302_UFP.h, PD_UFP_Protocol.h and Standard Arduino Library + * + * Support PD3.0 PPS + * + */ + +#include <cstring> + +#include "PD_UFP.h" +#include "pico/time.h" +#include "common.h" +#include <cstdio> +#include <cstring> +#include "pico/stdio.h" + + +#define t_PD_POLLING 100 +#define t_TypeCSinkWaitCap 350 +#define t_RequestToPSReady 580 // combine t_SenderResponse and t_PSTransition +#define t_PPSRequest 5000 // must less than 10000 (10s) + +#define PIN_OUTPUT_ENABLE 3 +#define PIN_FUSB302_INT 19 + +#define PIN_LED_CURRENT_1 13 +#define PIN_LED_CURRENT_2 12 +#define PIN_LED_VOLTAGE_1 // PE2 not support by ardunio library, manipulate register directly +#define PIN_LED_VOLTAGE_2 22 +#define PIN_LED_VOLTAGE_3 23 +#define PIN_LED_VOLTAGE_4 11 + +enum { + STATUS_LOG_MSG_TX, + STATUS_LOG_MSG_RX, + STATUS_LOG_DEV, + STATUS_LOG_CC, + STATUS_LOG_SRC_CAP, + STATUS_LOG_POWER_READY, + STATUS_LOG_POWER_PPS_STARTUP, + STATUS_LOG_POWER_REJECT, + STATUS_LOG_LOAD_SW_ON, + STATUS_LOG_LOAD_SW_OFF, +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PD_UFP_core_c +/////////////////////////////////////////////////////////////////////////////////////////////////// +PD_UFP_core_c::PD_UFP_core_c(): + ready_voltage(0), + ready_current(0), + PPS_voltage_next(0), + PPS_current_next(0), + status_initialized(0), + status_src_cap_received(0), + status_power(STATUS_POWER_NA), + time_polling(0), + time_wait_src_cap(0), + time_wait_ps_rdy(0), + time_PPS_request(0), + get_src_cap_retry_count(0), + wait_src_cap(0), + wait_ps_rdy(0), + send_request(0) +{ + memset(&FUSB302, 0, sizeof(FUSB302_dev_t)); + memset(&protocol, 0, sizeof(PD_protocol_t)); +} + +void PD_UFP_core_c::init(enum PD_power_option_t power_option) +{ + init_PPS(0, 0, power_option); +} + +void PD_UFP_core_c::init_PPS(uint16_t PPS_voltage, uint8_t PPS_current, enum PD_power_option_t power_option) +{ + // Initialize FUSB302 + gpio_init(PIN_FUSB302_INT); + gpio_set_dir(PIN_FUSB302_INT, GPIO_IN); + gpio_pull_up(PIN_FUSB302_INT); // Set FUSB302 int pin input ant pull up + FUSB302.i2c_address = 0x22; + FUSB302.i2c_read = FUSB302_i2c_read; + FUSB302.i2c_write = FUSB302_i2c_write; + FUSB302.delay_ms = FUSB302_delay_ms; + if (FUSB302_init(&FUSB302) == FUSB302_SUCCESS && FUSB302_get_ID(&FUSB302, nullptr, nullptr) == FUSB302_SUCCESS) { + status_initialized = 1; + } + + // Two stage startup for PPS Voltge < 5V + if (PPS_voltage && PPS_voltage < PPS_V(5.0)) { + PPS_voltage_next = PPS_voltage; + PPS_current_next = PPS_current; + PPS_voltage = PPS_V(5.0); + } + + // Initialize PD protocol engine + PD_protocol_init(&protocol); + PD_protocol_set_power_option(&protocol, power_option); + PD_protocol_set_PPS(&protocol, PPS_voltage, PPS_current, false); + + status_log_event(STATUS_LOG_DEV); +} + +void PD_UFP_core_c::run() +{ + if (timer() || gpio_get(PIN_FUSB302_INT) == 0) { + FUSB302_event_t FUSB302_events = 0; + for (uint8_t i = 0; i < 3 && FUSB302_alert(&FUSB302, &FUSB302_events) != FUSB302_SUCCESS; i++) {} + if (FUSB302_events) { + handle_FUSB302_event(FUSB302_events); + } + } +} + +bool PD_UFP_core_c::set_PPS(uint16_t PPS_voltage, uint8_t PPS_current) +{ + if (status_power == STATUS_POWER_PPS && PD_protocol_set_PPS(&protocol, PPS_voltage, PPS_current, true)) { + send_request = 1; + return true; + } + return false; +} + +void PD_UFP_core_c::set_power_option(enum PD_power_option_t power_option) +{ + if (PD_protocol_set_power_option(&protocol, power_option)) { + send_request = 1; + } +} + +void PD_UFP_core_c::clock_prescale_set(uint8_t prescaler) +{ + if (prescaler) { + clock_prescaler = prescaler; + } +} + +FUSB302_ret_t PD_UFP_core_c::FUSB302_i2c_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count) +{ + uint8_t reg_data[1]; + reg_data[0] = reg_addr; + i2c_write_timeout_us(PD_I2C_INST, dev_addr, reg_data, 1, false, 50000); + count-= i2c_read_timeout_us(PD_I2C_INST, dev_addr, data, count, false, 50000); + return count == 0 ? FUSB302_SUCCESS : FUSB302_ERR_READ_DEVICE; +} + +FUSB302_ret_t PD_UFP_core_c::FUSB302_i2c_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t count) +{ + uint8_t reg_data[count+1]; + reg_data[0] = reg_addr; + memcpy(®_data[1],data,count); + i2c_write_timeout_us(PD_I2C_INST, dev_addr, reg_data, count+1, true, 50000); + return FUSB302_SUCCESS; +} + +FUSB302_ret_t PD_UFP_core_c::FUSB302_delay_ms(uint32_t t) +{ + delay_ms(t); + return FUSB302_SUCCESS; +} + +void PD_UFP_core_c::handle_protocol_event(PD_protocol_event_t events) +{ + if (events & PD_PROTOCOL_EVENT_SRC_CAP) { + wait_src_cap = 0; + get_src_cap_retry_count = 0; + wait_ps_rdy = 1; + time_wait_ps_rdy = clock_ms(); + status_log_event(STATUS_LOG_SRC_CAP); + } + if (events & PD_PROTOCOL_EVENT_REJECT) { + if (wait_ps_rdy) { + wait_ps_rdy = 0; + status_log_event(STATUS_LOG_POWER_REJECT); + } + } + if (events & PD_PROTOCOL_EVENT_PS_RDY) { + PD_power_info_t p; + uint8_t selected_power = PD_protocol_get_selected_power(&protocol); + PD_protocol_get_power_info(&protocol, selected_power, &p); + wait_ps_rdy = 0; + if (p.type == PD_PDO_TYPE_AUGMENTED_PDO) { + // PPS mode + FUSB302_set_vbus_sense(&FUSB302, 0); + if (PPS_voltage_next) { + // Two stage startup for PPS voltage < 5V + PD_protocol_set_PPS(&protocol, PPS_voltage_next, PPS_current_next, false); + PPS_voltage_next = 0; + send_request = 1; + status_log_event(STATUS_LOG_POWER_PPS_STARTUP); + } else { + time_PPS_request = clock_ms(); + status_power_ready(STATUS_POWER_PPS, + PD_protocol_get_PPS_voltage(&protocol), PD_protocol_get_PPS_current(&protocol)); + status_log_event(STATUS_LOG_POWER_READY); + } + } else { + FUSB302_set_vbus_sense(&FUSB302, 1); + status_power_ready(STATUS_POWER_TYP, p.max_v, p.max_i); + status_log_event(STATUS_LOG_POWER_READY); + } + } +} + +void PD_UFP_core_c::handle_FUSB302_event(FUSB302_event_t events) +{ + if (events & FUSB302_EVENT_DETACHED) { + PD_protocol_reset(&protocol); + return; + } + if (events & FUSB302_EVENT_ATTACHED) { + uint8_t cc1 = 0, cc2 = 0, cc = 0; + FUSB302_get_cc(&FUSB302, &cc1, &cc2); + PD_protocol_reset(&protocol); + if (cc1 && cc2 == 0) { + cc = cc1; + } else if (cc2 && cc1 == 0) { + cc = cc2; + } + /* TODO: handle no cc detected error */ + if (cc > 1) { + wait_src_cap = 1; + } else { + set_default_power(); + } + status_log_event(STATUS_LOG_CC); + } + if (events & FUSB302_EVENT_RX_SOP) { + PD_protocol_event_t protocol_event = 0; + uint16_t header; + uint32_t obj[7]; + FUSB302_get_message(&FUSB302, &header, obj); + PD_protocol_handle_msg(&protocol, header, obj, &protocol_event); + status_log_event(STATUS_LOG_MSG_RX, obj); + if (protocol_event) { + handle_protocol_event(protocol_event); + } + } + if (events & FUSB302_EVENT_GOOD_CRC_SENT) { + uint16_t header; + uint32_t obj[7]; + delay_ms(2); /* Delay respond in case there are retry messages */ + if (PD_protocol_respond(&protocol, &header, obj)) { + status_log_event(STATUS_LOG_MSG_TX, obj); + FUSB302_tx_sop(&FUSB302, header, obj); + } + } +} + +bool PD_UFP_core_c::timer() +{ + uint16_t t = clock_ms(); + if (wait_src_cap && t - time_wait_src_cap > t_TypeCSinkWaitCap) { + time_wait_src_cap = t; + if (get_src_cap_retry_count < 3) { + uint16_t header; + get_src_cap_retry_count += 1; + /* Try to request soruce capabilities message (will not cause power cycle VBUS) */ + PD_protocol_create_get_src_cap(&protocol, &header); + status_log_event(STATUS_LOG_MSG_TX); + FUSB302_tx_sop(&FUSB302, header, nullptr); + } else { + get_src_cap_retry_count = 0; + /* Hard reset will cause the source power cycle VBUS. */ + FUSB302_tx_hard_reset(&FUSB302); + PD_protocol_reset(&protocol); + } + } + if (wait_ps_rdy) { + if (t - time_wait_ps_rdy > t_RequestToPSReady) { + wait_ps_rdy = 0; + set_default_power(); + } + } else if (send_request || (status_power == STATUS_POWER_PPS && t - time_PPS_request > t_PPSRequest)) { + wait_ps_rdy = 1; + send_request = 0; + time_PPS_request = t; + uint16_t header; + uint32_t obj[7]; + /* Send request if option updated or regularly in PPS mode to keep power alive */ + PD_protocol_create_request(&protocol, &header, obj); + status_log_event(STATUS_LOG_MSG_TX, obj); + time_wait_ps_rdy = clock_ms(); + FUSB302_tx_sop(&FUSB302, header, obj); + } + if (t - time_polling > t_PD_POLLING) { + time_polling = t; + return true; + } + return false; +} + +void PD_UFP_core_c::set_default_power() +{ + status_power_ready(STATUS_POWER_TYP, PD_V(5), PD_A(1)); + status_log_event(STATUS_LOG_POWER_READY); +} + +void PD_UFP_core_c::status_power_ready(status_power_t status, uint16_t voltage, uint16_t current) +{ + ready_voltage = voltage; + ready_current = current; + status_power = status; +} + +uint8_t PD_UFP_core_c::clock_prescaler = 1; + +void PD_UFP_core_c::delay_ms(uint16_t ms) +{ + sleep_ms(ms); +} + +uint16_t PD_UFP_core_c::clock_ms() +{ + return to_ms_since_boot(get_absolute_time()); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PD_UFP_c, extended from PD_UFP_core_c, Add LED and Load switch functions +/////////////////////////////////////////////////////////////////////////////////////////////////// +PD_UFP_c::PD_UFP_c(): + status_load_sw(0) +{ + gpio_init(PIN_OUTPUT_ENABLE); + gpio_set_dir(PIN_OUTPUT_ENABLE, GPIO_OUT); + gpio_put(PIN_OUTPUT_ENABLE, false); +} + + +void PD_UFP_c::set_output(uint8_t enable) +{ + gpio_put(PIN_OUTPUT_ENABLE, enable); + if (status_load_sw != enable) { + status_load_sw = enable; + status_log_event(enable ? STATUS_LOG_LOAD_SW_ON : STATUS_LOG_LOAD_SW_OFF); + } +} + +void PD_UFP_c::run() +{ + PD_UFP_core_c::run(); +} + +void PD_UFP_c::status_power_ready(status_power_t status, uint16_t voltage, uint16_t current) +{ + PD_UFP_core_c::status_power_ready(status, voltage, current); +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Optional: PD_UFP_log_c, extended from PD_UFP_c to provide logging function. +// Asynchronous, minimal impact on PD timing. +/////////////////////////////////////////////////////////////////////////////////////////////////// +#define STATUS_LOG_MASK (sizeof(status_log) / sizeof(status_log[0]) - 1) +#define STATUS_LOG_OBJ_MASK (sizeof(status_log_obj) / sizeof(status_log_obj[0]) - 1) + +PD_UFP_log_c::PD_UFP_log_c(pd_log_level_t log_level): + status_log_write(0), + status_log_read(0), + status_log_counter(0), + status_log_obj_read(0), + status_log_obj_write(0), + status_log_level(log_level) +{ + +} + +uint8_t PD_UFP_log_c::status_log_obj_add(uint16_t header, const uint32_t * obj) +{ + if (obj) { + uint8_t i, w = status_log_obj_write, r = status_log_obj_read; + PD_msg_info_t info; + PD_protocol_get_msg_info(header, &info); + for (i = 0; i < info.num_of_obj && (uint8_t)(w - r) < STATUS_LOG_OBJ_MASK; i++) { + status_log_obj[w++ & STATUS_LOG_OBJ_MASK] = obj[i]; + } + status_log_obj_write = w; + return i; + } + return 0; +} + +void PD_UFP_log_c::status_log_event(uint8_t status, uint32_t * obj) +{ + if (((status_log_write - status_log_read) & STATUS_LOG_MASK) >= STATUS_LOG_MASK) { + return; + } + status_log_t * log = &status_log[status_log_write & STATUS_LOG_MASK]; + switch (status) { + case STATUS_LOG_MSG_TX: + log->msg_header = PD_protocol_get_tx_msg_header(&protocol); + log->obj_count = status_log_obj_add(log->msg_header, obj); + break; + case STATUS_LOG_MSG_RX: + log->msg_header = PD_protocol_get_rx_msg_header(&protocol); + log->obj_count = status_log_obj_add(log->msg_header, obj); + break; + default: + break; + } + log->status = status; + log->time = clock_ms(); + status_log_write++; +} + +// Optimize RAM usage on AVR MCU by allocate format string in program memory +#if defined(__AVR__) +#include <avr/pgmspace.h> +#define SNPRINTF snprintf_P +#else +#define SNPRINTF snprintf +#define PSTR(str) str +#endif + +#define LOG(format, ...) do { n = SNPRINTF(buffer, maxlen, PSTR(format), ## __VA_ARGS__); } while (0) + +int PD_UFP_log_c::status_log_readline_msg(char * buffer, int maxlen, status_log_t * log) +{ + char * t = status_log_time; + int n; + if (status_log_counter == 0) { + // output message header + char type = log->status == STATUS_LOG_MSG_TX ? 'T' : 'R'; + PD_msg_info_t info; + PD_protocol_get_msg_info(log->msg_header, &info); + if (status_log_level >= PD_LOG_LEVEL_VERBOSE) { + const char * ext = info.extended ? "ext, " : ""; + LOG("%s%cX %s id=%d %sraw=0x%04X\n", t, type, info.name, info.id, ext, log->msg_header); + if (info.num_of_obj) { + status_log_counter++; + } + } else { + LOG("%s%cX %s\n", t, type, info.name); + } + } else { + // output object data + int i = status_log_counter - 1; + uint32_t obj = status_log_obj[status_log_obj_read++ & STATUS_LOG_OBJ_MASK]; + LOG("%s obj%d=0x%08lX\n", t, i, obj); + if (++status_log_counter > log->obj_count) { + status_log_counter = 0; + } + } + return n; +} + +int PD_UFP_log_c::status_log_readline_src_cap(char * buffer, int maxlen) +{ + PD_power_info_t p; + int n = 0; + uint8_t i = status_log_counter; + if (PD_protocol_get_power_info(&protocol, i, &p)) { + const char * str_pps[] = {"", " BAT", " VAR", " PPS"}; /* PD_power_data_obj_type_t */ + char * t = status_log_time; + uint8_t selected = PD_protocol_get_selected_power(&protocol); + char min_v[8] = {0}, max_v[8] = {0}, power[8] = {0}; + if (p.min_v) SNPRINTF(min_v, sizeof(min_v)-1, PSTR("%d.%02dV-"), p.min_v / 20, (p.min_v * 5) % 100); + if (p.max_v) SNPRINTF(max_v, sizeof(max_v)-1, PSTR("%d.%02dV"), p.max_v / 20, (p.max_v * 5) % 100); + if (p.max_i) { + SNPRINTF(power, sizeof(power)-1, PSTR("%d.%02dA"), p.max_i / 100, p.max_i % 100); + } else { + SNPRINTF(power, sizeof(power)-1, PSTR("%d.%02dW"), p.max_p / 4, p.max_p * 25); + } + LOG("%s [%d] %s%s %s%s%s\n", t, i, min_v, max_v, power, str_pps[p.type], i == selected ? " *" : ""); + status_log_counter++; + } else { + status_log_counter = 0; + } + return n; +} + +int PD_UFP_log_c::status_log_readline(char * buffer, int maxlen) +{ + if (status_log_write == status_log_read) { + return 0; + } + + status_log_t * log = &status_log[status_log_read & STATUS_LOG_MASK]; + int n = 0; + char * t = status_log_time; + if (t[0] == 0) { // Convert timestamp number to string + SNPRINTF(t, sizeof(status_log_time)-1, PSTR("%04u: "), log->time); + return 0; + } + + switch (log->status) { + case STATUS_LOG_MSG_TX: + case STATUS_LOG_MSG_RX: + n = status_log_readline_msg(buffer, maxlen, log); + break; + case STATUS_LOG_DEV: + if (status_initialized) { + uint8_t version_ID = 0, revision_ID = 0; + FUSB302_get_ID(&FUSB302, &version_ID, &revision_ID); + LOG("\n%sFUSB302 ver ID:%c_rev%c\n", t, 'A' + version_ID, 'A' + revision_ID); + } else { + LOG("\n%sFUSB302 init error\n", t); + } + break; + case STATUS_LOG_CC: { + const char *detection_type_str[] = {"USB", "1.5", "3.0"}; + uint8_t cc1 = 0, cc2 = 0; + FUSB302_get_cc(&FUSB302, &cc1, &cc2); + if (cc1 == 0 && cc2 == 0) { + LOG("%sUSB attached vRA\n", t); + } else if (cc1 && cc2 == 0) { + LOG("%sUSB attached CC1 vRd-%s\n", t, detection_type_str[cc1 - 1]); + } else if (cc2 && cc1 == 0) { + LOG("%sUSB attached CC2 vRd-%s\n", t, detection_type_str[cc2 - 1]); + } else { + LOG("%sUSB attached unknown\n", t); + } + break; } + case STATUS_LOG_SRC_CAP: + n = status_log_readline_src_cap(buffer, maxlen); + break; + case STATUS_LOG_POWER_READY: { + uint16_t v = ready_voltage; + uint16_t a = ready_current; + if (status_power == STATUS_POWER_TYP) { + LOG("%s%d.%02dV %d.%02dA supply ready\n", t, v / 20, (v * 5) % 100, a / 100, a % 100); + } else if (status_power == STATUS_POWER_PPS) { + LOG("%sPPS %d.%02dV %d.%02dA supply ready\n", t, v / 50, (v * 2) % 100, a / 20, (a * 5) % 100); + } + break; } + case STATUS_LOG_POWER_PPS_STARTUP: + LOG("%sPPS 2-stage startup\n", t); + break; + case STATUS_LOG_POWER_REJECT: + LOG("%sRequest Rejected\n", t); + break; + case STATUS_LOG_LOAD_SW_ON: + LOG("%sLoad SW ON\n", t); + break; + case STATUS_LOG_LOAD_SW_OFF: + LOG("%sLoad SW OFF\n", t); + break; + } + if (status_log_counter == 0) { + t[0] = 0; + status_log_read++; + status_log_counter = 0; + } + return n; +} + diff --git a/src/PD_UFP_Protocol.c b/src/PD_UFP_Protocol.c new file mode 100644 index 0000000..aac1e9e --- /dev/null +++ b/src/PD_UFP_Protocol.c @@ -0,0 +1,599 @@ + +/** + * PD_UFP_Protocol.c + * + * Updated on: Jan 25, 2021 + * Author: Ryan Ma + * + * Minimalist USB PD implement with only UFP(device) sink only functionality + * Requires PD PHY to do automatic GoodCRC response on valid SOP messages. + * Requires only stdint.h, stdbool.h and string.h + * No use of bit-field for better cross-platform compatibility + * + * Support PD3.0 PPS + * Do not support extended message. Not necessary for PD trigger and PPS. + * + * Reference: USB_PD_R2_0 V1.3 - 20170112 + * USB_PD_R3_0 V2.0 20190829 + ECNs 2020-12-10 + * - Chapter 6. Protocol Layer + * + */ + +#include <string.h> +#include "PD_UFP_Protocol.h" + +#define PD_SPECIFICATION_REVISION 0x2 + +#define PD_CONTROL_MSG_TYPE_ACCEPT 0x3 +#define PD_CONTROL_MSG_TYPE_REJECT 0x4 +#define PD_CONTROL_MSG_TYPE_GET_SRC_CAP 0x7 +#define PD_CONTROL_MSG_TYPE_NOT_SUPPORT 0x10 +#define PD_CONTROL_MSG_TYPE_GET_PPS_STATUS 0x14 + +#define PD_DATA_MSG_TYPE_REQUEST 0x2 +#define PD_DATA_MSG_TYPE_SINK_CAP 0x4 +#define PD_DATA_MSG_TYPE_VENDOR_DEFINED 0xF + +#define PD_EXT_MSG_TYPE_SINK_CAP_EXT 0xF + +typedef struct { + uint8_t type; + uint8_t spec_rev; + uint8_t id; + uint8_t num_of_obj; +} PD_msg_header_info_t; + +typedef struct { + uint16_t limit; + uint8_t use_voltage; + uint8_t use_current; +} PD_power_option_setting_t; + +struct PD_msg_state_t { + const char * name; + void (*handler)(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); + bool (*responder)(PD_protocol_t * p, uint16_t * header, uint32_t * obj); +}; + +/* Optimize RAM usage on AVR MCU by allocate const in PROGMEM */ +#if defined(__AVR__) +#include <avr/pgmspace.h> +#define SET_MSG_STAGE(d, s) do { static struct PD_msg_state_t m; memcpy_P(&m, s, sizeof(struct PD_msg_state_t)); d = &m; } while (0) +#define SET_MSG_NAME(d, s) do { static char n[16]; strncpy_P(n, s, 15); d = n; } while (0) +#define COPY_PDO(d, s) do { memcpy_P(&d, &s, 4); } while (0) +#else +#define PROGMEM +#define SET_MSG_STAGE(d, s) do { d = s; } while (0) +#define SET_MSG_NAME(d, s) do { d = s; } while (0) +#define COPY_PDO(d, s) do { d = s; } while (0) +#endif + +#define T(name) static const char str_ ## name [] PROGMEM = #name + +static void handler_good_crc (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_goto_min (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_accept (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_reject (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_ps_rdy (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_source_cap (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_BIST (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_alert (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_vender_def (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); +static void handler_PPS_Status (PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events); + +static bool responder_get_sink_cap (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_reject (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_soft_reset (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_source_cap (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_vender_def (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_sink_cap_ext (PD_protocol_t * p, uint16_t * header, uint32_t * obj); +static bool responder_not_support (PD_protocol_t * p, uint16_t * header, uint32_t * obj); + +T(C0); T(GoodCRC); T(GotoMin); T(Accept); T(Reject); T(Ping); T(PS_RDY); T(Get_Src_Cap); +T(Get_Sink_Cap); T(DR_Swap); T(PR_Swap); T(VCONN_Swap); T(Wait); T(Soft_Rst); T(Dat_Rst); T(Dat_Rst_Cpt); +T(NS); T(Get_Src_Ext); T(Get_Stat); T(FR_Swap); T(Get_PPS_Stat); T(Get_CC); T(Get_Sink_Ext); T(C_R); + +static const struct PD_msg_state_t ctrl_msg_list[] PROGMEM = { + {.name = str_C0, .handler = 0, .responder = 0}, + {.name = str_GoodCRC, .handler = handler_good_crc, .responder = 0}, + {.name = str_GotoMin, .handler = handler_goto_min, .responder = 0}, + {.name = str_Accept, .handler = handler_accept, .responder = 0}, + {.name = str_Reject, .handler = handler_reject, .responder = 0}, + {.name = str_Ping, .handler = 0, .responder = 0}, + {.name = str_PS_RDY, .handler = handler_ps_rdy, .responder = 0}, + {.name = str_Get_Src_Cap, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_Sink_Cap, .handler = 0, .responder = responder_get_sink_cap}, + {.name = str_DR_Swap, .handler = 0, .responder = responder_reject}, + {.name = str_PR_Swap, .handler = 0, .responder = responder_not_support}, + {.name = str_VCONN_Swap, .handler = 0, .responder = responder_reject}, + {.name = str_Wait, .handler = 0, .responder = 0}, + {.name = str_Soft_Rst, .handler = 0, .responder = responder_soft_reset}, + {.name = str_Dat_Rst, .handler = 0, .responder = 0}, + {.name = str_Dat_Rst_Cpt, .handler = 0, .responder = 0}, + + {.name = str_NS, .handler = 0, .responder = 0}, + {.name = str_Get_Src_Ext, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_Stat, .handler = 0, .responder = responder_not_support}, + {.name = str_FR_Swap, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_PPS_Stat, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_CC, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_Sink_Ext, .handler = 0, .responder = responder_sink_cap_ext}, + {.name = str_C_R, .handler = 0, .responder = responder_not_support}, +}; + +T(D0); T(Src_Cap); T(Request); T(BIST); T(Sink_Cap); T(Bat_Stat); T(Alert); T(Get_CI); +T(Enter_USB); T(D9); T(D10); T(D11); T(D12); T(D13); T(D14); T(VDM); +T(D_R); + +static const struct PD_msg_state_t data_msg_list[] PROGMEM = { + {.name = str_D0, .handler = 0, .responder = 0}, + {.name = str_Src_Cap, .handler = handler_source_cap, .responder = responder_source_cap}, + {.name = str_Request, .handler = 0, .responder = responder_not_support}, + {.name = str_BIST, .handler = handler_BIST, .responder = 0}, + {.name = str_Sink_Cap, .handler = 0, .responder = responder_not_support}, + {.name = str_Bat_Stat, .handler = 0, .responder = responder_not_support}, + {.name = str_Alert, .handler = handler_alert, .responder = 0}, + {.name = str_Get_CI, .handler = 0, .responder = responder_not_support}, + {.name = str_Enter_USB, .handler = 0, .responder = 0}, + {.name = str_D9, .handler = 0, .responder = 0}, + {.name = str_D10, .handler = 0, .responder = 0}, + {.name = str_D11, .handler = 0, .responder = 0}, + {.name = str_D12, .handler = 0, .responder = 0}, + {.name = str_D13, .handler = 0, .responder = 0}, + {.name = str_D14, .handler = 0, .responder = 0}, + {.name = str_VDM, .handler = handler_vender_def, .responder = responder_vender_def}, + + {.name = str_D_R, .handler = 0, .responder = responder_not_support}, +}; + +T(E0); T(Src_Cap_Ext); T(Status); T(Get_Bat_cap); T(Get_Bat_Stat); T(Bat_Cap); T(Get_Mfg_Info); T(Mfg_Info); +T(Sec_Request); T(Sec_Response); T(FU_request); T(FU_Response); T(PPS_Stat); T(Country_Info); T(Country_Code); T(Sink_Cap_Ext); +T(E_R); + +static const struct PD_msg_state_t ext_msg_list[] PROGMEM = { + {.name = str_E0, .handler = 0, .responder = responder_not_support}, + {.name = str_Src_Cap_Ext, .handler = 0, .responder = 0}, + {.name = str_Status, .handler = 0, .responder = 0}, + {.name = str_Get_Bat_cap, .handler = 0, .responder = responder_not_support}, + {.name = str_Get_Bat_Stat, .handler = 0, .responder = responder_not_support}, + {.name = str_Bat_Cap, .handler = 0, .responder = 0}, + {.name = str_Get_Mfg_Info, .handler = 0, .responder = responder_not_support}, + {.name = str_Mfg_Info, .handler = 0, .responder = 0}, + {.name = str_Sec_Request, .handler = 0, .responder = responder_not_support}, + {.name = str_Sec_Response, .handler = 0, .responder = 0}, + {.name = str_FU_request, .handler = 0, .responder = responder_not_support}, + {.name = str_FU_Response, .handler = 0, .responder = 0}, + {.name = str_PPS_Stat, .handler = handler_PPS_Status, .responder = 0}, + {.name = str_Country_Info, .handler = 0, .responder = 0}, + {.name = str_Country_Code, .handler = 0, .responder = 0}, + {.name = str_Sink_Cap_Ext, .handler = 0, .responder = responder_not_support}, + + {.name = str_E_R, .handler = 0, .responder = responder_not_support}, +}; + +static const PD_power_option_setting_t power_option_setting[8] = { + {.limit = 25, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_5V */ + {.limit = 45, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_9V */ + {.limit = 60, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_12V */ + {.limit = 75, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_15V */ + {.limit = 100, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_20V */ + {.limit = 100, .use_voltage = 1, .use_current = 0}, /* PD_POWER_OPTION_MAX_VOLTAGE */ + {.limit = 125, .use_voltage = 0, .use_current = 1}, /* PD_POWER_OPTION_MAX_CURRENT */ + {.limit = 12500,.use_voltage = 1, .use_current = 1}, /* PD_POWER_OPTION_MAX_POWER */ +}; + +static uint8_t evaluate_src_cap(PD_protocol_t * p, uint16_t PPS_voltage, uint8_t PPS_current) +{ + const PD_power_option_setting_t * setting; + PD_power_info_t info; + uint8_t option = p->power_option; + uint8_t selected = 0; + + /* If selected option is not available, use first PDO. Reference: 6.4.1 Capabilities Message + The vSafe5V Fixed Supply Object Shall always be the first object. */ + if (option >= sizeof(power_option_setting) / sizeof(power_option_setting[0])) { + return 0; + } + + setting = &power_option_setting[option]; + for (uint8_t n = 0; PD_protocol_get_power_info(p, n, &info); n++) { + if (info.type == PD_PDO_TYPE_AUGMENTED_PDO) { + uint16_t pps_v = PPS_voltage * 2; /* Voltage in 20mV units */ + uint16_t pps_i = PPS_current * 5; /* Current in 50mA units */ + /* PD_power_info_t: Voltage in 50mV units, Current in 10mA units */ + if (info.min_v * 5 <= pps_v && pps_v <= info.max_v * 5 && pps_i <= info.max_i) { + return n; + } + } else { + uint8_t v = setting->use_voltage ? info.max_v >> 2 : 1; + uint8_t i = setting->use_current ? info.max_i >> 2 : 1; + uint16_t power = (uint16_t)v * i; /* reduce 10-bit power info to 8-bit and use 8-bit x 8-bit multiplication */ + if (power <= setting->limit) { + selected = n; + } + } + } + return selected; +} + +static void parse_header(PD_msg_header_info_t * info, uint16_t header) +{ + /* Reference: 6.2.1.1 Message Header */ + info->type = (header >> 0) & 0x1F; /* 4...0 Message Type */ + info->spec_rev = (header >> 6) & 0x3; /* 7...6 Specification Revision */ + info->id = (header >> 9) & 0x7; /* 11...9 MessageID */ + info->num_of_obj = (header >> 12) & 0x7; /* 14...12 Number of Data Objects */ +} + +static uint16_t generate_header(PD_protocol_t * p, uint8_t type, uint8_t obj_count) +{ + /* Reference: 6.2.1.1 Message Header */ + uint16_t h = ((uint16_t)type << 0) | /* 4...0 Message Type */ + ((uint16_t)PD_SPECIFICATION_REVISION << 6) | /* 7...6 Specification Revision */ + ((uint16_t)p->message_id << 9) | /* 11...9 MessageID */ + ((uint16_t)obj_count << 12); /* 14...12 Number of Data Objects */ + p->tx_msg_header = h; + return h; +} + +static uint16_t generate_header_ext(PD_protocol_t * p, uint8_t type, uint8_t data_size, uint32_t * obj) +{ + uint16_t h = generate_header(p, type, (data_size + 5) >> 2); /* set obj_count to fit ext header and data */ + h |= (uint16_t)1 << 15; /* Set extended field */ + /* Reference: 6.2.1.2 Extended Message Headerr */ + obj[0] |= ((uint16_t)data_size << 0) | /* 8...0 ata Size */ + /* Assume short message, set Chunk Number and Request Chunk to 0 */ + ((uint16_t)1 << 15); /* 15 Chunked */ + p->tx_msg_header = h; + return h; +} + +static void handler_good_crc(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + /* Reference: 6.2.1.3 Message ID + MessageIDCounter Shall be initialized to zero at power-on / reset, increment when receive GoodCRC Message */ + uint8_t message_id = p->message_id; + if (++message_id > 7) { + message_id = 0; + } + p->message_id = message_id; +} + +static void handler_goto_min(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + // Not implemented +} + +static void handler_accept(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + if (events) { + *events |= PD_PROTOCOL_EVENT_ACCEPT; + } +} + +static void handler_reject(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + if (events) { + *events |= PD_PROTOCOL_EVENT_PS_RDY; + } +} + +static void handler_ps_rdy(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + if (events) { + *events |= PD_PROTOCOL_EVENT_PS_RDY; + } +} + +static void handler_source_cap(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + PD_msg_header_info_t h; + parse_header(&h, header); + p->power_data_obj_count = h.num_of_obj; + for (uint8_t i = 0; i < h.num_of_obj; i++) { + p->power_data_obj[i] = obj[i]; + } + p->power_data_obj_selected = evaluate_src_cap(p, p->PPS_voltage, p->PPS_current); + if (events) { + *events |= PD_PROTOCOL_EVENT_SRC_CAP; + } +} + +static void handler_BIST(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + // TODO: implement BIST +} + +static void handler_alert(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + // TODO: implement alert +} + +static void handler_vender_def(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + // TODO: implement VDM parsing +} + +static void handler_PPS_Status(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + /* Handle chunked Extended message, Offset 2 byte for Extended Message Header */ + p->PPSSDB[0] = (obj[0] >> 16) & 0xFF; + p->PPSSDB[1] = (obj[0] >> 24) & 0xFF; + p->PPSSDB[2] = (obj[1] >> 0) & 0xFF; + p->PPSSDB[3] = (obj[1] >> 8) & 0xFF; + if (events) { + *events |= PD_PROTOCOL_EVENT_PPS_STATUS; + } +} + +static bool responder_get_sink_cap(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + /* Reference: 6.4.1.2.3 Sink Fixed Supply Power Data Object */ + uint32_t data = ((uint32_t)100 << 0) | /* B9...0 Operational Current in 10mA units */ + ((uint32_t)100 << 10) | /* B19...10 Voltage in 50mV units */ + ((uint32_t)1 << 26) | /* B26 USB Communications Capable */ + ((uint32_t)1 << 28) | /* B28 Higher Capability */ + ((uint32_t)PD_PDO_TYPE_FIXED_SUPPLY << 30); /* B31...30 Fixed supply */ + *obj = data; /* Only implement 5V 1A Fix supply PDO. Source rarely request sink cap */ + *header = generate_header(p, PD_DATA_MSG_TYPE_SINK_CAP, 1); + return true; +} + +static bool responder_sink_cap_ext(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + /* Reference: 6.5.13 Sink_Capabilities_Extended Message + 6.12.3 Applicability of Extended Messages (Normative; Shall be supported) */ + #define SINK_CAP_VID 0 + #define SINK_CAP_PID 0 + #define SINK_CAP_XID 0 /* If the vendor does not have an XID, then it Shall return zero */ + #define SINK_CAP_FW_Version 1 + #define SINK_CAP_HW_Version 1 + #define SINK_CAP_SKEDB_Version 1 + #define SINK_CAP_SINK_MODE 0x3 /* Bit 0: PPS charging supported, Bit 1: VBUS powered */ + #define SINK_CAP_SINK_MIN_PDP 5 /* Minimum PD Power in Watt */ + #define SINK_CAP_SINK_OP_PDP 5 /* Operational PD Power in Watt */ + #define SINK_CAP_SINK_MAX_PDP 100 /* Maximum PD Power in Watt */ + static const uint32_t SKEDB[6] PROGMEM = { /* 2-byte header + 21-byte data, chunked to 6 PDO */ + /* PDO[0], data byte 0...1 */ + /* 16-bit LSB is reserved for Extended Message Header */ + ((uint32_t)SINK_CAP_VID << 16), /* Byte 0...1 VID */ + /* PDO[1], data byte 2...5 */ + ((uint32_t)SINK_CAP_PID << 0) | /* Byte 2...3 PID */ + (((uint32_t)SINK_CAP_XID & 0xFF) << 16), /* Byte 4...5 XID */ + /* PDO[2], data byte 6...9 */ + (((uint32_t)SINK_CAP_XID >> 16) << 0) | /* Byte 6...7 XID */ + ((uint32_t)SINK_CAP_FW_Version << 16) | /* Byte 8 FW Version */ + ((uint32_t)SINK_CAP_HW_Version << 24), /* Byte 9 HW Version */ + /* PDO[3], data byte 10...13 */ + ((uint32_t)SINK_CAP_SKEDB_Version << 0), /* Byte 10 SKEDB Version */ + /* Not set Byte 11 Load Step, Byte 13..12 Sink Load Characteristics */ + /* PDO[4], data byte 14...17 */ + /* Not set Byte 14 Compliance, Byte 15 Touch Temp, Byte 16 Battery Info */ + ((uint32_t)SINK_CAP_SINK_MODE << 24), /* Byte 17 Sink Modes */ + /* PDO[5], data byte 18...20 */ + ((uint32_t)SINK_CAP_SINK_MIN_PDP << 0) | /* Byte 18 Minimum PDP */ + ((uint32_t)SINK_CAP_SINK_OP_PDP << 8) | /* Byte 19 Operational PDP */ + ((uint32_t)SINK_CAP_SINK_MAX_PDP << 16) /* Byte 20 Maximum PDP */ + }; + uint8_t i; + for (i = 0; i < 6; i++) { + COPY_PDO(obj[i], SKEDB[i]); + } + *header = generate_header_ext(p, PD_EXT_MSG_TYPE_SINK_CAP_EXT, 21, obj); + return false; +} + +static bool responder_reject(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + *header = generate_header(p, PD_CONTROL_MSG_TYPE_REJECT, 0); + return true; +} + +static bool responder_not_support(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + *header = generate_header(p, PD_CONTROL_MSG_TYPE_NOT_SUPPORT, 0); + return true; +} + +static bool responder_soft_reset(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + *header = generate_header(p, PD_CONTROL_MSG_TYPE_ACCEPT, 0); + return true; +} + +static bool responder_source_cap(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + PD_power_info_t info; + uint32_t data, pos = p->power_data_obj_selected + 1; + PD_protocol_get_power_info(p, p->power_data_obj_selected, &info); + /* Reference: 6.4.2 Request Message */ + if (info.type == PD_PDO_TYPE_AUGMENTED_PDO) { + /* NOTE: To compatible PD2.0 PHY, do not set Unchunked Extended Messages Supported */ + data = ((uint32_t)p->PPS_current << 0) | /* B6 ...0 Operating Current 50mA units */ + ((uint32_t)p->PPS_voltage << 9) | /* B19...9 Output Voltage in 20mV units */ + ((uint32_t)1 << 25) | /* B25 USB Communication Capable */ + ((uint32_t)pos << 28); /* B30...28 Object position (000b is Reserved and Shall Not be used) */ + } else { + uint32_t req = info.max_i ? info.max_i : info.max_p; + data = ((uint32_t)req << 0) | /* B9 ...0 Max Operating Current 10mA units / Max Operating Power in 250mW units */ + ((uint32_t)req << 10) | /* B19...10 Operating Current 10mA units / Operating Power in 250mW units */ + ((uint32_t)1 << 25) | /* B25 USB Communication Capable */ + ((uint32_t)pos << 28); /* B30...28 Object position (000b is Reserved and Shall Not be used) */ + } + *obj = data; + *header = generate_header(p, PD_DATA_MSG_TYPE_REQUEST, 1); + return true; +} + +static bool responder_vender_def(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + // TODO: implement VDM respond + return false; +} + +void PD_protocol_handle_msg(PD_protocol_t * p, uint16_t header, uint32_t * obj, PD_protocol_event_t * events) +{ + #define EXT_MSG_LIMIT (sizeof(ext_msg_list) / sizeof(ext_msg_list[0]) - 1) + #define DATA_MSG_LIMIT (sizeof(data_msg_list) / sizeof(data_msg_list[0]) - 1) + #define CTRL_MSG_LIMIT (sizeof(ctrl_msg_list) / sizeof(ctrl_msg_list[0]) - 1) + + const struct PD_msg_state_t * state; + PD_msg_header_info_t h; + parse_header(&h, header); + p->rx_msg_header = header; + if ((header >> 15) & 0x1) { + state = &ext_msg_list[h.type > EXT_MSG_LIMIT ? EXT_MSG_LIMIT : h.type]; + } else if (h.num_of_obj) { + state = &data_msg_list[h.type > DATA_MSG_LIMIT ? DATA_MSG_LIMIT : h.type]; + } else { + state =&ctrl_msg_list[h.type > CTRL_MSG_LIMIT ? CTRL_MSG_LIMIT : h.type]; + } + SET_MSG_STAGE(p->msg_state, state); + if (p->msg_state->handler) { + p->msg_state->handler(p, header, obj, events); + } +} + +bool PD_protocol_respond(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + if (p && p->msg_state && p->msg_state->responder && header && obj) { + return p->msg_state->responder(p, (uint16_t *)header, obj); + } + return false; +} + +void PD_protocol_create_get_src_cap(PD_protocol_t * p, uint16_t * header) +{ + *header = generate_header(p, PD_CONTROL_MSG_TYPE_GET_SRC_CAP, 0); +} + +void PD_protocol_create_get_PPS_status(PD_protocol_t *p, uint16_t *header) +{ + *header = generate_header(p, PD_CONTROL_MSG_TYPE_GET_PPS_STATUS, 0); +} + +void PD_protocol_create_request(PD_protocol_t * p, uint16_t * header, uint32_t * obj) +{ + responder_source_cap(p, header, obj); +} + +bool PD_protocol_get_power_info(PD_protocol_t * p, uint8_t index, PD_power_info_t * power_info) +{ + if (p && index < p->power_data_obj_count && power_info) { + uint32_t obj = p->power_data_obj[index]; + power_info->type = obj >> 30; + switch (power_info->type) { + case PD_PDO_TYPE_FIXED_SUPPLY: + /* Reference: 6.4.1.2.3 Source Fixed Supply Power Data Object */ + power_info->min_v = 0; + power_info->max_v = (obj >> 10) & 0x3FF; /* B19...10 Voltage in 50mV units */ + power_info->max_i = (obj >> 0) & 0x3FF; /* B9 ...0 Max Current in 10mA units */ + power_info->max_p = 0; + break; + case PD_PDO_TYPE_BATTERY: + /* Reference: 6.4.1.2.5 Battery Supply Power Data Object */ + power_info->min_v = (obj >> 10) & 0x3FF; /* B19...10 Min Voltage in 50mV units */ + power_info->max_v = (obj >> 20) & 0x3FF; /* B29...20 Max Voltage in 50mV units */ + power_info->max_i = 0; + power_info->max_p = (obj >> 0) & 0x3FF; /* B9 ...0 Max Allowable Power in 250mW units */ + break; + case PD_PDO_TYPE_VARIABLE_SUPPLY: + /* Reference: 6.4.1.2.4 Variable Supply (non-Battery) Power Data Object */ + power_info->min_v = (obj >> 10) & 0x3FF; /* B19...10 Min Voltage in 50mV units */ + power_info->max_v = (obj >> 20) & 0x3FF; /* B29...20 Max Voltage in 50mV units */ + power_info->max_i = (obj >> 0) & 0x3FF; /* B9 ...0 Max Current in 10mA units */ + power_info->max_p = 0; + break; + case PD_PDO_TYPE_AUGMENTED_PDO: + /* Reference: 6.4.1.3.4 Programmable Power Supply Augmented Power Data Object */ + power_info->max_v = ((obj >> 17) & 0xFF) * 2; /* B24...17 Max Voltage in 100mV units */ + power_info->min_v = ((obj >> 8) & 0xFF) * 2; /* B15...8 Min Voltage in 100mV units */ + power_info->max_i = ((obj >> 0) & 0x7F) * 5; /* B6 ...0 Max Current in 50mA units */ + power_info->max_p = 0; + break; + } + return true; + } + return false; +} + +bool PD_protocol_get_msg_info(uint16_t header, PD_msg_info_t * msg_info) +{ + PD_msg_header_info_t h; + parse_header(&h, header); + if (msg_info) { + const char * name; + const struct PD_msg_state_t * state; + uint8_t type = h.type; + SET_MSG_STAGE(state, header & 0x8000 ? &ext_msg_list[type] : + h.num_of_obj ? &data_msg_list[type] : &ctrl_msg_list[type]); + SET_MSG_NAME(name, state->name); + msg_info->name = name; + msg_info->id = h.id; + msg_info->spec_rev = h.spec_rev; + msg_info->num_of_obj = h.num_of_obj; + msg_info->extended = header >> 15; + return true; + } + return false; +} + +bool PD_protocol_get_PPS_status(PD_protocol_t *p, PPS_status_t * PPS_status) +{ + if (p && PPS_status) { + /* Reference: 6.5.10 PPS_Status Message */ + PPS_status->output_voltage = ((uint16_t)p->PPSSDB[1] << 8) | p->PPSSDB[0]; + PPS_status->output_current = p->PPSSDB[2]; + PPS_status->flag_PTF = (p->PPSSDB[3] >> 1) & 0x3; /* Bit 1 ... 2 */ + PPS_status->flag_OMF = (p->PPSSDB[3] >> 3) & 0x1; /* Bit 3 */ + return true; + } + return false; +} + +bool PD_protocol_set_power_option(PD_protocol_t * p, enum PD_power_option_t option) +{ + p->power_option = option; + p->PPS_voltage = 0; + p->PPS_current = 0; + if (p->power_data_obj_count > 0) { + p->power_data_obj_selected = evaluate_src_cap(p, p->PPS_voltage, p->PPS_current); + return true; /* need to re-send request */ + } + return false; +} + +bool PD_protocol_select_power(PD_protocol_t * p, uint8_t index) +{ + if (index < p->power_data_obj_count) { + p->power_data_obj_selected = index; + return true; /* need to re-send request */ + } + return false; +} + +bool PD_protocol_set_PPS(PD_protocol_t * p, uint16_t PPS_voltage, uint8_t PPS_current, bool strict) +{ + if (p->PPS_voltage != PPS_voltage || p->PPS_current != PPS_current) { + uint8_t selected = evaluate_src_cap(p, PPS_voltage, PPS_current); + if (selected || !strict) { + p->PPS_voltage = PPS_voltage; + p->PPS_current = PPS_current; + p->power_data_obj_selected = selected; + return true; /* need to re-send request */ + } + } + return false; +} + +void PD_protocol_reset(PD_protocol_t * p) +{ + p->msg_state = &ctrl_msg_list[0]; + p->message_id = 0; +} + +void PD_protocol_init(PD_protocol_t * p) +{ + memset(p, 0, sizeof(PD_protocol_t)); + p->msg_state = &ctrl_msg_list[0]; +} -- GitLab