| // Copyright 2023 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/amlogic-display/hdmi-transmitter.h" |
| |
| #include <lib/zx/result.h> |
| |
| #include <algorithm> |
| #include <iterator> |
| #include <list> |
| #include <memory> |
| |
| #include <gtest/gtest.h> |
| #include <mock-mmio-range/mock-mmio-range.h> |
| |
| #include "src/graphics/display/lib/api-types-cpp/display-timing.h" |
| #include "src/graphics/display/lib/designware-hdmi/hdmi-transmitter-controller.h" |
| #include "src/lib/testing/predicates/status.h" |
| |
| namespace amlogic_display { |
| |
| enum class HdmiTransmitterControllerCall : uint8_t { |
| kConfigHdmitx, |
| kSetupInterrupts, |
| kReset, |
| kSetupScdc, |
| kResetFc, |
| kSetFcScramblerCtrl, |
| }; |
| |
| // TODO(https://fxbug.dev/42085847): Consider replacing the mock class with a fake |
| // HdmiTransmitterController implementation. |
| class MockHdmiTransmitterController : public designware_hdmi::HdmiTransmitterController { |
| public: |
| MockHdmiTransmitterController() = default; |
| ~MockHdmiTransmitterController() { EXPECT_TRUE(expected_calls_.empty()); } |
| |
| zx_status_t InitHw() override { return ZX_OK; } |
| zx_status_t EdidTransfer(const i2c_impl_op_t* op_list, size_t op_count) override { return ZX_OK; } |
| |
| void ConfigHdmitx(const designware_hdmi::ColorParam& color_param, |
| const display::DisplayTiming& mode, |
| const designware_hdmi::hdmi_param_tx& p) override { |
| OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kConfigHdmitx); |
| } |
| void SetupInterrupts() override { |
| OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kSetupInterrupts); |
| } |
| void Reset() override { OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kReset); } |
| void SetupScdc(bool is4k) override { |
| OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kSetupScdc); |
| } |
| void ResetFc() override { |
| OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kResetFc); |
| } |
| void SetFcScramblerCtrl(bool is4k) override { |
| OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall::kSetFcScramblerCtrl); |
| } |
| |
| void OnHdmiTransmitterControllerCall(HdmiTransmitterControllerCall call) { |
| ASSERT_FALSE(expected_calls_.empty()); |
| EXPECT_EQ(expected_calls_.front(), call); |
| expected_calls_.pop_front(); |
| } |
| |
| void ExpectCalls(const cpp20::span<const HdmiTransmitterControllerCall> expected_calls) { |
| std::copy(expected_calls.begin(), expected_calls.end(), std::back_inserter(expected_calls_)); |
| } |
| |
| void PrintRegisters() override {} |
| |
| private: |
| std::list<HdmiTransmitterControllerCall> expected_calls_; |
| }; |
| |
| class HdmiTransmitterTest : public testing::Test { |
| public: |
| void SetUp() override { |
| std::unique_ptr<MockHdmiTransmitterController> mock_hdmitx_controller = |
| std::make_unique<MockHdmiTransmitterController>(); |
| mock_hdmitx_controller_ = mock_hdmitx_controller.get(); |
| |
| // TODO(https://fxbug.dev/42074342): Use a fake SMC resource, when the |
| // implementation lands. |
| dut_ = std::make_unique<HdmiTransmitter>(std::move(mock_hdmitx_controller), |
| top_level_mmio_range_.GetMmioBuffer(), |
| /*smc=*/zx::resource{}); |
| ASSERT_TRUE(dut_); |
| } |
| |
| void TearDown() override { top_level_mmio_range_.CheckAllAccessesReplayed(); } |
| |
| protected: |
| constexpr static int kTopLevelMmioRangeSize = 0x8000; |
| ddk_mock::MockMmioRange top_level_mmio_range_{kTopLevelMmioRangeSize, |
| ddk_mock::MockMmioRange::Size::k32}; |
| |
| std::unique_ptr<HdmiTransmitter> dut_; |
| |
| // Owned by `dut_`. |
| MockHdmiTransmitterController* mock_hdmitx_controller_ = nullptr; |
| }; |
| |
| // Register addresses from the A311D datasheet section 10.2.3.44 "HDCP2.2 IP |
| // Register Access". |
| constexpr int kHdmiTxTopSwResetOffset = 0x00 * 4; |
| constexpr int kHdmiTxTopClkCntlOffset = 0x01 * 4; |
| constexpr int kHdmiTxTopIntrMaskn = 0x03 * 4; |
| constexpr int kHdmiTxTopIntrStatClr = 0x05 * 4; |
| constexpr int kHdmiTxTopBistCntl = 0x06 * 4; |
| constexpr int kHdmiTxTopTmdsClkPttn01 = 0x0a * 4; |
| constexpr int kHdmiTxTopTmdsClkPttn23 = 0x0b * 4; |
| constexpr int kHdmiTxTopTmdsClkPttnCntl = 0x0c * 4; |
| |
| TEST_F(HdmiTransmitterTest, ResetTest) { |
| top_level_mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({ |
| {.address = kHdmiTxTopSwResetOffset, .value = 0, .write = true}, |
| {.address = kHdmiTxTopClkCntlOffset, .value = 0xff, .write = true}, |
| })); |
| zx::result<> result = dut_->Reset(); |
| EXPECT_OK(result.status_value()); |
| } |
| |
| TEST_F(HdmiTransmitterTest, ModeSetTest) { |
| // TODO(https://fxbug.dev/42085738): Use valid synthetic values for timings. |
| display::DisplayTiming display_timing = { |
| .horizontal_active_px = 0, |
| .horizontal_front_porch_px = 0, |
| .horizontal_sync_width_px = 0, |
| .horizontal_back_porch_px = 0, |
| .vertical_active_lines = 0, |
| .vertical_front_porch_lines = 0, |
| .vertical_sync_width_lines = 0, |
| .vertical_back_porch_lines = 0, |
| .pixel_clock_frequency_hz = 0, |
| .fields_per_frame = display::FieldsPerFrame::kProgressive, |
| .hsync_polarity = display::SyncPolarity::kNegative, |
| .vsync_polarity = display::SyncPolarity::kNegative, |
| .vblank_alternates = false, |
| .pixel_repetition = 0, |
| }; |
| designware_hdmi::ColorParam color_param = { |
| .input_color_format = designware_hdmi::ColorFormat::kCfRgb, |
| .output_color_format = designware_hdmi::ColorFormat::kCfRgb, |
| .color_depth = designware_hdmi::ColorDepth::kCd24B, |
| }; |
| |
| top_level_mmio_range_.Expect(ddk_mock::MockMmioRange::AccessList({ |
| {.address = kHdmiTxTopBistCntl, .value = 1 << 12, .write = true}, |
| {.address = kHdmiTxTopIntrStatClr, .value = 0x1f, .write = true}, |
| {.address = kHdmiTxTopIntrMaskn, .value = 0x9f, .write = true}, |
| {.address = kHdmiTxTopTmdsClkPttn01, .value = 0x001f'001f, .write = true}, |
| {.address = kHdmiTxTopTmdsClkPttn23, .value = 0x001f'001f, .write = true}, |
| {.address = kHdmiTxTopTmdsClkPttnCntl, .value = 0x01, .write = true}, |
| {.address = kHdmiTxTopTmdsClkPttnCntl, .value = 0x02, .write = true}, |
| })); |
| |
| static constexpr HdmiTransmitterControllerCall kExpectedCalls[] = { |
| HdmiTransmitterControllerCall::kConfigHdmitx, |
| HdmiTransmitterControllerCall::kSetupInterrupts, |
| HdmiTransmitterControllerCall::kReset, |
| HdmiTransmitterControllerCall::kSetFcScramblerCtrl, |
| HdmiTransmitterControllerCall::kSetupScdc, |
| HdmiTransmitterControllerCall::kResetFc, |
| }; |
| mock_hdmitx_controller_->ExpectCalls(kExpectedCalls); |
| |
| zx::result<> result = dut_->ModeSet(display_timing, color_param); |
| EXPECT_OK(result.status_value()); |
| } |
| |
| } // namespace amlogic_display |