| // 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/pipe.h" |
| |
| #include <fidl/fuchsia.images2/cpp/wire.h> |
| #include <fuchsia/hardware/display/controller/c/banjo.h> |
| #include <lib/ddk/debug.h> |
| #include <lib/sysmem-version/sysmem-version.h> |
| #include <lib/zx/time.h> |
| #include <lib/zx/vmo.h> |
| |
| #include <cfloat> |
| #include <cmath> |
| #include <cstdint> |
| #include <memory> |
| #include <optional> |
| |
| #include "src/graphics/display/drivers/intel-i915/hardware-common.h" |
| #include "src/graphics/display/drivers/intel-i915/poll-until.h" |
| #include "src/graphics/display/drivers/intel-i915/registers-pipe-scaler.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/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" |
| |
| namespace { |
| |
| uint32_t float_to_i915_csc_offset(float f) { |
| ZX_DEBUG_ASSERT(0 <= f && f < 1.0f); // Controller::CheckConfiguration validates this |
| |
| // f is in [0, 1). Multiply by 2^12 to convert to a 12-bit fixed-point fraction. |
| return static_cast<uint32_t>(f * pow(FLT_RADIX, 12)); |
| } |
| |
| uint32_t float_to_i915_csc_coefficient(float f) { |
| registers::CscCoeffFormat res; |
| if (f < 0) { |
| f *= -1; |
| res.set_sign(1); |
| } |
| |
| if (f < .125) { |
| res.set_exponent(res.kExponent0125); |
| f /= .125f; |
| } else if (f < .25) { |
| res.set_exponent(res.kExponent025); |
| f /= .25f; |
| } else if (f < .5) { |
| res.set_exponent(res.kExponent05); |
| f /= .5f; |
| } else if (f < 1) { |
| res.set_exponent(res.kExponent1); |
| } else if (f < 2) { |
| res.set_exponent(res.kExponent2); |
| f /= 2.0f; |
| } else { |
| res.set_exponent(res.kExponent4); |
| f /= 4.0f; |
| } |
| f = (f * 512) + .5f; |
| |
| if (f >= 512) { |
| res.set_mantissa(0x1ff); |
| } else { |
| res.set_mantissa(static_cast<uint16_t>(f)); |
| } |
| |
| return res.reg_value(); |
| } |
| |
| uint32_t encode_pipe_color_component(uint8_t component) { |
| // Convert to unsigned .10 fixed point format |
| return component << 2; |
| } |
| |
| } // namespace |
| |
| namespace i915 { |
| |
| Pipe::Pipe(fdf::MmioBuffer* mmio_space, registers::Platform platform, PipeId pipe_id, |
| PowerWellRef pipe_power) |
| : mmio_space_(mmio_space), |
| platform_(platform), |
| pipe_id_(pipe_id), |
| pipe_power_(std::move(pipe_power)) {} |
| |
| // static |
| void Pipe::ResetTranscoder(TranscoderId transcoder_id, registers::Platform platform, |
| fdf::MmioBuffer* mmio_space) { |
| registers::TranscoderRegs transcoder_regs(transcoder_id); |
| |
| // Disable transcoder and wait for it to stop. These are the "Disable |
| // Transcoder" steps from: |
| // |
| // Tiger Lake - IHD-OS-TGL-Vol 12-12.21 |
| // * "DSI Transcoder Disable Sequence" pages 128-129 (Incomplete) |
| // * "Sequences for DisplayPort" > "Disable Sequence" pages 147-148 (Incomplete) |
| // * "Sequences for HDMI and DVI" > "Disable Sequence" pages 150-151 |
| // * "Sequences for WD" > "Disable Sequence" pages 151-152 (Incomplete) |
| // Kaby Lake - IHD-OS-KBL-Vol 12-1.17 |
| // * "Sequences for DisplayPort" > "Disable Sequence" pages 115-116 (Incomplete) |
| // * "Sequences for HDMI" > "Disable Sequence" page 118 |
| // Skylake - IHD-OS-SKL-Vol 12-05.16 |
| // * "Sequences for DisplayPort" > "Disable Sequence" pages 115-116 (Incomplete) |
| // * "Sequences for HDMI and DVI" > "Disable Sequence" page 118 |
| // |
| // The transcoder should be turned off only after the associated backlight, |
| // audio, and image planes are disabled. |
| auto transcoder_config = transcoder_regs.Config().ReadFrom(mmio_space); |
| |
| // Our experiments on NUC 11 indicate that the display engine may crash the |
| // whole system if the driver sets `enabled_target` to false and writes the |
| // transcoder configuration register when the transcoder is already disabled, |
| // so we avoid crashing the system by only writing the register when the |
| // transcoder is currently enabled. To be on the safe side, we use the same |
| // caution on Kaby Lake and Skylake display engines as well. |
| if (transcoder_config.enabled()) { |
| transcoder_config.set_enabled_target(false).WriteTo(mmio_space); |
| } else { |
| zxlogf(TRACE, "ResetTranscoder() skipping already-disabled control for transcoder %d", |
| transcoder_id); |
| zxlogf(TRACE, "Transcoder %d control register: %x", transcoder_id, |
| transcoder_config.reg_value()); |
| } |
| |
| if (platform == registers::Platform::kTigerLake) { |
| auto transcoder_chicken = transcoder_regs.Chicken().ReadFrom(mmio_space); |
| zxlogf(TRACE, "ResetTranscoder() - Transcoder %d chicken register: %x", transcoder_id, |
| transcoder_chicken.reg_value()); |
| if (transcoder_chicken.override_forward_error_correction_tiger_lake()) { |
| zxlogf(INFO, "Disabling FEC override chicken bit for transcoder %d", transcoder_id); |
| transcoder_chicken.set_override_forward_error_correction_tiger_lake(false).WriteTo( |
| mmio_space); |
| |
| // TODO(https://fxbug.dev/42061773): Remove this warning once we support DisplayPort |
| // MST (Multi-Stream). |
| zxlogf(WARNING, "Transcoder %d was using a DisplayPort MST feature. Reset may be incomplete.", |
| transcoder_id); |
| } |
| } |
| |
| // Wait for off status in TRANS_CONF, timeout after two frames. |
| // Here we wait for 60 msecs, which is enough to guarantee to include two |
| // whole frames in ~50 fps. |
| constexpr size_t kTransConfStatusWaitTimeoutMs = 60; |
| if (!PollUntil([&] { return !transcoder_config.ReadFrom(mmio_space).enabled(); }, zx::msec(1), |
| kTransConfStatusWaitTimeoutMs)) { |
| // Because this is a logical "reset", we only log failures rather than |
| // crashing the driver. |
| zxlogf(WARNING, "Failed to reset transcoder"); |
| return; |
| } |
| |
| if (platform == registers::Platform::kTigerLake) { |
| auto transcoder_variable_rate_refresh_control = |
| transcoder_regs.VariableRateRefreshControl().ReadFrom(mmio_space); |
| zxlogf(TRACE, "ResetTranscoder() - Transcoder %d VRR register: %x", transcoder_id, |
| transcoder_variable_rate_refresh_control.reg_value()); |
| if (transcoder_variable_rate_refresh_control.enabled()) { |
| zxlogf(INFO, "Disabling VRR (Variable Refresh Rate) for transcoder %d", transcoder_id); |
| transcoder_variable_rate_refresh_control.set_enabled(false).WriteTo(mmio_space); |
| } |
| } |
| |
| // Disable transcoder DDI select and clock select. |
| auto transcoder_ddi_control = transcoder_regs.DdiControl().ReadFrom(mmio_space); |
| |
| // Our experiments on Dell 5420 with Tiger Lake CPU indicate that the display |
| // engine may crash the whole system if the driver sets `enabled` to false and |
| // writes the transcoder DDI functionality configuration register when the DDI |
| // functionality is already disabled. We avoid crashing the system by only |
| // writing the register when the transcoder is currently enabled. To be on the |
| // safe side, we use the same caution on Kaby Lake and Skylake display engines |
| // as well. |
| if (transcoder_ddi_control.enabled()) { |
| // `set_ddi_tiger_lake()` works on both Tiger Lake and Skylake / Kaby Lake |
| // when passed std::nullopt, because nullopt translates to zeroing out all the |
| // field's bits, and on Kaby Lake the highest bit of "ddi_tiger_lake" is |
| // reserved to be zero, so it is safe to set the whole field to zero. |
| transcoder_ddi_control.set_enabled(false).set_ddi_tiger_lake(std::nullopt).WriteTo(mmio_space); |
| } else { |
| zxlogf(TRACE, "ResetTranscoder() skipping already-disabled DDI functionality for transcoder %d", |
| transcoder_id); |
| zxlogf(TRACE, "Transcoder %d DDI functionality control register: %x", transcoder_id, |
| transcoder_ddi_control.reg_value()); |
| } |
| |
| if (transcoder_id != TranscoderId::TRANSCODER_EDP) { |
| auto transcoder_clock_select = transcoder_regs.ClockSelect().ReadFrom(mmio_space); |
| |
| // `set_ddi_tiger_lake()` works on both Tiger Lake and Skylake / Kaby Lake |
| // when passed std::nullopt, because nullopt translates to zeroing out all |
| // the field's bits, and on Kaby Lake the highest bit of |
| // "ddi_clock_tiger_lake" is reserved to be zero, so it is safe to set the |
| // whole field to zero. |
| transcoder_clock_select.set_ddi_clock_tiger_lake(std::nullopt).WriteTo(mmio_space); |
| } |
| } |
| |
| void Pipe::Reset() { |
| // Follow the steps in "DisplayPort disable sequence" / "HDMI/DVI disable |
| // sequence" to disable planes, connected transcoder and scalers (i.e. panel |
| // fitter). |
| // |
| // TODO(https://fxbug.dev/42061773): Currently the procedure is the same for DisplayPort |
| // and HDMI/DVI. This may change once DisplayPort Multistream (MST) is |
| // supported. |
| // |
| // Skylake: IHD-OS-SKL-Vol 12-05.16, |
| // DisplayPort: Pages 113-114, "Disable Sequence", Step 2. |
| // "Disable Planes, Pipe and Transcoder". |
| // HDMI/DVI : Pages 115-116, "Disable Sequence", Step 2. |
| // "Disable Planes, Pipe and Transcoder". |
| // |
| // Kaby Lake: IHD-OS-KBL-Vol 12-1.17, |
| // DisplayPort: Pages 115-116, "Disable Sequence", |
| // Step 2. "Disable Planes, Pipe and Transcoder". |
| // HDMI/DVI : Pages 118, "Disable Sequence", Step 2. |
| // "Disable Planes, Pipe and Transcoder". |
| // |
| // Tiger Lake: IHD-OS-TGL-Vol 12-1.22-Rev 2.0, |
| // DisplayPort: Pages 147-148, "Disable Sequence", |
| // Step 2. "If not in compliance mode: Disable |
| // Planes, Pipe and Transcoder". |
| // HDMI/DVI: Pages 150, "Disable Sequence", |
| // Step 2. "Disable Planes, Pipe and Transcoder". |
| |
| ResetPlanes(); |
| ResetActiveTranscoder(); |
| ResetScaler(); |
| } |
| |
| void Pipe::ResetPlanes() { |
| registers::PipeRegs pipe_regs(pipe_id()); |
| |
| // Disable planes, bottom color, and cursor |
| const int32_t plane_count = platform_ == registers::Platform::kTigerLake ? 7 : 3; |
| for (int32_t i = 0; i < plane_count; i++) { |
| pipe_regs.PlaneControl(i).FromValue(0).WriteTo(mmio_space_); |
| pipe_regs.PlaneSurface(i).FromValue(0).WriteTo(mmio_space_); |
| } |
| auto cursor_ctrl = pipe_regs.CursorCtrl().ReadFrom(mmio_space_); |
| cursor_ctrl.set_mode_select(registers::CursorCtrl::kDisabled); |
| cursor_ctrl.WriteTo(mmio_space_); |
| pipe_regs.CursorBase().FromValue(0).WriteTo(mmio_space_); |
| pipe_regs.PipeBottomColor().FromValue(0).WriteTo(mmio_space_); |
| } |
| |
| void Pipe::ResetActiveTranscoder() { |
| if (in_use()) { |
| ResetTranscoder(connected_transcoder_id(), platform_, mmio_space_); |
| zxlogf(DEBUG, "Reset active transcoder %d for pipe %d", connected_transcoder_id(), pipe_id()); |
| } |
| } |
| |
| void Pipe::ResetScaler() { |
| registers::PipeRegs pipe_regs(pipe_id()); |
| |
| // This works for Skylake / Kaby Lake and Tiger Lake. |
| // Note that Skylake / Kaby Lake doesn't have PS_CTRL_2_C documented in the |
| // PRM, but experiments on Atlas (using Kaby Lake) shows that it does have |
| // this scaler, so we use the same value across all generations. |
| // |
| // TODO(https://fxbug.dev/42071441): Verify the existence of the scaler and document |
| // the experiment results. |
| const int kScalerCount = 2; |
| |
| for (int scaler_num = 0; scaler_num < kScalerCount; scaler_num++) { |
| auto pipe_scaler_regs = pipe_regs.PipeScalerRegs(scaler_num); |
| pipe_scaler_regs.PipeScalerControlSkylake() |
| .ReadFrom(mmio_space_) |
| .set_is_enabled(false) |
| .WriteTo(mmio_space_); |
| } |
| } |
| |
| void Pipe::Detach() { |
| attached_display_id_ = display::kInvalidDisplayId; |
| attached_edp_ = false; |
| } |
| |
| void Pipe::AttachToDisplay(display::DisplayId id, bool is_edp) { |
| attached_display_id_ = id; |
| attached_edp_ = is_edp; |
| } |
| |
| void Pipe::ApplyModeConfig(const display::DisplayTiming& mode) { |
| registers::TranscoderRegs trans_regs(connected_transcoder_id()); |
| |
| // Configure the rest of the transcoder |
| uint32_t h_active = mode.horizontal_active_px - 1; |
| uint32_t h_sync_start = h_active + mode.horizontal_front_porch_px; |
| uint32_t h_sync_end = h_sync_start + mode.horizontal_sync_width_px; |
| uint32_t h_total = h_active + mode.horizontal_blank_px(); |
| |
| uint32_t v_active = mode.vertical_active_lines - 1; |
| uint32_t v_sync_start = v_active + mode.vertical_front_porch_lines; |
| uint32_t v_sync_end = v_sync_start + mode.vertical_sync_width_lines; |
| uint32_t v_total = v_active + mode.vertical_blank_lines(); |
| |
| auto h_total_reg = trans_regs.HTotal().FromValue(0); |
| h_total_reg.set_count_total(h_total); |
| h_total_reg.set_count_active(h_active); |
| h_total_reg.WriteTo(mmio_space_); |
| auto v_total_reg = trans_regs.VTotal().FromValue(0); |
| v_total_reg.set_count_total(v_total); |
| v_total_reg.set_count_active(v_active); |
| v_total_reg.WriteTo(mmio_space_); |
| |
| auto h_sync_reg = trans_regs.HSync().FromValue(0); |
| h_sync_reg.set_sync_start(h_sync_start); |
| h_sync_reg.set_sync_end(h_sync_end); |
| h_sync_reg.WriteTo(mmio_space_); |
| auto v_sync_reg = trans_regs.VSync().FromValue(0); |
| v_sync_reg.set_sync_start(v_sync_start); |
| v_sync_reg.set_sync_end(v_sync_end); |
| v_sync_reg.WriteTo(mmio_space_); |
| |
| // Assume it is not interlacing... |
| trans_regs.VSyncShift() |
| .ReadFrom(mmio_space_) |
| .set_second_field_vsync_shift(0) |
| .WriteTo(mmio_space_); |
| |
| // The Intel docs say that H/VBlank should be programmed with the same H/VTotal |
| trans_regs.HBlank().FromValue(h_total_reg.reg_value()).WriteTo(mmio_space_); |
| trans_regs.VBlank().FromValue(v_total_reg.reg_value()).WriteTo(mmio_space_); |
| |
| registers::PipeRegs pipe_regs(pipe_id()); |
| auto pipe_size = pipe_regs.PipeSourceSize().FromValue(0); |
| pipe_size.set_horizontal_source_size_minus_one(mode.horizontal_active_px - 1); |
| pipe_size.set_vertical_source_size_minus_one(mode.vertical_active_lines - 1); |
| pipe_size.WriteTo(mmio_space_); |
| } |
| |
| void Pipe::LoadActiveMode(display::DisplayTiming* mode) { |
| registers::TranscoderRegs trans_regs(connected_transcoder_id()); |
| |
| auto h_total_reg = trans_regs.HTotal().ReadFrom(mmio_space_); |
| uint32_t h_total = h_total_reg.count_total(); |
| uint32_t h_active = h_total_reg.count_active(); |
| auto v_total_reg = trans_regs.VTotal().ReadFrom(mmio_space_); |
| uint32_t v_total = v_total_reg.count_total(); |
| uint32_t v_active = v_total_reg.count_active(); |
| |
| auto h_sync_reg = trans_regs.HSync().ReadFrom(mmio_space_); |
| uint32_t h_sync_start = h_sync_reg.sync_start(); |
| uint32_t h_sync_end = h_sync_reg.sync_end(); |
| auto v_sync_reg = trans_regs.VSync().ReadFrom(mmio_space_); |
| uint32_t v_sync_start = v_sync_reg.sync_start(); |
| uint32_t v_sync_end = v_sync_reg.sync_end(); |
| |
| mode->horizontal_active_px = h_active + 1; |
| mode->horizontal_front_porch_px = h_sync_start - h_active; |
| mode->horizontal_sync_width_px = h_sync_end - h_sync_start; |
| mode->horizontal_back_porch_px = h_total - h_sync_end; |
| |
| mode->vertical_active_lines = v_active + 1; |
| mode->vertical_front_porch_lines = v_sync_start - v_active; |
| mode->vertical_sync_width_lines = v_sync_end - v_sync_start; |
| mode->vertical_back_porch_lines = v_total - v_sync_end; |
| |
| auto transcoder_ddi_control = trans_regs.DdiControl().ReadFrom(mmio_space_); |
| mode->fields_per_frame = trans_regs.Config().ReadFrom(mmio_space_).interlaced_display() |
| ? display::FieldsPerFrame::kInterlaced |
| : display::FieldsPerFrame::kProgressive; |
| mode->vsync_polarity = transcoder_ddi_control.vsync_polarity_not_inverted() |
| ? display::SyncPolarity::kPositive |
| : display::SyncPolarity::kNegative; |
| mode->hsync_polarity = transcoder_ddi_control.hsync_polarity_not_inverted() |
| ? display::SyncPolarity::kPositive |
| : display::SyncPolarity::kNegative; |
| mode->vblank_alternates = false; |
| mode->pixel_repetition = 0; |
| |
| // If we're reusing hardware state, make sure the pipe source size matches |
| // the display mode size, since we never scale pipes. |
| registers::PipeRegs pipe_regs(pipe_id_); |
| auto pipe_size = pipe_regs.PipeSourceSize().FromValue(0); |
| pipe_size.set_horizontal_source_size_minus_one(mode->horizontal_active_px - 1); |
| pipe_size.set_vertical_source_size_minus_one(mode->vertical_active_lines - 1); |
| pipe_size.WriteTo(mmio_space_); |
| } |
| |
| void Pipe::ApplyConfiguration(const display_config_t* banjo_display_config, |
| display::ConfigStamp config_stamp, |
| const SetupGttImageFunc& get_gtt_region_fn, |
| const GetImagePixelFormatFunc& get_pixel_format) { |
| ZX_ASSERT(banjo_display_config); |
| ZX_ASSERT(config_stamp != display::kInvalidConfigStamp); |
| |
| // The values of the config stamps in `pending_eviction_config_stamps_` must |
| // be strictly increasing. |
| ZX_ASSERT(pending_eviction_config_stamps_.empty() || |
| pending_eviction_config_stamps_.back() < config_stamp); |
| pending_eviction_config_stamps_.push_back(config_stamp); |
| |
| registers::pipe_arming_regs_t regs; |
| registers::PipeRegs pipe_regs(pipe_id_); |
| |
| if (banjo_display_config->cc_flags) { |
| float zero_offset[3] = {}; |
| SetColorConversionOffsets(true, banjo_display_config->cc_flags & COLOR_CONVERSION_PREOFFSET |
| ? banjo_display_config->cc_preoffsets |
| : zero_offset); |
| SetColorConversionOffsets(false, banjo_display_config->cc_flags & COLOR_CONVERSION_POSTOFFSET |
| ? banjo_display_config->cc_postoffsets |
| : zero_offset); |
| |
| float identity[3][3] = { |
| { |
| 1, |
| 0, |
| 0, |
| }, |
| { |
| 0, |
| 1, |
| 0, |
| }, |
| { |
| 0, |
| 0, |
| 1, |
| }, |
| }; |
| for (uint32_t i = 0; i < 3; i++) { |
| for (uint32_t j = 0; j < 3; j++) { |
| float val = banjo_display_config->cc_flags & COLOR_CONVERSION_COEFFICIENTS |
| ? banjo_display_config->cc_coefficients[i][j] |
| : identity[i][j]; |
| |
| auto reg = pipe_regs.CscCoeff(i, j).ReadFrom(mmio_space_); |
| reg.coefficient(i, j).set(float_to_i915_csc_coefficient(val)); |
| reg.WriteTo(mmio_space_); |
| } |
| } |
| } |
| regs.csc_mode = pipe_regs.CscMode().ReadFrom(mmio_space_).reg_value(); |
| |
| auto bottom_color = pipe_regs.PipeBottomColor().FromValue(0); |
| bottom_color.set_csc_enable(!!banjo_display_config->cc_flags); |
| bool has_color_layer = banjo_display_config->layer_count && |
| banjo_display_config->layer_list[0]->type == LAYER_TYPE_COLOR; |
| if (has_color_layer) { |
| const color_layer_t* layer = &banjo_display_config->layer_list[0]->cfg.color; |
| const auto format = static_cast<fuchsia_images2::wire::PixelFormat>(layer->format); |
| ZX_DEBUG_ASSERT(format == fuchsia_images2::wire::PixelFormat::kB8G8R8A8); |
| uint32_t color = *reinterpret_cast<const uint32_t*>(layer->color_list); |
| |
| bottom_color.set_r(encode_pipe_color_component(static_cast<uint8_t>(color >> 16))); |
| bottom_color.set_g(encode_pipe_color_component(static_cast<uint8_t>(color >> 8))); |
| bottom_color.set_b(encode_pipe_color_component(static_cast<uint8_t>(color))); |
| config_stamp_with_color_layer_ = config_stamp; |
| } else { |
| config_stamp_with_color_layer_ = display::kInvalidConfigStamp; |
| } |
| |
| regs.pipe_bottom_color = bottom_color.reg_value(); |
| |
| bool scaler_1_claimed = false; |
| for (unsigned plane = 0; plane < 3; plane++) { |
| const primary_layer_t* primary = nullptr; |
| for (unsigned j = 0; j < banjo_display_config->layer_count; j++) { |
| const layer_t* layer = banjo_display_config->layer_list[j]; |
| if (layer->type == LAYER_TYPE_PRIMARY && (layer->z_index - has_color_layer) == plane) { |
| primary = &layer->cfg.primary; |
| break; |
| } |
| } |
| ConfigurePrimaryPlane(plane, primary, !!banjo_display_config->cc_flags, &scaler_1_claimed, |
| ®s, config_stamp, get_gtt_region_fn, get_pixel_format); |
| } |
| DisableCursorPlane(®s, config_stamp); |
| |
| if (platform_ != registers::Platform::kTigerLake) { |
| pipe_regs.CscMode().FromValue(regs.csc_mode).WriteTo(mmio_space_); |
| } |
| pipe_regs.PipeBottomColor().FromValue(regs.pipe_bottom_color).WriteTo(mmio_space_); |
| pipe_regs.CursorBase().FromValue(regs.cur_base).WriteTo(mmio_space_); |
| pipe_regs.CursorPos().FromValue(regs.cur_pos).WriteTo(mmio_space_); |
| for (unsigned i = 0; i < registers::kImagePlaneCount; i++) { |
| pipe_regs.PlaneSurface(i).FromValue(regs.plane_surf[i]).WriteTo(mmio_space_); |
| } |
| pipe_regs.PipeScalerRegs(/* num= */ 0) |
| .PipeScalerWindowSize() |
| .FromValue(regs.ps_win_sz[0]) |
| .WriteTo(mmio_space_); |
| if (pipe_id_ != PipeId::PIPE_C) { |
| pipe_regs.PipeScalerRegs(/* num= */ 1) |
| .PipeScalerWindowSize() |
| .FromValue(regs.ps_win_sz[1]) |
| .WriteTo(mmio_space_); |
| } |
| } |
| |
| void Pipe::ConfigurePrimaryPlane(uint32_t plane_num, const primary_layer_t* primary, |
| bool enable_csc, bool* scaler_1_claimed, |
| registers::pipe_arming_regs_t* regs, |
| display::ConfigStamp config_stamp, |
| const SetupGttImageFunc& setup_gtt_image, |
| const GetImagePixelFormatFunc& get_pixel_format) { |
| registers::PipeRegs pipe_regs(pipe_id()); |
| |
| auto plane_ctrl = pipe_regs.PlaneControl(plane_num).ReadFrom(mmio_space_); |
| if (primary == nullptr) { |
| plane_ctrl.set_plane_enabled(false).WriteTo(mmio_space_); |
| regs->plane_surf[plane_num] = 0; |
| return; |
| } |
| plane_ctrl.set_decompress_render_compressed_surfaces(false) |
| .set_double_buffer_update_disabling_allowed(true); |
| |
| const image_metadata_t& image_metadata = primary->image_metadata; |
| const GttRegion& region = |
| setup_gtt_image(primary->image_metadata, primary->image_handle, primary->transform_mode); |
| uint32_t base_address = static_cast<uint32_t>(region.base()); |
| uint32_t plane_width; |
| uint32_t plane_height; |
| uint32_t stride; |
| uint32_t x_offset; |
| uint32_t y_offset; |
| if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY || |
| primary->transform_mode == FRAME_TRANSFORM_ROT_180) { |
| plane_width = primary->src_frame.width; |
| plane_height = primary->src_frame.height; |
| stride = |
| [&]() { |
| uint64_t stride = |
| region.bytes_per_row() / get_tile_byte_width(image_metadata.tiling_type); |
| ZX_DEBUG_ASSERT_MSG(stride <= std::numeric_limits<uint32_t>::max(), |
| "%lu overflows uint32_t", stride); |
| return static_cast<uint32_t>(stride); |
| }(), |
| x_offset = primary->src_frame.x_pos; |
| y_offset = primary->src_frame.y_pos; |
| } else { |
| uint32_t tile_height = height_in_tiles(image_metadata.tiling_type, image_metadata.height); |
| uint32_t tile_px_height = get_tile_px_height(image_metadata.tiling_type); |
| uint32_t total_height = tile_height * tile_px_height; |
| |
| plane_width = primary->src_frame.height; |
| plane_height = primary->src_frame.width; |
| stride = tile_height; |
| x_offset = total_height - primary->src_frame.y_pos - primary->src_frame.height; |
| y_offset = primary->src_frame.x_pos; |
| } |
| |
| if (plane_width == primary->dest_frame.width && plane_height == primary->dest_frame.height) { |
| auto plane_pos = pipe_regs.PlanePosition(plane_num).FromValue(0); |
| plane_pos.set_x_pos(primary->dest_frame.x_pos); |
| plane_pos.set_y_pos(primary->dest_frame.y_pos); |
| plane_pos.WriteTo(mmio_space_); |
| |
| // If there's a scaler pointed at this plane, immediately disable it |
| // in case there's nothing else that will claim it this frame. |
| if (scaled_planes_[pipe_id()][plane_num]) { |
| int scaler_num = scaled_planes_[pipe_id()][plane_num] - 1; |
| registers::PipeScalerRegs pipe_scaler_regs(pipe_id_, scaler_num); |
| pipe_scaler_regs.PipeScalerControlSkylake() |
| .ReadFrom(mmio_space_) |
| .set_is_enabled(false) |
| .WriteTo(mmio_space_); |
| scaled_planes_[pipe_id()][plane_num] = 0; |
| regs->ps_win_sz[scaler_num] = 0; |
| } |
| } else { |
| pipe_regs.PlanePosition(plane_num).FromValue(0).WriteTo(mmio_space_); |
| |
| int scaler_num = *scaler_1_claimed ? 1 : 0; |
| registers::PipeScalerRegs pipe_scaler_regs(pipe_id_, scaler_num); |
| |
| auto ps_ctrl = pipe_scaler_regs.PipeScalerControlSkylake().ReadFrom(mmio_space_); |
| ps_ctrl.set_mode(registers::PipeScalerControlSkylake::ScalerMode::kDynamic); |
| if (platform_ != registers::Platform::kTigerLake) { |
| // The mode bits are different in Tiger Lake. |
| if (primary->src_frame.width > 2048) { |
| float max_dynamic_height = |
| static_cast<float>(plane_height) * |
| registers::PipeScalerControlSkylake::kDynamicMaxVerticalRatio2049; |
| if (static_cast<uint32_t>(max_dynamic_height) < primary->dest_frame.height) { |
| // TODO(stevensd): This misses some cases where 7x5 can be used. |
| ps_ctrl.set_mode(registers::PipeScalerControlSkylake::ScalerMode::kDynamic); |
| } |
| } |
| } |
| |
| ps_ctrl.set_scaled_plane_index(plane_num + 1); |
| ps_ctrl.set_is_enabled(1); |
| ps_ctrl.WriteTo(mmio_space_); |
| |
| auto ps_win_pos = pipe_scaler_regs.PipeScalerWindowPosition().FromValue(0); |
| ps_win_pos.set_x_position(primary->dest_frame.x_pos); |
| ps_win_pos.set_x_position(primary->dest_frame.y_pos); |
| ps_win_pos.WriteTo(mmio_space_); |
| |
| auto ps_win_size = pipe_scaler_regs.PipeScalerWindowSize().FromValue(0); |
| ps_win_size.set_x_size(primary->dest_frame.width); |
| ps_win_size.set_y_size(primary->dest_frame.height); |
| regs->ps_win_sz[*scaler_1_claimed] = ps_win_size.reg_value(); |
| |
| scaled_planes_[pipe_id()][plane_num] = (*scaler_1_claimed) + 1; |
| *scaler_1_claimed = true; |
| } |
| |
| auto plane_size = pipe_regs.PlaneSurfaceSize(plane_num).FromValue(0); |
| plane_size.set_width_minus_1(plane_width - 1); |
| plane_size.set_height_minus_1(plane_height - 1); |
| plane_size.WriteTo(mmio_space_); |
| |
| auto plane_offset = pipe_regs.PlaneOffset(plane_num).FromValue(0); |
| plane_offset.set_start_x(x_offset); |
| plane_offset.set_start_y(y_offset); |
| plane_offset.WriteTo(mmio_space_); |
| |
| auto stride_reg = pipe_regs.PlaneSurfaceStride(plane_num).FromValue(0); |
| stride_reg.set_stride(stride); |
| stride_reg.WriteTo(mmio_space_); |
| |
| registers::PlaneControlAlphaMode alpha_mode; |
| if (primary->alpha_mode == ALPHA_DISABLE) { |
| alpha_mode = registers::PlaneControlAlphaMode::kAlphaIgnored; |
| } else if (primary->alpha_mode == ALPHA_PREMULTIPLIED) { |
| alpha_mode = registers::PlaneControlAlphaMode::kAlphaPreMultiplied; |
| } else { |
| ZX_ASSERT(primary->alpha_mode == ALPHA_HW_MULTIPLY); |
| alpha_mode = registers::PlaneControlAlphaMode::kAlphaHardwareMultiply; |
| } |
| |
| if (platform_ == registers::Platform::kTigerLake) { |
| auto plane_color_ctl = pipe_regs.PlaneColorControlTigerLake(plane_num).ReadFrom(mmio_space_); |
| plane_color_ctl.set_pipe_gamma_enabled_deprecated(false) |
| .set_pipe_csc_enabled_deprecated(enable_csc) |
| .set_plane_input_csc_enabled(false) |
| .set_pre_csc_gamma_enabled(false) |
| .set_post_csc_gamma_disabled(true) |
| .set_alpha_mode(alpha_mode) |
| .WriteTo(mmio_space_); |
| } |
| |
| auto plane_key_mask = pipe_regs.PlaneKeyMask(plane_num).FromValue(0); |
| if (primary->alpha_mode != ALPHA_DISABLE && !isnan(primary->alpha_layer_val)) { |
| plane_key_mask.set_plane_alpha_enable(1); |
| |
| uint8_t alpha = static_cast<uint8_t>(round(primary->alpha_layer_val * 255)); |
| |
| auto plane_key_max = pipe_regs.PlaneKeyMax(plane_num).FromValue(0); |
| plane_key_max.set_plane_alpha_value(alpha); |
| plane_key_max.WriteTo(mmio_space_); |
| } |
| plane_key_mask.WriteTo(mmio_space_); |
| |
| plane_ctrl.set_plane_enabled(true); |
| if (platform_ != registers::Platform::kTigerLake) { |
| plane_ctrl.set_pipe_csc_enabled_kaby_lake(enable_csc).set_alpha_mode_kaby_lake(alpha_mode); |
| } |
| if (platform_ == registers::Platform::kTigerLake) { |
| plane_ctrl.set_source_pixel_format_tiger_lake( |
| registers::PlaneControl::ColorFormatTigerLake::kRgb8888); |
| } else { |
| plane_ctrl.set_source_pixel_format_kaby_lake( |
| registers::PlaneControl::ColorFormatKabyLake::kRgb8888); |
| } |
| |
| PixelFormatAndModifier pixel_format = get_pixel_format(primary->image_handle); |
| switch (pixel_format.pixel_format) { |
| case fuchsia_images2::PixelFormat::kR8G8B8A8: |
| plane_ctrl.set_rgb_color_order(registers::PlaneControl::RgbColorOrder::kRgbx); |
| break; |
| case fuchsia_images2::PixelFormat::kB8G8R8A8: |
| plane_ctrl.set_rgb_color_order(registers::PlaneControl::RgbColorOrder::kBgrx); |
| break; |
| default: |
| // This should not happen. The sysmem-negotiated pixel format type can |
| // only be RGBA or BGRA. |
| // TODO(https://fxbug.dev/42076788): Support other formats. |
| ZX_ASSERT_MSG(false, |
| "Sysmem-negotiated pixel format %u does not meet the constraints we placed", |
| static_cast<uint32_t>(pixel_format.pixel_format)); |
| } |
| |
| if (image_metadata.tiling_type == IMAGE_TILING_TYPE_LINEAR) { |
| plane_ctrl.set_surface_tiling(registers::PlaneControl::SurfaceTiling::kLinear); |
| } else if (image_metadata.tiling_type == IMAGE_TILING_TYPE_X_TILED) { |
| plane_ctrl.set_surface_tiling(registers::PlaneControl::SurfaceTiling::kTilingX); |
| } else if (image_metadata.tiling_type == IMAGE_TILING_TYPE_Y_LEGACY_TILED) { |
| plane_ctrl.set_surface_tiling(registers::PlaneControl::SurfaceTiling::kTilingYLegacy); |
| } else { |
| ZX_ASSERT(image_metadata.tiling_type == IMAGE_TILING_TYPE_YF_TILED); |
| if (platform_ == registers::Platform::kTigerLake) { |
| // TODO(https://fxbug.dev/42062668): Remove this warning or turn it into an error. |
| zxlogf(ERROR, "The Tiger Lake display engine may not support YF tiling."); |
| } |
| plane_ctrl.set_surface_tiling(registers::PlaneControl::SurfaceTiling::kTilingYFKabyLake); |
| } |
| if (primary->transform_mode == FRAME_TRANSFORM_IDENTITY) { |
| plane_ctrl.set_rotation(registers::PlaneControl::Rotation::kIdentity); |
| } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_90) { |
| plane_ctrl.set_rotation(registers::PlaneControl::Rotation::k90degrees); |
| } else if (primary->transform_mode == FRAME_TRANSFORM_ROT_180) { |
| plane_ctrl.set_rotation(registers::PlaneControl::Rotation::k180degrees); |
| } else { |
| ZX_ASSERT(primary->transform_mode == FRAME_TRANSFORM_ROT_270); |
| plane_ctrl.set_rotation(registers::PlaneControl::Rotation::k270degrees); |
| } |
| plane_ctrl.WriteTo(mmio_space_); |
| |
| auto plane_surface = pipe_regs.PlaneSurface(plane_num).ReadFrom(mmio_space_); |
| plane_surface.set_surface_base_addr(base_address >> plane_surface.kRShiftCount); |
| regs->plane_surf[plane_num] = plane_surface.reg_value(); |
| |
| latest_config_stamp_with_image_[primary->image_handle] = config_stamp; |
| } |
| |
| void Pipe::DisableCursorPlane(registers::pipe_arming_regs* regs, |
| display::ConfigStamp config_stamp) { |
| registers::PipeRegs pipe_regs(pipe_id()); |
| |
| auto cursor_ctrl = pipe_regs.CursorCtrl().ReadFrom(mmio_space_); |
| cursor_ctrl.set_mode_select(cursor_ctrl.kDisabled).WriteTo(mmio_space_); |
| regs->cur_base = regs->cur_pos = 0; |
| } |
| |
| display::ConfigStamp Pipe::GetVsyncConfigStamp(const std::vector<uint64_t>& image_handles) { |
| display::ConfigStamp oldest_config_stamp = display::kInvalidConfigStamp; |
| |
| if (config_stamp_with_color_layer_ != display::kInvalidConfigStamp) { |
| oldest_config_stamp = config_stamp_with_color_layer_; |
| } |
| for (const uint64_t handle : image_handles) { |
| auto config_it = latest_config_stamp_with_image_.find(handle); |
| if (config_it == latest_config_stamp_with_image_.end()) { |
| continue; |
| } |
| |
| if (oldest_config_stamp != display::kInvalidConfigStamp) { |
| oldest_config_stamp = std::min(oldest_config_stamp, config_it->second); |
| } else { |
| oldest_config_stamp = config_it->second; |
| } |
| } |
| |
| if (oldest_config_stamp == display::kInvalidConfigStamp) { |
| // Display device may carry garbage contents in the registers, for example |
| // if the driver restarted. In that case none of the images stored in the |
| // device register will be recognized by the driver, so we just return a |
| // null config stamp to ignore it. |
| zxlogf(DEBUG, "%s: NO valid images for the display.", __func__); |
| return display::kInvalidConfigStamp; |
| } |
| if (pending_eviction_config_stamps_.empty()) { |
| // Vsync signals could be sent to the driver before the first |
| // ApplyConfiguration() is called. In that case the Vsync signal should be |
| // just ignored by the driver, so we return a null config stamp. |
| zxlogf(DEBUG, "%s: No config has been applied.", __func__); |
| return display::kInvalidConfigStamp; |
| } |
| if (pending_eviction_config_stamps_.front() > oldest_config_stamp) { |
| zxlogf(ERROR, "%s: Device returns a config (%lu) that is already evicted.", __func__, |
| oldest_config_stamp.value()); |
| return display::kInvalidConfigStamp; |
| } |
| |
| // Evict all pending config stamps older than the current one from Vsync. |
| while (!pending_eviction_config_stamps_.empty() && |
| pending_eviction_config_stamps_.front() < oldest_config_stamp) { |
| pending_eviction_config_stamps_.pop_front(); |
| } |
| |
| ZX_DEBUG_ASSERT(!pending_eviction_config_stamps_.empty()); |
| return pending_eviction_config_stamps_.front(); |
| } |
| |
| void Pipe::SetColorConversionOffsets(bool preoffsets, const float vals[3]) { |
| registers::PipeRegs pipe_regs(pipe_id()); |
| |
| for (uint32_t i = 0; i < 3; i++) { |
| float offset = vals[i]; |
| auto offset_reg = pipe_regs.CscOffset(preoffsets, i).FromValue(0); |
| if (offset < 0) { |
| offset_reg.set_sign(1); |
| offset *= -1; |
| } |
| offset_reg.set_magnitude(float_to_i915_csc_offset(offset)); |
| offset_reg.WriteTo(mmio_space_); |
| } |
| } |
| |
| } // namespace i915 |