├── platform.h ├── tcpm_driver.h ├── tusb_config.h ├── CMakeLists.txt ├── LICENSE ├── tcpm.h ├── m1-pd-bmc.h ├── tcpm_driver.c ├── usb_descriptors.c ├── usb_pd_tcpm.h ├── FUSB302.h ├── README ├── start.c ├── vdmtool.c └── FUSB302.c /platform.h: -------------------------------------------------------------------------------- 1 | #include "m1-pd-bmc.h" 2 | 3 | static void platform_usleep(uint64_t us) 4 | { 5 | sleep_ms(us / 1000); 6 | sleep_us(us % 1000); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tcpm_driver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * tcpm_driver.h 3 | * 4 | * Created: 11/11/2017 18:42:39 5 | * Author: jason 6 | */ 7 | 8 | 9 | #ifndef TCPM_DRIVER_H_ 10 | #define TCPM_DRIVER_H_ 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | #include 17 | 18 | // USB-C Stuff 19 | #include "tcpm.h" 20 | #include "FUSB302.h" 21 | #define CONFIG_USB_PD_PORT_COUNT 2 22 | extern struct i2c_master_module i2c_master_instance; 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif /* TCPM_DRIVER_H_ */ 29 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | #ifndef _TUSB_CONFIG_H_ 2 | #define _TUSB_CONFIG_H_ 3 | 4 | #define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE 5 | 6 | #define CFG_TUD_EP_MAX 5 7 | 8 | #define CFG_TUD_CDC 2 9 | 10 | #define CFG_TUD_CDC_EP_BUFSIZE 512 11 | #define CFG_TUD_CDC_RX_BUFSIZE 512 12 | #define CFG_TUD_CDC_TX_BUFSIZE 512 13 | 14 | #define CFG_TUD_WEB 0 15 | #define CFG_TUD_ECM_RNDIS 0 16 | #define CFG_TUD_MSC 0 17 | #define CFG_TUD_HID 0 18 | #define CFG_TUD_MIDI 0 19 | #define CFG_TUD_AUDIO 0 20 | #define CFG_TUD_BTH 0 21 | #define CFG_TUD_TMC 0 22 | #define CFG_TUD_GUD 0 23 | #define CFG_TUD_VENDOR 0 24 | 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Set minimum required version of CMake 2 | cmake_minimum_required(VERSION 3.12) 3 | 4 | # Include build functions from Pico SDK 5 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 6 | 7 | # Set name of project (as PROJECT_NAME) and C/C++ standards 8 | project(m1_ubmc C CXX ASM) 9 | set(CMAKE_C_STANDARD 11) 10 | set(CMAKE_CXX_STANDARD 17) 11 | 12 | # Creates a pico-sdk subdirectory in our project for the libraries 13 | pico_sdk_init() 14 | 15 | # Tell CMake where to find the executable source file 16 | add_executable(${PROJECT_NAME} 17 | start.c 18 | FUSB302.c 19 | tcpm_driver.c 20 | vdmtool.c 21 | usb_descriptors.c 22 | ) 23 | 24 | target_include_directories(${PROJECT_NAME} PUBLIC 25 | ${CMAKE_CURRENT_LIST_DIR} 26 | ) 27 | 28 | target_compile_options(${PROJECT_NAME} PRIVATE -Wall -funsigned-char) 29 | 30 | # Create map/bin/hex/uf2 files 31 | pico_add_extra_outputs(${PROJECT_NAME}) 32 | 33 | # Link to pico_stdlib (gpio, time, etc. functions) 34 | target_link_libraries(${PROJECT_NAME} 35 | pico_stdlib 36 | hardware_i2c 37 | pico_unique_id 38 | tinyusb_board 39 | tinyusb_device 40 | ) 41 | 42 | #pico_set_binary_type(${PROJECT_NAME} no_flash) 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 The Chromium OS Authors 4 | Copyright (c) 2018 Reclaimer Labs - https://www.reclaimerlabs.com/ 5 | Copyright (c) 2020 The Asahi Linux Contributors 6 | Copyright (c) 2022 Marc Zyngier 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /tcpm.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 The Chromium OS Authors. All rights reserved. 2 | * Use of this source code is governed by a BSD-style license that can be 3 | * found in the LICENSE file. 4 | */ 5 | 6 | /* USB Power delivery port management - common header for TCPM drivers */ 7 | 8 | #ifndef __CROS_EC_USB_PD_TCPM_TCPM_H 9 | #define __CROS_EC_USB_PD_TCPM_TCPM_H 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | #include "tcpm_driver.h" 16 | #include "usb_pd_tcpm.h" 17 | 18 | #if defined(CONFIG_USB_PD_DUAL_ROLE_AUTO_TOGGLE) && \ 19 | !defined(CONFIG_USB_PD_DUAL_ROLE) 20 | #error "DRP auto toggle requires board to have DRP support" 21 | #error "Please upgrade your board configuration" 22 | #endif 23 | 24 | #ifndef CONFIG_USB_PD_TCPC 25 | extern const struct tcpc_config_t tcpc_config[]; 26 | 27 | /* I2C wrapper functions - get I2C port / slave addr from config struct. */ 28 | int16_t tcpc_write(int16_t port, int16_t reg, int16_t val); 29 | int16_t tcpc_write16(int16_t port, int16_t reg, int16_t val); 30 | int16_t tcpc_read(int16_t port, int16_t reg, int16_t *val); 31 | int16_t tcpc_read16(int16_t port, int16_t reg, int16_t *val); 32 | int16_t tcpc_xfer(int16_t port, 33 | const uint8_t *out, int16_t out_size, 34 | uint8_t *in, int16_t in_size, 35 | int16_t flags); 36 | 37 | #endif 38 | 39 | #ifdef __cplusplus 40 | } 41 | #endif 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /m1-pd-bmc.h: -------------------------------------------------------------------------------- 1 | // FUSB302-based serial/reset/whatever controller for M1-based systems 2 | 3 | #include 4 | #include 5 | 6 | #include "pico/stdlib.h" 7 | 8 | #include "hardware/gpio.h" 9 | #include "hardware/i2c.h" 10 | #include "tusb.h" 11 | 12 | struct gpio_pin_config { 13 | uint16_t pin; 14 | enum gpio_function mode; 15 | int dir; 16 | bool pu; 17 | bool level; 18 | bool skip; 19 | }; 20 | 21 | enum m1_pd_bmc_pins { 22 | M1_BMC_PIN_START, 23 | LED_G = M1_BMC_PIN_START, 24 | I2C_SDA, 25 | I2C_SCL, 26 | FUSB_INT, 27 | FUSB_VBUS, 28 | UART_TX, 29 | UART_RX, 30 | SBU_SWAP, 31 | SEL_USB, 32 | LED_R_TX, 33 | LED_R_RX, 34 | M1_BMC_PIN_END = LED_R_RX, 35 | }; 36 | 37 | struct hw_context { 38 | const struct gpio_pin_config *pins; 39 | uart_inst_t *const uart; 40 | i2c_inst_t *const i2c; 41 | void (*uart_handler)(void); 42 | uint8_t addr; 43 | uint8_t nr_pins; 44 | uint8_t uart_irq; 45 | }; 46 | 47 | const struct hw_context *get_hw_from_port(int port); 48 | void m1_pd_bmc_fusb_setup(unsigned int port, 49 | const struct hw_context *hw); 50 | void m1_pd_bmc_run(void); 51 | 52 | struct upstream_ops { 53 | void (*tx_bytes)(int32_t port, const char *ptr, int len); 54 | int32_t (*rx_byte)(int32_t port); 55 | void (*flush)(void); 56 | }; 57 | 58 | extern const struct upstream_ops *upstream_ops; 59 | void set_upstream_ops(bool serial); 60 | bool upstream_is_serial(void); 61 | 62 | void upstream_tx_str(int32_t port, const char *ptr); 63 | 64 | #define PRINTF_SIZE 512 65 | 66 | #define __printf(__p, __f, ...) do { \ 67 | char __str[PRINTF_SIZE]; \ 68 | snprintf(__str, PRINTF_SIZE, __f, ##__VA_ARGS__); \ 69 | upstream_tx_str(__p, __str); \ 70 | upstream_ops->flush(); \ 71 | } while(0) 72 | 73 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) 74 | -------------------------------------------------------------------------------- /tcpm_driver.c: -------------------------------------------------------------------------------- 1 | #include "m1-pd-bmc.h" 2 | #include "tcpm_driver.h" 3 | 4 | /* I2C wrapper functions - get I2C port / slave addr from config struct. */ 5 | int16_t tcpc_write(int16_t port, int16_t reg, int16_t val) 6 | { 7 | const struct hw_context *fusb = get_hw_from_port(port); 8 | uint8_t buf[] = { 9 | reg & 0xff, 10 | val & 0xff, 11 | }; 12 | 13 | i2c_write_blocking(fusb->i2c, fusb->addr, buf, sizeof(buf), false); 14 | 15 | return 0; 16 | } 17 | 18 | int16_t tcpc_write16(int16_t port, int16_t reg, int16_t val) 19 | { 20 | const struct hw_context *fusb = get_hw_from_port(port); 21 | uint8_t buf[] = { 22 | reg & 0xff, 23 | val & 0xff, 24 | (val >> 8) & 0xff, 25 | }; 26 | 27 | i2c_write_blocking(fusb->i2c, fusb->addr, buf, sizeof(buf), false); 28 | 29 | return 0; 30 | } 31 | 32 | int16_t tcpc_read(int16_t port, int16_t reg, int16_t *val) 33 | { 34 | const struct hw_context *fusb = get_hw_from_port(port); 35 | uint8_t buf[] = { 36 | reg & 0xff, 37 | 0, 38 | }; 39 | 40 | i2c_write_blocking(fusb->i2c, fusb->addr, &buf[0], 1, true); 41 | i2c_read_blocking(fusb->i2c, fusb->addr, &buf[1], 1, false); 42 | 43 | *val = buf[1]; 44 | 45 | return 0; 46 | } 47 | 48 | int16_t tcpc_read16(int16_t port, int16_t reg, int16_t *val) 49 | { 50 | const struct hw_context *fusb = get_hw_from_port(port); 51 | uint8_t buf[] = { 52 | reg & 0xff, 53 | 0, 54 | 0, 55 | }; 56 | 57 | i2c_write_blocking(fusb->i2c, fusb->addr, &buf[0], 1, true); 58 | i2c_read_blocking(fusb->i2c, fusb->addr, &buf[1], 2, false); 59 | *val = buf[1]; 60 | *val |= (buf[2] << 8); 61 | 62 | return 0; 63 | } 64 | 65 | int16_t tcpc_xfer(int16_t port, 66 | const uint8_t * out, int16_t out_size, 67 | uint8_t * in, int16_t in_size, int16_t flags) 68 | { 69 | const struct hw_context *fusb = get_hw_from_port(port); 70 | 71 | if (out_size) { 72 | i2c_write_blocking(fusb->i2c, fusb->addr, out, out_size, 73 | !(flags & I2C_XFER_STOP)); 74 | } 75 | 76 | if (in_size) { 77 | i2c_read_blocking(fusb->i2c, fusb->addr, in, in_size, 78 | !(flags & I2C_XFER_STOP)); 79 | } 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | #include "tusb.h" 2 | #include "pico/unique_id.h" 3 | #include "m1-pd-bmc.h" 4 | 5 | /* Still a RPi Pico, but let's be creative anyway */ 6 | #define USBD_VID (0x2e8a) 7 | #define USBD_PID (0x000a) 8 | 9 | enum { 10 | USBD_STR_LANGUAGE, // 0 11 | USBD_STR_MANUFACTURER, // 1 12 | USBD_STR_PRODUCT, // 2 13 | USBD_STR_SERIAL_NUMBER, // 3 14 | USBD_STR_CDC_0_NAME, // 4 15 | USBD_STR_CDC_1_NAME, // 5 16 | USBD_STR_LAST, 17 | }; 18 | 19 | static const tusb_desc_device_t usbd_desc_device = { 20 | .bLength = sizeof(tusb_desc_device_t), 21 | .bDescriptorType = TUSB_DESC_DEVICE, 22 | .bcdUSB = 0x0200, 23 | .bDeviceClass = TUSB_CLASS_MISC, 24 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 25 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 26 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 27 | .idVendor = USBD_VID, 28 | .idProduct = USBD_PID, 29 | .bcdDevice = 0x0100, 30 | .iManufacturer = USBD_STR_MANUFACTURER, 31 | .iProduct = USBD_STR_PRODUCT, 32 | .iSerialNumber = USBD_STR_SERIAL_NUMBER, 33 | .bNumConfigurations = 1, 34 | }; 35 | 36 | #define EPNUM_CDC_0_CMD (0x81) 37 | #define EPNUM_CDC_0_DATA (0x82) 38 | 39 | #define EPNUM_CDC_1_CMD (0x83) 40 | #define EPNUM_CDC_1_DATA (0x84) 41 | 42 | #define USBD_CDC_CMD_SIZE (64) 43 | #define USBD_CDC_DATA_SIZE (64) 44 | 45 | #define USBD_MAX_POWER_MA (250) 46 | 47 | #define USBD_DESC_LEN (TUD_CONFIG_DESC_LEN + \ 48 | TUD_CDC_DESC_LEN * CFG_TUD_CDC) 49 | 50 | enum { 51 | ITF_NUM_CDC_0, 52 | ITF_NUM_CDC_0_DATA, 53 | ITF_NUM_CDC_1, 54 | ITF_NUM_CDC_1_DATA, 55 | ITF_NUM_TOTAL, 56 | }; 57 | 58 | static const uint8_t usbd_desc_cfg[USBD_DESC_LEN] = { 59 | 60 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 61 | USBD_STR_LANGUAGE, 62 | USBD_DESC_LEN, 63 | TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 64 | USBD_MAX_POWER_MA), 65 | 66 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_0, 67 | USBD_STR_CDC_0_NAME, 68 | EPNUM_CDC_0_CMD, 69 | USBD_CDC_CMD_SIZE, 70 | EPNUM_CDC_0_DATA & 0x7F, 71 | EPNUM_CDC_0_DATA, 72 | USBD_CDC_DATA_SIZE), 73 | 74 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_1, 75 | USBD_STR_CDC_1_NAME, 76 | EPNUM_CDC_1_CMD, 77 | USBD_CDC_CMD_SIZE, 78 | EPNUM_CDC_1_DATA & 0x7F, 79 | EPNUM_CDC_1_DATA, 80 | USBD_CDC_DATA_SIZE), 81 | }; 82 | 83 | const uint8_t *tud_descriptor_device_cb(void) 84 | { 85 | return (const uint8_t *)&usbd_desc_device; 86 | } 87 | 88 | const uint8_t *tud_descriptor_configuration_cb(uint8_t index) 89 | { 90 | (void)index; 91 | return usbd_desc_cfg; 92 | } 93 | 94 | #define DESC_STR_MAX_LENGTH 20 95 | #define USB_DESC_STRLEN(l) ((TUSB_DESC_STRING << 8) | ((l + 1) * 2)) 96 | 97 | static void str8_to_str16(const char *str, uint16_t *str16) 98 | { 99 | int i; 100 | 101 | for (i = 0; i < DESC_STR_MAX_LENGTH - 1 && str[i]; i++) 102 | str16[i + 1] = str[i]; 103 | 104 | str16[0] = USB_DESC_STRLEN(i); 105 | } 106 | 107 | const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid) 108 | { 109 | static uint16_t desc_str[DESC_STR_MAX_LENGTH]; 110 | 111 | if (index >= USBD_STR_LAST) 112 | return NULL; 113 | 114 | switch (index) { 115 | case USBD_STR_LANGUAGE: 116 | desc_str[0] = USB_DESC_STRLEN(1); 117 | desc_str[1] = 0x0409; // English 118 | break; 119 | 120 | case USBD_STR_SERIAL_NUMBER: { 121 | char str[DESC_STR_MAX_LENGTH] = {}; 122 | pico_unique_board_id_t id; 123 | 124 | pico_get_unique_board_id(&id); 125 | snprintf(str, DESC_STR_MAX_LENGTH, 126 | "%02X%02X%02X%02X%02X%02X%02X%02X", 127 | id.id[0], id.id[1], id.id[2], id.id[3], 128 | id.id[4], id.id[5], id.id[6], id.id[7]); 129 | 130 | str8_to_str16(str, desc_str); 131 | break; 132 | } 133 | 134 | case USBD_STR_MANUFACTURER: 135 | str8_to_str16("AAAFNRAA", desc_str); 136 | break; 137 | 138 | case USBD_STR_PRODUCT: 139 | str8_to_str16("Central Scrutinizer", desc_str); 140 | break; 141 | 142 | case USBD_STR_CDC_0_NAME: 143 | str8_to_str16("Port-0", desc_str); 144 | break; 145 | 146 | case USBD_STR_CDC_1_NAME: 147 | str8_to_str16("Port-1", desc_str); 148 | break; 149 | } 150 | 151 | return desc_str; 152 | } 153 | -------------------------------------------------------------------------------- /usb_pd_tcpm.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 The Chromium OS Authors. All rights reserved. 2 | * Use of this source code is governed by a BSD-style license that can be 3 | * found in the LICENSE file. 4 | */ 5 | 6 | /* USB Power delivery port management */ 7 | 8 | #ifndef __CROS_EC_USB_PD_TCPM_H 9 | #define __CROS_EC_USB_PD_TCPM_H 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | /* List of common error codes that can be returned */ 16 | enum ec_error_list { 17 | /* Success - no error */ 18 | EC_SUCCESS = 0, 19 | /* Unknown error */ 20 | EC_ERROR_UNKNOWN = 1, 21 | /* Function not implemented yet */ 22 | EC_ERROR_UNIMPLEMENTED = 2, 23 | /* Overflow error; too much input provided. */ 24 | EC_ERROR_OVERFLOW = 3, 25 | /* Timeout */ 26 | EC_ERROR_TIMEOUT = 4, 27 | /* Invalid argument */ 28 | EC_ERROR_INVAL = 5, 29 | /* Already in use, or not ready yet */ 30 | EC_ERROR_BUSY = 6, 31 | /* Access denied */ 32 | EC_ERROR_ACCESS_DENIED = 7, 33 | /* Failed because component does not have power */ 34 | EC_ERROR_NOT_POWERED = 8, 35 | /* Failed because component is not calibrated */ 36 | EC_ERROR_NOT_CALIBRATED = 9, 37 | /* Failed because CRC error */ 38 | EC_ERROR_CRC = 10, 39 | }; 40 | 41 | /* Flags for i2c_xfer() */ 42 | #define I2C_XFER_START (1 << 0) /* Start smbus session from idle state */ 43 | #define I2C_XFER_STOP (1 << 1) /* Terminate smbus session with stop bit */ 44 | #define I2C_XFER_SINGLE (I2C_XFER_START | I2C_XFER_STOP) /* One transaction */ 45 | 46 | /* Default retry count for transmitting */ 47 | #define PD_RETRY_COUNT 3 48 | 49 | /* Time to wait for TCPC to complete transmit */ 50 | #define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US) 51 | 52 | /* No connect voltage threshold for sources based on Rp */ 53 | #define PD_SRC_DEF_VNC_MV 1600 54 | #define PD_SRC_1_5_VNC_MV 1600 55 | #define PD_SRC_3_0_VNC_MV 2600 56 | 57 | /* Rd voltage threshold for sources based on Rp */ 58 | #define PD_SRC_DEF_RD_THRESH_MV 200 59 | #define PD_SRC_1_5_RD_THRESH_MV 400 60 | #define PD_SRC_3_0_RD_THRESH_MV 800 61 | 62 | /* Control Message type */ 63 | enum pd_ctrl_msg_type { 64 | /* 0 Reserved */ 65 | PD_CTRL_GOOD_CRC = 1, 66 | PD_CTRL_GOTO_MIN = 2, 67 | PD_CTRL_ACCEPT = 3, 68 | PD_CTRL_REJECT = 4, 69 | PD_CTRL_PING = 5, 70 | PD_CTRL_PS_RDY = 6, 71 | PD_CTRL_GET_SOURCE_CAP = 7, 72 | PD_CTRL_GET_SINK_CAP = 8, 73 | PD_CTRL_DR_SWAP = 9, 74 | PD_CTRL_PR_SWAP = 10, 75 | PD_CTRL_VCONN_SWAP = 11, 76 | PD_CTRL_WAIT = 12, 77 | PD_CTRL_SOFT_RESET = 13, 78 | /* 14-15 Reserved */ 79 | 80 | /* Used for REV 3.0 */ 81 | PD_CTRL_NOT_SUPPORTED = 16, 82 | PD_CTRL_GET_SOURCE_CAP_EXT = 17, 83 | PD_CTRL_GET_STATUS = 18, 84 | PD_CTRL_FR_SWAP = 19, 85 | PD_CTRL_GET_PPS_STATUS = 20, 86 | PD_CTRL_GET_COUNTRY_CODES = 21, 87 | /* 22-31 Reserved */ 88 | }; 89 | 90 | /* Extended message type for REV 3.0 */ 91 | enum pd_ext_msg_type { 92 | /* 0 Reserved */ 93 | PD_EXT_SOURCE_CAP = 1, 94 | PD_EXT_STATUS = 2, 95 | PD_EXT_GET_BATTERY_CAP = 3, 96 | PD_EXT_GET_BATTERY_STATUS = 4, 97 | PD_EXT_BATTERY_CAP = 5, 98 | PD_EXT_GET_MANUFACTURER_INFO = 6, 99 | PD_EXT_MANUFACTURER_INFO = 7, 100 | PD_EXT_SECURITY_REQUEST = 8, 101 | PD_EXT_SECURITY_RESPONSE = 9, 102 | PD_EXT_FIRMWARE_UPDATE_REQUEST = 10, 103 | PD_EXT_FIRMWARE_UPDATE_RESPONSE = 11, 104 | PD_EXT_PPS_STATUS = 12, 105 | PD_EXT_COUNTRY_INFO = 13, 106 | PD_EXT_COUNTRY_CODES = 14, 107 | /* 15-31 Reserved */ 108 | }; 109 | 110 | /* Data message type */ 111 | enum pd_data_msg_type { 112 | /* 0 Reserved */ 113 | PD_DATA_SOURCE_CAP = 1, 114 | PD_DATA_REQUEST = 2, 115 | PD_DATA_BIST = 3, 116 | PD_DATA_SINK_CAP = 4, 117 | /* 5-14 Reserved for REV 2.0 */ 118 | PD_DATA_BATTERY_STATUS = 5, 119 | PD_DATA_ALERT = 6, 120 | PD_DATA_GET_COUNTRY_INFO = 7, 121 | /* 8-14 Reserved for REV 3.0 */ 122 | PD_DATA_VENDOR_DEF = 15, 123 | }; 124 | /* build extended message header */ 125 | /* All extended messages are chunked, so set bit 15 */ 126 | #define PD_EXT_HEADER(cnum, rchk, dsize) \ 127 | ((1 << 15) | ((cnum) << 11) | \ 128 | ((rchk) << 10) | (dsize)) 129 | 130 | /* build message header */ 131 | #define PD_HEADER(type, prole, drole, id, cnt, rev, ext) \ 132 | ((type) | ((rev) << 6) | \ 133 | ((drole) << 5) | ((prole) << 8) | \ 134 | ((id) << 9) | ((cnt) << 12) | ((ext) << 15)) 135 | 136 | /* Used for processing pd header */ 137 | #define PD_HEADER_EXT(header) (((header) >> 15) & 1) 138 | #define PD_HEADER_CNT(header) (((header) >> 12) & 7) 139 | #define PD_HEADER_TYPE(header) ((header) & 0x1F) 140 | #define PD_HEADER_ID(header) (((header) >> 9) & 7) 141 | #define PD_HEADER_REV(header) (((header) >> 6) & 3) 142 | 143 | /* Used for processing pd extended header */ 144 | #define PD_EXT_HEADER_CHUNKED(header) (((header) >> 15) & 1) 145 | #define PD_EXT_HEADER_CHUNK_NUM(header) (((header) >> 11) & 0xf) 146 | #define PD_EXT_HEADER_REQ_CHUNK(header) (((header) >> 10) & 1) 147 | #define PD_EXT_HEADER_DATA_SIZE(header) ((header) & 0x1ff) 148 | 149 | #define PD_REV10 0 150 | #define PD_REV20 1 151 | #define PD_REV30 2 152 | 153 | enum tcpc_cc_voltage_status { 154 | TYPEC_CC_VOLT_OPEN = 0, 155 | TYPEC_CC_VOLT_RA = 1, 156 | TYPEC_CC_VOLT_RD = 2, 157 | TYPEC_CC_VOLT_SNK_DEF = 5, 158 | TYPEC_CC_VOLT_SNK_1_5 = 6, 159 | TYPEC_CC_VOLT_SNK_3_0 = 7, 160 | }; 161 | 162 | enum tcpc_cc_pull { 163 | TYPEC_CC_RA = 0, 164 | TYPEC_CC_RP = 1, 165 | TYPEC_CC_RD = 2, 166 | TYPEC_CC_OPEN = 3, 167 | }; 168 | 169 | enum tcpc_rp_value { 170 | TYPEC_RP_USB = 0, 171 | TYPEC_RP_1A5 = 1, 172 | TYPEC_RP_3A0 = 2, 173 | TYPEC_RP_RESERVED = 3, 174 | }; 175 | 176 | enum tcpm_transmit_type { 177 | TCPC_TX_SOP = 0, 178 | TCPC_TX_SOP_PRIME = 1, 179 | TCPC_TX_SOP_PRIME_PRIME = 2, 180 | TCPC_TX_SOP_DEBUG_PRIME = 3, 181 | TCPC_TX_SOP_DEBUG_PRIME_PRIME = 4, 182 | TCPC_TX_HARD_RESET = 5, 183 | TCPC_TX_CABLE_RESET = 6, 184 | TCPC_TX_BIST_MODE_2 = 7 185 | }; 186 | 187 | enum tcpc_transmit_complete { 188 | TCPC_TX_COMPLETE_SUCCESS = 0, 189 | TCPC_TX_COMPLETE_DISCARDED = 1, 190 | TCPC_TX_COMPLETE_FAILED = 2, 191 | }; 192 | 193 | struct tcpc_config_t { 194 | int i2c_host_port; 195 | int i2c_slave_addr; 196 | const struct tcpm_drv *drv; 197 | }; 198 | 199 | #ifdef __cplusplus 200 | } 201 | #endif 202 | #endif /* __CROS_EC_USB_PD_TCPM_H */ 203 | -------------------------------------------------------------------------------- /FUSB302.h: -------------------------------------------------------------------------------- 1 | /* 2 | FUSB302.h - Library for interacting with the FUSB302B chip. 3 | Copyright 2010 The Chromium OS Authors 4 | Copyright 2017 Jason Cerundolo 5 | Released under an MIT license. See LICENSE file. 6 | */ 7 | 8 | #ifndef fusb302_H 9 | #define fusb302_H 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | #include 16 | #include "usb_pd_tcpm.h" 17 | 18 | /* Chip Device ID - 302A or 302B */ 19 | #define fusb302_DEVID_302A 0x08 20 | #define fusb302_DEVID_302B 0x09 21 | 22 | /* I2C slave address varies by part number */ 23 | /* FUSB302BUCX / FUSB302BMPX */ 24 | #define fusb302_I2C_SLAVE_ADDR 0x22 // 7-bit address for Arduino 25 | /* FUSB302B01MPX */ 26 | #define fusb302_I2C_SLAVE_ADDR_B01 0x23 27 | /* FUSB302B10MPX */ 28 | #define fusb302_I2C_SLAVE_ADDR_B10 0x24 29 | /* FUSB302B11MPX */ 30 | #define fusb302_I2C_SLAVE_ADDR_B11 0x25 31 | 32 | /* Default retry count for transmitting */ 33 | #define PD_RETRY_COUNT 3 34 | 35 | /* Time to wait for TCPC to complete transmit */ 36 | #define PD_T_TCPC_TX_TIMEOUT (100*MSEC_US) 37 | 38 | #define TCPC_REG_DEVICE_ID 0x01 39 | 40 | #define TCPC_REG_SWITCHES0 0x02 41 | #define TCPC_REG_SWITCHES0_CC2_PU_EN (1<<7) 42 | #define TCPC_REG_SWITCHES0_CC1_PU_EN (1<<6) 43 | #define TCPC_REG_SWITCHES0_VCONN_CC2 (1<<5) 44 | #define TCPC_REG_SWITCHES0_VCONN_CC1 (1<<4) 45 | #define TCPC_REG_SWITCHES0_MEAS_CC2 (1<<3) 46 | #define TCPC_REG_SWITCHES0_MEAS_CC1 (1<<2) 47 | #define TCPC_REG_SWITCHES0_CC2_PD_EN (1<<1) 48 | #define TCPC_REG_SWITCHES0_CC1_PD_EN (1<<0) 49 | 50 | #define TCPC_REG_SWITCHES1 0x03 51 | #define TCPC_REG_SWITCHES1_POWERROLE (1<<7) 52 | #define TCPC_REG_SWITCHES1_SPECREV1 (1<<6) 53 | #define TCPC_REG_SWITCHES1_SPECREV0 (1<<5) 54 | #define TCPC_REG_SWITCHES1_DATAROLE (1<<4) 55 | #define TCPC_REG_SWITCHES1_AUTO_GCRC (1<<2) 56 | #define TCPC_REG_SWITCHES1_TXCC2_EN (1<<1) 57 | #define TCPC_REG_SWITCHES1_TXCC1_EN (1<<0) 58 | 59 | #define TCPC_REG_MEASURE 0x04 60 | #define TCPC_REG_MEASURE_VBUS (1<<6) 61 | #define TCPC_REG_MEASURE_MDAC_MV(mv) (((mv)/42) & 0x3f) 62 | 63 | #define TCPC_REG_CONTROL0 0x06 64 | #define TCPC_REG_CONTROL0_TX_FLUSH (1<<6) 65 | #define TCPC_REG_CONTROL0_INT_MASK (1<<5) 66 | #define TCPC_REG_CONTROL0_HOST_CUR_MASK (3<<2) 67 | #define TCPC_REG_CONTROL0_HOST_CUR_3A0 (3<<2) 68 | #define TCPC_REG_CONTROL0_HOST_CUR_1A5 (2<<2) 69 | #define TCPC_REG_CONTROL0_HOST_CUR_USB (1<<2) 70 | #define TCPC_REG_CONTROL0_TX_START (1<<0) 71 | 72 | #define TCPC_REG_CONTROL1 0x07 73 | #define TCPC_REG_CONTROL1_ENSOP2DB (1<<6) 74 | #define TCPC_REG_CONTROL1_ENSOP1DB (1<<5) 75 | #define TCPC_REG_CONTROL1_BIST_MODE2 (1<<4) 76 | #define TCPC_REG_CONTROL1_RX_FLUSH (1<<2) 77 | #define TCPC_REG_CONTROL1_ENSOP2 (1<<1) 78 | #define TCPC_REG_CONTROL1_ENSOP1 (1<<0) 79 | 80 | #define TCPC_REG_CONTROL2 0x08 81 | /* two-bit field, valid values below */ 82 | #define TCPC_REG_CONTROL2_MODE (1<<1) 83 | #define TCPC_REG_CONTROL2_MODE_DFP (0x3) 84 | #define TCPC_REG_CONTROL2_MODE_UFP (0x2) 85 | #define TCPC_REG_CONTROL2_MODE_DRP (0x1) 86 | #define TCPC_REG_CONTROL2_MODE_POS (1) 87 | #define TCPC_REG_CONTROL2_TOGGLE (1<<0) 88 | 89 | #define TCPC_REG_CONTROL3 0x09 90 | #define TCPC_REG_CONTROL3_SEND_HARDRESET (1<<6) 91 | #define TCPC_REG_CONTROL3_BIST_TMODE (1<<5) /* 302B Only */ 92 | #define TCPC_REG_CONTROL3_AUTO_HARDRESET (1<<4) 93 | #define TCPC_REG_CONTROL3_AUTO_SOFTRESET (1<<3) 94 | /* two-bit field */ 95 | #define TCPC_REG_CONTROL3_N_RETRIES (1<<1) 96 | #define TCPC_REG_CONTROL3_N_RETRIES_POS (1) 97 | #define TCPC_REG_CONTROL3_N_RETRIES_SIZE (2) 98 | #define TCPC_REG_CONTROL3_AUTO_RETRY (1<<0) 99 | 100 | #define TCPC_REG_MASK 0x0A 101 | #define TCPC_REG_MASK_VBUSOK (1<<7) 102 | #define TCPC_REG_MASK_ACTIVITY (1<<6) 103 | #define TCPC_REG_MASK_COMP_CHNG (1<<5) 104 | #define TCPC_REG_MASK_CRC_CHK (1<<4) 105 | #define TCPC_REG_MASK_ALERT (1<<3) 106 | #define TCPC_REG_MASK_WAKE (1<<2) 107 | #define TCPC_REG_MASK_COLLISION (1<<1) 108 | #define TCPC_REG_MASK_BC_LVL (1<<0) 109 | 110 | #define TCPC_REG_POWER 0x0B 111 | #define TCPC_REG_POWER_PWR (1<<0) /* four-bit field */ 112 | #define TCPC_REG_POWER_PWR_LOW 0x1 /* Bandgap + Wake circuitry */ 113 | #define TCPC_REG_POWER_PWR_MEDIUM 0x3 /* LOW + Receiver + Current refs */ 114 | #define TCPC_REG_POWER_PWR_HIGH 0x7 /* MEDIUM + Measure block */ 115 | #define TCPC_REG_POWER_PWR_ALL 0xF /* HIGH + Internal Oscillator */ 116 | 117 | #define TCPC_REG_RESET 0x0C 118 | #define TCPC_REG_RESET_PD_RESET (1<<1) 119 | #define TCPC_REG_RESET_SW_RESET (1<<0) 120 | 121 | #define TCPC_REG_MASKA 0x0E 122 | #define TCPC_REG_MASKA_OCP_TEMP (1<<7) 123 | #define TCPC_REG_MASKA_TOGDONE (1<<6) 124 | #define TCPC_REG_MASKA_SOFTFAIL (1<<5) 125 | #define TCPC_REG_MASKA_RETRYFAIL (1<<4) 126 | #define TCPC_REG_MASKA_HARDSENT (1<<3) 127 | #define TCPC_REG_MASKA_TX_SUCCESS (1<<2) 128 | #define TCPC_REG_MASKA_SOFTRESET (1<<1) 129 | #define TCPC_REG_MASKA_HARDRESET (1<<0) 130 | 131 | #define TCPC_REG_MASKB 0x0F 132 | #define TCPC_REG_MASKB_GCRCSENT (1<<0) 133 | 134 | #define TCPC_REG_STATUS0A 0x3C 135 | #define TCPC_REG_STATUS0A_SOFTFAIL (1<<5) 136 | #define TCPC_REG_STATUS0A_RETRYFAIL (1<<4) 137 | #define TCPC_REG_STATUS0A_POWER (1<<2) /* two-bit field */ 138 | #define TCPC_REG_STATUS0A_RX_SOFT_RESET (1<<1) 139 | #define TCPC_REG_STATUS0A_RX_HARD_RESET (1<<0) 140 | 141 | #define TCPC_REG_STATUS1A 0x3D 142 | /* three-bit field, valid values below */ 143 | #define TCPC_REG_STATUS1A_TOGSS (1<<3) 144 | #define TCPC_REG_STATUS1A_TOGSS_RUNNING 0x0 145 | #define TCPC_REG_STATUS1A_TOGSS_SRC1 0x1 146 | #define TCPC_REG_STATUS1A_TOGSS_SRC2 0x2 147 | #define TCPC_REG_STATUS1A_TOGSS_SNK1 0x5 148 | #define TCPC_REG_STATUS1A_TOGSS_SNK2 0x6 149 | #define TCPC_REG_STATUS1A_TOGSS_AA 0x7 150 | #define TCPC_REG_STATUS1A_TOGSS_POS (3) 151 | #define TCPC_REG_STATUS1A_TOGSS_MASK (0x7) 152 | 153 | #define TCPC_REG_STATUS1A_RXSOP2DB (1<<2) 154 | #define TCPC_REG_STATUS1A_RXSOP1DB (1<<1) 155 | #define TCPC_REG_STATUS1A_RXSOP (1<<0) 156 | 157 | #define TCPC_REG_INTERRUPTA 0x3E 158 | #define TCPC_REG_INTERRUPTA_OCP_TEMP (1<<7) 159 | #define TCPC_REG_INTERRUPTA_TOGDONE (1<<6) 160 | #define TCPC_REG_INTERRUPTA_SOFTFAIL (1<<5) 161 | #define TCPC_REG_INTERRUPTA_RETRYFAIL (1<<4) 162 | #define TCPC_REG_INTERRUPTA_HARDSENT (1<<3) 163 | #define TCPC_REG_INTERRUPTA_TX_SUCCESS (1<<2) 164 | #define TCPC_REG_INTERRUPTA_SOFTRESET (1<<1) 165 | #define TCPC_REG_INTERRUPTA_HARDRESET (1<<0) 166 | 167 | #define TCPC_REG_INTERRUPTB 0x3F 168 | #define TCPC_REG_INTERRUPTB_GCRCSENT (1<<0) 169 | 170 | #define TCPC_REG_STATUS0 0x40 171 | #define TCPC_REG_STATUS0_VBUSOK (1<<7) 172 | #define TCPC_REG_STATUS0_ACTIVITY (1<<6) 173 | #define TCPC_REG_STATUS0_COMP (1<<5) 174 | #define TCPC_REG_STATUS0_CRC_CHK (1<<4) 175 | #define TCPC_REG_STATUS0_ALERT (1<<3) 176 | #define TCPC_REG_STATUS0_WAKE (1<<2) 177 | #define TCPC_REG_STATUS0_BC_LVL1 (1<<1) /* two-bit field */ 178 | #define TCPC_REG_STATUS0_BC_LVL0 (1<<0) /* two-bit field */ 179 | 180 | #define TCPC_REG_STATUS1 0x41 181 | #define TCPC_REG_STATUS1_RXSOP2 (1<<7) 182 | #define TCPC_REG_STATUS1_RXSOP1 (1<<6) 183 | #define TCPC_REG_STATUS1_RX_EMPTY (1<<5) 184 | #define TCPC_REG_STATUS1_RX_FULL (1<<4) 185 | #define TCPC_REG_STATUS1_TX_EMPTY (1<<3) 186 | #define TCPC_REG_STATUS1_TX_FULL (1<<2) 187 | 188 | #define TCPC_REG_INTERRUPT 0x42 189 | #define TCPC_REG_INTERRUPT_VBUSOK (1<<7) 190 | #define TCPC_REG_INTERRUPT_ACTIVITY (1<<6) 191 | #define TCPC_REG_INTERRUPT_COMP_CHNG (1<<5) 192 | #define TCPC_REG_INTERRUPT_CRC_CHK (1<<4) 193 | #define TCPC_REG_INTERRUPT_ALERT (1<<3) 194 | #define TCPC_REG_INTERRUPT_WAKE (1<<2) 195 | #define TCPC_REG_INTERRUPT_COLLISION (1<<1) 196 | #define TCPC_REG_INTERRUPT_BC_LVL (1<<0) 197 | 198 | #define TCPC_REG_FIFOS 0x43 199 | 200 | /* Tokens defined for the FUSB302 TX FIFO */ 201 | enum fusb302_txfifo_tokens { 202 | fusb302_TKN_TXON = 0xA1, 203 | fusb302_TKN_SYNC1 = 0x12, 204 | fusb302_TKN_SYNC2 = 0x13, 205 | fusb302_TKN_SYNC3 = 0x1B, 206 | fusb302_TKN_RST1 = 0x15, 207 | fusb302_TKN_RST2 = 0x16, 208 | fusb302_TKN_PACKSYM = 0x80, 209 | fusb302_TKN_JAMCRC = 0xFF, 210 | fusb302_TKN_EOP = 0x14, 211 | fusb302_TKN_TXOFF = 0xFE, 212 | }; 213 | 214 | enum fusb302_rxfifo_tokens { 215 | fusb302_TKN_SOP = 0xE0, 216 | fusb302_TKN_SOP1 = 0xC0, 217 | fusb302_TKN_SOP2 = 0xA0, 218 | fusb302_TKN_SOP1DB = 0x80, 219 | fusb302_TKN_SOP2DB = 0x60, 220 | fusb302_TKN_SOP_MASK = 0xE0, 221 | }; 222 | 223 | extern const struct tcpm_drv fusb302_tcpm_drv; 224 | 225 | // Common methods for TCPM implementations 226 | int16_t fusb302_tcpm_init(int16_t port); 227 | void fusb302_pd_reset(int16_t port); 228 | void fusb302_flush_rx_fifo(int16_t port); 229 | void fusb302_flush_tx_fifo(int16_t port); 230 | void fusb302_auto_goodcrc_enable(int16_t port, int16_t enable); 231 | int16_t fusb302_tcpm_get_cc(int16_t port, int16_t *cc1, int16_t *cc2); 232 | int16_t fusb302_tcpm_set_cc(int16_t port, int16_t pull); 233 | int16_t fusb302_tcpm_set_polarity(int16_t port, int16_t polarity); 234 | int16_t fusb302_tcpm_set_vconn(int16_t port, int16_t enable); 235 | int16_t fusb302_tcpm_set_msg_header(int16_t port, int16_t power_role, int16_t data_role); 236 | int16_t fusb302_tcpm_set_rx_enable(int16_t port, int16_t enable); 237 | int16_t fusb302_tcpm_get_message(int16_t port, uint32_t *payload, int16_t *head, enum fusb302_rxfifo_tokens *sop); 238 | int16_t fusb302_tcpm_transmit(int16_t port, enum tcpm_transmit_type type, uint16_t header, const uint32_t *data); 239 | int16_t fusb302_tcpm_get_vbus_level(int16_t port); 240 | int16_t fusb302_tcpm_select_rp_value(int16_t port, int16_t rp); 241 | void fusb302_get_irq(int16_t port, int16_t *irq, int16_t *irqa, int16_t *irqb); 242 | int16_t fusb302_rx_fifo_is_empty(int16_t port); 243 | 244 | #ifdef __cplusplus 245 | } 246 | #endif 247 | 248 | #endif /* fusb302_H */ 249 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | "This is the Central Scrutinizer" 2 | 3 | So you have built (or otherwise obtained) a device labelled "Central 4 | Scrutinizer" which allows you interact with the serial port of a M1/M2 5 | and reboot it. 6 | 7 | Otherwise, you can look at 8 | https://git.kernel.org/pub/scm/linux/kernel/git/maz/cs-hw.git 9 | for the hardware side of the project. Everything in this file 10 | assumes that the board is assembled and functionnal. 11 | 12 | This file describe how to build the software and flash it onto the 13 | Raspberry-Pi Pico that controls the adapter. As for the HW, the SW 14 | started its life as m1-ubmc, but let's be clear that its name really 15 | is "Central Scrutinizer" so that you too can channel your inner FZ. 16 | 17 | But first, credit where credit is due: this software is not an 18 | original work, but is built on top of code which is: 19 | 20 | Copyright (c) 2014 The Chromium OS Authors 21 | Copyright (c) 2018 Reclaimer Labs - https://www.reclaimerlabs.com/ 22 | Copyright (c) 2020 The Asahi Linux Contributors 23 | 24 | See the LICENSE file for the fine print. 25 | 26 | ** Install the pre-requisites 27 | 28 | On a Debian system, this is what you need: 29 | 30 | sudo apt install cmake gcc-arm-none-eabi build-essential git 31 | 32 | I'm sure there is an equivalent for other OSs, but life is too short 33 | to use anything else. 34 | 35 | ** Install the pico-sdk: 36 | 37 | I've used versions 1.4 and 1.5 of the SDK. YMMV. 38 | 39 | git clone -b master https://github.com/raspberrypi/pico-sdk.git 40 | 41 | export PICO_SDK_PATH=/the/long/and/winding/road/to/pico-sdk 42 | 43 | cd pico-sdk 44 | 45 | git submodule update --init 46 | 47 | ** Build the Pico firmware: 48 | 49 | It builds just like any other pico-sdk project: 50 | 51 | git clone git://git.kernel.org/pub/scm/linux/kernel/git/maz/cs-sw 52 | 53 | cd cs-sw 54 | 55 | [optionally checkout the dev branch you want] 56 | 57 | mkdir build 58 | 59 | cd build 60 | 61 | cmake .. 62 | 63 | make 64 | 65 | You should end-up with a file called m1_ubmc.uf2 in the build 66 | directory. If you don't, something is wrong. Finding what is wrong is 67 | your responsibility, not mine! ;-) 68 | 69 | ** Flash it 70 | 71 | Place the Pico in programming mode by pressing the BOOTROM button 72 | while plugging the USB connector, and issue something along the lines 73 | of: 74 | 75 | sudo mount /dev/disk/by-id/usb-RPI_RP2_E0C9125B0D9B-0\:0-part1 /mnt 76 | 77 | sudo cp m1_ubmc.uf2 /mnt 78 | 79 | sudo eject /mnt 80 | 81 | ** Plug it 82 | 83 | You will need a cable that contains most (if not all) of the USB-C 84 | wires, and crucially the SBU signals. Cheap cables won't carry them, 85 | but you won't find out until you actually try them. 86 | 87 | You really want something that looks thick and stiff, and flimsy 88 | cables are unlikely to work (the cable that ships with M1 laptops 89 | doesn't). None of my cables labelled USB2.0 seem to work. I've had 90 | good results with cables designed to carry video signals, and USB3.1 91 | cables seem to do the trick. 92 | 93 | If you are stuck with a USB2.0 cable that doesn't have the SBU1/2 94 | signals, there is still a way to get a serial console by using the 95 | USB2.0 lines if you have a v2 or later: 96 | 97 | - v2: you can connect SBU1 to USB_TX and SBU2 to USB_RX, which is 98 | only a matter of linking two sets of pins next to the micro-USB 99 | connector. In such a configuration, *DO NOT USE* the micro-USB 100 | connector *AT ALL* (put some tape or glue on it). If you don't 101 | know how to perform this change, please don't try. You can then 102 | instruct the firmware to claim these pins instead of the SBU pins. 103 | 104 | - v3+: switching the serial lines is fully under SW control, and you 105 | can dynamically switch between the two configurations. Better use 106 | a proper SBU capable cable though, as you otherwise lose the USB 107 | passthrough capability. 108 | 109 | Other oddities: because I'm lazy, the v0/v1 hardware only connects a 110 | single CC line to the board's PD controller, and there is no provision 111 | to swap TX and RX. Which means that on the board side, there is only 112 | a single valid orientation for the USB-C cable. Trial and error are, 113 | as usual, your best friends. Put a label on the board's end of the 114 | cable as an indication of the orientation. v2 and later boards have 115 | the required logic to detect the orientation and swap the SBU pins, 116 | making them easier to use. 117 | 118 | Also, there is only *ONE* USB-C port that is capable of serial 119 | output. The device will happily connect to others, and allow things 120 | like rebooting the Mac, but you won't get any serial output. Please 121 | use the correct port. 122 | 123 | Models I know of: 124 | - M1 mini 2020: USB-C port closest to the power supply 125 | - M1 Ultra 2022: USB-C port closest to the Ethernet port 126 | - M2 MacBook Air 2022: USB-C port closest to the MagSafe port 127 | - M2 Pro mini 2023: USB-C port closest to the Ethernet port (Oliver) 128 | 129 | Apple documents the correct port in their "Revive or restore a Mac 130 | using Apple Configurator": 131 | https://support.apple.com/guide/apple-configurator-mac/revive-or-restore-a-mac-with-apple-silicon-apdd5f3c75ad/mac 132 | 133 | On desktop devices it is usually the port closest to the power supply. 134 | On laptops it is the USB-C port on the back left side. 135 | 136 | Optionally, you can make use of the micro-USB connector that is on 137 | the other side of the board. It's main use it to allow interacting 138 | with the Asahi m1n1 firmware, such as tethered booting. Do not connect 139 | it to anything if you use the USB2.0 lines as the serial console. 140 | 141 | ** Use it 142 | 143 | If you have correctly built and flashed the firmware, you will have 144 | the Pico led blinking at the rate of twice a second, and a couple of 145 | /dev/ttyACM* being advertised by your host: 146 | 147 | [708023.097129] usb 1-4: new full-speed USB device number 72 using xhci_hcd 148 | [708023.265195] usb 1-4: New USB device found, idVendor=2e8a, idProduct=000a, bcdDevice= 1.00 149 | [708023.265213] usb 1-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 150 | [708023.265219] usb 1-4: Product: Central Scrutinizer 151 | [708023.265223] usb 1-4: Manufacturer: AAAFNRAA 152 | [708023.265228] usb 1-4: SerialNumber: E66164084319392A 153 | [708023.273622] cdc_acm 1-4:1.0: ttyACM0: USB ACM device 154 | [708023.278612] cdc_acm 1-4:1.2: ttyACM1: USB ACM device 155 | 156 | The board identifies itself as a Pico (as per VID/PID), and claims to 157 | be the Central Scrutinizer, as you were hoping for. 158 | 159 | Each of the two /dev/ttyACM* devices is a potential connection to a 160 | Mac. Assuming the likely case that you have a single CS board attached 161 | to the Pico, run: 162 | 163 | screen /dev/ttyACM0 164 | 165 | and you should see something like: 166 | 167 | This is the Central Scrutinizer 168 | Control character is ^_ 169 | Press ^_ + ? for help 170 | P0: VBUS OFF 171 | P0: Device ID: 0x91 172 | P0: Init 173 | P0: STATUS0: 0x80 174 | P0: VBUS OFF 175 | P0: Disconnected 176 | P0: S: DISCONNECTED 177 | P0: Empty debug message 178 | P0: IRQ=0 10 0 179 | P0: Connected: cc1=2 cc2=0 180 | P0: Polarity: CC1 (normal) 181 | P0: VBUS ON 182 | P0: S: DFP_VBUS_ON 183 | P0: Empty debug message 184 | P0: IRQ=71 4 0 185 | P0: S: DFP_CONNECTED 186 | P0: >VDM serial -> SBU1/2 187 | P0: IRQ=71 4 1 188 | P0: VDM serial -> SBU1/2" line, the serial line should 191 | now be connected and you can interact with the M1. Note that you can 192 | use any serial configuration you want on the Mac side as long as it is 193 | 115200n8. One day I may implement the required controls, but that's 194 | super low priority on the list of things I want to do. Also, there is 195 | no such list, and the current setup works well enough for me. 196 | 197 | Replace "screen" with whatever you want to communicate with the 198 | device, be it conserver, minicom, cu, or even cat (there is no 199 | accounting for taste). 200 | 201 | Typing ^_? (Control-Underscore followed by a question mark) will lead 202 | to the follwing dump: 203 | 204 | P0: Current port 205 | ^_ Escape character 206 | ^_ ^_ Raw ^_ 207 | ^_ ! DUT reset 208 | ^_ ^R Central Scrutinizer reset 209 | ^_ ^^ Central Scrutinizer reset to programming mode 210 | ^_ ^X Force disconnect 211 | ^_ ^D Toggle debug 212 | ^_ ^M Send empty debug VDM 213 | ^_ 1 Serial on Primary USB pins 214 | ^_ 2 Serial on SBU pins 215 | ^_ ? This message 216 | P0: Port 0: present,cc1,SBU1/2 217 | P0: Port 1: absent 218 | 219 | which is completely self explainatory, but let's expand on it anyway: 220 | 221 | - ^_ ^_ sends a raw ^_, just in case you really need it 222 | 223 | - ^_ ! resets the Mac without any warning. Yes, this is dangerous, use 224 | with caution and only when nothing else will do. 225 | 226 | - ^_ ^R reboots the Central Scrutinizer itself. Not very useful, except 227 | when it is. 228 | 229 | - ^_ ^^ reboots the Central Scrutinizer in programming mode, exactly 230 | as if you had plugged it with the BOOTROM button pressed. You end-up 231 | in mass-storage mode and can update the firmware. 232 | 233 | - ^_ ^X forces a PD disconnection, in case either the CS or the Mac 234 | becomes a bit confused. It was useful a couple of times in debugging 235 | situations... 236 | 237 | - ^_ ^D toggles the "debug" internal flag. This has very little effect 238 | at the moment as most of the debug statements ignore the flag and 239 | spit out the junk on the console unconditionally. 240 | 241 | - ^_ ^M sends an empty debug message to the remote PD controller. 242 | That's a debug feature... 243 | 244 | - ^_ 1 Configure the Mac's serial on Primary USB pins. On v3+, this 245 | also isolates the micro-USb connector. On older versions, it 246 | doesn't, so make sure you don't have anything plugged there. 247 | 248 | - ^_ 2 Configure the Mac's serial on SBU pins, which is the default. 249 | On v3+, this enables the use of the micro-USB connector. 250 | 251 | - ^_ ? prints the help message (duh). 252 | 253 | Finally, the Port 0:/1: lines indicate which I2C/UART combinations the 254 | board is using, as well as the CC line used, the pin set used for 255 | serial, and potentially the debug status. The HW supports two boards 256 | being driven by a single Pico (see the HW documentation for the gory 257 | details). 258 | -------------------------------------------------------------------------------- /start.c: -------------------------------------------------------------------------------- 1 | // FUSB302-based serial/reset/whatever controller for M1-based systems 2 | 3 | #include 4 | 5 | #include "bsp/board.h" 6 | #include "tusb.h" 7 | #include "m1-pd-bmc.h" 8 | #include "FUSB302.h" 9 | 10 | static const struct gpio_pin_config m1_pd_bmc_pin_config0[] = { 11 | [M1_BMC_PIN_START ... M1_BMC_PIN_END] = { 12 | .skip = true, 13 | }, 14 | [LED_G] = { 15 | .pin = 25, 16 | .mode = GPIO_FUNC_SIO, 17 | .dir = GPIO_OUT, 18 | }, 19 | [I2C_SDA] = { /* I2C0 */ 20 | .pin = 16, 21 | .mode = GPIO_FUNC_I2C, 22 | }, 23 | [I2C_SCL] = { /* I2C0 */ 24 | .pin = 17, 25 | .mode = GPIO_FUNC_I2C, 26 | }, 27 | [FUSB_INT] = { 28 | .pin = 18, 29 | .mode = GPIO_FUNC_SIO, 30 | .dir = GPIO_IN, 31 | }, 32 | [FUSB_VBUS] = { 33 | .pin = 26, 34 | .mode = GPIO_FUNC_SIO, 35 | .dir = GPIO_IN, 36 | }, 37 | [UART_TX] = { /* UART0 */ 38 | .pin = 12, 39 | .mode = GPIO_FUNC_UART, 40 | }, 41 | [UART_RX] = { /* UART0 */ 42 | .pin = 13, 43 | .mode = GPIO_FUNC_UART, 44 | }, 45 | [SBU_SWAP] = { 46 | .pin = 20, 47 | .mode = GPIO_FUNC_SIO, 48 | .dir = GPIO_OUT, 49 | }, 50 | [SEL_USB] = { 51 | .pin = 7, 52 | .mode = GPIO_FUNC_SIO, 53 | .dir = GPIO_OUT, 54 | }, 55 | }; 56 | 57 | static const struct gpio_pin_config waveshare_2ch_rs232_config0[] = { 58 | [M1_BMC_PIN_START ... M1_BMC_PIN_END] = { 59 | .skip = true, 60 | }, 61 | /* Prevent the unused leds from drawing much curent */ 62 | [LED_R_TX] = { 63 | .pin = 0, 64 | .mode = GPIO_FUNC_SIO, 65 | .dir = GPIO_IN, 66 | .pu = true, 67 | }, 68 | [LED_R_RX] = { 69 | .pin = 1, 70 | .mode = GPIO_FUNC_SIO, 71 | .dir = GPIO_IN, 72 | .pu = true, 73 | }, 74 | }; 75 | 76 | static const struct gpio_pin_config m1_pd_bmc_pin_config1[] = { 77 | [M1_BMC_PIN_START ... M1_BMC_PIN_END] = { 78 | .skip = true, 79 | }, 80 | [LED_G] = { 81 | .pin = 25, 82 | .mode = GPIO_FUNC_SIO, 83 | .dir = GPIO_OUT, 84 | .skip = true, 85 | }, 86 | [I2C_SDA] = { /* I2C1 */ 87 | .pin = 22, 88 | .mode = GPIO_FUNC_I2C, 89 | }, 90 | [I2C_SCL] = { /* I2C1 */ 91 | .pin = 27, 92 | .mode = GPIO_FUNC_I2C, 93 | }, 94 | [FUSB_INT] = { 95 | .pin = 19, 96 | .mode = GPIO_FUNC_SIO, 97 | .dir = GPIO_IN, 98 | }, 99 | [FUSB_VBUS] = { 100 | .pin = 28, 101 | .mode = GPIO_FUNC_SIO, 102 | .dir = GPIO_IN, 103 | }, 104 | [UART_TX] = { /* UART1 */ 105 | .pin = 8, 106 | .mode = GPIO_FUNC_UART, 107 | }, 108 | [UART_RX] = { /* UART1 */ 109 | .pin = 9, 110 | .mode = GPIO_FUNC_UART, 111 | }, 112 | [SBU_SWAP] = { 113 | .pin = 21, 114 | .mode = GPIO_FUNC_SIO, 115 | .dir = GPIO_OUT, 116 | }, 117 | [SEL_USB] = { 118 | .pin = 6, 119 | .mode = GPIO_FUNC_SIO, 120 | .dir = GPIO_OUT, 121 | }, 122 | }; 123 | 124 | static const struct gpio_pin_config waveshare_2ch_rs232_config1[] = { 125 | [M1_BMC_PIN_START ... M1_BMC_PIN_END] = { 126 | .skip = true, 127 | }, 128 | [UART_TX] = { /* UART1 */ 129 | .pin = 4, 130 | .mode = GPIO_FUNC_UART, 131 | }, 132 | [UART_RX] = { /* UART1 */ 133 | .pin = 5, 134 | .mode = GPIO_FUNC_UART, 135 | }, 136 | }; 137 | 138 | static struct { 139 | /* 140 | * we rely on prod/cons rollover behaviour, and buf must cover 141 | * the full range of prod/cons. 142 | */ 143 | uint8_t prod; 144 | uint8_t cons; 145 | char buf[256]; 146 | } uart1_buf; 147 | 148 | static void __not_in_flash_func(uart_irq_fn)(int port, 149 | const struct hw_context *hw) 150 | { 151 | while (uart_is_readable(hw->uart)) { 152 | const char c = uart_getc(hw->uart); 153 | upstream_ops->tx_bytes(port, &c, 1); 154 | } 155 | } 156 | 157 | static void uart0_irq_fn(void); 158 | static void uart1_irq_fn(void); 159 | 160 | static const struct hw_context hw0 = { 161 | .pins = m1_pd_bmc_pin_config0, 162 | .nr_pins = ARRAY_SIZE(m1_pd_bmc_pin_config0), 163 | .uart = uart0, 164 | .uart_irq = UART0_IRQ, 165 | .uart_handler = uart0_irq_fn, 166 | .i2c = i2c0, 167 | .addr = fusb302_I2C_SLAVE_ADDR, 168 | }; 169 | 170 | static const struct hw_context hw1 = { 171 | .pins = m1_pd_bmc_pin_config1, 172 | .nr_pins = ARRAY_SIZE(m1_pd_bmc_pin_config1), 173 | .uart = uart1, 174 | .uart_irq = UART1_IRQ, 175 | .uart_handler = uart1_irq_fn, 176 | .i2c = i2c1, 177 | .addr = fusb302_I2C_SLAVE_ADDR, 178 | }; 179 | 180 | static void __not_in_flash_func(uart0_irq_fn)(void) 181 | { 182 | uart_irq_fn(0, &hw0); 183 | } 184 | 185 | static void __not_in_flash_func(uart1_irq_fn)(void) 186 | { 187 | if (!upstream_is_serial()) { 188 | uart_irq_fn(1, &hw1); 189 | return; 190 | } 191 | 192 | while (uart_is_readable(uart1)) { 193 | uart1_buf.buf[uart1_buf.prod++] = uart_getc(uart1); 194 | 195 | /* Oops, we're losing data... */ 196 | if (uart1_buf.prod == uart1_buf.cons) 197 | uart1_buf.cons++; 198 | } 199 | } 200 | 201 | static void init_system(const struct hw_context *hw) 202 | { 203 | i2c_init(hw->i2c, 400 * 1000); 204 | 205 | uart_init(hw->uart, 115200); 206 | uart_set_hw_flow(hw->uart, false, false); 207 | uart_set_fifo_enabled(hw->uart, true); 208 | irq_set_exclusive_handler(hw->uart_irq, hw->uart_handler); 209 | irq_set_enabled(hw->uart_irq, true); 210 | uart_set_irq_enables(hw->uart, true, false); 211 | 212 | /* Interrupt when the RX FIFO is 3/4 full */ 213 | hw_write_masked(&uart_get_hw(hw->uart)->ifls, 214 | 3 << UART_UARTIFLS_RXIFLSEL_LSB, 215 | UART_UARTIFLS_RXIFLSEL_BITS); 216 | } 217 | 218 | static void m1_pd_bmc_gpio_setup_one(const struct gpio_pin_config *pin) 219 | { 220 | if (pin->skip) 221 | return; 222 | gpio_set_function(pin->pin, pin->mode); 223 | if (pin->mode == GPIO_FUNC_SIO) { 224 | gpio_init(pin->pin); 225 | gpio_set_dir(pin->pin, pin->dir); 226 | if (pin->dir == GPIO_OUT) 227 | gpio_put(pin->pin, pin->level); 228 | } 229 | if (pin->pu) 230 | gpio_pull_up(pin->pin); 231 | } 232 | 233 | static void m1_pd_bmc_system_init(const struct hw_context *hw) 234 | { 235 | init_system(hw); 236 | 237 | for (unsigned int i = 0; i < hw->nr_pins; i++) 238 | m1_pd_bmc_gpio_setup_one(&hw->pins[i]); 239 | } 240 | 241 | static bool apply_waveshare_2ch_rs232_overrides(void) 242 | { 243 | /* 244 | * Hack: check for PUPs on 0/1, and assumes this is a dual 245 | * RS232 board plugged in. Ideally, we'd also check UART1 on 246 | * 4/5, but this looks unreliable. If this gets in the way, 247 | * turn it into a compilation option instead. 248 | */ 249 | if (!(gpio_get(waveshare_2ch_rs232_config0[LED_R_TX].pin) && 250 | gpio_get(waveshare_2ch_rs232_config0[LED_R_RX].pin))) 251 | return false; 252 | 253 | for (int i = 0; i < ARRAY_SIZE(waveshare_2ch_rs232_config0); i++) 254 | m1_pd_bmc_gpio_setup_one(&waveshare_2ch_rs232_config0[i]); 255 | for (int i = 0; i < ARRAY_SIZE(waveshare_2ch_rs232_config1); i++) 256 | m1_pd_bmc_gpio_setup_one(&waveshare_2ch_rs232_config1[i]); 257 | 258 | return true; 259 | } 260 | 261 | static void usb_tx_bytes(int32_t port, const char *ptr, int len) 262 | { 263 | if (!tud_cdc_n_connected(port)) 264 | return; 265 | 266 | while (len > 0) { 267 | size_t available = tud_cdc_n_write_available(port); 268 | 269 | if (!available) { 270 | tud_task(); 271 | } else { 272 | size_t send = MIN(len, available); 273 | size_t sent = tud_cdc_n_write(port, ptr, send); 274 | 275 | ptr += sent; 276 | len -= sent; 277 | } 278 | 279 | tud_cdc_n_write_flush(port); 280 | } 281 | } 282 | 283 | static int32_t usb_rx_byte(int32_t port) 284 | { 285 | uint8_t c; 286 | 287 | if (!tud_cdc_n_connected(port) || !tud_cdc_n_available(port)) 288 | return -1; 289 | 290 | tud_cdc_n_read(port, &c, 1); 291 | return c; 292 | } 293 | 294 | static const struct upstream_ops usb_upstream_ops = { 295 | .tx_bytes = usb_tx_bytes, 296 | .rx_byte = usb_rx_byte, 297 | .flush = tud_task, 298 | }; 299 | 300 | static void serial1_tx_bytes(int32_t port, const char *ptr, int len) 301 | { 302 | uart_write_blocking(uart1, (const uint8_t *)ptr, len); 303 | } 304 | 305 | static int32_t serial1_rx_byte(int32_t port) 306 | { 307 | uint32_t status; 308 | int32_t val; 309 | 310 | tud_task(); 311 | val = usb_rx_byte(port); 312 | if (val != -1) 313 | return val; 314 | 315 | status = save_and_disable_interrupts(); 316 | 317 | if (uart1_buf.prod == uart1_buf.cons) 318 | val = -1; 319 | else 320 | val = uart1_buf.buf[uart1_buf.cons++]; 321 | 322 | restore_interrupts(status); 323 | 324 | return val; 325 | } 326 | 327 | static void serial1_flush(void) {} 328 | 329 | static const struct upstream_ops serial1_upstream_ops = { 330 | .tx_bytes = serial1_tx_bytes, 331 | .rx_byte = serial1_rx_byte, 332 | .flush = serial1_flush, 333 | }; 334 | 335 | const struct upstream_ops *upstream_ops; 336 | 337 | void upstream_tx_str(int32_t port, const char *str) 338 | { 339 | do { 340 | const char *cursor = str; 341 | 342 | while (*cursor && *cursor != '\n') 343 | cursor++; 344 | 345 | upstream_ops->tx_bytes(port, str, cursor - str); 346 | 347 | if (!*cursor) 348 | return; 349 | 350 | upstream_ops->tx_bytes(port, "\n\r", 2); 351 | 352 | str = cursor + 1; 353 | } while (*str); 354 | } 355 | 356 | void set_upstream_ops(bool serial) 357 | { 358 | if (serial) { 359 | upstream_ops = &serial1_upstream_ops; 360 | } else { 361 | upstream_ops = &usb_upstream_ops; 362 | } 363 | 364 | __dsb(); 365 | } 366 | 367 | bool upstream_is_serial(void) 368 | { 369 | return upstream_ops == &serial1_upstream_ops; 370 | } 371 | 372 | static int wait_for_usb_connection(void) 373 | { 374 | do { 375 | static bool state = false; 376 | static int cnt = 0; 377 | 378 | tud_task(); 379 | gpio_put(hw0.pins[LED_G].pin, state); 380 | sleep_ms(1); 381 | cnt++; 382 | cnt %= 256; 383 | if (!cnt) 384 | state = !state; 385 | } while (!tud_cdc_n_connected(0) && !tud_cdc_n_connected(1)); 386 | 387 | return !tud_cdc_n_connected(0); 388 | } 389 | 390 | int main(void) 391 | { 392 | bool success; 393 | int port; 394 | 395 | set_upstream_ops(false); 396 | 397 | success = set_sys_clock_khz(133000, false); 398 | 399 | board_init(); 400 | tusb_init(); 401 | 402 | m1_pd_bmc_system_init(&hw0); 403 | m1_pd_bmc_system_init(&hw1); 404 | 405 | if (apply_waveshare_2ch_rs232_overrides()) { 406 | set_upstream_ops(true); 407 | port = 0; 408 | __printf(port, "Detected Waveshare 2CH RS232, switching UART1\n"); 409 | } else { 410 | port = wait_for_usb_connection(); 411 | } 412 | 413 | __printf(port, "This is the Central Scrutinizer\n"); 414 | __printf(port, "Control character is ^_\n"); 415 | __printf(port, "Press ^_ + ? for help\n"); 416 | 417 | if (!success) 418 | __printf(port, "WARNING: Nominal frequency NOT reached\n"); 419 | 420 | m1_pd_bmc_fusb_setup(0, &hw0); 421 | if (!upstream_is_serial()) { 422 | m1_pd_bmc_fusb_setup(1, &hw1); 423 | } 424 | 425 | m1_pd_bmc_run(); 426 | } 427 | -------------------------------------------------------------------------------- /vdmtool.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "tcpm_driver.h" 8 | #include "FUSB302.h" 9 | #include "m1-pd-bmc.h" 10 | #include "hardware/watchdog.h" 11 | #include "hardware/sync.h" 12 | #include "pico/bootrom.h" 13 | 14 | enum state { 15 | STATE_INVALID = -1, 16 | STATE_DISCONNECTED = 0, 17 | STATE_READY, 18 | STATE_DFP_VBUS_ON, 19 | STATE_DFP_CONNECTED, 20 | STATE_DFP_ACCEPT, 21 | STATE_IDLE, 22 | }; 23 | 24 | struct vdm_context { 25 | const struct hw_context *hw; 26 | enum state state; 27 | int16_t std_flag; 28 | int16_t source_cap_timer; 29 | int16_t cc_debounce; 30 | volatile bool pending; 31 | bool verbose; 32 | bool vdm_escape; 33 | bool cc_line; 34 | uint8_t serial_pin_set; 35 | uint8_t version; 36 | }; 37 | 38 | static struct vdm_context vdm_contexts[CONFIG_USB_PD_PORT_COUNT]; 39 | 40 | #define PIN(cxt, idx) (cxt)->hw->pins[(idx)].pin 41 | #define PORT(cxt) ((cxt) - vdm_contexts) 42 | #define UART(cxt) (cxt)->hw->uart 43 | 44 | #define HIGH true 45 | #define LOW false 46 | 47 | #define cprintf_cont(cxt, str, ...) do { \ 48 | __printf(PORT(cxt), str, ##__VA_ARGS__); \ 49 | } while(0) 50 | 51 | #define cprintf(cxt, str, ...) do { \ 52 | cprintf_cont(cxt, \ 53 | "P%d: " str, PORT(cxt), ##__VA_ARGS__); \ 54 | } while(0) 55 | 56 | #define dprintf(cxt, ...) do { \ 57 | if (cxt->verbose) \ 58 | cprintf(cxt, ##__VA_ARGS__); \ 59 | } while(0) 60 | 61 | #define STATE(cxt, x) do { \ 62 | cxt->state = STATE_##x; \ 63 | cprintf(cxt, "S: " #x "\n"); \ 64 | } while(0) 65 | 66 | static void vbus_off(struct vdm_context *cxt) 67 | { 68 | gpio_put(PIN(cxt, FUSB_VBUS), LOW); 69 | sleep_ms(800); 70 | gpio_set_dir(PIN(cxt, FUSB_VBUS), GPIO_IN); 71 | cprintf(cxt, "Turning VBUS OFF\n"); 72 | } 73 | 74 | static void vbus_on(struct vdm_context *cxt) 75 | { 76 | cprintf(cxt, "Turning VBUS ON\n"); 77 | gpio_set_dir(PIN(cxt, FUSB_VBUS), GPIO_OUT); 78 | gpio_put(PIN(cxt, FUSB_VBUS), HIGH); 79 | } 80 | 81 | void debug_poke(struct vdm_context *cxt) 82 | { 83 | int16_t hdr = PD_HEADER(PD_DATA_VENDOR_DEF, 1, 1, 0, 1, PD_REV20, 0); 84 | const uint32_t x = 0; 85 | 86 | dprintf(cxt, "Empty debug message\n"); 87 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP_DEBUG_PRIME_PRIME, hdr, &x); 88 | } 89 | 90 | static void evt_dfpconnect(struct vdm_context *cxt, int16_t cc1, int16_t cc2) 91 | { 92 | int ms = 200; 93 | 94 | cprintf(cxt, "Connected: cc1=%d cc2=%d\n", cc1, cc2); 95 | 96 | /* The original FUSB302 needs a bit longer... */ 97 | if ((cxt->version & 0xf0) < 0x90) 98 | ms += 300; 99 | sleep_ms(ms); 100 | fusb302_tcpm_set_vconn(PORT(cxt), 0); 101 | 102 | fusb302_pd_reset(PORT(cxt)); 103 | fusb302_tcpm_set_msg_header(PORT(cxt), 1, 1); // Source 104 | cxt->cc_line = !(cc1 > cc2); 105 | fusb302_tcpm_set_polarity(PORT(cxt), cxt->cc_line); 106 | cprintf(cxt, "Polarity: CC%d (%s)\n", 107 | (int)cxt->cc_line + 1, cxt->cc_line ? "flipped" : "normal"); 108 | 109 | /* If none of the CCs are disconnected, enable VCONN */ 110 | if (cc1 && cc2) { 111 | fusb302_tcpm_set_vconn(PORT(cxt), 1); 112 | cprintf(cxt, "VCONN on CC%d\n", (int)cxt->cc_line + 1); 113 | } 114 | 115 | fusb302_tcpm_set_rx_enable(PORT(cxt), 1); 116 | vbus_on(cxt); 117 | STATE(cxt, DFP_VBUS_ON); 118 | 119 | debug_poke(cxt); 120 | } 121 | 122 | static void evt_disconnect(struct vdm_context *cxt) 123 | { 124 | vbus_off(cxt); 125 | cprintf(cxt, "Disconnected\n"); 126 | fusb302_pd_reset(PORT(cxt)); 127 | fusb302_tcpm_set_vconn(PORT(cxt), 0); 128 | fusb302_tcpm_set_rx_enable(PORT(cxt), 0); 129 | fusb302_tcpm_select_rp_value(PORT(cxt), TYPEC_RP_USB); 130 | fusb302_tcpm_set_cc(PORT(cxt), TYPEC_CC_RP); // DFP mode 131 | STATE(cxt, DISCONNECTED); 132 | } 133 | 134 | static void send_power_request(struct vdm_context *cxt, uint32_t cap) 135 | { 136 | int16_t hdr = PD_HEADER(PD_DATA_REQUEST, 0, 0, 0, 1, PD_REV20, 0); 137 | uint32_t req = 138 | (1L << 28) | // Object position (fixed 5V) 139 | (1L << 25) | // USB communications capable 140 | (4L << 10) | // Random mA operating 141 | (4L << 0); // Random mA max 142 | 143 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, &req); 144 | cprintf(cxt, ">REQUEST\n"); 145 | (void)cap; 146 | } 147 | 148 | static void send_sink_cap(struct vdm_context *cxt) 149 | { 150 | int16_t hdr = PD_HEADER(PD_DATA_SINK_CAP, 1, 1, 0, 1, PD_REV20, 0); 151 | uint32_t cap = 152 | (1L << 26) | // USB communications capable 153 | (0L << 10) | // 0mA operating 154 | (0L << 0); // 0mA max 155 | 156 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, &cap); 157 | cprintf(cxt, ">SINK_CAP\n"); 158 | STATE(cxt, READY); 159 | } 160 | 161 | static void send_source_cap(struct vdm_context *cxt) 162 | { 163 | int16_t hdr = PD_HEADER(PD_DATA_SOURCE_CAP, 1, 1, 0, 1, PD_REV20, 0); 164 | uint32_t cap = 1UL << 31; /* Variable non-battery PS, 0V, 0mA */ 165 | 166 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, &cap); 167 | cprintf(cxt, ">SOURCE_CAP\n"); 168 | cxt->source_cap_timer = 0; 169 | } 170 | 171 | static void dump_msg(struct vdm_context *cxt, 172 | enum fusb302_rxfifo_tokens sop, int16_t hdr, uint32_t * msg) 173 | { 174 | int16_t len = PD_HEADER_CNT(hdr); 175 | switch (sop) { 176 | case fusb302_TKN_SOP: 177 | cprintf_cont(cxt, "RX SOP ("); 178 | break; 179 | case fusb302_TKN_SOP1: 180 | cprintf_cont(cxt, "RX SOP' ("); 181 | break; 182 | case fusb302_TKN_SOP2: 183 | cprintf_cont(cxt, "RX SOP\" ("); 184 | break; 185 | case fusb302_TKN_SOP1DB: 186 | cprintf_cont(cxt, "RX SOP'DEBUG ("); 187 | break; 188 | case fusb302_TKN_SOP2DB: 189 | cprintf_cont(cxt, "RX SOP\"DEBUG ("); 190 | break; 191 | default: 192 | cprintf_cont(cxt, "RX ? ("); 193 | break; 194 | } 195 | 196 | cprintf_cont(cxt, "%d) [%x]", len, hdr); 197 | for (int16_t i = 0; i < PD_HEADER_CNT(hdr); i++) 198 | cprintf_cont(cxt, " %lx", msg[i]); 199 | 200 | cprintf_cont(cxt, "\n"); 201 | } 202 | 203 | static void handle_discover_identity(struct vdm_context *cxt) 204 | { 205 | int16_t hdr = PD_HEADER(PD_DATA_VENDOR_DEF, 0, 0, 0, 4, PD_REV20, 0); 206 | uint32_t vdm[4] = { 207 | 0xff008001L | (1L << 6), // ACK 208 | 209 | (1L << 30) | // USB Device 210 | (0L << 27) | // UFP Product Type = Undefined 211 | (0L << 26) | // No modal operation 212 | (0L << 23) | // DFP Product Type = Undefined 213 | 0x5acL, // USB VID = Apple 214 | 215 | 0L, // XID 216 | 217 | (0x0001L << 16) | // USB PID, 218 | 0x100L // bcdDevice 219 | }; 220 | 221 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, vdm); 222 | cprintf(cxt, ">VDM DISCOVER_IDENTITY\n"); 223 | } 224 | 225 | static void handle_power_request(struct vdm_context *cxt, uint32_t req) 226 | { 227 | int16_t hdr = PD_HEADER(PD_CTRL_ACCEPT, 1, 1, 0, 0, PD_REV20, 0); 228 | 229 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, NULL); 230 | cprintf(cxt, ">ACCEPT\n"); 231 | STATE(cxt, DFP_ACCEPT); 232 | } 233 | 234 | static void send_ps_rdy(struct vdm_context *cxt) 235 | { 236 | int16_t hdr = PD_HEADER(PD_CTRL_PS_RDY, 1, 1, 0, 0, PD_REV20, 0); 237 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, NULL); 238 | cprintf(cxt, ">PS_RDY\n"); 239 | 240 | STATE(cxt, IDLE); 241 | } 242 | 243 | static void send_reject(struct vdm_context *cxt) 244 | { 245 | int16_t hdr = PD_HEADER(PD_CTRL_REJECT, 1, 1, 0, 0, PD_REV20, 0); 246 | 247 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP, hdr, NULL); 248 | cprintf(cxt, ">REJECT\n"); 249 | 250 | STATE(cxt, IDLE); 251 | } 252 | 253 | static void handle_vdm(struct vdm_context *cxt, enum fusb302_rxfifo_tokens sop, 254 | int16_t hdr, uint32_t *msg) 255 | { 256 | switch (*msg) { 257 | case 0xff008001: // Structured VDM: DISCOVER IDENTITY 258 | cprintf(cxt, "state) { 344 | case STATE_DFP_VBUS_ON: 345 | STATE(cxt, DFP_CONNECTED); 346 | vdm_claim_serial(cxt); 347 | break; 348 | case STATE_DFP_ACCEPT: 349 | send_ps_rdy(cxt); 350 | break; 351 | default: 352 | break; 353 | } 354 | } 355 | 356 | static void handle_irq(struct vdm_context *cxt) 357 | { 358 | int16_t irq, irqa, irqb; 359 | fusb302_get_irq(PORT(cxt), &irq, &irqa, &irqb); 360 | 361 | dprintf(cxt, "IRQ=%x %x %x\n", irq, irqa, irqb); 362 | if (irq & TCPC_REG_INTERRUPT_VBUSOK) { 363 | cprintf(cxt, "IRQ: VBUSOK (VBUS="); 364 | if (fusb302_tcpm_get_vbus_level(PORT(cxt))) { 365 | cprintf_cont(cxt, "ON)\n"); 366 | send_source_cap(cxt); 367 | debug_poke(cxt); 368 | } else { 369 | cprintf_cont(cxt, "OFF)\n"); 370 | evt_disconnect(cxt); 371 | } 372 | } 373 | if (irqa & TCPC_REG_INTERRUPTA_HARDRESET) { 374 | cprintf(cxt, "IRQ: HARDRESET\n"); 375 | evt_disconnect(cxt); 376 | } 377 | if (irqa & TCPC_REG_INTERRUPTA_TX_SUCCESS) { 378 | //cprintf(cxt, "IRQ: TXSUCCESS\n"); 379 | evt_sent(cxt); 380 | } 381 | if (irqb & TCPC_REG_INTERRUPTB_GCRCSENT) { 382 | //cprintf(cxt, "IRQ: GCRCSENT\n"); 383 | while (!fusb302_rx_fifo_is_empty(PORT(cxt))) 384 | evt_packet(cxt); 385 | } 386 | } 387 | 388 | static void vdm_send_msg(struct vdm_context *cxt, const uint32_t *vdm, int nr_u32) 389 | { 390 | cprintf(cxt, "Sending: "); 391 | for(int i=0; i < nr_u32; i++) { 392 | cprintf(cxt, "0x%08X ", vdm[i]); 393 | } 394 | cprintf(cxt, "\n"); 395 | int16_t hdr = PD_HEADER(PD_DATA_VENDOR_DEF, 1, 1, 0, nr_u32, PD_REV20, 0); 396 | fusb302_tcpm_transmit(PORT(cxt), TCPC_TX_SOP_DEBUG_PRIME_PRIME, hdr, vdm); 397 | } 398 | 399 | static void vdm_pd_reset(struct vdm_context *cxt) 400 | { 401 | uint32_t vdm[] = { 0x5ac8012, 0x0103, 0x8000<<16 }; 402 | vdm_send_msg(cxt, vdm, ARRAY_SIZE(vdm)); 403 | cprintf(cxt, ">VDM SET ACTION PD reset\n"); 404 | } 405 | 406 | static const char *pinsets[] = { 407 | "AltUSB", "PrimUSB", "SBU1/2", 408 | }; 409 | 410 | static void vdm_claim_serial(struct vdm_context *cxt) 411 | { 412 | bool usb_serial, sbu_swap; 413 | 414 | //uint32_t vdm[] = { 0x5ac8010 }; // Get Action List 415 | //uint32_t vdm[] = { 0x5ac8012, 0x0105, 0x8002<<16 }; // PMU Reset + DFU Hold 416 | //uint32_t vdm[] = { 0x5ac8011, 0x0809 }; // Get Action List 417 | //uint32_t vdm[] = { 0x5ac8012, 0x0105, 0x8000<<16 }; 418 | 419 | // Maybe SWD? 420 | uint32_t vdm[] = { 0x5AC8012, 0x01800206 }; 421 | 422 | vdm[1] |= 1 << (cxt->serial_pin_set + 16); 423 | 424 | vdm_send_msg(cxt, vdm, ARRAY_SIZE(vdm)); 425 | cprintf(cxt, ">VDM serial -> %s\n", pinsets[cxt->serial_pin_set]); 426 | 427 | /* If using the SBU pins, swap the pins if using CC2. */ 428 | sbu_swap = (cxt->serial_pin_set == 2) ? cxt->cc_line : LOW; 429 | usb_serial = (cxt->serial_pin_set == 1); 430 | 431 | gpio_put(PIN(cxt, SBU_SWAP), sbu_swap); 432 | gpio_put(PIN(cxt, SEL_USB), usb_serial); 433 | dprintf(cxt, "SBU_SWAP = %d, SEL_USB = %d\n", sbu_swap, usb_serial); 434 | } 435 | 436 | void vdm_send_reboot(struct vdm_context *cxt) 437 | { 438 | uint32_t vdm[] = { 0x5ac8012, 0x0105, 0x8000UL<<16 }; 439 | vdm_send_msg(cxt, vdm, ARRAY_SIZE(vdm)); 440 | cprintf(cxt, ">VDM SET ACTION reboot\n"); 441 | } 442 | 443 | static void serial_out(struct vdm_context *cxt, char c) 444 | { 445 | uart_putc_raw(UART(cxt), c); 446 | } 447 | 448 | static void help(struct vdm_context *cxt) 449 | { 450 | cprintf(cxt, "Current port\n" 451 | "^_ Escape character\n" 452 | "^_ ^_ Raw ^_\n" 453 | "^_ ! DUT reset\n" 454 | "^_ ^R Central Scrutinizer reset\n" 455 | "^_ ^^ Central Scrutinizer reset to programming mode\n" 456 | "^_ ^X Force disconnect\n" 457 | "^_ ^D Toggle debug\n" 458 | "^_ ^M Send empty debug VDM\n" 459 | "^_ 1 Serial on Primary USB pins\n" 460 | "^_ 2 Serial on SBU pins\n"); 461 | 462 | if (upstream_is_serial()) 463 | cprintf_cont(cxt, "^_ ^@ Send break\n"); 464 | if (PORT(cxt) == 0 && !vdm_contexts[1].hw) 465 | cprintf_cont(cxt, "^_ ^U Switch upstream port USB/Serial\n"); 466 | cprintf_cont(cxt, "^_ ? This message\n"); 467 | 468 | for (int i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { 469 | struct vdm_context *tmp = &vdm_contexts[i]; 470 | 471 | cprintf(cxt, "Port %d: %s", 472 | PORT(tmp), 473 | tmp->hw ? "present" : "absent"); 474 | if (tmp->hw) 475 | cprintf_cont(cxt, ",cc%d,%s,%s%s", 476 | tmp->cc_line + 1, 477 | pinsets[tmp->serial_pin_set], 478 | upstream_is_serial() ? "serial" : "USB", 479 | tmp->verbose ? ",debug" : ""); 480 | cprintf_cont(cxt, "\n"); 481 | } 482 | } 483 | 484 | /* Break is handled as sideband data via the CDC layer */ 485 | void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) 486 | { 487 | struct vdm_context *cxt; 488 | 489 | if (itf > CONFIG_USB_PD_PORT_COUNT) 490 | return; 491 | 492 | cxt = &vdm_contexts[itf]; 493 | 494 | if (!cxt->hw) 495 | return; 496 | 497 | /* Section 6.2.15 of the spec has the recipe */ 498 | uart_set_break(UART(cxt), !!duration_ms); 499 | if (duration_ms && duration_ms != (uint16_t)~0) { 500 | sleep_ms(duration_ms); 501 | uart_set_break(UART(cxt), 0); 502 | } 503 | } 504 | 505 | static bool serial_handler(struct vdm_context *cxt) 506 | { 507 | bool uart_active = false; 508 | int32_t c; 509 | 510 | while ((c = upstream_ops->rx_byte(PORT(cxt))) != -1) { 511 | uart_active = true; 512 | 513 | if ((!cxt->vdm_escape && c != 0x1f)) { 514 | serial_out(cxt, c); 515 | continue; 516 | } 517 | 518 | switch (c) { 519 | case '!': /* ! */ 520 | vdm_send_reboot(cxt); 521 | break; 522 | case 0x12: /* ^R */ 523 | watchdog_enable(1, 1); 524 | break; 525 | case 0x1E: /* ^^ */ 526 | reset_usb_boot(1 << PICO_DEFAULT_LED_PIN,0); 527 | break; 528 | case 0x1F: /* ^_ */ 529 | if (!cxt->vdm_escape) { 530 | cxt->vdm_escape = true; 531 | continue; 532 | } 533 | 534 | serial_out(cxt, c); 535 | break; 536 | case 4: /* ^D */ 537 | cxt->verbose = !cxt->verbose; 538 | cprintf(cxt, "Debug o%s\n", cxt->verbose ? "n" : "ff"); 539 | break; 540 | case 0: /* ^@ */ 541 | tud_cdc_send_break_cb(PORT(cxt), 100); 542 | break; 543 | case '\r': /* Enter */ 544 | debug_poke(cxt); 545 | break; 546 | case '1' ... '2': 547 | cxt->serial_pin_set = c - '0'; 548 | vdm_pd_reset(cxt); 549 | break; 550 | case 0x15: /* ^U */ 551 | /* We can't do that if port 1 exists */ 552 | if (PORT(cxt) != 0 || vdm_contexts[1].hw) 553 | break; 554 | 555 | cprintf(cxt, "Upstream switching to %s\n", 556 | !upstream_is_serial() ? "serial" : "USB"); 557 | set_upstream_ops(!upstream_is_serial()); 558 | cprintf(cxt, "Upstream is %s\n", 559 | upstream_is_serial() ? "serial" : "USB"); 560 | break; 561 | case 0x18: /* ^X */ 562 | cxt->pending = true; 563 | evt_disconnect(cxt); 564 | break; 565 | case '?': 566 | help(cxt); 567 | break; 568 | } 569 | 570 | cxt->vdm_escape = false; 571 | } 572 | 573 | return uart_active; 574 | } 575 | 576 | static void state_machine(struct vdm_context *cxt) 577 | { 578 | switch (cxt->state) { 579 | case STATE_DISCONNECTED:{ 580 | int16_t cc1 = -1, cc2 = -1; 581 | fusb302_tcpm_get_cc(PORT(cxt), &cc1, &cc2); 582 | dprintf(cxt, "Poll: cc1=%d cc2=%d\n", (int)cc1, (int)cc2); 583 | if (cc1 >= 2 || cc2 >= 2) { 584 | evt_dfpconnect(cxt, cc1, cc2); 585 | } else { 586 | vbus_off(cxt); 587 | } 588 | break; 589 | } 590 | case STATE_DFP_VBUS_ON:{ 591 | if (++cxt->source_cap_timer > 37) { 592 | cprintf(cxt, "Sourcecap timer expired\n"); 593 | send_source_cap(cxt); 594 | debug_poke(cxt); 595 | } 596 | break; 597 | } 598 | case STATE_DFP_CONNECTED:{ 599 | break; 600 | } 601 | case STATE_DFP_ACCEPT:{ 602 | break; 603 | } 604 | case STATE_READY:{ 605 | STATE(cxt, IDLE); 606 | break; 607 | } 608 | case STATE_IDLE:{ 609 | break; 610 | } 611 | default:{ 612 | cprintf(cxt, "Invalid state %d\n", cxt->state); 613 | } 614 | } 615 | if (cxt->state != STATE_DISCONNECTED) { 616 | int16_t cc1 = -1, cc2 = -1; 617 | fusb302_tcpm_get_cc(PORT(cxt), &cc1, &cc2); 618 | if (cc1 < 2 && cc2 < 2) { 619 | if (cxt->cc_debounce++ > 5) { 620 | cprintf(cxt, "Disconnect: cc1=%d cc2=%d\n", 621 | cc1, cc2); 622 | evt_disconnect(cxt); 623 | cxt->cc_debounce = 0; 624 | } 625 | } else { 626 | cxt->cc_debounce = 0; 627 | } 628 | } 629 | } 630 | 631 | const struct hw_context *get_hw_from_port(int port) 632 | { 633 | return vdm_contexts[port].hw; 634 | } 635 | 636 | static void fusb_int_handler(uint gpio, uint32_t event_mask) 637 | { 638 | for (int i = 0; i < CONFIG_USB_PD_PORT_COUNT; i++) { 639 | struct vdm_context *cxt = &vdm_contexts[i]; 640 | 641 | if (!cxt->hw) 642 | continue; 643 | 644 | if (gpio == PIN(cxt, FUSB_INT) && 645 | (event_mask & GPIO_IRQ_LEVEL_LOW)) { 646 | cxt->pending = true; 647 | gpio_set_irq_enabled(PIN(cxt, FUSB_INT), GPIO_IRQ_LEVEL_LOW, false); 648 | } 649 | } 650 | } 651 | 652 | void m1_pd_bmc_fusb_setup(unsigned int port, 653 | const struct hw_context *hw) 654 | { 655 | struct vdm_context *cxt; 656 | int16_t reg; 657 | 658 | if (port >= CONFIG_USB_PD_PORT_COUNT) 659 | return; 660 | 661 | cxt = vdm_contexts + port; 662 | *cxt = (struct vdm_context) { 663 | .hw = hw, 664 | .state = STATE_DISCONNECTED, 665 | .source_cap_timer = 0, 666 | .cc_debounce = 0, 667 | .verbose = true, 668 | .vdm_escape = false, 669 | .serial_pin_set = 2, /* SBU1/2 */ 670 | }; 671 | 672 | /* 673 | * If we can't infer pull-ups on the I2C, it is likely that 674 | * nothing is connected and we'd better skip this port. 675 | */ 676 | if (!gpio_get(PIN(cxt, I2C_SCL)) || !gpio_get(PIN(cxt, I2C_SDA))) { 677 | cprintf(cxt, "I2C pins low while idling, skipping port\n"); 678 | cxt->hw = NULL; 679 | return; 680 | } 681 | 682 | gpio_put(PIN(cxt, LED_G), HIGH); 683 | /* No swapping */ 684 | gpio_put(PIN(cxt, SBU_SWAP), LOW); 685 | /* USB2.0 pins routed to USB */ 686 | gpio_put(PIN(cxt, SEL_USB), LOW); 687 | vbus_off(cxt); 688 | 689 | tcpc_read(PORT(cxt), TCPC_REG_DEVICE_ID, ®); 690 | if (!(reg & 0x80)) { 691 | cprintf(cxt, "Invalid device ID. Is the FUSB302 alive?\n"); 692 | cxt->hw = NULL; 693 | return; 694 | } 695 | 696 | cprintf(cxt, "Device ID: %c_rev%c (0x%x)\n", 697 | 'A' + ((reg >> 4) & 0x7), 'A' + (reg & 3), reg); 698 | 699 | cxt->version = reg & 0xff; 700 | 701 | cprintf(cxt, "Init\n"); 702 | fusb302_tcpm_init(PORT(cxt)); 703 | 704 | fusb302_pd_reset(PORT(cxt)); 705 | fusb302_tcpm_set_rx_enable(PORT(cxt), 0); 706 | fusb302_tcpm_set_cc(PORT(cxt), TYPEC_CC_OPEN); 707 | sleep_ms(500); 708 | 709 | tcpc_read(PORT(cxt), TCPC_REG_STATUS0, ®); 710 | cprintf(cxt, "STATUS0: 0x%x\n", reg); 711 | 712 | gpio_set_irq_enabled_with_callback(PIN(cxt, FUSB_INT), GPIO_IRQ_LEVEL_LOW, true, 713 | fusb_int_handler); 714 | 715 | evt_disconnect(cxt); 716 | debug_poke(cxt); 717 | } 718 | 719 | static bool m1_pd_bmc_run_one(struct vdm_context *cxt) 720 | { 721 | if (cxt->pending) { 722 | handle_irq(cxt); 723 | state_machine(cxt); 724 | cxt->pending = false; 725 | gpio_set_irq_enabled(PIN(cxt, FUSB_INT), GPIO_IRQ_LEVEL_LOW, true); 726 | } 727 | 728 | return serial_handler(cxt) || cxt->pending; 729 | } 730 | 731 | #define for_each_cxt(___c) \ 732 | for (struct vdm_context *___c = &vdm_contexts[0]; \ 733 | (___c - vdm_contexts) < CONFIG_USB_PD_PORT_COUNT; \ 734 | ___c++) \ 735 | if (___c->hw) 736 | 737 | void m1_pd_bmc_run(void) 738 | { 739 | while (1) { 740 | bool busy = false; 741 | 742 | for_each_cxt(cxt) { 743 | gpio_put(PIN(cxt, LED_G), HIGH); 744 | busy |= m1_pd_bmc_run_one(cxt); 745 | irq_set_enabled(cxt->hw->uart_irq, false); 746 | } 747 | 748 | upstream_ops->flush(); 749 | 750 | for_each_cxt(cxt) 751 | irq_set_enabled(cxt->hw->uart_irq, true); 752 | 753 | if (busy) 754 | continue; 755 | 756 | for_each_cxt(cxt) 757 | gpio_put(PIN(cxt, LED_G), LOW); 758 | 759 | __wfe(); 760 | } 761 | } 762 | -------------------------------------------------------------------------------- /FUSB302.c: -------------------------------------------------------------------------------- 1 | /* 2 | FUSB302.c - Library for interacting with the FUSB302B chip. 3 | Copyright 2015 The Chromium OS Authors 4 | Copyright 2017 Jason Cerundolo 5 | Released under an MIT license. See LICENSE file. 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | #include "FUSB302.h" 12 | #include "usb_pd_tcpm.h" 13 | #include "tcpm_driver.h" 14 | #include "platform.h" 15 | 16 | #define PACKET_IS_GOOD_CRC(head) (PD_HEADER_TYPE(head) == PD_CTRL_GOOD_CRC && \ 17 | PD_HEADER_CNT(head) == 0) 18 | 19 | static struct fusb302_chip_state { 20 | int16_t cc_polarity; 21 | int16_t vconn_enabled; 22 | /* 1 = pulling up (DFP) 0 = pulling down (UFP) */ 23 | int16_t pulling_up; 24 | int16_t rx_enable; 25 | uint8_t control1; 26 | uint8_t mdac_vnc; 27 | uint8_t mdac_rd; 28 | uint8_t msgid; 29 | } state[CONFIG_USB_PD_PORT_COUNT]; 30 | 31 | /* 32 | * Bring the FUSB302 out of reset after Hard Reset signaling. This will 33 | * automatically flush both the Rx and Tx FIFOs. 34 | */ 35 | void fusb302_pd_reset(int16_t port) 36 | { 37 | tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_PD_RESET); 38 | state[port].msgid = 0; 39 | } 40 | 41 | /* 42 | * Flush our Rx FIFO. To prevent packet framing issues, this function should 43 | * only be called when Rx is disabled. 44 | */ 45 | void fusb302_flush_rx_fifo(int16_t port) 46 | { 47 | tcpc_write(port, TCPC_REG_CONTROL1, 48 | state[port].control1 | TCPC_REG_CONTROL1_RX_FLUSH); 49 | } 50 | 51 | void fusb302_flush_tx_fifo(int16_t port) 52 | { 53 | int16_t reg; 54 | 55 | tcpc_read(port, TCPC_REG_CONTROL0, ®); 56 | reg |= TCPC_REG_CONTROL0_TX_FLUSH; 57 | tcpc_write(port, TCPC_REG_CONTROL0, reg); 58 | } 59 | 60 | void fusb302_auto_goodcrc_enable(int16_t port, int16_t enable) 61 | { 62 | int16_t reg; 63 | 64 | tcpc_read(port, TCPC_REG_SWITCHES1, ®); 65 | 66 | if (enable) 67 | reg |= TCPC_REG_SWITCHES1_AUTO_GCRC; 68 | else 69 | reg &= ~TCPC_REG_SWITCHES1_AUTO_GCRC; 70 | 71 | // Spec says these should be zero, default is bad 72 | reg &= ~(TCPC_REG_SWITCHES1_SPECREV0 | TCPC_REG_SWITCHES1_SPECREV1); 73 | 74 | tcpc_write(port, TCPC_REG_SWITCHES1, reg); 75 | } 76 | 77 | /* Convert BC LVL values (in FUSB302) to Type-C CC Voltage Status */ 78 | static int16_t convert_bc_lvl(int16_t port, int16_t bc_lvl) 79 | { 80 | /* assume OPEN unless one of the following conditions is true... */ 81 | int16_t ret = TYPEC_CC_VOLT_OPEN; 82 | 83 | if (state[port].pulling_up) { 84 | if (bc_lvl == 0x00) 85 | ret = TYPEC_CC_VOLT_RA; 86 | else if (bc_lvl < 0x3) 87 | ret = TYPEC_CC_VOLT_RD; 88 | } else { 89 | if (bc_lvl == 0x1) 90 | ret = TYPEC_CC_VOLT_SNK_DEF; 91 | else if (bc_lvl == 0x2) 92 | ret = TYPEC_CC_VOLT_SNK_1_5; 93 | else if (bc_lvl == 0x3) 94 | ret = TYPEC_CC_VOLT_SNK_3_0; 95 | } 96 | 97 | return ret; 98 | } 99 | 100 | static int16_t measure_cc_pin_source(int16_t port, int16_t cc_measure) 101 | { 102 | int16_t switches0_reg; 103 | int16_t reg; 104 | int16_t cc_lvl; 105 | 106 | /* Read status register */ 107 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 108 | /* Save current value */ 109 | switches0_reg = reg; 110 | /* Clear pull-up register settings and measure bits */ 111 | reg &= ~(TCPC_REG_SWITCHES0_MEAS_CC1 | TCPC_REG_SWITCHES0_MEAS_CC2 | 112 | TCPC_REG_SWITCHES0_CC1_PU_EN | TCPC_REG_SWITCHES0_CC2_PU_EN); 113 | /* Set desired pullup register bit */ 114 | if (cc_measure == TCPC_REG_SWITCHES0_MEAS_CC1) 115 | reg |= TCPC_REG_SWITCHES0_CC1_PU_EN; 116 | else 117 | reg |= TCPC_REG_SWITCHES0_CC2_PU_EN; 118 | /* Set CC measure bit */ 119 | reg |= cc_measure; 120 | 121 | /* Set measurement switch */ 122 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 123 | 124 | /* Set MDAC for Open vs Rd/Ra comparison */ 125 | tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_vnc); 126 | 127 | /* Wait on measurement */ 128 | platform_usleep(250); 129 | 130 | /* Read status register */ 131 | tcpc_read(port, TCPC_REG_STATUS0, ®); 132 | 133 | /* Assume open */ 134 | cc_lvl = TYPEC_CC_VOLT_OPEN; 135 | 136 | /* CC level is below the 'no connect' threshold (vOpen) */ 137 | if ((reg & TCPC_REG_STATUS0_COMP) == 0) { 138 | /* Set MDAC for Rd vs Ra comparison */ 139 | tcpc_write(port, TCPC_REG_MEASURE, state[port].mdac_rd); 140 | 141 | /* Wait on measurement */ 142 | platform_usleep(250); 143 | 144 | /* Read status register */ 145 | tcpc_read(port, TCPC_REG_STATUS0, ®); 146 | 147 | cc_lvl = (reg & TCPC_REG_STATUS0_COMP) ? TYPEC_CC_VOLT_RD 148 | : TYPEC_CC_VOLT_RA; 149 | } 150 | 151 | /* Restore SWITCHES0 register to its value prior */ 152 | tcpc_write(port, TCPC_REG_SWITCHES0, switches0_reg); 153 | 154 | return cc_lvl; 155 | } 156 | 157 | /* Determine cc pin state for source when in manual detect mode */ 158 | static void detect_cc_pin_source_manual(int16_t port, int16_t *cc1_lvl, int16_t *cc2_lvl) 159 | { 160 | int16_t cc1_measure = TCPC_REG_SWITCHES0_MEAS_CC1; 161 | int16_t cc2_measure = TCPC_REG_SWITCHES0_MEAS_CC2; 162 | 163 | if (state[port].vconn_enabled) { 164 | /* If VCONN enabled, measure cc_pin that matches polarity */ 165 | if (state[port].cc_polarity) 166 | *cc2_lvl = measure_cc_pin_source(port, cc2_measure); 167 | else 168 | *cc1_lvl = measure_cc_pin_source(port, cc1_measure); 169 | } else { 170 | /* If VCONN not enabled, measure both cc1 and cc2 */ 171 | *cc1_lvl = measure_cc_pin_source(port, cc1_measure); 172 | *cc2_lvl = measure_cc_pin_source(port, cc2_measure); 173 | } 174 | 175 | } 176 | 177 | /* Determine cc pin state for sink */ 178 | static void detect_cc_pin_sink(int16_t port, int16_t *cc1, int16_t *cc2) 179 | { 180 | int16_t reg; 181 | int16_t orig_meas_cc1; 182 | int16_t orig_meas_cc2; 183 | int16_t bc_lvl_cc1; 184 | int16_t bc_lvl_cc2; 185 | 186 | /* 187 | * Measure CC1 first. 188 | */ 189 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 190 | 191 | /* save original state to be returned to later... */ 192 | if (reg & TCPC_REG_SWITCHES0_MEAS_CC1) 193 | orig_meas_cc1 = 1; 194 | else 195 | orig_meas_cc1 = 0; 196 | 197 | if (reg & TCPC_REG_SWITCHES0_MEAS_CC2) 198 | orig_meas_cc2 = 1; 199 | else 200 | orig_meas_cc2 = 0; 201 | 202 | /* Disable CC2 measurement switch, enable CC1 measurement switch */ 203 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; 204 | reg |= TCPC_REG_SWITCHES0_MEAS_CC1; 205 | 206 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 207 | 208 | /* CC1 is now being measured by FUSB302. */ 209 | 210 | /* Wait on measurement */ 211 | platform_usleep(250); 212 | 213 | tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc1); 214 | 215 | /* mask away unwanted bits */ 216 | bc_lvl_cc1 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1); 217 | 218 | /* 219 | * Measure CC2 next. 220 | */ 221 | 222 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 223 | 224 | /* Disable CC1 measurement switch, enable CC2 measurement switch */ 225 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; 226 | reg |= TCPC_REG_SWITCHES0_MEAS_CC2; 227 | 228 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 229 | 230 | /* CC2 is now being measured by FUSB302. */ 231 | 232 | /* Wait on measurement */ 233 | platform_usleep(250); 234 | 235 | tcpc_read(port, TCPC_REG_STATUS0, &bc_lvl_cc2); 236 | 237 | /* mask away unwanted bits */ 238 | bc_lvl_cc2 &= (TCPC_REG_STATUS0_BC_LVL0 | TCPC_REG_STATUS0_BC_LVL1); 239 | 240 | *cc1 = convert_bc_lvl(port, bc_lvl_cc1); 241 | *cc2 = convert_bc_lvl(port, bc_lvl_cc2); 242 | 243 | /* return MEAS_CC1/2 switches to original state */ 244 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 245 | if (orig_meas_cc1) 246 | reg |= TCPC_REG_SWITCHES0_MEAS_CC1; 247 | else 248 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; 249 | if (orig_meas_cc2) 250 | reg |= TCPC_REG_SWITCHES0_MEAS_CC2; 251 | else 252 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; 253 | 254 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 255 | 256 | } 257 | 258 | /* Parse header bytes for the size of packet */ 259 | static int16_t get_num_bytes(uint16_t header) 260 | { 261 | int16_t rv; 262 | 263 | /* Grab the Number of Data Objects field. */ 264 | rv = PD_HEADER_CNT(header); 265 | 266 | /* Multiply by four to go from 32-bit words -> bytes */ 267 | rv *= 4; 268 | 269 | /* Plus 2 for header */ 270 | rv += 2; 271 | 272 | return rv; 273 | } 274 | 275 | static int16_t fusb302_send_message(int16_t port, uint16_t header, 276 | const uint32_t * data, uint8_t * buf, 277 | int16_t buf_pos) 278 | { 279 | int16_t rv; 280 | int16_t reg; 281 | int16_t len; 282 | 283 | len = get_num_bytes(header); 284 | 285 | /* 286 | * packsym tells the TXFIFO that the next X bytes are payload, 287 | * and should not be interpreted as special tokens. 288 | * The 5 LSBs represent X, the number of bytes. 289 | */ 290 | reg = fusb302_TKN_PACKSYM; 291 | reg |= (len & 0x1F); 292 | 293 | buf[buf_pos++] = reg; 294 | 295 | /* write in the header */ 296 | reg = header; 297 | buf[buf_pos++] = reg & 0xFF; 298 | 299 | reg >>= 8; 300 | buf[buf_pos++] = reg & 0xFF; 301 | 302 | /* header is done, subtract from length to make this for-loop simpler */ 303 | len -= 2; 304 | 305 | /* write data objects, if present */ 306 | memcpy(&buf[buf_pos], data, len); 307 | buf_pos += len; 308 | 309 | /* put in the CRC */ 310 | buf[buf_pos++] = fusb302_TKN_JAMCRC; 311 | 312 | /* put in EOP */ 313 | buf[buf_pos++] = fusb302_TKN_EOP; 314 | 315 | /* Turn transmitter off after sending message */ 316 | buf[buf_pos++] = fusb302_TKN_TXOFF; 317 | 318 | /* Start transmission */ 319 | reg = fusb302_TKN_TXON; 320 | buf[buf_pos++] = fusb302_TKN_TXON; 321 | 322 | /* burst write for speed! */ 323 | rv = tcpc_xfer(port, buf, buf_pos, 0, 0, I2C_XFER_SINGLE); 324 | 325 | return rv; 326 | } 327 | 328 | int16_t fusb302_tcpm_select_rp_value(int16_t port, int16_t rp) 329 | { 330 | int16_t reg; 331 | int16_t rv; 332 | uint8_t vnc, rd; 333 | 334 | rv = tcpc_read(port, TCPC_REG_CONTROL0, ®); 335 | if (rv) 336 | return rv; 337 | 338 | /* Set the current source for Rp value */ 339 | reg &= ~TCPC_REG_CONTROL0_HOST_CUR_MASK; 340 | switch (rp) { 341 | case TYPEC_RP_1A5: 342 | reg |= TCPC_REG_CONTROL0_HOST_CUR_1A5; 343 | vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_VNC_MV); 344 | rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_1_5_RD_THRESH_MV); 345 | break; 346 | case TYPEC_RP_3A0: 347 | reg |= TCPC_REG_CONTROL0_HOST_CUR_3A0; 348 | vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_VNC_MV); 349 | rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_3_0_RD_THRESH_MV); 350 | break; 351 | case TYPEC_RP_USB: 352 | default: 353 | reg |= TCPC_REG_CONTROL0_HOST_CUR_USB; 354 | vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV); 355 | rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV); 356 | } 357 | state[port].mdac_vnc = vnc; 358 | state[port].mdac_rd = rd; 359 | rv = tcpc_write(port, TCPC_REG_CONTROL0, reg); 360 | 361 | return rv; 362 | } 363 | 364 | int16_t fusb302_tcpm_init(int16_t port) 365 | { 366 | int16_t reg; 367 | 368 | /* set default */ 369 | state[port].cc_polarity = -1; 370 | 371 | /* set the voltage threshold for no connect detection (vOpen) */ 372 | state[port].mdac_vnc = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_VNC_MV); 373 | /* set the voltage threshold for Rd vs Ra detection */ 374 | state[port].mdac_rd = TCPC_REG_MEASURE_MDAC_MV(PD_SRC_DEF_RD_THRESH_MV); 375 | 376 | /* all other variables assumed to default to 0 */ 377 | 378 | /* Restore default settings */ 379 | tcpc_write(port, TCPC_REG_RESET, TCPC_REG_RESET_SW_RESET); 380 | 381 | tcpc_read(port, TCPC_REG_DEVICE_ID, ®); 382 | 383 | /* Turn on retries and set number of retries */ 384 | tcpc_read(port, TCPC_REG_CONTROL3, ®); 385 | reg |= TCPC_REG_CONTROL3_AUTO_RETRY; 386 | reg |= (PD_RETRY_COUNT & 0x3) << TCPC_REG_CONTROL3_N_RETRIES_POS; 387 | reg |= TCPC_REG_CONTROL3_SEND_HARDRESET; 388 | tcpc_write(port, TCPC_REG_CONTROL3, reg); 389 | 390 | /* Create interrupt masks */ 391 | reg = 0xFF; 392 | /* VBUS OK */ 393 | reg &= ~TCPC_REG_MASK_VBUSOK; 394 | /* CC level changes */ 395 | reg &= ~TCPC_REG_MASK_BC_LVL; 396 | /* collisions */ 397 | reg &= ~TCPC_REG_MASK_COLLISION; 398 | /* misc alert */ 399 | reg &= ~TCPC_REG_MASK_ALERT; 400 | /* packet received with correct CRC */ 401 | reg &= ~TCPC_REG_MASK_CRC_CHK; 402 | tcpc_write(port, TCPC_REG_MASK, reg); 403 | 404 | reg = 0xFF; 405 | /* when all pd message retries fail... */ 406 | reg &= ~TCPC_REG_MASKA_RETRYFAIL; 407 | /* when fusb302 send a hard reset. */ 408 | reg &= ~TCPC_REG_MASKA_HARDSENT; 409 | /* when fusb302 receives GoodCRC ack for a pd message */ 410 | reg &= ~TCPC_REG_MASKA_TX_SUCCESS; 411 | /* when fusb302 receives a hard reset */ 412 | reg &= ~TCPC_REG_MASKA_HARDRESET; 413 | tcpc_write(port, TCPC_REG_MASKA, reg); 414 | 415 | reg = 0xFF; 416 | /* when fusb302 sends GoodCRC to ack a pd message */ 417 | reg &= ~TCPC_REG_MASKB_GCRCSENT; 418 | tcpc_write(port, TCPC_REG_MASKB, reg); 419 | 420 | /* Interrupt Enable */ 421 | tcpc_read(port, TCPC_REG_CONTROL0, ®); 422 | reg &= ~TCPC_REG_CONTROL0_INT_MASK; 423 | tcpc_write(port, TCPC_REG_CONTROL0, reg); 424 | 425 | state[port].control1 = 426 | TCPC_REG_CONTROL1_RX_FLUSH | TCPC_REG_CONTROL1_ENSOP1DB | 427 | TCPC_REG_CONTROL1_ENSOP2DB; 428 | tcpc_write(port, TCPC_REG_CONTROL1, state[port].control1); 429 | 430 | fusb302_auto_goodcrc_enable(port, 0); 431 | 432 | /* Turn on the power! */ 433 | /* TODO: Reduce power consumption */ 434 | tcpc_write(port, TCPC_REG_POWER, TCPC_REG_POWER_PWR_ALL); 435 | 436 | return 0; 437 | } 438 | 439 | int16_t fusb302_tcpm_get_cc(int16_t port, int16_t *cc1, int16_t *cc2) 440 | { 441 | if (state[port].pulling_up) { 442 | /* Source mode? */ 443 | detect_cc_pin_source_manual(port, cc1, cc2); 444 | } else { 445 | /* Sink mode? */ 446 | detect_cc_pin_sink(port, cc1, cc2); 447 | } 448 | 449 | return 0; 450 | } 451 | 452 | int16_t fusb302_tcpm_set_cc(int16_t port, int16_t pull) 453 | { 454 | int16_t reg; 455 | 456 | /* NOTE: FUSB302 toggles a single pull-up between CC1 and CC2 */ 457 | /* NOTE: FUSB302 Does not support Ra. */ 458 | switch (pull) { 459 | case TYPEC_CC_RP: 460 | /* enable the pull-up we know to be necessary */ 461 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 462 | 463 | reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN | 464 | TCPC_REG_SWITCHES0_CC1_PU_EN | 465 | TCPC_REG_SWITCHES0_CC1_PD_EN | 466 | TCPC_REG_SWITCHES0_CC2_PD_EN | 467 | TCPC_REG_SWITCHES0_VCONN_CC1 | 468 | TCPC_REG_SWITCHES0_VCONN_CC2); 469 | 470 | reg |= TCPC_REG_SWITCHES0_CC1_PU_EN | 471 | TCPC_REG_SWITCHES0_CC2_PU_EN; 472 | 473 | if (state[port].vconn_enabled) 474 | reg |= state[port].cc_polarity ? 475 | TCPC_REG_SWITCHES0_VCONN_CC1 : 476 | TCPC_REG_SWITCHES0_VCONN_CC2; 477 | 478 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 479 | 480 | state[port].pulling_up = 1; 481 | break; 482 | case TYPEC_CC_RD: 483 | /* Enable UFP Mode */ 484 | 485 | /* turn off toggle */ 486 | tcpc_read(port, TCPC_REG_CONTROL2, ®); 487 | reg &= ~TCPC_REG_CONTROL2_TOGGLE; 488 | tcpc_write(port, TCPC_REG_CONTROL2, reg); 489 | 490 | /* enable pull-downs, disable pullups */ 491 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 492 | 493 | reg &= ~(TCPC_REG_SWITCHES0_CC2_PU_EN); 494 | reg &= ~(TCPC_REG_SWITCHES0_CC1_PU_EN); 495 | reg |= (TCPC_REG_SWITCHES0_CC1_PD_EN); 496 | reg |= (TCPC_REG_SWITCHES0_CC2_PD_EN); 497 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 498 | 499 | state[port].pulling_up = 0; 500 | break; 501 | case TYPEC_CC_OPEN: 502 | /* Disable toggling */ 503 | tcpc_read(port, TCPC_REG_CONTROL2, ®); 504 | reg &= ~TCPC_REG_CONTROL2_TOGGLE; 505 | tcpc_write(port, TCPC_REG_CONTROL2, reg); 506 | 507 | /* Ensure manual switches are opened */ 508 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 509 | reg &= ~TCPC_REG_SWITCHES0_CC1_PU_EN; 510 | reg &= ~TCPC_REG_SWITCHES0_CC2_PU_EN; 511 | reg &= ~TCPC_REG_SWITCHES0_CC1_PD_EN; 512 | reg &= ~TCPC_REG_SWITCHES0_CC2_PD_EN; 513 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 514 | 515 | state[port].pulling_up = 0; 516 | break; 517 | default: 518 | /* Unsupported... */ 519 | return EC_ERROR_UNIMPLEMENTED; 520 | } 521 | 522 | return 0; 523 | } 524 | 525 | int16_t fusb302_tcpm_set_polarity(int16_t port, int16_t polarity) 526 | { 527 | /* Port polarity : 0 => CC1 is CC line, 1 => CC2 is CC line */ 528 | int16_t reg; 529 | 530 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 531 | 532 | /* clear VCONN switch bits */ 533 | reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1; 534 | reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2; 535 | 536 | if (state[port].vconn_enabled) { 537 | /* set VCONN switch to be non-CC line */ 538 | if (polarity) { 539 | reg |= TCPC_REG_SWITCHES0_VCONN_CC1; 540 | reg &= ~TCPC_REG_SWITCHES0_CC1_PU_EN; 541 | } else { 542 | reg |= TCPC_REG_SWITCHES0_VCONN_CC2; 543 | reg &= ~TCPC_REG_SWITCHES0_CC2_PU_EN; 544 | } 545 | } 546 | 547 | /* clear meas_cc bits (RX line select) */ 548 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; 549 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; 550 | 551 | /* set rx polarity */ 552 | if (polarity) 553 | reg |= TCPC_REG_SWITCHES0_MEAS_CC2; 554 | else 555 | reg |= TCPC_REG_SWITCHES0_MEAS_CC1; 556 | 557 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 558 | 559 | tcpc_read(port, TCPC_REG_SWITCHES1, ®); 560 | 561 | /* clear tx_cc bits */ 562 | reg &= ~TCPC_REG_SWITCHES1_TXCC1_EN; 563 | reg &= ~TCPC_REG_SWITCHES1_TXCC2_EN; 564 | 565 | /* set tx polarity */ 566 | if (polarity) 567 | reg |= TCPC_REG_SWITCHES1_TXCC2_EN; 568 | else 569 | reg |= TCPC_REG_SWITCHES1_TXCC1_EN; 570 | 571 | tcpc_write(port, TCPC_REG_SWITCHES1, reg); 572 | 573 | /* Save the polarity for later */ 574 | state[port].cc_polarity = polarity; 575 | 576 | return 0; 577 | } 578 | 579 | int16_t fusb302_tcpm_set_msg_header(int16_t port, int16_t power_role, int16_t data_role) 580 | { 581 | int16_t reg; 582 | 583 | tcpc_read(port, TCPC_REG_SWITCHES1, ®); 584 | 585 | reg &= ~TCPC_REG_SWITCHES1_POWERROLE; 586 | reg &= ~TCPC_REG_SWITCHES1_DATAROLE; 587 | 588 | if (power_role) 589 | reg |= TCPC_REG_SWITCHES1_POWERROLE; 590 | if (data_role) 591 | reg |= TCPC_REG_SWITCHES1_DATAROLE; 592 | 593 | tcpc_write(port, TCPC_REG_SWITCHES1, reg); 594 | 595 | return 0; 596 | } 597 | 598 | int16_t fusb302_tcpm_set_rx_enable(int16_t port, int16_t enable) 599 | { 600 | int16_t reg; 601 | 602 | state[port].rx_enable = enable; 603 | 604 | /* Get current switch state */ 605 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 606 | 607 | /* Clear CC1/CC2 measure bits */ 608 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC1; 609 | reg &= ~TCPC_REG_SWITCHES0_MEAS_CC2; 610 | 611 | if (enable) { 612 | switch (state[port].cc_polarity) { 613 | /* if CC polarity hasnt been determined, can't enable */ 614 | case -1: 615 | return EC_ERROR_UNKNOWN; 616 | case 0: 617 | reg |= TCPC_REG_SWITCHES0_MEAS_CC1; 618 | break; 619 | case 1: 620 | reg |= TCPC_REG_SWITCHES0_MEAS_CC2; 621 | break; 622 | default: 623 | /* "shouldn't get here" */ 624 | return EC_ERROR_UNKNOWN; 625 | } 626 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 627 | 628 | /* Disable BC_LVL interrupt when enabling PD comm */ 629 | if (!tcpc_read(port, TCPC_REG_MASK, ®)) 630 | tcpc_write(port, TCPC_REG_MASK, 631 | reg | TCPC_REG_MASK_BC_LVL); 632 | 633 | /* flush rx fifo in case messages have been coming our way */ 634 | fusb302_flush_rx_fifo(port); 635 | 636 | } else { 637 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 638 | 639 | /* Enable BC_LVL interrupt when disabling PD comm */ 640 | if (!tcpc_read(port, TCPC_REG_MASK, ®)) 641 | tcpc_write(port, TCPC_REG_MASK, 642 | reg & ~TCPC_REG_MASK_BC_LVL); 643 | } 644 | 645 | fusb302_auto_goodcrc_enable(port, enable); 646 | 647 | return 0; 648 | } 649 | 650 | /* Return true if our Rx FIFO is empty */ 651 | int16_t fusb302_rx_fifo_is_empty(int16_t port) 652 | { 653 | int16_t reg, ret; 654 | 655 | ret = (!tcpc_read(port, TCPC_REG_STATUS1, ®)) && 656 | (reg & TCPC_REG_STATUS1_RX_EMPTY); 657 | 658 | return ret; 659 | } 660 | 661 | int16_t fusb302_tcpm_get_message(int16_t port, uint32_t * payload, int16_t *head, 662 | enum fusb302_rxfifo_tokens *sop) 663 | { 664 | /* 665 | * This is the buffer that will get the burst-read data 666 | * from the fusb302. 667 | * 668 | * It's re-used in a couple different spots, the worst of which 669 | * is the PD packet (not header) and CRC. 670 | * maximum size necessary = 28 + 4 = 32 671 | */ 672 | uint8_t buf[32]; 673 | int16_t rv, len; 674 | 675 | /* If our FIFO is empty then we have no packet */ 676 | if (fusb302_rx_fifo_is_empty(port)) 677 | return EC_ERROR_UNKNOWN; 678 | 679 | /* Read until we have a non-GoodCRC packet or an empty FIFO */ 680 | do { 681 | buf[0] = TCPC_REG_FIFOS; 682 | 683 | /* 684 | * PART 1 OF BURST READ: Write in register address. 685 | * Issue a START, no STOP. 686 | */ 687 | rv = tcpc_xfer(port, buf, 1, 0, 0, I2C_XFER_START); 688 | 689 | /* 690 | * PART 2 OF BURST READ: Read up to the header. 691 | * Issue a repeated START, no STOP. 692 | * only grab three bytes so we can get the header 693 | * and determine how many more bytes we need to read. 694 | * TODO: Check token to ensure valid packet. 695 | */ 696 | rv |= tcpc_xfer(port, 0, 0, buf, 3, I2C_XFER_START); 697 | 698 | /* Grab the header */ 699 | *sop = buf[0] & fusb302_TKN_SOP_MASK; 700 | *head = (buf[1] & 0xFF); 701 | *head |= ((buf[2] << 8) & 0xFF00); 702 | 703 | /* figure out packet length, subtract header bytes */ 704 | len = get_num_bytes(*head) - 2; 705 | 706 | /* 707 | * PART 3 OF BURST READ: Read everything else. 708 | * No START, but do issue a STOP at the end. 709 | * add 4 to len to read CRC out 710 | */ 711 | rv |= tcpc_xfer(port, 0, 0, buf, len + 4, I2C_XFER_STOP); 712 | 713 | } while (!rv && PACKET_IS_GOOD_CRC(*head) && 714 | !fusb302_rx_fifo_is_empty(port)); 715 | 716 | if (!rv) { 717 | /* Discard GoodCRC packets */ 718 | if (PACKET_IS_GOOD_CRC(*head)) 719 | rv = EC_ERROR_UNKNOWN; 720 | else 721 | memcpy(payload, buf, len); 722 | } 723 | 724 | /* 725 | * If our FIFO is non-empty then we may have a packet, we may get 726 | * fewer interrupts than packets due to interrupt latency. 727 | */ 728 | //if (!fusb302_rx_fifo_is_empty(port)) 729 | // task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_RX, 0); 730 | 731 | return rv; 732 | } 733 | 734 | int16_t fusb302_tcpm_transmit(int16_t port, enum tcpm_transmit_type type, 735 | uint16_t header, const uint32_t * data) 736 | { 737 | /* 738 | * this is the buffer that will be burst-written into the fusb302 739 | * maximum size necessary = 740 | * 1: FIFO register address 741 | * 4: SOP* tokens 742 | * 1: Token that signifies "next X bytes are not tokens" 743 | * 30: 2 for header and up to 7*4 = 28 for rest of message 744 | * 1: "Insert CRC" Token 745 | * 1: EOP Token 746 | * 1: "Turn transmitter off" token 747 | * 1: "Star Transmission" Command 748 | * - 749 | * 40: 40 bytes worst-case 750 | */ 751 | uint8_t buf[40]; 752 | int16_t buf_pos = 0; 753 | 754 | int16_t reg; 755 | 756 | /* Flush the TXFIFO */ 757 | fusb302_flush_tx_fifo(port); 758 | 759 | header |= state[port].msgid++ << 9; 760 | state[port].msgid &= 0x7; 761 | 762 | switch (type) { 763 | case TCPC_TX_SOP: 764 | 765 | /* put register address first for of burst tcpc write */ 766 | buf[buf_pos++] = TCPC_REG_FIFOS; 767 | 768 | /* Write the SOP Ordered Set into TX FIFO */ 769 | buf[buf_pos++] = fusb302_TKN_SYNC1; 770 | buf[buf_pos++] = fusb302_TKN_SYNC1; 771 | buf[buf_pos++] = fusb302_TKN_SYNC1; 772 | buf[buf_pos++] = fusb302_TKN_SYNC2; 773 | 774 | fusb302_send_message(port, header, data, buf, buf_pos); 775 | // wait for the GoodCRC to come back before we let the rest 776 | // of the code do stuff like change polarity and miss it 777 | platform_usleep(1200); 778 | return 0; 779 | case TCPC_TX_SOP_PRIME: 780 | 781 | /* put register address first for of burst tcpc write */ 782 | buf[buf_pos++] = TCPC_REG_FIFOS; 783 | 784 | /* Write the SOP Ordered Set into TX FIFO */ 785 | buf[buf_pos++] = fusb302_TKN_SYNC1; 786 | buf[buf_pos++] = fusb302_TKN_SYNC1; 787 | buf[buf_pos++] = fusb302_TKN_SYNC3; 788 | buf[buf_pos++] = fusb302_TKN_SYNC3; 789 | 790 | fusb302_send_message(port, header, data, buf, buf_pos); 791 | // wait for the GoodCRC to come back before we let the rest 792 | // of the code do stuff like change polarity and miss it 793 | platform_usleep(1200); 794 | return 0; 795 | case TCPC_TX_SOP_PRIME_PRIME: 796 | 797 | /* put register address first for of burst tcpc write */ 798 | buf[buf_pos++] = TCPC_REG_FIFOS; 799 | 800 | /* Write the SOP Ordered Set into TX FIFO */ 801 | buf[buf_pos++] = fusb302_TKN_SYNC1; 802 | buf[buf_pos++] = fusb302_TKN_SYNC3; 803 | buf[buf_pos++] = fusb302_TKN_SYNC1; 804 | buf[buf_pos++] = fusb302_TKN_SYNC3; 805 | 806 | fusb302_send_message(port, header, data, buf, buf_pos); 807 | // wait for the GoodCRC to come back before we let the rest 808 | // of the code do stuff like change polarity and miss it 809 | platform_usleep(1200); 810 | return 0; 811 | case TCPC_TX_SOP_DEBUG_PRIME: 812 | 813 | /* put register address first for of burst tcpc write */ 814 | buf[buf_pos++] = TCPC_REG_FIFOS; 815 | 816 | /* Write the SOP Ordered Set into TX FIFO */ 817 | buf[buf_pos++] = fusb302_TKN_SYNC1; 818 | buf[buf_pos++] = fusb302_TKN_RST2; 819 | buf[buf_pos++] = fusb302_TKN_RST2; 820 | buf[buf_pos++] = fusb302_TKN_SYNC3; 821 | 822 | fusb302_send_message(port, header, data, buf, buf_pos); 823 | // wait for the GoodCRC to come back before we let the rest 824 | // of the code do stuff like change polarity and miss it 825 | platform_usleep(1200); 826 | return 0; 827 | case TCPC_TX_SOP_DEBUG_PRIME_PRIME: 828 | 829 | /* put register address first for of burst tcpc write */ 830 | buf[buf_pos++] = TCPC_REG_FIFOS; 831 | 832 | /* Write the SOP Ordered Set into TX FIFO */ 833 | buf[buf_pos++] = fusb302_TKN_SYNC1; 834 | buf[buf_pos++] = fusb302_TKN_RST2; 835 | buf[buf_pos++] = fusb302_TKN_SYNC3; 836 | buf[buf_pos++] = fusb302_TKN_SYNC2; 837 | 838 | fusb302_send_message(port, header, data, buf, buf_pos); 839 | // wait for the GoodCRC to come back before we let the rest 840 | // of the code do stuff like change polarity and miss it 841 | platform_usleep(1200); 842 | return 0; 843 | case TCPC_TX_HARD_RESET: 844 | /* Simply hit the SEND_HARD_RESET bit */ 845 | tcpc_read(port, TCPC_REG_CONTROL3, ®); 846 | reg |= TCPC_REG_CONTROL3_SEND_HARDRESET; 847 | tcpc_write(port, TCPC_REG_CONTROL3, reg); 848 | 849 | break; 850 | case TCPC_TX_BIST_MODE_2: 851 | /* Hit the BIST_MODE2 bit and start TX */ 852 | tcpc_read(port, TCPC_REG_CONTROL1, ®); 853 | reg |= TCPC_REG_CONTROL1_BIST_MODE2; 854 | tcpc_write(port, TCPC_REG_CONTROL1, reg); 855 | 856 | tcpc_read(port, TCPC_REG_CONTROL0, ®); 857 | reg |= TCPC_REG_CONTROL0_TX_START; 858 | tcpc_write(port, TCPC_REG_CONTROL0, reg); 859 | 860 | //task_wait_event(PD_T_BIST_TRANSMIT); 861 | 862 | /* Clear BIST mode bit, TX_START is self-clearing */ 863 | tcpc_read(port, TCPC_REG_CONTROL1, ®); 864 | reg &= ~TCPC_REG_CONTROL1_BIST_MODE2; 865 | tcpc_write(port, TCPC_REG_CONTROL1, reg); 866 | 867 | break; 868 | default: 869 | return EC_ERROR_UNIMPLEMENTED; 870 | } 871 | 872 | return 0; 873 | } 874 | 875 | int16_t fusb302_tcpm_get_vbus_level(int16_t port) 876 | { 877 | int16_t reg; 878 | 879 | /* Read status register */ 880 | tcpc_read(port, TCPC_REG_STATUS0, ®); 881 | 882 | return (reg & TCPC_REG_STATUS0_VBUSOK) ? 1 : 0; 883 | } 884 | 885 | void fusb302_get_irq(int16_t port, int16_t *interrupt, int16_t *interrupta, int16_t *interruptb) 886 | { 887 | /* reading interrupt registers clears them */ 888 | 889 | tcpc_read(port, TCPC_REG_INTERRUPT, interrupt); 890 | tcpc_read(port, TCPC_REG_INTERRUPTA, interrupta); 891 | tcpc_read(port, TCPC_REG_INTERRUPTB, interruptb); 892 | 893 | #if 0 894 | /* 895 | * Ignore BC_LVL changes when transmitting / receiving PD, 896 | * since CC level will constantly change. 897 | */ 898 | if (state[port].rx_enable) 899 | interrupt &= ~TCPC_REG_INTERRUPT_BC_LVL; 900 | 901 | if (interrupt & TCPC_REG_INTERRUPT_BC_LVL) { 902 | /* CC Status change */ 903 | //task_set_event(PD_PORT_TO_TASK_ID(port), PD_EVENT_CC, 0); 904 | } 905 | 906 | if (interrupt & TCPC_REG_INTERRUPT_COLLISION) { 907 | /* packet sending collided */ 908 | pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); 909 | } 910 | 911 | /* GoodCRC was received, our FIFO is now non-empty */ 912 | if (interrupta & TCPC_REG_INTERRUPTA_TX_SUCCESS) { 913 | //task_set_event(PD_PORT_TO_TASK_ID(port), 914 | // PD_EVENT_RX, 0); 915 | 916 | pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); 917 | } 918 | 919 | if (interrupta & TCPC_REG_INTERRUPTA_RETRYFAIL) { 920 | /* all retries have failed to get a GoodCRC */ 921 | pd_transmit_complete(port, TCPC_TX_COMPLETE_FAILED); 922 | } 923 | 924 | if (interrupta & TCPC_REG_INTERRUPTA_HARDSENT) { 925 | /* hard reset has been sent */ 926 | 927 | /* bring FUSB302 out of reset */ 928 | fusb302_pd_reset(port); 929 | 930 | pd_transmit_complete(port, TCPC_TX_COMPLETE_SUCCESS); 931 | } 932 | 933 | if (interrupta & TCPC_REG_INTERRUPTA_HARDRESET) { 934 | /* hard reset has been received */ 935 | 936 | /* bring FUSB302 out of reset */ 937 | fusb302_pd_reset(port); 938 | 939 | pd_execute_hard_reset(port); 940 | 941 | //task_wake(PD_PORT_TO_TASK_ID(port)); 942 | } 943 | 944 | if (interruptb & TCPC_REG_INTERRUPTB_GCRCSENT) { 945 | /* Packet received and GoodCRC sent */ 946 | /* (this interrupt fires after the GoodCRC finishes) */ 947 | if (state[port].rx_enable) { 948 | //task_set_event(PD_PORT_TO_TASK_ID(port), 949 | // PD_EVENT_RX, 0); 950 | } else { 951 | /* flush rx fifo if rx isn't enabled */ 952 | fusb302_flush_rx_fifo(port); 953 | } 954 | } 955 | #endif 956 | } 957 | 958 | int16_t fusb302_tcpm_set_vconn(int16_t port, int16_t enable) 959 | { 960 | /* 961 | * FUSB302 does not have dedicated VCONN Enable switch. 962 | * We'll get through this by disabling both of the 963 | * VCONN - CC* switches to disable, and enabling the 964 | * saved polarity when enabling. 965 | * Therefore at startup, set_polarity should be called first, 966 | * or else live with the default put into init. 967 | */ 968 | int16_t reg; 969 | 970 | /* save enable state for later use */ 971 | state[port].vconn_enabled = enable; 972 | 973 | if (enable) { 974 | /* set to saved polarity */ 975 | fusb302_tcpm_set_polarity(port, state[port].cc_polarity); 976 | } else { 977 | 978 | tcpc_read(port, TCPC_REG_SWITCHES0, ®); 979 | 980 | /* clear VCONN switch bits */ 981 | reg &= ~TCPC_REG_SWITCHES0_VCONN_CC1; 982 | reg &= ~TCPC_REG_SWITCHES0_VCONN_CC2; 983 | 984 | tcpc_write(port, TCPC_REG_SWITCHES0, reg); 985 | } 986 | 987 | return 0; 988 | } 989 | 990 | #if 0 991 | /* For BIST receiving */ 992 | void tcpm_set_bist_test_data(int16_t port) 993 | { 994 | int16_t reg; 995 | 996 | /* Read control3 register */ 997 | tcpc_read(port, TCPC_REG_CONTROL3, ®); 998 | 999 | /* Set the BIST_TMODE bit (Clears on Hard Reset) */ 1000 | reg |= TCPC_REG_CONTROL3_BIST_TMODE; 1001 | 1002 | /* Write the updated value */ 1003 | tcpc_write(port, TCPC_REG_CONTROL3, reg); 1004 | } 1005 | #endif 1006 | --------------------------------------------------------------------------------