| // 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_ |