From 4db5132a686428dd63571aea5d8fce01d4dbbb08 Mon Sep 17 00:00:00 2001 From: "igor.masar" Date: Wed, 8 Oct 2025 18:53:06 +0200 Subject: [PATCH] feat(type_c,pd): add public APIs for Type-C (HUSB320) and PD (FUSB302) Introduce initial, beta public headers aligned with esp-idf/esp-usb style: - Type-C core (CC-only): attach/detach/orientation & power-role control - File: esp-usb/type_c/include/esp_typec.h - Install/uninstall + library info - Events: ATTACHED/DETACHED/ORIENTATION/PWR_ROLE/ERROR - Factory: esp_typec_port_create_husb320() (HUSB320; CC-only) - APIs: esp_typec_set_power_role(), esp_typec_get_orientation(), esp_typec_is_attached(), esp_typec_port_destroy() - Power Delivery (PD-only): runtime role control and sink requests - File: esp-usb/type_c/include/esp_typec_pd.h - Install/uninstall + optional polling handler + library info - Events: ATTACHED/DETACHED/CONTRACT_READY/CONTRACT_LOST/PPS_ADJUSTED/ERROR - Factory: esp_typec_pd_port_create_fusb302() (FUSB302; PD-capable) - APIs: esp_typec_pd_set_power_role(), esp_typec_pd_sink_request_fixed(), esp_typec_pd_sink_request_pps(), esp_typec_pd_get_contract(), esp_typec_pd_get_orientation(), esp_typec_pd_is_attached(), esp_typec_pd_port_destroy() Decisions: - Split CC-only and PD APIs to keep modules independent and build-light. - Reused event naming across CC and PD for consistency; separate enums to avoid coupling. - Only include HUSB320 (CC-only) and FUSB302 (PD-capable) factories for now. Notes: - APIs are intentionally minimal; more controls (source caps, swaps, DRP policies) can be added without breaking the current surface. --- .github/workflows/upload_component.yml | 1 + type_c/CHANGELOG.md | 5 + type_c/CMakeLists.txt | 7 + type_c/LICENSE | 202 +++++++ type_c/README.md | 133 +++++ .../examples/pd_fusb302_attach/CMakeLists.txt | 5 + .../pd_fusb302_attach/main/CMakeLists.txt | 5 + type_c/examples/pd_fusb302_attach/main/main.c | 104 ++++ .../pd_fusb302_attach/sdkconfig.defaults | 1 + type_c/idf_component.yml | 23 + type_c/include/esp_typec.h | 209 +++++++ type_c/include/esp_typec_pd.h | 204 +++++++ type_c/include_private/fusb302_ctrl.h | 93 ++++ type_c/include_private/husb320_ctrl.h | 86 +++ type_c/include_private/typec_backend.h | 35 ++ type_c/src/esp_typec.c | 251 +++++++++ type_c/src/esp_typec_pd.c | 438 +++++++++++++++ type_c/src/fusb302_ctrl.c | 515 ++++++++++++++++++ type_c/src/husb320_ctrl.c | 68 +++ type_c_manager/CHANGELOG.md | 5 + type_c_manager/CMakeLists.txt | 10 + type_c_manager/LICENSE | 202 +++++++ type_c_manager/README.md | 133 +++++ .../pd_fusb302_msc_device/CMakeLists.txt | 22 + .../pd_fusb302_msc_device/main/CMakeLists.txt | 12 + .../pd_fusb302_msc_device/main/main.c | 251 +++++++++ .../pd_fusb302_msc_device/partitions.csv | 6 + .../pd_fusb302_msc_device/sdkconfig.defaults | 16 + type_c_manager/idf_component.yml | 23 + type_c_manager/include/esp_typec_manager.h | 79 +++ type_c_manager/src/esp_typec_manager.c | 126 +++++ 31 files changed, 3270 insertions(+) create mode 100644 type_c/CHANGELOG.md create mode 100644 type_c/CMakeLists.txt create mode 100644 type_c/LICENSE create mode 100644 type_c/README.md create mode 100644 type_c/examples/pd_fusb302_attach/CMakeLists.txt create mode 100644 type_c/examples/pd_fusb302_attach/main/CMakeLists.txt create mode 100644 type_c/examples/pd_fusb302_attach/main/main.c create mode 100644 type_c/examples/pd_fusb302_attach/sdkconfig.defaults create mode 100644 type_c/idf_component.yml create mode 100644 type_c/include/esp_typec.h create mode 100644 type_c/include/esp_typec_pd.h create mode 100644 type_c/include_private/fusb302_ctrl.h create mode 100644 type_c/include_private/husb320_ctrl.h create mode 100644 type_c/include_private/typec_backend.h create mode 100644 type_c/src/esp_typec.c create mode 100644 type_c/src/esp_typec_pd.c create mode 100644 type_c/src/fusb302_ctrl.c create mode 100644 type_c/src/husb320_ctrl.c create mode 100644 type_c_manager/CHANGELOG.md create mode 100644 type_c_manager/CMakeLists.txt create mode 100644 type_c_manager/LICENSE create mode 100644 type_c_manager/README.md create mode 100644 type_c_manager/examples/pd_fusb302_msc_device/CMakeLists.txt create mode 100644 type_c_manager/examples/pd_fusb302_msc_device/main/CMakeLists.txt create mode 100644 type_c_manager/examples/pd_fusb302_msc_device/main/main.c create mode 100644 type_c_manager/examples/pd_fusb302_msc_device/partitions.csv create mode 100644 type_c_manager/examples/pd_fusb302_msc_device/sdkconfig.defaults create mode 100644 type_c_manager/idf_component.yml create mode 100644 type_c_manager/include/esp_typec_manager.h create mode 100644 type_c_manager/src/esp_typec_manager.c diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index f7667193..73bbaf64 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -44,6 +44,7 @@ jobs: host/class/uac/usb_host_uac host/class/uvc/usb_host_uvc host/usb + type_c namespace: "espressif" # API token will only be available in the master branch in the main repository. # However, dry-run doesn't require a valid token. diff --git a/type_c/CHANGELOG.md b/type_c/CHANGELOG.md new file mode 100644 index 00000000..382b9bf1 --- /dev/null +++ b/type_c/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog for USB type C/PD library + +## 0.1.0-beta + +- Initial version diff --git a/type_c/CMakeLists.txt b/type_c/CMakeLists.txt new file mode 100644 index 00000000..e26b7fc4 --- /dev/null +++ b/type_c/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "src/esp_typec.c" "src/esp_typec_pd.c" "src/fusb302_ctrl.c" + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "include_private" + PRIV_REQUIRES esp_driver_gpio esp_driver_i2c + REQUIRES freertos esp_event +) diff --git a/type_c/LICENSE b/type_c/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/type_c/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/type_c/README.md b/type_c/README.md new file mode 100644 index 00000000..9f784543 --- /dev/null +++ b/type_c/README.md @@ -0,0 +1,133 @@ +# ESP Type-C / USB Power Delivery (esp-usb/type_c) + +> **Status:** Beta API — names and behavior may change before v1.0. + +This component provides two focused, minimal public APIs for USB Type-C: + +- **Type-C Core (CC-only)** — attach/detach, orientation (CC1/CC2), and power-role presence (Rp/Rd). + Backed by **HUSB320** (Type-C CC controller; **no PD**). +- **USB Power Delivery (PD-only)** — PD negotiation, runtime power-role control, fixed/PPS sink requests, and contract reporting. + Backed by **FUSB302** (PD-capable TCPC). + +The split keeps builds small, avoids unnecessary coupling, and mirrors esp-usb / esp-idf style. + +--- + +## Contents + +- [`include/esp_typec.h`](./include/esp_typec.h) — **Type-C Core (CC-only)** public API +- [`include/esp_typec_pd.h`](./include/esp_typec_pd.h) — **PD-only** public API +- `src/` — stub sources to make the component build; real backends land incrementally +- `CMakeLists.txt`, `idf_component.yml` — component metadata + +> New files use: +> `/* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD */` +> `/* SPDX-License-Identifier: Apache-2.0 */` + +--- + +## Supported Controllers + +| Controller | Capability | API / Factory Function | Notes | +|-----------:|------------|-----------------------------------------------------------|--------------------------------| +| HUSB320 | Type-C CC | `esp_typec_port_create_husb320()` (in `esp_typec.h`) | CC attach/orientation only | +| FUSB302 | PD (TCPC) | `esp_typec_pd_port_create_fusb302()` (in `esp_typec_pd.h`)| Full PD PHY over CC (BMC) | + +> **Not supported here:** CC-only parts for PD; they cannot negotiate PD by design. +> Additional PD-capable TCPCs (e.g., PTN5110/TCPM family) can be added later using the same factory pattern. + +--- + +## Quick Start + +### 1) Type-C Core (CC-only) with HUSB320 + +```c +#include "esp_typec.h" + +static void on_typec(esp_typec_event_t evt, const void *payload, void *arg) +{ + switch (evt) { + case ESP_TYPEC_EVENT_ATTACHED: { + const esp_typec_evt_attached_t *a = payload; + // a->flags & ESP_TYPEC_FLAG_CC2 -> polarity + // a->rp_cur_ma -> advertised current (if known) + break; + } + case ESP_TYPEC_EVENT_ORIENTATION: { + const bool *cc2_active = payload; + (void)cc2_active; + break; + } + case ESP_TYPEC_EVENT_DETACHED: + default: break; + } +} + +void app_main(void) +{ + ESP_ERROR_CHECK(esp_typec_install()); + + esp_typec_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PWR_SINK, + .try_snk = true, + .try_src = false, + .task_stack = 0, + .task_prio = 0, + }; + esp_typec_husb320_config_t hw = { + .i2c_port = 0, .i2c_addr = 0x60, .gpio_int = 4, .use_intr = true, + }; + esp_typec_port_handle_t h; + ESP_ERROR_CHECK(esp_typec_port_create_husb320(&port_cfg, &hw, on_typec, NULL, &h)); + + // Optional: request a role change (sets Rp/Rd) + ESP_ERROR_CHECK(esp_typec_set_power_role(h, ESP_TYPEC_PWR_DRP)); +} +``` + +### 1) Power Delivery (PD) with FUSB302 + +```c +#include "esp_typec_pd.h" + +static void on_pd(esp_typec_pd_event_t evt, const void *payload, void *arg) +{ + switch (evt) { + case ESP_TYPEC_PD_EVENT_CONTRACT_READY: { + const esp_typec_pd_contract_t *c = payload; + // c->mv, c->ma, c->selected_pdo, c->flags (PPS/CC2) + break; + } + case ESP_TYPEC_PD_EVENT_ATTACHED: + case ESP_TYPEC_PD_EVENT_DETACHED: + default: break; + } +} + +void app_main(void) +{ + esp_typec_pd_install_config_t lib_cfg = { + .intr_flags = 0, + }; + ESP_ERROR_CHECK(esp_typec_pd_install(&lib_cfg)); + + esp_typec_pd_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PD_PWR_SINK, + .sink_i_min_ma = 500, + .sink_fixed_pref_mv = 9000, // 0 = auto select best + .enable_pps = true, + .sink_pps_v_min_mv = 5000, .sink_pps_v_max_mv = 11000, .sink_pps_i_max_ma = 2000, + .src_pdos = NULL, .src_pdo_count = 0, + .task_stack = 0, .task_prio = 0, + }; + esp_typec_pd_fusb302_config_t hw = { + .i2c_port = 0, .i2c_addr = 0x22, .gpio_int = 5, .use_intr = true, + }; + esp_typec_pd_port_handle_t h; + ESP_ERROR_CHECK(esp_typec_pd_port_create_fusb302(&port_cfg, &hw, on_pd, NULL, &h)); + + // Example: request a specific Fixed PDO after Source_Capabilities are received + // ESP_ERROR_CHECK(esp_typec_pd_sink_request_fixed(h, 9000, 2000)); +} +``` diff --git a/type_c/examples/pd_fusb302_attach/CMakeLists.txt b/type_c/examples/pd_fusb302_attach/CMakeLists.txt new file mode 100644 index 00000000..865ef161 --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.16) +set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pd_fusb302_attach) diff --git a/type_c/examples/pd_fusb302_attach/main/CMakeLists.txt b/type_c/examples/pd_fusb302_attach/main/CMakeLists.txt new file mode 100644 index 00000000..44be0898 --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "main.c" + PRIV_REQUIRES esp_driver_i2c esp_driver_gpio + REQUIRES type_c +) diff --git a/type_c/examples/pd_fusb302_attach/main/main.c b/type_c/examples/pd_fusb302_attach/main/main.c new file mode 100644 index 00000000..633b070b --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/main/main.c @@ -0,0 +1,104 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "esp_typec_pd.h" + +static const char *TAG = "example_pd"; + +/* ===== Event callback ===== */ +static void on_pd_event(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data) +{ + (void)handler_arg; + (void)base; + + switch (id) { + case ESP_TYPEC_PD_EVENT_ID_ATTACHED: { + const esp_typec_pd_evt_attached_t *a = (const esp_typec_pd_evt_attached_t *)event_data; + bool cc2 = a ? a->cc2_active : false; + uint32_t ma = a ? a->rp_cur_ma : 0; + ESP_LOGI(TAG, "ATTACHED: cc2=%d, rp=%u mA", cc2 ? 1 : 0, (unsigned)ma); + break; + } + case ESP_TYPEC_PD_EVENT_ID_DETACHED: + ESP_LOGI(TAG, "DETACHED"); + break; + case ESP_TYPEC_PD_EVENT_ID_ERROR: + default: + ESP_LOGW(TAG, "EVENT %d", (int)id); + break; + } +} + +/* ===== App entry ===== */ +void app_main(void) +{ + ESP_LOGI("main_task", "Calling app_main()"); + ESP_ERROR_CHECK(esp_typec_pd_install(NULL)); + /* --- I2C master bus (new driver) --- */ + const int I2C_SDA = 7; + const int I2C_SCL = 8; + const int INT_GPIO = 9; + const uint8_t FUSB_ADDR_7B = 0x22; + + i2c_master_bus_handle_t bus = NULL; + i2c_master_dev_handle_t fusb_i2c = NULL; + + i2c_master_bus_config_t bus_cfg = { + .i2c_port = 0, /* use I2C0 */ + .sda_io_num = I2C_SDA, + .scl_io_num = I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags = { .enable_internal_pullup = 1 }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &bus)); + + i2c_device_config_t dev_cfg = { + .device_address = FUSB_ADDR_7B, + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .scl_speed_hz = 400000, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(bus, &dev_cfg, &fusb_i2c)); + + /* --- Create PD port (FUSB302 backend) --- */ + esp_typec_pd_port_handle_t port = NULL; + + esp_typec_pd_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PWR_SINK, + .task_stack = 4096, + .task_prio = 5, + .src_vbus_gpio = 28, + }; + + esp_typec_pd_fusb302_config_t hw = { + .i2c_dev = fusb_i2c, + .gpio_int = INT_GPIO, + }; + + ESP_ERROR_CHECK(esp_typec_pd_port_create_fusb302(&port_cfg, &hw, &port)); + //ESP_ERROR_CHECK(esp_typec_pd_set_power_role(port, ESP_TYPEC_PWR_SOURCE)); + ESP_ERROR_CHECK(esp_event_handler_register( + ESP_TYPEC_PD_EVENT, + ESP_EVENT_ANY_ID, + &on_pd_event, NULL)); + ESP_LOGI(TAG, "Waiting for attach/detach events..."); + + /* App can idle; the PD task & ISR do the work */ + while (true) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + /* (Optional cleanup if you ever leave the loop) + esp_typec_pd_port_destroy(port); + i2c_master_bus_rm_device(fusb_i2c); + i2c_del_master_bus(bus); + */ +} diff --git a/type_c/examples/pd_fusb302_attach/sdkconfig.defaults b/type_c/examples/pd_fusb302_attach/sdkconfig.defaults new file mode 100644 index 00000000..5493c2e1 --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_LOG_DEFAULT_LEVEL=3 diff --git a/type_c/idf_component.yml b/type_c/idf_component.yml new file mode 100644 index 00000000..34f2a6db --- /dev/null +++ b/type_c/idf_component.yml @@ -0,0 +1,23 @@ +## IDF Component Manager Manifest File + +description: ESP-IDF USB Type-C/PD stack (beta) +version: "0.1.0-beta" +url: https://github.com/espressif/esp-usb/tree/master/type_c +issues: "https://github.com/espressif/esp-usb/issues" +repository: "https://github.com/espressif/esp-usb.git" +repository_info: + path: "type_c" +dependencies: + idf: ">=5.4" +targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 +files: + exclude: + - "test/**/*" +tags: + - usb + - usb_typec + - usb_pd diff --git a/type_c/include/esp_typec.h b/type_c/include/esp_typec.h new file mode 100644 index 00000000..e522cea3 --- /dev/null +++ b/type_c/include/esp_typec.h @@ -0,0 +1,209 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* +Warning: The Type‑C Core API is an initial (beta) version and may change. +This header covers **Type‑C CC (no PD)** features and a factory for **HUSB320**. +*/ + +#pragma once + +#include +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------------------------------------- Macros and Types -------------------------------------------------- + + +/** + * @brief Monotonic API version so apps can assert compatibility at compile-time. + */ +#define ESP_TYPEC_API_VERSION 0x00000100u /* v0.1.0-beta */ + +// ----------------------- Handles ------------------------- + + +/** + * @brief Handle to a Type‑C port instance (CC controller). + */ +typedef struct esp_typec_port_s *esp_typec_port_handle_t; + +// ------------------------ Events ------------------------- + + +/** + * @brief Type‑C event types. + */ +typedef enum { + ESP_TYPEC_EVENT_ATTACHED, /**< CC attach detected. payload: esp_typec_evt_attached_t */ + ESP_TYPEC_EVENT_DETACHED, /**< CC detach detected. no payload */ + ESP_TYPEC_EVENT_ORIENTATION, /**< Polarity changed. payload: bool (true=CC2) */ + ESP_TYPEC_EVENT_PWR_ROLE, /**< Power role changed. payload: esp_typec_power_role_t */ + ESP_TYPEC_EVENT_ERROR, /**< Error occurred. payload: esp_typec_evt_error_t */ +} esp_typec_event_t; + +/** + * @brief Optional flags for event payloads. + */ +#define ESP_TYPEC_FLAG_CC2 (1u << 0) /**< Polarity: CC2 active */ + +/** + * @brief Type‑C power role. + */ +typedef enum { + ESP_TYPEC_PWR_SINK = 0, /**< Sink role */ + ESP_TYPEC_PWR_SOURCE, /**< Source role */ + ESP_TYPEC_PWR_DRP, /**< Dual-role power */ +} esp_typec_power_role_t; + + +/** + * @brief Type‑C error codes. + */ +typedef enum { + ESP_TYPEC_ERR_NONE = 0, /**< No error */ + ESP_TYPEC_ERR_HW_COMM, /**< Hardware communication error */ + ESP_TYPEC_ERR_TIMEOUT, /**< Timeout */ + ESP_TYPEC_ERR_STATE, /**< Invalid state */ +} esp_typec_error_t; + + +/** + * @brief Payload for ESP_TYPEC_EVENT_ATTACHED event. + */ +typedef struct { + uint32_t flags; /**< ESP_TYPEC_FLAG_* */ + uint32_t rp_cur_ma; /**< Advertised Rp current in mA, 0 if unknown */ +} esp_typec_evt_attached_t; + + +/** + * @brief Payload for ESP_TYPEC_EVENT_ERROR event. + */ +typedef struct { + esp_typec_error_t code; /**< Error category */ + int detail; /**< Backend-specific minor code */ +} esp_typec_evt_error_t; + + +/** + * @brief Type‑C event callback. + * + * @param event Event type + * @param payload Event payload (valid only during callback) + * @param arg User argument + */ +typedef void (*esp_typec_event_cb_t)(esp_typec_event_t event, const void *payload, void *arg); + +// ---------------------- Configurations ------------------- + + +/** + * @brief Port creation configuration (Type‑C CC settings). + */ +typedef struct { + esp_typec_power_role_t default_power_role; /**< Startup default; runtime changes allowed */ + bool try_snk; /**< DRP Try.SNK policy (if supported by controller) */ + bool try_src; /**< DRP Try.SRC policy (if supported by controller) */ + size_t task_stack; /**< Optional task stack (0 = default) */ + int task_prio; /**< Optional task priority (0 = default) */ +} esp_typec_port_config_t; + +// -------------------------- Info ------------------------- + +/** + * @brief Type-C library info structure. + */ +typedef struct { + int num_ports; /**< Number of created ports */ +} esp_typec_lib_info_t; + +// ------------------------------------------------ Library Functions -------------------------------------------------- + +/** + * @brief Install and initialize the Type-C library. + * @return ESP_OK on success + */ +esp_err_t esp_typec_install(void); + +/** + * @brief Uninstall and deinitialize the Type-C library. + * @return ESP_OK on success + */ +esp_err_t esp_typec_uninstall(void); + +/** + * @brief Get Type-C library info (number of ports, etc). + * @param info_ret Pointer to info struct to fill + * @return ESP_OK on success + */ +esp_err_t esp_typec_lib_info(esp_typec_lib_info_t *info_ret); + +// ------------------------------------------------- Port Functions (HUSB320) ----------------------------------------- + +/** + * @brief Hardware configuration for HUSB320 (CC-only, no PD). + */ +typedef struct { + int i2c_port; /**< I2C controller port */ + uint8_t i2c_addr; /**< 7-bit I2C address */ + int gpio_int; /**< INT/ATTACH GPIO */ + bool use_intr; /**< If false, backend may poll (debug) */ +} esp_typec_husb320_config_t; + +/** + * @brief Create a Type‑C CC-only port using HUSB320 (no PD). + * @param port_cfg Port configuration + * @param hw_cfg Hardware configuration for HUSB320 + * @param cb Event callback + * @param cb_arg User callback argument + * @param port_hdl_ret Pointer to port handle output + * @return ESP_OK on success + */ +esp_err_t esp_typec_port_create_husb320(const esp_typec_port_config_t *port_cfg, + const esp_typec_husb320_config_t *hw_cfg, + esp_typec_event_cb_t cb, void *cb_arg, + esp_typec_port_handle_t *port_hdl_ret); + +/** + * @brief Destroy a Type‑C port. + * @param port_hdl Port handle to destroy + * @return ESP_OK on success + */ +esp_err_t esp_typec_port_destroy(esp_typec_port_handle_t port_hdl); + +/** + * @brief Power-role control (CC Rp/Rd). + * @param port_hdl Port handle + * @param role Power role to set + * @return ESP_OK on success + */ +esp_err_t esp_typec_set_power_role(esp_typec_port_handle_t port_hdl, esp_typec_power_role_t role); + +/** + * @brief Get cable orientation (CC2 active) for a Type‑C port. + * @param port_hdl Port handle + * @param cc2_active Pointer to bool output + * @return ESP_OK if attached, ESP_ERR_INVALID_STATE if not + */ +esp_err_t esp_typec_get_orientation(esp_typec_port_handle_t port_hdl, bool *cc2_active); + +/** + * @brief Get attached state for a Type‑C port. + * @param port_hdl Port handle + * @param attached Pointer to bool output + * @return ESP_OK on success + */ +esp_err_t esp_typec_is_attached(esp_typec_port_handle_t port_hdl, bool *attached); + +#ifdef __cplusplus +} +#endif diff --git a/type_c/include/esp_typec_pd.h b/type_c/include/esp_typec_pd.h new file mode 100644 index 00000000..9c6859f9 --- /dev/null +++ b/type_c/include/esp_typec_pd.h @@ -0,0 +1,204 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/i2c_master.h" // for i2c_master_dev_handle_t +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ===== Public enums / flags ===== */ + + +#include "esp_typec.h" // for esp_typec_power_role_t and ESP_TYPEC_FLAG_CC2 + +#define ESP_TYPEC_PD_FLAG_CC2 ESP_TYPEC_FLAG_CC2 + +/** + * @brief Event base for Type-C PD events. + * + * Events are posted with: + * - base: ESP_TYPEC_PD_EVENT + * - id: esp_typec_pd_event_id_t + */ +ESP_EVENT_DECLARE_BASE(ESP_TYPEC_PD_EVENT); + +/** + * @brief PD event IDs. + */ +typedef enum { + ESP_TYPEC_PD_EVENT_ID_ATTACHED = 0, /**< payload: esp_typec_pd_evt_attached_t */ + ESP_TYPEC_PD_EVENT_ID_DETACHED, /**< payload: NULL */ + ESP_TYPEC_PD_EVENT_ID_ERROR, /**< payload: esp_typec_pd_error_t (TBD) */ +} esp_typec_pd_event_id_t; + +/** + * @brief Advertised Rp current levels when acting as Source/DFP. + */ +typedef enum { + ESP_TYPEC_RP_DEFAULT = 0, /**< Default USB current (~500 mA) */ + ESP_TYPEC_RP_1A5, /**< 1.5 A advertised current */ + ESP_TYPEC_RP_3A0, /**< 3.0 A advertised current */ +} esp_typec_rp_current_t; + + +/** + * @brief PD library install configuration (reserved for future use). + * @note Currently no global tunables; reserved for future expansion. + */ +typedef struct { + uint32_t reserved; /**< Reserved for future use */ +} esp_typec_pd_install_config_t; + +/** + * @brief PD library info structure. + */ +typedef struct { + uint32_t num_ports; /**< Number of created PD ports */ +} esp_typec_pd_lib_info_t; + +/** + * @brief PD port configuration structure. + */ +typedef struct { + esp_typec_power_role_t default_power_role; /**< Default role at bring-up */ + uint32_t task_stack; /**< Task stack size in bytes (0 = default) */ + uint32_t task_prio; /**< Task priority (0 = default) */ + esp_typec_rp_current_t rp_current; /**< Advertised Rp current when acting as Source/DFP */ + gpio_num_t src_vbus_gpio; /**< GPIO_NUM_NC if not used */ +} esp_typec_pd_port_config_t; + +/** + * @brief Hardware configuration for FUSB302 backend. + */ +typedef struct { + i2c_master_dev_handle_t i2c_dev; /**< I2C device handle */ + int gpio_int; /**< Active-low INT GPIO from the chip */ +} esp_typec_pd_fusb302_config_t; + +/** + * @brief Opaque handle for a PD port instance. + */ +typedef void *esp_typec_pd_port_handle_t; + +/** + * @brief Event payload for PD ATTACHED event. + */ +typedef struct { + esp_typec_pd_port_handle_t port; /**< Port that generated the event */ + bool cc2_active; /**< true if CC2 is active, false = CC1 */ + uint32_t rp_cur_ma; /**< 0/500/1500/3000 mapped from BC_LVL */ +} esp_typec_pd_evt_attached_t; + +/** + * @brief Placeholder for future PD contract details. + */ +typedef struct { + uint32_t mv; /**< Millivolts */ + uint32_t ma; /**< Milliamps */ +} esp_typec_pd_contract_t; + +/** + * @brief Install and initialize the PD library. + * @param config Optional install configuration + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_install(const esp_typec_pd_install_config_t *config); + +/** + * @brief Uninstall and deinitialize the PD library. + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_uninstall(void); + +/** + * @brief Get PD library info (number of ports, etc). + * @param info_ret Pointer to info struct to fill + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_lib_info(esp_typec_pd_lib_info_t *info_ret); + + + +/** + * @brief Create a PD port backed by FUSB302 (I2C + INT). + * events go via esp_event + * @param port_cfg Port configuration + * @param hw_cfg Hardware configuration for FUSB302 + * @param port_hdl_ret Pointer to port handle output + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_port_create_fusb302(const esp_typec_pd_port_config_t *port_cfg, + const esp_typec_pd_fusb302_config_t *hw_cfg, + esp_typec_pd_port_handle_t *port_hdl_ret); + + +/** + * @brief Destroy a PD port instance and free resources. + * @param port_hdl Port handle to destroy + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_port_destroy(esp_typec_pd_port_handle_t port_hdl); + +/** + * @brief Set power role (sink/source/drp) for a PD port. + * @param port_hdl Port handle + * @param role Power role to set + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_set_power_role(esp_typec_pd_port_handle_t port_hdl, + esp_typec_power_role_t role); + +/** + * @brief Request a fixed PD sink contract (stub). + * @param port PD port handle + * @param mv Millivolts requested + * @param ma Milliamps requested + * @return ESP_ERR_NOT_SUPPORTED (stub) + */ +esp_err_t esp_typec_pd_sink_request_fixed(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma); + +/** + * @brief Request a PD sink PPS contract (stub). + * @param port PD port handle + * @param mv Millivolts requested + * @param ma Milliamps requested + * @return ESP_ERR_NOT_SUPPORTED (stub) + */ +esp_err_t esp_typec_pd_sink_request_pps(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma); + +/** + * @brief Get current PD contract (stub). + * @param port PD port handle + * @param out Pointer to contract struct + * @return ESP_ERR_NOT_SUPPORTED (stub) + */ +esp_err_t esp_typec_pd_get_contract(esp_typec_pd_port_handle_t port, esp_typec_pd_contract_t *out); + +/** + * @brief Get cable orientation (CC2 active) for a PD port. + * @param port_hdl Port handle + * @param cc2_active Pointer to bool output + * @return ESP_OK if attached, ESP_ERR_INVALID_STATE if not + */ +esp_err_t esp_typec_pd_get_orientation(esp_typec_pd_port_handle_t port_hdl, bool *cc2_active); + +/** + * @brief Get attached state for a PD port. + * @param port_hdl Port handle + * @param attached Pointer to bool output + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_is_attached(esp_typec_pd_port_handle_t port_hdl, bool *attached); + +#ifdef __cplusplus +} +#endif diff --git a/type_c/include_private/fusb302_ctrl.h b/type_c/include_private/fusb302_ctrl.h new file mode 100644 index 00000000..2845c6e6 --- /dev/null +++ b/type_c/include_private/fusb302_ctrl.h @@ -0,0 +1,93 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + + +#include +#include +#include "esp_err.h" +#include "driver/i2c_master.h" +#include "esp_typec.h" // for esp_typec_power_role_t +#include "esp_typec_pd.h" // for esp_typec_rp_current_t +#include "typec_backend.h" /* PD_EVT_* and typec_cc_status_t */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Opaque device handle for FUSB302. + */ +typedef struct fusb302_dev fusb302_dev_t; + +/** + * @brief Hardware wiring/configuration for FUSB302. + * @param i2c_dev I2C device handle from esp_driver_i2c + * @param gpio_int GPIO number for FUSB302 INT (active-low) + */ +typedef struct { + i2c_master_dev_handle_t i2c_dev; /**< REQUIRED: device handle from esp_driver_i2c */ + int gpio_int;/**< REQUIRED: GPIO number for FUSB302 INT (active-low) */ +} fusb302_hw_cfg_t; + +/** + * @brief Initialize and configure the FUSB302 with given hardware config. + * @param hw Pointer to hardware configuration + * @param out Pointer to device handle output + * @return ESP_OK on success + */ +esp_err_t fusb302_init(const fusb302_hw_cfg_t *hw, fusb302_dev_t **out); + +/** + * @brief Put the chip into a safe low-power state and free the handle. + * @param dev Device handle + * @return ESP_OK on success + */ +esp_err_t fusb302_deinit(fusb302_dev_t *dev); + +/** + * @brief Change power role (Sink/Source/DRP). + * @param dev Device handle + * @param role Power role to set + * @return ESP_OK on success + */ +esp_err_t fusb302_set_role(fusb302_dev_t *dev, esp_typec_power_role_t role); + +/** + * @brief Configure advertised Rp current (Default/1.5A/3A). + * @note If already in SOURCE/DRP role, this updates HOST_CUR immediately. + */ +esp_err_t fusb302_set_rp_current(fusb302_dev_t *dev, esp_typec_rp_current_t current); + +/** + * @brief Enable or disable FUSB302 interrupt sources (MASK regs). + * @param dev Device handle + * @param enable True to enable, false to disable + * @return ESP_OK on success + */ +esp_err_t fusb302_enable_irq(fusb302_dev_t *dev, bool enable); + +/** + * @brief Read/clear interrupt cause registers (read-to-clear). Any pointer may be NULL. + * @param dev Device handle + * @param int0 Pointer to INT0 value + * @param inta Pointer to INTA value + * @param intb Pointer to INTB value + * @return ESP_OK on success + */ +esp_err_t fusb302_get_and_clear_int(fusb302_dev_t *dev, + uint8_t *int0, uint8_t *inta, uint8_t *intb); + +esp_err_t fusb302_service_irq(fusb302_dev_t *dev, + typec_evt_mask_t *events); + + +esp_err_t fusb302_get_status(fusb302_dev_t *dev, typec_cc_status_t *st_out); + +#ifdef __cplusplus +} +#endif diff --git a/type_c/include_private/husb320_ctrl.h b/type_c/include_private/husb320_ctrl.h new file mode 100644 index 00000000..f3a020b1 --- /dev/null +++ b/type_c/include_private/husb320_ctrl.h @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief CC status snapshot for HUSB320. + */ +typedef struct { + bool attached; /**< Rd/Ra detected */ + bool cc2_active; /**< true if CC2 is active line */ + uint32_t rp_cur_ma; /**< advertised current (0 if unknown) */ +} husb320_cc_status_t; + +/** + * @brief Hardware configuration for HUSB320. + */ +typedef struct { + int i2c_port; /**< I2C controller port */ + uint8_t i2c_addr; /**< 7-bit I2C address */ + int gpio_int; /**< INT/ATTACH GPIO */ + bool use_intr; /**< If false, backend may poll (debug) */ +} husb320_hw_cfg_t; + +/** + * @brief Opaque device handle for HUSB320. + */ +typedef struct husb320_dev husb320_dev_t; + +/** + * @brief Initialize HUSB320 device. + * @param hw Hardware configuration + * @param out Pointer to device handle + * @return ESP_OK on success + */ +esp_err_t husb320_init(const husb320_hw_cfg_t *hw, husb320_dev_t **out); + +/** + * @brief Deinitialize HUSB320 device. + * @param dev Device handle + */ +void husb320_deinit(husb320_dev_t *dev); + +/** + * @brief Set power role (Rp/Rd) for HUSB320. + * @param dev Device handle + * @param role Power role + * @return ESP_OK on success + */ +esp_err_t husb320_set_role(husb320_dev_t *dev, esp_typec_power_role_t role); + +/** + * @brief GPIO ISR handler for HUSB320 (do NOT touch I2C here). + * @param arg User argument + */ +void husb320_isr_handler(void *arg); + +/** + * @brief Service and drain/ack interrupts (called from Type-C task). + * @param dev Device handle + * @param had_any Set true if any IRQ was serviced + * @return ESP_OK on success + */ +esp_err_t husb320_service_irq(husb320_dev_t *dev, bool *had_any); + +/** + * @brief Read current CC status (attach/orientation/Rp). + * @param dev Device handle + * @param st Pointer to status struct + * @return ESP_OK on success + */ +esp_err_t husb320_read_cc_status(husb320_dev_t *dev, husb320_cc_status_t *st); + +#ifdef __cplusplus +} +#endif diff --git a/type_c/include_private/typec_backend.h b/type_c/include_private/typec_backend.h new file mode 100644 index 00000000..8444a5ca --- /dev/null +++ b/type_c/include_private/typec_backend.h @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Simple CC status snapshot for FUSB302. + * @param attached True when exactly one CC detects Rp + * @param cc2_active True if CC2 is the active/connected CC + * @param rp_cur_ma Advertised Rp current (0 / 500 / 1500 / 3000 mA) + */ +typedef struct { + bool attached; /**< True when exactly one CC detects Rp */ + bool cc2_active; /**< True if CC2 is the active/connected CC */ + uint32_t rp_cur_ma; /**< 0 / 500 / 1500 / 3000 (mA) */ + bool vbus_ok; +} typec_cc_status_t; + +typedef enum { + PD_EVT_CC = (1u << 0), /* CC comparator / BC_LVL change */ + PD_EVT_VBUS = (1u << 1), /* VBUSOK change */ + PD_EVT_RX = (1u << 2), /* PD message available */ + PD_EVT_HRST = (1u << 3), /* Hard reset detected */ + PD_EVT_FAULT = (1u << 4), /* OCP/OTP/etc. */ +} typec_evt_mask_t; diff --git a/type_c/src/esp_typec.c b/type_c/src/esp_typec.c new file mode 100644 index 00000000..3e989f21 --- /dev/null +++ b/type_c/src/esp_typec.c @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_typec.h" +#include "husb320_ctrl.h" // internal + +#define TAG "esp_typec" +typedef struct typec_port_ctx { + // controller + husb320_dev_t *ctrl; + + // tasking + TaskHandle_t task; + QueueHandle_t q; + + // user cb + esp_typec_event_cb_t cb; + void *cb_arg; + + // cached state + bool attached; + bool cc2_active; + uint32_t rp_ma; + + // config snapshot + esp_typec_port_config_t cfg; + +} typec_port_ctx_t; + +typedef enum { + TC_EVT_POLL = 1, +} tc_evt_t; + +// ---- Forward ---- +static void typec_task(void *arg); +static void typec_commit_status(typec_port_ctx_t *ctx, const husb320_cc_status_t *st); + +// ------------------------------------------------- Public API ------------------------------------------------- + +esp_err_t esp_typec_install(void) +{ + return ESP_OK; +} + +esp_err_t esp_typec_uninstall(void) +{ + return ESP_OK; +} + +esp_err_t esp_typec_lib_info(esp_typec_lib_info_t *info) +{ + if (!info) { + return ESP_ERR_INVALID_ARG; + } + // TODO: track number of ports created + info->num_ports = 0; + return ESP_OK; +} + +esp_err_t esp_typec_port_create_husb320(const esp_typec_port_config_t *port_cfg, + const esp_typec_husb320_config_t *hw_cfg, + esp_typec_event_cb_t cb, void *cb_arg, + esp_typec_port_handle_t *port_hdl_ret) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(port_cfg && hw_cfg && cb && port_hdl_ret, ESP_ERR_INVALID_ARG, TAG, "bad args"); + + typec_port_ctx_t *ctx = calloc(1, sizeof(*ctx)); + ESP_RETURN_ON_FALSE(ctx, ESP_ERR_NO_MEM, TAG, "no mem"); + + ctx->cfg = *port_cfg; + ctx->cb = cb; + ctx->cb_arg = cb_arg; + + husb320_hw_cfg_t hw = { + .i2c_port = hw_cfg->i2c_port, + .i2c_addr = hw_cfg->i2c_addr, + .gpio_int = hw_cfg->gpio_int, + .use_intr = false, // start with polling; IRQ later + }; + ESP_GOTO_ON_ERROR(husb320_init(&hw, &ctx->ctrl), fail, TAG, "ctrl init failed"); + + // Apply initial role + esp_typec_power_role_t role = ESP_TYPEC_PWR_SINK; + switch (port_cfg->default_power_role) { + case ESP_TYPEC_PWR_SOURCE: role = ESP_TYPEC_PWR_SOURCE; break; + case ESP_TYPEC_PWR_DRP: role = ESP_TYPEC_PWR_DRP; break; + default: break; + } + ESP_GOTO_ON_ERROR(husb320_set_role(ctx->ctrl, role), fail, TAG, "set role failed"); + + // Create queue and task + ctx->q = xQueueCreate(4, sizeof(uint32_t)); + ESP_GOTO_ON_FALSE(ctx->q, ESP_ERR_NO_MEM, fail, TAG, "queue alloc failed"); + + BaseType_t ok = xTaskCreate(typec_task, "typec_husb320", + (port_cfg->task_stack ? port_cfg->task_stack : 3072) / sizeof(StackType_t), + ctx, + (port_cfg->task_prio ? port_cfg->task_prio : tskIDLE_PRIORITY + 2), + &ctx->task); + ESP_GOTO_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, fail, TAG, "task alloc failed"); + + // Kick initial poll + uint32_t e = TC_EVT_POLL; + (void)xQueueSend(ctx->q, &e, 0); + + *port_hdl_ret = (esp_typec_port_handle_t)ctx; + return ret; + +fail: + if (ctx) { + if (ctx->ctrl) { + husb320_deinit(ctx->ctrl); + } + if (ctx->q) { + vQueueDelete(ctx->q); + } + free(ctx); + } + return ret; +} + +esp_err_t esp_typec_port_destroy(esp_typec_port_handle_t port_hdl) +{ + typec_port_ctx_t *ctx = (typec_port_ctx_t *)port_hdl; + if (!ctx) { + return ESP_ERR_INVALID_ARG; + } + + if (ctx->task) { + vTaskDelete(ctx->task); + ctx->task = NULL; + } + if (ctx->q) { + vQueueDelete(ctx->q); + ctx->q = NULL; + } + if (ctx->ctrl) { + husb320_deinit(ctx->ctrl); + ctx->ctrl = NULL; + } + free(ctx); + return ESP_OK; +} + +esp_err_t esp_typec_set_power_role(esp_typec_port_handle_t port_hdl, esp_typec_power_role_t role) +{ + typec_port_ctx_t *ctx = (typec_port_ctx_t *)port_hdl; + if (!ctx) { + return ESP_ERR_INVALID_ARG; + } + return husb320_set_role(ctx->ctrl, role); +} + +esp_err_t esp_typec_get_orientation(esp_typec_port_handle_t port_hdl, bool *cc2_active) +{ + typec_port_ctx_t *ctx = (typec_port_ctx_t *)port_hdl; + if (!ctx || !cc2_active) { + return ESP_ERR_INVALID_ARG; + } + if (!ctx->attached) { + return ESP_ERR_INVALID_STATE; + } + *cc2_active = ctx->cc2_active; + return ESP_OK; +} + +esp_err_t esp_typec_is_attached(esp_typec_port_handle_t port_hdl, bool *attached) +{ + typec_port_ctx_t *ctx = (typec_port_ctx_t *)port_hdl; + if (!ctx || !attached) { + return ESP_ERR_INVALID_ARG; + } + *attached = ctx->attached; + return ESP_OK; +} + +// ------------------------------------------------- Task & helpers ------------------------------------------------- + +static void typec_emit_attached(typec_port_ctx_t *ctx) +{ + if (!ctx->cb) { + return; + } + esp_typec_evt_attached_t a = { + .flags = ctx->cc2_active ? ESP_TYPEC_FLAG_CC2 : 0, + .rp_cur_ma = ctx->rp_ma, + }; + ctx->cb(ESP_TYPEC_EVENT_ATTACHED, &a, ctx->cb_arg); +} + +static void typec_emit_detached(typec_port_ctx_t *ctx) +{ + if (!ctx->cb) { + return; + } + ctx->cb(ESP_TYPEC_EVENT_DETACHED, NULL, ctx->cb_arg); +} + +static void typec_emit_orientation(typec_port_ctx_t *ctx) +{ + if (!ctx->cb) { + return; + } + bool cc2 = ctx->cc2_active; + ctx->cb(ESP_TYPEC_EVENT_ORIENTATION, &cc2, ctx->cb_arg); +} + +static void typec_commit_status(typec_port_ctx_t *ctx, const husb320_cc_status_t *st) +{ + if (!ctx->attached && st->attached) { + ctx->attached = true; + ctx->cc2_active = st->cc2_active; + ctx->rp_ma = st->rp_cur_ma; + typec_emit_attached(ctx); + } else if (ctx->attached && !st->attached) { + ctx->attached = false; + typec_emit_detached(ctx); + } else if (ctx->attached && (ctx->cc2_active != st->cc2_active)) { + ctx->cc2_active = st->cc2_active; + typec_emit_orientation(ctx); + } +} + +static void typec_task(void *arg) +{ + typec_port_ctx_t *ctx = (typec_port_ctx_t *)arg; + + const TickType_t period = pdMS_TO_TICKS(50); // simple debounce/poll period + TickType_t last = xTaskGetTickCount(); + + for (;;) { + // periodic poll (no IRQ yet) + vTaskDelayUntil(&last, period); + + husb320_cc_status_t st = {0}; + if (husb320_read_cc_status(ctx->ctrl, &st) == ESP_OK) { + typec_commit_status(ctx, &st); + } + } +} diff --git a/type_c/src/esp_typec_pd.c b/type_c/src/esp_typec_pd.c new file mode 100644 index 00000000..46557103 --- /dev/null +++ b/type_c/src/esp_typec_pd.c @@ -0,0 +1,438 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_check.h" +#include "esp_log.h" +#include "esp_rom_sys.h" + +#include "driver/gpio.h" + +#include "esp_typec_pd.h" // public PD API (events, configs) +#include "typec_backend.h" /* PD_EVT_* and typec_cc_status_t */ +#include "fusb302_ctrl.h" // backend for fusb302 + +#define TAG "esp_typec_pd" + +/* Define the event base */ +ESP_EVENT_DEFINE_BASE(ESP_TYPEC_PD_EVENT); + +/* ---------- Per-port context ---------- */ +typedef struct pd_port_backend { + esp_err_t (*set_role)(void *dev, esp_typec_power_role_t role); + esp_err_t (*service_irq)(void *dev, typec_evt_mask_t *events); + esp_err_t (*get_status)(void *dev, typec_cc_status_t *st); + esp_err_t (*deinit)(void *dev); +} pd_port_backend_t; + +typedef struct pd_port_ctx { + // Backend handle + void *ctrl; + const pd_port_backend_t *backend; + // INT GPIO (active-low) + int gpio_int; + // Task + TaskHandle_t task; + // Cached state + bool attached; + bool cc2_active; + uint32_t rp_ma; + // Current power role (sink/source/drp) + esp_typec_power_role_t role; + // Config snapshot + esp_typec_pd_port_config_t cfg; +} pd_port_ctx_t; + + +/* ---------- Tiny helpers ---------- */ +static inline void pd_emit_attached(pd_port_ctx_t *ctx) +{ + esp_typec_pd_evt_attached_t evt = { + .port = (esp_typec_pd_port_handle_t)ctx, + .cc2_active = ctx->cc2_active, + .rp_cur_ma = ctx->rp_ma, + }; + + esp_err_t err = esp_event_post( + ESP_TYPEC_PD_EVENT, + ESP_TYPEC_PD_EVENT_ID_ATTACHED, + &evt, + sizeof(evt), + portMAX_DELAY); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to post PD ATTACHED event: %s", esp_err_to_name(err)); + } +} + + +static inline void pd_emit_detached(pd_port_ctx_t *ctx) +{ + (void)ctx; // if you don’t yet have a payload + + esp_err_t err = esp_event_post( + ESP_TYPEC_PD_EVENT, + ESP_TYPEC_PD_EVENT_ID_DETACHED, + NULL, + 0, + portMAX_DELAY); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to post PD DETACHED event: %s", esp_err_to_name(err)); + } +} + +static inline void pd_set_vbus_source(pd_port_ctx_t *ctx, bool enable) +{ + if (ctx->cfg.src_vbus_gpio == GPIO_NUM_NC) { + return; // no MOSFET controlled by PD stack + } + gpio_set_level(ctx->cfg.src_vbus_gpio, enable); +} + +/* ---------- ISR & Task ---------- */ + +static void IRAM_ATTR pd_gpio_isr(void *arg) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)arg; + // Gate further edges until the task drains the device + gpio_intr_disable(ctx->gpio_int); + BaseType_t hp = pdFALSE; + vTaskNotifyGiveFromISR(ctx->task, &hp); + if (hp) { + portYIELD_FROM_ISR(); + } +} + +static void pd_task(void *arg) +{ + pd_port_ctx_t *ctx = arg; + + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + typec_evt_mask_t ev = 0; + typec_cc_status_t st = (typec_cc_status_t) { + 0 + }; + + /* Drain IRQs and get a fresh snapshot in one call */ + ctx->backend->service_irq(ctx->ctrl, &ev); + + if (ctx->role == ESP_TYPEC_PWR_SOURCE) { + /* -------- SOURCE policy: CC-driven attach/detach + VBUS MOSFET -------- */ + if (ev & PD_EVT_CC) { + ctx->backend->get_status(ctx->ctrl, &st); + if (st.attached && !ctx->attached) { + // Sink just appeared on one of the CC pins + ctx->attached = true; + ctx->cc2_active = st.cc2_active; + ctx->rp_ma = st.rp_cur_ma; + + // Now it’s safe to start sourcing 5 V + pd_set_vbus_source(ctx, true); + pd_emit_attached(ctx); + + } else if (!st.attached && ctx->attached) { + // Cable removed / sink detached + ctx->attached = false; + pd_set_vbus_source(ctx, false); + pd_emit_detached(ctx); + + } else if (ctx->attached) { + // Orientation or advertised current changed while attached + ctx->cc2_active = st.cc2_active; + ctx->rp_ma = st.rp_cur_ma; + } + } + + } else { + /* -------- SINK policy: VBUS-driven attach/detach only -------- */ + if (ev & PD_EVT_VBUS) { + // First, read current VBUS/CC state + ctx->backend->get_status(ctx->ctrl, &st); + + if (st.vbus_ok && !ctx->attached) { + // VBUS became valid: give CC comparators time to settle + vTaskDelay(pdMS_TO_TICKS(30)); + + // Re-read after the delay for orientation + Rp + ctx->backend->get_status(ctx->ctrl, &st); + + ctx->attached = true; + ctx->cc2_active = st.cc2_active; + ctx->rp_ma = st.rp_cur_ma; + pd_emit_attached(ctx); + + } else if (!st.vbus_ok && ctx->attached) { + // VBUS dropped -> detached + ctx->attached = false; + pd_emit_detached(ctx); + } + } + + // IMPORTANT: in sink mode we ignore PD_EVT_CC completely + } + + /* Handle rare stuck-low INT */ + if (gpio_get_level(ctx->gpio_int) == 0) { + vTaskDelay(pdMS_TO_TICKS(1)); + (void)ctx->backend->service_irq(ctx->ctrl, &ev); + } + + gpio_intr_enable(ctx->gpio_int); + } +} + +/* ---------- Public API ---------- */ + +esp_err_t esp_typec_pd_install(const esp_typec_pd_install_config_t *config) +{ + (void)config; + + // Make sure the default event loop exists. + esp_err_t err = esp_event_loop_create_default(); + if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { + ESP_LOGE(TAG, "Failed to create default event loop: %s", esp_err_to_name(err)); + return err; + } + return ESP_OK; +} + + +esp_err_t esp_typec_pd_uninstall(void) +{ + return ESP_OK; +} + +esp_err_t esp_typec_pd_lib_info(esp_typec_pd_lib_info_t *info_ret) +{ + if (!info_ret) { + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; +} + +static const pd_port_backend_t fusb302_backend = { + .set_role = (esp_err_t (*)(void *, esp_typec_power_role_t))fusb302_set_role, + .service_irq = (esp_err_t (*)(void *, typec_evt_mask_t *))fusb302_service_irq, + .get_status = (esp_err_t (*)(void *, typec_cc_status_t *))fusb302_get_status, + .deinit = (esp_err_t (*)(void *))fusb302_deinit, +}; + +esp_err_t esp_typec_pd_port_create_fusb302(const esp_typec_pd_port_config_t *port_cfg, + const esp_typec_pd_fusb302_config_t *hw_cfg, + esp_typec_pd_port_handle_t *port_hdl_ret) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(port_cfg && hw_cfg && port_hdl_ret, + ESP_ERR_INVALID_ARG, TAG, "bad args"); + ESP_RETURN_ON_FALSE(port_cfg->rp_current <= ESP_TYPEC_RP_3A0, + ESP_ERR_INVALID_ARG, TAG, "rp_current invalid"); + + pd_port_ctx_t *ctx = (pd_port_ctx_t *)calloc(1, sizeof(*ctx)); + ESP_RETURN_ON_FALSE(ctx, ESP_ERR_NO_MEM, TAG, "no mem"); + + ctx->cfg = *port_cfg; + ctx->backend = &fusb302_backend; + + // Configure VBUS MOSFET GPIO if present + if (ctx->cfg.src_vbus_gpio != GPIO_NUM_NC) { + gpio_config_t io = { + .pin_bit_mask = 1ULL << ctx->cfg.src_vbus_gpio, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_RETURN_ON_ERROR(gpio_config(&io), TAG, "src_vbus gpio_config failed"); + + // Ensure VBUS is OFF at startup + gpio_set_level(ctx->cfg.src_vbus_gpio, 0); + } + + // Init backend + fusb302_hw_cfg_t hw = { + .i2c_dev = hw_cfg->i2c_dev, // NEW: i2c_master_dev_handle_t + .gpio_int = hw_cfg->gpio_int, + }; + fusb302_dev_t *dev = NULL; + ESP_RETURN_ON_ERROR(fusb302_init(&hw, &dev), TAG, "ctrl init failed"); + ESP_RETURN_ON_ERROR(fusb302_set_rp_current(dev, port_cfg->rp_current), TAG, "set Rp current failed"); + ctx->ctrl = dev; + // apply initial role from config ----- + esp_typec_power_role_t role = port_cfg->default_power_role; + if (role == ESP_TYPEC_PWR_SOURCE || role == ESP_TYPEC_PWR_DRP) { + // only if the role is not default sink + ESP_GOTO_ON_ERROR(ctx->backend->set_role(ctx->ctrl, role), + fail, TAG, "set role failed"); + } + ctx->role = role; + ctx->gpio_int = hw_cfg->gpio_int; + + // Configure INT GPIO as input with pull-up and any-edge interrupt + gpio_config_t gc = { + .pin_bit_mask = 1ULL << ctx->gpio_int, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_NEGEDGE, + }; + ESP_GOTO_ON_ERROR(gpio_config(&gc), fail, TAG, "gpio_config"); + + esp_err_t isr_ret = gpio_install_isr_service(0); + if (isr_ret != ESP_OK && isr_ret != ESP_ERR_INVALID_STATE) { + goto fail; + } + ESP_GOTO_ON_ERROR(gpio_isr_handler_add(ctx->gpio_int, pd_gpio_isr, ctx), fail, TAG, "gpio_isr_add"); + + // Create task + const uint32_t stack_words = (port_cfg->task_stack ? port_cfg->task_stack : 4096) / sizeof(StackType_t); + const UBaseType_t prio = (port_cfg->task_prio ? port_cfg->task_prio : (tskIDLE_PRIORITY + 2)); + + BaseType_t ok = xTaskCreate(pd_task, "pd_fusb302", stack_words, ctx, prio, &ctx->task); + if (ok != pdPASS) { + gpio_isr_handler_remove(ctx->gpio_int); + ctx->backend->deinit(ctx->ctrl); + free(ctx); + return ESP_ERR_NO_MEM; + } + + /* Drain any pre-existing latched causes before arming GPIO */ + typec_evt_mask_t ev = 0; + + ESP_GOTO_ON_ERROR(ctx->backend->service_irq(ctx->ctrl, &ev), fail, TAG, "prime irq"); + + /* Now arm the GPIO */ + gpio_intr_enable(ctx->gpio_int); + + /* If INT is already asserted low right now, kick the task once */ + if (gpio_get_level(ctx->gpio_int) == 0) { + BaseType_t hp = pdFALSE; + vTaskNotifyGiveFromISR(ctx->task, &hp); + if (hp) { + portYIELD_FROM_ISR(); + } + } + + *port_hdl_ret = (esp_typec_pd_port_handle_t)ctx; + ESP_LOGI(TAG, "PD task started"); + return ESP_OK; +fail: + ESP_LOGE(TAG, "failed, err=%d", ret); + if (ctx) { + if (ctx->task) { + vTaskDelete(ctx->task); + ctx->task = NULL; + } + if (ctx->ctrl) { + ctx->backend->deinit(ctx->ctrl); + ctx->ctrl = NULL; + } + free(ctx); + } + return ret; +} + +esp_err_t esp_typec_pd_port_destroy(esp_typec_pd_port_handle_t port_hdl) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx) { + return ESP_ERR_INVALID_ARG; + } + + if (ctx->task) { + vTaskDelete(ctx->task); + ctx->task = NULL; + } + + gpio_isr_handler_remove(ctx->gpio_int); + + if (ctx->ctrl && ctx->backend) { + ctx->backend->deinit(ctx->ctrl); + ctx->ctrl = NULL; + } + + free(ctx); + return ESP_OK; +} + +esp_err_t esp_typec_pd_set_power_role(esp_typec_pd_port_handle_t port_hdl, + esp_typec_power_role_t role) +{ + if (role == ESP_TYPEC_PWR_DRP) { + return ESP_ERR_NOT_SUPPORTED;// TODO: add DRP support later + } + + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx || !ctx->backend) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = ctx->backend->set_role(ctx->ctrl, role); + if (err != ESP_OK) { + return err; + } + + ctx->role = role; + + // Simple safety policy: only SOURCE is allowed to drive VBUS + if (role != ESP_TYPEC_PWR_SOURCE) { + pd_set_vbus_source(ctx, false); + } + + return ESP_OK; +} + +/* Stubs for later PD support */ +esp_err_t esp_typec_pd_sink_request_fixed(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma) +{ + (void)port; + (void)mv; + (void)ma; + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t esp_typec_pd_sink_request_pps(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma) +{ + (void)port; + (void)mv; + (void)ma; + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t esp_typec_pd_get_contract(esp_typec_pd_port_handle_t port, esp_typec_pd_contract_t *out) +{ + (void)port; + (void)out; + return ESP_ERR_NOT_SUPPORTED; +} + +esp_err_t esp_typec_pd_get_orientation(esp_typec_pd_port_handle_t port_hdl, bool *cc2_active) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx || !cc2_active) { + return ESP_ERR_INVALID_ARG; + } + if (!ctx->attached) { + return ESP_ERR_INVALID_STATE; + } + *cc2_active = ctx->cc2_active; + return ESP_OK; +} + +esp_err_t esp_typec_pd_is_attached(esp_typec_pd_port_handle_t port_hdl, bool *attached) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx || !attached) { + return ESP_ERR_INVALID_ARG; + } + *attached = ctx->attached; + return ESP_OK; +} diff --git a/type_c/src/fusb302_ctrl.c b/type_c/src/fusb302_ctrl.c new file mode 100644 index 00000000..49f678a8 --- /dev/null +++ b/type_c/src/fusb302_ctrl.c @@ -0,0 +1,515 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_log.h" +#include "driver/i2c_master.h" +#include "driver/gpio.h" +#include "esp_rom_sys.h" +#include "fusb302_ctrl.h" +#include "esp_typec.h" // for esp_typec_power_role_t + +#define TAG "fusb302" + +#define MAX_INT_READS 8 /* Maximum number of interrupt register reads to avoid infinite loops */ + +/** + * @brief FUSB302 device context structure. + * @param hw Hardware configuration + * @param role Current power role + */ +struct fusb302_dev { + fusb302_hw_cfg_t hw; /**< Hardware configuration */ + esp_typec_power_role_t role; /**< Current power role */ + esp_typec_rp_current_t rp_current;/**< Advertised Rp current when sourcing */ +}; + +/* FUSB302 registers */ +enum { + REG_DEVICE_ID = 0x01, + REG_SWITCHES0 = 0x02, + REG_SWITCHES1 = 0x03, + REG_MEASURE = 0x04, + REG_CONTROL0 = 0x06, + REG_CONTROL1 = 0x07, + REG_CONTROL2 = 0x08, + REG_CONTROL3 = 0x09, + REG_MASK = 0x0A, + REG_POWER = 0x0B, + REG_RESET = 0x0C, + REG_MASKA = 0x0E, + REG_MASKB = 0x0F, + + REG_STATUS0A = 0x3C, + REG_STATUS1A = 0x3D, + REG_INTERRUPTA = 0x3E, + REG_INTERRUPTB = 0x3F, + REG_STATUS0 = 0x40, + REG_STATUS1 = 0x41, + REG_INTERRUPT = 0x42, + REG_FIFOS = 0x43, +}; + +/* SWITCHES0 */ +#define SW0_CC2_PU_EN (1u << 7) +#define SW0_CC1_PU_EN (1u << 6) +#define SW0_VCONN_CC2 (1u << 5) +#define SW0_VCONN_CC1 (1u << 4) +#define SW0_MEAS_CC2 (1u << 3) +#define SW0_MEAS_CC1 (1u << 2) +#define SW0_CC2_PD_EN (1u << 1) +#define SW0_CC1_PD_EN (1u << 0) + +/* SWITCHES1 */ +#define SW1_POWERROLE (1u << 7) +#define SW1_SPECREV1 (1u << 6) +#define SW1_SPECREV0 (1u << 5) +#define SW1_DATAROLE (1u << 4) +#define SW1_TXCC2_EN (1u << 1) +#define SW1_TXCC1_EN (1u << 0) + +/* CONTROL0 */ +#define CTL0_INT_MASK (1u << 5) /* 0 = INT pin enabled, 1 = masked */ +#define CTL0_HOST_CUR_MASK (0x3 << 2) +#define CTL0_HOST_CUR_DEFAULT (0x0 << 2) /* Default (80uA) */ +#define CTL0_HOST_CUR_MEDIUM (0x2 << 2) /* Medium (180uA, 1.5A) */ +#define CTL0_HOST_CUR_HIGH (0x3 << 2) /* High (330uA, 3A) */ + +/* CONTROL2 MODE bits */ +#define CTL2_MODE_MASK (0xE) +#define CTL2_MODE_DFP (0x6) +#define CTL2_MODE_UFP (0x4) +#define CTL2_MODE_DRP (0x2) +#define CTL2_MODE_NONE (0x0) +#define CTL2_TOGGLE (1u << 0) + +/* POWER levels */ +#define PWR_PWR_ALL 0x0F /* full on */ +#define PWR_PWR_HIGH 0x07 +#define PWR_PWR_MEDIUM 0x03 +#define PWR_PWR_LOW 0x01 + +/* RESET */ +#define RST_PD_RESET (1u << 1) +#define RST_SW_RESET (1u << 0) + +/* STATUS0 */ +#define ST0_VBUSOK (1u << 7) +#define ST0_BC_LVL_MASK 0x03 +#define ST0_BC_LVL_0_200 0x0 +#define ST0_BC_LVL_200_600 0x1 +#define ST0_BC_LVL_600_1230 0x2 +#define ST0_BC_LVL_1230_MAX 0x3 + +/* --- helpers for Rp current mapping --- */ +static inline uint8_t rp_current_to_host_cur_bits(esp_typec_rp_current_t rp) +{ + switch (rp) { + case ESP_TYPEC_RP_1A5: + return CTL0_HOST_CUR_MEDIUM; + case ESP_TYPEC_RP_3A0: + return CTL0_HOST_CUR_HIGH; + case ESP_TYPEC_RP_DEFAULT: + default: + return CTL0_HOST_CUR_DEFAULT; + } +} + +static inline uint32_t rp_current_to_ma(esp_typec_rp_current_t rp) +{ + switch (rp) { + case ESP_TYPEC_RP_1A5: + return 1500; + case ESP_TYPEC_RP_3A0: + return 3000; + case ESP_TYPEC_RP_DEFAULT: + default: + return 500; + } +} + +/* REG_INTERRUPT (0x42) — top-level CC/VBUS causes */ +#define INT_BC_LVL (1u << 0) +#define INT_COLLISION (1u << 1) +#define INT_WAKE (1u << 2) +#define INT_ALERT (1u << 3) +#define INT_CRC_CHK (1u << 4) +#define INT_COMP_CHNG (1u << 5) +#define INT_ACTIVITY (1u << 6) +#define INT_VBUSOK (1u << 7) + +/* REG_INTERRUPTA (0x3E) — PD/Toggle outcomes (mapping varies by rev) */ +#define INTA_TX_SUCC (1u << 1) +#define INTA_RETRY_FAIL (1u << 2) +#define INTA_SOFT_FAIL (1u << 3) +#define INTA_HARD_SENT (1u << 5) +#define INTA_TOG_DONE (1u << 6) + +/* REG_INTERRUPTB (0x3F) — PD RX, resets, FIFO (mapping varies by rev) */ +#define INTB_GCRCSENT (1u << 0) +#define INTB_RX_SOP (1u << 1) +#define INTB_RX_ANY_MASK 0x1F + +/* ---------------- New I2C helpers (esp_driver_i2c) ---------------- */ + +/** + * @brief Write 8-bit value to FUSB302 register via I2C. + * @param dev I2C device handle + * @param reg Register address + * @param val Value to write + * @return ESP_OK on success + */ +static inline esp_err_t i2c_wr8(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t val) +{ + uint8_t buf[2] = { reg, val }; + return i2c_master_transmit(dev, buf, sizeof(buf), 100 /*ms*/); +} + +/** + * @brief Read 8-bit value from FUSB302 register via I2C. + * @param dev I2C device handle + * @param reg Register address + * @param val Pointer to value output + * @return ESP_OK on success + */ +static inline esp_err_t i2c_rd8(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t *val) +{ + if (!val) { + return ESP_ERR_INVALID_ARG; + } + esp_err_t err = i2c_master_transmit(dev, ®, 1, 100); + if (err != ESP_OK) { + return err; + } + return i2c_master_receive(dev, val, 1, 100); +} + +/* One-shot CC/VBUS measurement WITHOUT touching IRQ latches. */ +static esp_err_t fusb302_measure_status(fusb302_dev_t *dev, typec_cc_status_t *st) +{ + if (!dev || !st) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + uint8_t sw0 = 0, st0 = 0; + uint8_t old_mask = 0; + + /* ---- Temporarily mask CC-change interrupts while we flip MEAS ---- */ + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_MASK, &old_mask), TAG, "rd MASK"); + + uint8_t mask_during_meas = old_mask | INT_BC_LVL | INT_COMP_CHNG; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASK, mask_during_meas), TAG, "mask CC during meas"); + + // Read current SWITCHES0, we will only touch MEAS bits + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_SWITCHES0, &sw0), TAG, "rd SW0"); + + uint8_t sw0_meas = sw0 & ~(SW0_MEAS_CC1 | SW0_MEAS_CC2); + + /* ---- Measure CC1 ---- */ + sw0_meas |= SW0_MEAS_CC1; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, sw0_meas), TAG, "meas CC1"); + esp_rom_delay_us(100); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_STATUS0, &st0), TAG, "st0 CC1"); + uint8_t bclvl_cc1 = (st0 & ST0_BC_LVL_MASK); + bool vbus_ok = (st0 & ST0_VBUSOK) != 0; + + /* ---- Measure CC2 ---- */ + sw0_meas = (sw0_meas & ~SW0_MEAS_CC1) | SW0_MEAS_CC2; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, sw0_meas), TAG, "meas CC2"); + esp_rom_delay_us(100); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_STATUS0, &st0), TAG, "st0 CC2"); + uint8_t bclvl_cc2 = (st0 & ST0_BC_LVL_MASK); + + /* ---- Restore original MASK (re-enable CC interrupts as before) ---- */ + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASK, old_mask), TAG, "restore MASK"); + + memset(st, 0, sizeof(*st)); + st->vbus_ok = vbus_ok; + + if (dev->role == ESP_TYPEC_PWR_SINK) { + /* ========= SINK path (current behaviour preserved) ========= */ + + bool cc1_has_rp = (bclvl_cc1 != ST0_BC_LVL_0_200); + bool cc2_has_rp = (bclvl_cc2 != ST0_BC_LVL_0_200); + + st->attached = (cc1_has_rp ^ cc2_has_rp); // exactly one CC sees Rp + st->cc2_active = st->attached ? cc2_has_rp : false; + + uint8_t bclvl = cc2_has_rp ? bclvl_cc2 : bclvl_cc1; + st->rp_cur_ma = (bclvl == ST0_BC_LVL_200_600) ? 500 : + (bclvl == ST0_BC_LVL_600_1230) ? 1500 : + (bclvl == ST0_BC_LVL_1230_MAX) ? 3000 : + (vbus_ok ? 500 : 0); + + } else { + /* ========= SOURCE path (DFP) – decode based on advertised Rp current ========= */ + + bool cc1_rd = false; + bool cc2_rd = false; + + switch (rp_current_to_host_cur_bits(dev->rp_current)) { + case CTL0_HOST_CUR_DEFAULT: + // Default (80 µA): Rd around 0.4 V -> BC_LVL_200_600 + cc1_rd = (bclvl_cc1 == ST0_BC_LVL_200_600); + cc2_rd = (bclvl_cc2 == ST0_BC_LVL_200_600); + break; + case CTL0_HOST_CUR_MEDIUM: + // 1.5 A (180 µA): Rd around 0.9 V -> BC_LVL_600_1230 + cc1_rd = (bclvl_cc1 == ST0_BC_LVL_600_1230); + cc2_rd = (bclvl_cc2 == ST0_BC_LVL_600_1230); + break; + case CTL0_HOST_CUR_HIGH: + // 3 A (330 µA): Rd and open both end up above 1.23 V + // so BC_LVL alone can't distinguish. For now we don’t + // try to detect attach in this mode. + cc1_rd = false; + cc2_rd = false; + break; + default: + cc1_rd = cc2_rd = false; + break; + } + st->rp_cur_ma = rp_current_to_ma(dev->rp_current); + + st->attached = (cc1_rd ^ cc2_rd); // exactly one CC has Rd + st->cc2_active = st->attached ? cc2_rd : false; + } + + ESP_LOGI(TAG, "CC: role=%d att=%d act=%s CC1=0x%02X CC2=0x%02X Rp=%umA VBUSOK=%d", + dev->role, st->attached, st->cc2_active ? "CC2" : "CC1", + bclvl_cc1, bclvl_cc2, (unsigned)st->rp_cur_ma, vbus_ok); + + return ESP_OK; +} + +/* ---------------- Public functions (logic unchanged) ---------------- */ + +esp_err_t fusb302_init(const fusb302_hw_cfg_t *hw, fusb302_dev_t **out) +{ + if (!hw || !out) { + return ESP_ERR_INVALID_ARG; + } + + fusb302_dev_t *dev = calloc(1, sizeof(*dev)); + if (!dev) { + return ESP_ERR_NO_MEM; + } + dev->hw = *hw; + dev->rp_current = ESP_TYPEC_RP_DEFAULT; + *out = dev; + + uint8_t v; + + // Probe + ESP_RETURN_ON_ERROR(i2c_rd8(hw->i2c_dev, REG_DEVICE_ID, &v), TAG, "probe"); + + // Soft reset + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_RESET, RST_SW_RESET), TAG, "reset"); + vTaskDelay(pdMS_TO_TICKS(2)); + + // Power all relevant blocks + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_POWER, PWR_PWR_ALL), TAG, "power"); + + // Default role (sink/UFP) BEFORE unmasking INT + ESP_RETURN_ON_ERROR(fusb302_set_role(dev, ESP_TYPEC_PWR_SINK), TAG, "set default role"); + + // Unmask (values kept as-is from your current code) + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_MASK, 0xBE), TAG, "mask"); + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_MASKA, 0xFF), TAG, "maska"); + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_MASKB, 0x0F), TAG, "maskb"); + + // Clear any latched causes *after* unmask + (void)i2c_rd8(hw->i2c_dev, REG_INTERRUPT, &v); + (void)i2c_rd8(hw->i2c_dev, REG_INTERRUPTA, &v); + (void)i2c_rd8(hw->i2c_dev, REG_INTERRUPTB, &v); + + fusb302_enable_irq(dev, true); + + ESP_LOGI(TAG, "init done"); + return ESP_OK; +} + +esp_err_t fusb302_deinit(fusb302_dev_t *dev) +{ + esp_err_t ret = ESP_OK; + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + + ESP_GOTO_ON_ERROR(fusb302_enable_irq(dev, false), fail, TAG, "disable irq"); + ESP_GOTO_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, 0x00), fail, TAG, "SWITCHES0 Hi-Z"); + ESP_GOTO_ON_ERROR(i2c_wr8(d, REG_CONTROL2, CTL2_MODE_NONE), fail, TAG, "CONTROL2 mode none"); + ESP_GOTO_ON_ERROR(i2c_wr8(d, REG_POWER, PWR_PWR_LOW), fail, TAG, "power down"); + +fail: + free(dev); + return ret; +} + +esp_err_t fusb302_set_role(fusb302_dev_t *dev, esp_typec_power_role_t role) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + uint8_t value; + switch (role) { + case ESP_TYPEC_PWR_SINK: { // Configure as Sink (UFP) + // Set FUSB302 to UFP mode + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL2, CTL2_MODE_UFP), TAG, "set UFP"); + // Enable Rd on both CC1/CC2, no Rp (pull-downs active) + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, SW0_CC1_PD_EN | SW0_CC2_PD_EN), TAG, "SWITCHES0 Rd"); + break; + } + case ESP_TYPEC_PWR_SOURCE: { // Configure as Source (DFP) + // Set FUSB302 to DFP mode + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL2, CTL2_MODE_DFP), TAG, "set DFP"); + // Enable Rp (pull-ups) on both CC1/CC2. Start measuring on CC1 by default. + value = SW0_CC1_PU_EN | SW0_CC2_PU_EN | SW0_MEAS_CC1; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, value), TAG, "SWITCHES0 Rp"); + // Set advertised current (e.g., 1.5A) and CTL0 HOST_CUR + value = SW1_POWERROLE; // POWERROLE=1 (source) + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES1, value), TAG, "SWITCHES1 Rp current"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_CONTROL0, &value), TAG, "read CONTROL0"); + value &= ~CTL0_HOST_CUR_MASK; // clear HOST_CUR bits + value |= rp_current_to_host_cur_bits(dev->rp_current); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL0, value), TAG, "set HOST_CUR"); + ESP_LOGI(TAG, "FUSB302 set as Source, Rp advert=%umA", (unsigned)rp_current_to_ma(dev->rp_current)); + break; + } + case ESP_TYPEC_PWR_DRP: { // Configure as Dual-Role (DRP toggle) + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL2, CTL2_MODE_DRP | CTL2_TOGGLE), TAG, "set DRP"); + // For DRP, start with one CC measurement (e.g., CC1) and let toggle happen + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, SW0_MEAS_CC1), TAG, "SWITCHES0 DRP"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_CONTROL0, &value), TAG, "read CONTROL0"); + value &= ~CTL0_HOST_CUR_MASK; + value |= rp_current_to_host_cur_bits(dev->rp_current); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL0, value), TAG, "set HOST_CUR default"); + break; + } + default: + return ESP_ERR_INVALID_ARG; + } + dev->role = role; + + return ESP_OK; +} + +esp_err_t fusb302_set_rp_current(fusb302_dev_t *dev, esp_typec_rp_current_t current) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + if (current < ESP_TYPEC_RP_DEFAULT || current > ESP_TYPEC_RP_3A0) { + return ESP_ERR_INVALID_ARG; + } + + dev->rp_current = current; + + // If we are already advertising as source/DRP, update HOST_CUR immediately + if (dev->role == ESP_TYPEC_PWR_SOURCE || dev->role == ESP_TYPEC_PWR_DRP) { + uint8_t v = 0; + ESP_RETURN_ON_ERROR(i2c_rd8(dev->hw.i2c_dev, REG_CONTROL0, &v), TAG, "rd ctl0"); + v &= ~CTL0_HOST_CUR_MASK; + v |= rp_current_to_host_cur_bits(current); + ESP_RETURN_ON_ERROR(i2c_wr8(dev->hw.i2c_dev, REG_CONTROL0, v), TAG, "wr ctl0"); + } + + return ESP_OK; +} + +esp_err_t fusb302_service_irq(fusb302_dev_t *dev, + typec_evt_mask_t *events) +{ + if (!dev || !events) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + *events = 0; + + // Drain latched causes; exit when no causes AND pin is high + for (int i = 0; i < MAX_INT_READS; ++i) { + uint8_t i0 = 0, ia = 0, ib = 0; + + // Read-to-clear + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_INTERRUPT, &i0), TAG, "rd INT"); + (void)i2c_rd8(d, REG_INTERRUPTA, &ia); + (void)i2c_rd8(d, REG_INTERRUPTB, &ib); + + if (i0 & (INT_BC_LVL | INT_COMP_CHNG)) { + *events |= PD_EVT_CC; + } + if (i0 & INT_VBUSOK) { + *events |= PD_EVT_VBUS; + } + if (ib & INTB_RX_SOP) { + *events |= PD_EVT_RX; + } + + // Done if nothing latched AND INT pin is high (deasserted) + if ((i0 | ia | ib) == 0 && gpio_get_level(dev->hw.gpio_int) == 1) { + break; + } + + // Small breath to let the device clear internal latches + esp_rom_delay_us(200); + } + return ESP_OK; +} + +esp_err_t fusb302_enable_irq(fusb302_dev_t *dev, bool enable) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + uint8_t v = 0; + if (enable) { + // Enable INT: CONTROL0.INT_MASK = 0 + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_CONTROL0, &v), TAG, "rd ctl0"); + v &= ~CTL0_INT_MASK; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL0, v), TAG, "wr ctl0"); + /* Clear any latched interrupts */ + uint8_t tmp; + (void)i2c_rd8(d, REG_INTERRUPT, &tmp); + (void)i2c_rd8(d, REG_INTERRUPTA, &tmp); + (void)i2c_rd8(d, REG_INTERRUPTB, &tmp); + + // Keep your existing unmask choices + uint8_t mask_val = 0xFF; + mask_val &= ~(INT_VBUSOK | INT_BC_LVL | INT_COMP_CHNG); + + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASK, mask_val), TAG, "unmask INT"); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASKA, 0x00), TAG, "unmask INTA"); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASKB, 0x00), TAG, "unmask INTB"); + } else { + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASK, 0xFF), TAG, "mask INT"); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASKA, 0xFF), TAG, "mask INTA"); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_MASKB, 0xFF), TAG, "mask INTB"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_CONTROL0, &v), TAG, "rd ctl0"); + v |= CTL0_INT_MASK; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL0, v), TAG, "wr ctl0"); + } + return ESP_OK; +} + +esp_err_t fusb302_get_status(fusb302_dev_t *dev, typec_cc_status_t *st_out) +{ + if (!dev || !st_out) { + return ESP_ERR_INVALID_ARG; + } + return fusb302_measure_status(dev, st_out); +} diff --git a/type_c/src/husb320_ctrl.c b/type_c/src/husb320_ctrl.c new file mode 100644 index 00000000..829ca014 --- /dev/null +++ b/type_c/src/husb320_ctrl.c @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_err.h" +#include "esp_log.h" +#include "husb320_ctrl.h" + +// TODO: replace with real register map +#define TAG "husb320" + +struct husb320_dev { + husb320_hw_cfg_t hw; + esp_typec_power_role_t role; +}; + +esp_err_t husb320_init(const husb320_hw_cfg_t *hw, husb320_dev_t **out) +{ + if (!hw || !out) { + return ESP_ERR_INVALID_ARG; + } + husb320_dev_t *dev = calloc(1, sizeof(*dev)); + if (!dev) { + return ESP_ERR_NO_MEM; + } + dev->hw = *hw; + dev->role = HUSB320_ROLE_SINK; + + // TODO: I2C probe, optional soft reset, clear INTs, enable CC measure + ESP_LOGI(TAG, "init i2c=%d addr=0x%02x (polling)", hw->i2c_port, hw->i2c_addr); + + *out = dev; + return ESP_OK; +} + +void husb320_deinit(husb320_dev_t *dev) +{ + if (!dev) { + return; + } + // TODO: put device into safe state if needed + free(dev); +} + +esp_err_t husb320_set_role(husb320_dev_t *dev, esp_typec_power_role_t role) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + dev->role = role; + // TODO: write Rp/Rd/DRP selection to device + ESP_LOGI(TAG, "set role=%d", (int)role); + return ESP_OK; +} + +esp_err_t husb320_read_cc_status(husb320_dev_t *dev, husb320_cc_status_t *st) +{ + if (!dev || !st) { + return ESP_ERR_INVALID_ARG; + } + // TODO: read CC comparators / status regs and fill fields. + // For now, report detached so the task runs harmlessly. + memset(st, 0, sizeof(*st)); + return ESP_OK; +} diff --git a/type_c_manager/CHANGELOG.md b/type_c_manager/CHANGELOG.md new file mode 100644 index 00000000..955767f5 --- /dev/null +++ b/type_c_manager/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog for USB type C/PD Manager + +## 0.1.0-beta + +- Initial version diff --git a/type_c_manager/CMakeLists.txt b/type_c_manager/CMakeLists.txt new file mode 100644 index 00000000..e5b4ee0f --- /dev/null +++ b/type_c_manager/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register( + SRCS "src/esp_typec_manager.c" + INCLUDE_DIRS "include" + PRIV_REQUIRES + type_c # for esp_typec_pd.h / esp_typec_pd_* + esp_event # for esp_event_handler_register/post + esp_driver_gpio + esp_driver_i2c + esp_tinyusb # for tusb.h (used internally) +) diff --git a/type_c_manager/LICENSE b/type_c_manager/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/type_c_manager/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/type_c_manager/README.md b/type_c_manager/README.md new file mode 100644 index 00000000..9f784543 --- /dev/null +++ b/type_c_manager/README.md @@ -0,0 +1,133 @@ +# ESP Type-C / USB Power Delivery (esp-usb/type_c) + +> **Status:** Beta API — names and behavior may change before v1.0. + +This component provides two focused, minimal public APIs for USB Type-C: + +- **Type-C Core (CC-only)** — attach/detach, orientation (CC1/CC2), and power-role presence (Rp/Rd). + Backed by **HUSB320** (Type-C CC controller; **no PD**). +- **USB Power Delivery (PD-only)** — PD negotiation, runtime power-role control, fixed/PPS sink requests, and contract reporting. + Backed by **FUSB302** (PD-capable TCPC). + +The split keeps builds small, avoids unnecessary coupling, and mirrors esp-usb / esp-idf style. + +--- + +## Contents + +- [`include/esp_typec.h`](./include/esp_typec.h) — **Type-C Core (CC-only)** public API +- [`include/esp_typec_pd.h`](./include/esp_typec_pd.h) — **PD-only** public API +- `src/` — stub sources to make the component build; real backends land incrementally +- `CMakeLists.txt`, `idf_component.yml` — component metadata + +> New files use: +> `/* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD */` +> `/* SPDX-License-Identifier: Apache-2.0 */` + +--- + +## Supported Controllers + +| Controller | Capability | API / Factory Function | Notes | +|-----------:|------------|-----------------------------------------------------------|--------------------------------| +| HUSB320 | Type-C CC | `esp_typec_port_create_husb320()` (in `esp_typec.h`) | CC attach/orientation only | +| FUSB302 | PD (TCPC) | `esp_typec_pd_port_create_fusb302()` (in `esp_typec_pd.h`)| Full PD PHY over CC (BMC) | + +> **Not supported here:** CC-only parts for PD; they cannot negotiate PD by design. +> Additional PD-capable TCPCs (e.g., PTN5110/TCPM family) can be added later using the same factory pattern. + +--- + +## Quick Start + +### 1) Type-C Core (CC-only) with HUSB320 + +```c +#include "esp_typec.h" + +static void on_typec(esp_typec_event_t evt, const void *payload, void *arg) +{ + switch (evt) { + case ESP_TYPEC_EVENT_ATTACHED: { + const esp_typec_evt_attached_t *a = payload; + // a->flags & ESP_TYPEC_FLAG_CC2 -> polarity + // a->rp_cur_ma -> advertised current (if known) + break; + } + case ESP_TYPEC_EVENT_ORIENTATION: { + const bool *cc2_active = payload; + (void)cc2_active; + break; + } + case ESP_TYPEC_EVENT_DETACHED: + default: break; + } +} + +void app_main(void) +{ + ESP_ERROR_CHECK(esp_typec_install()); + + esp_typec_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PWR_SINK, + .try_snk = true, + .try_src = false, + .task_stack = 0, + .task_prio = 0, + }; + esp_typec_husb320_config_t hw = { + .i2c_port = 0, .i2c_addr = 0x60, .gpio_int = 4, .use_intr = true, + }; + esp_typec_port_handle_t h; + ESP_ERROR_CHECK(esp_typec_port_create_husb320(&port_cfg, &hw, on_typec, NULL, &h)); + + // Optional: request a role change (sets Rp/Rd) + ESP_ERROR_CHECK(esp_typec_set_power_role(h, ESP_TYPEC_PWR_DRP)); +} +``` + +### 1) Power Delivery (PD) with FUSB302 + +```c +#include "esp_typec_pd.h" + +static void on_pd(esp_typec_pd_event_t evt, const void *payload, void *arg) +{ + switch (evt) { + case ESP_TYPEC_PD_EVENT_CONTRACT_READY: { + const esp_typec_pd_contract_t *c = payload; + // c->mv, c->ma, c->selected_pdo, c->flags (PPS/CC2) + break; + } + case ESP_TYPEC_PD_EVENT_ATTACHED: + case ESP_TYPEC_PD_EVENT_DETACHED: + default: break; + } +} + +void app_main(void) +{ + esp_typec_pd_install_config_t lib_cfg = { + .intr_flags = 0, + }; + ESP_ERROR_CHECK(esp_typec_pd_install(&lib_cfg)); + + esp_typec_pd_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PD_PWR_SINK, + .sink_i_min_ma = 500, + .sink_fixed_pref_mv = 9000, // 0 = auto select best + .enable_pps = true, + .sink_pps_v_min_mv = 5000, .sink_pps_v_max_mv = 11000, .sink_pps_i_max_ma = 2000, + .src_pdos = NULL, .src_pdo_count = 0, + .task_stack = 0, .task_prio = 0, + }; + esp_typec_pd_fusb302_config_t hw = { + .i2c_port = 0, .i2c_addr = 0x22, .gpio_int = 5, .use_intr = true, + }; + esp_typec_pd_port_handle_t h; + ESP_ERROR_CHECK(esp_typec_pd_port_create_fusb302(&port_cfg, &hw, on_pd, NULL, &h)); + + // Example: request a specific Fixed PDO after Source_Capabilities are received + // ESP_ERROR_CHECK(esp_typec_pd_sink_request_fixed(h, 9000, 2000)); +} +``` diff --git a/type_c_manager/examples/pd_fusb302_msc_device/CMakeLists.txt b/type_c_manager/examples/pd_fusb302_msc_device/CMakeLists.txt new file mode 100644 index 00000000..b635189a --- /dev/null +++ b/type_c_manager/examples/pd_fusb302_msc_device/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.16) + +# Tell IDF where to find the local components +set(EXTRA_COMPONENT_DIRS + "${CMAKE_CURRENT_LIST_DIR}/../.." # type_c_manager + "${CMAKE_CURRENT_LIST_DIR}/../../../type_c" # type_c + "${CMAKE_CURRENT_LIST_DIR}/../../../device/esp_tinyusb" # esp_tinyusb +) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(pd_fusb302_msc_device) + +# TinyUSB needs a root hub mode defined; provide a sane default per target. +idf_build_get_property(idf_target IDF_TARGET) +if(idf_target STREQUAL "esp32p4") + set(_tusb_rhport0 "CFG_TUSB_RHPORT0_MODE=(OPT_MODE_DEVICE|OPT_MODE_HIGH_SPEED)") +else() + set(_tusb_rhport0 "CFG_TUSB_RHPORT0_MODE=(OPT_MODE_DEVICE|OPT_MODE_FULL_SPEED)") +endif() +idf_build_set_property(COMPILE_DEFINITIONS "${_tusb_rhport0}" APPEND) +idf_build_set_property(COMPILE_DEFINITIONS "CFG_TUSB_RHPORT1_MODE=0" APPEND) +idf_build_set_property(COMPILE_DEFINITIONS "BOARD_TUD_RHPORT=0" APPEND) diff --git a/type_c_manager/examples/pd_fusb302_msc_device/main/CMakeLists.txt b/type_c_manager/examples/pd_fusb302_msc_device/main/CMakeLists.txt new file mode 100644 index 00000000..b9b63813 --- /dev/null +++ b/type_c_manager/examples/pd_fusb302_msc_device/main/CMakeLists.txt @@ -0,0 +1,12 @@ +idf_component_register( + SRCS "main.c" + PRIV_REQUIRES + esp_driver_i2c + esp_driver_gpio + esp_partition + wear_levelling + fatfs + type_c # for esp_typec_pd.h + type_c_manager # for esp_typec_manager.h / API + esp_tinyusb # TinyUSB integration component +) diff --git a/type_c_manager/examples/pd_fusb302_msc_device/main/main.c b/type_c_manager/examples/pd_fusb302_msc_device/main/main.c new file mode 100644 index 00000000..6a6f8576 --- /dev/null +++ b/type_c_manager/examples/pd_fusb302_msc_device/main/main.c @@ -0,0 +1,251 @@ +/* + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_check.h" +#include "esp_log.h" +#include "esp_partition.h" +#include "wear_levelling.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "tinyusb.h" +#include "tinyusb_default_config.h" +#include "tinyusb_msc.h" +#include "esp_typec_pd.h" +#include "esp_typec_manager.h" + +#define I2C_SDA 7 +#define I2C_SCL 8 +#define INT_GPIO 9 +#define FUSB_ADDR_7B 0x22 +#define SRC_VBUS_GPIO -1 // not used in sink device + +static const char *TAG = "example_pd_msc_device"; + +static void tusb_evt_handler(tinyusb_event_t *event, void *arg) +{ + (void)arg; + switch (event->id) { + case TINYUSB_EVENT_ATTACHED: + ESP_LOGI(TAG, "TinyUSB: configured by host"); + break; + case TINYUSB_EVENT_DETACHED: + ESP_LOGI(TAG, "TinyUSB: detached from host"); + break; + default: + break; + } +} + +/* -------------------------------------------------------------------------- */ +/* TinyUSB descriptors */ +/* -------------------------------------------------------------------------- */ +#define EPNUM_MSC 1 +#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_MSC_DESC_LEN) + +enum { + ITF_NUM_MSC = 0, + ITF_NUM_TOTAL +}; + +enum { + EDPT_MSC_OUT = 0x01, + EDPT_MSC_IN = 0x81, +}; + +static tusb_desc_device_t descriptor_config = { + .bLength = sizeof(descriptor_config), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .idVendor = 0x303A, // Espressif VID for examples; change for products + .idProduct = 0x4002, + .bcdDevice = 0x100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x03, + .bNumConfigurations = 0x01 +}; + +static uint8_t const msc_fs_configuration_desc[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 64), +}; + +#if (TUD_OPT_HIGH_SPEED) +static const tusb_desc_device_qualifier_t device_qualifier = { + .bLength = sizeof(tusb_desc_device_qualifier_t), + .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, + .bcdUSB = 0x0200, + .bDeviceClass = TUSB_CLASS_MISC, + .bDeviceSubClass = MISC_SUBCLASS_COMMON, + .bDeviceProtocol = MISC_PROTOCOL_IAD, + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + .bNumConfigurations = 0x01, + .bReserved = 0 +}; + +static uint8_t const msc_hs_configuration_desc[] = { + // Config number, interface count, string index, total length, attribute, power in mA + TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), + + // Interface number, string index, EP Out & EP In address, EP size + TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 0, EDPT_MSC_OUT, EDPT_MSC_IN, 512), +}; +#endif // TUD_OPT_HIGH_SPEED + +static char const *string_desc_arr[] = { + (const char[]) { 0x09, 0x04 }, // 0: English (0x0409) + "TinyUSB", // 1: Manufacturer + "TinyUSB MSC Device", // 2: Product + "123456", // 3: Serials + "Example MSC", // 4: MSC +}; + +/* -------------------------------------------------------------------------- */ +/* Helpers */ +/* -------------------------------------------------------------------------- */ +static esp_err_t init_tinyusb(void) +{ + tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG(tusb_evt_handler); + + tusb_cfg.descriptor.device = &descriptor_config; + tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc; + tusb_cfg.descriptor.string = string_desc_arr; + tusb_cfg.descriptor.string_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); +#if (TUD_OPT_HIGH_SPEED) + tusb_cfg.descriptor.high_speed_config = msc_hs_configuration_desc; + tusb_cfg.descriptor.qualifier = &device_qualifier; +#endif // TUD_OPT_HIGH_SPEED + + return tinyusb_driver_install(&tusb_cfg); +} + +static esp_err_t init_msc_storage(wl_handle_t *wl_handle, tinyusb_msc_storage_handle_t *storage_hdl) +{ + const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL); + ESP_RETURN_ON_FALSE(data_partition, ESP_ERR_NOT_FOUND, TAG, "No FATFS partition found"); + + ESP_RETURN_ON_ERROR(wl_mount(data_partition, wl_handle), TAG, "wl_mount failed"); + + tinyusb_msc_storage_config_t storage_cfg = { + .mount_point = TINYUSB_MSC_STORAGE_MOUNT_USB, // start exposed to host + .fat_fs = { + .base_path = NULL, // default base path (/data) + .config.max_files = 5, + .format_flags = 0, + }, + .medium = { + .wl_handle = *wl_handle, + }, + }; + + return tinyusb_msc_new_storage_spiflash(&storage_cfg, storage_hdl); +} + +static esp_err_t init_pd_stack(esp_typec_pd_port_handle_t *port_out, i2c_master_bus_handle_t *bus_out) +{ + ESP_RETURN_ON_ERROR(esp_typec_pd_install(NULL), TAG, "pd install failed"); + ESP_LOGI(TAG, "PD core installed"); + + i2c_master_bus_config_t bus_cfg = { + .i2c_port = 0, + .sda_io_num = I2C_SDA, + .scl_io_num = I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags = { .enable_internal_pullup = 1 }, + }; + ESP_RETURN_ON_ERROR(i2c_new_master_bus(&bus_cfg, bus_out), TAG, "i2c bus init failed"); + + i2c_device_config_t dev_cfg = { + .device_address = FUSB_ADDR_7B, + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .scl_speed_hz = 400000, + }; + i2c_master_dev_handle_t fusb_i2c = NULL; + ESP_RETURN_ON_ERROR(i2c_master_bus_add_device(*bus_out, &dev_cfg, &fusb_i2c), TAG, "fusb add failed"); + + esp_typec_pd_port_config_t port_cfg = { + .default_power_role = ESP_TYPEC_PWR_SINK, + .task_stack = 4096, + .task_prio = 5, + .rp_current = ESP_TYPEC_RP_DEFAULT, + .src_vbus_gpio = SRC_VBUS_GPIO, + }; + + esp_typec_pd_fusb302_config_t hw = { + .i2c_dev = fusb_i2c, + .gpio_int = INT_GPIO, + }; + + ESP_RETURN_ON_ERROR(esp_typec_pd_port_create_fusb302(&port_cfg, &hw, port_out), TAG, "pd port create failed"); + ESP_LOGI(TAG, "PD port created"); + return ESP_OK; +} + +static esp_err_t start_typec_manager(esp_typec_pd_port_handle_t port, esp_typec_manager_handle_t *manager_out) +{ + esp_typec_manager_config_t manager_cfg = { + .mode = ESP_TYPEC_MANAGER_MODE_SINK_DEVICE, + .pd_port = port, + .min_rp_cur_ma = 500, // require at least 500 mA before enumerating + }; + ESP_RETURN_ON_ERROR(esp_typec_manager_install(&manager_cfg, manager_out), TAG, "manager install failed"); + ESP_LOGI(TAG, "Type-C manager installed"); + return ESP_OK; +} + +/* -------------------------------------------------------------------------- */ +/* Main */ +/* -------------------------------------------------------------------------- */ +void app_main(void) +{ + ESP_LOGI(TAG, "Starting TinyUSB MSC + Type-C PD example"); + + // 1) Bring up TinyUSB stack (manager will connect/disconnect on PD events) + ESP_ERROR_CHECK(init_tinyusb()); + ESP_LOGI(TAG, "TinyUSB initialized"); + + // 2) Initialize PD stack and Type-C manager + esp_typec_pd_port_handle_t pd_port = NULL; + i2c_master_bus_handle_t i2c_bus = NULL; + ESP_ERROR_CHECK(init_pd_stack(&pd_port, &i2c_bus)); + + esp_typec_manager_handle_t manager = NULL; + ESP_ERROR_CHECK(start_typec_manager(pd_port, &manager)); + + // 3) Prepare MSC storage on SPI flash (FATFS partition required) + wl_handle_t wl_handle = WL_INVALID_HANDLE; + tinyusb_msc_storage_handle_t storage_hdl = NULL; + ESP_ERROR_CHECK(init_msc_storage(&wl_handle, &storage_hdl)); + + // Expose storage to host; manager will connect when PD attached + ESP_ERROR_CHECK(tinyusb_msc_set_storage_mount_point(storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB)); + + ESP_LOGI(TAG, "Setup complete. Attach USB-C power/host to enumerate MSC device."); + + // Keep task alive; PD/Type-C manager drives tud_connect/disconnect. + while (true) { + bool isPresent = false; + vTaskDelay(pdMS_TO_TICKS(1000)); + if (ESP_OK != esp_typec_manager_is_bus_present(manager, &isPresent)) { + ESP_LOGE(TAG, "Failed to get bus present status"); + } + if (isPresent) { + ESP_LOGI(TAG, "Type-C cable is connected"); + } + } +} diff --git a/type_c_manager/examples/pd_fusb302_msc_device/partitions.csv b/type_c_manager/examples/pd_fusb302_msc_device/partitions.csv new file mode 100644 index 00000000..1c79321a --- /dev/null +++ b/type_c_manager/examples/pd_fusb302_msc_device/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +storage, data, fat, , 1M, diff --git a/type_c_manager/examples/pd_fusb302_msc_device/sdkconfig.defaults b/type_c_manager/examples/pd_fusb302_msc_device/sdkconfig.defaults new file mode 100644 index 00000000..cf5dd194 --- /dev/null +++ b/type_c_manager/examples/pd_fusb302_msc_device/sdkconfig.defaults @@ -0,0 +1,16 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_TINYUSB_MSC_ENABLED=y + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_MODE_PERF=y + +CONFIG_FATFS_LFN_HEAP=y + +# On chips with USB Serial JTAG, disable secondary console which does not make sense when using console component +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y diff --git a/type_c_manager/idf_component.yml b/type_c_manager/idf_component.yml new file mode 100644 index 00000000..12599be6 --- /dev/null +++ b/type_c_manager/idf_component.yml @@ -0,0 +1,23 @@ +description: "ESP-IDF USB Type-C/PD manager (beta)" +version: "0.1.0-beta" +url: https://github.com/espressif/esp-usb/tree/master/type_c_manager +issues: "https://github.com/espressif/esp-usb/issues" +repository: "https://github.com/espressif/esp-usb.git" +repository_info: + path: "type_c_manager" + +targets: + - esp32s2 + - esp32s3 + - esp32p4 + - esp32h4 + +files: + exclude: + - "test/**/*" + +keywords: + - usb + - usb_typec + - usb_pd + - usb_typec_manager diff --git a/type_c_manager/include/esp_typec_manager.h b/type_c_manager/include/esp_typec_manager.h new file mode 100644 index 00000000..401c27a9 --- /dev/null +++ b/type_c_manager/include/esp_typec_manager.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" +#include "esp_event.h" + +#include "esp_typec_pd.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct esp_typec_manager_ctx *esp_typec_manager_handle_t; + +/** + * High-level usage mode of the Type-C manager. + * + * For now we only implement SINK_DEVICE, but we keep the enum open. + */ +typedef enum { + ESP_TYPEC_MANAGER_MODE_SINK_DEVICE = 0, //!< act as Type-C sink + USB device (TinyUSB) + ESP_TYPEC_MANAGER_MODE_SOURCE_HOST, //!< (future) Type-C source + USB host + ESP_TYPEC_MANAGER_MODE_DRP_DEVICE_HOST, //!< (future) DRP with role swap +} esp_typec_manager_mode_t; + +/** + * Configuration for the manager. + * + * The application is responsible for: + * - initializing TinyUSB (device stack) before calling esp_typec_manager_install() + * - creating a PD port (esp_typec_pd_port_create_fusb302, etc.) + */ +typedef struct { + esp_typec_manager_mode_t mode; + + // PD port we should follow + esp_typec_pd_port_handle_t pd_port; + + // Optional: only connect TinyUSB if advertised Rp is at least this (mA). + // 0 = ignore current, any valid attach will connect. + uint32_t min_rp_cur_ma; + + // Future: we could add callbacks or extra policy flags here. +} esp_typec_manager_config_t; + +/** + * Install and start the Type-C manager. + * + * - Does NOT initialize TinyUSB; you must do that before calling here. + * - Subscribes to ESP_TYPEC_PD_EVENT ATTACHED/DETACHED. + * - In SINK_DEVICE mode, it will: + * - call tud_disconnect() initially + * - on ATTACHED as sink: if Rp >= min_rp_cur_ma, call tud_connect() + * - on DETACHED: call tud_disconnect() + */ +esp_err_t esp_typec_manager_install(const esp_typec_manager_config_t *cfg, + esp_typec_manager_handle_t *out); + +/** + * Uninstall the manager. + * - Unregisters event handlers. + * - Does NOT deinit TinyUSB or the PD port. + */ +esp_err_t esp_typec_manager_uninstall(esp_typec_manager_handle_t handle); + +/** + * Optional helper: query whether the manager currently sees the port as + * "USB bus should be connected" (i.e. attached and policy satisfied). + */ +esp_err_t esp_typec_manager_is_bus_present(esp_typec_manager_handle_t handle, bool *present); + +#ifdef __cplusplus +} +#endif diff --git a/type_c_manager/src/esp_typec_manager.c b/type_c_manager/src/esp_typec_manager.c new file mode 100644 index 00000000..ba6c9156 --- /dev/null +++ b/type_c_manager/src/esp_typec_manager.c @@ -0,0 +1,126 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_typec_manager.h" +#include "esp_event.h" +#include "esp_check.h" +#include "esp_log.h" +#include "tusb.h" // for tud_connect()/tud_disconnect() when in SINK_DEVICE mode +// (and later usb_host.h for SOURCE_HOST) + +typedef struct esp_typec_manager_ctx { + esp_typec_manager_config_t cfg; + esp_typec_pd_port_handle_t pd_port; + bool attached; + bool cc2_active; + uint32_t rp_ma; + esp_event_handler_instance_t evt_inst; // so we can unregister later +} esp_typec_manager_ctx_t; + +static const char *TAG = "esp_typec_manager"; + +static void pd_evt_handler(void *arg, esp_event_base_t base, + int32_t id, void *data) +{ + esp_typec_manager_ctx_t *manager = arg; + (void)base; + + switch (id) { + case ESP_TYPEC_PD_EVENT_ID_ATTACHED: { + const esp_typec_pd_evt_attached_t *a = data; + // Ignore events from other ports (future multi-port support) + if (manager->pd_port && manager->pd_port != a->port) { + return; + } + manager->attached = true; + manager->cc2_active = a->cc2_active; + manager->rp_ma = a->rp_cur_ma; + + if (manager->cfg.mode == ESP_TYPEC_MANAGER_MODE_SINK_DEVICE && + a->rp_cur_ma >= manager->cfg.min_rp_cur_ma) { + tud_connect(); + ESP_LOGI(TAG, "Connecting USB device (TinyUSB)"); + } else { + ESP_LOGI(TAG, "Attach ignored: rp_cur_ma=%"PRIu32" < min_rp_cur_ma=%"PRIu32, + a->rp_cur_ma, manager->cfg.min_rp_cur_ma); + } + break; + } + case ESP_TYPEC_PD_EVENT_ID_DETACHED: + manager->attached = false; + + if (manager->cfg.mode == ESP_TYPEC_MANAGER_MODE_SINK_DEVICE) { + tud_disconnect(); + ESP_LOGI(TAG, "Disconnecting USB device (TinyUSB)"); + } + break; + + default: + break; + } +} + +esp_err_t esp_typec_manager_install(const esp_typec_manager_config_t *cfg, + esp_typec_manager_handle_t *out) +{ + ESP_RETURN_ON_FALSE(cfg && out, ESP_ERR_INVALID_ARG, TAG, "bad args"); + + esp_typec_manager_ctx_t *manager = calloc(1, sizeof(*manager)); + ESP_RETURN_ON_FALSE(manager, ESP_ERR_NO_MEM, TAG, "no mem"); + + manager->cfg = *cfg; + manager->pd_port = cfg->pd_port; + + // Start with device logically disconnected + if (cfg->mode == ESP_TYPEC_MANAGER_MODE_SINK_DEVICE) { + tud_disconnect(); + } + + // Register for PD events + esp_err_t err = esp_event_handler_instance_register( + ESP_TYPEC_PD_EVENT, + ESP_EVENT_ANY_ID, + &pd_evt_handler, + manager, + &manager->evt_inst); + if (err != ESP_OK) { + free(manager); + return err; + } + + *out = (esp_typec_manager_handle_t)manager; + return ESP_OK; +} + +esp_err_t esp_typec_manager_uninstall(esp_typec_manager_handle_t handle) +{ + esp_typec_manager_ctx_t *manager = (esp_typec_manager_ctx_t *)handle; + if (!manager) { + return ESP_ERR_INVALID_ARG; + } + + if (manager->evt_inst) { + esp_event_handler_instance_unregister( + ESP_TYPEC_PD_EVENT, + ESP_EVENT_ANY_ID, + manager->evt_inst); + } + + free(manager); + return ESP_OK; +} + +esp_err_t esp_typec_manager_is_bus_present(esp_typec_manager_handle_t handle, bool *present) +{ + esp_typec_manager_ctx_t *manager = (esp_typec_manager_ctx_t *)handle; + if (!manager || !present) { + return ESP_ERR_INVALID_ARG; + } + + // simple policy: attached + Rp >= min_rp_cur_ma + *present = manager->attached && + (manager->rp_ma >= manager->cfg.min_rp_cur_ma); + return ESP_OK; +}