blob: dcf0dc3b9350dd8981c99d70044c35c52676af42 [file] [log] [blame]
// 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 "src/graphics/display/drivers/intel-i915/dp-display.h"
#include <endian.h>
#include <lib/ddk/driver.h>
#include <math.h>
#include <string.h>
#include <zircon/assert.h>
#include <algorithm>
#include <iterator>
#include <variant>
#include <fbl/string_printf.h>
#include "src/graphics/display/drivers/intel-i915/dpll.h"
#include "src/graphics/display/drivers/intel-i915/intel-i915.h"
#include "src/graphics/display/drivers/intel-i915/macros.h"
#include "src/graphics/display/drivers/intel-i915/pci-ids.h"
#include "src/graphics/display/drivers/intel-i915/registers-ddi.h"
#include "src/graphics/display/drivers/intel-i915/registers-dpll.h"
#include "src/graphics/display/drivers/intel-i915/registers-pipe.h"
#include "src/graphics/display/drivers/intel-i915/registers-transcoder.h"
#include "src/graphics/display/drivers/intel-i915/registers.h"
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
namespace i915 {
namespace {
constexpr uint32_t kBitsPerPixel = 24; // kPixelFormat
// Recommended DDI buffer translation programming values
struct ddi_buf_trans_entry {
uint32_t high_dword;
uint32_t low_dword;
};
const ddi_buf_trans_entry dp_ddi_buf_trans_skl_hs[9] = {
{0x000000a0, 0x00002016}, {0x0000009b, 0x00005012}, {0x00000088, 0x00007011},
{0x000000c0, 0x80009010}, {0x0000009b, 0x00002016}, {0x00000088, 0x00005012},
{0x000000c0, 0x80007011}, {0x000000df, 0x00002016}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry dp_ddi_buf_trans_skl_y[9] = {
{0x000000a2, 0x00000018}, {0x00000088, 0x00005012}, {0x000000cd, 0x80007011},
{0x000000c0, 0x80009010}, {0x0000009d, 0x00000018}, {0x000000c0, 0x80005012},
{0x000000c0, 0x80007011}, {0x00000088, 0x00000018}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry dp_ddi_buf_trans_skl_u[9] = {
{0x000000a2, 0x0000201b}, {0x00000088, 0x00005012}, {0x000000cd, 0x80007011},
{0x000000c0, 0x80009010}, {0x0000009d, 0x0000201b}, {0x000000c0, 0x80005012},
{0x000000c0, 0x80007011}, {0x00000088, 0x00002016}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry dp_ddi_buf_trans_kbl_hs[9] = {
{0x000000a0, 0x00002016}, {0x0000009b, 0x00005012}, {0x00000088, 0x00007011},
{0x000000c0, 0x80009010}, {0x0000009b, 0x00002016}, {0x00000088, 0x00005012},
{0x000000c0, 0x80007011}, {0x00000097, 0x00002016}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry dp_ddi_buf_trans_kbl_y[9] = {
{0x000000a1, 0x00001017}, {0x00000088, 0x00005012}, {0x000000cd, 0x80007011},
{0x000000c0, 0x8000800f}, {0x0000009d, 0x00001017}, {0x000000c0, 0x80005012},
{0x000000c0, 0x80007011}, {0x0000004c, 0x00001017}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry dp_ddi_buf_trans_kbl_u[9] = {
{0x000000a1, 0x0000201b}, {0x00000088, 0x00005012}, {0x000000cd, 0x80007011},
{0x000000c0, 0x80009010}, {0x0000009d, 0x0000201b}, {0x000000c0, 0x80005012},
{0x000000c0, 0x80007011}, {0x0000004f, 0x00002016}, {0x000000c0, 0x80005012},
};
const ddi_buf_trans_entry edp_ddi_buf_trans_skl_hs[10] = {
{0x000000a8, 0x00000018}, {0x000000a9, 0x00004013}, {0x000000a2, 0x00007011},
{0x0000009c, 0x00009010}, {0x000000a9, 0x00000018}, {0x000000a2, 0x00006013},
{0x000000a6, 0x00007011}, {0x000000ab, 0x00000018}, {0x0000009f, 0x00007013},
{0x000000df, 0x00000018},
};
const ddi_buf_trans_entry edp_ddi_buf_trans_skl_y[10] = {
{0x000000a8, 0x00000018}, {0x000000ab, 0x00004013}, {0x000000a4, 0x00007011},
{0x000000df, 0x00009010}, {0x000000aa, 0x00000018}, {0x000000a4, 0x00006013},
{0x0000009d, 0x00007011}, {0x000000a0, 0x00000018}, {0x000000df, 0x00006012},
{0x0000008a, 0x00000018},
};
const ddi_buf_trans_entry edp_ddi_buf_trans_skl_u[10] = {
{0x000000a8, 0x00000018}, {0x000000a9, 0x00004013}, {0x000000a2, 0x00007011},
{0x0000009c, 0x00009010}, {0x000000a9, 0x00000018}, {0x000000a2, 0x00006013},
{0x000000a6, 0x00007011}, {0x000000ab, 0x00002016}, {0x0000009f, 0x00005013},
{0x000000df, 0x00000018},
};
void GetDpDdiBufTransEntries(uint16_t device_id, const ddi_buf_trans_entry** entries,
uint8_t* i_boost, unsigned* count) {
if (is_skl(device_id)) {
if (is_skl_u(device_id)) {
*entries = dp_ddi_buf_trans_skl_u;
*i_boost = 0x1;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_skl_u));
} else if (is_skl_y(device_id)) {
*entries = dp_ddi_buf_trans_skl_y;
*i_boost = 0x3;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_skl_y));
} else {
*entries = dp_ddi_buf_trans_skl_hs;
*i_boost = 0x1;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_skl_hs));
}
} else if (is_kbl(device_id)) {
if (is_kbl_u(device_id)) {
*entries = dp_ddi_buf_trans_kbl_u;
*i_boost = 0x1;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_kbl_u));
} else if (is_kbl_y(device_id)) {
*entries = dp_ddi_buf_trans_kbl_y;
*i_boost = 0x3;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_kbl_y));
} else {
*entries = dp_ddi_buf_trans_kbl_hs;
*i_boost = 0x3;
*count = static_cast<unsigned>(std::size(dp_ddi_buf_trans_kbl_hs));
}
} else {
zxlogf(ERROR, "Unrecognized i915 device id: %#.4x", device_id);
*count = 0;
*i_boost = 0;
}
}
void GetEdpDdiBufTransEntries(uint16_t device_id, const ddi_buf_trans_entry** entries,
unsigned* count) {
if (is_skl_u(device_id) || is_kbl_u(device_id)) {
*entries = edp_ddi_buf_trans_skl_u;
*count = static_cast<int>(std::size(edp_ddi_buf_trans_skl_u));
} else if (is_skl_y(device_id) || is_kbl_y(device_id)) {
*entries = edp_ddi_buf_trans_skl_y;
*count = static_cast<int>(std::size(edp_ddi_buf_trans_skl_y));
} else {
*entries = edp_ddi_buf_trans_skl_hs;
*count = static_cast<int>(std::size(edp_ddi_buf_trans_skl_hs));
}
}
// Aux port functions
// 4-bit request type in Aux channel request messages.
enum {
DP_REQUEST_I2C_WRITE = 0,
DP_REQUEST_I2C_READ = 1,
DP_REQUEST_NATIVE_WRITE = 8,
DP_REQUEST_NATIVE_READ = 9,
};
// 4-bit statuses in Aux channel reply messages.
enum {
DP_REPLY_AUX_ACK = 0,
DP_REPLY_AUX_NACK = 1,
DP_REPLY_AUX_DEFER = 2,
DP_REPLY_I2C_NACK = 4,
DP_REPLY_I2C_DEFER = 8,
};
std::string DpcdRevisionToString(dpcd::Revision rev) {
switch (rev) {
case dpcd::Revision::k1_0:
return "DPCD r1.0";
case dpcd::Revision::k1_1:
return "DPCD r1.1";
case dpcd::Revision::k1_2:
return "DPCD r1.2";
case dpcd::Revision::k1_3:
return "DPCD r1.3";
case dpcd::Revision::k1_4:
return "DPCD r1.4";
}
return "unknown";
}
std::string EdpDpcdRevisionToString(dpcd::EdpRevision rev) {
switch (rev) {
case dpcd::EdpRevision::k1_1:
return "eDP v1.1 or lower";
case dpcd::EdpRevision::k1_2:
return "eDP v1.2";
case dpcd::EdpRevision::k1_3:
return "eDP v1.3";
case dpcd::EdpRevision::k1_4:
return "eDP v1.4";
case dpcd::EdpRevision::k1_4a:
return "eDP v1.4a";
case dpcd::EdpRevision::k1_4b:
return "eDP v1.4b";
}
return "unknown";
}
} // namespace
// This represents a message sent over DisplayPort's Aux channel, including
// reply messages.
class DpAuxMessage {
public:
// Sizes in bytes. DisplayPort Aux messages are quite small.
static constexpr uint32_t kMaxTotalSize = 20;
static constexpr uint32_t kMaxBodySize = 16;
uint8_t data[kMaxTotalSize];
uint32_t size;
// Fill out the header of a DisplayPort Aux message. For write operations,
// |body_size| is the size of the body of the message to send. For read
// operations, |body_size| is the size of our receive buffer.
bool SetDpAuxHeader(uint32_t addr, uint32_t dp_cmd, uint32_t body_size) {
if (body_size > DpAuxMessage::kMaxBodySize) {
zxlogf(WARNING, "DP aux: Message too large");
return false;
}
// Addresses should fit into 20 bits.
if (addr >= (1 << 20)) {
zxlogf(WARNING, "DP aux: Address is too large: 0x%x", addr);
return false;
}
// For now, we don't handle messages with empty bodies. (However, they
// can be used for checking whether there is an I2C device at a given
// address.)
if (body_size == 0) {
zxlogf(WARNING, "DP aux: Empty message not supported");
return false;
}
data[0] = static_cast<uint8_t>((dp_cmd << 4) | ((addr >> 16) & 0xf));
data[1] = static_cast<uint8_t>(addr >> 8);
data[2] = static_cast<uint8_t>(addr);
// For writes, the size of the message will be encoded twice:
// * The msg->size field contains the total message size (header and
// body).
// * If the body of the message is non-empty, the header contains an
// extra field specifying the body size (in bytes minus 1).
// For reads, the message to send is a header only.
size = 4;
data[3] = static_cast<uint8_t>(body_size - 1);
return true;
}
};
zx_status_t DpAux::SendDpAuxMsg(const DpAuxMessage& request, DpAuxMessage* reply) {
registers::DdiRegs ddi_regs(ddi_);
uint32_t data_reg = ddi_regs.DdiAuxData().addr();
// Write the outgoing message to the hardware.
for (uint32_t offset = 0; offset < request.size; offset += 4) {
// For some reason intel made these data registers big endian...
const uint32_t* data = reinterpret_cast<const uint32_t*>(request.data + offset);
mmio_space_->Write<uint32_t>(htobe32(*data), data_reg + offset);
}
auto status = ddi_regs.DdiAuxControl().ReadFrom(mmio_space_);
status.set_message_size(request.size);
// Reset R/W Clear bits
status.set_done(1);
status.set_timeout(1);
status.set_rcv_error(1);
// The documentation says to not use setting 0 (400us), so use 3 (1600us).
status.set_timeout_timer_value(3);
// TODO(fxbug.dev/31313): Support interrupts
status.set_interrupt_on_done(1);
// Send busy starts the transaction
status.set_send_busy(1);
status.WriteTo(mmio_space_);
// Poll for the reply message.
const int kNumTries = 10000;
for (int tries = 0; tries < kNumTries; ++tries) {
auto status = ddi_regs.DdiAuxControl().ReadFrom(mmio_space_);
if (!status.send_busy()) {
if (status.timeout()) {
return ZX_ERR_TIMED_OUT;
}
if (status.rcv_error()) {
zxlogf(DEBUG, "DP aux: rcv error");
return ZX_ERR_IO;
}
if (!status.done()) {
continue;
}
reply->size = status.message_size();
if (!reply->size || reply->size > DpAuxMessage::kMaxTotalSize) {
zxlogf(TRACE, "DP aux: Invalid reply size %d", reply->size);
return ZX_ERR_IO;
}
// Read the reply message from the hardware.
for (uint32_t offset = 0; offset < reply->size; offset += 4) {
// For some reason intel made these data registers big endian...
*reinterpret_cast<uint32_t*>(reply->data + offset) =
be32toh(mmio_space_->Read32(data_reg + offset));
}
return ZX_OK;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(1)));
}
zxlogf(TRACE, "DP aux: No reply after %d tries", kNumTries);
return ZX_ERR_TIMED_OUT;
}
zx_status_t DpAux::SendDpAuxMsgWithRetry(const DpAuxMessage& request, DpAuxMessage* reply) {
// If the DisplayPort sink device isn't ready to handle an Aux message,
// it can return an AUX_DEFER reply, which means we should retry the
// request. The spec added a requirement for >=7 defer retries in v1.3,
// but there are no requirements before that nor is there a max value. 16
// retries is pretty arbitrary and might need to be increased for slower
// displays.
const int kMaxDefers = 16;
// Per table 2-43 in v1.1a, we need to retry >3 times, since some
// DisplayPort sink devices time out on the first DP aux request
// but succeed on later requests.
const int kMaxTimeouts = 5;
unsigned defers_seen = 0;
unsigned timeouts_seen = 0;
for (;;) {
zx_status_t res = SendDpAuxMsg(request, reply);
if (res != ZX_OK) {
if (res == ZX_ERR_TIMED_OUT) {
if (++timeouts_seen == kMaxTimeouts) {
zxlogf(DEBUG, "DP aux: Got too many timeouts (%d)", kMaxTimeouts);
return ZX_ERR_TIMED_OUT;
}
// Retry on timeout.
continue;
}
// We do not retry if sending the raw message failed for
// an unexpected reason.
return ZX_ERR_IO;
}
uint8_t header_byte = reply->data[0];
uint8_t padding = header_byte & 0xf;
uint8_t status = static_cast<uint8_t>(header_byte >> 4);
// Sanity check: The padding should be zero. If it's not, we
// shouldn't return an error, in case this space gets used for some
// later extension to the protocol. But report it, in case this
// indicates some problem.
if (padding) {
zxlogf(INFO, "DP aux: Reply header padding is non-zero (header byte: 0x%x)", header_byte);
}
switch (status) {
case DP_REPLY_AUX_ACK:
// The AUX_ACK implies that we got an I2C ACK too.
return ZX_OK;
case DP_REPLY_AUX_DEFER:
if (++defers_seen == kMaxDefers) {
zxlogf(TRACE, "DP aux: Received too many AUX DEFERs (%d)", kMaxDefers);
return ZX_ERR_TIMED_OUT;
}
// Go around the loop again to retry.
continue;
case DP_REPLY_AUX_NACK:
zxlogf(TRACE, "DP aux: Reply was not an ack (got AUX_NACK)");
return ZX_ERR_IO_REFUSED;
case DP_REPLY_I2C_NACK:
zxlogf(TRACE, "DP aux: Reply was not an ack (got I2C_NACK)");
return ZX_ERR_IO_REFUSED;
case DP_REPLY_I2C_DEFER:
// TODO(fxbug.dev/31313): Implement handling of I2C_DEFER.
zxlogf(TRACE, "DP aux: Received I2C_DEFER (not implemented)");
return ZX_ERR_NEXT;
default:
// We got a reply that is not defined by the DisplayPort spec.
zxlogf(TRACE, "DP aux: Unrecognized reply (header byte: 0x%x)", header_byte);
return ZX_ERR_IO;
}
}
}
zx_status_t DpAux::DpAuxRead(uint32_t dp_cmd, uint32_t addr, uint8_t* buf, size_t size) {
while (size > 0) {
uint32_t chunk_size = static_cast<uint32_t>(MIN(size, DpAuxMessage::kMaxBodySize));
size_t bytes_read = 0;
zx_status_t status = DpAuxReadChunk(dp_cmd, addr, buf, chunk_size, &bytes_read);
if (status != ZX_OK) {
return status;
}
if (bytes_read == 0) {
// We failed to make progress on the last call. To avoid the
// risk of getting an infinite loop from that happening
// continually, we return.
return ZX_ERR_IO;
}
buf += bytes_read;
size -= bytes_read;
}
return ZX_OK;
}
zx_status_t DpAux::DpAuxReadChunk(uint32_t dp_cmd, uint32_t addr, uint8_t* buf, uint32_t size_in,
size_t* size_out) {
DpAuxMessage msg;
DpAuxMessage reply;
if (!msg.SetDpAuxHeader(addr, dp_cmd, size_in)) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = SendDpAuxMsgWithRetry(msg, &reply);
if (status != ZX_OK) {
return status;
}
size_t bytes_read = reply.size - 1;
if (bytes_read > size_in) {
zxlogf(WARNING, "DP aux read: Reply was larger than requested");
return ZX_ERR_IO;
}
memcpy(buf, &reply.data[1], bytes_read);
*size_out = bytes_read;
return ZX_OK;
}
zx_status_t DpAux::DpAuxWrite(uint32_t dp_cmd, uint32_t addr, const uint8_t* buf, size_t size) {
// Implement this if it's ever needed
ZX_ASSERT_MSG(size <= 16, "message too large");
DpAuxMessage msg;
DpAuxMessage reply;
if (!msg.SetDpAuxHeader(addr, dp_cmd, static_cast<uint32_t>(size))) {
return ZX_ERR_INVALID_ARGS;
}
memcpy(&msg.data[4], buf, size);
msg.size = static_cast<uint32_t>(size + 4);
zx_status_t status = SendDpAuxMsgWithRetry(msg, &reply);
if (status != ZX_OK) {
return status;
}
// TODO(fxbug.dev/31313): Handle the case where the hardware did a short write,
// for which we could send the remaining bytes.
if (reply.size != 1) {
zxlogf(WARNING, "DP aux write: Unexpected reply size");
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t DpAux::I2cTransact(const i2c_impl_op_t* ops, size_t count) {
fbl::AutoLock lock(&lock_);
for (unsigned i = 0; i < count; i++) {
uint8_t* buf = static_cast<uint8_t*>(ops[i].data_buffer);
uint8_t len = static_cast<uint8_t>(ops[i].data_size);
zx_status_t status = ops[i].is_read
? DpAuxRead(DP_REQUEST_I2C_READ, ops[i].address, buf, len)
: DpAuxWrite(DP_REQUEST_I2C_WRITE, ops[i].address, buf, len);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
bool DpAux::DpcdRead(uint32_t addr, uint8_t* buf, size_t size) {
fbl::AutoLock lock(&lock_);
constexpr uint32_t kReadAttempts = 3;
for (unsigned i = 0; i < kReadAttempts; i++) {
if (DpAuxRead(DP_REQUEST_NATIVE_READ, addr, buf, size) == ZX_OK) {
return true;
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(5)));
}
return false;
}
bool DpAux::DpcdWrite(uint32_t addr, const uint8_t* buf, size_t size) {
fbl::AutoLock lock(&lock_);
return DpAuxWrite(DP_REQUEST_NATIVE_WRITE, addr, buf, size) == ZX_OK;
}
DpAux::DpAux(registers::Ddi ddi, fdf::MmioBuffer* mmio_space) : ddi_(ddi), mmio_space_(mmio_space) {
ZX_ASSERT(mtx_init(&lock_, mtx_plain) == thrd_success);
}
DpCapabilities::DpCapabilities() { dpcd_.fill(0); }
DpCapabilities::Edp::Edp() { bytes.fill(0); }
DpCapabilities::DpCapabilities(inspect::Node* parent_node) {
dpcd_.fill(0);
node_ = parent_node->CreateChild("dpcd-capabilities");
}
// static
fpromise::result<DpCapabilities> DpCapabilities::Read(DpcdChannel* dp_aux,
inspect::Node* parent_node) {
DpCapabilities caps(parent_node);
if (!dp_aux->DpcdRead(dpcd::DPCD_CAP_START, caps.dpcd_.data(), caps.dpcd_.size())) {
zxlogf(TRACE, "Failed to read dpcd capabilities");
return fpromise::error();
}
auto dsp_present =
caps.dpcd_reg<dpcd::DownStreamPortPresent, dpcd::DPCD_DOWN_STREAM_PORT_PRESENT>();
if (dsp_present.is_branch()) {
auto dsp_count = caps.dpcd_reg<dpcd::DownStreamPortCount, dpcd::DPCD_DOWN_STREAM_PORT_COUNT>();
zxlogf(DEBUG, "Found branch with %d ports", dsp_count.count());
}
if (!dp_aux->DpcdRead(dpcd::DPCD_SINK_COUNT, caps.sink_count_.reg_value_ptr(), 1)) {
zxlogf(ERROR, "Failed to read DisplayPort sink count");
return fpromise::error();
}
caps.max_lane_count_ = caps.dpcd_reg<dpcd::LaneCount, dpcd::DPCD_MAX_LANE_COUNT>();
if (caps.max_lane_count() != 1 && caps.max_lane_count() != 2 && caps.max_lane_count() != 4) {
zxlogf(ERROR, "Unsupported DisplayPort lane count: %u", caps.max_lane_count());
return fpromise::error();
}
if (!caps.ProcessEdp(dp_aux)) {
return fpromise::error();
}
if (!caps.ProcessSupportedLinkRates(dp_aux)) {
return fpromise::error();
}
ZX_ASSERT(!caps.supported_link_rates_mbps_.empty());
caps.PublishInspect();
return fpromise::ok(std::move(caps));
}
bool DpCapabilities::ProcessEdp(DpcdChannel* dp_aux) {
// Check if the Display Control registers reserved for eDP are available.
auto edp_config = dpcd_reg<dpcd::EdpConfigCap, dpcd::DPCD_EDP_CONFIG>();
if (!edp_config.dpcd_display_ctrl_capable()) {
return true;
}
zxlogf(TRACE, "eDP registers are available");
edp_dpcd_.emplace();
if (!dp_aux->DpcdRead(dpcd::DPCD_EDP_CAP_START, edp_dpcd_->bytes.data(),
edp_dpcd_->bytes.size())) {
zxlogf(ERROR, "Failed to read eDP capabilities");
return false;
}
edp_dpcd_->revision = dpcd::EdpRevision(edp_dpcd_at(dpcd::DPCD_EDP_REV));
auto general_cap1 = edp_dpcd_reg<dpcd::EdpGeneralCap1, dpcd::DPCD_EDP_GENERAL_CAP1>();
auto backlight_cap = edp_dpcd_reg<dpcd::EdpBacklightCap, dpcd::DPCD_EDP_BACKLIGHT_CAP>();
edp_dpcd_->backlight_aux_power =
general_cap1.tcon_backlight_adjustment_cap() && general_cap1.backlight_aux_enable_cap();
edp_dpcd_->backlight_aux_brightness =
general_cap1.tcon_backlight_adjustment_cap() && backlight_cap.brightness_aux_set_cap();
return true;
}
bool DpCapabilities::ProcessSupportedLinkRates(DpcdChannel* dp_aux) {
ZX_ASSERT(supported_link_rates_mbps_.empty());
// According to eDP v1.4b, Table 4-24, a device supporting eDP version v1.4 and higher can support
// link rate selection by way of both the DPCD MAX_LINK_RATE register and the "Link Rate Table"
// method via DPCD SUPPORTED_LINK_RATES registers.
//
// The latter method can represent more values than the former (which is limited to only 4
// discrete values). Hence we attempt to use the "Link Rate Table" method first.
use_link_rate_table_ = false;
if (edp_dpcd_ && edp_dpcd_->revision >= dpcd::EdpRevision::k1_4) {
constexpr size_t kBufferSize =
dpcd::DPCD_SUPPORTED_LINK_RATE_END - dpcd::DPCD_SUPPORTED_LINK_RATE_START + 1;
std::array<uint8_t, kBufferSize> link_rates;
if (dp_aux->DpcdRead(dpcd::DPCD_SUPPORTED_LINK_RATE_START, link_rates.data(), kBufferSize)) {
for (size_t i = 0; i < link_rates.size(); i += 2) {
uint16_t value = link_rates[i] | (static_cast<uint16_t>(link_rates[i + 1]) << 8);
// From the eDP specification: "A table entry containing the value 0 indicates that the
// entry and all entries at higher DPCD addressess contain invalid link rates."
if (value == 0) {
break;
}
// Each valid entry indicates a nominal per-lane link rate equal to `value * 200kHz`. We
// convert value to MHz: `value * 200 / 1000 ==> value / 5`.
supported_link_rates_mbps_.push_back(value / 5);
}
}
use_link_rate_table_ = !supported_link_rates_mbps_.empty();
}
// Fall back to the MAX_LINK_RATE register if the Link Rate Table method is not supported.
if (supported_link_rates_mbps_.empty()) {
uint32_t max_link_rate = dpcd_reg<dpcd::LinkBw, dpcd::DPCD_MAX_LINK_RATE>().link_bw();
// All link rates including and below the maximum are supported.
switch (max_link_rate) {
case dpcd::LinkBw::k8100Mbps:
supported_link_rates_mbps_.push_back(8100);
__FALLTHROUGH;
case dpcd::LinkBw::k5400Mbps:
supported_link_rates_mbps_.push_back(5400);
__FALLTHROUGH;
case dpcd::LinkBw::k2700Mbps:
supported_link_rates_mbps_.push_back(2700);
__FALLTHROUGH;
case dpcd::LinkBw::k1620Mbps:
supported_link_rates_mbps_.push_back(1620);
break;
case 0:
zxlogf(ERROR, "Device did not report supported link rates");
return false;
default:
zxlogf(ERROR, "Unsupported max link rate: %u", max_link_rate);
return false;
}
// Make sure the values are in ascending order.
std::reverse(supported_link_rates_mbps_.begin(), supported_link_rates_mbps_.end());
}
return true;
}
void DpCapabilities::PublishInspect() {
node_.RecordString("dpcd_revision", DpcdRevisionToString(dpcd_revision()));
node_.RecordUint("sink_count", sink_count());
node_.RecordUint("max_lane_count", max_lane_count());
{
auto node = node_.CreateUintArray("supported_link_rates_mbps_per_lane",
supported_link_rates_mbps_.size());
for (size_t i = 0; i < supported_link_rates_mbps_.size(); ++i) {
node.Add(i, supported_link_rates_mbps_[i]);
}
node_.Record(std::move(node));
}
{
std::string value =
edp_dpcd_.has_value() ? EdpDpcdRevisionToString(edp_dpcd_->revision) : "not supported";
node_.RecordString("edp_revision", std::move(value));
}
}
bool DpDisplay::DpcdWrite(uint32_t addr, const uint8_t* buf, size_t size) {
return dp_aux_->DpcdWrite(addr, buf, size);
}
bool DpDisplay::DpcdRead(uint32_t addr, uint8_t* buf, size_t size) {
return dp_aux_->DpcdRead(addr, buf, size);
}
// Link training functions
// Tell the sink device to start link training.
bool DpDisplay::DpcdRequestLinkTraining(const dpcd::TrainingPatternSet& tp_set,
const dpcd::TrainingLaneSet lane[]) {
// The DisplayPort spec says that we are supposed to write these
// registers with a single operation: "The AUX CH burst write must be
// used for writing to TRAINING_LANEx_SET bytes of the enabled lanes."
// (From section 3.5.1.3, "Link Training", in v1.1a.)
uint8_t reg_bytes[1 + dp_lane_count_];
reg_bytes[0] = static_cast<uint8_t>(tp_set.reg_value());
for (unsigned i = 0; i < dp_lane_count_; i++) {
reg_bytes[i + 1] = static_cast<uint8_t>(lane[i].reg_value());
}
constexpr int kAddr = dpcd::DPCD_TRAINING_PATTERN_SET;
static_assert(kAddr + 1 == dpcd::DPCD_TRAINING_LANE0_SET, "");
static_assert(kAddr + 2 == dpcd::DPCD_TRAINING_LANE1_SET, "");
static_assert(kAddr + 3 == dpcd::DPCD_TRAINING_LANE2_SET, "");
static_assert(kAddr + 4 == dpcd::DPCD_TRAINING_LANE3_SET, "");
if (!DpcdWrite(kAddr, reg_bytes, 1 + dp_lane_count_)) {
zxlogf(ERROR, "Failure setting TRAINING_PATTERN_SET");
return false;
}
return true;
}
template <uint32_t addr, typename T>
bool DpDisplay::DpcdReadPairedRegs(hwreg::RegisterBase<T, typename T::ValueType>* regs) {
static_assert(addr == dpcd::DPCD_LANE0_1_STATUS || addr == dpcd::DPCD_ADJUST_REQUEST_LANE0_1,
"Bad register address");
uint32_t num_bytes = dp_lane_count_ == 4 ? 2 : 1;
uint8_t reg_byte[num_bytes];
if (!DpcdRead(addr, reg_byte, num_bytes)) {
zxlogf(ERROR, "Failure reading addr %d", addr);
return false;
}
for (unsigned i = 0; i < dp_lane_count_; i++) {
regs[i].set_reg_value(reg_byte[i / 2]);
}
return true;
}
bool DpDisplay::DpcdHandleAdjustRequest(dpcd::TrainingLaneSet* training,
dpcd::AdjustRequestLane* adjust) {
bool voltage_change = false;
uint8_t v = 0;
uint8_t pe = 0;
for (unsigned i = 0; i < dp_lane_count_; i++) {
if (adjust[i].voltage_swing(i).get() > v) {
v = static_cast<uint8_t>(adjust[i].voltage_swing(i).get());
}
if (adjust[i].pre_emphasis(i).get() > pe) {
pe = static_cast<uint8_t>(adjust[i].pre_emphasis(i).get());
}
}
// In the Recommended buffer translation programming for DisplayPort from the intel display
// doc, the max voltage swing is 2/3 for DP/eDP and the max (voltage swing + pre-emphasis) is
// 3. According to the v1.1a of the DP docs, if v + pe is too large then v should be reduced
// to the highest supported value for the pe level (section 3.5.1.3)
static constexpr uint32_t kMaxVPlusPe = 3;
uint8_t max_v = controller()->igd_opregion().IsLowVoltageEdp(ddi()) ? 3 : 2;
if (v + pe > kMaxVPlusPe) {
v = static_cast<uint8_t>(kMaxVPlusPe - pe);
}
if (v > max_v) {
v = max_v;
}
for (unsigned i = 0; i < dp_lane_count_; i++) {
voltage_change |= (training[i].voltage_swing_set() != v);
training[i].set_voltage_swing_set(v);
training[i].set_max_swing_reached(v == max_v);
training[i].set_pre_emphasis_set(pe);
training[i].set_max_pre_emphasis_set(pe + v == kMaxVPlusPe);
}
// Compute the index into the programmed table
int level;
if (v == 0) {
level = pe;
} else if (v == 1) {
level = 4 + pe;
} else if (v == 2) {
level = 7 + pe;
} else {
level = 9;
}
registers::DdiRegs ddi_regs(ddi());
auto buf_ctl = ddi_regs.DdiBufControl().ReadFrom(mmio_space());
buf_ctl.set_dp_vswing_emp_sel(level);
buf_ctl.WriteTo(mmio_space());
return voltage_change;
}
bool DpDisplay::LinkTrainingSetup() {
ZX_ASSERT(capabilities_);
registers::DdiRegs ddi_regs(ddi());
// Tell the source device to emit the training pattern.
auto dp_tp = ddi_regs.DdiDpTransportControl().ReadFrom(mmio_space());
dp_tp.set_transport_enable(1);
dp_tp.set_transport_mode_select(0);
dp_tp.set_enhanced_framing_enable(capabilities_->enhanced_frame_capability());
dp_tp.set_dp_link_training_pattern(dp_tp.kTrainingPattern1);
dp_tp.WriteTo(mmio_space());
// Configure ddi voltage swing
// TODO(fxbug.dev/31313): Read the VBT to handle unique motherboard configs for kaby lake
unsigned count;
uint8_t i_boost;
const ddi_buf_trans_entry* entries;
if (controller()->igd_opregion().IsLowVoltageEdp(ddi())) {
i_boost = 0;
GetEdpDdiBufTransEntries(controller()->device_id(), &entries, &count);
} else {
GetDpDdiBufTransEntries(controller()->device_id(), &entries, &i_boost, &count);
}
uint8_t i_boost_override = controller()->igd_opregion().GetIBoost(ddi(), true /* is_dp */);
for (unsigned i = 0; i < count; i++) {
auto ddi_buf_trans_high = ddi_regs.DdiBufTransHi(i).ReadFrom(mmio_space());
auto ddi_buf_trans_low = ddi_regs.DdiBufTransLo(i).ReadFrom(mmio_space());
ddi_buf_trans_high.set_reg_value(entries[i].high_dword);
ddi_buf_trans_low.set_reg_value(entries[i].low_dword);
if (i_boost_override) {
ddi_buf_trans_low.set_balance_leg_enable(1);
}
ddi_buf_trans_high.WriteTo(mmio_space());
ddi_buf_trans_low.WriteTo(mmio_space());
}
const uint8_t i_boost_val = i_boost_override ? i_boost_override : i_boost;
auto disio_cr_tx_bmu = registers::DisplayIoCtrlRegTxBmu::Get().ReadFrom(mmio_space());
disio_cr_tx_bmu.set_disable_balance_leg(!i_boost && !i_boost_override);
disio_cr_tx_bmu.tx_balance_leg_select(ddi()).set(i_boost_val);
if (ddi() == registers::DDI_A && dp_lane_count_ == 4) {
disio_cr_tx_bmu.tx_balance_leg_select(registers::DDI_E).set(i_boost_val);
}
disio_cr_tx_bmu.WriteTo(mmio_space());
// Enable and wait for DDI_BUF_CTL
auto buf_ctl = ddi_regs.DdiBufControl().ReadFrom(mmio_space());
buf_ctl.set_ddi_buffer_enable(1);
buf_ctl.set_dp_vswing_emp_sel(0);
buf_ctl.set_dp_port_width_selection(dp_lane_count_ - 1);
buf_ctl.WriteTo(mmio_space());
zx_nanosleep(zx_deadline_after(ZX_USEC(518)));
uint16_t link_rate_reg;
uint8_t link_rate_val;
if (dp_link_rate_table_idx_) {
dpcd::LinkRateSet link_rate_set;
link_rate_set.set_link_rate_idx(static_cast<uint8_t>(dp_link_rate_table_idx_.value()));
link_rate_reg = dpcd::DPCD_LINK_RATE_SET;
link_rate_val = link_rate_set.reg_value();
} else {
uint8_t target_bw;
if (dp_link_rate_mhz_ == 1620) {
target_bw = dpcd::LinkBw::k1620Mbps;
} else if (dp_link_rate_mhz_ == 2700) {
target_bw = dpcd::LinkBw::k2700Mbps;
} else {
ZX_ASSERT(dp_link_rate_mhz_ == 5400);
target_bw = dpcd::LinkBw::k5400Mbps;
}
dpcd::LinkBw bw_setting;
bw_setting.set_link_bw(target_bw);
link_rate_reg = dpcd::DPCD_LINK_BW_SET;
link_rate_val = bw_setting.reg_value();
}
// Configure the bandwidth and lane count settings
dpcd::LaneCount lc_setting;
lc_setting.set_lane_count_set(dp_lane_count_);
lc_setting.set_enhanced_frame_enabled(capabilities_->enhanced_frame_capability());
if (!DpcdWrite(link_rate_reg, &link_rate_val, 1) ||
!DpcdWrite(dpcd::DPCD_COUNT_SET, lc_setting.reg_value_ptr(), 1)) {
zxlogf(ERROR, "DP: Link training: failed to configure settings");
return false;
}
return true;
}
// Number of times to poll with the same voltage level configured, as
// specified by the DisplayPort spec.
static const int kPollsPerVoltageLevel = 5;
bool DpDisplay::LinkTrainingStage1(dpcd::TrainingPatternSet* tp_set, dpcd::TrainingLaneSet* lanes) {
ZX_ASSERT(capabilities_);
// Tell the sink device to look for the training pattern.
tp_set->set_training_pattern_set(tp_set->kTrainingPattern1);
tp_set->set_scrambling_disable(1);
dpcd::AdjustRequestLane adjust_req[dp_lane_count_];
dpcd::LaneStatus lane_status[dp_lane_count_];
int poll_count = 0;
auto delay =
capabilities_->dpcd_reg<dpcd::TrainingAuxRdInterval, dpcd::DPCD_TRAINING_AUX_RD_INTERVAL>();
for (;;) {
if (!DpcdRequestLinkTraining(*tp_set, lanes)) {
return false;
}
zx_nanosleep(
zx_deadline_after(ZX_USEC(delay.clock_recovery_delay_us(capabilities_->dpcd_revision()))));
// Did the sink device receive the signal successfully?
if (!DpcdReadPairedRegs<dpcd::DPCD_LANE0_1_STATUS, dpcd::LaneStatus>(lane_status)) {
return false;
}
bool done = true;
for (unsigned i = 0; i < dp_lane_count_; i++) {
done &= lane_status[i].lane_cr_done(i).get();
}
if (done) {
break;
}
for (unsigned i = 0; i < dp_lane_count_; i++) {
if (lanes[i].max_swing_reached()) {
zxlogf(ERROR, "DP: Link training: max voltage swing reached");
return false;
}
}
if (!DpcdReadPairedRegs<dpcd::DPCD_ADJUST_REQUEST_LANE0_1, dpcd::AdjustRequestLane>(
adjust_req)) {
return false;
}
if (DpcdHandleAdjustRequest(lanes, adjust_req)) {
poll_count = 0;
} else if (++poll_count == kPollsPerVoltageLevel) {
zxlogf(ERROR, "DP: Link training: clock recovery step failed");
return false;
}
}
return true;
}
bool DpDisplay::LinkTrainingStage2(dpcd::TrainingPatternSet* tp_set, dpcd::TrainingLaneSet* lanes) {
ZX_ASSERT(capabilities_);
registers::DdiRegs ddi_regs(ddi());
auto dp_tp = ddi_regs.DdiDpTransportControl().ReadFrom(mmio_space());
dpcd::AdjustRequestLane adjust_req[dp_lane_count_];
dpcd::LaneStatus lane_status[dp_lane_count_];
dp_tp.set_dp_link_training_pattern(dp_tp.kTrainingPattern2);
dp_tp.WriteTo(mmio_space());
tp_set->set_training_pattern_set(tp_set->kTrainingPattern2);
int poll_count = 0;
auto delay =
capabilities_->dpcd_reg<dpcd::TrainingAuxRdInterval, dpcd::DPCD_TRAINING_AUX_RD_INTERVAL>();
for (;;) {
// lane0_training and lane1_training can change in the loop
if (!DpcdRequestLinkTraining(*tp_set, lanes)) {
return false;
}
zx_nanosleep(zx_deadline_after(ZX_USEC(delay.channel_eq_delay_us())));
// Did the sink device receive the signal successfully?
if (!DpcdReadPairedRegs<dpcd::DPCD_LANE0_1_STATUS, dpcd::LaneStatus>(lane_status)) {
return false;
}
for (unsigned i = 0; i < dp_lane_count_; i++) {
if (!lane_status[i].lane_cr_done(i).get()) {
zxlogf(ERROR, "DP: Link training: clock recovery regressed");
return false;
}
}
bool symbol_lock_done = true;
bool channel_eq_done = true;
for (unsigned i = 0; i < dp_lane_count_; i++) {
symbol_lock_done &= lane_status[i].lane_symbol_locked(i).get();
channel_eq_done &= lane_status[i].lane_channel_eq_done(i).get();
}
if (symbol_lock_done && channel_eq_done) {
break;
}
// The training attempt has not succeeded yet.
if (++poll_count == kPollsPerVoltageLevel) {
if (symbol_lock_done) {
zxlogf(ERROR, "DP: Link training: symbol lock failed");
return false;
} else {
zxlogf(ERROR, "DP: Link training: channel equalization failed");
return false;
}
}
if (!DpcdReadPairedRegs<dpcd::DPCD_ADJUST_REQUEST_LANE0_1, dpcd::AdjustRequestLane>(
adjust_req)) {
return false;
}
DpcdHandleAdjustRequest(lanes, adjust_req);
}
dp_tp.set_dp_link_training_pattern(dp_tp.kSendPixelData);
dp_tp.WriteTo(mmio_space());
return true;
}
bool DpDisplay::DoLinkTraining() {
// TODO(fxbug.dev/31313): If either of the two training steps fails, we're
// supposed to try with a reduced bit rate.
bool result = LinkTrainingSetup();
if (result) {
dpcd::TrainingPatternSet tp_set;
dpcd::TrainingLaneSet lanes[dp_lane_count_];
result = LinkTrainingStage1(&tp_set, lanes) && LinkTrainingStage2(&tp_set, lanes);
}
// Tell the sink device to end its link training attempt.
//
// If link training was successful, we need to do this so that the sink
// device will accept pixel data from the source device.
//
// If link training was not successful, we want to do this so that
// subsequent link training attempts can work. If we don't unset this
// register, subsequent link training attempts can also fail. (This
// can be important during development. The sink device won't
// necessarily get reset when the computer is reset. This means that a
// bad version of the driver can leave the sink device in a state where
// good versions subsequently don't work.)
uint32_t addr = dpcd::DPCD_TRAINING_PATTERN_SET;
uint8_t reg_byte = 0;
if (!DpcdWrite(addr, &reg_byte, sizeof(reg_byte))) {
zxlogf(ERROR, "Failure setting TRAINING_PATTERN_SET");
return false;
}
return result;
}
} // namespace i915
namespace {
// Convert ratio x/y into the form used by the Link/Data M/N ratio registers.
void CalculateRatio(uint32_t x, uint32_t y, uint32_t* m_out, uint32_t* n_out) {
// The exact values of N and M shouldn't matter too much. N and M can be
// up to 24 bits, and larger values will tend to represent the ratio more
// accurately. However, large values of N (e.g. 1 << 23) cause some monitors
// to inexplicably fail. Pick a relatively arbitrary value for N that works
// well in practice.
*n_out = 1 << 20;
*m_out = static_cast<uint32_t>(static_cast<uint64_t>(x) * *n_out / y);
}
} // namespace
namespace i915 {
DpDisplay::DpDisplay(Controller* controller, uint64_t id, registers::Ddi ddi, DpcdChannel* dp_aux,
inspect::Node* parent_node)
: DisplayDevice(controller, id, ddi), dp_aux_(dp_aux) {
ZX_ASSERT(dp_aux);
inspect_node_ = parent_node->CreateChild(fbl::StringPrintf("dp-display-%lu", id));
dp_lane_count_inspect_ = inspect_node_.CreateUint("dp_lane_count", 0);
dp_link_rate_mhz_inspect_ = inspect_node_.CreateUint("dp_link_rate_mhz", 0);
}
bool DpDisplay::Query() {
// For eDP displays, assume that the BIOS has enabled panel power, given
// that we need to rely on it properly configuring panel power anyway. For
// general DP displays, the default power state is D0, so we don't have to
// worry about AUX failures because of power saving mode.
{
auto capabilities = DpCapabilities::Read(dp_aux_, &inspect_node_);
if (capabilities.is_error()) {
return false;
}
capabilities_ = capabilities.take_value();
}
// TODO(fxbug.dev/31313): Add support for MST
if (capabilities_->sink_count() != 1) {
zxlogf(ERROR, "MST not supported");
return false;
}
uint8_t lane_count = 0;
if ((ddi() == registers::DDI_A || ddi() == registers::DDI_E) &&
capabilities_->max_lane_count() == 4 &&
!registers::DdiRegs(registers::DDI_A)
.DdiBufControl()
.ReadFrom(mmio_space())
.ddi_a_lane_capability_control()) {
lane_count = 2;
} else {
lane_count = capabilities_->max_lane_count();
}
dp_lane_count_ = lane_count;
dp_lane_count_inspect_.Set(lane_count);
ZX_ASSERT(!dp_link_rate_table_idx_.has_value());
ZX_ASSERT(!capabilities_->supported_link_rates_mbps().empty());
uint8_t last = static_cast<uint8_t>(capabilities_->supported_link_rates_mbps().size() - 1);
zxlogf(INFO, "Found %s monitor (max link rate: %d MHz, lane count: %d)",
(controller()->igd_opregion().IsEdp(ddi()) ? "eDP" : "DP"),
capabilities_->supported_link_rates_mbps()[last], dp_lane_count_);
return true;
}
bool DpDisplay::InitDdi() {
ZX_ASSERT(capabilities_);
bool is_edp = controller()->igd_opregion().IsEdp(ddi());
if (is_edp) {
auto panel_ctrl = registers::PanelPowerCtrl::Get().ReadFrom(mmio_space());
auto panel_status = registers::PanelPowerStatus::Get().ReadFrom(mmio_space());
if (!panel_status.on_status() ||
panel_status.pwr_seq_progress() == panel_status.kPrwSeqPwrDown) {
panel_ctrl.set_power_state_target(1).set_pwr_down_on_reset(1).WriteTo(mmio_space());
}
// Per eDP 1.4, the panel must be on and ready to accept AUX messages
// within T1 + T3, which is at most 90 ms.
// TODO(fxbug.dev/31313): Read the hardware's actual value for T1 + T3.
zx_nanosleep(zx_deadline_after(ZX_MSEC(90)));
if (!panel_status.ReadFrom(mmio_space()).on_status() ||
panel_status.pwr_seq_progress() != panel_status.kPrwSeqNone) {
zxlogf(ERROR, "Failed to enable panel!");
return false;
}
}
if (capabilities_->dpcd_revision() >= dpcd::Revision::k1_1) {
// If the device is in a low power state, the first write can fail. It should be ready
// within 1ms, but try a few extra times to be safe.
dpcd::SetPower set_pwr;
set_pwr.set_set_power_state(set_pwr.kOn);
int count = 0;
while (!DpcdWrite(dpcd::DPCD_SET_POWER, set_pwr.reg_value_ptr(), 1) && ++count < 5) {
zx_nanosleep(zx_deadline_after(ZX_MSEC(1)));
}
if (count >= 5) {
zxlogf(ERROR, "Failed to set dp power state");
return ZX_ERR_INTERNAL;
}
}
dpcd::LaneAlignStatusUpdate status;
if (!DpcdRead(dpcd::DPCD_LANE_ALIGN_STATUS_UPDATED, status.reg_value_ptr(), 1)) {
zxlogf(WARNING, "Failed to read align status on hotplug");
return false;
}
// If the link is already trained, assume output is working
if (status.interlane_align_done()) {
return true;
}
// Determine the current link rate if one hasn't been assigned.
if (dp_link_rate_mhz_ == 0) {
ZX_ASSERT(!capabilities_->supported_link_rates_mbps().empty());
// Pick the maximum supported link rate.
uint8_t index = static_cast<uint8_t>(capabilities_->supported_link_rates_mbps().size() - 1);
uint32_t link_rate = capabilities_->supported_link_rates_mbps()[index];
zxlogf(INFO, "Selected maximum supported DisplayPort link rate: %u Mbps/lane", link_rate);
SetLinkRate(link_rate);
if (capabilities_->use_link_rate_table()) {
dp_link_rate_table_idx_ = index;
}
}
DpllState state = DpDpllState{
.dp_bit_rate_mhz = dp_link_rate_mhz_,
};
DisplayPll* dpll = controller()->dpll_manager()->Map(ddi(), is_edp, state);
if (dpll == nullptr) {
zxlogf(ERROR, "Cannot find an available DPLL for DP display on DDI %d", ddi());
return false;
}
// Enable power for this DDI.
controller()->power()->SetDdiIoPowerState(ddi(), /* enable */ true);
if (!WAIT_ON_US(controller()->power()->GetDdiIoPowerState(ddi()), 20)) {
zxlogf(ERROR, "Failed to enable IO power for ddi");
return false;
}
// Do link training
if (!DoLinkTraining()) {
zxlogf(ERROR, "DDI %d: DisplayPort link training failed", ddi());
return false;
}
return true;
}
void DpDisplay::InitWithDpllState(const DpllState* dpll_state) {
if (dpll_state == nullptr) {
return;
}
ZX_DEBUG_ASSERT(std::holds_alternative<DpDpllState>(*dpll_state));
if (!std::holds_alternative<DpDpllState>(*dpll_state)) {
zxlogf(ERROR, "Non DP dpll_state is given to DpDisplay!");
return;
}
auto dp_state = std::get_if<DpDpllState>(dpll_state);
// Some display (e.g. eDP) may have already been configured by the bootloader with a
// link clock. Assign the link rate based on the already enabled DPLL.
if (dp_link_rate_mhz_ == 0) {
// Since the link rate is read from the register directly, we can guarantee
// that it is always valid.
zxlogf(INFO, "Selected pre-configured DisplayPort link rate: %u Mbps/lane",
dp_state->dp_bit_rate_mhz);
SetLinkRate(dp_state->dp_bit_rate_mhz);
}
}
bool DpDisplay::ComputeDpllState(uint32_t pixel_clock_10khz, DpllState* config) {
*config = DpDpllState{
.dp_bit_rate_mhz = dp_link_rate_mhz_,
};
return true;
}
bool DpDisplay::DdiModeset(const display_mode_t& mode, registers::Pipe pipe,
registers::Trans trans) {
return true;
}
bool DpDisplay::PipeConfigPreamble(const display_mode_t& mode, registers::Pipe pipe,
registers::Trans trans) {
registers::TranscoderRegs trans_regs(trans);
// Configure Transcoder Clock Select
if (trans != registers::TRANS_EDP) {
auto clock_select = trans_regs.ClockSelect().ReadFrom(mmio_space());
clock_select.set_trans_clock_select(ddi() + 1);
clock_select.WriteTo(mmio_space());
}
// Pixel clock rate: The rate at which pixels are sent, in pixels per
// second (Hz), divided by 10000.
uint32_t pixel_clock_rate = mode.pixel_clock_10khz;
// This is the rate at which bits are sent on a single DisplayPort
// lane, in raw bits per second, divided by 10000.
uint32_t link_raw_bit_rate = dp_link_rate_mhz_ * 100;
// Link symbol rate: The rate at which link symbols are sent on a
// single DisplayPort lane. A link symbol is 10 raw bits (using 8b/10b
// encoding, which usually encodes an 8-bit data byte).
uint32_t link_symbol_rate = link_raw_bit_rate / 10;
// Configure ratios between pixel clock/bit rate and symbol clock/bit rate
uint32_t link_m;
uint32_t link_n;
CalculateRatio(pixel_clock_rate, link_symbol_rate, &link_m, &link_n);
uint32_t pixel_bit_rate = pixel_clock_rate * kBitsPerPixel;
uint32_t total_link_bit_rate = link_symbol_rate * 8 * dp_lane_count_;
ZX_DEBUG_ASSERT(pixel_bit_rate <= total_link_bit_rate); // Should be caught by CheckPixelRate
uint32_t data_m;
uint32_t data_n;
CalculateRatio(pixel_bit_rate, total_link_bit_rate, &data_m, &data_n);
auto data_m_reg = trans_regs.DataM().FromValue(0);
data_m_reg.set_tu_or_vcpayload_size(63); // Size - 1, default TU size is 64
data_m_reg.set_data_m_value(data_m);
data_m_reg.WriteTo(mmio_space());
auto data_n_reg = trans_regs.DataN().FromValue(0);
data_n_reg.set_data_n_value(data_n);
data_n_reg.WriteTo(mmio_space());
auto link_m_reg = trans_regs.LinkM().FromValue(0);
link_m_reg.set_link_m_value(link_m);
link_m_reg.WriteTo(mmio_space());
auto link_n_reg = trans_regs.LinkN().FromValue(0);
link_n_reg.set_link_n_value(link_n);
link_n_reg.WriteTo(mmio_space());
return true;
}
bool DpDisplay::PipeConfigEpilogue(const display_mode_t& mode, registers::Pipe pipe,
registers::Trans trans) {
registers::TranscoderRegs trans_regs(trans);
auto msa_misc = trans_regs.MsaMisc().FromValue(0);
msa_misc.set_sync_clock(1);
msa_misc.set_bits_per_color(msa_misc.k8Bbc); // kPixelFormat
msa_misc.set_color_format(msa_misc.kRgb); // kPixelFormat
msa_misc.WriteTo(mmio_space());
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(ddi_func.kModeDisplayPortSst);
ddi_func.set_bits_per_color(ddi_func.k8bbc); // kPixelFormat
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.set_edp_input_select(
pipe == registers::PIPE_A ? ddi_func.kPipeA
: (pipe == registers::PIPE_B ? ddi_func.kPipeB : ddi_func.kPipeC));
ddi_func.set_dp_port_width_selection(dp_lane_count_ - 1);
ddi_func.WriteTo(mmio_space());
auto trans_conf = trans_regs.Conf().FromValue(0);
trans_conf.set_transcoder_enable(1);
trans_conf.set_interlaced_mode(!!(mode.flags & MODE_FLAG_INTERLACED));
trans_conf.WriteTo(mmio_space());
return true;
}
bool DpDisplay::InitBacklightHw() {
if (capabilities_ && capabilities_->backlight_aux_brightness()) {
dpcd::EdpBacklightModeSet mode;
mode.set_brightness_ctrl_mode(mode.kAux);
if (!DpcdWrite(dpcd::DPCD_EDP_BACKLIGHT_MODE_SET, mode.reg_value_ptr(), 1)) {
zxlogf(ERROR, "Failed to init backlight");
return false;
}
}
return true;
}
bool DpDisplay::SetBacklightOn(bool on) {
if (!controller()->igd_opregion().IsEdp(ddi())) {
return true;
}
if (capabilities_ && capabilities_->backlight_aux_power()) {
dpcd::EdpDisplayCtrl ctrl;
ctrl.set_backlight_enable(true);
if (!DpcdWrite(dpcd::DPCD_EDP_DISPLAY_CTRL, ctrl.reg_value_ptr(), 1)) {
zxlogf(ERROR, "Failed to enable backlight");
return false;
}
} else {
registers::PanelPowerCtrl::Get()
.ReadFrom(mmio_space())
.set_backlight_enable(on)
.WriteTo(mmio_space());
registers::SouthBacklightCtl1::Get()
.ReadFrom(mmio_space())
.set_enable(on)
.WriteTo(mmio_space());
}
return !on || SetBacklightBrightness(backlight_brightness_);
}
bool DpDisplay::IsBacklightOn() {
// If there is no embedded display, return false.
if (!controller()->igd_opregion().IsEdp(ddi())) {
return false;
}
if (capabilities_ && capabilities_->backlight_aux_power()) {
dpcd::EdpDisplayCtrl ctrl;
if (!DpcdRead(dpcd::DPCD_EDP_DISPLAY_CTRL, ctrl.reg_value_ptr(), 1)) {
zxlogf(ERROR, "Failed to read backlight");
return false;
}
return ctrl.backlight_enable();
} else {
return registers::PanelPowerCtrl::Get().ReadFrom(mmio_space()).backlight_enable() ||
registers::SouthBacklightCtl1::Get().ReadFrom(mmio_space()).enable();
}
}
bool DpDisplay::SetBacklightBrightness(double val) {
if (!controller()->igd_opregion().IsEdp(ddi())) {
return true;
}
backlight_brightness_ = std::max(val, controller()->igd_opregion().GetMinBacklightBrightness());
backlight_brightness_ = std::min(backlight_brightness_, 1.0);
if (capabilities_ && capabilities_->backlight_aux_brightness()) {
uint16_t percent = static_cast<uint16_t>(0xffff * backlight_brightness_ + .5);
uint8_t lsb = static_cast<uint8_t>(percent & 0xff);
uint8_t msb = static_cast<uint8_t>(percent >> 8);
if (!DpcdWrite(dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_MSB, &msb, 1) ||
!DpcdWrite(dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_LSB, &lsb, 1)) {
zxlogf(ERROR, "Failed to set backlight brightness");
return false;
}
} else {
auto backlight_ctrl = registers::SouthBacklightCtl2::Get().ReadFrom(mmio_space());
uint16_t max = static_cast<uint16_t>(backlight_ctrl.modulation_freq());
backlight_ctrl.set_duty_cycle(static_cast<uint16_t>(max * backlight_brightness_ + .5));
backlight_ctrl.WriteTo(mmio_space());
}
return true;
}
double DpDisplay::GetBacklightBrightness() {
if (!HasBacklight()) {
return 0;
}
double percent = 0;
if (capabilities_ && capabilities_->backlight_aux_brightness()) {
uint8_t lsb;
uint8_t msb;
if (!DpcdRead(dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_MSB, &msb, 1) ||
!DpcdRead(dpcd::DPCD_EDP_BACKLIGHT_BRIGHTNESS_LSB, &lsb, 1)) {
zxlogf(ERROR, "Failed to read backlight brightness");
return 0;
}
uint16_t brightness = static_cast<uint16_t>((lsb & 0xff) | (msb << 8));
percent = (brightness * 1.0f) / 0xffff;
} else {
auto backlight_ctrl = registers::SouthBacklightCtl2::Get().ReadFrom(mmio_space());
uint16_t max = static_cast<uint16_t>(backlight_ctrl.modulation_freq());
uint16_t duty_cycle = static_cast<uint16_t>(backlight_ctrl.duty_cycle());
percent = (duty_cycle * 1.0f) / max;
}
return percent;
}
bool DpDisplay::HandleHotplug(bool long_pulse) {
if (!long_pulse) {
dpcd::SinkCount sink_count;
if (!DpcdRead(dpcd::DPCD_SINK_COUNT, sink_count.reg_value_ptr(), 1)) {
zxlogf(WARNING, "Failed to read sink count on hotplug");
return false;
}
// The pulse was from a downstream monitor being connected
// TODO(fxbug.dev/31313): Add support for MST
if (sink_count.count() > 1) {
return true;
}
// The pulse was from a downstream monitor disconnecting
if (sink_count.count() == 0) {
return false;
}
dpcd::LaneAlignStatusUpdate status;
if (!DpcdRead(dpcd::DPCD_LANE_ALIGN_STATUS_UPDATED, status.reg_value_ptr(), 1)) {
zxlogf(WARNING, "Failed to read align status on hotplug");
return false;
}
if (status.interlane_align_done()) {
zxlogf(DEBUG, "HPD event for trained link");
return true;
}
return DoLinkTraining();
}
return false;
}
bool DpDisplay::HasBacklight() { return controller()->igd_opregion().IsEdp(ddi()); }
namespace FidlBacklight = fuchsia_hardware_backlight;
zx_status_t DpDisplay::SetBacklightState(bool power, double brightness) {
SetBacklightOn(power);
brightness = std::max(brightness, 0.0);
brightness = std::min(brightness, 1.0);
double range = 1.0f - controller()->igd_opregion().GetMinBacklightBrightness();
if (!SetBacklightBrightness((range * brightness) +
controller()->igd_opregion().GetMinBacklightBrightness())) {
return ZX_ERR_IO;
}
return ZX_OK;
}
zx_status_t DpDisplay::GetBacklightState(bool* power, double* brightness) {
*power = IsBacklightOn();
*brightness = GetBacklightBrightness();
return ZX_OK;
}
void DpDisplay::SetLinkRate(uint32_t value) {
dp_link_rate_mhz_ = value;
dp_link_rate_mhz_inspect_.Set(value);
}
bool DpDisplay::CheckPixelRate(uint64_t pixel_rate) {
uint64_t bit_rate = (dp_link_rate_mhz_ * 1000000lu) * dp_lane_count_;
// Multiply by 8/10 because of 8b/10b encoding
uint64_t max_pixel_rate = (bit_rate * 8 / 10) / kBitsPerPixel;
return pixel_rate <= max_pixel_rate;
}
uint32_t DpDisplay::LoadClockRateForTranscoder(registers::Trans transcoder) {
registers::TranscoderRegs trans_regs(transcoder);
uint32_t data_m = trans_regs.DataM().ReadFrom(mmio_space()).data_m_value();
uint32_t data_n = trans_regs.DataN().ReadFrom(mmio_space()).data_n_value();
double total_link_bit_rate_10khz = dp_link_rate_mhz_ * 100. * (8. / 10.) * dp_lane_count_;
double res = (data_m * total_link_bit_rate_10khz) / (data_n * kBitsPerPixel);
return static_cast<uint32_t>(round(res));
}
} // namespace i915