blob: 328b9465bf2fd79b43d13492e9b207461f25a604 [file] [log] [blame]
// Copyright 2019 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 "aml-clk.h"
#include <lib/ddk/platform-defs.h>
#include <lib/mmio-ptr/fake.h>
#include <lib/mmio/mmio-buffer.h>
#include <soc/aml-a311d/a311d-hw.h>
#include <soc/aml-meson/aml-clk-common.h>
#include <soc/aml-meson/axg-clk.h>
#include <soc/aml-meson/g12a-clk.h>
#include <soc/aml-meson/sm1-clk.h>
#include <soc/aml-s905d2/s905d2-hw.h>
#include <soc/aml-s905d3/s905d3-hw.h>
#include <soc/aml-s912/s912-hw.h>
#include <zxtest/zxtest.h>
#include "aml-axg-blocks.h"
#include "aml-g12a-blocks.h"
#include "aml-g12b-blocks.h"
#include "aml-sm1-blocks.h"
#include "src/devices/lib/mmio/test-helper.h"
namespace amlogic_clock {
namespace {
constexpr uint64_t kilohertz(uint64_t hz) { return hz * 1000; }
constexpr uint64_t megahertz(uint64_t hz) { return kilohertz(hz) * 1000; }
constexpr uint64_t gigahertz(uint64_t hz) { return megahertz(hz) * 1000; }
constexpr uint32_t kCpuClkSupportedFrequencies[] = {
100'000'000, 250'000'000, 500'000'000, 667'000'000, 1'000'000'000, 1'200'000'000,
1'398'000'000, 1'512'000'000, 1'608'000'000, 1'704'000'000, 1'896'000'000,
};
} // namespace
class AmlClockTest : public AmlClock {
public:
AmlClockTest(fdf::MmioBuffer mmio_buffer, fdf::MmioBuffer dosbus_buffer, uint32_t did)
: AmlClock(nullptr, std::move(mmio_buffer), std::move(dosbus_buffer), std::nullopt,
std::nullopt, did) {}
~AmlClockTest() = default;
};
bool MmioMemcmp(fdf::MmioBuffer& actual, std::unique_ptr<uint8_t[]>& expected) {
for (size_t i = 0; i < actual.get_size(); i++) {
if (actual.Read8(i) != expected[i]) {
return true;
}
}
return false;
}
std::tuple<fdf::MmioView, fdf::MmioBuffer> MakeDosbusMmio() {
auto dos_buffer = fdf_testing::CreateMmioBuffer(S912_DOS_LENGTH);
return std::make_tuple(dos_buffer.View(0), std::move(dos_buffer));
}
TEST(ClkTestAml, AxgEnableDisableAll) {
auto expected = std::make_unique<uint8_t[]>(S912_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S912_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_AXG_CLK);
// Initialization sets a bunch of registers that we don't care about, so we
// can reset the array to a clean slate.
memset(expected.get(), 0, S912_HIU_LENGTH);
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
constexpr uint16_t kClkStart = 0;
constexpr uint16_t kClkEnd = static_cast<uint16_t>(axg_clk::CLK_AXG_COUNT);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (axg_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = axg_clk_gates[i].reg;
const uint32_t bit = (1u << axg_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) |= bit;
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplEnable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (axg_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = axg_clk_gates[i].reg;
const uint32_t bit = (1u << axg_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) &= ~(bit);
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplDisable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
}
TEST(ClkTestAml, G12aEnableDisableAll) {
auto expected = std::make_unique<uint8_t[]>(S912_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S912_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
// Initialization sets a bunch of registers that we don't care about, so we
// can reset the array to a clean slate.
memset(expected.get(), 0, S905D2_HIU_LENGTH);
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
constexpr uint16_t kClkStart = 0;
constexpr uint16_t kClkEnd = static_cast<uint16_t>(g12a_clk::CLK_G12A_COUNT);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (g12a_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = g12a_clk_gates[i].reg;
const uint32_t bit = (1u << g12a_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) |= bit;
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplEnable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (g12a_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = g12a_clk_gates[i].reg;
const uint32_t bit = (1u << g12a_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) &= ~(bit);
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplDisable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
}
TEST(ClkTestAml, Sm1EnableDisableAll) {
auto expected = std::make_unique<uint8_t[]>(S905D3_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D3_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_SM1_CLK);
// Initialization sets a bunch of registers that we don't care about, so we
// can reset the array to a clean slate.
memset(expected.get(), 0, S905D3_HIU_LENGTH);
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
constexpr uint16_t kClkStart = 0;
constexpr uint16_t kClkEnd = static_cast<uint16_t>(sm1_clk::CLK_SM1_GATE_COUNT);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (sm1_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = sm1_clk_gates[i].reg;
const uint32_t bit = (1u << sm1_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) |= bit;
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplEnable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
for (uint16_t i = kClkStart; i < kClkEnd; ++i) {
if (sm1_clk_gates[i].register_set != kMesonRegisterSetHiu)
continue;
const uint32_t reg = sm1_clk_gates[i].reg;
const uint32_t bit = (1u << sm1_clk_gates[i].bit);
uint32_t* ptr = reinterpret_cast<uint32_t*>(&expected[reg]);
(*ptr) &= ~(bit);
const uint32_t clk_i = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonGate);
zx_status_t st = clk.ClockImplDisable(clk_i);
EXPECT_OK(st);
}
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
}
TEST(ClkTestAml, G12aEnableDos) {
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
zx_status_t st = clk.ClockImplEnable(g12a_clk::CLK_DOS_GCLK_VDEC);
EXPECT_OK(st);
EXPECT_EQ(0x3ff, dos_data.Read32(0x3f01 * sizeof(uint32_t)));
}
TEST(ClkTestAml, G12bEnableAudio) {
auto buffer = fdf_testing::CreateMmioBuffer(A311D_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12B_CLK);
zx_status_t st = clk.ClockImplEnable(g12b_clk::G12B_CLK_AUDIO);
EXPECT_OK(st);
constexpr uint32_t kHhiGclkMpeg1Offset = 0x51;
EXPECT_EQ(0x1, actual.Read32(kHhiGclkMpeg1Offset * sizeof(uint32_t)));
}
TEST(ClkTestAml, G12bEnableEmmcC) {
auto buffer = fdf_testing::CreateMmioBuffer(A311D_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12B_CLK);
zx_status_t st = clk.ClockImplEnable(g12b_clk::G12B_CLK_EMMC_C);
EXPECT_OK(st);
constexpr uint32_t kHhiGclkMpeg0Offset = 0x50;
EXPECT_EQ(0x1 << 26, actual.Read32(kHhiGclkMpeg0Offset * sizeof(uint32_t)));
}
static void TestPlls(const uint32_t did) {
auto ignored = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), did);
constexpr uint16_t kPllStart = 0;
constexpr uint16_t kPllEnd = HIU_PLL_COUNT;
for (uint16_t i = kPllStart; i < kPllEnd; i++) {
const uint32_t clkid = aml_clk_common::AmlClkId(i, aml_clk_common::aml_clk_type::kMesonPll);
zx_status_t st;
uint64_t best_supported_rate;
constexpr uint64_t kMaxRateHz = gigahertz(1);
st = clk.ClockImplQuerySupportedRate(clkid, kMaxRateHz, &best_supported_rate);
EXPECT_OK(st);
EXPECT_LE(best_supported_rate, kMaxRateHz);
st = clk.ClockImplSetRate(clkid, best_supported_rate);
EXPECT_OK(st);
}
}
TEST(ClkTestAml, G12aSetRate) { TestPlls(PDEV_DID_AMLOGIC_G12A_CLK); }
TEST(ClkTestAml, G12bSetRate) { TestPlls(PDEV_DID_AMLOGIC_G12B_CLK); }
TEST(ClkTestAml, Sm1MuxRo) {
auto buffer = fdf_testing::CreateMmioBuffer(S905D3_HIU_LENGTH);
auto regs = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_SM1_CLK);
// Ensure that SetInput fails for RO muxes.
zx_status_t st = clk.ClockImplSetInput(sm1_clk::CLK_MPEG_CLK_SEL, 0);
EXPECT_NOT_OK(st);
// Make sure we can read the number of parents.
uint32_t out_num_inputs = 0;
st = clk.ClockImplGetNumInputs(sm1_clk::CLK_MPEG_CLK_SEL, &out_num_inputs);
EXPECT_OK(st);
EXPECT_GT(out_num_inputs, 0);
// Make sure that we can read the current parent of the mux.
uint32_t out_input = UINT32_MAX;
st = clk.ClockImplGetInput(sm1_clk::CLK_MPEG_CLK_SEL, &out_input);
EXPECT_OK(st);
EXPECT_NE(out_input, UINT32_MAX);
// Also ensure that we didn't whack any registers for a read-only mux.
for (size_t i = 0; i < S905D3_HIU_LENGTH; i++) {
EXPECT_EQ(0, regs.Read8(i));
}
}
TEST(ClkTestAml, Sm1Mux) {
constexpr uint32_t kTestMux = sm1_clk::CLK_CTS_VIPNANOQ_AXI_CLK_MUX;
constexpr uint16_t kTestMuxIdx = aml_clk_common::AmlClkIndex(kTestMux);
const meson_clk_mux_t& test_mux = sm1_muxes[kTestMuxIdx];
auto buffer = fdf_testing::CreateMmioBuffer(S905D3_HIU_LENGTH);
auto regs = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_SM1_CLK);
const uint32_t newParentIdx = test_mux.n_inputs - 1;
zx_status_t st = clk.ClockImplSetInput(kTestMux, newParentIdx);
EXPECT_OK(st);
const uint32_t actual_regval = regs.Read32((test_mux.reg >> 2) * sizeof(uint32_t));
const uint32_t expected_regval = (newParentIdx & test_mux.mask) << test_mux.shift;
EXPECT_EQ(expected_regval, actual_regval);
// Make sure we can read the number of parents.
uint32_t out_num_inputs = 0;
st = clk.ClockImplGetNumInputs(kTestMux, &out_num_inputs);
EXPECT_OK(st);
EXPECT_EQ(out_num_inputs, test_mux.n_inputs);
// Make sure that we can read the current parent of the mux.
uint32_t out_input = UINT32_MAX;
st = clk.ClockImplGetInput(kTestMux, &out_input);
EXPECT_OK(st);
EXPECT_EQ(out_input, newParentIdx);
}
TEST(ClkTestAml, TestCpuClkSetRate) {
constexpr uint32_t kTestCpuClk = g12a_clk::CLK_SYS_CPU_CLK;
auto regs = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
zx_status_t st;
for (size_t i = 0; i < std::size(kCpuClkSupportedFrequencies); i++) {
st = clk.ClockImplSetRate(kTestCpuClk, kCpuClkSupportedFrequencies[i]);
EXPECT_OK(st);
st = clk.ClockImplSetRate(kTestCpuClk, kCpuClkSupportedFrequencies[i] + 1);
EXPECT_NOT_OK(st);
}
}
TEST(ClkTestAml, TestCpuClkQuerySupportedRates) {
constexpr uint32_t kTestCpuClk = g12a_clk::CLK_SYS_CPU_CLK;
constexpr uint32_t kJustOver1GHz = gigahertz(1) + 1;
auto regs = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
uint64_t rate;
zx_status_t st = clk.ClockImplQuerySupportedRate(kTestCpuClk, kJustOver1GHz, &rate);
EXPECT_OK(st);
EXPECT_EQ(rate, gigahertz(1));
}
TEST(ClkTestAml, TestCpuClkGetRate) {
constexpr uint32_t kTestCpuClk = g12a_clk::CLK_SYS_CPU_CLK;
constexpr uint32_t kOneGHz = gigahertz(1);
auto regs = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
zx_status_t st;
st = clk.ClockImplSetRate(kTestCpuClk, kOneGHz);
EXPECT_OK(st);
uint64_t rate;
st = clk.ClockImplGetRate(kTestCpuClk, &rate);
EXPECT_OK(st);
EXPECT_EQ(rate, kOneGHz);
}
TEST(ClkTestAml, TestCpuClkG12b) {
constexpr uint32_t kTestCpuBigClk = g12b_clk::CLK_SYS_CPU_BIG_CLK;
constexpr uint32_t kTestCpuLittleClk = g12b_clk::CLK_SYS_CPU_LITTLE_CLK;
constexpr uint32_t kBigClockTestFreq = gigahertz(1);
constexpr uint32_t kLittleClockTestFreq = megahertz(1800);
auto regs = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12B_CLK);
zx_status_t st;
st = clk.ClockImplSetRate(kTestCpuBigClk, kBigClockTestFreq);
EXPECT_OK(st);
uint64_t rate;
st = clk.ClockImplGetRate(kTestCpuBigClk, &rate);
EXPECT_OK(st);
EXPECT_EQ(rate, kBigClockTestFreq);
st = clk.ClockImplSetRate(kTestCpuLittleClk, kLittleClockTestFreq);
EXPECT_OK(st);
st = clk.ClockImplGetRate(kTestCpuLittleClk, &rate);
EXPECT_OK(st);
EXPECT_EQ(rate, kLittleClockTestFreq);
}
TEST(ClkTestAml, DisableRefZero) {
// Attempts to disable a clock that has never been enabled.
// Confirm that this is fatal.
auto expected = std::make_unique<uint8_t[]>(S905D2_HIU_LENGTH);
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
// Initialization sets a bunch of registers that we don't care about, so we
// can reset the array to a clean slate.
memset(expected.get(), 0, S905D2_HIU_LENGTH);
EXPECT_EQ(MmioMemcmp(actual, expected), 0);
constexpr uint16_t kTestClock = 0;
const uint32_t clk_id =
aml_clk_common::AmlClkId(kTestClock, aml_clk_common::aml_clk_type::kMesonGate);
ASSERT_DEATH(([&clk]() { clk.ClockImplDisable(clk_id); }),
"Failed to crash when trying to disable already disabled clock.");
}
TEST(ClkTestAml, EnableDisableRefCount) {
zx_status_t st;
auto buffer = fdf_testing::CreateMmioBuffer(S905D2_HIU_LENGTH);
auto actual = buffer.View(0);
auto [dos_data, dos_buffer] = MakeDosbusMmio();
AmlClockTest clk(std::move(buffer), std::move(dos_buffer), PDEV_DID_AMLOGIC_G12A_CLK);
// Initialization sets a bunch of registers that we don't care about, so we
// can reset the array to a clean slate.
constexpr uint16_t kTestClock = 0;
constexpr uint32_t kTestClockId =
aml_clk_common::AmlClkId(kTestClock, aml_clk_common::aml_clk_type::kMesonGate);
constexpr uint32_t reg = g12a_clk_gates[kTestClock].reg;
constexpr uint32_t bit = (1u << g12a_clk_gates[kTestClock].bit);
// Enable the test clock and verify that the register was written.
st = clk.ClockImplEnable(kTestClockId);
EXPECT_OK(st);
EXPECT_EQ(actual.Read32(reg), bit);
// Zero out the register and enable the clock again. Since the driver
// should already think the clock is enabled, this should be a no-op.
actual.Write32(0, reg);
st = clk.ClockImplEnable(kTestClockId);
EXPECT_OK(st);
EXPECT_EQ(actual.Read32(reg), 0); // Make sure the driver didn't touch the register.
// This time we fill the registers with Fs and try disabling the clock. Since
// it's been enabled twice, the first disable should only drop a ref rather than
// actually disabling clock hardware.
actual.Write32(0xffffffff, reg);
st = clk.ClockImplDisable(kTestClockId);
EXPECT_OK(st);
EXPECT_EQ(actual.Read32(reg), 0xffffffff); // Make sure the driver didn't touch the register.
// The second call to disable should actually disable the clock hardware.
actual.Write32(0xffffffff, reg);
st = clk.ClockImplDisable(kTestClockId);
EXPECT_OK(st);
EXPECT_EQ(actual.Read32(reg),
(0xffffffff & (~bit))); // Make sure the driver actually disabled the hardware.
}
} // namespace amlogic_clock