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..ba5c184a --- /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 +) 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..05a90a30 --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) + +# Tell IDF where to find the local "type_c" component (two levels up). +# This lets the example depend on your in-tree component without copying it. +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..e59f8187 --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/main/main.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "esp_log.h" + +/* Make sure TickType_t is known before including our PD header */ +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#include "esp_typec_pd.h" // your library header + +static const char *TAG = "example_pd"; + +/* ===== Event callback ===== */ +static void on_pd_event(esp_typec_pd_event_t evt, const void *data, void *arg) +{ + (void)arg; + switch (evt) { + case ESP_TYPEC_PD_EVENT_ATTACHED: { + const esp_typec_pd_evt_attached_t *a = (const esp_typec_pd_evt_attached_t *)data; + bool cc2 = (a && (a->flags & ESP_TYPEC_PD_FLAG_CC2)); + 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_DETACHED: + ESP_LOGI(TAG, "DETACHED"); + break; + case ESP_TYPEC_PD_EVENT_ERROR: + default: + ESP_LOGW(TAG, "EVENT %d", evt); + break; + } +} + +/* ===== App entry ===== */ +void app_main(void) +{ + ESP_LOGI("main_task", "Calling app_main()"); + + /* --- 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; /* from your bring-up */ + + 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, + /* only fields guaranteed by your current header */ + }; + + esp_typec_pd_fusb302_config_t hw = { + .i2c_dev = fusb_i2c, + .gpio_int = INT_GPIO, + }; + + /* If your library requires install(), call it here; otherwise skip */ + // ESP_ERROR_CHECK(esp_typec_pd_install(NULL)); + + ESP_ERROR_CHECK(esp_typec_pd_port_create_fusb302(&port_cfg, &hw, + on_pd_event, NULL, &port)); + ESP_ERROR_CHECK(esp_typec_pd_set_power_role(port, ESP_TYPEC_PWR_SOURCE)); + 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..6082d323 --- /dev/null +++ b/type_c/include/esp_typec_pd.h @@ -0,0 +1,198 @@ +/* + * 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 + +#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 PD event types for Type-C stack. + */ +typedef enum { + ESP_TYPEC_PD_EVENT_ATTACHED = 0, /**< CC attach detected. payload: esp_typec_pd_evt_attached_t */ + ESP_TYPEC_PD_EVENT_DETACHED, /**< CC detach detected. no payload */ + ESP_TYPEC_PD_EVENT_ERROR, /**< Error occurred. payload: TBD */ +} esp_typec_pd_event_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_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 Event payload for PD ATTACHED event. + */ +typedef struct { + uint32_t flags; /**< ESP_TYPEC_PD_FLAG_CC2 if CC2 active */ + 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 Opaque handle for a PD port instance. + */ +typedef void *esp_typec_pd_port_handle_t; + +/** + * @brief Event callback signature for PD events. + * + * @param evt Event type + * @param event_data Pointer to event payload + * @param user_arg User argument + */ +typedef void (*esp_typec_pd_event_cb_t)(esp_typec_pd_event_t evt, + const void *event_data, + void *user_arg); + +/** + * @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 Handle library-global events (stub; all work is per-port). + * @param timeout_ticks Timeout for event handling + * @param event_flags_ret Pointer to event flags output + * @return ESP_OK on success + */ +esp_err_t esp_typec_pd_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret); + +/** + * @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). + * @param port_cfg Port configuration + * @param hw_cfg Hardware configuration for FUSB302 + * @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_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_event_cb_t cb, void *cb_arg, + 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..9adca749 --- /dev/null +++ b/type_c/include_private/fusb302_ctrl.h @@ -0,0 +1,87 @@ +/* + * 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 "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 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, + typec_cc_status_t *st_out); + + +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..b1a4c6b8 --- /dev/null +++ b/type_c/src/esp_typec_pd.c @@ -0,0 +1,344 @@ +/* + * 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" + +/* ---------- 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, typec_cc_status_t *st); + 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 + callback + TaskHandle_t task; + esp_typec_pd_event_cb_t cb; + void *cb_arg; + + // Cached state + bool attached; + bool cc2_active; + uint32_t rp_ma; + + // Snapshot of config (optional) + esp_typec_pd_port_config_t cfg; +} pd_port_ctx_t; + +/* ---------- Tiny helpers ---------- */ +static inline void pd_emit_attached(pd_port_ctx_t *ctx) +{ + if (!ctx->cb) { + return; + } + esp_typec_pd_evt_attached_t a = { + .flags = ctx->cc2_active ? ESP_TYPEC_PD_FLAG_CC2 : 0, + .rp_cur_ma = ctx->rp_ma, + }; + ctx->cb(ESP_TYPEC_PD_EVENT_ATTACHED, &a, ctx->cb_arg); +} + +static inline void pd_emit_detached(pd_port_ctx_t *ctx) +{ + if (!ctx->cb) { + return; + } + ctx->cb(ESP_TYPEC_PD_EVENT_DETACHED, NULL, ctx->cb_arg); +} + +/* ---------- 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 = {0}; + + /* Drain IRQs and get a fresh snapshot in one call */ + ctx->backend->service_irq(ctx->ctrl, &ev, &st); + + /* VBUS-driven attach/detach (sink policy) */ + if (ev & PD_EVT_VBUS) { + if (st.vbus_ok && !ctx->attached) { + // VBUS became valid -> read CC once for orientation/current + 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); + } + } + + /* Optional: while attached, reflect advertised current changes quietly */ + if ((ev & PD_EVT_CC) && ctx->attached) { + ctx->backend->get_status(ctx->ctrl, &st); + ctx->rp_ma = st.rp_cur_ma; + } + + /* 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, &st); + } + + 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; + return ESP_OK; +} + +esp_err_t esp_typec_pd_uninstall(void) +{ + return ESP_OK; +} + +esp_err_t esp_typec_pd_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret) +{ + (void)timeout_ticks; + (void)event_flags_ret; + // No library-global pump; all work is per-port task + 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 *, typec_cc_status_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_event_cb_t cb, void *cb_arg, + esp_typec_pd_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"); + + 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->cb = cb; + ctx->cb_arg = cb_arg; + ctx->backend = &fusb302_backend; + + // 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"); + ctx->ctrl = dev; + + 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"); + int lvl = gpio_get_level(ctx->gpio_int); + ESP_LOGI(TAG, "INT gpio=%d configured, current level=%d", ctx->gpio_int, lvl); + + 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; + typec_cc_status_t st = (typec_cc_status_t) { + 0 + }; + ESP_GOTO_ON_ERROR(ctx->backend->service_irq(ctx->ctrl, &ev, &st), 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) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx || !ctx->backend) { + return ESP_ERR_INVALID_ARG; + } + return ctx->backend->set_role(ctx->ctrl, role); +} + +/* 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..1f2f7c70 --- /dev/null +++ b/type_c/src/fusb302_ctrl.c @@ -0,0 +1,418 @@ +/* + * 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 */ +}; + +/* 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 + +/* 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; + + /* Sink base: Rd on both CCs, clear MEAS, no Rp/VCONN */ + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_SWITCHES0, &sw0), TAG, "rd SW0"); + sw0 &= ~(SW0_CC1_PU_EN | SW0_CC2_PU_EN | SW0_VCONN_CC1 | SW0_VCONN_CC2 | SW0_MEAS_CC2); + sw0 |= (SW0_CC1_PD_EN | SW0_CC2_PD_EN | SW0_MEAS_CC1); + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, sw0), TAG, "meas CC1"); + esp_rom_delay_us(100); + + /* Measure 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); + + /* Measure CC2 */ + sw0 = (sw0 & ~SW0_MEAS_CC1) | SW0_MEAS_CC2; + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, sw0), 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); + + /* VBUS info */ + bool vbus_ok = (st0 & ST0_VBUSOK) != 0; + + 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; + st->vbus_ok = vbus_ok; + + 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 : 0; + + ESP_LOGI(TAG, "CC: att=%d act=%s CC1=0x%02X CC2=0x%02X Rp=%dmA VBUSOK=%d", + st->attached, st->cc2_active ? "CC2" : "CC1", + bclvl_cc1, bclvl_cc2, 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; + *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 | CTL0_HOST_CUR_MEDIUM; // POWERROLE=1 (source), 180µA Rp for 1.5A + 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 |= CTL0_HOST_CUR_MEDIUM; // HOST_CUR for 1.5A + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_CONTROL0, value), TAG, "set HOST_CUR"); + // (If needed, handle VCONN enable here if supporting powered cables) + ESP_LOGI(TAG, "FUSB302 set as Source"); + 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 |= CTL0_HOST_CUR_MEDIUM; + 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_service_irq(fusb302_dev_t *dev, + typec_evt_mask_t *events, + typec_cc_status_t *st_out) +{ + if (!dev || !events || !st_out) { + 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); + } + + // Always return a fresh snapshot at the end + return fusb302_measure_status(dev, st_out); +} + +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; +}