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, &REG_STATUS0, 1);
+    cc = REG_STATUS0 & BC_LVL_MASK;
+    for (uint8_t i = 0; i < 5; i++) {
+        REG_READ(ADDRESS_STATUS0, &REG_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, &REG_STATUS0, 1);
+    if (REG_STATUS0 & VBUSOK) {
+        /* enable internal oscillator */
+        REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE | PWR_INT_OSC;
+        REG_WRITE(ADDRESS_POWER, &REG_POWER, 1);
+        dev->delay_ms(1);
+
+        /* read cc1 */
+        REG_SWITCHES0 = PDWN1 | PDWN2 | MEAS_CC1;
+        REG_SWITCHES1 = SPECREV0;
+        REG_MEASURE = 49;
+        REG_WRITE(ADDRESS_SWITCHES0, &REG_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, &REG_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, &REG_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, &REG_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, &REG_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, &REG_SWITCHES0, 3);
+
+        /* turn off internal oscillator */
+        REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE;
+        REG_WRITE(ADDRESS_POWER, &REG_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, &reg_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, &REG_RESET, 1);
+    
+    /* fetch all R/W registers */
+    REG_READ(ADDRESS_DEVICE_ID, &REG_DEVICE_ID, 15);
+
+    /* configure switchs and comparators */
+    REG_SWITCHES0 = PDWN1 | PDWN2;
+    REG_SWITCHES1 = SPECREV0;
+    REG_MEASURE = 49;
+	REG_WRITE(ADDRESS_SWITCHES0, &REG_SWITCHES0, 3);
+
+    /* configure auto retries */
+    REG_CONTROL3 &= ~N_RETRIES_MASK;
+    REG_CONTROL3 |= N_RETRIES(3) | AUTO_RETRY;
+    REG_WRITE(ADDRESS_CONTROL3, &REG_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, &REG_MASK, 1);
+    
+    /* configure interrupt maska/maskb */
+    REG_MASKA = 0xFF;
+    REG_MASKA &= ~(M_RETRYFAIL | M_HARDSENT | M_TXSENT | M_HARDRST);
+    REG_WRITE(ADDRESS_MASKA, &REG_MASKA, 1);
+    REG_MASKB = 0xFF;
+    REG_MASKB &= ~(M_GCRCSENT);
+    REG_WRITE(ADDRESS_MASKB, &REG_MASKB, 1);
+    
+    /* enable interrupt */
+    REG_CONTROL0 &= ~INT_MASK;
+    REG_WRITE(ADDRESS_CONTROL0, &REG_CONTROL0, 1);
+
+    /* Power on, enable VUSB detection */
+    REG_POWER = PWR_BANDGAP | PWR_RECEIVER | PWR_MEASURE;
+    REG_WRITE(ADDRESS_POWER, &REG_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, &reg, 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, &REG_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, &REG_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, &reg_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, &reg_control, 1);
+    dev->delay_ms(5);
+    reg_control = PD_RESET;
+    REG_WRITE(ADDRESS_RESET, &reg_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(&reg_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