blob: e0a721dc42dfa4fbf3c7155b6f7ba8835d0fe31d [file] [log] [blame]
// 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(&region, &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_