blob: f79b473fc840690e6b7e25e3a8b98eeabcc161aa [file] [log] [blame]
// Copyright 2024 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.
#ifndef SRC_DEVICES_SPI_DRIVERS_AML_SPI_TESTS_AML_SPI_TEST_ENV_H_
#define SRC_DEVICES_SPI_DRIVERS_AML_SPI_TESTS_AML_SPI_TEST_ENV_H_
#include <endian.h>
#include <fidl/fuchsia.hardware.gpio/cpp/wire_test_base.h>
#include <fidl/fuchsia.hardware.platform.device/cpp/wire_test_base.h>
#include <lib/ddk/metadata.h>
#include <lib/driver/fake-bti/cpp/fake-bti.h>
#include <lib/driver/fake-mmio-reg/cpp/fake-mmio-reg.h>
#include <lib/driver/fake-platform-device/cpp/fake-pdev.h>
#include <lib/driver/testing/cpp/driver_test.h>
#include <lib/fpromise/promise.h>
#include <lib/zx/clock.h>
#include <lib/zx/vmo.h>
#include <zircon/errors.h>
#include <gtest/gtest.h>
#include "src/devices/registers/testing/mock-registers/mock-registers.h"
#include "src/devices/spi/drivers/aml-spi/aml-spi.h"
#include "src/devices/spi/drivers/aml-spi/registers.h"
#include "src/lib/testing/predicates/status.h"
namespace spi {
class TestAmlSpiDriver : public AmlSpiDriver {
public:
TestAmlSpiDriver(fdf::DriverStartArgs start_args, fdf::UnownedSynchronizedDispatcher dispatcher)
: AmlSpiDriver(std::move(start_args), std::move(dispatcher)),
mmio_region_(sizeof(uint32_t), 17) {}
static DriverRegistration GetDriverRegistration() {
// Use a custom DriverRegistration to create the DUT. Without this, the non-test
// implementation will be used by default.
return FUCHSIA_DRIVER_REGISTRATION_V1(fdf_internal::DriverServer<TestAmlSpiDriver>::initialize,
fdf_internal::DriverServer<TestAmlSpiDriver>::destroy);
}
fake_mmio::FakeMmioRegRegion& mmio() { return mmio_region_; }
uint32_t conreg() const { return conreg_; }
uint32_t enhance_cntl() const { return enhance_cntl_; }
uint32_t testreg() const { return testreg_; }
protected:
fpromise::promise<fdf::MmioBuffer, zx_status_t> MapMmio(
fidl::WireClient<fuchsia_hardware_platform_device::Device>& pdev, uint32_t mmio_id) override {
return fpromise::make_promise([this]() -> fpromise::result<fdf::MmioBuffer, zx_status_t> {
// Set the transfer complete bit so the driver doesn't get stuck waiting on the interrupt.
mmio_region_[AML_SPI_STATREG].SetReadCallback(
[]() { return StatReg::Get().FromValue(0).set_tc(1).set_te(1).set_rr(1).reg_value(); });
mmio_region_[AML_SPI_CONREG].SetWriteCallback([this](uint32_t value) { conreg_ = value; });
mmio_region_[AML_SPI_CONREG].SetReadCallback([this]() { return conreg_; });
mmio_region_[AML_SPI_ENHANCE_CNTL].SetWriteCallback(
[this](uint32_t value) { enhance_cntl_ = value; });
mmio_region_[AML_SPI_TESTREG].SetWriteCallback([this](uint32_t value) { testreg_ = value; });
return fpromise::ok(mmio_region_.GetMmioBuffer());
});
}
private:
fake_mmio::FakeMmioRegRegion mmio_region_;
uint32_t conreg_{};
uint32_t enhance_cntl_{};
uint32_t testreg_{};
};
class FakePDev : public fidl::testing::WireTestBase<fuchsia_hardware_platform_device::Device> {
public:
fuchsia_hardware_platform_device::Service::InstanceHandler GetInstanceHandler(
async_dispatcher_t* dispatcher) {
return fuchsia_hardware_platform_device::Service::InstanceHandler({
.device = binding_group_.CreateHandler(this, dispatcher, fidl::kIgnoreBindingClosure),
});
}
void set_interrupt(zx::interrupt interrupt) { interrupt_ = std::move(interrupt); }
void set_bti(zx::bti bti) { bti_ = std::move(bti); }
private:
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) override {}
void GetInterruptById(
fuchsia_hardware_platform_device::wire::DeviceGetInterruptByIdRequest* request,
GetInterruptByIdCompleter::Sync& completer) override {
if (request->index != 0 || !interrupt_) {
return completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::interrupt out_interrupt;
zx_status_t status = interrupt_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_interrupt);
if (status == ZX_OK) {
completer.ReplySuccess(std::move(out_interrupt));
} else {
completer.ReplyError(status);
}
}
void GetBtiById(fuchsia_hardware_platform_device::wire::DeviceGetBtiByIdRequest* request,
GetBtiByIdCompleter::Sync& completer) override {
if (request->index != 0 || !bti_) {
return completer.ReplyError(ZX_ERR_NOT_FOUND);
}
zx::bti out_bti;
zx_status_t status = bti_.duplicate(ZX_RIGHT_SAME_RIGHTS, &out_bti);
if (status == ZX_OK) {
completer.ReplySuccess(std::move(out_bti));
} else {
completer.ReplyError(status);
}
}
zx::interrupt interrupt_;
zx::bti bti_;
fidl::ServerBindingGroup<fuchsia_hardware_platform_device::Device> binding_group_;
};
class BaseTestEnvironment : public fdf_testing::Environment,
public fidl::testing::WireTestBase<fuchsia_hardware_gpio::Gpio> {
public:
BaseTestEnvironment()
: fdf_testing::Environment(), registers_(fdf::Dispatcher::GetCurrent()->async_dispatcher()) {}
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
std::map<uint32_t, zx::interrupt> irqs;
std::optional interrupt = CreateInterrupt();
if (interrupt.has_value()) {
irqs[0] = std::move(interrupt.value());
}
std::map<uint32_t, zx::bti> btis;
std::optional bti = CreateBti();
if (bti.has_value()) {
btis[0] = std::move(bti.value());
}
pdev_server_.SetConfig({
.irqs = std::move(irqs),
.btis = std::move(btis),
});
auto result = to_driver_vfs.AddService<fuchsia_hardware_platform_device::Service>(
pdev_server_.GetInstanceHandler(fdf::Dispatcher::GetCurrent()->async_dispatcher()), "pdev");
if (!result.is_ok()) {
return result.take_error();
}
SetMetadata(compat_);
compat_.Init("pdev", {});
EXPECT_OK(compat_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs));
// Serve a second compat instance at default in order to satisfy AmlSpiDriver's compat
// server. Without this, metadata doesn't get forwarded.
compat_default_.Init("default", {});
EXPECT_OK(
compat_default_.Serve(fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs));
result = to_driver_vfs.AddService<fuchsia_hardware_gpio::Service>(CreateInstanceHandler(),
"gpio-cs-2");
if (result.is_error()) {
return result.take_error();
}
result = to_driver_vfs.AddService<fuchsia_hardware_gpio::Service>(CreateInstanceHandler(),
"gpio-cs-3");
if (result.is_error()) {
return result.take_error();
}
result = to_driver_vfs.AddService<fuchsia_hardware_gpio::Service>(CreateInstanceHandler(),
"gpio-cs-5");
if (result.is_error()) {
return result.take_error();
}
if (SetupResetRegister()) {
auto result = to_driver_vfs.AddService<fuchsia_hardware_registers::Service>(
registers_.GetInstanceHandler(), "reset");
if (result.is_error()) {
return result.take_error();
}
}
registers_.ExpectWrite<uint32_t>(0x1c, 1 << 1, 1 << 1);
return zx::ok();
}
virtual std::optional<zx::interrupt> CreateInterrupt() {
EXPECT_OK(zx::interrupt::create({}, 0, ZX_INTERRUPT_VIRTUAL, &interrupt_));
zx::interrupt dut_interrupt;
EXPECT_OK(interrupt_.duplicate(ZX_RIGHT_SAME_RIGHTS, &dut_interrupt));
interrupt_.trigger(0, zx::clock::get_boot());
return std::move(dut_interrupt);
}
virtual std::optional<zx::bti> CreateBti() { return std::nullopt; }
virtual bool SetupResetRegister() { return true; }
virtual void SetMetadata(compat::DeviceServer& compat) {
EXPECT_OK(compat.AddMetadata(DEVICE_METADATA_AMLSPI_CONFIG, &kSpiConfig, sizeof(kSpiConfig)));
}
uint32_t cs_toggle_count() const { return cs_toggle_count_; }
bool ControllerReset() {
zx_status_t status = registers_.VerifyAll();
if (status == ZX_OK) {
// Always keep a single expectation in the queue, that way we can verify when the controller
// is not reset.
registers_.ExpectWrite<uint32_t>(0x1c, 1 << 1, 1 << 1);
}
return status == ZX_OK;
}
protected:
static constexpr amlogic_spi::amlspi_config_t kSpiConfig = {
.bus_id = 0,
.cs_count = 3,
.cs = {5, 3, amlogic_spi::amlspi_config_t::kCsClientManaged},
.clock_divider_register_value = 0,
.use_enhanced_clock_mode = false,
};
fuchsia_hardware_gpio::Service::InstanceHandler CreateInstanceHandler() {
return fuchsia_hardware_gpio::Service::InstanceHandler({
.device = bindings_.CreateHandler(this, fdf::Dispatcher::GetCurrent()->async_dispatcher(),
fidl::kIgnoreBindingClosure),
});
}
void SetBufferMode(SetBufferModeRequestView request,
SetBufferModeCompleter::Sync& completer) override {
if (request->mode != cs_buffer_mode_) {
cs_toggle_count_++;
}
cs_buffer_mode_ = request->mode;
completer.ReplySuccess();
}
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
private:
fdf_fake::FakePDev pdev_server_;
zx::interrupt interrupt_;
mock_registers::MockRegisters registers_;
compat::DeviceServer compat_;
compat::DeviceServer compat_default_;
fuchsia_hardware_gpio::BufferMode cs_buffer_mode_{fuchsia_hardware_gpio::BufferMode::kOutputHigh};
uint32_t cs_toggle_count_ = 0;
fidl::ServerBindingGroup<fuchsia_hardware_gpio::Gpio> bindings_;
};
} // namespace spi
#endif // SRC_DEVICES_SPI_DRIVERS_AML_SPI_TESTS_AML_SPI_TEST_ENV_H_