| // Copyright 2022 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_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_REGISTERS_DDI_H_ |
| #define SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_REGISTERS_DDI_H_ |
| |
| #include <lib/ddk/debug.h> |
| #include <zircon/assert.h> |
| |
| #include <cstdint> |
| #include <optional> |
| |
| #include <hwreg/bitfields.h> |
| |
| #include "src/graphics/display/drivers/intel-i915/hardware-common.h" |
| |
| namespace registers { |
| |
| // Interrupt registers for the south (in the PCH) display engine. |
| // |
| // SINTERRUPT is made up of the interrupt registers below. |
| // - ISR (Interrupt Status Register), also abbreviated to SDE_ISR |
| // - IMR (Interrupt Mask Register), also abbreviated to SDE_IMR |
| // - IIR (Interrupt Identity Register), also abbreviated to SDE_IIR |
| // - IER (Interrupt Enable Register), also abbreviated to SDE_IER |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 pages 1196-1197 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 pages 820-821 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 pages 800-801 |
| // |
| // The individual bits in each register are covered in the South Display Engine |
| // Interrupt Bit Definition, or SDE_INTERRUPT. |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 pages 1262-1264 |
| // DG1: IHD-OS-DG1-Vol 2c-2.21 Part 2 pages 1328-1329 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 pages 874-875 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 pages 854-855 |
| class SdeInterruptBase : public hwreg::RegisterBase<SdeInterruptBase, uint32_t> { |
| public: |
| // SDE_INTERRUPT documents the base MMIO offset. SINTERRUPT documents the |
| // individual register offsets. |
| static constexpr uint32_t kSdeIntMask = 0xc4004; |
| static constexpr uint32_t kSdeIntIdentity = 0xc4008; |
| static constexpr uint32_t kSdeIntEnable = 0xc400c; |
| |
| DEF_BIT(23, gmbus); |
| |
| hwreg::BitfieldRef<uint32_t> skl_ddi_bit(i915::DdiId ddi_id) { |
| uint32_t bit; |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| bit = 24; |
| break; |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| case i915::DdiId::DDI_D: |
| bit = 20 + ddi_id; |
| break; |
| case i915::DdiId::DDI_E: |
| bit = 25; |
| break; |
| default: |
| bit = -1; |
| } |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> icl_ddi_bit(i915::DdiId ddi_id) { |
| uint32_t bit; |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| bit = 16 + ddi_id - i915::DdiId::DDI_A; |
| break; |
| case i915::DdiId::DDI_TC_1: |
| case i915::DdiId::DDI_TC_2: |
| case i915::DdiId::DDI_TC_3: |
| case i915::DdiId::DDI_TC_4: |
| case i915::DdiId::DDI_TC_5: |
| case i915::DdiId::DDI_TC_6: |
| bit = 24 + ddi_id - i915::DdiId::DDI_TC_1; |
| break; |
| default: |
| bit = -1; |
| } |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get(uint32_t offset) { return hwreg::RegisterAddr<SdeInterruptBase>(offset); } |
| }; |
| |
| // DE_HPD_INTERRUPT : Display Engine HPD Interrupts for Type C / Thunderbolt (since gen11) |
| class HpdInterruptBase : public hwreg::RegisterBase<HpdInterruptBase, uint32_t> { |
| public: |
| static constexpr uint32_t kHpdIntMask = 0x44474; |
| static constexpr uint32_t kHpdIntIdentity = 0x44478; |
| static constexpr uint32_t kHpdIntEnable = 0x4447c; |
| |
| hwreg::BitfieldRef<uint32_t> tc_hotplug(i915::DdiId ddi_id) { |
| ZX_DEBUG_ASSERT(ddi_id >= i915::DdiId::DDI_TC_1); |
| ZX_DEBUG_ASSERT(ddi_id <= i915::DdiId::DDI_TC_6); |
| uint32_t bit = 16 + ddi_id - i915::DdiId::DDI_TC_1; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> tbt_hotplug(i915::DdiId ddi_id) { |
| ZX_DEBUG_ASSERT(ddi_id >= i915::DdiId::DDI_TC_1); |
| ZX_DEBUG_ASSERT(ddi_id <= i915::DdiId::DDI_TC_6); |
| uint32_t bit = ddi_id - i915::DdiId::DDI_TC_1; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get(uint32_t offset) { return hwreg::RegisterAddr<HpdInterruptBase>(offset); } |
| }; |
| |
| // TBT_HOTPLUG_CTL : Thunderbolt Hot Plug Control (since gen11) |
| class TbtHotplugCtrl : public hwreg::RegisterBase<TbtHotplugCtrl, uint32_t> { |
| public: |
| hwreg::BitfieldRef<uint32_t> hpd_enable(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdEnableBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_long_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdLongPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_short_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdShortPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get() { return hwreg::RegisterAddr<TbtHotplugCtrl>(kOffset); } |
| |
| private: |
| static constexpr uint32_t kOffset = 0x44030; |
| |
| static constexpr uint32_t kHpdShortPulseBitSubOffset = 0; |
| static constexpr uint32_t kHpdLongPulseBitSubOffset = 1; |
| static constexpr uint32_t kHpdEnableBitSubOffset = 3; |
| |
| static uint32_t ddi_id_to_first_bit(i915::DdiId ddi_id) { |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| ZX_DEBUG_ASSERT_MSG(false, "Use south display hot plug registers for DDI A-C"); |
| return -1; |
| case i915::DdiId::DDI_TC_1: |
| case i915::DdiId::DDI_TC_2: |
| case i915::DdiId::DDI_TC_3: |
| case i915::DdiId::DDI_TC_4: |
| case i915::DdiId::DDI_TC_5: |
| case i915::DdiId::DDI_TC_6: |
| return 4 * (ddi_id - i915::DdiId::DDI_TC_1); |
| } |
| } |
| }; |
| |
| // TC_HOTPLUG_CTL : Type-C Hot Plug Control (since gen11) |
| class TcHotplugCtrl : public hwreg::RegisterBase<TcHotplugCtrl, uint32_t> { |
| public: |
| hwreg::BitfieldRef<uint32_t> hpd_enable(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdEnableBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_long_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdLongPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_short_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdShortPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get() { return hwreg::RegisterAddr<TcHotplugCtrl>(kOffset); } |
| |
| private: |
| static constexpr uint32_t kOffset = 0x44038; |
| |
| static constexpr uint32_t kHpdShortPulseBitSubOffset = 0; |
| static constexpr uint32_t kHpdLongPulseBitSubOffset = 1; |
| static constexpr uint32_t kHpdEnableBitSubOffset = 3; |
| |
| static uint32_t ddi_id_to_first_bit(i915::DdiId ddi_id) { |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| ZX_DEBUG_ASSERT_MSG(false, "Use south display hot plug registers for DDI A-C"); |
| return -1; |
| case i915::DdiId::DDI_TC_1: |
| case i915::DdiId::DDI_TC_2: |
| case i915::DdiId::DDI_TC_3: |
| case i915::DdiId::DDI_TC_4: |
| case i915::DdiId::DDI_TC_5: |
| case i915::DdiId::DDI_TC_6: |
| return 4 * (ddi_id - i915::DdiId::DDI_TC_1); |
| } |
| } |
| }; |
| |
| // SHOTPLUG_CTL_DDI + SHOTPLUG_CTL_TC |
| class IclSouthHotplugCtrl : public hwreg::RegisterBase<IclSouthHotplugCtrl, uint32_t> { |
| public: |
| hwreg::BitfieldRef<uint32_t> hpd_enable(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdEnableBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_long_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdLongPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_short_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdShortPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get(i915::DdiId ddi_id) { |
| return hwreg::RegisterAddr<IclSouthHotplugCtrl>(ddi_id >= i915::DdiId::DDI_TC_1 ? kTcOffset |
| : kDdiOffset); |
| } |
| |
| private: |
| static constexpr uint32_t kDdiOffset = 0xc4030; |
| static constexpr uint32_t kTcOffset = 0xc4034; |
| |
| static constexpr uint32_t kHpdShortPulseBitSubOffset = 0; |
| static constexpr uint32_t kHpdLongPulseBitSubOffset = 1; |
| static constexpr uint32_t kHpdEnableBitSubOffset = 3; |
| |
| static uint32_t ddi_id_to_first_bit(i915::DdiId ddi_id) { |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| return 4 * (ddi_id - i915::DdiId::DDI_A); // SHOTPLUG_CTL_DDI |
| case i915::DdiId::DDI_TC_1: |
| case i915::DdiId::DDI_TC_2: |
| case i915::DdiId::DDI_TC_3: |
| case i915::DdiId::DDI_TC_4: |
| case i915::DdiId::DDI_TC_5: |
| case i915::DdiId::DDI_TC_6: |
| return 4 * (ddi_id - i915::DdiId::DDI_TC_1); // SHOTPLUG_CTL_TC |
| default: |
| return -1; |
| } |
| } |
| }; |
| |
| // SHOTPLUG_CTL + SHOTPLUG_CTL2 |
| class SouthHotplugCtrl : public hwreg::RegisterBase<SouthHotplugCtrl, uint32_t> { |
| public: |
| hwreg::BitfieldRef<uint32_t> hpd_enable(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdEnableBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_long_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdLongPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| hwreg::BitfieldRef<uint32_t> hpd_short_pulse(i915::DdiId ddi_id) { |
| uint32_t bit = ddi_id_to_first_bit(ddi_id) + kHpdShortPulseBitSubOffset; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit, bit); |
| } |
| |
| static auto Get(i915::DdiId ddi_id) { |
| return hwreg::RegisterAddr<SouthHotplugCtrl>(ddi_id == i915::DdiId::DDI_E ? kOffset2 : kOffset); |
| } |
| |
| private: |
| static constexpr uint32_t kOffset = 0xc4030; |
| static constexpr uint32_t kOffset2 = 0xc403c; |
| |
| static constexpr uint32_t kHpdShortPulseBitSubOffset = 0; |
| static constexpr uint32_t kHpdLongPulseBitSubOffset = 1; |
| static constexpr uint32_t kHpdEnableBitSubOffset = 4; |
| |
| static uint32_t ddi_id_to_first_bit(i915::DdiId ddi_id) { |
| switch (ddi_id) { |
| case i915::DdiId::DDI_A: |
| return 24; |
| case i915::DdiId::DDI_B: |
| case i915::DdiId::DDI_C: |
| case i915::DdiId::DDI_D: |
| return 8 * (ddi_id - 1); |
| case i915::DdiId::DDI_E: |
| return 0; |
| default: |
| return -1; |
| } |
| } |
| }; |
| |
| // SFUSE_STRAP (South / PCH Fuses and Straps) |
| // |
| // This register is not documented on DG1. |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 2 page 1185 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 2 page 811 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 2 page 791 |
| class PchDisplayFuses : public hwreg::RegisterBase<PchDisplayFuses, uint32_t> { |
| public: |
| // On Tiger Lake, indicates whether RawClk should be clocked at 24MHz or |
| // 19.2MHz. |
| DEF_BIT(8, rawclk_is_24mhz); |
| |
| // Not present (set to zero) on Tiger Lake. The driver is expected to use the |
| // VBT (Video BIOS Table) or hotplug detection to figure out which ports are |
| // present. |
| DEF_BIT(2, port_b_present); |
| DEF_BIT(1, port_c_present); |
| DEF_BIT(0, port_d_present); |
| |
| static auto Get() { return hwreg::RegisterAddr<PchDisplayFuses>(0xc2014); } |
| }; |
| |
| // DDI_BUF_CTL (DDI Buffer Control) |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 1 pages 352-355 |
| // DG1: IHD-OS-DG1-Vol 2c-2.21 pages 331-334 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 442-445 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 pages 438-441 |
| class DdiBufferControl : public hwreg::RegisterBase<DdiBufferControl, uint32_t> { |
| public: |
| // If true, the DDI buffer is enabled. |
| DEF_BIT(31, enabled); |
| |
| // If true, the DDI ignores PHY parameter changes during link training. |
| // |
| // The impacted PHY parameters include voltage swing and pre-emphasis. This |
| // field is generally set when using specific PHY parameters for the DDI. |
| // |
| // This field does not exist (is reserved) on Kaby Lake and Skylake. |
| DEF_BIT(29, override_training_tiger_lake); |
| |
| // If true, the DDI uses adjusted PHY parameter values. |
| // |
| // The value is ignored if `override_training` is false. |
| // |
| // This field does not exist (is reserved) on Kaby Lake and Skylake. |
| DEF_BIT(28, adjust_phy_parameters_tiger_lake); |
| |
| // Selects one of the DisplayPort PHY configurations set up in DDI_BUF_TRANS. |
| // |
| // DDIs A and E support indexes 0 through 9. DDIs B-D only support indexes 0 |
| // through 8, because the 9th PHY configuration is used for HDMI. |
| // |
| // This field is meaningless for HDMI and DVI. |
| // |
| // This field does not exist (is reserved) on Tiger Lake and DG1. |
| DEF_FIELD(27, 24, display_port_phy_config_kaby_lake); |
| |
| // If true, data is swapped on the lanes output by the port. |
| // |
| // This field must not be changed while the DDI is enabled. |
| // |
| // Tiger Lake and DG1: |
| // |
| // FIA handles lane reversal for Thunderbolt and USB-C DisplayPort Alt Mode, |
| // and this field should be set to false in those cases. Static and fixed |
| // connections (DisplayPort, HDMI) through the FIA only use this field in |
| // "No pin Assignment (Non Type-C DP)" static configurations. Other |
| // connections use the field. |
| // |
| // Kaby Lake and Skylake: |
| // |
| // For DDIs B-D, enabling swaps lanes 0 <-> 3 and lanes 1 <-> 2. If DDI E is |
| // enabled (in DDI A Lane Capability Control), then DDI A reversal swaps its |
| // two remaining lanes (0 <-> 1). If DDI E is disabled, DDI A reversal acts |
| // the same as B-D reversal (lanes 0 <-> 3 and 1 <->2 are swapped). DDI E does |
| // not support port reversal. |
| DEF_BIT(16, port_reversal); |
| |
| // Delay used to stagger the assertion/deassertion of the port lane enables. |
| // |
| // The value is expressed in multiples of the symbol clock period, so it |
| // depends on the link frequency. |
| // |
| // The delay should be at least 100ns when the port is used in USB Type C |
| // mode. In all other cases, the delay should be zero. |
| // |
| // This field does not exist (is reserved) on Kaby Lake and Skylake, which |
| // don't have Type C DDIs. |
| DEF_FIELD(15, 8, type_c_display_port_lane_staggering_delay_tiger_lake); |
| |
| // If true, the DDI is idle. |
| DEF_BIT(7, is_idle); |
| |
| // If false, two lanes from DDI A are repurposed to form DDI E. |
| // |
| // If true, DDI A has four lanes, and behaves similarly to DDIs B-D. If false, |
| // |
| // This field is only meaningful on DDI A, whose lanes get redistributed to |
| // DDI E. The field must be programmed at boot time (based on the board |
| // configuration) and must not be changed afterwards. |
| // |
| // This field does not exist (is reserved) on Tiger Lake or DG1. |
| DEF_BIT(4, ddi_e_disabled_kaby_lake); |
| |
| // Selects the number of DisplayPort lanes enabled. |
| // |
| // The field's value is the number of lanes minus 1. 0 = x1 lane, 1 = x2 |
| // lanes, 3 = x4 lanes. display_port_lane_count() and |
| // set_display_port_lane_count() take care of this encoding detail. |
| DEF_FIELD(3, 1, display_port_lane_count_selection); |
| |
| // The number of DisplayPort lanes enabled. |
| // |
| // This field is not meaningful for HDMI, which always uses all the lanes. |
| // |
| // When the DDI is in DisplayPort mode, the field must match the corresponding |
| // setting in TRANS_DDI_FUNC_CTL for the transcoder attached to this DDI. |
| // |
| // On Kaby Lake and Skylake, DDI E only supports 1 and 2 lanes |
| // (if it's enabled), since it takes two lanes from DDI A. On the same |
| // hardware, DDI A always supports x1 and x2, and supports x4 if DDI E is |
| // disabled (and therefore not taking away 2 lanes from DDI A). |
| uint8_t display_port_lane_count() const { |
| // The addition will not overflow and the cast is lossless because |
| // display_port_lane_count_selection() is a 3-bit field. |
| return static_cast<int8_t>(display_port_lane_count_selection() + 1); |
| } |
| |
| // See display_port_lane_count() for details. |
| DdiBufferControl& set_display_port_lane_count(uint8_t lane_count) { |
| switch (lane_count) { |
| case 1: |
| case 2: |
| case 4: |
| set_display_port_lane_count_selection(lane_count - 1); |
| return *this; |
| } |
| ZX_ASSERT_MSG(false, "Unsupported lane count: %d", lane_count); |
| return *this; |
| } |
| |
| // The level of the port detect pin at boot time. |
| // |
| // This field is only meaningful on DDI A. On Skylake and Kaby Lake, the other |
| // DDIs' port detect pin status is communicated in SFUSE_STRAP. |
| DEF_BIT(0, boot_time_port_detect_pin_status); |
| |
| // For Kaby Lake and Skylake DDI A - DDI E. |
| static auto GetForKabyLakeDdi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiBufferControl>(0x64000 + 0x100 * ddi_index); |
| } |
| |
| // For Tiger Lake and DG1. |
| static auto GetForTigerLakeDdi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_TC_6); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiBufferControl>(0x64000 + 0x100 * ddi_index); |
| } |
| }; |
| |
| // Part 1 of DDI_BUF_TRANS (DDI Buffer Translation) |
| // |
| // Each DDI has 10 instances of the DDI_BUF_TRANS register, storing 10 entries |
| // of the port's PHY configuration table. The MMIO addresses for the 10 |
| // instances are consecutive. The active entry is selected using the DDI_BUF_CTL |
| // register. |
| // |
| // Each DDI_BUF_TRANS register instance (storing one entry in the PHY |
| // configuration table) consists of two 32-bit parts (double-words). We don't |
| // know if it's safe to use 64-bit MMIO accesses with the registers. |
| // |
| // DDI_BUF_TRANS is not documented on Tiger Lake or DG1. |
| // |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 446-447 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 pages 442-441 |
| class DdiPhyConfigEntry1 : public hwreg::RegisterBase<DdiPhyConfigEntry1, uint32_t> { |
| public: |
| // The PRMs do not go in depth on the meanings of the fields. |
| DEF_BIT(31, balance_leg_enable); |
| DEF_FIELD(17, 0, deemphasis_level); |
| |
| static auto GetDdiInstance(i915::DdiId ddi_id, int instance_index) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| ZX_ASSERT(instance_index >= 0); |
| ZX_ASSERT(instance_index < 10); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiPhyConfigEntry1>(0x64e00 + 0x60 * ddi_index + 8 * instance_index); |
| } |
| }; |
| |
| // Part 2 of DDI_BUF_TRANS (DDI Buffer Translation) |
| // |
| // See Part 1 above for documentation. |
| class DdiPhyConfigEntry2 : public hwreg::RegisterBase<DdiPhyConfigEntry2, uint32_t> { |
| public: |
| // The PRMs do not go in depth on the meanings of the fields. |
| DEF_FIELD(20, 16, voltage_reference_select); |
| DEF_FIELD(10, 0, voltage_swing); |
| |
| static auto GetDdiInstance(i915::DdiId ddi_id, int instance_index) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| ZX_ASSERT(instance_index >= 0); |
| ZX_ASSERT(instance_index < 10); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiPhyConfigEntry2>(0x64e04 + 0x60 * ddi_index + 8 * instance_index); |
| } |
| }; |
| |
| // DISPIO_CR_TX_BMU_CR0 |
| // |
| // Involved in PHY parameters for transmission on all DDIs. |
| // |
| // This register does not exist on Tiger Lake or DG1. |
| // |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 446-447 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 pages 442-441 |
| class DdiPhyBalanceControl : public hwreg::RegisterBase<DdiPhyBalanceControl, uint32_t> { |
| public: |
| // Not managed by driver software. |
| DEF_FIELD(31, 29, digital_analog); |
| |
| // Not managed by driver software. |
| DEF_BIT(28, global_vs_local_voltage_reference_select); |
| |
| // Must be zero for `balance_leg_select` fields to be used. |
| DEF_FIELD(27, 23, disable_balance_leg); |
| |
| // For DDI4 - DDI E or DDI A when DDI E is disabled. |
| DEF_FIELD(22, 20, balance_leg_select_ddi_e); |
| |
| // For DDI3 / DDI D. |
| DEF_FIELD(19, 17, balance_leg_select_ddi_d); |
| |
| // For DDI2 / DDI C. |
| DEF_FIELD(16, 14, balance_leg_select_ddi_c); |
| |
| // For DDI1 / DDI B. |
| DEF_FIELD(13, 11, balance_leg_select_ddi_b); |
| |
| // For DDI0 / DDI A. |
| DEF_FIELD(10, 8, balance_leg_select_ddi_a); |
| |
| // Not managed by driver software. |
| DEF_FIELD(7, 0, h_mode); |
| |
| hwreg::BitfieldRef<uint32_t> balance_leg_select_for_ddi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| const int bit_index = 8 + 3 * ddi_index; |
| return hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit_index + 2, bit_index); |
| } |
| |
| static auto Get() { return hwreg::RegisterAddr<DdiPhyBalanceControl>(0x6c00c); } |
| }; |
| |
| // DDI_AUX_CTL (DDI AUX Channel Control) |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol2c-12.21 Part 1 pages 342-345 |
| // DG1: IHD-OS-DG1-Vol 2c-2.21 Part 1 pages 321-323 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 436-438 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 pages 432-434 |
| class DdiAuxControl : public hwreg::RegisterBase<DdiAuxControl, uint32_t> { |
| public: |
| // True while the DDI is performing an AUX transaction. |
| // |
| // The driver sets this field to true to start an AUX transaction. The |
| // hardware resets it back to false when the AUX transaction is completed. |
| // |
| // The register should not be modified while this field is true. |
| // |
| // On Kaby Lake and Skylake, DDI_AUX_MUTEX must be acquired before setting up |
| // an AUX transaction. |
| DEF_BIT(31, transaction_in_progress); |
| |
| // Set to true by hardware when it completes an AUX transaction. |
| // |
| // This bit is sticky Read/Write-Clear. It stays true until the driver resets |
| // it by writing true to it. |
| DEF_BIT(30, transaction_done); |
| |
| // If true, an interrupt is triggered when an AUX transaction is completed. |
| DEF_BIT(29, interrupt_on_done); |
| |
| // Set to true by hardware when an AUX transaction times out. |
| // |
| // This bit is sticky Read/Write-Clear. It stays true until the driver resets |
| // it by writing true to it. |
| DEF_BIT(28, timeout); |
| |
| // Selects the AUX transaction timeout. |
| // |
| // The AUX transaction limit in the DisplayPort specification is 500us. |
| // |
| // The values are documented as 0 (400us, unsupported), 1 (600us), 2 (800us) |
| DEF_FIELD(27, 26, timeout_timer_select); |
| |
| // Only documented on Kaby Lake and Skylake. The docs advise against using. |
| static constexpr int kTimeoutUnsupported400us = 0; |
| |
| static constexpr int kTimeout600us = 1; |
| static constexpr int kTimeout800us = 2; |
| |
| // 4,000 us on Tiger Lake and DG1. 1,600 us on Kaby Lake and Skylake. |
| static constexpr int kTimeoutLarge = 3; |
| |
| // Set to true by hardware when an AUX transaction receives invalid data. |
| // |
| // The received data could be invalid due to: corruption detected, the bits |
| // received don't add up to an integer number of bytes, more than 20 bytes |
| // received. |
| // |
| // This bit is sticky Read/Write-Clear. It stays true until the driver resets |
| // it by writing true to it. |
| DEF_BIT(25, receive_error); |
| |
| // Total number of bytes in an AUX message, including the message header. |
| // |
| // The driver writes this field to indicate the message size for the next AUX |
| // transaction. The hardware writes this field to indicate the response size |
| // for the last AUX transaction. |
| // |
| // The message includes the header bytes (4 for command, 2 for reply). The |
| // DisplayPort specification states that the maximum data size is 16 bytes, |
| // leading to a 20-byte maximum message size. |
| // |
| // The driver must write values between 1 and 20. The value read from this |
| // field is only valid and meaningful if `transaction_done` is true, and |
| // `transaction_in_progress`, `timeout`, and `receive_error` are false. |
| DEF_FIELD(24, 20, message_size); |
| |
| // Directs AUX transactions to the Thunderbolt IO, or the USB-C / Combo IO. |
| // |
| // If true, transactions will be performed via the Thunderbolt controller. |
| // Otherwise, the transactions will be performed over USB-C (using the FIA) or |
| // over the Combo DDI IO. |
| // |
| // This field is reserved (must be false) on Kaby Lake and Skylake, which |
| // don't support Thunderbolt IO. |
| DEF_BIT(11, use_thunderbolt); |
| |
| // Number of SYNC pulses sent during SYNC for eDP fast wake transactions. |
| // |
| // The value is the number of SYNC pulses minus 1. |
| DEF_FIELD(9, 5, fast_wake_sync_pulse_count); |
| |
| // `fast_wake_sync_pulse_count` should be set to 7, to match the Embedded |
| // DisplayPort standard requirement for 8 pre-charge pulses (zeros) in the |
| // AUX_PHY_WAKE preamble. |
| static constexpr int kFastWakeSyncPulseCount = 8 - 1; |
| |
| // Number of SYNC pulses sent during SYNC for standard transactions. |
| // |
| // The value is the number of SYNC pulses minus 1. This is the sum of the |
| // 10-16 pre-charge pulses (zeros) and the 16 consecutive zeros at the start |
| // of the AUX_SYNC pattern. |
| DEF_FIELD(4, 0, sync_pulse_count); |
| |
| // `sync_pulse_count` should be set to at least 25, to meet the DisplayPort |
| // 26-pulse minimum, which is equivalent to 10 pre-charge pulses. |
| static constexpr int kMinSyncPulseCount = 26 - 1; |
| |
| // For Kaby Lake and Skylake DDI A - DDI E. |
| // |
| // The Kaby Lake and Skylake references only document the AUX registers for |
| // DDIs A-D. Other manuals, such as IHD-OS-ICLLP-Vol 2c-1.20, document AUX |
| // registers for DDIs E-F, and their MMIO addresses are what we'd expect. |
| // For now, we assume DDI E has an AUX channel that works like the other DDIs. |
| static auto GetForKabyLakeDdi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiAuxControl>(0x64010 + 0x100 * ddi_index); |
| } |
| |
| // For Tiger Lake and DG1. |
| static auto GetForTigerLakeDdi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_TC_6); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DdiAuxControl>(0x64010 + 0x100 * ddi_index); |
| } |
| }; |
| |
| // DDI_AUX_DATA (DDI AUX Channel Data) |
| // |
| // Each DDI has 5 instances of the DDI_AUX_DATA register, making up a 20-byte |
| // buffer for storing AUX messages. The MMIO addresses for the 5 instances are |
| // consecutive. |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol2c-12.21 Part 1 pages 346-351 |
| // DG1: IHD-OS-DG1-Vol 2c-2.21 Part 1 pages 324-330 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 page 439 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 page 435 |
| class DdiAuxData : public hwreg::RegisterBase<DdiAuxData, uint32_t> { |
| public: |
| // The most significant byte in each 32-bit register gets transmitted first. |
| // Intel machines are little-endian, so the transmission order doesn't match |
| // the memory order. The `swapped_` part of the name aims to draw attention |
| // to this subtlety. |
| // |
| // The value is not meaningful while the corresponding DDI_AUX_CTL register's |
| // `transaction_in_progress` field is true. |
| DEF_FIELD(31, 0, swapped_bytes); |
| |
| // DDI_AUX_DATA_*_0 for the AUX channel with the given control register. |
| // |
| // The DDI_AUX_DATA_*_[1-4] data registers are accessed using direct MMIO |
| // operations. |
| // |
| // We can get away with using DDI_AUX_CTL as the input because all DDI AUX |
| // channels currently have the same MMIO layout. When this isn't the case |
| // anymore, we'll replace this factory function with GetFor*Ddi() functions, |
| // matching DdiAuxControl. |
| static auto GetData0ForAuxControl(const DdiAuxControl& aux_control) { |
| static constexpr uint32_t kAuxControlMmioBase = 0x64010; |
| static constexpr uint32_t kAuxDataMmioBase = 0x64014; |
| return hwreg::RegisterAddr<DdiAuxData>(aux_control.reg_addr() + |
| (kAuxDataMmioBase - kAuxControlMmioBase)); |
| } |
| }; |
| |
| // DPCLKA_CFGCR0 (Display Clock Configuration Control Register 0?) |
| // |
| // This register controls which DPLL (Display PLL) is used as a clock source by |
| // each Combo DDI, and whether the DDI's clock is gated. Each Type C DDI has its |
| // own dedicated MGPLL, so this register only configures the clock gating for |
| // Type C DDIs. |
| // |
| // The Kaby Lake and Skylake equivalent of this register is |
| // `DisplayPllDdiMapKabyLake` (DPLL_CTRL2). |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev2.0 Part 1, pages 608-610 |
| // Lakefield: IHD-OS-LKF-Vol 2c-5.21 Part 1, pages 561-563 |
| class DdiClockConfig : public hwreg::RegisterBase<DdiClockConfig, uint32_t> { |
| public: |
| DEF_BIT(24, ddi_c_clock_disabled); |
| DEF_BIT(23, ddi_type_c_6_clock_disabled); |
| DEF_BIT(22, ddi_type_c_5_clock_disabled); |
| DEF_BIT(21, ddi_type_c_4_clock_disabled); |
| |
| DEF_BIT(14, ddi_type_c_3_clock_disabled); |
| DEF_BIT(13, ddi_type_c_2_clock_disabled); |
| DEF_BIT(12, ddi_type_c_1_clock_disabled); |
| DEF_BIT(11, ddi_b_clock_disabled); |
| DEF_BIT(10, ddi_a_clock_disabled); |
| |
| // If true, the DDI's clock is disabled. This is accomplished by gating. |
| bool ddi_clock_disabled(i915::DdiId ddi_id) const { |
| static constexpr int kComboBitIndices[] = {10, 11, 24}; |
| static constexpr int kTypeCBitIndices[] = {12, 13, 14, 21, 22, 23}; |
| int bit_index; |
| if (ddi_id >= i915::DdiId::DDI_TC_1 && ddi_id <= i915::DdiId::DDI_TC_6) { |
| const int ddi_index = ddi_id - i915::DdiId::DDI_TC_1; |
| bit_index = kTypeCBitIndices[ddi_index]; |
| } else { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_C); |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| bit_index = kComboBitIndices[ddi_index]; |
| } |
| return static_cast<bool>( |
| hwreg::BitfieldRef<const uint32_t>(reg_value_ptr(), bit_index, bit_index).get()); |
| } |
| |
| // See `ddi_clock_disabled()` for details. |
| DdiClockConfig& set_ddi_clock_disabled(i915::DdiId ddi_id, bool clock_disabled) { |
| static constexpr int kComboBitIndices[] = {10, 11, 24}; |
| static constexpr int kTypeCBitIndices[] = {12, 13, 14, 21, 22, 23}; |
| int bit_index; |
| if (ddi_id >= i915::DdiId::DDI_TC_1 && ddi_id <= i915::DdiId::DDI_TC_6) { |
| const int ddi_index = ddi_id - i915::DdiId::DDI_TC_1; |
| bit_index = kTypeCBitIndices[ddi_index]; |
| } else { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_C); |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| bit_index = kComboBitIndices[ddi_index]; |
| } |
| hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit_index, bit_index).set(clock_disabled ? 1 : 0); |
| return *this; |
| } |
| |
| // Documented values for `ddi_*_clock_display_pll_select` fields. |
| enum class DdiClockDisplayPllSelect { |
| kDisplayPll0 = 0b00, |
| kDisplayPll1 = 0b01, |
| kDisplayPll4 = 0b10, |
| }; |
| |
| // These fields have a non-trivial representation. They should be used via the |
| // `ddi_clock_display_pll()` and `set_ddi_clock_display_pll()` helpers. |
| DEF_ENUM_FIELD(DdiClockDisplayPllSelect, 5, 4, ddi_c_clock_display_pll_select); |
| DEF_ENUM_FIELD(DdiClockDisplayPllSelect, 3, 2, ddi_b_clock_display_pll_select); |
| DEF_ENUM_FIELD(DdiClockDisplayPllSelect, 1, 0, ddi_a_clock_display_pll_select); |
| |
| // The DPLL (Display PLL) used as a clock source for a DDI. |
| // |
| // Returns DPLL_INVALID if the field is set to an undocumented value. |
| i915::PllId ddi_clock_display_pll(i915::DdiId ddi_id) const { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_C); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| const int bit_index = ddi_index * 2; |
| const auto dpll_select = static_cast<DdiClockDisplayPllSelect>( |
| hwreg::BitfieldRef<const uint32_t>(reg_value_ptr(), bit_index + 1, bit_index).get()); |
| |
| switch (dpll_select) { |
| case DdiClockDisplayPllSelect::kDisplayPll0: |
| return i915::PllId::DPLL_0; |
| case DdiClockDisplayPllSelect::kDisplayPll1: |
| return i915::PllId::DPLL_1; |
| case DdiClockDisplayPllSelect::kDisplayPll4: |
| // TODO(https://fxbug.dev/42061706): Add support for DPLL4. |
| break; |
| } |
| // The field is set to an undocumented value. |
| return i915::PllId::DPLL_INVALID; |
| } |
| |
| // See `ddi_clock_display_pll()` for details. |
| DdiClockConfig& set_ddi_clock_display_pll(i915::DdiId ddi_id, i915::PllId pll_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_C); |
| |
| DdiClockDisplayPllSelect dpll_select; |
| switch (pll_id) { |
| case i915::PllId::DPLL_0: |
| dpll_select = DdiClockDisplayPllSelect::kDisplayPll0; |
| break; |
| case i915::PllId::DPLL_1: |
| dpll_select = DdiClockDisplayPllSelect::kDisplayPll1; |
| break; |
| |
| // TODO(https://fxbug.dev/42061706): Add support for DPLL4. |
| default: |
| ZX_DEBUG_ASSERT_MSG(false, "Unsupported DDI PLL: %d", pll_id); |
| dpll_select = DdiClockDisplayPllSelect::kDisplayPll0; |
| } |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| const int bit_index = ddi_index * 2; |
| hwreg::BitfieldRef<uint32_t>(reg_value_ptr(), bit_index + 1, bit_index) |
| .set(static_cast<uint32_t>(dpll_select)); |
| return *this; |
| } |
| |
| static auto Get() { return hwreg::RegisterAddr<DdiClockConfig>(0x164280); } |
| }; |
| |
| // DDI_CLK_SEL |
| // Type C DDI Clock Selection |
| // |
| // Each Type-C DDI has 5 PLL inputs: Type-C PLL, and Thunderbolt PLL with 4 |
| // different frequencies. |
| // Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev 2.0, Page 169 "PLL Arrangement" |
| // |
| // This register selects the clock source for a given Type-C DDI. |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev 2.0 Part 1, Page 356-357 |
| class TypeCDdiClockSelect : public hwreg::RegisterBase<TypeCDdiClockSelect, uint32_t> { |
| public: |
| // Select which clock to use for this DDI. |
| // Valid values are listed below in `ClockSelect` enum class. |
| // |
| // Driver can use `clock_select` and `set_clock_select` helper methods to |
| // read / write to this field. |
| DEF_FIELD(31, 28, clock_select_raw); |
| |
| enum class ClockSelect : uint32_t { |
| kNone = 0b0000, |
| kTypeCPll = 0b1000, |
| kThunderbolt162MHz = 0b1100, |
| kThunderbolt270MHz = 0b1101, |
| kThunderbolt540MHz = 0b1110, |
| kThunderbolt810MHz = 0b1111, |
| }; |
| |
| // Helper method to read the `clock_select_raw` field and check its validity. |
| std::optional<ClockSelect> clock_select() const { |
| auto raw = clock_select_raw(); |
| if (IsValidClockSelect(raw)) { |
| return static_cast<ClockSelect>(raw); |
| } |
| zxlogf(WARNING, "Invalid clock_select field: 0x%x", raw); |
| return std::nullopt; |
| } |
| |
| // Helper method to set the `clock_select_raw` field using a strongly-typed |
| // enum class. |
| SelfType& set_clock_select(ClockSelect clock) { |
| return set_clock_select_raw(static_cast<ValueType>(clock)); |
| } |
| |
| static auto GetForDdi(i915::DdiId ddi_id) { |
| // Register address at |
| // Tiger Lake: IHD-OS-TGL-Vol 2c-1.22-Rev 2.0 Part 1, Page 356 |
| switch (ddi_id) { |
| case i915::DdiId::DDI_TC_1: |
| return hwreg::RegisterAddr<SelfType>(0x4610C); |
| case i915::DdiId::DDI_TC_2: |
| return hwreg::RegisterAddr<SelfType>(0x46110); |
| case i915::DdiId::DDI_TC_3: |
| return hwreg::RegisterAddr<SelfType>(0x46114); |
| case i915::DdiId::DDI_TC_4: |
| return hwreg::RegisterAddr<SelfType>(0x46118); |
| case i915::DdiId::DDI_TC_5: |
| return hwreg::RegisterAddr<SelfType>(0x4611C); |
| case i915::DdiId::DDI_TC_6: |
| return hwreg::RegisterAddr<SelfType>(0x46120); |
| default: |
| ZX_ASSERT_MSG(false, "DDI_CLK_SEL: Invalid DDI %d", ddi_id); |
| } |
| } |
| |
| private: |
| static bool IsValidClockSelect(uint32_t clock_select_raw) { |
| switch (clock_select_raw) { |
| case 0b0000: |
| case 0b1000: |
| case 0b1100: |
| case 0b1101: |
| case 0b1110: |
| case 0b1111: |
| return true; |
| default: |
| return false; |
| } |
| } |
| }; |
| |
| // DDI_AUX_MUTEX (DDI AUX Channel Mutex) |
| // |
| // This register is not documented on Tiger Lake or DG1. |
| // |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 440-441 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 page 436-437 |
| class DdiAuxMutex : public hwreg::RegisterBase<DdiAuxMutex, uint32_t> { |
| public: |
| // If true, the mutex is used to arbitrate AUX channel access. |
| // |
| // The mutex must be enabled and acquired if PSR 1/2 (Panel Self-Refresh) or |
| // GTC (Global Time Code) are used. Otherwise, the mutex can remain disabled. |
| DEF_BIT(31, enabled); |
| |
| // Reads acquire the mutex, writes release the mutex. |
| // |
| // Any read is an attempt to acquire the mutex. A successful attempt returns |
| // true in this field. Once the driver acquires the mutex, it retains |
| // ownership (reads continue to return true) until it explicitly releases the |
| // mutex. |
| // |
| // This is a Write-Clear field. Writing true releases the mutex. |
| // |
| // The driver should release the mutex once it completes an AUX transaction, |
| // so the hardware can use it as well. |
| DEF_BIT(30, acquired); |
| |
| // We can get away with using DDI_AUX_CTL as the input because all DDI AUX |
| // channels currently have the same MMIO layout. When this isn't the case |
| // anymore, we'll replace this factory function with GetFor*Ddi() functions, |
| // matching DdiAuxControl. |
| static auto GetForAuxControl(const DdiAuxControl& aux_control) { |
| static constexpr uint32_t kAuxControlMmioBase = 0x64010; |
| static constexpr uint32_t kAuxMutexMmioBase = 0x6402c; |
| return hwreg::RegisterAddr<DdiAuxData>(aux_control.reg_addr() + |
| (kAuxMutexMmioBase - kAuxControlMmioBase)); |
| } |
| }; |
| |
| // DP_TP_CTL (DisplayPort Transport Control) |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol2c-12.21 Part 1 pages 600-603 |
| // DG1: IHD-OS-DG1-Vol 2c-2.21 Part 1 pages 572-575 |
| // Kaby Lake: IHD-OS-KBL-Vol 2c-1.17 Part 1 pages 517-520 |
| // Skylake: IHD-OS-SKL-Vol 2c-05.16 Part 1 pages 515-518 |
| class DpTransportControl : public hwreg::RegisterBase<DpTransportControl, uint32_t> { |
| public: |
| // If true, the DisplayPort transport function is enabled for the DDI. |
| DEF_BIT(31, enabled); |
| |
| // If true, FEC (Forward Error Correction) coding is enabled. |
| // |
| // Must only be set to true after the `enabled` is set to true. Must only be |
| // set to false after `enabled` is set to false. |
| // |
| // This field does not exist on Kaby Lake and Skylake. |
| DEF_BIT(30, forward_error_correction_enabled_tiger_lake); |
| |
| // True for MST (Multi Stream) mode, false for SST (Single Stream) mode. |
| // |
| // Kaby Lake and Skylake DDI A (eDP) and DDI E do not support MST. |
| // |
| // Must match the mode in the Transcoder DDI Function Control registers. Must |
| // not change while the DDI is enabled. |
| DEF_BIT(27, is_multi_stream); |
| |
| // Forces MST ACT (Allocation Change Trigger) to be sent at the next link |
| // frame boundary. After the ACT is sent (indicated by DP_TP_STATUS), the bit |
| // can be reset and set again to force sending another ACT. |
| DEF_BIT(25, force_allocation_change_trigger); |
| |
| // This field does not exist on Kaby Lake and Skylake. |
| DEF_FIELD(20, 19, training_pattern4_tiger_lake); |
| static constexpr int kTrainingPattern4a = 0; |
| static constexpr int kTrainingPattern4b = 1; |
| static constexpr int kTrainingPattern4c = 2; |
| |
| // True if enhanced framing is enabled for SST. Must be false in MST mode. |
| // |
| // Must not change while the DDI is enabled. |
| DEF_BIT(18, sst_enhanced_framing); |
| |
| // Training pattern 1 must be selected when a port is enabled. |
| // |
| // To re-train a link, the port must be disabled and re-enabled (with |
| // training pattern 1 selected). |
| DEF_FIELD(10, 8, training_pattern); |
| static constexpr int kTrainingPattern1 = 0; |
| static constexpr int kTrainingPattern2 = 1; |
| static constexpr int kIdlePattern = 2; |
| static constexpr int kSendPixelData = 3; |
| static constexpr int kTrainingPattern3 = 4; |
| |
| // Not supported on Kaby Lake and Skylake. |
| static constexpr int kTrainingPattern4TigerLake = 5; |
| |
| // For eDP only. Must not change while the DDI is enabled. |
| DEF_BIT(6, alternate_scrambler_reset); |
| |
| // For Kaby Lake and Skylake. The DisplayPort transport logic is in DDIs. |
| static auto GetForKabyLakeDdi(i915::DdiId ddi_id) { |
| ZX_ASSERT(ddi_id >= i915::DdiId::DDI_A); |
| ZX_ASSERT(ddi_id <= i915::DdiId::DDI_E); |
| |
| const int ddi_index = ddi_id - i915::DdiId::DDI_A; |
| return hwreg::RegisterAddr<DpTransportControl>(0x64040 + 0x100 * ddi_index); |
| } |
| |
| // For Tiger Lake and DG1. The DisplayPort transport logic is in transcoders. |
| static auto GetForTigerLakeTranscoder(i915::TranscoderId transcoder_id) { |
| ZX_ASSERT(transcoder_id >= i915::TranscoderId::TRANSCODER_A); |
| |
| // TODO(https://fxbug.dev/42060657): Allow transcoder D, once we support it. |
| ZX_ASSERT(transcoder_id <= i915::TranscoderId::TRANSCODER_C); |
| |
| const int transcoder_index = transcoder_id - i915::TranscoderId::TRANSCODER_A; |
| return hwreg::RegisterAddr<DpTransportControl>(0x60540 + 0x1000 * transcoder_index); |
| } |
| }; |
| |
| // An instance of DdiRegs represents the registers for a particular DDI. |
| class DdiRegs { |
| public: |
| explicit DdiRegs(i915::DdiId ddi_id) : ddi_id_(ddi_id) {} |
| |
| hwreg::RegisterAddr<DdiBufferControl> BufferControl() { |
| // This works for Kaby Lake too, because the Ddi integer mapping takes |
| // advantage of MMIO address space. |
| return DdiBufferControl::GetForTigerLakeDdi(ddi_id_); |
| } |
| hwreg::RegisterAddr<DpTransportControl> DpTransportControl() { |
| // This does not work for Tiger Lake. |
| return DpTransportControl::GetForKabyLakeDdi(ddi_id_); |
| } |
| hwreg::RegisterAddr<DdiPhyConfigEntry1> PhyConfigEntry1(int entry_index) const { |
| return DdiPhyConfigEntry1::GetDdiInstance(ddi_id_, entry_index); |
| } |
| hwreg::RegisterAddr<DdiPhyConfigEntry2> PhyConfigEntry2(int entry_index) const { |
| return DdiPhyConfigEntry2::GetDdiInstance(ddi_id_, entry_index); |
| } |
| |
| private: |
| template <class RegType> |
| hwreg::RegisterAddr<RegType> GetReg() { |
| return hwreg::RegisterAddr<RegType>(RegType::kBaseAddr + 0x100 * ddi_id_); |
| } |
| |
| i915::DdiId ddi_id_; |
| }; |
| |
| } // namespace registers |
| |
| #endif // SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_REGISTERS_DDI_H_ |