diff --git a/esphome/components/fusb302b/__init__.py b/esphome/components/fusb302b/__init__.py index 3cf9043f418..212ef057e94 100644 --- a/esphome/components/fusb302b/__init__.py +++ b/esphome/components/fusb302b/__init__.py @@ -1,2 +1,40 @@ +from esphome import pins +import esphome.codegen as cg +from esphome.components import i2c +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN +from esphome.types import ConfigType + CODEOWNERS = ["@remcom"] +DEPENDENCIES = ["i2c"] DOMAIN = "fusb302b" + +CONF_REQUEST_VOLTAGE = "request_voltage" + +fusb302b_ns = cg.esphome_ns.namespace("fusb302b") +FUSB302B = fusb302b_ns.class_("FUSB302B", cg.Component, i2c.I2CDevice) + +# USB PD fixed supply voltages are 5, 9, 12, 15 and 20 V; the highest one that does not exceed the +# requested voltage is chosen. +validate_voltage = cv.int_range(min=5, max=20) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(FUSB302B), + cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pullup_pin_schema, + cv.Required(CONF_REQUEST_VOLTAGE): validate_voltage, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x22)), + cv.only_on_esp32, +) + + +async def to_code(config: ConfigType) -> None: + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + var = cg.new_Pvariable(config[CONF_ID], interrupt_pin) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + cg.add(var.set_request_voltage(config[CONF_REQUEST_VOLTAGE])) diff --git a/esphome/components/fusb302b/fusb302b.cpp b/esphome/components/fusb302b/fusb302b.cpp new file mode 100644 index 00000000000..6cffbf114f7 --- /dev/null +++ b/esphome/components/fusb302b/fusb302b.cpp @@ -0,0 +1,415 @@ +#include "fusb302b.h" + +#ifdef USE_ESP32 + +#include "esphome/core/log.h" + +#include "fusb302b_registers.h" + +namespace esphome::fusb302b { + +ESPHOME_LOG_TAG(TAG, "fusb302b"); + +// Task notification bits +static constexpr uint32_t NOTIFY_IRQ = 1 << 0; +static constexpr uint32_t NOTIFY_STOP = 1 << 1; + +static constexpr uint32_t TASK_STACK_SIZE = 4096; +static constexpr UBaseType_t TASK_PRIORITY = 18; + +static constexpr uint32_t STARTUP_DELAY_MS = 2000; +static constexpr uint32_t CC_SETTLE_MS = 5; +static constexpr uint32_t CC_POLL_INTERVAL_MS = 500; +static constexpr uint32_t PD_POLL_INTERVAL_MS = 100; +// Safety net in case an interrupt edge is missed while attached and idle +static constexpr uint32_t IDLE_POLL_INTERVAL_MS = 1000; +// Time to wait for the source to send its capabilities on its own after attach +static constexpr uint32_t SOURCE_CAP_WAIT_MS = 5000; +// Time to wait after each GET_SOURCE_CAP / soft reset before the next recovery step +static constexpr uint32_t SOURCE_CAP_RETRY_MS = 2500; +// Upper bound on back-to-back interrupt handling while INT_N stays asserted +static constexpr uint8_t MAX_IRQ_ROUNDS = 8; +// BC_LVL is read this many extra times to confirm a stable measurement +static constexpr uint8_t CC_STABLE_READS = 5; +static constexpr uint8_t CC_LEVEL_UNSTABLE = 0xFF; + +void IRAM_ATTR FUSB302B::gpio_intr(FUSB302B *arg) { + TaskHandle_t handle = arg->task_.get_handle(); + if (handle == nullptr) + return; + BaseType_t woken = pdFALSE; + xTaskNotifyFromISR(handle, NOTIFY_IRQ, eSetBits, &woken); + portYIELD_FROM_ISR(woken); +} + +void FUSB302B::task_func(void *arg) { static_cast(arg)->run_task_(); } + +void FUSB302B::setup() { + uint8_t device_id = 0; + if (!this->read_byte(FUSB_DEVICE_ID, &device_id)) { + this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL)); + return; + } + const uint8_t version = device_id & FUSB_DEVICE_ID_VERSION_MASK; + if (version != FUSB_DEVICE_ID_VERSION_A && version != FUSB_DEVICE_ID_VERSION_B) { + ESP_LOGE(TAG, "Unknown device ID 0x%02X", device_id); + this->mark_failed(); + return; + } + ESP_LOGD(TAG, "Device ID 0x%02X (product %u, revision %u)", device_id, (device_id >> 2) & 0x3, device_id & 0x3); + if (!this->init_chip_()) { + this->mark_failed(LOG_STR("Initialization failed")); + return; + } + + this->interrupt_pin_->setup(); + // The task must exist before the interrupt is attached, since the ISR notifies it + if (!this->task_.create(FUSB302B::task_func, "fusb302b", TASK_STACK_SIZE, this, TASK_PRIORITY, false)) { + this->mark_failed(LOG_STR("Failed to create task")); + return; + } + this->interrupt_pin_->attach_interrupt(&FUSB302B::gpio_intr, this, gpio::INTERRUPT_FALLING_EDGE); +} + +void FUSB302B::dump_config() { + ESP_LOGCONFIG(TAG, + "FUSB302B:\n" + " Request Voltage: %u V", + this->get_request_voltage()); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} + +void FUSB302B::on_shutdown() { + this->interrupt_pin_->detach_interrupt(); + // The task parks itself instead of being deleted, so it can never be stopped in the middle of an I2C transfer + if (this->task_.is_created()) + xTaskNotify(this->task_.get_handle(), NOTIFY_STOP, eSetBits); +} + +void FUSB302B::run_task_() { + // Give the source time to settle after power-up before the first CC measurement + vTaskDelay(pdMS_TO_TICKS(STARTUP_DELAY_MS)); + + while (true) { + uint32_t wait_ms = CC_POLL_INTERVAL_MS; + if (this->attached_) { + const bool busy = this->waiting_for_source_caps_ || this->retry_request_pending_ || this->check_ams_(); + wait_ms = busy ? PD_POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS; + } + uint32_t bits = 0; + xTaskNotifyWait(0, UINT32_MAX, &bits, pdMS_TO_TICKS(wait_ms)); + + if (bits & NOTIFY_STOP) { + while (true) + vTaskSuspend(nullptr); + } + + // INT_N is held low until the interrupt registers are read, so a new event that arrives while it is + // already low has no falling edge: keep servicing while the pin stays asserted. + if (bits & NOTIFY_IRQ) + this->handle_interrupt_(); + for (uint8_t i = 0; i < MAX_IRQ_ROUNDS && !this->interrupt_pin_->digital_read(); i++) + this->handle_interrupt_(); + + if (!this->attached_) { + this->try_attach_(); + continue; + } + this->check_transition_timeout_(); + this->check_request_retry_(); + this->check_source_caps_(); + } +} + +bool FUSB302B::init_chip_() { + // Software reset restores all registers to their defaults + if (!this->write_byte(FUSB_RESET, FUSB_RESET_SW_RES)) + return false; + if (!this->reset_pd_()) + return false; + + // Unmask the interrupts used for PD messaging and VBUS changes + if (!this->write_byte(FUSB_MASK1, FUSB_MASK1_DEFAULT) || !this->write_byte(FUSB_MASKA, 0) || + !this->write_byte(FUSB_MASKB, 0)) + return false; + + // Let the chip retransmit unacknowledged messages up to 3 times + uint8_t control3 = 0; + if (!this->read_byte(FUSB_CONTROL3, &control3)) + return false; + control3 = + (control3 & ~FUSB_CONTROL3_N_RETRIES_MASK) | (3 << FUSB_CONTROL3_N_RETRIES_SHIFT) | FUSB_CONTROL3_AUTO_RETRY; + if (!this->write_byte(FUSB_CONTROL3, control3)) + return false; + + if (!this->write_byte(FUSB_POWER, FUSB_POWER_ALL)) + return false; + return this->reset_pd_(); +} + +bool FUSB302B::reset_pd_() { + this->reset_protocol_(); + // Writing CONTROL0 as a whole byte also clears INT_MASK, which is what enables the INT_N pin + return this->write_byte(FUSB_CONTROL0, FUSB_CONTROL0_TX_FLUSH) && + this->write_byte(FUSB_CONTROL1, FUSB_CONTROL1_RX_FLUSH) && this->write_byte(FUSB_RESET, FUSB_RESET_PD_RESET); +} + +bool FUSB302B::enable_auto_crc_() { + uint8_t switches1 = 0; + return this->read_byte(FUSB_SWITCHES1, &switches1) && + this->write_byte(FUSB_SWITCHES1, switches1 | FUSB_SWITCHES1_AUTO_CRC); +} + +void FUSB302B::handle_interrupt_() { + // STATUS0A, STATUS1A, INTERRUPTA, INTERRUPTB, STATUS0, STATUS1, INTERRUPT; reading clears the interrupts + uint8_t regs[7]; + if (this->read_register(FUSB_STATUS0A, regs, sizeof(regs)) != i2c::ERROR_OK) { + this->enter_error_(); + return; + } + const uint8_t interrupta = regs[2]; + const uint8_t status0 = regs[4]; + uint8_t status1 = regs[5]; + const uint8_t interrupt = regs[6]; + + if ((interrupt & FUSB_INTERRUPT_I_VBUSOK) && !(status0 & FUSB_STATUS0_VBUSOK)) { + if (this->attached_) + this->detach_(); + return; + } + if (interrupta & FUSB_INTERRUPTA_I_HARDRST) { + if (this->attached_) + this->handle_hard_reset_(); + return; + } + if (interrupta & FUSB_INTERRUPTA_I_SOFTRST) { + ESP_LOGV(TAG, "Soft reset received"); + } + if ((interrupta & FUSB_INTERRUPTA_I_RETRYFAIL) && this->attached_) + this->handle_transmit_failed_(); + + if (!this->attached_) + return; + PdMsg msg; + while (!(status1 & FUSB_STATUS1_RX_EMPTY)) { + bool is_sop = false; + if (!this->read_message_(msg, is_sop)) { + ESP_LOGW(TAG, "Reading message failed"); + // A partial read leaves the FIFO misaligned + this->write_byte(FUSB_CONTROL1, FUSB_CONTROL1_RX_FLUSH); + return; + } + // Only SOP packets are addressed to a sink; SOP'/SOP'' are for cable plugs + if (is_sop) + this->handle_message_(msg); + if (!this->read_byte(FUSB_STATUS1, &status1)) + return; + } +} + +bool FUSB302B::read_message_(PdMsg &msg, bool &is_sop) { + uint8_t token = 0; + uint8_t header[2]; + if (!this->read_byte(FUSB_FIFOS, &token) || this->read_register(FUSB_FIFOS, header, 2) != i2c::ERROR_OK) + return false; + msg.set_header(encode_uint16(header[1], header[0])); + + // Data objects, followed by the CRC32 that the chip already checked + uint8_t buf[PD_MAX_NUM_DATA_OBJECTS * 4 + 4]; + if (this->read_register(FUSB_FIFOS, buf, msg.num_of_obj * 4 + 4) != i2c::ERROR_OK) + return false; + for (uint8_t i = 0; i < msg.num_of_obj; i++) { + const uint8_t *obj = &buf[i * 4]; + msg.data_objects[i] = encode_uint32(obj[3], obj[2], obj[1], obj[0]); + } + is_sop = (token & FUSB_FIFO_RX_TOKEN_BITS) == FUSB_FIFO_RX_SOP; + return true; +} + +bool FUSB302B::send_message(const PdMsg &msg) { + // Register address, then the TX tokens; written in one transfer without the copy write_register() makes + uint8_t buf[1 + 4 + 1 + 2 + PD_MAX_NUM_DATA_OBJECTS * 4 + 4]; + size_t len = 0; + const uint16_t header = msg.get_coded_header(); + + buf[len++] = FUSB_FIFOS; + buf[len++] = FUSB_TX_TOKEN_SYNC1; + buf[len++] = FUSB_TX_TOKEN_SYNC1; + buf[len++] = FUSB_TX_TOKEN_SYNC1; + buf[len++] = FUSB_TX_TOKEN_SYNC2; + buf[len++] = FUSB_TX_TOKEN_PACKSYM | (msg.num_of_obj * 4 + 2); + buf[len++] = header & 0xFF; + buf[len++] = header >> 8; + for (uint8_t i = 0; i < msg.num_of_obj; i++) { + const uint32_t obj = msg.data_objects[i]; + buf[len++] = obj & 0xFF; + buf[len++] = (obj >> 8) & 0xFF; + buf[len++] = (obj >> 16) & 0xFF; + buf[len++] = obj >> 24; + } + buf[len++] = FUSB_TX_TOKEN_JAM_CRC; + buf[len++] = FUSB_TX_TOKEN_EOP; + buf[len++] = FUSB_TX_TOKEN_TXOFF; + buf[len++] = FUSB_TX_TOKEN_TXON; + + i2c::ErrorCode err = this->write(buf, len); + if (err != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Sending message type %u failed (error %d)", msg.type, static_cast(err)); + return false; + } + return true; +} + +bool FUSB302B::measure_cc_(uint8_t meas_switch, uint8_t &level) { + if (!this->write_byte(FUSB_SWITCHES0, FUSB_SWITCHES0_PDWN_1 | FUSB_SWITCHES0_PDWN_2 | meas_switch)) + return false; + vTaskDelay(pdMS_TO_TICKS(CC_SETTLE_MS)); + uint8_t status0 = 0; + if (!this->read_byte(FUSB_STATUS0, &status0)) + return false; + level = status0 & FUSB_STATUS0_BC_LVL; + for (uint8_t i = 0; i < CC_STABLE_READS; i++) { + if (!this->read_byte(FUSB_STATUS0, &status0)) + return false; + if ((status0 & FUSB_STATUS0_BC_LVL) != level) { + level = CC_LEVEL_UNSTABLE; + return true; + } + } + return true; +} + +void FUSB302B::try_attach_() { + if (this->state_ == PdState::PD_STATE_ERROR) { + if (!this->init_chip_()) + return; + ESP_LOGI(TAG, "Communication restored"); + this->set_state_(PdState::PD_STATE_DISCONNECTED); + } + + uint8_t status0 = 0; + if (!this->write_byte(FUSB_POWER, FUSB_POWER_ALL) || !this->read_byte(FUSB_STATUS0, &status0)) { + this->enter_error_(); + return; + } + // A source may present its CC pull-up before VBUS is on; detach is only detected when VBUS drops + if (!(status0 & FUSB_STATUS0_VBUSOK)) { + this->cc_ambiguous_logged_ = false; + return; + } + + uint8_t cc1 = 0; + uint8_t cc2 = 0; + if (!this->write_byte(FUSB_SWITCHES1, FUSB_SWITCHES1_SPECREV_REV2) || + !this->write_byte(FUSB_MEASURE, FUSB_MEASURE_MDAC_CC) || !this->measure_cc_(FUSB_SWITCHES0_MEAS_CC1, cc1) || + !this->measure_cc_(FUSB_SWITCHES0_MEAS_CC2, cc2)) { + this->enter_error_(); + return; + } + // Exactly one CC line carries the source pull-up; the other one is open + uint8_t meas_switch; + uint8_t tx_switch; + if (cc1 != CC_LEVEL_UNSTABLE && cc1 > 0 && cc2 == 0) { + meas_switch = FUSB_SWITCHES0_MEAS_CC1; + tx_switch = FUSB_SWITCHES1_TXCC1; + } else if (cc2 != CC_LEVEL_UNSTABLE && cc2 > 0 && cc1 == 0) { + meas_switch = FUSB_SWITCHES0_MEAS_CC2; + tx_switch = FUSB_SWITCHES1_TXCC2; + } else { + if (!this->cc_ambiguous_logged_) { + ESP_LOGD(TAG, "VBUS present but no single CC line with a source pull-up (CC1 %u, CC2 %u)", cc1, cc2); + this->cc_ambiguous_logged_ = true; + } + return; + } + + if (!this->write_byte(FUSB_SWITCHES0, FUSB_SWITCHES0_PDWN_1 | FUSB_SWITCHES0_PDWN_2 | meas_switch) || + !this->write_byte(FUSB_SWITCHES1, tx_switch | FUSB_SWITCHES1_SPECREV_REV2) || !this->enable_auto_crc_() || + !this->reset_pd_()) { + this->enter_error_(); + return; + } + ESP_LOGD(TAG, "Attached on CC%u", meas_switch == FUSB_SWITCHES0_MEAS_CC1 ? 1 : 2); + this->attached_ = true; + this->cc_ambiguous_logged_ = false; + this->start_source_cap_wait_(SourceCapStep::SOURCE_CAP_STEP_WAIT); + this->set_state_and_contract_(PdState::PD_STATE_DEFAULT_CONTRACT, DEFAULT_CONTRACT); +} + +void FUSB302B::detach_() { + ESP_LOGD(TAG, "Detached (VBUS lost)"); + this->attached_ = false; + this->waiting_for_source_caps_ = false; + this->reset_pd_(); + this->clear_contract_(); + this->set_state_(PdState::PD_STATE_DISCONNECTED); +} + +void FUSB302B::handle_hard_reset_() { + ESP_LOGD(TAG, "Hard reset received"); + // The source returns to Type-C default power and sends its capabilities again + // Also flushes the RX FIFO, so the caller has nothing left to read + if (!this->reset_pd_()) { + this->enter_error_(); + return; + } + this->set_state_and_contract_(PdState::PD_STATE_DEFAULT_CONTRACT, DEFAULT_CONTRACT); + this->start_source_cap_wait_(SourceCapStep::SOURCE_CAP_STEP_WAIT); +} + +void FUSB302B::enter_error_() { + if (this->state_ == PdState::PD_STATE_ERROR) + return; + ESP_LOGW(TAG, "Communication with the chip failed"); + this->attached_ = false; + this->waiting_for_source_caps_ = false; + this->clear_contract_(); + this->set_state_(PdState::PD_STATE_ERROR); +} + +void FUSB302B::send_soft_reset_() { + this->reset_pd_(); + this->send_message(this->make_control_msg_(PD_CNTRL_SOFT_RESET)); + // The source answers a soft reset with new source capabilities + this->start_source_cap_wait_(SourceCapStep::SOURCE_CAP_STEP_SOFT_RESET_SENT); +} + +void FUSB302B::start_source_cap_wait_(SourceCapStep step) { + this->waiting_for_source_caps_ = true; + this->source_cap_step_ = step; + this->source_cap_step_start_ = millis(); +} + +void FUSB302B::check_source_caps_() { + if (!this->waiting_for_source_caps_ || this->check_ams_()) + return; + const uint32_t elapsed = millis() - this->source_cap_step_start_; + switch (this->source_cap_step_) { + case SourceCapStep::SOURCE_CAP_STEP_WAIT: + if (elapsed < SOURCE_CAP_WAIT_MS) + return; + ESP_LOGD(TAG, "No source capabilities received, requesting them"); + this->send_message(this->make_control_msg_(PD_CNTRL_GET_SOURCE_CAP)); + this->source_cap_step_ = SourceCapStep::SOURCE_CAP_STEP_GET_SENT; + break; + case SourceCapStep::SOURCE_CAP_STEP_GET_SENT: + if (elapsed < SOURCE_CAP_RETRY_MS) + return; + ESP_LOGD(TAG, "No source capabilities received, sending soft reset"); + this->send_soft_reset_(); + return; + case SourceCapStep::SOURCE_CAP_STEP_SOFT_RESET_SENT: + if (elapsed < SOURCE_CAP_RETRY_MS) + return; + ESP_LOGW(TAG, "PD negotiation failed, staying at 5 V"); + this->waiting_for_source_caps_ = false; + this->set_state_(PdState::PD_STATE_PD_TIMEOUT); + return; + } + this->source_cap_step_start_ = millis(); +} + +} // namespace esphome::fusb302b + +#endif // USE_ESP32 diff --git a/esphome/components/fusb302b/fusb302b.h b/esphome/components/fusb302b/fusb302b.h new file mode 100644 index 00000000000..341d64558df --- /dev/null +++ b/esphome/components/fusb302b/fusb302b.h @@ -0,0 +1,74 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" +#include "esphome/core/static_task.h" + +#include "pd.h" + +namespace esphome::fusb302b { + +/// FUSB302B USB Type-C / Power Delivery sink controller. +/// +/// A dedicated task owns all I2C traffic to the chip so that PD replies meet the protocol timing +/// even while the main loop is busy. +class FUSB302B : public PowerDelivery, public Component, public i2c::I2CDevice { + public: + explicit FUSB302B(InternalGPIOPin *interrupt_pin) : interrupt_pin_(interrupt_pin) {} + + void setup() override; + void dump_config() override; + void on_shutdown() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + + protected: + enum class SourceCapStep : uint8_t { + SOURCE_CAP_STEP_WAIT, + SOURCE_CAP_STEP_GET_SENT, + SOURCE_CAP_STEP_SOFT_RESET_SENT, + }; + + static void gpio_intr(FUSB302B *arg); + static void task_func(void *arg); + + bool send_message(const PdMsg &msg) override; + void on_state_changed() override {} + void on_soft_reset_received() override { this->start_source_cap_wait_(SourceCapStep::SOURCE_CAP_STEP_WAIT); } + void on_source_caps_received() override { this->waiting_for_source_caps_ = false; } + void on_transition_timeout() override { this->send_soft_reset_(); } + + // PHY task only + [[noreturn]] void run_task_(); + bool init_chip_(); + bool reset_pd_(); + bool enable_auto_crc_(); + void handle_interrupt_(); + bool read_message_(PdMsg &msg, bool &is_sop); + void try_attach_(); + bool measure_cc_(uint8_t meas_switch, uint8_t &level); + void detach_(); + void handle_hard_reset_(); + void start_source_cap_wait_(SourceCapStep step); + void send_soft_reset_(); + void check_source_caps_(); + void enter_error_(); + + InternalGPIOPin *interrupt_pin_; + StaticTask task_; + + // PHY task only + uint32_t source_cap_step_start_{0}; + SourceCapStep source_cap_step_{SourceCapStep::SOURCE_CAP_STEP_WAIT}; + bool attached_{false}; + bool waiting_for_source_caps_{false}; + bool cc_ambiguous_logged_{false}; +}; + +} // namespace esphome::fusb302b + +#endif // USE_ESP32 diff --git a/esphome/components/fusb302b/fusb302b_registers.h b/esphome/components/fusb302b/fusb302b_registers.h new file mode 100644 index 00000000000..3efaf57215f --- /dev/null +++ b/esphome/components/fusb302b/fusb302b_registers.h @@ -0,0 +1,94 @@ +#pragma once + +#include + +namespace esphome::fusb302b { + +// Register addresses +static constexpr uint8_t FUSB_DEVICE_ID = 0x01; +static constexpr uint8_t FUSB_SWITCHES0 = 0x02; +static constexpr uint8_t FUSB_SWITCHES1 = 0x03; +static constexpr uint8_t FUSB_MEASURE = 0x04; +static constexpr uint8_t FUSB_CONTROL0 = 0x06; +static constexpr uint8_t FUSB_CONTROL1 = 0x07; +static constexpr uint8_t FUSB_CONTROL3 = 0x09; +static constexpr uint8_t FUSB_MASK1 = 0x0A; +static constexpr uint8_t FUSB_POWER = 0x0B; +static constexpr uint8_t FUSB_RESET = 0x0C; +static constexpr uint8_t FUSB_MASKA = 0x0E; +static constexpr uint8_t FUSB_MASKB = 0x0F; +static constexpr uint8_t FUSB_STATUS0A = 0x3C; +static constexpr uint8_t FUSB_STATUS0 = 0x40; +static constexpr uint8_t FUSB_STATUS1 = 0x41; +static constexpr uint8_t FUSB_FIFOS = 0x43; + +// DEVICE_ID: version in bits 7:4, product variant (I2C address 0x22-0x25) in bits 3:2, silicon revision in bits 1:0 +static constexpr uint8_t FUSB_DEVICE_ID_VERSION_MASK = 0xF0; +static constexpr uint8_t FUSB_DEVICE_ID_VERSION_A = 0x80; +static constexpr uint8_t FUSB_DEVICE_ID_VERSION_B = 0x90; + +// SWITCHES0 bits +static constexpr uint8_t FUSB_SWITCHES0_MEAS_CC2 = 1 << 3; +static constexpr uint8_t FUSB_SWITCHES0_MEAS_CC1 = 1 << 2; +static constexpr uint8_t FUSB_SWITCHES0_PDWN_2 = 1 << 1; +static constexpr uint8_t FUSB_SWITCHES0_PDWN_1 = 1 << 0; + +// SWITCHES1 bits +static constexpr uint8_t FUSB_SWITCHES1_SPECREV_REV2 = 0x01 << 5; +static constexpr uint8_t FUSB_SWITCHES1_AUTO_CRC = 1 << 2; +static constexpr uint8_t FUSB_SWITCHES1_TXCC2 = 1 << 1; +static constexpr uint8_t FUSB_SWITCHES1_TXCC1 = 1 << 0; + +// MEASURE value used for CC detection +static constexpr uint8_t FUSB_MEASURE_MDAC_CC = 49; + +// CONTROL0 bits +static constexpr uint8_t FUSB_CONTROL0_TX_FLUSH = 1 << 6; + +// CONTROL1 bits +static constexpr uint8_t FUSB_CONTROL1_RX_FLUSH = 1 << 2; + +// CONTROL3 bits +static constexpr uint8_t FUSB_CONTROL3_N_RETRIES_SHIFT = 1; +static constexpr uint8_t FUSB_CONTROL3_N_RETRIES_MASK = 0x3 << FUSB_CONTROL3_N_RETRIES_SHIFT; +static constexpr uint8_t FUSB_CONTROL3_AUTO_RETRY = 1 << 0; + +// MASK1: mask the ACTIVITY, CRC_CHK and BC_LVL interrupts +static constexpr uint8_t FUSB_MASK1_DEFAULT = 0x51; + +// POWER bits +static constexpr uint8_t FUSB_POWER_ALL = 0x0F; + +// RESET bits +static constexpr uint8_t FUSB_RESET_PD_RESET = 1 << 1; +static constexpr uint8_t FUSB_RESET_SW_RES = 1 << 0; + +// INTERRUPTA bits +static constexpr uint8_t FUSB_INTERRUPTA_I_RETRYFAIL = 1 << 4; +static constexpr uint8_t FUSB_INTERRUPTA_I_SOFTRST = 1 << 1; +static constexpr uint8_t FUSB_INTERRUPTA_I_HARDRST = 1 << 0; + +// INTERRUPT bits +static constexpr uint8_t FUSB_INTERRUPT_I_VBUSOK = 1 << 7; + +// STATUS0 bits +static constexpr uint8_t FUSB_STATUS0_VBUSOK = 1 << 7; +static constexpr uint8_t FUSB_STATUS0_BC_LVL = 0x03; + +// STATUS1 bits +static constexpr uint8_t FUSB_STATUS1_RX_EMPTY = 1 << 5; + +// RX FIFO token +static constexpr uint8_t FUSB_FIFO_RX_TOKEN_BITS = 0xE0; +static constexpr uint8_t FUSB_FIFO_RX_SOP = 0xE0; + +// TX FIFO tokens +static constexpr uint8_t FUSB_TX_TOKEN_TXON = 0xA1; +static constexpr uint8_t FUSB_TX_TOKEN_SYNC1 = 0x12; +static constexpr uint8_t FUSB_TX_TOKEN_SYNC2 = 0x13; +static constexpr uint8_t FUSB_TX_TOKEN_PACKSYM = 0x80; +static constexpr uint8_t FUSB_TX_TOKEN_JAM_CRC = 0xFF; +static constexpr uint8_t FUSB_TX_TOKEN_EOP = 0x14; +static constexpr uint8_t FUSB_TX_TOKEN_TXOFF = 0xFE; + +} // namespace esphome::fusb302b diff --git a/esphome/components/fusb302b/pd.h b/esphome/components/fusb302b/pd.h index 26c75239152..66918ce98da 100644 --- a/esphome/components/fusb302b/pd.h +++ b/esphome/components/fusb302b/pd.h @@ -30,6 +30,7 @@ enum PdControlMsgType : uint8_t { PD_CNTRL_REJECT = 0x04, PD_CNTRL_PING = 0x05, PD_CNTRL_PS_RDY = 0x06, + PD_CNTRL_GET_SOURCE_CAP = 0x07, PD_CNTRL_GET_SINK_CAP = 0x08, PD_CNTRL_WAIT = 0x0C, PD_CNTRL_SOFT_RESET = 0x0D, diff --git a/tests/components/fusb302b/common.yaml b/tests/components/fusb302b/common.yaml new file mode 100644 index 00000000000..e684462ba7c --- /dev/null +++ b/tests/components/fusb302b/common.yaml @@ -0,0 +1,4 @@ +fusb302b: + id: pd + interrupt_pin: ${interrupt_pin} + request_voltage: 20 diff --git a/tests/components/fusb302b/test.esp32-idf.yaml b/tests/components/fusb302b/test.esp32-idf.yaml new file mode 100644 index 00000000000..b9ca02073b4 --- /dev/null +++ b/tests/components/fusb302b/test.esp32-idf.yaml @@ -0,0 +1,6 @@ +substitutions: + interrupt_pin: GPIO15 + +packages: + i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml + fusb302b: !include common.yaml