18static constexpr uint32_t NOTIFY_IRQ = 1 << 0;
19static constexpr uint32_t NOTIFY_STOP = 1 << 1;
20static constexpr uint32_t NOTIFY_REQUEST = 1 << 2;
22static constexpr uint32_t TASK_STACK_SIZE = 4096;
23static constexpr UBaseType_t TASK_PRIORITY = 18;
25static constexpr uint32_t STARTUP_DELAY_MS = 2000;
26static constexpr uint32_t CC_SETTLE_MS = 5;
27static constexpr uint32_t CC_POLL_INTERVAL_MS = 500;
28static constexpr uint32_t PD_POLL_INTERVAL_MS = 100;
30static constexpr uint32_t IDLE_POLL_INTERVAL_MS = 1000;
32static constexpr uint32_t SOURCE_CAP_WAIT_MS = 5000;
34static constexpr uint32_t SOURCE_CAP_RETRY_MS = 2500;
36static constexpr uint8_t MAX_IRQ_ROUNDS = 8;
38static constexpr uint8_t CC_STABLE_READS = 5;
39static constexpr uint8_t CC_LEVEL_UNSTABLE = 0xFF;
45 BaseType_t woken = pdFALSE;
46 xTaskNotifyFromISR(
handle, NOTIFY_IRQ, eSetBits, &woken);
47 portYIELD_FROM_ISR(woken);
53 uint8_t device_id = 0;
54 if (!this->
read_byte(FUSB_DEVICE_ID, &device_id)) {
58 const uint8_t version = device_id & FUSB_DEVICE_ID_VERSION_MASK;
59 if (version != FUSB_DEVICE_ID_VERSION_A && version != FUSB_DEVICE_ID_VERSION_B) {
60 ESP_LOGE(TAG,
"Unknown device ID 0x%02X", device_id);
64 ESP_LOGD(TAG,
"Device ID 0x%02X (product %u, revision %u)", device_id, (device_id >> 2) & 0x3, device_id & 0x3);
66 this->
mark_failed(LOG_STR(
"Initialization failed"));
73 this->
mark_failed(LOG_STR(
"Failed to create task"));
114 while (tail != head) {
124 ESP_LOGW(TAG,
"Events arrived faster than the main loop handled them; some state triggers were skipped");
129 for (; tail !=
end; tail++) {
135 for (; failures > 0; failures--)
157#ifdef USE_TEXT_SENSOR
163 snprintf(buf,
sizeof(buf),
"%.2fA @ %.0fV", contract.
current(), contract.
voltage());
173 " Request Voltage: %u V",
175 LOG_I2C_DEVICE(
this);
181#ifdef USE_TEXT_SENSOR
194 if (voltage < MIN_REQUEST_VOLTAGE || voltage > MAX_REQUEST_VOLTAGE) {
195 ESP_LOGW(TAG,
"Requested %d V is outside %u-%u V, clamping", voltage, MIN_REQUEST_VOLTAGE, MAX_REQUEST_VOLTAGE);
197 this->
set_request_voltage(
static_cast<uint8_t
>(std::clamp<int>(voltage, MIN_REQUEST_VOLTAGE, MAX_REQUEST_VOLTAGE)));
212 vTaskDelay(pdMS_TO_TICKS(STARTUP_DELAY_MS));
215 uint32_t wait_ms = CC_POLL_INTERVAL_MS;
219 wait_ms = busy ? PD_POLL_INTERVAL_MS : IDLE_POLL_INTERVAL_MS;
222 xTaskNotifyWait(0, UINT32_MAX, &bits, pdMS_TO_TICKS(wait_ms));
224 if (bits & NOTIFY_STOP) {
226 vTaskSuspend(
nullptr);
228 if (bits & NOTIFY_REQUEST) {
235 if (bits & NOTIFY_IRQ)
257 if (!this->
write_byte(FUSB_RESET, FUSB_RESET_SW_RES))
268 uint8_t control3 = 0;
269 if (!this->
read_byte(FUSB_CONTROL3, &control3))
272 (control3 & ~FUSB_CONTROL3_N_RETRIES_MASK) | (3 << FUSB_CONTROL3_N_RETRIES_SHIFT) | FUSB_CONTROL3_AUTO_RETRY;
273 if (!this->
write_byte(FUSB_CONTROL3, control3))
276 if (!this->
write_byte(FUSB_POWER, FUSB_POWER_ALL))
284 return this->
write_byte(FUSB_CONTROL0, FUSB_CONTROL0_TX_FLUSH) &&
285 this->
write_byte(FUSB_CONTROL1, FUSB_CONTROL1_RX_FLUSH) && this->
write_byte(FUSB_RESET, FUSB_RESET_PD_RESET);
289 uint8_t switches1 = 0;
290 return this->
read_byte(FUSB_SWITCHES1, &switches1) &&
291 this->
write_byte(FUSB_SWITCHES1, switches1 | FUSB_SWITCHES1_AUTO_CRC);
301 const uint8_t interrupta = regs[2];
302 const uint8_t status0 = regs[4];
303 uint8_t status1 = regs[5];
304 const uint8_t interrupt = regs[6];
306 if ((interrupt & FUSB_INTERRUPT_I_VBUSOK) && !(status0 & FUSB_STATUS0_VBUSOK)) {
311 if (interrupta & FUSB_INTERRUPTA_I_HARDRST) {
316 if (interrupta & FUSB_INTERRUPTA_I_SOFTRST) {
317 ESP_LOGV(TAG,
"Soft reset received");
319 if ((interrupta & FUSB_INTERRUPTA_I_RETRYFAIL) && this->
attached_)
325 while (!(status1 & FUSB_STATUS1_RX_EMPTY)) {
328 ESP_LOGW(TAG,
"Reading message failed");
330 this->
write_byte(FUSB_CONTROL1, FUSB_CONTROL1_RX_FLUSH);
336 if (!this->
read_byte(FUSB_STATUS1, &status1))
349 uint8_t buf[PD_MAX_NUM_DATA_OBJECTS * 4 + 4];
352 for (uint8_t i = 0; i < msg.
num_of_obj; i++) {
353 const uint8_t *obj = &buf[i * 4];
356 is_sop = (token & FUSB_FIFO_RX_TOKEN_BITS) == FUSB_FIFO_RX_SOP;
362 uint8_t buf[1 + 4 + 1 + 2 + PD_MAX_NUM_DATA_OBJECTS * 4 + 4];
366 buf[
len++] = FUSB_FIFOS;
367 buf[
len++] = FUSB_TX_TOKEN_SYNC1;
368 buf[
len++] = FUSB_TX_TOKEN_SYNC1;
369 buf[
len++] = FUSB_TX_TOKEN_SYNC1;
370 buf[
len++] = FUSB_TX_TOKEN_SYNC2;
371 buf[
len++] = FUSB_TX_TOKEN_PACKSYM | (msg.
num_of_obj * 4 + 2);
372 buf[
len++] = header & 0xFF;
373 buf[
len++] = header >> 8;
374 for (uint8_t i = 0; i < msg.
num_of_obj; i++) {
376 buf[
len++] = obj & 0xFF;
377 buf[
len++] = (obj >> 8) & 0xFF;
378 buf[
len++] = (obj >> 16) & 0xFF;
379 buf[
len++] = obj >> 24;
381 buf[
len++] = FUSB_TX_TOKEN_JAM_CRC;
382 buf[
len++] = FUSB_TX_TOKEN_EOP;
383 buf[
len++] = FUSB_TX_TOKEN_TXOFF;
384 buf[
len++] = FUSB_TX_TOKEN_TXON;
388 ESP_LOGW(TAG,
"Sending message type %u failed (error %d)", msg.
type,
static_cast<int>(err));
395 if (!this->
write_byte(FUSB_SWITCHES0, FUSB_SWITCHES0_PDWN_1 | FUSB_SWITCHES0_PDWN_2 | meas_switch))
397 vTaskDelay(pdMS_TO_TICKS(CC_SETTLE_MS));
399 if (!this->
read_byte(FUSB_STATUS0, &status0))
401 level = status0 & FUSB_STATUS0_BC_LVL;
402 for (uint8_t i = 0; i < CC_STABLE_READS; i++) {
403 if (!this->
read_byte(FUSB_STATUS0, &status0))
405 if ((status0 & FUSB_STATUS0_BC_LVL) != level) {
406 level = CC_LEVEL_UNSTABLE;
417 ESP_LOGI(TAG,
"Communication restored");
422 if (!this->
write_byte(FUSB_POWER, FUSB_POWER_ALL) || !this->
read_byte(FUSB_STATUS0, &status0)) {
427 if (!(status0 & FUSB_STATUS0_VBUSOK)) {
434 if (!this->
write_byte(FUSB_SWITCHES1, FUSB_SWITCHES1_SPECREV_REV2) ||
435 !this->
write_byte(FUSB_MEASURE, FUSB_MEASURE_MDAC_CC) || !this->
measure_cc_(FUSB_SWITCHES0_MEAS_CC1, cc1) ||
436 !this->
measure_cc_(FUSB_SWITCHES0_MEAS_CC2, cc2)) {
443 if (cc1 != CC_LEVEL_UNSTABLE && cc1 > 0 && cc2 == 0) {
444 meas_switch = FUSB_SWITCHES0_MEAS_CC1;
445 tx_switch = FUSB_SWITCHES1_TXCC1;
446 }
else if (cc2 != CC_LEVEL_UNSTABLE && cc2 > 0 && cc1 == 0) {
447 meas_switch = FUSB_SWITCHES0_MEAS_CC2;
448 tx_switch = FUSB_SWITCHES1_TXCC2;
451 ESP_LOGD(TAG,
"VBUS present but no single CC line with a source pull-up (CC1 %u, CC2 %u)", cc1, cc2);
457 if (!this->
write_byte(FUSB_SWITCHES0, FUSB_SWITCHES0_PDWN_1 | FUSB_SWITCHES0_PDWN_2 | meas_switch) ||
463 ESP_LOGD(TAG,
"Attached on CC%u", meas_switch == FUSB_SWITCHES0_MEAS_CC1 ? 1 : 2);
471 ESP_LOGD(TAG,
"Detached (VBUS lost)");
481 ESP_LOGD(TAG,
"Hard reset received");
496 ESP_LOGW(TAG,
"Communication with the chip failed");
533 ESP_LOGW(TAG,
"PD negotiation failed, staying at 5 V");
537 ESP_LOGW(TAG,
"Renegotiation failed, keeping the current contract");
554 if (elapsed < SOURCE_CAP_WAIT_MS)
556 ESP_LOGD(TAG,
"No source capabilities received, requesting them");
564 if (elapsed < SOURCE_CAP_RETRY_MS)
566 ESP_LOGD(TAG,
"No source capabilities received, sending soft reset");
570 if (elapsed < SOURCE_CAP_RETRY_MS)
void mark_failed()
Mark this component as failed.
void enable_loop_soon_any_context()
Thread and ISR-safe version of enable_loop() that can be called from any context.
void disable_loop()
Disable this component's loop.
bool is_created() const
Check if the task has been created and not yet destroyed.
TaskHandle_t get_handle() const
Get the FreeRTOS task handle.
bool create(TaskFunction_t fn, const char *name, uint32_t stack_size, void *param, UBaseType_t priority, bool use_psram, BaseType_t core_id=tskNO_AFFINITY)
Allocate the TCB (always internal RAM) and the stack, then create the task.
void attach_interrupt(void(*func)(T *), T *arg, gpio::InterruptType type) const
void detach_interrupt() const
bool digital_read() override
FUSB302B USB Type-C / Power Delivery sink controller.
std::atomic< bool > event_queue_overflow_
LazyCallbackManager< void(PdState, PdState)> state_callback_
static constexpr uint8_t EVENT_QUEUE_SIZE
void publish_contract_(PackedContract contract)
void start_source_cap_wait_(SourceCapStep step)
text_sensor::TextSensor * contract_text_sensor_
bool read_message_(PdMsg &msg, bool &is_sop)
void request_voltage(int voltage)
Negotiate a new contract with the highest source voltage that does not exceed voltage.
InternalGPIOPin * interrupt_pin_
void check_source_caps_()
void handle_hard_reset_()
std::array< uint8_t, EVENT_QUEUE_SIZE > event_queue_
void publish_state_(PdState state)
static constexpr uint8_t EVENT_REQUEST_FAILED
void on_shutdown() override
LazyCallbackManager< void()> request_failed_callback_
sensor::Sensor * voltage_sensor_
uint32_t published_contract_
std::atomic< uint8_t > dropped_request_failures_
std::atomic< uint8_t > event_queue_tail_
uint32_t source_cap_step_start_
void on_state_changed() override
bool waiting_for_source_caps_
static void gpio_intr(FUSB302B *arg)
void start_negotiation_()
std::atomic< uint8_t > event_queue_head_
SourceCapStep source_cap_step_
@ SOURCE_CAP_STEP_GET_SENT
@ SOURCE_CAP_STEP_SOFT_RESET_SENT
sensor::Sensor * current_sensor_
bool cc_ambiguous_logged_
void queue_event_(uint8_t event)
static void task_func(void *arg)
void on_request_failed() override
bool send_message(const PdMsg &msg) override
void source_caps_failed_()
void dump_config() override
bool measure_cc_(uint8_t meas_switch, uint8_t &level)
PdMsg make_control_msg_(PdControlMsgType type) const
bool retry_request_pending_
uint8_t get_request_voltage() const
std::atomic< uint32_t > contract_
void set_state_(PdState state)
std::atomic< PdState > state_
void set_request_voltage(uint8_t voltage)
void handle_message_(const PdMsg &msg)
void check_request_retry_()
Sends the last request again once tSinkRequest has passed after the source answered Wait.
void handle_transmit_failed_()
Called by the PHY when the chip gave up on a message after its retries without receiving a GoodCRC.
bool check_ams_()
Returns true while an atomic message sequence is in progress.
void set_state_and_contract_(PdState state, const PdContract &contract)
Stores both before calling on_state_changed() once, so the main loop never sees the new state with th...
void check_transition_timeout_()
Falls back to default power when the source accepted a request but did not report the new supply read...
ErrorCode write(const uint8_t *data, size_t len) const
writes an array of bytes to a device using an I2CBus
bool write_byte(uint8_t a_register, uint8_t data) const
bool read_byte(uint8_t a_register, uint8_t *data)
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len)
reads an array of bytes from a specific register in the I²C device
void publish_state(float state)
Publish a new state to the front-end.
void publish_state(const std::string &state)
ESPHOME_LOG_TAG(TAG, "fusb302b")
@ PD_STATE_DEFAULT_CONTRACT
@ PD_STATE_PD_TIMEOUT
Negotiation is over without a PD contract (no capabilities, or the request was not accepted); the sou...
@ PD_CNTRL_GET_SOURCE_CAP
ErrorCode
Error codes returned by I2CBus and I2CDevice methods.
@ ERROR_OK
No error found during execution of method.
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.
A contract packed into one word so it can be shared between tasks as an atomic.
void set_header(uint16_t header)
uint16_t get_coded_header() const
uint32_t data_objects[PD_MAX_NUM_DATA_OBJECTS]
spi_device_handle_t handle