blob: 9db1a5c06545b337985fce65ab2c5fdfb57fad2f [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_DEVICES_POWER_DRIVERS_FUSB302_REGISTERS_H_
#define SRC_DEVICES_POWER_DRIVERS_FUSB302_REGISTERS_H_
// The comments in this file reference the datasheet from onsemi titled
// "FUSB302B Programmable USB Type‐C Controller w/PD", order number FUSB302B/D,
// downloadable at https://www.onsemi.com/pdf/datasheet/fusb302b-d.pdf
//
// The section and page numbers reference Revision 5, published August 2021.
#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
#include <lib/driver/logging/cpp/logger.h>
#include <lib/zx/result.h>
#include <zircon/assert.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cstdint>
#include <hwreg/bitfields.h>
#include <hwreg/i2c.h>
#include "src/devices/power/drivers/fusb302/usb-pd-defs.h"
// We reason about the FUSB302B behavior using the functional breakdown
// described here. Some of the functional units here are outlined in the Rev 5
// datasheet (Figure 3 page 2), and the rest are inferred.
//
// The analog front-end has the following functional units.
//
// * CC (USB Type C Configuration Channel) pin termination: two pull-down
// resistors (Pd) and two programmable current sources (alternate
// implementation of Pu)
// * BMC (Bi-phase Mark Encoding) PHY (Physical Layer): Tx (transmitter) and Rx
// (receiver) blocks that implement the Physical Layer in the USB PD spec;
// illustrated in Figure 12 page 11 of the Rev 5 datasheet
// * Measure block: four voltage comparators compare the measure block's input
// against reference voltages; three comparators use fixed references that can
// determine Sink termination (Ra, or Rd with Default / 1.5A / 3.0A Type C
// power delivery), and the fourth comparator uses a variable voltage
// generated by an MDAC (mutliplying DAC) with programmable input
// * CC (Configuration Channel) switches: can connect the CC1 and CC2 pins to
// USB PD termination (Rd or current source), to the measure block, to the
// BMC PHY, and/or to the VCONN (cable power source) pin (Figure 6 page 6 of
// the Rev 5 datasheet)
//
// The FUSB302B also has the following digital functional units.
//
// * Power role detector - implements a subset of the state machines in typec2.2
// 4.5 "Configuration Channel (CC)"; these should not be confused with the USB
// PD state machines, which must be implemented in the driver
// * PD (USB Type C Power Delivery) Protocol Layer - implements a subset of the
// state machines described in usbpd3.1 6.12.2.2 "Protocol Layer Message
// Transmission"
// * Interrupt block - functional units signal specific conditions by asserting
// interrupts; the interrupt block tracks asserted interrupts (in the
// Interrupt/InterruptA/InterruptB registers) until software acknowledges
// them, and implements interrupt propagation; each interrupt has an
// individual mask bit (in the Mask/MaskA/MaskB register), and there is also a
// primary mask bit that suppresses all interrupts; interrupt signals are
// propagated through masks to the INT_N pin
namespace fusb302 {
template <class RegType>
class Fusb302Register : public hwreg::I2cRegisterBase<RegType, uint8_t, 1> {
public:
static RegType ReadFrom(fidl::ClientEnd<fuchsia_hardware_i2c::Device>& i2c) {
auto reg = RegType::Get().FromValue(0);
ZX_ASSERT(reg.I2cRegisterBase::ReadFrom(i2c) == ZX_OK);
return reg;
}
// Only writes to the register if the mutation changes the value.
//
// Usage:
// zx::result<> result = Control0Reg::ReadModifyWrite(
// i2c_, [&](Control0Reg& control0) {
// control0.set_tx_flush(true);
// });
// if (result.is_ok()) {
// ...
// }
template <typename F>
static zx::result<> ReadModifyWrite(fidl::ClientEnd<fuchsia_hardware_i2c::Device>& i2c,
F modify_function) {
auto reg = RegType::Get().FromValue(0);
const zx_status_t read_status = reg.I2cRegisterBase::ReadFrom(i2c);
if (read_status != ZX_OK) {
return zx::error_result(read_status);
}
const uint8_t old_value = reg.reg_value();
modify_function(reg);
if (reg.reg_value() == old_value) {
return zx::ok();
}
const zx_status_t write_status = reg.WriteTo(i2c);
if (write_status != ZX_OK) {
FDF_LOG(ERROR, "Failed to write register 0x%02x over I2C: %s", reg.reg_addr(),
zx_status_get_string(write_status));
return zx::error_result(write_status);
}
return zx::ok();
}
};
// DEVICE ID - Identifies the chip.
//
// Rev 5 datasheet: Table 17 on page 19
class DeviceIdReg : public Fusb302Register<DeviceIdReg> {
public:
DEF_FIELD(7, 4, version_id);
DEF_FIELD(3, 2, product_id);
DEF_FIELD(1, 0, revision_id);
// The product name is "F302", suffixed by the return value character.
//
// Returns "?" if the version ID is invalid.
char VersionCharacter() const;
// The revision is the returned character.
char RevisionCharacter() const;
// Returns a C-style string that contains the expanded product ID.
const char* ProductString() const;
static auto Get() { return hwreg::I2cRegisterAddr<DeviceIdReg>(0x01); }
};
// Mutually exclusive configurations of the switch blocks attached to CC pins.
//
// Rev 5 datasheet: Figure 6 "Configuration Channel Switch Functionality" on
// page 6, Figure 3 "Functional Block Diagram" on page 3, Table 10 "Type-C CC
// Switch" on page 15.
enum class SwitchBlockConfig : int8_t {
// The CC pin is floating.
kOpen,
// The CC pin is connected to a <= 2.18V (VUFPDB) current source.
//
// The current source is used to advertise the power source capability,
// following typec2.2 4.5.1.2.1 "Detecting a Valid Source-to-Sink Connection".
//
// The current source is configured by the HOST_CUR field in the
// `Control0Reg` register.
kPullUp,
// The CC pin is connected to the ground via a 5.1 Ohm (RDEVICE) resistor.
//
// The pull-down resistor value follows Table 4-27 "Sink CC Termination (Rd)
// Requirements" in typec2.2 4.11.1 "Termination Parameters".
kPullDown,
// The CC pin is connected to the VCONN source. Powers cable electronics.
kConnectorVoltage,
};
// Descriptor for logging and debugging.
const char* SwitchBlockConfigToString(SwitchBlockConfig config);
// SWITCHES0 - Controls most CC (Configuration Channel) circuit switches.
//
// After reset, both CC pins will be connected to pull-down resistors, and none
// of them will be connected to the measure block.
//
// Rev 5 datasheet: Table 18 on page 20
class Switches0Reg : public Fusb302Register<Switches0Reg> {
public:
// Read/written by {Set}SwitchBlockConfigFor() and {Set}MeasureConfigChannelPinSwitch().
DEF_BIT(7, pu_en2);
DEF_BIT(6, pu_en1);
DEF_BIT(5, vconn_cc2);
DEF_BIT(4, vconn_cc1);
DEF_BIT(3, meas_cc2);
DEF_BIT(2, meas_cc1);
DEF_BIT(1, pdwn2);
DEF_BIT(0, pdwn1);
SwitchBlockConfig SwitchBlockConfigFor(usb_pd::ConfigChannelPinId cc_pin_id) const;
Switches0Reg& SetSwitchBlockConfig(usb_pd::ConfigChannelPinId cc_pin_id,
SwitchBlockConfig connection);
usb_pd::ConfigChannelPinSwitch MeasureBlockInput() const;
Switches0Reg& SetMeasureBlockInput(usb_pd::ConfigChannelPinSwitch input);
static auto Get() { return hwreg::I2cRegisterAddr<Switches0Reg>(0x02); }
};
// SWITCHES1 - Configures the BMC PHY.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, the GoodCRC header bits will be set to (Sink, PD Rev 2.0, UFP).
//
// Rev 5 datasheet: Table 19 on page 20, Figure 12 "USB BMC Power Delivery
// Blocks" on page 11, Figure 3 "Functional Block Diagram" on page 3
class Switches1Reg : public Fusb302Register<Switches1Reg> {
public:
// The "Port Power Role" bit in the header of auto-generated GoodCRC messages.
//
// The datasheet values match the values in usbpd3.1 6.2.1.1.4 "Port Power
// Role".
DEF_ENUM_FIELD(usb_pd::PowerRole, 7, 7, power_role);
// The "Spec Revision" bits in the header of auto-generated GoodCRC messages.
//
// The datasheet values match the values in usbpd3.1 6.2.1.1.5 "Specification
// Revision". While the Rev 5 datasheet marks 0b10 (Revision 3.0) as "Do Not
// Use", experiments with a FUSB302BMPX did not encounter any issue using the
// Revision 3.0 value.
DEF_ENUM_FIELD(usb_pd::SpecRevision, 6, 5, spec_rev);
// The "Port Data Role" bit in the header of auto-generated GoodCRC messages.
//
// The datasheet values are intended to match the values in usbpd3.1 6.2.1.1.6
// "Port Data Role". The datasheet conflates the power roles Sink/Source with
// the data roles UFP/DFP.
DEF_ENUM_FIELD(usb_pd::DataRole, 4, 4, data_role);
// If true, the PD Protocol Layer generates and sends GoodCRC messages.
//
// This bit enables the hardware implementation of usbpd3.1 6.3.1 "GoodCRC
// Message".
//
// Delegating GoodCRC generation to the hardware relieves the application
// processor from having to react to every USB PD message in 195 us (tTransmit
// in usbpd3.1). On the flip side, having the PD Protocol Layer acknowledge
// all received messages leads to tighter timing requirements in some cases.
// For example, a Sink has 5 seconds (tNoResponse) to generate a Request in
// response to a Source_Capabilities, but that time is cut down to 30
// milliseconds (tSenderResponse) after the Sink sends GoodCRC.
DEF_BIT(2, auto_crc);
// Read/written by {Set}BmcPhyConnection().
DEF_BIT(1, txcc2);
DEF_BIT(0, txcc1);
usb_pd::ConfigChannelPinSwitch BmcPhyConnection() const;
Switches1Reg& SetBmcPhyConnection(usb_pd::ConfigChannelPinSwitch connection);
static auto Get() { return hwreg::I2cRegisterAddr<Switches1Reg>(0x03); }
};
// MEASURE - Configures the measuring block.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, the measure block will be set up to measure CC pins, and its
// MDAC will generate 2.142 V.
//
// Rev 5 datasheet: Table 20 on page 21
class MeasureReg : public Fusb302Register<MeasureReg> {
public:
// If true, the VBUS is connected to the measure block's input.
//
// If this bit is true, `Switches0Reg::MeasureConfigChannelPinSwitch()` must be kNone. The
// datasheet schematics suggest that breaking this precondition will short the
// VBUS and one of the CC pins.
//
// If this bit is true, the MDAC's base is multiplied by 10 (420mV, instead of
// 42mV).
DEF_BIT(6, meas_vbus);
// Read/written by {Set}ComparatorVoltageMv().
DEF_FIELD(5, 0, mdac);
// The reference voltage for the comparator, in mV.
int32_t ComparatorVoltageMv() const;
// Configures the MDAC that generates the comparator reference voltage.
//
// The comparator's other input must be configured (via
// `set_meas_vbus()`) for the intended comparison before
// using this helper.
MeasureReg& SetComparatorVoltageMv(int32_t voltage_mv);
static auto Get() { return hwreg::I2cRegisterAddr<MeasureReg>(0x04); }
private:
// The base voltage for the measuring block's MDAC (Multiplier DAC).
int16_t MdacBaseMv() const;
};
// SLICE - Configures the receiver in the BMC PHY.
//
// The Rev 5 datasheet is a bit vague around this register, so getting a clear
// description would require an investigation. This investigation wasn't done,
// because the driver doesn't need to modify the register.
//
// Rev 5 datasheet: Table 21 on page 21
class SliceReg : public Fusb302Register<SliceReg> {
public:
// Values for `sdac_hys`.
enum class Hysteresis {
kNone = 0b00, // Higher threshold = lower threshold
k85mV = 0b01, // Higher threshold = lower threshold + 5
k170mV = 0b10, // Higher threshold = lower threshold + 10 (0x0A)
k255mV = 0b11, // Higher threshold = lower threshold + 32 (0x20)
};
// Configures the higher threshold used by the BMC SDAC (Slicer DAC).
DEF_ENUM_FIELD(Hysteresis, 7, 6, sdac_hys);
// Configures the lower threshold used by the BMC SDAC (Slicer DAC).
//
// The threshold can be used to meet the BMC receive mask under various noise
// conditions.
DEF_FIELD(5, 0, sdac);
static auto Get() { return hwreg::I2cRegisterAddr<SliceReg>(0x05); }
};
// CONTROL0 - Various settings. Most apply to BMC transmission.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, all interrupts will be masked, the pull-up current source will
// be set to 80 uA, and the transmitter optimization will be disabled.
//
// Rev 5 datasheet: Table 22 on page 21
class Control0Reg : public Fusb302Register<Control0Reg> {
public:
// Set to true by the driver to start flushing the transmitter FIFO.
//
// The hardware sets this bit to false when it's done flushing the FIFO.
DEF_BIT(6, tx_flush);
// If true, all interrupts are masked, regardless of other mask registers.
//
// This bit is a central switch that supplements the masks set by
DEF_BIT(5, int_mask);
// Values for `host_cur`.
//
// The values follow Table 4-26 "Source Termination (Rp)" in typec2.2 4.11.1
// "Termination Parameters".
enum PullUpCurrent {
kNone = 0b00,
// Standard USB current capability.
kUsbStandard_80uA = 0b01,
// USB Type C Current, maximum 1.5A @ 5V.
kUsb1500mA_180uA = 0b10,
// USB Type C Current, maximum 3A @ 5V.
kUsb3000mA_330uA = 0b11,
};
// Amount of current generated in the pull-up current source.
//
// The pull-up current source can be connected to CC1 or CC2, by setting
// `Switches1Reg::SetBmcPhyConnection()`, to advertise USB power source
// capabilities.
DEF_ENUM_FIELD(PullUpCurrent, 3, 2, host_cur);
// If true, the transmitter is started upon the reception of a correct CRC.
//
// To take advantage of this optimization, the driver needs to start writing
// to the transmitter FIFO within 330 microseconds of the FUSB302 asserting
// the I_CRC_CHK interrupt. This only seems plausible in a constrained
// environment.
DEF_BIT(1, auto_pre);
// Set to true by the driver to start a transmission.
//
// The hardware sets this bit to false when it's done powering up the
// transmitter driver.
//
// After the transmitter is powered up, it starts transmitting the USB PD
// message preamble. The driver can keep writing to the transmitter FIFO while
// the preamble is transmitted.
DEF_BIT(0, tx_start);
static auto Get() { return hwreg::I2cRegisterAddr<Control0Reg>(0x06); }
};
// CONTROL1 - Configures the BMC PHY. Most bits apply to the Rx (receiver).
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, all SOP' / SOP" messages are dropped, and the BIST Carrier Mode
// pattern is disabled.
//
// Rev 5 datasheet: Table 23 on page 22
class Control1Reg : public Fusb302Register<Control1Reg> {
public:
// If false, SOP" (double-prime) debug messages are silently dropped.
//
// The USB PD spec does not standardize any SOP" debug messages.
DEF_BIT(6, ensop2db);
// If false, SOP' (prime) debug messages are silently dropped.
//
// The USB PD spec does not standardize any SOP' debug messages.
DEF_BIT(5, ensop1db);
// If true, the transmitter will send the BIST Carrier Mode pattern.
//
// This helps implement usbpd3.1 6.4.3.1 "BIST Carrier Mode".
DEF_BIT(4, bist_mode2);
// Set to true by the driver to start flushing the receiver FIFO.
//
// The hardware sets this bit to false when it's done flushing the FIFO.
DEF_BIT(2, rx_flush);
// If false, SOP" (prime) messages are silently dropped.
//
// SOP" messages are only exchanged between the Source and a Cable Plug. They
// are not used in Source / Sink communication.
DEF_BIT(1, ensop2);
// If false, SOP' (prime) messages are silently dropped.
//
// SOP' messages are only exchanged between the Source and a Cable Plug. They
// are not used in Source / Sink communication.
DEF_BIT(0, ensop1);
static auto Get() { return hwreg::I2cRegisterAddr<Control1Reg>(0x07); }
};
// Values for `mode`.
enum class Fusb302RoleDetectionMode : int8_t {
kReserved = 0b00,
// DPR (Dual-Power-Role) toggles between advertising a Source and Sink.
//
// This is a partial implementation of the DRP (Dual Power Role) toggling
// functionality in the Type C spec. According to Table 10 in the Rev 5
// datasheet, a power detection cycle consists of advertising as a Sink
// for ~45 ms (tTOG1) and advertising as a Source for ~30 ms (tTOG2). The
// tDIS description mentions that a full "toggle" cycle takes tTOG1 + tTOG2.
kDualPowerRole = 0b01,
// Advertise Sink capabilities, check for attached Source.
//
// This is a partial implementation of the state machine in Figure 4-13
// "Connection State Diagram: Sink" in the USB Type C spec.
kSinkOnly = 0b10,
// Advertise Source capabilities, check for attached Sink.
//
// This is a partial implementation of the state machine in Figure 4-12
// "Connection State Diagram: Source" in the USB Type C spec.
kSourceOnly = 0b11,
};
// Descriptor for logging and debugging.
const char* Fusb302RoleDetectionModeToString(Fusb302RoleDetectionMode mode);
// CONTROL2 - Configures automated power role detection.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// The Rev 5 datasheet refers to the entire power role detection feature as
// "toggling". That term is a bit misleading, as the process can be configured
// to only advertise as a Source or Sink, in which case no DRP toggling is
// involved.
//
// After reset, automated power role detection is disabled.
//
// Rev 5 datasheet: Table 23 on page 22
class Control2Reg : public Fusb302Register<Control2Reg> {
public:
// Values for `tog_save_pwr`.
enum class SleepDuration {
k0ms = 0b00,
k40ms = 0b01,
k80ms = 0b10,
k160ms = 0b11,
};
// The duration of sleep intervals between power role detection cycles.
//
// This field is only used when `automated_dual_power_role_toggle` is set. It
// configures the amount of time spent in a low-power sleep state after one
// cycle of the power role detection process completes.
DEF_ENUM_FIELD(SleepDuration, 7, 6, tog_save_pwr);
// If true, the power role detection process will ignore Ra termination.
//
// This bit is only used when `toggle` is set. If set,
// the hardware will not end the DRP toggling cycle when it detects Ra
// termination (used by Audio Accessories) on its CC pins.
DEF_BIT(5, tog_rd_only);
// If true, Wake Detection is enabled.
//
// Wake Detection only works if the `wake_detection_powered_on` field in the
// `PowerReg` register is set to true.
DEF_BIT(3, wake_en);
// The behavior in a cycle of the automated power role detection process.
DEF_ENUM_FIELD(Fusb302RoleDetectionMode, 2, 1, mode);
// If true, the hardware performs automated Type C power role detection.
//
// This bit enables the partial hardware implementation of the state machines
// in usbpd2.2 4.5 "Configuration Channel (CC)". The driver sets this bit to
// true to start the power role detection process. The hardware sets this bit
// to false after it detects a Port Partner and establishes its own power
// role.
//
// Before setting this bit to true, the driver should configure other
// registers as described in the "Toggle Functionality" section on page 7 of
// the Rev 5 datasheet.
DEF_BIT(0, toggle);
static auto Get() { return hwreg::I2cRegisterAddr<Control2Reg>(0x08); }
};
// CONTROL3 - Configures the PD Protocol Layer above the BMC PHY.
//
// After reset, all automated transmissions are disabled. The GoodCRC timeout
// retry count is set to 3, but the setting is not in effect, because
// re-transmissions are disabled.
//
// Rev 5 datasheet: Table 23 on page 22
class Control3Reg : public Fusb302Register<Control3Reg> {
public:
// Set to true by the driver to transmit a Hard Reset message.
//
// The transmitter in the BMC PHY follows usbpd3.1 5.6.4 "Hard Reset" to
// send a Hard Reset packet over the CC pin that it is currently connected to.
//
// The hardware sets this bit to false when it's done transmitting.
DEF_BIT(6, send_hard_reset);
// If true, received messages are dropped after generating GoodCRC replies.
//
// This helps implement usbpd3.1 6.4.3.2 "BIST Test Data". Enabling it helps
// avoid receiver FIFO overflow when the USB PD compliance testing equipment
// repeatedly sends the same data message.
DEF_BIT(5, bist_tmode);
// If true, automatically send HardReset messages per the USB PD spec.
//
//
DEF_BIT(4, auto_hardreset);
// If true, automatically send SoftReset messages per the USB PD spec.
//
// This bit enables the hardware implementation of the SoftReset provision in
// usbpd3.1 6.7.2 "Retry Counter" and 6.6.9.1 "tSoftReset".
DEF_BIT(3, auto_softreset);
// Number of re-transmissions after timing out waiting for a GoodCRC message.
//
// Only meaningful if `auto_retry` is true.
//
// This field acts as the nRetryCount value in usbpd3.1 6.7.2 "Retry Counter".
// usbpd3.1 6.7.7 "Counter Values and Counters" states that this value should
// be set to 2.
DEF_FIELD(2, 1, n_retries);
// If true, messages are retransmitted after timing out waiting for a GoodCRC.
//
// This bit enables the hardware implementation of usbpd3.1 6.6.1
// "CRCReceiveTimer".
DEF_BIT(0, auto_retry);
static auto Get() { return hwreg::I2cRegisterAddr<Control3Reg>(0x09); }
};
// MASK - Individual interrupt masking for the requests in `InterruptReg`.
//
// After reset, no interrupt is individually masked.
//
// Rev 5 datasheet: Table 26 on page 23
class MaskReg : public Fusb302Register<MaskReg> {
public:
// See the corresponding bits in `InterruptReg` for interrupt definitions.
DEF_BIT(7, m_vbusok);
DEF_BIT(6, m_activity);
DEF_BIT(5, m_comp_chng);
DEF_BIT(4, m_crc_chk);
DEF_BIT(3, m_alert);
DEF_BIT(2, m_wake);
DEF_BIT(1, m_collision);
DEF_BIT(0, m_bc_lvl);
static auto Get() { return hwreg::I2cRegisterAddr<MaskReg>(0x0a); }
static MaskReg FromAllInterruptsMasked() { return Get().FromValue(0xff); }
};
// POWER - Configures the chip's power gates.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, only the Wake Detection power gate is enabled.
//
// Rev 5 datasheet: Table 27 on page 23
class PowerReg : public Fusb302Register<PowerReg> {
public:
// If true, PWR[3] (power gate 3) is enabled.
//
// PWR[3] gates power to the oscillator used by the Tx (transmitter) in the
// BMC PHY. Experiments with a FUSB302BMPX indicate that BMC reception works
// when this power gate is disabled, but transmission fails silently.
DEF_BIT(3, pwr3);
// If true, PWR[2] (power gate 2) is enabled.
//
// Gates power to the measure block.
DEF_BIT(2, pwr2);
// If true, PWR[1] (power gate 1) is enabled.
//
// Gates power to the Rx (receiver) in the BMC PHY, and to the
// reference voltage generators used by the comparators in the measure block.
DEF_BIT(1, pwr1);
// If true, PWR[0] (power gate 0) is enabled.
//
// Gates power to the Wake Detector, which includes a bandgap voltage
// reference circuit and a voltage comparator.
DEF_BIT(0, pwr0);
static auto Get() { return hwreg::I2cRegisterAddr<PowerReg>(0x0b); }
};
// RESET - Triggers for the reset unit.
//
// Reset bits are W/C (write/clear). The register should not be changed using
// read-modify-write.
//
// Rev 5 datasheet: Table 28 on page 24
class ResetReg : public Fusb302Register<ResetReg> {
public:
// Set to true by the driver to start a transmitter / receiver PD logic reset.
//
// The hardware sets this bit to false when it's done resetting the PD (Power
// Delivery) logic in the transmitter and receiver.
DEF_BIT(1, pd_reset);
// Set to true by the driver to start a full software reset.
//
// The hardware sets this bit to false when it's done resetting. This involves
// setting all I2C registers to their default values.
DEF_BIT(0, sw_res);
static auto Get() { return hwreg::I2cRegisterAddr<ResetReg>(0x0c); }
};
// OCPREG - Configures the VCONN OCP (Overcurrent Protection) circuit threshold.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, the VCONN OCP threshold is 800 mA.
//
// Rev 5 datasheet: Table 29 on page 24
class OcpReg : public Fusb302Register<OcpReg> {
public:
// If true, the threshold baseline is 100 mA. If false, the baseline is 10 mA.
DEF_BIT(3, ocp_range);
// Multiplies the threshold baseline by a number between 1 and 8.
DEF_FIELD(2, 0, ocp_cur);
// The OCP (Overcurrent Protection) circuit threshold, in mA.
int16_t ThresholdMilliamps() const;
// Sets the OCP (Overcurrent Protection) circuit threshold, in mA.
OcpReg& SetThresholdMilliamps(int16_t threshold_ma);
static auto Get() { return hwreg::I2cRegisterAddr<OcpReg>(0x0d); }
};
// MASKA - Individual interrupt masking for the requests in `InterruptAReg`.
//
// After reset, no interrupt is individually masked.
//
// Rev 5 datasheet: Table 30 on page 24
class MaskAReg : public Fusb302Register<MaskAReg> {
public:
// See the corresponding bits in `InterruptAReg` for interrupt definitions.
DEF_BIT(7, m_ocp_temp);
DEF_BIT(6, m_togdone);
DEF_BIT(5, m_softfail);
DEF_BIT(4, m_retryfail);
DEF_BIT(3, m_hardsent);
DEF_BIT(2, m_txsent);
DEF_BIT(1, m_softrst);
DEF_BIT(0, m_hardrst);
static auto Get() { return hwreg::I2cRegisterAddr<MaskAReg>(0x0e); }
static MaskAReg FromAllInterruptsMasked() { return Get().FromValue(0xff); }
};
// MASKB - Individual interrupt masking for the requests in `InterruptBReg`.
//
// After reset, no interrupt is individually masked.
//
// Rev 5 datasheet: Table 31 on page 24
class MaskBReg : public Fusb302Register<MaskBReg> {
public:
// See the corresponding bits in `InterruptBReg` for interrupt definitions.
DEF_BIT(0, m_gcrcsent);
static auto Get() { return hwreg::I2cRegisterAddr<MaskBReg>(0x0f); }
static MaskBReg FromAllInterruptsMasked() { return Get().FromValue(0x01); }
};
// CONTROL4 - Additional configuration for automated power role detection.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// After reset, Audio Accessories are not a special case for power role
// detection.
//
// Rev 5 datasheet: Table 31 on page 24
class Control4Reg : public Fusb302Register<Control4Reg> {
public:
// If true, power role detection takes Audio Accessories into account.
//
// When this bit is true, automated power role detection succeeds when Ra is
// detected on both CC pins, which signals an Audio Accessory.
//
// This bit is only used when the `tog_rd_only` field in
// `Control2Reg` is true. It effectively carves out an Ra case that is not
// ignored by the power role detection logic.
DEF_BIT(0, tog_exit_aud);
static auto Get() { return hwreg::I2cRegisterAddr<Control4Reg>(0x10); }
};
// STATUS0A - Additional configuration for automated power role detection.
//
// This register has reserved/undocumented bits. It can only be safely updated
// via read/modify/write operations.
//
// This is a read-only register.
//
// Rev 5 datasheet: Table 31 on page 24
class Status0AReg : public Fusb302Register<Status0AReg> {
public:
// If true, the PD Protocol Layer signaled that a Soft Reset was not received.
//
// The hardware implements usbpd3.1 6.6.1 "CRCReceiveTimer" and 6.7.2 "Retry
// Counter". This bit signals that the retry counter has reached the
// nRetryCount limit set by `n_retries` in `Control3Reg`
// after sending a a Soft Reset message.
//
// This bit is cleared when the BMC PHY is asked to start a transmission via
// the `tx_start` bit in `Control0Reg` or via the TXON token, and
// when the BMC PHY is asked to send a Hard Reset message via the
// `send_hard_reset` bit in the `Control3Reg` register.
DEF_BIT(5, softfail);
// If true, the PD Protocol Layer signaled that a message was not received.
//
// The hardware implements usbpd3.1 6.6.1 "CRCReceiveTimer" and 6.7.2 "Retry
// Counter". This bit signals that the retry counter has reached the
// nRetryCount limit set by `n_retries` in `Control3Reg`
// after sending a non-Reset message.
//
// This bit is cleared under the same conditions as `softfail`.
DEF_BIT(4, retryfail);
// If true, the hardware is forcing PWR[3] (power gate 3) to be enabled.
//
// This is a hardware override for the `pwr3` bit in the
// `PowerReg` register.
DEF_BIT(3, power3);
// If true, the hardware is forcing PWR[2] (power gate 2) to be enabled.
//
// This is a hardware override for the `pwr2` bit in the
// `PowerReg` register.
DEF_BIT(2, power2);
// If true, the receiver in the BMC PHY decoded a Soft Reset message.
//
// The Rev 5 datasheet does not specify when this bit gets reset.
DEF_BIT(1, softrst);
// If true, the receiver in the BMC PHY decoded a Hard Reset ordered set.
//
// The Rev 5 datasheet does not specify when this bit gets reset.
DEF_BIT(0, hardrst);
static auto Get() { return hwreg::I2cRegisterAddr<Status0AReg>(0x3c); }
};
// The reported state of the power role detection hardware logic.
//
// Values are obtained from the `togss` field in the
// `Status1AReg` register
enum class PowerRoleDetectionState {
kDetecting = 0b000, // Power role detection process still running
kSourceOnCC1 = 0b001, // Power Source, Configuration Channel is on CC1
kSourceOnCC2 = 0b010, // Power Source, Configuration Channel is on CC2
kSinkOnCC1 = 0b101, // Power Sink, Configuration Channel is on CC1
kSinkOnCC2 = 0b110, // Power Sink, Configuration Channel is on CC2
kAudioAccessory = 0b111, // Audio Accessory found, no Configuration Channel
};
usb_pd::ConfigChannelPinSwitch WiredCcPinFromPowerRoleDetectionState(PowerRoleDetectionState state);
// `state` must be one of the states that has a Configuration Channel.
usb_pd::PowerRole PowerRoleFromDetectionState(PowerRoleDetectionState state);
// Descriptor for logging and debugging.
const char* PowerRoleDetectionStateToString(PowerRoleDetectionState state);
// STATUS1A
//
// This is a read-only register.
//
// Rev 5 datasheet: Table 34 on page 25
class Status1AReg : public Fusb302Register<Status1AReg> {
public:
// The state of the power role detection hardware.
//
//
DEF_ENUM_FIELD(PowerRoleDetectionState, 5, 3, togss);
// If true, the last message in the Rx (receive) FIFO starts with SOP" Debug.
//
// This bit can't become true if `ensop2db` in
// `Control1Reg` is false.
DEF_BIT(2, rxsop2db);
// If true, the last message in the Rx (receive) FIFO starts with SOP' Debug.
//
// This bit can't become true if `ensop1db` in
// `Control1Reg` is false.
DEF_BIT(1, rxsop1db);
// If true, the last message in the Rx (receive) FIFO starts with SOP.
DEF_BIT(0, rxsop);
static auto Get() { return hwreg::I2cRegisterAddr<Status1AReg>(0x3d); }
};
// INTERRUPTA - Interrupt requests for some events
//
// This is a read-only register. The interrupt request bits are cleared when the
// register is read.
//
// The `MaskAReg` register has corresponding bits for individually masking the
// interrupt requests here.
//
// Rev 5 datasheet: Table 35 on page 26
class InterruptAReg : public Fusb302Register<InterruptAReg> {
public:
// One of the thermal monitors signaled an issue.
//
// The fields `ocp` and / or `ovrtemp` in
// `Status1Reg` became true.
DEF_BIT(7, i_ocp_temp);
// The hardware power role detection process finished.
//
// The field `togss` in `Status0AReg` transitioned away
// from `kDetecting`.
DEF_BIT(6, i_togdone);
// The field `soft_reset_loss_detected` in `Status0AReg` became true.
DEF_BIT(5, i_softfail);
// The field `message_loss_detected` in `Status0AReg` became true.
DEF_BIT(4, i_retryfail);
// The transmitter in the BMC PHY sent a Hard Reset ordered set.
DEF_BIT(3, i_hardsent);
// The PD protocol layer got a GoodCRC matching the last sent message.
//
// Experiments with FUSB302BMPX indicate that this interrupt is redundant with
// reading the message data from the Rx (receiver) FIFO. In other words, all
// GoodCRC messages signaled by this interrupt will show up in the Rx FIFO.
DEF_BIT(2, i_txsent);
// The field `softrst` in `Status0AReg` became true.
//
// Experiments with FUSB302BMPX indicate that this interrupt is redundant with
// reading the message data from the Rx (receiver) FIFO. In other words, all
// Soft Reset messages signaled by this interrupt will show up in the Rx FIFO.
DEF_BIT(1, i_softrst);
// The field `hardrst` in `Status0AReg` became true.
DEF_BIT(0, i_hardrst);
static auto Get() { return hwreg::I2cRegisterAddr<InterruptAReg>(0x3e); }
};
// INTERRUPTB - Interrupt requests for some events
//
// This is a read-only register. The interrupt request bits are cleared when the
// register is read.
//
// The `MaskBReg` register has corresponding bits for individually masking the
// interrupt requests here.
//
// Rev 5 datasheet: Table 36 on page 26
class InterruptBReg : public Fusb302Register<InterruptBReg> {
public:
// The BMC PHY transmitted a GoodCRC message generated by the Protocol Layer.
//
// This interrupt should not occur if `auto_crc` in
// `Switches1Reg` is false.
DEF_BIT(0, i_gcrcsent);
static auto Get() { return hwreg::I2cRegisterAddr<InterruptBReg>(0x3f); }
};
// CC pin voltage, as reported by the fixed comparators in the measure block.
//
// The fixed comparators can distinguish between the Sink terminations in
// typec2.2 4.11.3 "Voltage Parameters", Table 4.38 "Voltage on Sink CC pins
// (Multiple Source Current Announcements)".
//
// Rev 5 datasheet: Table 37 on page 27, field BC_LVL[1:0]
enum class FixedComparatorResult {
// The voltage threshold for rA. Below 200 mV.
kRa = 0b00,
// The voltage threshold for rD and Standard USB power rP. 200 mV - 660 mV.
kStandardUsbRd = 0b01,
// The voltage threshold for rD and Type C 1.5A power rP. 660 mV - 1,230 mV.
kTypeC1500mARd = 0b10,
// The voltage threshold for rD and Type C 3.0A power rP. Above 1,230 mV.
//
// When this level is reported, Table 5 in the Rev 5 datasheet recommends
// using the variable comparator in the measure block to confirm that the CC
// voltage is below 2.05 V.
kTypeC3000mARd = 0b11,
};
// Best-effort mapping from fixed comparator outcomes.
//
// A result of `kRp3000mA` means "anything above 1,230 mV" and should be
// supplemented by an upper bound check.
usb_pd::ConfigChannelTermination ConfigChannelTerminationFromFixedComparatorResult(
FixedComparatorResult result);
// STATUS0
//
// This is a read-only register.
//
// Rev 5 datasheet: Table 37 on page 27
class Status0Reg : public Fusb302Register<Status0Reg> {
public:
// If true, the VBUS voltage exceeds the minimum power supply voltage.
//
// Changes in this value trigger the `vbusok_change` interrupt
// request in the Interrupt register.
//
// This bit is only valid (and generates interrupts) if the measure block is
// powered on, which is gated by the `pwr2` field in
// `PowerReg`.
//
// The Rev 5 datasheet defines the "vVBUSthr" as 4.0 V in Table 10 "Type-C CC
// Switch" on page 15. typec2.2 4.4.2 "VBUS" defers to usb3.2 for the valid
// VBUS ranges. usb3.2 11.4.5 "VBUS Electrical Characteristics" states that
// the minimum VBUS voltage at an upstream connector Port is 4.0 V.
DEF_BIT(7, vbusok);
// If true, the BMC activity detector for the CC wire was triggered.
//
// The BMC activity detector triggers after 3 voltage transitions, and remains
// triggered while there are BMC transitions.
//
// The BMC activity detector uses the CC wire connected to ?? (what is
// "active CC line"?)
DEF_BIT(6, activity);
// If true, the variable voltage comparator's input exceeds the reference.
//
// Changes in this value trigger the `variable_comparator_result_pending`
// interrupt request in the `Interrupt` register.
DEF_BIT(5, comp);
// If true, the last message received by the BMC PHY had a correct CRC.
//
// This bit becomes valid after CRC comparison completes, and becomes invalid
// (is reset) when the BMC PHY receives the SOP* of a new message.
DEF_BIT(4, crc_chk);
// If true, the BMC PHY has encountered an error.
//
// The errors signaled are surfaced by the `tx_full` and
// `rx_full` bits in `Status1Reg.
//
// Despite the name used by the datasheet, this seems unrelated to the PD
// message defined in usbpd3.1 6.4.6 "Alert Message".
DEF_BIT(3, alert);
// If true, the Wake Detector was triggered.
//
// The Wake Detector triggers if the voltage of any of the CC pins is between
// 0.25 V (WAKE_low) and 1.45V (WAKE_high).
//
// The Wake Detector only works if its power gate is enabled, via the
// `pwr0` bit in `PowerReg`.
DEF_BIT(2, wake);
// The result of the fixed comparators in the measure block.
//
// This result is only meaningful when the measure block's input is connected
// to one of the CC pins. The driver is responsible for handling the CC pin
// voltage variation produced by BMC communication.
//
// Also, the result is only meaningful when power gates 1-3 are enabled, via
// the `pwr0`, `pwr1`, and `pwr2` bits in `PowerReg`.
DEF_ENUM_FIELD(FixedComparatorResult, 1, 0, bc_lvl);
static auto Get() { return hwreg::I2cRegisterAddr<Status0Reg>(0x40); }
};
// STATUS1
//
// This is a read-only register.
//
// Rev 5 datasheet: Table 39 on page 28
class Status1Reg : public Fusb302Register<Status1Reg> {
public:
// If true, the last message in the Rx (receive) FIFO starts with SOP".
//
// This bit can't become true if `ensop2` in
// `Control1Reg` is false.
DEF_BIT(7, rxsop2);
// If true, the last message in the Rx (receive) FIFO starts with SOP'.
//
// This bit can't become true if `ensop1` in `Control1Reg`
// is false.
DEF_BIT(6, rxsop1);
// Indicators for the BMC PHY Rx (receiver) FIFO.
DEF_BIT(5, rx_empty);
DEF_BIT(4, rx_full);
// Indicators for the BMC PHY Tx (transmitter) FIFO.
DEF_BIT(3, tx_empty);
DEF_BIT(2, tx_full);
// If true, the chip temperature is too high.
DEF_BIT(1, ovrtemp);
// If true, the VCONN OCP (over-current protection) circuit was tripped.
//
// The OCP current threshold is configured in the `OcpReg` register. The OCP
// circuit also trips if its temperature exceeds 145 Celsius (Tshut in the
// Rev 5 datashet).
//
// The OCP circuit is not connected if no CC pin is connected to the VCONN
// pin.
DEF_BIT(0, ocp);
static auto Get() { return hwreg::I2cRegisterAddr<Status1Reg>(0x41); }
};
// INTERRUPT - Interrupt requests for some events
//
// This is a read-only register. The interrupt request bits are cleared when the
// register is read.
//
// The `MaskReg` register has corresponding bits for individually masking the
// interrupt requests here.
//
// Rev 5 datasheet: Table 39 on page 28
class InterruptReg : public Fusb302Register<InterruptReg> {
public:
// The `vbusok` field in `Status0Reg` changed.
DEF_BIT(7, i_vbusok);
// The `cc_activity_detected` field in `Status0Reg` changed to true.
DEF_BIT(6, i_activity);
// The `variable_comparator_result` field in `Status0Reg` changed.
DEF_BIT(5, i_comp_chng);
// The `crc_chk` field in `Status0Reg` became valid.
//
// This is a signal that the BMC PHY layer just finished receiving a message.
DEF_BIT(4, i_crc_chk);
// The `alert` field in `Status0Reg` changed to true.
DEF_BIT(3, i_alert);
// The `wake` field in `Status0Reg` changed to true.
DEF_BIT(2, i_wake);
// The BMC PHY discarded a packet it was about to transmit due to CC activity.
//
// This behavior implements usbpd3.1 5.7 "Collision Avoidance". Collisions at
// the PHY layer signal an incorrect PD Protocol implementation.
DEF_BIT(1, i_collision);
// The `bc_lvl` field in `Status0Reg` changed.
DEF_BIT(0, i_bc_lvl);
static auto Get() { return hwreg::I2cRegisterAddr<InterruptReg>(0x42); }
};
// Receive fifo tokens decoded by FifosReg::AsReceiveTokenType().
//
// Rev 5 datasheet: Table 42 page 28
enum class ReceiveTokenType : uint8_t {
kSop = 0b111, // SOP (Source <-> Sink messages)
kSopPrime = 0b110, // SOP' (VCONN Source <-> Cable Plug messages)
kSopDoublePrime = 0b101, // SOP" (VCONN Source <-> Cable Plug messages)
kSopPrimeDebug = 0b100, // SOP' Debug - not specified
kSopDoublePrimeDebug = 0b011, // SOP" Debug - not specified
kUndocumented = 0b000, // Undocumented token
};
// Factory for transmit tokens used by the FIFOS register.
//
// Rev 5 datasheet: Table 41 page 28
class TransmitToken {
public:
// The special symbols in usbpd3.1 5.3 "Symbol Encoding".
//
// With the exception of EOP, these symbols only appear in the 4-symbol
// ordered sets described in usbpd3.1 5.4 "Ordered Sets".
static constexpr uint8_t kSync1 = 0x12;
static constexpr uint8_t kSync2 = 0x13;
static constexpr uint8_t kSync3 = 0x1b;
static constexpr uint8_t kRst1 = 0x15;
static constexpr uint8_t kRst2 = 0x16;
static constexpr uint8_t kEop = 0x14;
// Replaced with the packet CRC computed by the hardware.
//
// Drivers offload CRC computation to the hardware by placing this token in
// the transmit FIFO, right before `kEop`.
static constexpr uint8_t kInsertCrc = 0xff;
// Turns on the Tx (transmitter) driver in the BMC PHY.
//
// This is an alternative to setting the `tx_start` field in
// `Control0Reg` to true. This alternative results in less I2C communication,
// assuming the driver uses burst I2C writes to fill the FIFO.
//
// The Rev 5 datasheet recommends placing this token in the transmitter FIFO
// after all the message data.
static constexpr uint8_t kTxOn = 0xa1;
// Turns off the Tx (transmitter) driver in the BMC PHY.
//
// Should be used when done transmitting, to conserve power.
static constexpr uint8_t kTxOff = 0xfe;
// The transmitter will not interpret the following `data_bytes` as tokens.
static constexpr uint8_t PacketData(int8_t data_bytes);
// Instancing not allowed.
TransmitToken() = delete;
~TransmitToken() = delete;
};
// FIFOS (Transmit and Receive FIFOs)
//
// This "register" is an I2C FIFO-based interface to the BMC PHY's internal
// Tx (transmit) and Rx (receive) buffers, which are conceptually equivalent to
// ring buffers.
//
// Writing to this register enqueues data into the 48-byte transmit buffer.
// Writing multiple bytes must be done without address auto-increment. The
// driver is responsible for not issuing more writes when the buffer is full.
//
// Reading from this register dequeues data from the 80-byte receive buffer.
// Reading must be done without address auto-increment.
//
// The Rev 5 datasheet is unclear as to whether it's possible to queue up
// multiple messages (such as a GoodCRC and a control / data message) in the
// transmit buffer. Experiments with a FUSB302BMPX indicate that this
// optimization is not supported, and the chip may either silently drop one of
// the two messages, or take a long time to carry out the I2C writes.
//
// Rev 5 datasheet: Table 40 page 28
class FifosReg : public Fusb302Register<FifosReg> {
public:
// Rev 5 datasheet: Table 41 "Tokens used in FIFOs" ("FIFOs" should probably
// be "TxFIFO") and Table 42 "Tokens used in RxFIFO", both on page 29.
DEF_FIELD(7, 0, tx_rx_token);
static auto Get() { return hwreg::I2cRegisterAddr<FifosReg>(0x43); }
// Interprets a FIFO byte as a Rx (receiver) token.
//
// This is not meaningful for bytes that represent message data.
static ReceiveTokenType AsReceiveTokenType(uint8_t rx_token);
};
inline char DeviceIdReg::VersionCharacter() const {
if (version_id() < 0b1000) {
return '?'; // Invalid version.
}
// `version_id()` is a 4-bit field, so the cast result will be between 'A' and
// 'P'. No overflow / UB will occur.
return static_cast<char>('A' + (version_id() - 8));
}
inline char DeviceIdReg::RevisionCharacter() const {
// `revision_id()` is a 4-bit field, so the cast result will be between 'A' and
// 'D'. No overflow / UB will occur.
return static_cast<char>('A' + revision_id());
}
inline const char* DeviceIdReg::ProductString() const {
if (version_id() != 0b1001) {
static constexpr char kUnknownProduct[] = "Unknown product";
return kUnknownProduct;
}
static constexpr const char* kVersionBProductIds[] = {
"FUSB302BMPX",
"FUSB302B01MPX",
"FUSB302B10MPX",
"FUSB302B11MPX",
};
return kVersionBProductIds[product_id()];
}
inline SwitchBlockConfig Switches0Reg::SwitchBlockConfigFor(
usb_pd::ConfigChannelPinId cc_pin_id) const {
switch (cc_pin_id) {
case usb_pd::ConfigChannelPinId::kCc1: {
if (pdwn1()) {
return SwitchBlockConfig::kPullDown;
}
if (vconn_cc1()) {
return SwitchBlockConfig::kConnectorVoltage;
}
if (pu_en1()) {
return SwitchBlockConfig::kPullUp;
}
return SwitchBlockConfig::kOpen;
};
case usb_pd::ConfigChannelPinId::kCc2: {
if (pdwn2()) {
return SwitchBlockConfig::kPullDown;
}
if (vconn_cc2()) {
return SwitchBlockConfig::kConnectorVoltage;
}
if (pu_en2()) {
return SwitchBlockConfig::kPullUp;
}
return SwitchBlockConfig::kOpen;
};
}
ZX_DEBUG_ASSERT_MSG(false, "Invalid CC pin ID: %" PRIu8, static_cast<uint8_t>(cc_pin_id));
}
inline Switches0Reg& Switches0Reg::SetSwitchBlockConfig(usb_pd::ConfigChannelPinId cc_pin_id,
SwitchBlockConfig connection) {
switch (cc_pin_id) {
case usb_pd::ConfigChannelPinId::kCc1: {
return set_pdwn1(connection == SwitchBlockConfig::kPullDown)
.set_vconn_cc1(connection == SwitchBlockConfig::kConnectorVoltage)
.set_pu_en1(connection == SwitchBlockConfig::kPullUp);
};
case usb_pd::ConfigChannelPinId::kCc2: {
return set_pdwn2(connection == SwitchBlockConfig::kPullDown)
.set_vconn_cc2(connection == SwitchBlockConfig::kConnectorVoltage)
.set_pu_en2(connection == SwitchBlockConfig::kPullUp);
break;
};
}
ZX_DEBUG_ASSERT_MSG(false, "Invalid CC pin ID: %" PRIu8, static_cast<uint8_t>(cc_pin_id));
return *this;
}
inline usb_pd::ConfigChannelPinSwitch Switches0Reg::MeasureBlockInput() const {
if (meas_cc1()) {
return usb_pd::ConfigChannelPinSwitch::kCc1;
}
if (meas_cc2()) {
return usb_pd::ConfigChannelPinSwitch::kCc2;
}
return usb_pd::ConfigChannelPinSwitch::kNone;
}
inline Switches0Reg& Switches0Reg::SetMeasureBlockInput(usb_pd::ConfigChannelPinSwitch input) {
return set_meas_cc1(input == usb_pd::ConfigChannelPinSwitch::kCc1)
.set_meas_cc2(input == usb_pd::ConfigChannelPinSwitch::kCc2);
}
inline usb_pd::ConfigChannelPinSwitch Switches1Reg::BmcPhyConnection() const {
if (txcc1()) {
return usb_pd::ConfigChannelPinSwitch::kCc1;
}
if (txcc2()) {
return usb_pd::ConfigChannelPinSwitch::kCc2;
}
return usb_pd::ConfigChannelPinSwitch::kNone;
}
inline Switches1Reg& Switches1Reg::SetBmcPhyConnection(usb_pd::ConfigChannelPinSwitch connection) {
return set_txcc1(connection == usb_pd::ConfigChannelPinSwitch::kCc1)
.set_txcc2(connection == usb_pd::ConfigChannelPinSwitch::kCc2);
}
inline int16_t MeasureReg::MdacBaseMv() const {
// The MDAC base when the comparator input is a CC pin.
static constexpr int16_t kMdacCcBaseMv = 42;
// The MDAC base when the comparator input is the VBUS pin.
static constexpr int16_t kMdacVbusBaseMv = 420;
return meas_vbus() ? kMdacVbusBaseMv : kMdacCcBaseMv;
}
inline int32_t MeasureReg::ComparatorVoltageMv() const {
// The multiplication will not overflow (causing UB) because `mdac`
// is a 6-bit field. The maximum result is 26,880.
return int32_t{MdacBaseMv() * static_cast<int16_t>(mdac() + 1)};
}
inline MeasureReg& MeasureReg::SetComparatorVoltageMv(int32_t voltage_mv) {
const int16_t mdac_base = MdacBaseMv();
// We check the input range instead of clamping because the voltages involved
// in the USB specifications pass the checks, so we can rely on the checks to
// catch bugs.
ZX_DEBUG_ASSERT(voltage_mv > mdac_base / 2);
ZX_DEBUG_ASSERT(voltage_mv < mdac_base * 64 + mdac_base / 2);
// The input range documented above guarantees that the addition and casts do
// not overflow (causing UB).
const int8_t mdac_multiplier =
static_cast<int8_t>((static_cast<int16_t>(voltage_mv) + mdac_base / 2) / mdac_base - 1);
return set_mdac(mdac_multiplier);
}
inline int16_t OcpReg::ThresholdMilliamps() const {
const int16_t baseline_ma = ocp_range() ? 100 : 10;
// The addition will not overflow (causing UB) because
// `ocp_cur()` is a 3-bit field.
const int16_t multiplier = static_cast<int16_t>(int16_t{ocp_cur()} + 1);
// The multiplication will not overflow (causing UB) because the result is
// between 100 and 800.
return static_cast<int16_t>(baseline_ma * multiplier);
}
inline OcpReg& OcpReg::SetThresholdMilliamps(int16_t threshold_ma) {
ZX_DEBUG_ASSERT(threshold_ma >= 10);
ZX_DEBUG_ASSERT(threshold_ma <= 800);
ZX_DEBUG_ASSERT(threshold_ma % 10 == 0);
ZX_DEBUG_ASSERT(threshold_ma < 100 || threshold_ma % 100 == 0);
ZX_DEBUG_ASSERT(threshold_ma != 90);
const bool ocp_range = threshold_ma >= 100;
const int16_t baseline = ocp_range ? 100 : 10;
const int8_t multiplier = static_cast<int8_t>(threshold_ma / baseline);
// The subtraction will not overflow (causing UB) because `multiplier` will be
// between 1 and 8.
return set_ocp_range(ocp_range).set_ocp_cur(static_cast<int8_t>(multiplier - 1));
}
inline usb_pd::ConfigChannelPinSwitch WiredCcPinFromPowerRoleDetectionState(
PowerRoleDetectionState state) {
const bool cc1_terminated = (static_cast<int8_t>(state) & 0b001) != 0;
const bool cc2_terminated = (static_cast<int8_t>(state) & 0b010) != 0;
if (cc1_terminated == cc2_terminated) {
return usb_pd::ConfigChannelPinSwitch::kNone;
}
return cc1_terminated ? usb_pd::ConfigChannelPinSwitch::kCc1
: usb_pd::ConfigChannelPinSwitch::kCc2;
}
inline usb_pd::PowerRole PowerRoleFromDetectionState(PowerRoleDetectionState state) {
const bool cc1_terminated = (static_cast<int8_t>(state) & 0b001) != 0;
const bool cc2_terminated = (static_cast<int8_t>(state) & 0b010) != 0;
ZX_DEBUG_ASSERT(cc1_terminated != cc2_terminated);
const bool is_sink = (static_cast<int8_t>(state) & 0b100) != 0;
return is_sink ? usb_pd::PowerRole::kSink : usb_pd::PowerRole::kSource;
}
inline usb_pd::ConfigChannelTermination ConfigChannelTerminationFromFixedComparatorResult(
FixedComparatorResult result) {
switch (result) {
case FixedComparatorResult::kRa:
return usb_pd::ConfigChannelTermination::kRa;
case FixedComparatorResult::kStandardUsbRd:
return usb_pd::ConfigChannelTermination::kRpStandardUsb;
case FixedComparatorResult::kTypeC1500mARd:
return usb_pd::ConfigChannelTermination::kRp1500mA;
case FixedComparatorResult::kTypeC3000mARd:
return usb_pd::ConfigChannelTermination::kRp3000mA;
}
ZX_DEBUG_ASSERT_MSG(false, "Invalid result");
// This can use `kOpen` if we decide to remove `kUnknown`.
return usb_pd::ConfigChannelTermination::kUnknown;
}
// static
constexpr uint8_t TransmitToken::PacketData(int8_t data_bytes) {
ZX_DEBUG_ASSERT_MSG(data_bytes >= 2, "Missing header bytes");
ZX_DEBUG_ASSERT_MSG(data_bytes <= 30, "At most 2 header bytes and 7x4 data object bytes");
return (0b100 << 5) | data_bytes;
}
// static
inline ReceiveTokenType FifosReg::AsReceiveTokenType(uint8_t rx_token) {
const uint8_t token_type_bits = rx_token >> 5;
if (token_type_bits < 0b011) {
// The Rev 5 datasheet labels this range as "Do not use".
return ReceiveTokenType::kUndocumented;
}
// The check above guarantees that `token_type_bits` matches one of the enum's
// members.
return static_cast<ReceiveTokenType>(token_type_bits);
}
} // namespace fusb302
#endif // SRC_DEVICES_POWER_DRIVERS_FUSB302_REGISTERS_H_