Skip to content

stm32/ucpd: add split_idle_pd_phy with borrowed PD activation - #7502

Open
Finomnis wants to merge 4 commits into
embassy-rs:mainfrom
Finomnis:stm32-ucpd-idle-pd-phy
Open

Finomnis wants to merge 4 commits into
embassy-rs:mainfrom
Finomnis:stm32-ucpd-idle-pd-phy

Conversation

@Finomnis

@Finomnis Finomnis commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Depends on #7471 (uses modify_cr).

This PR contains the changes from #7471. See the individual commits for what is actually added by this PR. I will rebase and edit this message once #7471 is merged.

Adds a way to keep the UCPD driver alive across attach/detach cycles.

Today split_pd_phy consumes the Ucpd and enables PD on one CC line for good. To go back after a detach, both halves have to be dropped, which disables UCPD and removes the CC terminations (the partner sees an unplug), and Ucpd::new has to be called again, requiring the peripheral tokens to be reborrowed or stolen.

This PR adds:

  • Ucpd::split_idle_pd_phy(rx_dma, tx_dma, irqs) -> (CcPhy, IdlePdPhy): splits like split_pd_phy, but leaves PD disabled.
  • IdlePdPhy::activate(&mut self, cc_sel) -> ActivePdPhy: selects the CC line and enables PD reception. ActivePdPhy derefs to PdPhy for receive/transmit, and disables reception again when dropped. Stale RX state from a previous activation is discarded first.

The CC terminations are never touched by enabling or disabling PD. The change is purely additive: split_pd_phy, PdPhy, CcPhy and the error types are unchanged, and the drop/refcount behavior is the same, as IdlePdPhy owns a PdPhy. The guard follows the same pattern as ResumablePeripheralGuard.

A new example, examples/stm32g474/src/bin/usb_c_pd.rs, runs CC detection and PD reception across repeated attach/detach cycles without recreating Ucpd. It targets a NUCLEO-G474RE with the X-NUCLEO-DRP1M1 expansion board, whose TCPP03-M20 is enabled over I2C at startup.

Tested on a NUCLEO-G474RE with an X-NUCLEO-DRP1M1: PD reception in both cable orientations across repeated attach/detach cycles without recreating Ucpd, including a PD session that ends before the detach (receive_messages returning after one message). Built for G0, G4, H5, U5, L5, H7RS and N6.

Each Drop impl loaded drop_not_ready and then stored it in a separate
step. If the two parts were dropped at the same time from different
priority levels, both could see true, and neither disabled the
peripheral, its clock and its interrupt.

Read and clear the flag in one atomic step with AtomicClear::clear(),
which falls back to a critical section on thumbv6m (G0), where there is
no atomic swap.
CR holds settings of both driver parts: the pull resistors and CC
enables written by CcPhy::set_pull(), and PHYRXEN, TXMODE, TXSEND and
TXHRST written by PdPhy. All of them used a plain modify(). With the
two parts in executors of different priority, one could preempt the
other between the read and the write and then write back stale bits,
e.g. set_pull() turning PHYRXEN back on after PdPhy was dropped.

TXSEND, TXHRST and FRSTX are also commands that the hardware clears
once it acts on them. A modify() that read CR while TXSEND was still
set and wrote it back after the hardware had cleared it would start
the same transmission again, and likewise for a Hard Reset. Making the
access atomic does not help against that, since the hardware, not
software, changes the bit in between.

Do every CR update through modify_cr(), which runs in a critical
section and writes the command bits as 0 unless the caller sets them.
Writing 0 to a command bit has no effect.
split_pd_phy() consumes the Ucpd and enables PD on one CC line for good.
To go back after a detach, both halves have to be dropped, which turns
UCPD off and removes the CC terminations, and Ucpd::new() has to be
called again for the next attach. The partner sees that as an unplug,
and the peripheral tokens have to be reborrowed or stolen.

Add Ucpd::split_idle_pd_phy(), which splits the driver the same way but
leaves PD disabled. IdlePdPhy::activate() selects the CC line and
enables PD reception, and returns an ActivePdPhy guard that derefs to
PdPhy for receive/transmit and disables reception again when dropped.
The CC terminations are not touched by either.

activate() also discards what a previous activation left behind (a
received Hard Reset or message, and a byte left in RXDR), so the first
reception after an attach doesn't return stale data.

split_pd_phy() keeps its behavior; it now uses the same helpers. The
drop and refcount logic is unchanged, as IdlePdPhy owns a PdPhy.
Runs on a NUCLEO-G474RE with the X-NUCLEO-DRP1M1 expansion board, which
connects the USB-C port to UCPD1 through a TCPP03-M20. The example
enables the TCPP03 over I2C, then detects attach and detach in a loop
and enables PD on every attach with Ucpd::split_idle_pd_phy() and
IdlePdPhy::activate(), without recreating the Ucpd.
@github-actions github-actions Bot added the e-stm32 Issues for the STM32 family of chips label Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

e-stm32 Issues for the STM32 family of chips

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant