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..50fbf56e --- /dev/null +++ b/type_c/examples/pd_fusb302_attach/main/main.c @@ -0,0 +1,116 @@ +/* + * 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)); + + /* --- INT GPIO (active-low, input, pull-up) --- */ + gpio_config_t io = { + .pin_bit_mask = 1ULL << INT_GPIO, + .mode = GPIO_MODE_INPUT, + .pull_up_en = 1, + .pull_down_en = 0, + .intr_type = GPIO_INTR_ANYEDGE, /* backend will set handler; here it's safe */ + }; + ESP_ERROR_CHECK(gpio_config(&io)); + + /* --- 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_PD_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, /* NEW: pass device handle, not (port,addr) */ + .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_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..c293bbc8 --- /dev/null +++ b/type_c/include/esp_typec.h @@ -0,0 +1,162 @@ +/* + * 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 ------------------------- + +typedef struct { + int num_ports; /**< Number of created ports */ +} esp_typec_lib_info_t; + +// ------------------------------------------------ Library Functions -------------------------------------------------- + +esp_err_t esp_typec_install(void); +esp_err_t esp_typec_uninstall(void); +esp_err_t esp_typec_lib_info(esp_typec_lib_info_t *info_ret); + +// ------------------------------------------------- Port Functions (HUSB320) ----------------------------------------- + +/** 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; + +/** Create a Type‑C CC-only port using HUSB320 (no PD). */ +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); + +/** Destroy a Type‑C port. */ +esp_err_t esp_typec_port_destroy(esp_typec_port_handle_t port_hdl); + +/** Power-role control (CC Rp/Rd). */ +esp_err_t esp_typec_set_power_role(esp_typec_port_handle_t port_hdl, esp_typec_power_role_t role); + +/** Query orientation and attach state. */ +esp_err_t esp_typec_get_orientation(esp_typec_port_handle_t port_hdl, bool *cc2_active); +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..240983fb --- /dev/null +++ b/type_c/include/esp_typec_pd.h @@ -0,0 +1,112 @@ +/* + * 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 ===== */ + +typedef enum { + ESP_TYPEC_PD_PWR_SINK = 0, + ESP_TYPEC_PD_PWR_SOURCE, + ESP_TYPEC_PD_PWR_DRP, +} esp_typec_pd_power_role_t; + +typedef enum { + ESP_TYPEC_PD_EVENT_ATTACHED = 0, + ESP_TYPEC_PD_EVENT_DETACHED, + ESP_TYPEC_PD_EVENT_ERROR, +} esp_typec_pd_event_t; + +/* For esp_typec_pd_evt_attached_t.flags */ +#define ESP_TYPEC_PD_FLAG_CC2 (1u << 0) /* Orientation: CC2 active when set */ + +/* ===== Public structs ===== */ + +typedef struct { + /* Currently no global tunables; keep for forward-compat */ + uint32_t reserved; +} esp_typec_pd_install_config_t; + +typedef struct { + uint32_t num_ports; +} esp_typec_pd_lib_info_t; + +typedef struct { + /* Default role to apply at bring-up */ + esp_typec_pd_power_role_t default_power_role; + + /* Optional task sizing (bytes). 0 -> default (4096) */ + uint32_t task_stack; + + /* Optional task priority. 0 -> default (idle + 2) */ + uint32_t task_prio; +} esp_typec_pd_port_config_t; + +/* Backend-specific runtime wiring for FUSB302 */ +typedef struct { + i2c_master_dev_handle_t i2c_dev; /* NEW: handle from esp_driver_i2c */ + int gpio_int; /* Active-low INT GPIO from the chip */ +} esp_typec_pd_fusb302_config_t; + +/* Event payloads */ +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; + +/* Optional: placeholder for future PD contract details */ +typedef struct { + uint32_t mv; + uint32_t ma; +} esp_typec_pd_contract_t; + +/* Opaque port handle */ +typedef void *esp_typec_pd_port_handle_t; + +/* Event callback signature */ +typedef void (*esp_typec_pd_event_cb_t)(esp_typec_pd_event_t evt, + const void *event_data, + void *user_arg); + +/* ===== Public API ===== */ + +esp_err_t esp_typec_pd_install(const esp_typec_pd_install_config_t *config); +esp_err_t esp_typec_pd_uninstall(void); + +esp_err_t esp_typec_pd_lib_handle_events(TickType_t timeout_ticks, uint32_t *event_flags_ret); +esp_err_t esp_typec_pd_lib_info(esp_typec_pd_lib_info_t *info_ret); + +/* Create a PD port backed by FUSB302 (I²C + INT). */ +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 esp_typec_pd_port_destroy(esp_typec_pd_port_handle_t port_hdl); + +esp_err_t esp_typec_pd_set_power_role(esp_typec_pd_port_handle_t port_hdl, + esp_typec_pd_power_role_t role); + +/* Sink requests (PD) — stubs for now; return ESP_ERR_NOT_SUPPORTED */ +esp_err_t esp_typec_pd_sink_request_fixed(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma); +esp_err_t esp_typec_pd_sink_request_pps(esp_typec_pd_port_handle_t port, uint32_t mv, uint32_t ma); +esp_err_t esp_typec_pd_get_contract(esp_typec_pd_port_handle_t port, esp_typec_pd_contract_t *out); + +/* Simple status helpers */ +esp_err_t esp_typec_pd_get_orientation(esp_typec_pd_port_handle_t port_hdl, bool *cc2_active); +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..5fb774f3 --- /dev/null +++ b/type_c/include_private/fusb302_ctrl.h @@ -0,0 +1,70 @@ +/* + * 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" + +#ifdef __cplusplus +extern "C" { +#endif + +/* Opaque device handle */ +typedef struct fusb302_dev fusb302_dev_t; + +/* Power/connection role */ +typedef enum { + FUSB302_ROLE_SINK = 0, + FUSB302_ROLE_SOURCE = 1, + FUSB302_ROLE_DRP = 2, +} fusb302_role_t; + +/* Simple CC status snapshot */ +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) */ +} fusb302_cc_status_t; + +/* Hardware wiring/config. + * NOTE: The application must create the I2C bus and device, then pass the + * device handle here. INT pin is 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; + +/* ---- Driver API ---- */ + +/* Initialize and configure the FUSB302 with given hardware config. */ +esp_err_t fusb302_init(const fusb302_hw_cfg_t *hw, fusb302_dev_t **out); + +/* Put the chip into a safe low-power state and free the handle. */ +esp_err_t fusb302_deinit(fusb302_dev_t *dev); + +/* Change power role (Sink/Source/DRP). Minimal register programming only. */ +esp_err_t fusb302_set_role(fusb302_dev_t *dev, fusb302_role_t role); + +/* Optional helper: enable/disable FUSB302 interrupt sources (MASK regs). */ +esp_err_t fusb302_enable_irq(fusb302_dev_t *dev, bool enable); + +/* Read/clear interrupt cause registers (read-to-clear). Any pointer may be NULL. */ +esp_err_t fusb302_get_and_clear_int(fusb302_dev_t *dev, + uint8_t *int0, uint8_t *inta, uint8_t *intb); + +/* Drain interrupts until INT deasserts (active-high on pin). Sets had_any=true if any cause was seen. */ +esp_err_t fusb302_service_irq(fusb302_dev_t *dev, bool *had_any); + +/* Sample CC state (attached, orientation, advertised current). */ +esp_err_t fusb302_read_cc_status(fusb302_dev_t *dev, fusb302_cc_status_t *st); + +#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..df68b41a --- /dev/null +++ b/type_c/include_private/husb320_ctrl.h @@ -0,0 +1,106 @@ +/* + * 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 Power role for HUSB320. + */ +typedef enum { + HUSB320_ROLE_SINK = 0, /**< Sink role */ + HUSB320_ROLE_SOURCE, /**< Source role */ + HUSB320_ROLE_DRP, /**< Dual-role power */ +} husb320_role_t; + +esp_err_t husb320_set_role(husb320_dev_t *dev, husb320_role_t role); + +/** + * @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, husb320_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/src/esp_typec.c b/type_c/src/esp_typec.c new file mode 100644 index 00000000..f7373272 --- /dev/null +++ b/type_c/src/esp_typec.c @@ -0,0 +1,271 @@ +/* + * 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" + +// ---- Library state (simple singleton for now) ---- +static bool s_lib_installed = false; + +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) +{ + if (s_lib_installed) { + return ESP_ERR_INVALID_STATE; + } + s_lib_installed = true; + return ESP_OK; +} + +esp_err_t esp_typec_uninstall(void) +{ + if (!s_lib_installed) { + return ESP_ERR_INVALID_STATE; + } + s_lib_installed = false; + 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(s_lib_installed, ESP_ERR_INVALID_STATE, TAG, "library not installed"); + 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 + husb320_role_t role = HUSB320_ROLE_SINK; + switch (port_cfg->default_power_role) { + case ESP_TYPEC_PWR_SOURCE: role = HUSB320_ROLE_SOURCE; break; + case ESP_TYPEC_PWR_DRP: role = HUSB320_ROLE_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; + } + + husb320_role_t r = HUSB320_ROLE_SINK; + switch (role) { + case ESP_TYPEC_PWR_SOURCE: r = HUSB320_ROLE_SOURCE; break; + case ESP_TYPEC_PWR_DRP: r = HUSB320_ROLE_DRP; break; + default: break; + } + return husb320_set_role(ctx->ctrl, r); +} + +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..a2b395a1 --- /dev/null +++ b/type_c/src/esp_typec_pd.c @@ -0,0 +1,338 @@ +/* + * 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 "fusb302_ctrl.h" // backend (now uses i2c_master.*) + +#define TAG "esp_typec_pd" + +/* ---------- Per-port context ---------- */ +typedef struct pd_port_ctx { + // Backend handle + fusb302_dev_t *ctrl; + + // 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 = (pd_port_ctx_t *)arg; + + // Ensure line starts enabled + gpio_intr_enable(ctx->gpio_int); + + for (;;) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + // Drain device interrupts (read-to-clear) + bool had_irq = false; + (void)fusb302_service_irq(ctx->ctrl, &had_irq); + + // Sample CC status (contains its own small settle if needed) + fusb302_cc_status_t st = {0}; + if (fusb302_read_cc_status(ctx->ctrl, &st) == ESP_OK) { + // Simple 1-step confirmation debounce for attach/orientation transitions + bool changed = (st.attached != ctx->attached) || + (st.attached && (st.cc2_active != ctx->cc2_active)); + + if (changed) { + esp_rom_delay_us(3000); + fusb302_cc_status_t st2 = {0}; + if (fusb302_read_cc_status(ctx->ctrl, &st2) == ESP_OK && + st2.attached == st.attached && + (!st2.attached || st2.cc2_active == st.cc2_active)) { + + if (!ctx->attached && st2.attached) { + ctx->attached = true; + ctx->cc2_active = st2.cc2_active; + ctx->rp_ma = st2.rp_cur_ma; + pd_emit_attached(ctx); + } else if (ctx->attached && !st2.attached) { + ctx->attached = false; + pd_emit_detached(ctx); + } else { + ctx->cc2_active = st2.cc2_active; // orientation flip while attached + } + } + } else if (ctx->attached && st.rp_cur_ma != ctx->rp_ma) { + // Optional: track current changes silently + ctx->rp_ma = st.rp_cur_ma; + } + } + + // If INT pin is still asserted LOW (rare), give the chip a breath and check once + if (gpio_get_level(ctx->gpio_int) == 0) { + esp_rom_delay_us(300); + (void)fusb302_service_irq(ctx->ctrl, &had_irq); + } + + // Re-enable GPIO IRQ + 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; // no global resources to init right now + 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; + } + // If you later track ports globally, update this + info_ret->num_ports = 0; + return ESP_OK; +} + +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; + + // 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, + }; + ESP_RETURN_ON_ERROR(fusb302_init(&hw, &ctx->ctrl), TAG, "ctrl init failed"); + + ctx->gpio_int = hw_cfg->gpio_int; + + // Put default role from config + fusb302_role_t role = FUSB302_ROLE_SINK; + switch (port_cfg->default_power_role) { + case ESP_TYPEC_PD_PWR_SOURCE: role = FUSB302_ROLE_SOURCE; break; + case ESP_TYPEC_PD_PWR_DRP: role = FUSB302_ROLE_DRP; break; + default: break; + } + ESP_RETURN_ON_ERROR(fusb302_set_role(ctx->ctrl, role), TAG, "set role"); + + // 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_GOTO_ON_ERROR(gpio_install_isr_service(0), fail, TAG, "gpio_isr_service"); + ESP_GOTO_ON_ERROR(gpio_isr_handler_add(ctx->gpio_int, pd_gpio_isr, ctx), fail, TAG, "gpio_isr_add"); + ESP_GOTO_ON_ERROR(fusb302_enable_irq(ctx->ctrl, true), fail, TAG, "fusb irq enable"); + + // 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); + fusb302_deinit(ctx->ctrl); + free(ctx); + return ESP_ERR_NO_MEM; + } + + // Finally enable device IRQs and unmask GPIO + (void)fusb302_enable_irq(ctx->ctrl, true); + gpio_intr_enable(ctx->gpio_int); + + *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) { + fusb302_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) { + fusb302_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_pd_power_role_t role) +{ + pd_port_ctx_t *ctx = (pd_port_ctx_t *)port_hdl; + if (!ctx) { + return ESP_ERR_INVALID_ARG; + } + + fusb302_role_t r = FUSB302_ROLE_SINK; + switch (role) { + case ESP_TYPEC_PD_PWR_SOURCE: r = FUSB302_ROLE_SOURCE; break; + case ESP_TYPEC_PD_PWR_DRP: r = FUSB302_ROLE_DRP; break; + default: break; + } + return fusb302_set_role(ctx->ctrl, r); +} + +/* 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..409b56e3 --- /dev/null +++ b/type_c/src/fusb302_ctrl.c @@ -0,0 +1,436 @@ +/* + * 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" + +#define TAG "fusb302" + +/* NOTE: + * Only migrated to the new i2c_master API (esp_driver_i2c). No other logic changes. + */ + +struct fusb302_dev { + fusb302_hw_cfg_t hw; + fusb302_role_t 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 (not used for CC measure; it’s TX/datapath/specrev) */ +#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 */ + +/* CONTROL2 MODE bits */ +#define CTL2_MODE_MASK (0x6) +#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) ---------------- */ + +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*/); +} + +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); +} + +static esp_err_t fusb302_probe(fusb302_dev_t *dev) __attribute__((unused)); +static esp_err_t fusb302_probe(fusb302_dev_t *dev) +{ + uint8_t val = 0; + esp_err_t ret = i2c_rd8(dev->hw.i2c_dev, REG_DEVICE_ID, &val); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "probe failed (%s)", esp_err_to_name(ret)); + return ret; + } + ESP_LOGI(TAG, "probe ok: DEVICE_ID=0x%02X", val); + 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"); + + // Enable INT pin: CONTROL0.INT_MASK = 0 + ESP_RETURN_ON_ERROR(i2c_rd8(hw->i2c_dev, REG_CONTROL0, &v), TAG, "rd ctl0"); + v &= ~CTL0_INT_MASK; + ESP_RETURN_ON_ERROR(i2c_wr8(hw->i2c_dev, REG_CONTROL0, v), TAG, "wr ctl0"); + + // Default role (sink/UFP) BEFORE unmasking INT + ESP_RETURN_ON_ERROR(fusb302_set_role(dev, FUSB302_ROLE_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); + + ESP_LOGI(TAG, "init done"); + return ESP_OK; +} + +esp_err_t fusb302_deinit(fusb302_dev_t *dev) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t ret = ESP_OK, err; + uint8_t v; + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + + // 1) Mask all interrupt sources + err = i2c_wr8(d, REG_MASK, 0xFF); if (err != ESP_OK && ret == ESP_OK) ret = err; + err = i2c_wr8(d, REG_MASKA, 0xFF); if (err != ESP_OK && ret == ESP_OK) ret = err; + err = i2c_wr8(d, REG_MASKB, 0xFF); if (err != ESP_OK && ret == ESP_OK) ret = err; + + // 2) Clear any latched causes (read-to-clear) + (void)i2c_rd8(d, REG_INTERRUPT, &v); + (void)i2c_rd8(d, REG_INTERRUPTA, &v); + (void)i2c_rd8(d, REG_INTERRUPTB, &v); + + // 3) Disable INT pin (CONTROL0.INT_MASK = 1) + if (i2c_rd8(d, REG_CONTROL0, &v) == ESP_OK) { + v |= CTL0_INT_MASK; + err = i2c_wr8(d, REG_CONTROL0, v); + if (err != ESP_OK && ret == ESP_OK) { + ret = err; + } + } + + // 4) Put CC pins to Hi-Z (no Rp/Rd, no MEAS, no VCONN) + err = i2c_wr8(d, REG_SWITCHES0, 0x00); if (err != ESP_OK && ret == ESP_OK) ret = err; + + // 5) Stop any mode/toggle (CONTROL2 = MODE_NONE) + err = i2c_wr8(d, REG_CONTROL2, CTL2_MODE_NONE); if (err != ESP_OK && ret == ESP_OK) ret = err; + + // 6) Power down analog blocks + err = i2c_wr8(d, REG_POWER, PWR_PWR_LOW); if (err != ESP_OK && ret == ESP_OK) ret = err; + + free(dev); + return ret; +} + +esp_err_t fusb302_set_role(fusb302_dev_t *dev, fusb302_role_t role) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + esp_err_t err; + + switch (role) { + case FUSB302_ROLE_SINK: { + err = i2c_wr8(d, REG_CONTROL2, CTL2_MODE_UFP); + if (err != ESP_OK) { + return err; + } + const uint8_t sw0 = SW0_CC1_PD_EN | SW0_CC2_PD_EN | SW0_MEAS_CC1; + err = i2c_wr8(d, REG_SWITCHES0, sw0); + if (err != ESP_OK) { + return err; + } + break; + } + case FUSB302_ROLE_SOURCE: { + err = i2c_wr8(d, REG_CONTROL2, CTL2_MODE_DFP); + if (err != ESP_OK) { + return err; + } + const uint8_t sw0 = SW0_CC1_PU_EN | SW0_CC2_PU_EN | SW0_MEAS_CC1; + err = i2c_wr8(d, REG_SWITCHES0, sw0); + if (err != ESP_OK) { + return err; + } + break; + } + case FUSB302_ROLE_DRP: { + err = i2c_wr8(d, REG_CONTROL2, (uint8_t)(CTL2_MODE_DRP | CTL2_TOGGLE)); + if (err != ESP_OK) { + return err; + } + err = i2c_wr8(d, REG_SWITCHES0, SW0_MEAS_CC1); + if (err != ESP_OK) { + return err; + } + break; + } + default: + return ESP_ERR_INVALID_ARG; + } + + dev->role = role; + return ESP_OK; +} + +esp_err_t fusb302_service_irq(fusb302_dev_t *dev, bool *had_any) +{ + if (!dev || !had_any) { + return ESP_ERR_INVALID_ARG; + } + + *had_any = false; + const int max_loops = 8; // safety cap + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + + for (int i = 0; i < max_loops; ++i) { + uint8_t i0 = 0, ia = 0, ib = 0; + + // Read-to-clear all three interrupt registers + esp_err_t err = i2c_rd8(d, REG_INTERRUPT, &i0); + if (err != ESP_OK) { + return err; + } + (void)i2c_rd8(d, REG_INTERRUPTA, &ia); + (void)i2c_rd8(d, REG_INTERRUPTB, &ib); + + if (i0 | ia | ib) { + *had_any = true; + ESP_LOGI(TAG, "Cleared interrupts: INT=0x%02X, INTA=0x%02X, INTB=0x%02X", i0, ia, ib); + } + + // If INT deasserted (active-low), we’re done + if (gpio_get_level(dev->hw.gpio_int) == 1) { + break; + } + + esp_rom_delay_us(200); + } + + return ESP_OK; +} + +esp_err_t fusb302_read_cc_status(fusb302_dev_t *dev, fusb302_cc_status_t *st) +{ + if (!dev || !st) { + return ESP_ERR_INVALID_ARG; + } + memset(st, 0, sizeof(*st)); + + uint8_t s0 = 0; + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + + /* Ensure Rd remains enabled while switching measure channel */ + uint8_t base_sw0 = SW0_CC1_PD_EN | SW0_CC2_PD_EN; + + /* Measure CC1 */ + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, base_sw0 | SW0_MEAS_CC1), TAG, "meas cc1"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_STATUS0, &s0), TAG, "rd STATUS0 cc1"); + uint8_t bclvl_cc1 = (s0 & ST0_BC_LVL_MASK); + + /* Measure CC2 */ + ESP_RETURN_ON_ERROR(i2c_wr8(d, REG_SWITCHES0, base_sw0 | SW0_MEAS_CC2), TAG, "meas cc2"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_STATUS0, &s0), TAG, "rd STATUS0 cc2"); + uint8_t bclvl_cc2 = (s0 & ST0_BC_LVL_MASK); + + bool cc1_has_rp = (bclvl_cc1 != ST0_BC_LVL_0_200); + bool cc2_has_rp = (bclvl_cc2 != ST0_BC_LVL_0_200); + + bool attached = (cc1_has_rp ^ cc2_has_rp); + + st->attached = attached; + st->cc2_active = attached ? cc2_has_rp : false; + + uint8_t bclvl = cc2_has_rp ? bclvl_cc2 : bclvl_cc1; + switch (bclvl) { + case ST0_BC_LVL_200_600: st->rp_cur_ma = 500; break; + case ST0_BC_LVL_600_1230: st->rp_cur_ma = 1500; break; + case ST0_BC_LVL_1230_MAX: st->rp_cur_ma = 3000; break; + default: st->rp_cur_ma = 0; break; + } + + 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; + + if (enable) { + /* 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 | INT_ACTIVITY); + + 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"); + } + return ESP_OK; +} + +esp_err_t fusb302_get_and_clear_int(fusb302_dev_t *dev, + uint8_t *int0, uint8_t *inta, uint8_t *intb) +{ + if (!dev) { + return ESP_ERR_INVALID_ARG; + } + + i2c_master_dev_handle_t d = dev->hw.i2c_dev; + uint8_t i0 = 0, ia = 0, ib = 0; + + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_INTERRUPT, &i0), TAG, "rd INT"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_INTERRUPTA, &ia), TAG, "rd INTA"); + ESP_RETURN_ON_ERROR(i2c_rd8(d, REG_INTERRUPTB, &ib), TAG, "rd INTB"); + + if (int0) { + *int0 = i0; + } + if (inta) { + *inta = ia; + } + if (intb) { + *intb = ib; + } + return ESP_OK; +} diff --git a/type_c/src/husb320_ctrl.c b/type_c/src/husb320_ctrl.c new file mode 100644 index 00000000..910d5143 --- /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; + husb320_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, husb320_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; +}