blob: 299f9057a6bb9f505305b5b655e9322e20794578 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/graphics/display/drivers/intel-i915/display-device.h"
#include <lib/ddk/device.h>
#include <lib/ddk/driver.h>
#include <lib/fit/function.h>
#include <lib/zx/result.h>
#include <lib/zx/vmo.h>
#include <zircon/assert.h>
#include <zircon/errors.h>
#include <cfloat>
#include <cinttypes>
#include <cmath>
#include <ddktl/fidl.h>
#include "src/graphics/display/drivers/intel-i915/intel-i915.h"
#include "src/graphics/display/drivers/intel-i915/registers-dpll.h"
#include "src/graphics/display/drivers/intel-i915/registers-transcoder.h"
#include "src/graphics/display/drivers/intel-i915/registers.h"
#include "src/graphics/display/drivers/intel-i915/tiling.h"
#include "src/graphics/display/lib/api-types-cpp/config-stamp.h"
#include "src/graphics/display/lib/api-types-cpp/display-id.h"
#include "src/graphics/display/lib/api-types-cpp/display-timing.h"
#include "src/graphics/display/lib/api-types-cpp/driver-image-id.h"
namespace i915 {
namespace {
void backlight_message(void* ctx, fidl_incoming_msg_t msg, device_fidl_txn_t txn) {
DisplayDevice* ptr;
{
fbl::AutoLock lock(&static_cast<display_ref_t*>(ctx)->mtx);
ptr = static_cast<display_ref_t*>(ctx)->display_device;
}
fidl::WireDispatch<fuchsia_hardware_backlight::Device>(
ptr, fidl::IncomingHeaderAndMessage::FromEncodedCMessage(msg),
ddk::FromDeviceFIDLTransaction(txn));
}
void backlight_release(void* ctx) { delete static_cast<display_ref_t*>(ctx); }
constexpr zx_protocol_device_t kBacklightDeviceOps = {
.version = DEVICE_OPS_VERSION,
.release = &backlight_release,
.message = &backlight_message,
};
} // namespace
DisplayDevice::DisplayDevice(Controller* controller, display::DisplayId id, DdiId ddi_id,
DdiReference ddi_reference, Type type)
: controller_(controller),
id_(id),
ddi_id_(ddi_id),
ddi_reference_(std::move(ddi_reference)),
type_(type) {}
DisplayDevice::~DisplayDevice() {
if (pipe_) {
controller_->pipe_manager()->ReturnPipe(pipe_);
controller_->ResetPipePlaneBuffers(pipe_->pipe_id());
}
if (inited_) {
controller_->ResetDdi(ddi_id(), pipe()->connected_transcoder_id());
}
if (ddi_reference_) {
ddi_reference_.reset();
}
if (display_ref_) {
fbl::AutoLock lock(&display_ref_->mtx);
display_ref_->display_device = nullptr;
device_async_remove(backlight_device_);
}
}
fdf::MmioBuffer* DisplayDevice::mmio_space() const { return controller_->mmio_space(); }
bool DisplayDevice::Init() {
ddi_power_ = controller_->power()->GetDdiPowerWellRef(ddi_id_);
Pipe* pipe = controller_->pipe_manager()->RequestPipe(*this);
if (!pipe) {
zxlogf(ERROR, "Cannot request a new pipe!");
return false;
}
set_pipe(pipe);
if (!InitDdi()) {
return false;
}
inited_ = true;
InitBacklight();
return true;
}
bool DisplayDevice::InitWithDdiPllConfig(const DdiPllConfig& pll_config) {
Pipe* pipe = controller_->pipe_manager()->RequestPipeFromHardwareState(*this, mmio_space());
if (!pipe) {
zxlogf(ERROR, "Failed loading pipe from register!");
return false;
}
set_pipe(pipe);
return true;
}
void DisplayDevice::InitBacklight() {
if (HasBacklight() && InitBacklightHw()) {
fbl::AllocChecker ac;
auto display_ref = fbl::make_unique_checked<display_ref_t>(&ac);
zx_status_t status = ZX_ERR_NO_MEMORY;
if (ac.check()) {
mtx_init(&display_ref->mtx, mtx_plain);
{
fbl::AutoLock lock(&display_ref->mtx);
display_ref->display_device = this;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = "backlight",
.ctx = display_ref.get(),
.ops = &kBacklightDeviceOps,
.proto_id = ZX_PROTOCOL_BACKLIGHT,
};
status = device_add(controller_->zxdev(), &args, &backlight_device_);
if (status == ZX_OK) {
display_ref_ = display_ref.release();
}
}
if (display_ref_ == nullptr) {
zxlogf(WARNING, "Failed to add backlight (%d)", status);
}
std::ignore = SetBacklightState(true, 1.0);
}
}
bool DisplayDevice::Resume() {
if (pipe_) {
if (!DdiModeset(info_)) {
return false;
}
controller_->interrupts()->EnablePipeInterrupts(pipe_->pipe_id(), /*enable=*/true);
}
return pipe_ != nullptr;
}
void DisplayDevice::LoadActiveMode() {
pipe_->LoadActiveMode(&info_);
const int32_t pixel_clock_frequency_khz =
LoadPixelRateForTranscoderKhz(pipe_->connected_transcoder_id());
info_.pixel_clock_frequency_hz = int64_t{pixel_clock_frequency_khz} * 1'000;
zxlogf(INFO, "Active pixel clock: %" PRId64 " Hz", info_.pixel_clock_frequency_hz);
}
bool DisplayDevice::CheckNeedsModeset(const display::DisplayTiming& mode) {
// Check the clock and the flags later
display::DisplayTiming mode_without_clock_or_flags = mode;
mode_without_clock_or_flags.pixel_clock_frequency_hz = info_.pixel_clock_frequency_hz;
mode_without_clock_or_flags.hsync_polarity = info_.hsync_polarity;
mode_without_clock_or_flags.vsync_polarity = info_.vsync_polarity;
mode_without_clock_or_flags.pixel_repetition = info_.pixel_repetition;
mode_without_clock_or_flags.vblank_alternates = info_.vblank_alternates;
if (mode_without_clock_or_flags != info_) {
// Modeset is necessary if display params other than the clock frequency differ
zxlogf(DEBUG, "Modeset necessary for display params");
return true;
}
// TODO(stevensd): There are still some situations where the BIOS is better at setting up
// the display than we are. The BIOS seems to not always set the hsync/vsync polarity, so
// don't include that in the check for already initialized displays. Once we're better at
// initializing displays, merge the flags check back into the above memcmp.
if (mode.fields_per_frame != info_.fields_per_frame) {
zxlogf(DEBUG, "Modeset necessary for display flags");
return true;
}
if (mode.pixel_clock_frequency_hz == info_.pixel_clock_frequency_hz) {
// Modeset is necessary not necessary if all display params are the same
return false;
}
// Check to see if the hardware was already configured properly. The is primarily to
// prevent unnecessary modesetting at startup. The extra work this adds to regular
// modesetting is negligible.
DdiPllConfig desired_pll_config =
ComputeDdiPllConfig(/*pixel_clock_khz=*/
static_cast<int32_t>(info_.pixel_clock_frequency_hz / 1'000));
ZX_DEBUG_ASSERT_MSG(desired_pll_config.IsEmpty(),
"CheckDisplayMode() should have rejected unattainable pixel rates");
return !controller()->dpll_manager()->DdiPllMatchesConfig(ddi_id(), desired_pll_config);
}
void DisplayDevice::ApplyConfiguration(const display_config_t* banjo_display_config,
display::ConfigStamp config_stamp) {
ZX_ASSERT(banjo_display_config);
const display::DisplayTiming display_timing_params =
display::ToDisplayTiming(banjo_display_config->mode);
if (CheckNeedsModeset(display_timing_params)) {
if (pipe_) {
// TODO(https://fxbug.dev/42067272): When ApplyConfiguration() early returns on the
// following error conditions, we should reset the DDI, pipe and transcoder
// so that they can be possibly reused.
if (!DdiModeset(display_timing_params)) {
zxlogf(ERROR, "Display %lu: Modeset failed; ApplyConfiguration() aborted.", id().value());
return;
}
if (!PipeConfigPreamble(display_timing_params, pipe_->pipe_id(),
pipe_->connected_transcoder_id())) {
zxlogf(ERROR,
"Display %lu: Transcoder configuration failed before pipe setup; "
"ApplyConfiguration() aborted.",
id().value());
return;
}
pipe_->ApplyModeConfig(display_timing_params);
if (!PipeConfigEpilogue(display_timing_params, pipe_->pipe_id(),
pipe_->connected_transcoder_id())) {
zxlogf(ERROR,
"Display %lu: Transcoder configuration failed after pipe setup; "
"ApplyConfiguration() aborted.",
id().value());
return;
}
}
info_ = display_timing_params;
}
if (pipe_) {
pipe_->ApplyConfiguration(
banjo_display_config, config_stamp,
[controller = controller_](const image_metadata_t& image_metadata, uint64_t image_handle,
uint32_t rotation) -> const GttRegion& {
return controller->SetupGttImage(image_metadata, image_handle, rotation);
},
[controller = controller_](uint64_t image_handle) -> PixelFormatAndModifier {
const display::DriverImageId image_id = display::ToDriverImageId(image_handle);
return controller->GetImportedImagePixelFormat(image_id);
});
}
}
void DisplayDevice::GetStateNormalized(GetStateNormalizedCompleter::Sync& completer) {
zx::result<FidlBacklight::wire::State> backlight_state = zx::error(ZX_ERR_BAD_STATE);
if (display_ref_ != nullptr) {
fbl::AutoLock lock(&display_ref_->mtx);
if (display_ref_->display_device != nullptr) {
backlight_state = display_ref_->display_device->GetBacklightState();
}
}
if (backlight_state.is_ok()) {
completer.ReplySuccess(backlight_state.value());
} else {
completer.ReplyError(backlight_state.status_value());
}
}
void DisplayDevice::SetStateNormalized(SetStateNormalizedRequestView request,
SetStateNormalizedCompleter::Sync& completer) {
zx::result<> status = zx::error(ZX_ERR_BAD_STATE);
if (display_ref_ != nullptr) {
fbl::AutoLock lock(&display_ref_->mtx);
if (display_ref_->display_device != nullptr) {
status = display_ref_->display_device->SetBacklightState(request->state.backlight_on,
request->state.brightness);
}
}
if (status.is_error()) {
completer.ReplyError(status.status_value());
return;
}
completer.ReplySuccess();
}
void DisplayDevice::GetStateAbsolute(GetStateAbsoluteCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void DisplayDevice::SetStateAbsolute(SetStateAbsoluteRequestView request,
SetStateAbsoluteCompleter::Sync& completer) {
FidlBacklight::wire::DeviceSetStateAbsoluteResult result;
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void DisplayDevice::GetMaxAbsoluteBrightness(GetMaxAbsoluteBrightnessCompleter::Sync& completer) {
FidlBacklight::wire::DeviceGetMaxAbsoluteBrightnessResult result;
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void DisplayDevice::SetNormalizedBrightnessScale(
SetNormalizedBrightnessScaleRequestView request,
SetNormalizedBrightnessScaleCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
void DisplayDevice::GetNormalizedBrightnessScale(
GetNormalizedBrightnessScaleCompleter::Sync& completer) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
}
} // namespace i915