| // 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_IGD_H_ |
| #define SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_IGD_H_ |
| |
| #include <lib/device-protocol/pci.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/types.h> |
| |
| #include <cinttypes> |
| #include <cstddef> |
| #include <cstring> |
| #include <unordered_map> |
| |
| #include <hwreg/bitfields.h> |
| |
| #include "src/graphics/display/drivers/intel-i915/acpi-memory-region.h" |
| #include "src/graphics/display/drivers/intel-i915/hardware-common.h" |
| |
| namespace i915 { |
| // Various definitions from IGD OpRegion/Software SCI documentation. |
| |
| // Length of the igd opregion |
| constexpr uint32_t kIgdOpRegionLen = 0x2000; |
| |
| constexpr uint32_t kMaxVbtSize = 6144; |
| |
| typedef struct igd_opregion { |
| uint8_t signature[16]; |
| uint32_t kb_size; |
| uint32_t version; |
| uint8_t system_bios_build_version[32]; |
| uint8_t video_bios_build_version[16]; |
| uint8_t graphics_bios_build_version[16]; |
| uint32_t supported_mailboxes; |
| uint32_t driver_model; |
| uint32_t pcon; |
| uint8_t gop_version[32]; |
| uint8_t rsvd[124]; |
| |
| uint8_t mailbox1[256]; |
| uint8_t mailbox2[256]; |
| uint8_t mailbox3[256]; |
| uint8_t mailbox4[kMaxVbtSize]; |
| uint8_t mailbox5[1024]; |
| |
| uint8_t major_version() const { return version >> 24; } |
| uint8_t minor_version() const { return (version >> 16) & 0xff; } |
| |
| bool asle_supported() const { return supported_mailboxes & (1 << 2); } |
| |
| struct vbt_region_t { |
| uint64_t rvda; |
| uint32_t rvds; |
| } __attribute__((__packed__)); |
| |
| vbt_region_t vbt_region() { |
| vbt_region_t region; |
| // According to the IGD OpRegion spec v0.5, this offset is the beginning |
| // of a reserved region. It would be good to confirm this offset with a |
| // newer version of the spec. |
| std::memcpy(®ion, &mailbox3[186], sizeof(vbt_region_t)); |
| return region; |
| } |
| |
| bool validate() { |
| const char* sig = "IntelGraphicsMem"; |
| return !memcmp(signature, reinterpret_cast<const void*>(sig), 16) && |
| kb_size >= (sizeof(struct igd_opregion) >> 10); |
| } |
| } igd_opregion_t; |
| |
| static_assert(sizeof(igd_opregion_t) == 0x2000, "Bad igd opregion len"); |
| static_assert(offsetof(igd_opregion_t, mailbox4) == 1024, "Bad mailbox4 offset"); |
| |
| typedef struct sci_interface { |
| uint32_t entry_and_exit_params; |
| uint32_t additional_params; |
| uint32_t driver_sleep_timeout; |
| uint8_t rsvd[240]; |
| } sci_interface_protocol_t; |
| |
| static_assert(sizeof(sci_interface_protocol_t) == 252, "Bad sci_interface_protocol_t size"); |
| |
| // Header for each bios data block. |
| typedef struct block_header { |
| uint8_t type; |
| // Size of the block, not including the header |
| uint8_t size_low; |
| uint8_t size_high; |
| } block_header_t; |
| static_assert(sizeof(block_header_t) == 3, "Bad block_header size"); |
| |
| typedef struct bios_data_blocks_header { |
| uint8_t signature[16]; |
| uint16_t version; |
| // Size of the header by itself |
| uint16_t header_size; |
| // Size of the header + all the blocks |
| uint16_t bios_data_blocks_size; |
| |
| bool validate() { |
| const char* sig = "BIOS_DATA_BLOCK"; |
| return !memcmp(signature, sig, 15) && bios_data_blocks_size >= sizeof(block_header_t); |
| } |
| } bios_data_blocks_header_t; |
| static_assert(sizeof(bios_data_blocks_header_t) == 22, "Bad bios_data_blocks_header size"); |
| |
| typedef struct vbt_header { |
| uint8_t signature[20]; |
| uint16_t version; |
| uint16_t header_size; |
| uint16_t vbt_size; |
| uint8_t checksum; |
| uint8_t rsvd; |
| uint32_t bios_data_blocks_offset; |
| uint32_t aim_offset[4]; |
| |
| bool validate() { |
| const char* sig_prefix = "$VBT"; |
| return !memcmp(signature, sig_prefix, 4) && sizeof(bios_data_blocks_header_t) < vbt_size && |
| bios_data_blocks_offset < vbt_size - sizeof(bios_data_blocks_header_t); |
| } |
| } vbt_header_t; |
| static_assert(sizeof(vbt_header_t) == 48, "Bad vbt_header size"); |
| |
| typedef struct general_definitions { |
| static constexpr uint32_t kBlockType = 2; |
| |
| uint8_t unused[4]; |
| // Contains the length of each entry in ddis. |
| uint8_t ddi_config_size; |
| // Array of ddi_config structures. |
| uint8_t ddis[0]; |
| } general_definitions_t; |
| |
| // Bitfield for ddi_config's ddi_flags register |
| class DdiFlags : public hwreg::RegisterBase<DdiFlags, uint16_t> { |
| public: |
| DEF_BIT(12, internal); |
| DEF_BIT(11, not_hdmi); |
| DEF_BIT(4, tmds); |
| DEF_BIT(2, dp); |
| |
| static auto Get() { return hwreg::RegisterAddr<DdiFlags>(0); } |
| }; |
| |
| typedef struct __attribute__((packed)) ddi_config { |
| uint8_t unused1[2]; |
| // See DdiFlags class |
| uint16_t ddi_flags; |
| uint8_t unused2[3]; |
| |
| uint8_t hdmi_cfg; |
| // Index into the recommended buffer translation table to use when |
| // configuring DDI_BUF_TRANS[9] for HDMI/DVI. |
| DEF_SUBFIELD(hdmi_cfg, 3, 0, ddi_buf_trans_idx); |
| |
| uint8_t unused3[8]; |
| // Specifies the DDI this config this corresponds to as well as type of DDI. |
| uint8_t port_type; |
| uint8_t unused4[6]; |
| |
| uint8_t flags; |
| // Flag that indicates that there is an iboost override. An override enables |
| // iboost for all DDI_BUF_TRANS values and overrides the recommended iboost. |
| DEF_SUBBIT(flags, 3, has_iboost_override); |
| |
| uint8_t unused5[9]; |
| |
| uint8_t type_c_config; |
| DEF_SUBBIT(type_c_config, 0, is_usb_type_c); |
| DEF_SUBBIT(type_c_config, 1, is_thunderbolt); |
| |
| uint8_t unused6[3]; |
| |
| uint8_t iboost_levels; |
| // The iboost override level, if has_iboost_override is set. |
| DEF_SUBFIELD(iboost_levels, 7, 4, hdmi_iboost_override); |
| DEF_SUBFIELD(iboost_levels, 3, 0, dp_iboost_override); |
| } ddi_config_t; |
| static_assert(offsetof(ddi_config_t, ddi_flags) == 2, "Bad ddi_flags offset"); |
| static_assert(offsetof(ddi_config_t, hdmi_cfg) == 7, "Bad hdmi_cfg offset"); |
| static_assert(offsetof(ddi_config_t, port_type) == 16, "Bad port_type offset"); |
| static_assert(offsetof(ddi_config_t, flags) == 23, "Bad flags offset"); |
| static_assert(offsetof(ddi_config_t, iboost_levels) == 37, "Bad iboost_levels offset"); |
| static_assert(sizeof(ddi_config_t) == 38, "Bad ddi_config_t size"); |
| |
| typedef struct edp_config { |
| static constexpr uint32_t kBlockType = 27; |
| |
| uint8_t unused[204]; |
| // Contains 16 nibbles, one for each panel type 0x0-0xf. If the value |
| // is 0, then the panel is a low voltage panel. |
| uint8_t vswing_preemphasis[8]; |
| // A bunch of other unused stuff |
| } edp_config_t; |
| static_assert(offsetof(edp_config_t, vswing_preemphasis) == 204, "Bad vswing_preemphasis offset"); |
| |
| typedef struct lvds_config { |
| static constexpr uint32_t kBlockType = 40; |
| |
| // The default panel type for the hardware. Can be overridden by the IGD |
| // SCI panel details function. |
| uint8_t panel_type; |
| // A bunch of other unused stuff |
| } lvds_config_t; |
| |
| typedef struct lfp_backlight_entry { |
| uint8_t flags; |
| uint8_t pwm_freq_hz_low; |
| uint8_t pwm_freq_hz_high; |
| uint8_t min_brightness; |
| uint8_t unused[2]; |
| } lfp_backlight_entry_t; |
| static_assert(sizeof(lfp_backlight_entry_t) == 6, "Bad struct size"); |
| |
| typedef struct lfp_backlight { |
| static constexpr uint32_t kBlockType = 43; |
| |
| uint8_t entry_size; |
| lfp_backlight_entry_t entries[16]; |
| uint8_t level[16]; |
| } lfp_backlight_t; |
| static_assert(sizeof(lfp_backlight_t) == 113, "Bad struct size"); |
| |
| class IgdOpRegion { |
| public: |
| IgdOpRegion() = default; |
| ~IgdOpRegion(); |
| |
| // Returns ZX_ERR_NOT_SUPPORTED if the boot firmware doesn't support the |
| // OpRegion protocol. The firmware might be completely missing OpRegion |
| // support, or may have a broken implementation that reports invalid data. |
| zx_status_t Init(zx_device_t* parent, ddk::Pci& pci); |
| |
| bool HasDdi(DdiId ddi_id) const { return ddi_features_.find(ddi_id) != ddi_features_.end(); } |
| bool SupportsHdmi(DdiId ddi_id) const { |
| return HasDdi(ddi_id) && ddi_features_.at(ddi_id).supports_hdmi; |
| } |
| bool SupportsDvi(DdiId ddi_id) const { |
| return HasDdi(ddi_id) && ddi_features_.at(ddi_id).supports_dvi; |
| } |
| bool SupportsDp(DdiId ddi_id) const { |
| return HasDdi(ddi_id) && ddi_features_.at(ddi_id).supports_dp; |
| } |
| bool IsTypeC(DdiId ddi_id) const { return HasDdi(ddi_id) && ddi_features_.at(ddi_id).is_type_c; } |
| bool IsEdp(DdiId ddi_id) const { return HasDdi(ddi_id) && ddi_features_.at(ddi_id).is_edp; } |
| |
| bool IsLowVoltageEdp(DdiId ddi_id) const { |
| // TODO(stevensd): Support the case where more than one type of edp panel is present. |
| return HasDdi(ddi_id) && ddi_features_.at(ddi_id).is_edp && edp_is_low_voltage_; |
| } |
| |
| uint8_t GetIBoost(DdiId ddi_id, bool is_dp) const { |
| return is_dp ? HasDdi(ddi_id) && ddi_features_.at(ddi_id).iboosts.dp_iboost |
| : HasDdi(ddi_id) && ddi_features_.at(ddi_id).iboosts.hdmi_iboost; |
| } |
| |
| static constexpr uint8_t kUseDefaultIdx = 0xff; |
| uint8_t GetHdmiBufferTranslationIndex(DdiId ddi_id) const { |
| ZX_DEBUG_ASSERT(SupportsHdmi(ddi_id) || SupportsDvi(ddi_id)); |
| return ddi_features_.at(ddi_id).hdmi_buffer_translation_idx; |
| } |
| |
| double GetMinBacklightBrightness() const { return min_backlight_brightness_; } |
| |
| // TODO(https://fxbug.dev/42063424): Instead of adding the helper functions, these DDI |
| // features should be exported as a data-only struct that can be easily |
| // injected by tests. |
| void SetIsEdpForTesting(DdiId ddi_id, bool is_edp) { ddi_features_[ddi_id].is_edp = is_edp; } |
| void SetSupportsDpForTesting(DdiId ddi_id, bool value) { |
| ddi_features_[ddi_id].supports_dp = value; |
| } |
| void SetIsTypeCForTesting(DdiId ddi_id, bool value) { ddi_features_[ddi_id].is_type_c = value; } |
| |
| private: |
| template <typename T> |
| T* GetSection(uint16_t* size); |
| uint8_t* GetSection(uint8_t tag, uint16_t* size); |
| bool ProcessDdiConfigs(); |
| bool CheckForLowVoltageEdp(ddk::Pci& pci); |
| bool GetPanelType(ddk::Pci& pci, uint8_t* type); |
| bool Swsci(ddk::Pci& pci, uint16_t function, uint16_t subfunction, uint32_t additional_param, |
| uint16_t* exit_param, uint32_t* additional_res); |
| void ProcessBacklightData(); |
| |
| AcpiMemoryRegion memory_op_region_; |
| |
| // Empty if the VBT fits in the Memory OpRegion's Mailbox 4. |
| AcpiMemoryRegion extended_vbt_region_; |
| |
| igd_opregion_t* igd_opregion_; |
| bios_data_blocks_header_t* bdb_; |
| |
| struct DdiFeatures { |
| bool supports_hdmi; |
| bool supports_dvi; |
| bool supports_dp; |
| bool is_edp; |
| bool is_type_c; |
| bool is_thunderbolt; |
| |
| struct Iboost { |
| uint8_t hdmi_iboost = 0; |
| uint8_t dp_iboost = 0; |
| }; |
| Iboost iboosts; |
| uint8_t hdmi_buffer_translation_idx; |
| }; |
| std::unordered_map<DdiId, DdiFeatures> ddi_features_; |
| |
| bool edp_is_low_voltage_ = false; |
| uint8_t panel_type_; |
| double min_backlight_brightness_; |
| }; |
| |
| } // namespace i915 |
| |
| #endif // SRC_GRAPHICS_DISPLAY_DRIVERS_INTEL_I915_IGD_H_ |