// Copyright 2017 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 #include #include "intel-i915.h" #include "hdmi-display.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" // I2c functions namespace { // Recommended DDI buffer translation programming values struct ddi_buf_trans_entry { uint32_t high_dword; uint32_t low_dword; }; const ddi_buf_trans_entry hdmi_ddi_buf_trans_skl_uhs[11] { { 0x000000ac, 0x00000018 }, { 0x0000009d, 0x00005012 }, { 0x00000088, 0x00007011 }, { 0x000000a1, 0x00000018 }, { 0x00000098, 0x00000018 }, { 0x00000088, 0x00004013 }, { 0x000000cd, 0x80006012 }, { 0x000000df, 0x00000018 }, { 0x000000cd, 0x80003015 }, { 0x000000c0, 0x80003015 }, { 0x000000c0, 0x80000018 }, }; const ddi_buf_trans_entry hdmi_ddi_buf_trans_skl_y[11] { { 0x000000a1, 0x00000018 }, { 0x000000df, 0x00005012 }, { 0x000000cb, 0x80007011 }, { 0x000000a4, 0x00000018 }, { 0x0000009d, 0x00000018 }, { 0x00000080, 0x00004013 }, { 0x000000c0, 0x80006012 }, { 0x0000008a, 0x00000018 }, { 0x000000c0, 0x80003015 }, { 0x000000c0, 0x80003015 }, { 0x000000c0, 0x80000018 }, }; int ddi_to_pin(registers::Ddi ddi) { if (ddi == registers::DDI_B) { return registers::GMBus0::kDdiBPin; } else if (ddi == registers::DDI_C) { return registers::GMBus0::kDdiCPin; } else if (ddi == registers::DDI_D) { return registers::GMBus0::kDdiDPin; } return -1; } void write_gmbus3(hwreg::RegisterIo* mmio_space, const uint8_t* buf, uint32_t size, uint32_t idx) { int cur_byte = 0; uint32_t val = 0; while (idx < size && cur_byte < 4) { val |= buf[idx++] << (8 * cur_byte++); } registers::GMBus3::Get().FromValue(val).WriteTo(mmio_space); } void read_gmbus3(hwreg::RegisterIo* mmio_space, uint8_t* buf, uint32_t size, uint32_t idx) { int cur_byte = 0; uint32_t val = registers::GMBus3::Get().ReadFrom(mmio_space).reg_value(); while (idx < size && cur_byte++ < 4) { buf[idx++] = val & 0xff; val >>= 8; } } static constexpr uint8_t kDdcSegmentAddress = 0x30; static constexpr uint8_t kDdcDataAddress = 0x50; static constexpr uint8_t kI2cClockUs = 10; // 100 kHz // For bit banging i2c over the gpio pins bool i2c_scl(hwreg::RegisterIo* mmio_space, registers::Ddi ddi, bool hi) { auto gpio = registers::GpioCtl::Get(ddi).FromValue(0); if (!hi) { gpio.set_clock_direction_val(1); gpio.set_clock_mask(1); } gpio.set_clock_direction_mask(1); gpio.WriteTo(mmio_space); gpio.ReadFrom(mmio_space); // Posting read // Handle the case where something on the bus is holding the clock // low. Timeout after 1ms. if (hi) { int count = 0; do { if (count != 0) { zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs))); } gpio.ReadFrom(mmio_space); } while (count++ < 100 && hi != gpio.clock_in()); if (hi != gpio.clock_in()) { return false; } } zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs / 2))); return true; } // For bit banging i2c over the gpio pins void i2c_sda(hwreg::RegisterIo* mmio_space, registers::Ddi ddi, bool hi) { auto gpio = registers::GpioCtl::Get(ddi).FromValue(0); if (!hi) { gpio.set_data_direction_val(1); gpio.set_data_mask(1); } gpio.set_data_direction_mask(1); gpio.WriteTo(mmio_space); gpio.ReadFrom(mmio_space); // Posting read zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs / 2))); } // For bit banging i2c over the gpio pins bool i2c_send_byte(hwreg::RegisterIo* mmio_space, registers::Ddi ddi, uint8_t byte) { // Set the bits from MSB to LSB for (int i = 7; i >= 0; i--) { i2c_sda(mmio_space, ddi, (byte >> i) & 0x1); i2c_scl(mmio_space, ddi, 1); // Leave the data line where it is for the rest of the cycle zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs / 2))); i2c_scl(mmio_space, ddi, 0); } // Release the data line and check for an ack i2c_sda(mmio_space, ddi, 1); i2c_scl(mmio_space, ddi, 1); bool ack = !registers::GpioCtl::Get(ddi).ReadFrom(mmio_space).data_in(); // Sleep for the rest of the cycle zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs / 2))); i2c_scl(mmio_space, ddi, 0); return ack; } } // namespace namespace i915 { // Per the GMBUS Controller Programming Interface section of the Intel docs, GMBUS does not // directly support segment pointer addressing. Instead, the segment pointer needs to be // set by bit-banging the GPIO pins. bool GMBusI2c::SetDdcSegment(uint8_t segment_num) { // Reset the clock and data lines i2c_scl(mmio_space_, ddi_, 0); i2c_sda(mmio_space_, ddi_, 0); if (!i2c_scl(mmio_space_, ddi_, 1)) { return false; } i2c_sda(mmio_space_, ddi_, 1); // Wait for the rest of the cycle zx_nanosleep(zx_deadline_after(ZX_USEC(kI2cClockUs / 2))); // Send a start condition i2c_sda(mmio_space_, ddi_, 0); i2c_scl(mmio_space_, ddi_, 0); // Send the segment register index and the segment number uint8_t segment_write_command = kDdcSegmentAddress << 1; if (!i2c_send_byte(mmio_space_, ddi_, segment_write_command) || !i2c_send_byte(mmio_space_, ddi_, segment_num)) { return false; } // Set the data and clock lines high to prepare for the GMBus start i2c_sda(mmio_space_, ddi_, 1); return i2c_scl(mmio_space_, ddi_, 1); } zx_status_t GMBusI2c::I2cTransact(i2c_impl_op_t* ops, size_t size) { // The GMBus register is a limited interface to the i2c bus - it doesn't support complex // transactions like setting the E-DDC segment. For now, providing a special-case interface // for reading the E-DDC is good enough. fbl::AutoLock lock(&lock_); zx_status_t fail_res = ZX_ERR_IO; bool gmbus_set = false; for (unsigned i = 0; i < size; i++) { i2c_impl_op_t* op = ops + i; if (op->address == kDdcSegmentAddress && !op->is_read && op->length == 1) { registers::GMBus0::Get().FromValue(0).WriteTo(mmio_space_); gmbus_set = false; if (!SetDdcSegment(*static_cast(op->buf))) { goto fail; } } else if (op->address == kDdcDataAddress) { if (!gmbus_set) { auto gmbus0 = registers::GMBus0::Get().FromValue(0); gmbus0.set_pin_pair_select(ddi_to_pin(ddi_)); gmbus0.WriteTo(mmio_space_); gmbus_set = true; } uint8_t* buf = static_cast(op->buf); uint8_t len = static_cast(op->length); if (op->is_read ? GMBusRead(kDdcDataAddress, buf, len) : GMBusWrite(kDdcDataAddress, buf, len)) { if (!WAIT_ON_MS(registers::GMBus2::Get().ReadFrom(mmio_space_).wait(), 10)) { LOG_TRACE("Transition to wait phase timed out\n"); goto fail; } } else { goto fail; } } else { fail_res = ZX_ERR_NOT_SUPPORTED; goto fail; } if (op->stop) { if (!I2cFinish()) { goto fail; } gmbus_set = false; } } return ZX_OK; fail: if (!I2cClearNack()) { LOG_TRACE("Failed to clear nack\n"); } return fail_res; } bool GMBusI2c::GMBusWrite(uint8_t addr, const uint8_t* buf, uint8_t size) { unsigned idx = 0; write_gmbus3(mmio_space_, buf, size, idx); idx += 4; auto gmbus1 = registers::GMBus1::Get().FromValue(0); gmbus1.set_sw_ready(1); gmbus1.set_bus_cycle_wait(1); gmbus1.set_total_byte_count(size); gmbus1.set_slave_register_addr(addr); gmbus1.WriteTo(mmio_space_); while (idx < size) { if (!I2cWaitForHwReady()) { return false; } write_gmbus3(mmio_space_, buf, size, idx); idx += 4; } // One more wait to ensure we're ready when we leave the function return I2cWaitForHwReady(); } bool GMBusI2c::GMBusRead(uint8_t addr, uint8_t* buf, uint8_t size) { auto gmbus1 = registers::GMBus1::Get().FromValue(0); gmbus1.set_sw_ready(1); gmbus1.set_bus_cycle_wait(1); gmbus1.set_total_byte_count(size); gmbus1.set_slave_register_addr(addr); gmbus1.set_read_op(1); gmbus1.WriteTo(mmio_space_); unsigned idx = 0; while (idx < size) { if (!I2cWaitForHwReady()) { return false; } read_gmbus3(mmio_space_, buf, size, idx); idx += 4; } return true; } bool GMBusI2c::I2cFinish() { auto gmbus1 = registers::GMBus1::Get().FromValue(0); gmbus1.set_bus_cycle_stop(1); gmbus1.set_sw_ready(1); gmbus1.WriteTo(mmio_space_); bool idle = WAIT_ON_MS(!registers::GMBus2::Get().ReadFrom(mmio_space_).active(), 100); auto gmbus0 = registers::GMBus0::Get().FromValue(0); gmbus0.set_pin_pair_select(0); gmbus0.WriteTo(mmio_space_); if (!idle) { LOG_TRACE("hdmi: GMBus i2c failed to go idle\n"); } return idle; } bool GMBusI2c::I2cWaitForHwReady() { auto gmbus2 = registers::GMBus2::Get().FromValue(0); if (!WAIT_ON_MS({ gmbus2.ReadFrom(mmio_space_); gmbus2.nack() || gmbus2.hw_ready(); }, 50)) { LOG_TRACE("hdmi: GMBus i2c wait for hwready timeout\n"); return false; } if (gmbus2.nack()) { LOG_TRACE("hdmi: GMBus i2c got nack\n"); return false; } return true; } bool GMBusI2c::I2cClearNack() { I2cFinish(); if (!WAIT_ON_MS(!registers::GMBus2::Get().ReadFrom(mmio_space_).active(), 10)) { LOG_TRACE("hdmi: GMBus i2c failed to clear active nack\n"); return false; } // Set/clear sw clear int to reset the bus auto gmbus1 = registers::GMBus1::Get().FromValue(0); gmbus1.set_sw_clear_int(1); gmbus1.WriteTo(mmio_space_); gmbus1.set_sw_clear_int(0); gmbus1.WriteTo(mmio_space_); // Reset GMBus0 auto gmbus0 = registers::GMBus0::Get().FromValue(0); gmbus0.WriteTo(mmio_space_); return true; } GMBusI2c::GMBusI2c(registers::Ddi ddi) : ddi_(ddi) { ZX_ASSERT(mtx_init(&lock_, mtx_plain) == thrd_success); } } // namespace i915 // Modesetting functions namespace { // See the section on HDMI/DVI programming in intel-gfx-prm-osrc-skl-vol12-display.pdf // for documentation on this algorithm. static bool calculate_params(uint32_t symbol_clock_khz, uint16_t* dco_int, uint16_t* dco_frac, uint8_t* q, uint8_t* q_mode, uint8_t* k, uint8_t* p, uint8_t* cf) { uint8_t even_candidates[36] = { 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 30, 32, 36, 40, 42, 44, 48, 52, 54, 56, 60, 64, 66, 68, 70, 72, 76, 78, 80, 84, 88, 90, 92, 96, 98 }; uint8_t odd_candidates[7] = { 3, 5, 7, 9, 15, 21, 35 }; uint32_t candidate_freqs[3] = { 8400000, 9000000, 9600000 }; uint32_t chosen_central_freq = 0; uint8_t chosen_divisor = 0; uint64_t afe_clock = symbol_clock_khz * 5; uint32_t best_deviation = 60; // Deviation in .1% intervals for (int parity = 0; parity < 2; parity++) { uint8_t* candidates; uint8_t num_candidates; if (parity) { candidates = odd_candidates; num_candidates = sizeof(odd_candidates) / sizeof(*odd_candidates); } else { candidates = even_candidates; num_candidates = sizeof(even_candidates) / sizeof(*even_candidates); } for (unsigned i = 0; i < sizeof(candidate_freqs) / sizeof(*candidate_freqs); i++) { uint32_t candidate_freq = candidate_freqs[i]; for (unsigned j = 0; j < num_candidates; j++) { uint8_t candidate_divisor = candidates[j]; uint64_t dco_freq = candidate_divisor * afe_clock; if (dco_freq > candidate_freq) { uint32_t deviation = static_cast( 1000 * (dco_freq - candidate_freq) / candidate_freq); // positive deviation must be < 1% if (deviation < 10 && deviation < best_deviation) { best_deviation = deviation; chosen_central_freq = candidate_freq; chosen_divisor = candidate_divisor; } } else { uint32_t deviation = static_cast( 1000 * (candidate_freq - dco_freq) / candidate_freq); if (deviation < best_deviation) { best_deviation = deviation; chosen_central_freq = candidate_freq; chosen_divisor = candidate_divisor; } } } } if (chosen_divisor) { break; } } if (!chosen_divisor) { return false; } uint8_t p0, p1, p2; p0 = p1 = p2 = 1; if (chosen_divisor % 2 == 0) { uint8_t chosen_divisor1 = chosen_divisor / 2; if (chosen_divisor1 == 1 || chosen_divisor1 == 2 || chosen_divisor1 == 3 || chosen_divisor1 == 5) { p0 = 2; p2 = chosen_divisor1; } else if (chosen_divisor1 % 2 == 0) { p0 = 2; p1 = chosen_divisor1 / 2; p2 = 2; } else if (chosen_divisor1 % 3 == 0) { p0 = 3; p1 = chosen_divisor1 / 3; p2 = 2; } else if (chosen_divisor1 % 7 == 0) { p0 = 7; p1 = chosen_divisor1 / 7; p2 = 2; } } else if (chosen_divisor == 3 || chosen_divisor == 9) { p0 = 3; p2 = chosen_divisor / 3; } else if (chosen_divisor == 5 || chosen_divisor == 7) { p0 = chosen_divisor; } else if (chosen_divisor == 15) { p0 = 3; p2 = 5; } else if (chosen_divisor == 21) { p0 = 7; p2 = 3; } else if (chosen_divisor == 35) { p0 = 7; p2 = 5; } *q = p1; *q_mode = p1 != 1; if (p2 == 5) { *k = registers::DpllConfig2::kKdiv5; } else if (p2 == 2) { *k = registers::DpllConfig2::kKdiv2; } else if (p2 == 3) { *k = registers::DpllConfig2::kKdiv3; } else { // p2 == 1 *k = registers::DpllConfig2::kKdiv1; } if (p0 == 1) { *p = registers::DpllConfig2::kPdiv1; } else if (p0 == 2) { *p = registers::DpllConfig2::kPdiv2; } else if (p0 == 3) { *p = registers::DpllConfig2::kPdiv3; } else { // p0 == 7 *p = registers::DpllConfig2::kPdiv7; } uint64_t dco_freq_khz = chosen_divisor * afe_clock; *dco_int = static_cast((dco_freq_khz / 1000) / 24); *dco_frac = static_cast( ((dco_freq_khz * (1 << 15) / 24) - ((*dco_int * 1000L) * (1 << 15))) / 1000); if (chosen_central_freq == 9600000) { *cf = registers::DpllConfig2::k9600Mhz; } else if (chosen_central_freq == 9000000) { *cf = registers::DpllConfig2::k9000Mhz; } else { // chosen_central_freq == 8400000 *cf = registers::DpllConfig2::k8400Mhz; } return true; } } // namespace namespace i915 { HdmiDisplay::HdmiDisplay(Controller* controller, uint64_t id, registers::Ddi ddi) : DisplayDevice(controller, id, ddi) { } bool HdmiDisplay::Query() { // HDMI isn't supported on these DDIs if (ddi_to_pin(ddi()) == -1) { return false; } // Reset the GMBus registers and disable GMBus interrupts registers::GMBus0::Get().FromValue(0).WriteTo(mmio_space()); registers::GMBus4::Get().FromValue(0).WriteTo(mmio_space()); // The only way to tell if an HDMI monitor is actually connected is // to try to read from it over I2C. for (unsigned i = 0; i < 3; i++) { uint8_t test_data = 0; i2c_impl_op_t op = { .address = kDdcDataAddress, .buf = &test_data, .length = 1, .is_read = true, .stop = 1, }; registers::GMBus0::Get().FromValue(0).WriteTo(mmio_space()); if (controller()->Transact(i2c_bus_id(), &op, 1) == ZX_OK) { LOG_TRACE("Found a hdmi/dvi monitor\n"); return true; } zx_nanosleep(zx_deadline_after(ZX_MSEC(5))); } LOG_TRACE("Failed to query hdmi i2c bus\n"); return false; } bool HdmiDisplay::InitDdi() { // All the init happens during modeset return true; } bool HdmiDisplay::ComputeDpllState(uint32_t pixel_clock_10khz, struct dpll_state* config) { config->is_hdmi = true; return calculate_params( pixel_clock_10khz * 10, &config->hdmi.dco_int, &config->hdmi.dco_frac, &config->hdmi.q, &config->hdmi.q_mode, &config->hdmi.k, &config->hdmi.p, &config->hdmi.cf); } bool HdmiDisplay::DdiModeset(const display_mode_t& mode, registers::Pipe pipe, registers::Trans trans) { controller()->ResetPipe(pipe); controller()->ResetTrans(trans); controller()->ResetDdi(ddi()); // Calculate and the HDMI DPLL parameters dpll_state_t state; state.is_hdmi = true; if (!calculate_params(mode.pixel_clock_10khz * 10, &state.hdmi.dco_int, &state.hdmi.dco_frac, &state.hdmi.q, &state.hdmi.q_mode, &state.hdmi.k, &state.hdmi.p, &state.hdmi.cf)) { LOG_ERROR("hdmi: failed to calculate clock params\n"); return false; } registers::Dpll dpll = controller()->SelectDpll(false, state); if (dpll == registers::DPLL_INVALID) { return false; } auto dpll_enable = registers::DpllEnable::Get(dpll).ReadFrom(mmio_space()); if (!dpll_enable.enable_dpll()) { // Set the the DPLL control settings auto dpll_ctrl1 = registers::DpllControl1::Get().ReadFrom(mmio_space()); dpll_ctrl1.dpll_hdmi_mode(dpll).set(1); dpll_ctrl1.dpll_override(dpll).set(1); dpll_ctrl1.dpll_ssc_enable(dpll).set(0); dpll_ctrl1.WriteTo(mmio_space()); dpll_ctrl1.ReadFrom(mmio_space()); // Set the DCO frequency auto dpll_cfg1 = registers::DpllConfig1::Get(dpll).FromValue(0); dpll_cfg1.set_frequency_enable(1); dpll_cfg1.set_dco_integer(state.hdmi.dco_int); dpll_cfg1.set_dco_fraction(state.hdmi.dco_frac); dpll_cfg1.WriteTo(mmio_space()); dpll_cfg1.ReadFrom(mmio_space()); // Set the divisors and central frequency auto dpll_cfg2 = registers::DpllConfig2::Get(dpll).FromValue(0); dpll_cfg2.set_qdiv_ratio(state.hdmi.q); dpll_cfg2.set_qdiv_mode(state.hdmi.q_mode); dpll_cfg2.set_kdiv_ratio(state.hdmi.k); dpll_cfg2.set_pdiv_ratio(state.hdmi.p); dpll_cfg2.set_central_freq(state.hdmi.cf); dpll_cfg2.WriteTo(mmio_space()); dpll_cfg2.ReadFrom(mmio_space()); // Posting read // Enable and wait for the DPLL dpll_enable.set_enable_dpll(1); dpll_enable.WriteTo(mmio_space()); if (!WAIT_ON_MS(registers::DpllStatus ::Get().ReadFrom(mmio_space()).dpll_lock(dpll).get(), 5)) { LOG_ERROR("hdmi: DPLL failed to lock\n"); return false; } } // Direct the DPLL to the DDI auto dpll_ctrl2 = registers::DpllControl2::Get().ReadFrom(mmio_space()); dpll_ctrl2.ddi_select_override(ddi()).set(1); dpll_ctrl2.ddi_clock_off(ddi()).set(0); dpll_ctrl2.ddi_clock_select(ddi()).set(dpll); dpll_ctrl2.WriteTo(mmio_space()); // Enable DDI IO power and wait for it auto pwc2 = registers::PowerWellControl2::Get().ReadFrom(mmio_space()); pwc2.ddi_io_power_request(ddi()).set(1); pwc2.WriteTo(mmio_space()); if (!WAIT_ON_US(registers::PowerWellControl2 ::Get().ReadFrom(mmio_space()).ddi_io_power_state(ddi()).get(), 20)) { LOG_ERROR("hdmi: failed to enable IO power for ddi\n"); return false; } return true; } bool HdmiDisplay::PipeConfigPreamble(const display_mode_t& mode, registers::Pipe pipe, registers::Trans trans) { registers::TranscoderRegs trans_regs(trans); // Configure Transcoder Clock Select auto trans_clk_sel = trans_regs.ClockSelect().ReadFrom(mmio_space()); trans_clk_sel.set_trans_clock_select(ddi() + 1); trans_clk_sel.WriteTo(mmio_space()); return true; } bool HdmiDisplay::PipeConfigEpilogue(const display_mode_t& mode, registers::Pipe pipe, registers::Trans trans) { registers::TranscoderRegs trans_regs(trans); auto ddi_func = trans_regs.DdiFuncControl().ReadFrom(mmio_space()); ddi_func.set_trans_ddi_function_enable(1); ddi_func.set_ddi_select(ddi()); ddi_func.set_trans_ddi_mode_select(is_hdmi() ? ddi_func.kModeHdmi : ddi_func.kModeDvi); ddi_func.set_bits_per_color(ddi_func.k8bbc); ddi_func.set_sync_polarity((!!(mode.flags & MODE_FLAG_VSYNC_POSITIVE)) << 1 | (!!(mode.flags & MODE_FLAG_HSYNC_POSITIVE))); ddi_func.set_port_sync_mode_enable(0); ddi_func.set_dp_vc_payload_allocate(0); ddi_func.WriteTo(mmio_space()); auto trans_conf = trans_regs.Conf().ReadFrom(mmio_space()); trans_conf.set_transcoder_enable(1); trans_conf.set_interlaced_mode(!!(mode.flags & MODE_FLAG_INTERLACED)); trans_conf.WriteTo(mmio_space()); // Configure voltage swing and related IO settings. registers::DdiRegs ddi_regs(ddi()); auto ddi_buf_trans_hi = ddi_regs.DdiBufTransHi(9).ReadFrom(mmio_space()); auto ddi_buf_trans_lo = ddi_regs.DdiBufTransLo(9).ReadFrom(mmio_space()); auto disio_cr_tx_bmu = registers::DisplayIoCtrlRegTxBmu::Get().ReadFrom(mmio_space()); // kUseDefaultIdx always fails the idx-in-bounds check, so no additional handling is needed uint8_t idx = controller()->igd_opregion().GetHdmiBufferTranslationIndex(ddi()); uint8_t i_boost_override = controller()->igd_opregion().GetIBoost(ddi(), false /* is_dp */); const ddi_buf_trans_entry* entries; uint8_t default_iboost; if (is_skl_y(controller()->device_id()) || is_kbl_y(controller()->device_id())) { entries = hdmi_ddi_buf_trans_skl_y; if (idx >= fbl::count_of(hdmi_ddi_buf_trans_skl_y)) { idx = 8; // Default index } default_iboost = 3; } else { entries = hdmi_ddi_buf_trans_skl_uhs; if (idx >= fbl::count_of(hdmi_ddi_buf_trans_skl_uhs)) { idx = 8; // Default index } default_iboost = 1; } ddi_buf_trans_hi.set_reg_value(entries[idx].high_dword); ddi_buf_trans_lo.set_reg_value(entries[idx].low_dword); if (i_boost_override) { ddi_buf_trans_lo.set_balance_leg_enable(1); } disio_cr_tx_bmu.set_disable_balance_leg(0); disio_cr_tx_bmu.tx_balance_leg_select(ddi()).set( i_boost_override ? i_boost_override : default_iboost); ddi_buf_trans_hi.WriteTo(mmio_space()); ddi_buf_trans_lo.WriteTo(mmio_space()); disio_cr_tx_bmu.WriteTo(mmio_space()); // Configure and enable DDI_BUF_CTL auto ddi_buf_ctl = ddi_regs.DdiBufControl().ReadFrom(mmio_space()); ddi_buf_ctl.set_ddi_buffer_enable(1); ddi_buf_ctl.WriteTo(mmio_space()); return true; } bool HdmiDisplay::CheckPixelRate(uint64_t pixel_rate) { // Pixel rates of 300M/165M pixels per second for HDMI/DVI. The Intel docs state // that the maximum link bit rate of an HDMI port is 3GHz, not 3.4GHz that would // be expected based on the HDMI spec. if ((is_hdmi() ? 300000000 : 165000000) < pixel_rate) { return false; } dpll_state_t test_state; return ComputeDpllState(static_cast(pixel_rate / 10000 /* 10khz */), &test_state); } } // namespace i915