blob: bcf348bee0d380340b4b3022e2e19fdc237f5e21 [file] [log] [blame]
// Copyright 2016 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.
#include <string.h>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/display.h>
#include <ddk/protocol/pci.h>
#include <hw/inout.h>
#include <hw/pci.h>
#include <assert.h>
#include <fbl/unique_ptr.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <zx/vmar.h>
#include <zx/vmo.h>
#include "bootloader-display.h"
#include "dp-display.h"
#include "hdmi-display.h"
#include "intel-i915.h"
#include "macros.h"
#include "pci-ids.h"
#include "registers.h"
#include "registers-ddi.h"
#include "registers-dpll.h"
#include "registers-pipe.h"
#include "registers-transcoder.h"
#include "registers.h"
#define INTEL_I915_BROADWELL_DID (0x1616)
#define INTEL_I915_REG_WINDOW_SIZE (0x1000000u)
#define INTEL_I915_FB_WINDOW_SIZE (0x10000000u)
#define BACKLIGHT_CTRL_OFFSET (0xc8250)
#define BACKLIGHT_CTRL_BIT ((uint32_t)(1u << 31))
#define FLAGS_BACKLIGHT 1
#define ENABLE_MODESETTING 1
namespace {
static int irq_handler(void* arg) {
return static_cast<i915::Controller*>(arg)->IrqLoop();
}
bool pipe_in_use(const fbl::Vector<i915::DisplayDevice*>& displays, registers::Pipe pipe) {
for (size_t i = 0; i < displays.size(); i++) {
if (displays[i]->pipe() == pipe) {
return true;
}
}
return false;
}
} // namespace
namespace i915 {
int Controller::IrqLoop() {
for (;;) {
uint64_t slots;
if (zx_interrupt_wait(irq_, &slots) != ZX_OK) {
zxlogf(TRACE, "i915: interrupt wait failed\n");
break;
}
auto interrupt_ctrl = registers::MasterInterruptControl::Get().ReadFrom(mmio_space_.get());
interrupt_ctrl.set_enable_mask(0);
interrupt_ctrl.WriteTo(mmio_space_.get());
if (interrupt_ctrl.sde_int_pending()) {
auto sde_int_identity = registers::SdeInterruptBase::Get(registers::SdeInterruptBase::kSdeIntIdentity).ReadFrom(mmio_space_.get());
auto hp_ctrl1 = registers::HotplugCtrl
::Get(registers::DDI_A).ReadFrom(mmio_space_.get());
auto hp_ctrl2 = registers::HotplugCtrl
::Get(registers::DDI_E).ReadFrom(mmio_space_.get());
for (uint32_t i = 0; i < registers::kDdiCount; i++) {
registers::Ddi ddi = registers::kDdis[i];
bool hp_detected = sde_int_identity.ddi_bit(ddi).get();
auto hp_ctrl = ddi < registers::DDI_E ? hp_ctrl1 : hp_ctrl2;
if (hp_detected && hp_ctrl.hpd_long_pulse(ddi).get()) {
HandleHotplug(ddi);
}
}
// Write back the register to clear the bits
hp_ctrl1.WriteTo(mmio_space_.get());
hp_ctrl2.WriteTo(mmio_space_.get());
sde_int_identity.WriteTo(mmio_space_.get());
}
interrupt_ctrl.set_enable_mask(1);
interrupt_ctrl.WriteTo(mmio_space_.get());
}
return 0;
}
void Controller::EnableBacklight(bool enable) {
if (flags_ & FLAGS_BACKLIGHT) {
uint32_t tmp = mmio_space_->Read<uint32_t>(BACKLIGHT_CTRL_OFFSET);
if (enable) {
tmp |= BACKLIGHT_CTRL_BIT;
} else {
tmp &= ~BACKLIGHT_CTRL_BIT;
}
mmio_space_->Write<uint32_t>(BACKLIGHT_CTRL_OFFSET, tmp);
}
}
zx_status_t Controller::InitHotplug() {
// Disable interrupts here, we'll re-enable them at the very end of ::Bind
auto interrupt_ctrl = registers::MasterInterruptControl::Get().ReadFrom(mmio_space_.get());
interrupt_ctrl.set_enable_mask(0);
interrupt_ctrl.WriteTo(mmio_space_.get());
uint32_t irq_cnt = 0;
zx_status_t status = pci_query_irq_mode(&pci_, ZX_PCIE_IRQ_MODE_LEGACY, &irq_cnt);
if (status != ZX_OK || !irq_cnt) {
zxlogf(ERROR, "i915: Failed to find interrupts %d %d\n", status, irq_cnt);
return ZX_ERR_INTERNAL;
}
if ((status = pci_set_irq_mode(&pci_, ZX_PCIE_IRQ_MODE_LEGACY, 1)) != ZX_OK) {
zxlogf(ERROR, "i915: Failed to set irq mode %d\n", status);
return status;
}
if ((status = pci_map_interrupt(&pci_, 0, &irq_) != ZX_OK)) {
zxlogf(ERROR, "i915: Failed to map interrupt %d\n", status);
return status;
}
status = thrd_create_with_name(&irq_thread_, irq_handler, this, "i915-irq-thread");
if (status != ZX_OK) {
zxlogf(ERROR, "i915: Failed to create irq thread\n");
return status;
}
auto sfuse_strap = registers::SouthFuseStrap::Get().ReadFrom(mmio_space_.get());
for (uint32_t i = 0; i < registers::kDdiCount; i++) {
registers::Ddi ddi = registers::kDdis[i];
bool enabled = (ddi == registers::DDI_A) || (ddi == registers::DDI_E) || (ddi == registers::DDI_B && sfuse_strap.port_b_present()) || (ddi == registers::DDI_C && sfuse_strap.port_c_present()) || (ddi == registers::DDI_D && sfuse_strap.port_d_present());
auto hp_ctrl = registers::HotplugCtrl::Get(ddi).ReadFrom(mmio_space_.get());
hp_ctrl.hpd_enable(ddi).set(enabled);
hp_ctrl.WriteTo(mmio_space_.get());
auto mask = registers::SdeInterruptBase::Get(
registers::SdeInterruptBase::kSdeIntMask)
.ReadFrom(mmio_space_.get());
mask.ddi_bit(ddi).set(!enabled);
mask.WriteTo(mmio_space_.get());
auto enable = registers::SdeInterruptBase::Get(
registers::SdeInterruptBase::kSdeIntEnable)
.ReadFrom(mmio_space_.get());
enable.ddi_bit(ddi).set(enabled);
enable.WriteTo(mmio_space_.get());
}
return ZX_OK;
}
void Controller::HandleHotplug(registers::Ddi ddi) {
zxlogf(TRACE, "i915: hotplug detected %d\n", ddi);
DisplayDevice* device = nullptr;
bool was_kernel_framebuffer = false;
for (size_t i = 0; i < display_devices_.size(); i++) {
if (display_devices_[i]->ddi() == ddi) {
device = display_devices_.erase(i);
was_kernel_framebuffer = i == 0;
break;
}
}
if (device) { // Existing device was unplugged
if (was_kernel_framebuffer) {
if (display_devices_.is_empty()) {
zx_set_framebuffer_vmo(get_root_resource(), ZX_HANDLE_INVALID, 0, 0, 0, 0, 0);
} else {
DisplayDevice* new_device = display_devices_[0];
zx_set_framebuffer_vmo(get_root_resource(),
new_device->framebuffer_vmo().get(),
static_cast<uint32_t>(new_device->framebuffer_size()),
new_device->info().format, new_device->info().width,
new_device->info().height, new_device->info().stride);
}
}
device->DdkRemove();
} else { // New device was plugged in
fbl::unique_ptr<DisplayDevice> device = InitDisplay(ddi);
if (!device) {
zxlogf(INFO, "i915: failed to init hotplug display\n");
return;
}
if (AddDisplay(fbl::move(device)) != ZX_OK) {
zxlogf(INFO, "Failed to add display %d\n", ddi);
}
}
}
bool Controller::BringUpDisplayEngine() {
// Enable PCH Reset Handshake
auto nde_rstwrn_opt = registers::NorthDERestetWarning::Get().ReadFrom(mmio_space_.get());
nde_rstwrn_opt.set_rst_pch_handshake_enable(1);
nde_rstwrn_opt.WriteTo(mmio_space_.get());
// Wait for Power Well 0 distribution
if (!WAIT_ON_US(registers::FuseStatus::Get().ReadFrom(mmio_space_.get()).pg0_dist_status(), 5)) {
zxlogf(ERROR, "Power Well 0 distribution failed\n");
return false;
}
// Enable and wait for Power Well 1 and Misc IO power
auto power_well = registers::PowerWellControl2::Get().ReadFrom(mmio_space_.get());
power_well.set_power_well_1_request(1);
power_well.set_misc_io_power_state(1);
power_well.WriteTo(mmio_space_.get());
if (!WAIT_ON_US(registers::PowerWellControl2::Get().ReadFrom(mmio_space_.get()).power_well_1_state(), 10)) {
zxlogf(ERROR, "Power Well 1 failed to enable\n");
return false;
}
if (!WAIT_ON_US(registers::PowerWellControl2::Get().ReadFrom(mmio_space_.get()).misc_io_power_state(), 10)) {
zxlogf(ERROR, "Misc IO power failed to enable\n");
return false;
}
if (!WAIT_ON_US(registers::FuseStatus::Get().ReadFrom(mmio_space_.get()).pg1_dist_status(), 5)) {
zxlogf(ERROR, "Power Well 1 distribution failed\n");
return false;
}
// Enable CDCLK PLL to 337.5mhz if the BIOS didn't already enable it. If it needs to be
// something special (i.e. for eDP), assume that the BIOS already enabled it.
auto dpll_enable = registers::DpllEnable::Get(0).ReadFrom(mmio_space_.get());
if (!dpll_enable.enable_dpll()) {
// Set the cd_clk frequency to the minimum
auto cd_clk = registers::CdClockCtl::Get().ReadFrom(mmio_space_.get());
cd_clk.set_cd_freq_select(cd_clk.kFreqSelect3XX);
cd_clk.set_cd_freq_decimal(cd_clk.kFreqDecimal3375);
cd_clk.WriteTo(mmio_space_.get());
// Configure DPLL0
auto dpll_ctl1 = registers::DpllControl1::Get().ReadFrom(mmio_space_.get());
dpll_ctl1.dpll_link_rate(0).set(dpll_ctl1.kLinkRate810Mhz);
dpll_ctl1.dpll_override(0).set(1);
dpll_ctl1.dpll_hdmi_mode(0).set(0);
dpll_ctl1.dpll_ssc_enable(0).set(0);
dpll_ctl1.WriteTo(mmio_space_.get());
// Enable DPLL0 and wait for it
dpll_enable.set_enable_dpll(1);
dpll_enable.WriteTo(mmio_space_.get());
if (!WAIT_ON_MS(registers::Lcpll1Control::Get().ReadFrom(mmio_space_.get()).pll_lock(), 5)) {
zxlogf(ERROR, "Failed to configure dpll0\n");
return false;
}
// Do the magic sequence for Changing CD Clock Frequency specified on
// intel-gfx-prm-osrc-skl-vol12-display.pdf p.135
constexpr uint32_t kGtDriverMailboxInterface = 0x138124;
constexpr uint32_t kGtDriverMailboxData0 = 0x138128;
constexpr uint32_t kGtDriverMailboxData1 = 0x13812c;
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData0, 0x3);
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData1, 0x0);
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxInterface, 0x80000007);
int count = 0;
for (;;) {
if (!WAIT_ON_US(mmio_space_.get()
->Read<uint32_t>(kGtDriverMailboxInterface) &
0x80000000,
150)) {
zxlogf(ERROR, "GT Driver Mailbox driver busy\n");
return false;
}
if (mmio_space_.get()->Read<uint32_t>(kGtDriverMailboxData0) & 0x1) {
break;
}
if (count++ == 3) {
zxlogf(ERROR, "Failed to set cd_clk\n");
return false;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
}
cd_clk.WriteTo(mmio_space_.get());
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData0, 0x3);
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxData1, 0x0);
mmio_space_.get()->Write<uint32_t>(kGtDriverMailboxInterface, 0x80000007);
}
// Enable and wait for DBUF
auto dbuf_ctl = registers::DbufCtl::Get().ReadFrom(mmio_space_.get());
dbuf_ctl.set_power_request(1);
dbuf_ctl.WriteTo(mmio_space_.get());
if (!WAIT_ON_US(registers::DbufCtl::Get().ReadFrom(mmio_space_.get()).power_state(), 10)) {
zxlogf(ERROR, "Failed to enable DBUF\n");
return false;
}
// We never use VGA, so just disable it at startup
constexpr uint16_t kSequencerIdx = 0x3c4;
constexpr uint16_t kSequencerData = 0x3c5;
constexpr uint8_t kClockingModeIdx = 1;
constexpr uint8_t kClockingModeScreenOff = (1 << 5);
zx_status_t status = zx_mmap_device_io(get_root_resource(), kSequencerIdx, 2);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to map vga ports\n");
return false;
}
outp(kSequencerIdx, kClockingModeIdx);
uint8_t clocking_mode = inp(kSequencerData);
if (!(clocking_mode & kClockingModeScreenOff)) {
outp(kSequencerIdx, inp(kSequencerData) | kClockingModeScreenOff);
zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
auto vga_ctl = registers::VgaCtl::Get().ReadFrom(mmio_space());
vga_ctl.set_vga_display_disable(1);
vga_ctl.WriteTo(mmio_space());
}
return true;
}
bool Controller::ResetPipe(registers::Pipe pipe) {
registers::PipeRegs pipe_regs(pipe);
registers::TranscoderRegs trans_regs(pipe);
// Disable planes
pipe_regs.PlaneControl().FromValue(0).WriteTo(mmio_space());
pipe_regs.PlaneSurface().FromValue(0).WriteTo(mmio_space());
// Disable the scalers (double buffered on PipeScalerWinSize)
pipe_regs.PipeScalerCtrl(0).ReadFrom(mmio_space()).set_enable(0).WriteTo(mmio_space());
pipe_regs.PipeScalerWinSize(0).ReadFrom(mmio_space()).WriteTo(mmio_space());
if (pipe != registers::PIPE_C) {
pipe_regs.PipeScalerCtrl(1).ReadFrom(mmio_space()).set_enable(0).WriteTo(mmio_space());
pipe_regs.PipeScalerWinSize(1).ReadFrom(mmio_space()).WriteTo(mmio_space());
}
// Disable transcoder and wait it to stop
auto trans_conf = trans_regs.Conf().ReadFrom(mmio_space());
trans_conf.set_transcoder_enable(0);
trans_conf.WriteTo(mmio_space());
if (!WAIT_ON_MS(!trans_regs.Conf().ReadFrom(mmio_space()).transcoder_state(), 60)) {
zxlogf(ERROR, "Failed to reset transcoder\n");
return false;
}
// Disable transcoder ddi select and clock select
auto trans_ddi_ctl = trans_regs.DdiFuncControl().ReadFrom(mmio_space());
trans_ddi_ctl.set_trans_ddi_function_enable(0);
trans_ddi_ctl.set_ddi_select(0);
trans_ddi_ctl.WriteTo(mmio_space());
auto trans_clk_sel = trans_regs.ClockSelect().ReadFrom(mmio_space());
trans_clk_sel.set_trans_clock_select(0);
trans_clk_sel.WriteTo(mmio_space());
return true;
}
bool Controller::ResetDdi(registers::Ddi ddi) {
registers::DdiRegs ddi_regs(ddi);
// Disable the port
auto ddi_buf_ctl = ddi_regs.DdiBufControl().ReadFrom(mmio_space());
bool was_enabled = ddi_buf_ctl.ddi_buffer_enable();
ddi_buf_ctl.set_ddi_buffer_enable(0);
ddi_buf_ctl.WriteTo(mmio_space());
auto ddi_dp_tp_ctl = ddi_regs.DdiDpTransportControl().ReadFrom(mmio_space());
ddi_dp_tp_ctl.set_transport_enable(0);
ddi_dp_tp_ctl.set_dp_link_training_pattern(ddi_dp_tp_ctl.kTrainingPattern1);
ddi_dp_tp_ctl.WriteTo(mmio_space());
if (was_enabled && !WAIT_ON_MS(ddi_regs.DdiBufControl().ReadFrom(mmio_space()).ddi_idle_status(), 8)) {
zxlogf(ERROR, "Port failed to go idle\n");
return false;
}
// Disable IO power
auto pwc2 = registers::PowerWellControl2::Get().ReadFrom(mmio_space());
pwc2.ddi_io_power_request(ddi).set(0);
pwc2.WriteTo(mmio_space());
// Remove the PLL mapping and disable the PLL (we don't share PLLs)
auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space());
dpll_ctrl2.ddi_clock_off(ddi).set(1);
dpll_ctrl2.WriteTo(mmio_space());
uint8_t dpll_number = static_cast<uint8_t>(dpll_ctrl2.ddi_clock_select(ddi).get());
auto dpll_enable = registers::DpllEnable::Get(dpll_number).ReadFrom(mmio_space());
dpll_enable.set_enable_dpll(1);
dpll_enable.WriteTo(mmio_space());
return true;
}
void Controller::AllocDisplayBuffers() {
// Do display buffer alloc and watermark programming with fixed allocation from
// intel docs. This allows the display to work but prevents power management.
// TODO(ZX-1413): Calculate these dynamically based on what's enabled.
for (unsigned i = 0; i < registers::kPipeCount; i++) {
registers::Pipe pipe = registers::kPipes[i];
registers::PipeRegs pipe_regs(pipe);
// Plane 1 gets everything
constexpr uint32_t kPerDdi = 891 / 3;
auto buf_cfg = pipe_regs.PlaneBufCfg(1).FromValue(0);
buf_cfg.set_buffer_start(kPerDdi * pipe);
buf_cfg.set_buffer_end(kPerDdi * (pipe + 1) - 1);
buf_cfg.WriteTo(mmio_space());
// Cursor and planes 2 and 3 get nothing
pipe_regs.PlaneBufCfg(0).FromValue(0).WriteTo(mmio_space());
pipe_regs.PlaneBufCfg(2).FromValue(0).WriteTo(mmio_space());
pipe_regs.PlaneBufCfg(3).FromValue(0).WriteTo(mmio_space());
auto wm0 = pipe_regs.PlaneWatermark(0).FromValue(0);
wm0.set_enable(1);
wm0.set_lines(2);
wm0.set_blocks(kPerDdi);
wm0.WriteTo(mmio_space());
for (int i = 1; i < 8; i++) {
auto wm = pipe_regs.PlaneWatermark(i).FromValue(0);
wm.WriteTo(mmio_space());
}
// Write so double-buffered regs are updated
auto base = pipe_regs.PlaneSurface().ReadFrom(mmio_space());
base.WriteTo(mmio_space());
}
// TODO(ZX-1413): Wait for vblank instead of sleeping
zx_nanosleep(zx_deadline_after(ZX_MSEC(33)));
}
fbl::unique_ptr<DisplayDevice> Controller::InitDisplay(registers::Ddi ddi) {
registers::Pipe pipe;
if (!pipe_in_use(display_devices_, registers::PIPE_A)) {
pipe = registers::PIPE_A;
} else if (!pipe_in_use(display_devices_, registers::PIPE_B)
&& !registers::HdportState::Get().ReadFrom(mmio_space_.get()).dpll2_used()) {
pipe = registers::PIPE_B;
} else if (!pipe_in_use(display_devices_, registers::PIPE_C)) {
pipe = registers::PIPE_C;
} else {
zxlogf(INFO, "i915: Could not allocate pipe for ddi %d\n", ddi);
return nullptr;
}
fbl::AllocChecker ac;
if (igd_opregion_.IsHdmi(ddi) || igd_opregion_.IsDvi(ddi)) {
zxlogf(SPEW, "Checking for hdmi monitor\n");
auto hdmi_disp = fbl::make_unique_checked<HdmiDisplay>(&ac, this, ddi, pipe);
if (ac.check() && reinterpret_cast<DisplayDevice*>(hdmi_disp.get())->Init()) {
return hdmi_disp;
}
} else if (igd_opregion_.IsDp(ddi)) {
zxlogf(SPEW, "Checking for displayport monitor\n");
auto dp_disp = fbl::make_unique_checked<DpDisplay>(&ac, this, ddi, pipe);
if (ac.check() && reinterpret_cast<DisplayDevice*>(dp_disp.get())->Init()) {
return dp_disp;
}
} else {
zxlogf(SPEW, "Skipping ddi\n");
}
return nullptr;
}
zx_status_t Controller::InitDisplays() {
if (ENABLE_MODESETTING && is_gen9(device_id_)) {
BringUpDisplayEngine();
for (unsigned i = 0; i < registers::kPipeCount; i++) {
ResetPipe(registers::kPipes[i]);
}
for (unsigned i = 0; i < registers::kDdiCount; i++) {
ResetDdi(registers::kDdis[i]);
}
AllocDisplayBuffers();
for (uint32_t i = 0; i < registers::kDdiCount; i++) {
auto disp_device = InitDisplay(registers::kDdis[i]);
if (disp_device) {
if (AddDisplay(fbl::move(disp_device)) != ZX_OK) {
zxlogf(INFO, "Failed to add display %d\n", i);
}
}
}
return ZX_OK;
} else {
fbl::AllocChecker ac;
// The DDI doesn't actually matter, so just say DDI A. The BIOS does use PIPE_A.
auto disp_device = fbl::make_unique_checked<BootloaderDisplay>(
&ac, this, registers::DDI_A, registers::PIPE_A);
if (!ac.check()) {
zxlogf(ERROR, "i915: failed to alloc disp_device\n");
return ZX_ERR_NO_MEMORY;
}
if (!reinterpret_cast<DisplayDevice*>(disp_device.get())->Init()) {
zxlogf(ERROR, "i915: failed to init display\n");
return ZX_ERR_INTERNAL;
}
return AddDisplay(fbl::move(disp_device));
}
}
zx_status_t Controller::AddDisplay(fbl::unique_ptr<DisplayDevice>&& display) {
zx_status_t status = display->DdkAdd("intel_i915_disp");
fbl::AllocChecker ac;
display_devices_.reserve(display_devices_.size() + 1, &ac);
if (ac.check() && status == ZX_OK) {
display_devices_.push_back(display.release(), &ac);
assert(ac.check());
} else {
zxlogf(ERROR, "i915: failed to add display device %d\n", status);
return status == ZX_OK ? ZX_ERR_NO_MEMORY : status;
}
if (display_devices_.size() == 1) {
DisplayDevice* new_device = display_devices_[0];
zx_set_framebuffer_vmo(get_root_resource(), new_device->framebuffer_vmo().get(),
static_cast<uint32_t>(new_device->framebuffer_size()),
new_device->info().format, new_device->info().width,
new_device->info().height, new_device->info().stride);
}
return ZX_OK;
}
void Controller::DdkUnbind() {
while (!display_devices_.is_empty()) {
device_remove(display_devices_.erase(0)->zxdev());
}
device_remove(zxdev());
}
void Controller::DdkRelease() {
delete this;
}
zx_status_t Controller::DdkSuspend(uint32_t hint) {
if ((hint & DEVICE_SUSPEND_REASON_MASK) == DEVICE_SUSPEND_FLAG_MEXEC) {
uint32_t format, width, height, stride;
if (zx_bootloader_fb_get_info(&format, &width, &height, &stride) != ZX_OK) {
return ZX_OK;
}
// The bootloader framebuffer is most likely at the start of the display
// controller's bar 2. Try to get that buffer working again across the
// mexec by mapping gfx stolen memory to gaddr 0.
auto bdsm_reg = registers::BaseDsm::Get().FromValue(0);
zx_status_t status =
pci_config_read32(&pci_, bdsm_reg.kAddr, bdsm_reg.reg_value_ptr());
if (status != ZX_OK) {
zxlogf(TRACE, "i915: failed to read dsm base\n");
return ZX_OK;
}
// The Intel docs say that the first page should be reserved for the gfx
// hardware, but a lot of BIOSes seem to ignore that.
uintptr_t fb = bdsm_reg.base_phys_addr() << bdsm_reg.base_phys_addr_shift;
uint32_t fb_size = stride * height * ZX_PIXEL_FORMAT_BYTES(format);
gtt_.SetupForMexec(fb, fb_size, registers::PlaneSurface::kTrailingPtePadding);
// Try to map the framebuffer and clear it. If not, oh well.
void* gmadr;
uint64_t gmadr_size;
zx_handle_t gmadr_handle;
if (pci_map_bar(&pci_, 2, ZX_CACHE_POLICY_WRITE_COMBINING,
&gmadr, &gmadr_size, &gmadr_handle) == ZX_OK) {
memset(reinterpret_cast<void*>(gmadr), 0, fb_size);
zx_handle_close(gmadr_handle);
}
for (auto* display : display_devices_) {
// TODO(ZX-1413): Reset/scale the display to ensure the buffer displays properly
registers::PipeRegs pipe_regs(display->pipe());
auto plane_stride = pipe_regs.PlaneSurfaceStride().ReadFrom(mmio_space_.get());
plane_stride.set_stride(stride / registers::PlaneSurfaceStride::kLinearStrideChunkSize);
plane_stride.WriteTo(mmio_space_.get());
auto plane_surface = pipe_regs.PlaneSurface().ReadFrom(mmio_space_.get());
plane_surface.set_surface_base_addr(0);
plane_surface.WriteTo(mmio_space_.get());
}
}
return ZX_OK;
}
zx_status_t Controller::Bind(fbl::unique_ptr<i915::Controller>* controller_ptr) {
zxlogf(TRACE, "i915: binding to display controller\n");
if (device_get_protocol(parent_, ZX_PROTOCOL_PCI, &pci_)) {
return ZX_ERR_NOT_SUPPORTED;
}
pci_config_read16(&pci_, PCI_CONFIG_DEVICE_ID, &device_id_);
zxlogf(TRACE, "i915: device id %x\n", device_id_);
if (device_id_ == INTEL_I915_BROADWELL_DID) {
// TODO: this should be based on the specific target
flags_ |= FLAGS_BACKLIGHT;
}
zx_status_t status;
if (is_gen9(device_id_) && ENABLE_MODESETTING) {
status = igd_opregion_.Init(&pci_);
if (status != ZX_OK) {
zxlogf(ERROR, "i915: Failed to init VBT (%d)\n", status);
return status;
}
}
zxlogf(TRACE, "i915: mapping registers\n");
// map register window
uintptr_t regs;
uint64_t regs_size;
status = pci_map_bar(&pci_, 0u, ZX_CACHE_POLICY_UNCACHED_DEVICE,
reinterpret_cast<void**>(&regs), &regs_size, &regs_handle_);
if (status != ZX_OK) {
zxlogf(ERROR, "i915: failed to map bar 0: %d\n", status);
return status;
}
fbl::AllocChecker ac;
fbl::unique_ptr<hwreg::RegisterIo> mmio_space(
new (&ac) hwreg::RegisterIo(reinterpret_cast<volatile void*>(regs)));
if (!ac.check()) {
zxlogf(ERROR, "i915: failed to alloc RegisterIo\n");
return ZX_ERR_NO_MEMORY;
}
mmio_space_ = fbl::move(mmio_space);
if (ENABLE_MODESETTING && is_gen9(device_id_)) {
zxlogf(TRACE, "i915: initialzing hotplug\n");
status = InitHotplug();
if (status != ZX_OK) {
zxlogf(ERROR, "i915: failed to init hotplugging\n");
return status;
}
}
zxlogf(TRACE, "i915: mapping gtt\n");
if ((status = gtt_.Init(this)) != ZX_OK) {
zxlogf(ERROR, "i915: failed to init gtt %d\n", status);
return status;
}
status = DdkAdd("intel_i915");
if (status != ZX_OK) {
zxlogf(ERROR, "i915: failed to add controller device\n");
return status;
}
// DevMgr now owns this pointer, release it to avoid destroying the object
// when device goes out of scope.
__UNUSED auto ptr = controller_ptr->release();
zxlogf(TRACE, "i915: initializing displays\n");
status = InitDisplays();
if (status != ZX_OK) {
device_remove(zxdev());
return status;
}
if (is_gen9(device_id_)) {
auto interrupt_ctrl = registers::MasterInterruptControl::Get().ReadFrom(mmio_space_.get());
interrupt_ctrl.set_enable_mask(1);
interrupt_ctrl.WriteTo(mmio_space_.get());
}
// TODO remove when the gfxconsole moves to user space
EnableBacklight(true);
zxlogf(TRACE, "i915: initialization done\n");
return ZX_OK;
}
Controller::Controller(zx_device_t* parent)
: DeviceType(parent), irq_(ZX_HANDLE_INVALID) {}
Controller::~Controller() {
if (irq_ != ZX_HANDLE_INVALID) {
zx_interrupt_signal(irq_, ZX_INTERRUPT_SLOT_USER, 0);
thrd_join(irq_thread_, nullptr);
zx_handle_close(irq_);
}
if (mmio_space_) {
EnableBacklight(false);
zx_handle_close(regs_handle_);
regs_handle_ = ZX_HANDLE_INVALID;
}
}
} // namespace i915
zx_status_t intel_i915_bind(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
fbl::unique_ptr<i915::Controller> controller(new (&ac) i915::Controller(parent));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
return controller->Bind(&controller);
}