blob: 84f5769f93ffafdd663f682ec8f857d2516ddc2d [file] [log] [blame]
// Copyright 2020 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 <fidl/fuchsia.hardware.audio/cpp/wire.h>
#include <fidl/fuchsia.hardware.clock/cpp/wire_test_base.h>
#include <lib/async-loop/default.h>
#include <lib/async_patterns/testing/cpp/dispatcher_bound.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/ddk/metadata.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fidl/cpp/wire/server.h>
#include <lib/inspect/testing/cpp/zxtest/inspect.h>
#include <lib/simple-codec/simple-codec-server.h>
#include <lib/sync/completion.h>
#include <zircon/errors.h>
#include <vector>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <mock-mmio-reg/mock-mmio-reg.h>
#include <soc/aml-s905d2/s905d2-hw.h>
#include <zxtest/zxtest.h>
#include "../audio-stream.h"
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
#include "src/devices/gpio/testing/fake-gpio/fake-gpio.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace audio::aml_g12 {
namespace audio_fidl = fuchsia_hardware_audio;
static constexpr float kTestGain = 2.f;
static constexpr float kTestDeltaGain = 1.f;
static constexpr float kTestTurnOnNsecs = 12345;
static constexpr float kTestTurnOffNsecs = 67890;
// Fake clock for power management test.
class FakeClock : public fidl::testing::WireTestBase<fuchsia_hardware_clock::Clock> {
public:
FakeClock() = default;
bool IsFakeClockEnabled() { return enabled_; }
fidl::ClientEnd<fuchsia_hardware_clock::Clock> Connect() {
auto endpoints = fidl::Endpoints<fuchsia_hardware_clock::Clock>::Create();
bindings_.AddBinding(async_get_default_dispatcher(), std::move(endpoints.server), this,
fidl::kIgnoreBindingClosure);
return std::move(endpoints.client);
}
protected:
void Enable(EnableCompleter::Sync& completer) override {
enabled_ = true;
completer.Reply(zx::ok());
}
void Disable(DisableCompleter::Sync& completer) override {
enabled_ = false;
completer.Reply(zx::ok());
}
void IsEnabled(IsEnabledCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void SetRate(::fuchsia_hardware_clock::wire::ClockSetRateRequest* request,
SetRateCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void QuerySupportedRate(::fuchsia_hardware_clock::wire::ClockQuerySupportedRateRequest* request,
QuerySupportedRateCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetRate(GetRateCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void SetInput(::fuchsia_hardware_clock::wire::ClockSetInputRequest* request,
SetInputCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetNumInputs(GetNumInputsCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void GetInput(GetInputCompleter::Sync& completer) override {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void NotImplemented_(const std::string& name, fidl::CompleterBase& completer) override {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
private:
bool enabled_ = false;
fidl::ServerBindingGroup<fuchsia_hardware_clock::Clock> bindings_;
};
class PowerManagementTest : public zxtest::Test {
public:
void SetUp() override {
ASSERT_OK(loop_.StartThread("pm-test-loop"));
auto clock_gate = clock_gate_.SyncCall(&FakeClock::Connect);
clock_gate_client_ = fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(std::move(clock_gate));
auto pll = pll_.SyncCall(&FakeClock::Connect);
pll_client_ = fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(std::move(pll));
enable_gpio_.SyncCall(&fake_gpio::FakeGpio::SetCurrentState,
fake_gpio::State{.polarity = fuchsia_hardware_gpio::GpioPolarity::kHigh,
.sub_state = fake_gpio::WriteSubState{.value = 1}});
fake_parent_ = MockDevice::FakeRootParent();
}
void TearDown() override {
ASSERT_OK(fdf::RunOnDispatcherSync(loop_.dispatcher(), [this]() { clock_gate_.reset(); }));
}
protected:
std::shared_ptr<zx_device> fake_parent() const { return fake_parent_; }
bool IsClockGateEnabled() { return clock_gate_.SyncCall(&FakeClock::IsFakeClockEnabled); }
bool IsPllEnabled() { return pll_.SyncCall(&FakeClock::IsFakeClockEnabled); }
async::Loop loop_{&kAsyncLoopConfigAttachToCurrentThread};
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio> enable_gpio_{loop_.dispatcher(),
std::in_place};
async_patterns::TestDispatcherBound<FakeClock> clock_gate_{loop_.dispatcher(), std::in_place};
async_patterns::TestDispatcherBound<FakeClock> pll_{loop_.dispatcher(), std::in_place};
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_client_;
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> pll_client_;
std::shared_ptr<zx_device> fake_parent_;
};
class StreamTest : public zxtest::Test {
public:
void SetUp() override {
ASSERT_OK(gpio_loop_.StartThread("gpio"));
enable_gpio_.SyncCall(&fake_gpio::FakeGpio::SetCurrentState,
fake_gpio::State{.polarity = fuchsia_hardware_gpio::GpioPolarity::kHigh,
.sub_state = fake_gpio::WriteSubState{.value = 1}});
fake_parent_ = MockDevice::FakeRootParent();
}
protected:
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio>& enable_gpio() { return enable_gpio_; }
std::shared_ptr<zx_device> fake_parent() const { return fake_parent_; }
template <typename T>
fbl::RefPtr<T> CreateController(ddk_mock::MockMmioRegRegion& mmio_reg_region,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec) {
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
auto controller =
audio::SimpleAudioStream::Create<T>(fake_parent_.get(), std::move(codec), mmio_reg_region,
ddk::PDevFidl(), std::move(enable_gpio_client));
VerifyGpioAfterCreate();
return controller;
}
template <typename T>
fbl::RefPtr<T> CreateController(
ddk_mock::MockMmioRegRegion& mmio_reg_region,
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codecs) {
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
auto controller = audio::SimpleAudioStream::Create<T>(
fake_parent_.get(), std::move(codecs), mmio_reg_region, ddk::PDevFidl(),
std::move(enable_gpio_client), fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>());
VerifyGpioAfterCreate();
return controller;
}
template <typename T>
fbl::RefPtr<T> CreateController(ddk_mock::MockMmioRegRegion& mmio_reg_region) {
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
auto controller = audio::SimpleAudioStream::Create<T>(
fake_parent_.get(), mmio_reg_region, ddk::PDevFidl(), std::move(enable_gpio_client));
VerifyGpioAfterCreate();
return controller;
}
private:
void VerifyGpioAfterCreate() {
std::vector states = enable_gpio().SyncCall(&fake_gpio::FakeGpio::GetStateLog);
ASSERT_EQ(1, states.size());
ASSERT_EQ(fake_gpio::WriteSubState{.value = 1}, states[0].sub_state);
}
async::Loop gpio_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio> enable_gpio_{gpio_loop_.dispatcher(),
std::in_place};
std::shared_ptr<zx_device> fake_parent_;
};
audio_fidl::wire::PcmFormat GetDefaultPcmFormat() {
audio_fidl::wire::PcmFormat format;
format.number_of_channels = 2;
format.sample_format = audio_fidl::wire::SampleFormat::kPcmSigned;
format.frame_rate = 48'000;
format.bytes_per_sample = 2;
format.valid_bits_per_sample = 16;
return format;
}
fidl::WireSyncClient<audio_fidl::StreamConfig> GetStreamClient(
fidl::ClientEnd<audio_fidl::StreamConfigConnector> client) {
fidl::WireSyncClient client_wrap{std::move(client)};
if (!client_wrap.is_valid()) {
return {};
}
auto [stream_channel_local, stream_channel_remote] =
fidl::Endpoints<audio_fidl::StreamConfig>::Create();
auto result = client_wrap->Connect(std::move(stream_channel_remote));
if (!result.ok()) {
return {};
}
return fidl::WireSyncClient<audio_fidl::StreamConfig>(std::move(stream_channel_local));
}
class CodecTest : public SimpleCodecServer {
public:
explicit CodecTest(zx_device_t* device) : SimpleCodecServer(device) {}
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> GetClient() {
zx::channel channel_remote;
fidl::ClientEnd<fuchsia_hardware_audio::Codec> channel_local;
zx_status_t status = zx::channel::create(0, &channel_local.channel(), &channel_remote);
if (status != ZX_OK) {
return zx::error(status);
}
status = CodecConnect(std::move(channel_remote));
if (status != ZX_OK) {
return zx::error(status);
}
return zx::success(std::move(channel_local));
}
zx::result<DriverIds> Initialize() override { return zx::ok(DriverIds{}); }
zx_status_t Shutdown() override { return ZX_OK; }
zx_status_t Reset() override {
started_ = true;
return ZX_OK;
}
Info GetInfo() override { return {}; }
zx_status_t Stop() override {
started_ = false;
return ZX_OK;
}
zx_status_t Start() override {
started_ = true;
return ZX_OK;
}
bool IsBridgeable() override { return true; }
void SetBridgedMode(bool enable_bridged_mode) override {}
DaiSupportedFormats GetDaiFormats() override {
DaiSupportedFormats formats;
formats.number_of_channels.push_back(2);
formats.sample_formats.push_back(SampleFormat::PCM_SIGNED);
formats.frame_formats.push_back(FrameFormat::I2S);
formats.frame_rates.push_back(48'000);
formats.bits_per_slot.push_back(16);
formats.bits_per_sample.push_back(16);
return formats;
}
zx::result<CodecFormatInfo> SetDaiFormat(const DaiFormat& format) override {
last_frame_rate_ = format.frame_rate;
CodecFormatInfo format_info = {};
format_info.set_turn_on_delay(kTestTurnOnNsecs);
format_info.set_turn_off_delay(kTestTurnOffNsecs);
return zx::ok(std::move(format_info));
}
GainFormat GetGainFormat() override {
return {
.min_gain = -10.f, .max_gain = 10.f, .gain_step = .5f, .can_mute = true, .can_agc = true};
}
GainState GetGainState() override { return {}; }
void SetGainState(GainState state) override {
muted_ = state.muted;
gain_ = state.gain;
sync_completion_signal(&set_gain_completion_);
}
void DdkRelease() { delete this; }
void wait_for_set_gain_completion() {
sync_completion_wait(&set_gain_completion_, ZX_TIME_INFINITE);
sync_completion_reset(&set_gain_completion_);
}
uint32_t last_frame_rate() { return last_frame_rate_; }
bool started() { return started_; }
bool muted() { return muted_; }
float gain() { return gain_; }
private:
uint32_t last_frame_rate_ = 0;
bool started_ = false;
bool muted_ = false;
float gain_ = 0.f;
sync_completion_t set_gain_completion_;
};
struct AmlG12I2sOutTest : public AmlG12TdmStream {
void SetCommonDefaults() {
metadata_.is_input = false;
metadata_.mClockDivFactor = 10;
metadata_.sClockDivFactor = 25;
metadata_.bus = metadata::AmlBus::TDM_C;
metadata_.version = metadata::AmlVersion::kS905D2G;
metadata_.dai.type = metadata::DaiType::I2s;
metadata_.dai.number_of_channels = 2;
metadata_.dai.bits_per_sample = 16;
metadata_.dai.bits_per_slot = 32;
}
AmlG12I2sOutTest(zx_device_t* parent,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec_client_end,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12TdmStream(parent, false, std::move(pdev),
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>(std::move(enable_gpio)),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>()) {
SetCommonDefaults();
codecs_.push_back(std::make_unique<SimpleCodecClient>());
codecs_[0]->SetCodec(std::move(codec_client_end));
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
metadata_.ring_buffer.number_of_channels = 2;
metadata_.lanes_enable_mask[0] = 3;
metadata_.codecs.number_of_codecs = 1;
metadata_.codecs.types[0] = metadata::CodecType::Tas27xx;
metadata_.codecs.ring_buffer_channels_to_use_bitmask[0] = 1;
}
AmlG12I2sOutTest(zx_device_t* parent,
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_client,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> pll_client)
: AmlG12TdmStream(parent, false, std::move(pdev),
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>(std::move(enable_gpio)),
std::move(clock_gate_client), std::move(pll_client)) {
SetCommonDefaults();
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
// Simply one ring buffer channel per codec.
metadata_.ring_buffer.number_of_channels = codec_client_ends.size();
metadata_.codecs.number_of_codecs = codec_client_ends.size();
for (size_t i = 0; i < codec_client_ends.size(); ++i) {
codecs_.push_back(std::make_unique<SimpleCodecClient>());
codecs_[i]->SetCodec(std::move(codec_client_ends[i]));
metadata_.lanes_enable_mask[i] = (1 << i); // Simply one lane per codec.
metadata_.codecs.types[i] = metadata::CodecType::Tas27xx;
metadata_.codecs.delta_gains[i] = 0.f;
metadata_.codecs.ring_buffer_channels_to_use_bitmask[i] = (1 << i);
}
metadata_.codecs.delta_gains[0] = kTestDeltaGain; // Only first one non-zero.
}
zx_status_t Init() __TA_REQUIRES(domain_token()) override {
SimpleAudioStream::SupportedFormat format = {};
format.range.min_channels = 2;
format.range.max_channels = 4;
format.range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
format.range.min_frames_per_second = 8'000;
format.range.max_frames_per_second = 96'000;
format.range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
supported_formats_.push_back(std::move(format));
driver_transfer_bytes_ = 16;
SetInitialPlugState(AUDIO_PDNF_CAN_NOTIFY);
snprintf(device_name_, sizeof(device_name_), "Testy Device");
snprintf(mfr_name_, sizeof(mfr_name_), "Testy Inc");
snprintf(prod_name_, sizeof(prod_name_), "Testy McTest");
unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS;
InitDaiFormats();
auto status = InitCodecsGain();
if (status != ZX_OK) {
return status;
}
constexpr uint64_t channels_to_use = 0x03;
return aml_audio_->InitHW(metadata_, channels_to_use, 48'000);
}
zx_status_t GetBuffer(const audio_proto::RingBufGetBufferReq& req, uint32_t* out_num_rb_frames,
zx::vmo* out_buffer) __TA_REQUIRES(domain_token()) override {
zx::vmo rb;
*out_num_rb_frames = req.min_ring_buffer_frames;
zx::vmo::create(*out_num_rb_frames * 2 * 2, 0, &rb);
constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER;
return rb.duplicate(rights, out_buffer);
}
};
TEST_F(StreamTest, InitializeI2sOut) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM OUT for I2S.
mock[0x580].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM OUT CTRL0 disable.
// TDM OUT CTRL0 config, bitoffset 2, 2 slots, 32 bits per slot.
mock[0x580].ExpectWrite(0x0001003f);
// TDM OUT CTRL1 FRDDR C with 16 bits per sample.
mock[0x584].ExpectWrite(0x02000F20);
mock[0x050].ExpectWrite(0xc1807c3f); // SCLK CTRL, enabled, 24 sdiv, 31 lrduty, 63 lrdiv.
// SCLK CTRL1, clear delay, sclk_invert_ph0.
mock[0x054].ExpectWrite(0x00000000).ExpectWrite(0x00000001);
// CLK TDMOUT CTL, enable, no sclk_inv, sclk_ws_inv, mclk_ch 2.
mock[0x098].ExpectWrite(0).ExpectWrite(0xd2200000);
auto controller = CreateController<AmlG12I2sOutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
struct AmlG12PcmOutTest : public AmlG12I2sOutTest {
AmlG12PcmOutTest(zx_device_t* parent,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec_client_end,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12I2sOutTest(parent, std::move(codec_client_end), region, std::move(pdev),
std::move(enable_gpio)) {
metadata_.bus = metadata::AmlBus::TDM_A;
metadata_.ring_buffer.number_of_channels = 1;
metadata_.lanes_enable_mask[0] = 1;
metadata_.dai.type = metadata::DaiType::Tdm1;
metadata_.dai.number_of_channels = 1;
metadata_.dai.bits_per_slot = 16;
metadata_.codecs.number_of_codecs = 0;
metadata_.dai.sclk_on_raising = true;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
};
TEST_F(StreamTest, InitializePcmOut) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM OUT A for PCM. EE_AUDIO_TDMOUT_A_CTRL0.
mock[0x500].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM OUT CTRL0 disable.
// TDM OUT A CTRL0 config, bitoffset 2, 1 slot, 16 bits per slot.
mock[0x500].ExpectWrite(0x0001000f);
// TDM OUT A CTRL1 FRDDR A with 16 bits per sample.
mock[0x504].ExpectWrite(0x00000F20);
// SCLK A CTRL, enabled, 24 sdiv, 0 lrduty, 15 lrdiv. EE_AUDIO_MST_A_SCLK_CTRL0.
mock[0x040].ExpectWrite(0xc180000f);
// SCLK A CTRL1, clear delay, no sclk_invert_ph0. EE_AUDIO_MST_A_SCLK_CTRL1.
mock[0x044].ExpectWrite(0x00000000).ExpectWrite(0x00000000);
// CLK TDMOUT A CTL, enable, no sclk_inv, sclk_ws_inv, mclk_ch 0. EE_AUDIO_CLK_TDMOUT_A_CTRL.
mock[0x090].ExpectWrite(0).ExpectWrite(0xd0000000);
auto controller = CreateController<AmlG12PcmOutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
struct AmlG12LjtOutTest : public AmlG12I2sOutTest {
AmlG12LjtOutTest(zx_device_t* parent,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec_client_end,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12I2sOutTest(parent, std::move(codec_client_end), region, std::move(pdev),
std::move(enable_gpio)) {
metadata_.ring_buffer.number_of_channels = 2;
metadata_.lanes_enable_mask[0] = 3;
metadata_.dai.type = metadata::DaiType::StereoLeftJustified;
metadata_.dai.bits_per_sample = 16;
metadata_.dai.bits_per_slot = 16;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
};
TEST_F(StreamTest, InitializeLeftJustifiedOut) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM OUT for LeftJustified.
mock[0x580].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM OUT CTRL0 disable.
// TDM OUT CTRL0 config, bitoffset 3, 2 slots, 16 bits per slot.
mock[0x580].ExpectWrite(0x0001802f);
// TDM OUT CTRL1 FRDDR C with 16 bits per sample.
mock[0x584].ExpectWrite(0x02000F20);
mock[0x050].ExpectWrite(0xc1803c1f); // SCLK CTRL, enabled, 24 sdiv, 15 lrduty, 31 lrdiv.
// SCLK CTRL1, clear delay, sclk_invert_ph0.
mock[0x054].ExpectWrite(0x00000000).ExpectWrite(0x00000001);
// CLK TDMOUT CTL, enable, no sclk_inv, sclk_ws_inv, mclk_ch 2.
mock[0x098].ExpectWrite(0).ExpectWrite(0xd2200000);
auto controller = CreateController<AmlG12LjtOutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
struct AmlG12Tdm1OutTest : public AmlG12I2sOutTest {
AmlG12Tdm1OutTest(zx_device_t* parent,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec_client_end,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12I2sOutTest(parent, std::move(codec_client_end), region, std::move(pdev),
std::move(enable_gpio)) {
metadata_.ring_buffer.number_of_channels = 4;
metadata_.lanes_enable_mask[0] = 0xf;
metadata_.dai.type = metadata::DaiType::Tdm1;
metadata_.dai.number_of_channels = 4;
metadata_.dai.bits_per_slot = 16;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
};
TEST_F(StreamTest, InitializeTdm1Out) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM OUT for Tdm1.
mock[0x580].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM OUT CTRL0 disable.
// TDM OUT CTRL0 config, bitoffset 3, 4 slots, 16 bits per slot.
mock[0x580].ExpectWrite(0x0001806f);
// TDM OUT CTRL1 FRDDR C with 16 bits per sample.
mock[0x584].ExpectWrite(0x02000F20);
mock[0x050].ExpectWrite(0xc180003f); // SCLK CTRL, enabled, 24 sdiv, 0 lrduty, 63 lrdiv.
// SCLK CTRL1, clear delay, sclk_invert_ph0.
mock[0x054].ExpectWrite(0x00000000).ExpectWrite(0x00000001);
// CLK TDMOUT CTL, enable, no sclk_inv, sclk_ws_inv, mclk_ch 2.
mock[0x098].ExpectWrite(0).ExpectWrite(0xd2200000);
auto controller = CreateController<AmlG12Tdm1OutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, I2sOutCodecsStartedAndMuted) {
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
// Check we started (al least not stopped) both codecs and set them to muted.
ASSERT_TRUE(codec1->started());
ASSERT_TRUE(codec2->started());
ASSERT_TRUE(codec1->muted());
ASSERT_TRUE(codec2->muted());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutCodecsTurnOnDelay) {
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
EXPECT_EQ(kTestTurnOnNsecs, props.value().properties.turn_on_delay());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutSetGainState) {
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
{
{
fidl::Arena allocator;
// We start with agc false and muted true.
audio_fidl::wire::GainState gain_state(allocator);
gain_state.set_muted(true).set_agc_enabled(false).set_gain_db(kTestGain);
auto result = stream_client->SetGain(std::move(gain_state));
ASSERT_OK(result.status());
}
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
// In this test we want to get the gain state anyways.
auto gain_state = stream_client->WatchGainState();
ASSERT_TRUE(gain_state.value().gain_state.has_agc_enabled());
ASSERT_FALSE(gain_state.value().gain_state.agc_enabled());
ASSERT_TRUE(gain_state.value().gain_state.muted());
ASSERT_EQ(gain_state.value().gain_state.gain_db(), kTestGain);
ASSERT_EQ(codec1->gain(), kTestGain + kTestDeltaGain);
ASSERT_EQ(codec2->gain(), kTestGain);
ASSERT_TRUE(codec1->muted());
ASSERT_TRUE(codec2->muted());
}
{
{
fidl::Arena allocator;
// We switch to agc true and muted false.
audio_fidl::wire::GainState gain_state(allocator);
gain_state.set_muted(false).set_agc_enabled(true).set_gain_db(kTestGain);
auto result = stream_client->SetGain(std::move(gain_state));
ASSERT_OK(result.status());
}
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
// In this test we want to get the gain state anyways.
auto gain_state = stream_client->WatchGainState();
ASSERT_TRUE(gain_state.value().gain_state.has_agc_enabled());
ASSERT_TRUE(gain_state.value().gain_state.agc_enabled());
ASSERT_FALSE(gain_state.value().gain_state.muted());
ASSERT_EQ(gain_state.value().gain_state.gain_db(), kTestGain);
ASSERT_EQ(codec1->gain(), kTestGain + kTestDeltaGain);
ASSERT_EQ(codec2->gain(), kTestGain);
ASSERT_TRUE(codec1->muted()); // override_mute_ forces muted in the codec.
ASSERT_TRUE(codec2->muted()); // override_mute_ forces muted in the codec.
}
{
// Now we start the ring buffer so override_mute_ gets cleared.
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
auto vmo = fidl::WireCall(local)->GetVmo(8192, 0);
ASSERT_OK(vmo.status());
auto start = fidl::WireCall(local)->Start();
ASSERT_OK(start.status());
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
{
fidl::Arena allocator;
// Now we set gain again.
// Change agc from last one, so the Watch below replies.
audio_fidl::wire::GainState gain_state(allocator);
gain_state.set_muted(false).set_agc_enabled(false).set_gain_db(kTestGain);
auto result = stream_client->SetGain(std::move(gain_state));
ASSERT_OK(result.status());
}
// Wait until codecs have received a SetGainState call.
codec1->wait_for_set_gain_completion();
codec2->wait_for_set_gain_completion();
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
// In this test we want to get the gain state anyways.
auto gain_state = stream_client->WatchGainState();
ASSERT_TRUE(gain_state.value().gain_state.has_agc_enabled());
ASSERT_FALSE(gain_state.value().gain_state.agc_enabled());
ASSERT_FALSE(gain_state.value().gain_state.muted());
ASSERT_EQ(gain_state.value().gain_state.gain_db(), kTestGain);
// We check the gain delta support in one codec.
ASSERT_EQ(codec1->gain(), kTestGain + kTestDeltaGain);
ASSERT_EQ(codec2->gain(), kTestGain);
// And finally we check that we removed mute in the codecs.
ASSERT_FALSE(codec1->muted()); // override_mute_ is cleared, we were able to set mute to false.
ASSERT_FALSE(codec2->muted()); // override_mute_ is cleared, we were able to set mute to false.
}
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutOneCodecCantAgc) {
struct CodecCantAgcTest : public CodecTest {
explicit CodecCantAgcTest(zx_device_t* device) : CodecTest(device) {}
GainFormat GetGainFormat() override {
return {.min_gain = -10.f,
.max_gain = 10.f,
.gain_step = .5f,
.can_mute = true,
.can_agc = false};
}
};
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecCantAgcTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecCantAgcTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto props = stream_client->GetProperties();
ASSERT_OK(props.status());
EXPECT_TRUE(props.value().properties.can_mute());
EXPECT_FALSE(props.value().properties.can_agc());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutOneCodecCantMute) {
struct CodecCantMuteTest : public CodecTest {
explicit CodecCantMuteTest(zx_device_t* device) : CodecTest(device) {}
GainFormat GetGainFormat() override {
return {.min_gain = -10.f,
.max_gain = 10.f,
.gain_step = .5f,
.can_mute = false,
.can_agc = true};
}
};
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecCantMuteTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecCantMuteTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto props = stream_client->GetProperties();
ASSERT_OK(props.status());
EXPECT_FALSE(props.value().properties.can_mute());
EXPECT_TRUE(props.value().properties.can_agc());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutCodecsStop) {
// Setup a system with 3 codecs.
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev3 = fake_parent()->GetLatestChild();
auto codec3 = child_dev3->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end3 =
codec3->GetClient();
ASSERT_OK(codec_client_end3.status_value());
codec_client_ends.push_back(std::move(*codec_client_end3));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// We stop the ring buffer and expect the codecs are stopped.
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = 3;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
constexpr uint32_t kFramesRequested = 4096;
auto vmo = fidl::WireCall(local)->GetVmo(kFramesRequested, 0);
ASSERT_OK(vmo.status());
auto start = fidl::WireCall(local)->Start();
ASSERT_OK(start.status());
EXPECT_TRUE(codec1->started());
EXPECT_TRUE(codec2->started());
EXPECT_TRUE(codec3->started());
auto stop = fidl::WireCall(local)->Stop();
ASSERT_OK(stop.status());
EXPECT_FALSE(codec1->started());
EXPECT_FALSE(codec2->started());
EXPECT_FALSE(codec3->started());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutCodecsChannelsActive) {
// Setup a system with 3 codecs.
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev3 = fake_parent()->GetLatestChild();
auto codec3 = child_dev3->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end3 =
codec3->GetClient();
ASSERT_OK(codec_client_end3.status_value());
codec_client_ends.push_back(std::move(*codec_client_end3));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12I2sOutTest>(unused_mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// We expect the codecs to start/stop.
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = 3;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
constexpr uint32_t kFramesRequested = 4096;
auto vmo = fidl::WireCall(local)->GetVmo(kFramesRequested, 0);
ASSERT_OK(vmo.status());
auto start1 = fidl::WireCall(local)->Start();
ASSERT_OK(start1.status());
EXPECT_TRUE(codec1->started());
EXPECT_TRUE(codec2->started());
EXPECT_TRUE(codec3->started());
auto stop1 = fidl::WireCall(local)->Stop();
ASSERT_OK(stop1.status());
EXPECT_FALSE(codec1->started());
EXPECT_FALSE(codec2->started());
EXPECT_FALSE(codec3->started());
// We now use set active channels to disable.
auto active1 = fidl::WireCall(local)->SetActiveChannels(0x5);
ASSERT_TRUE(active1->is_ok());
auto start2 = fidl::WireCall(local)->Start();
ASSERT_OK(start2.status());
EXPECT_TRUE(codec1->started());
EXPECT_FALSE(codec2->started()); // Disabled via set active channels 0x05.
EXPECT_TRUE(codec3->started());
// We update active channels while started.
auto active2 = fidl::WireCall(local)->SetActiveChannels(0x08); // Out of range channel request.
ASSERT_TRUE(active2->is_error()); // Request is ignored, with error.
ASSERT_EQ(active2->error_value(), ZX_ERR_INVALID_ARGS);
EXPECT_TRUE(codec1->started()); // Values are retained from previous call.
EXPECT_FALSE(codec2->started());
EXPECT_TRUE(codec3->started());
// We update active channels while started.
auto active3 = fidl::WireCall(local)->SetActiveChannels(0x0);
ASSERT_TRUE(active3->is_ok());
EXPECT_FALSE(codec1->started());
EXPECT_FALSE(codec2->started()); // Stopped via set active channels 0x00.
EXPECT_FALSE(codec3->started());
// We update active channels while started.
auto active4 = fidl::WireCall(local)->SetActiveChannels(0x2);
ASSERT_TRUE(active4->is_ok());
EXPECT_FALSE(codec1->started());
EXPECT_TRUE(codec2->started()); // Enabled via set active channels 0x02.
EXPECT_FALSE(codec3->started());
auto stop2 = fidl::WireCall(local)->Stop();
ASSERT_OK(stop2.status());
EXPECT_FALSE(codec1->started());
EXPECT_FALSE(codec2->started());
EXPECT_FALSE(codec3->started());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(StreamTest, I2sOutSetMclks) {
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// HW Initialize the MCLK pads. EE_AUDIO_MST_PAD_CTRL0.
mock[0x01C].ExpectRead(0x00000000).ExpectWrite(0x00000002); // MCLK C for PAD 0.
// HW Initialize the MCLK pads. EE_AUDIO_MST_PAD_CTRL1.
// Set 3 bits twice to MCLK C (2) and leave other configurations unchanged.
mock[0x020].ExpectRead(0xffffffff).ExpectWrite(0xfafffaff); // MCLK C for PAD 1.
auto controller = CreateController<AmlG12I2sOutTest>(mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, I2sOutChangeRate96K) {
std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_ends;
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev1 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev1);
auto codec1 = child_dev1->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end1 =
codec1->GetClient();
ASSERT_OK(codec_client_end1.status_value());
codec_client_ends.push_back(std::move(*codec_client_end1));
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
auto codec2 = child_dev2->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end2 =
codec2->GetClient();
ASSERT_OK(codec_client_end2.status_value());
codec_client_ends.push_back(std::move(*codec_client_end2));
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// HW Initialize the MCLK pads. EE_AUDIO_MST_PAD_CTRL0.
mock[0x01C].ExpectRead(0x00000000).ExpectWrite(0x00000002); // MCLK C for PAD 0.
// HW Initialize with 48kHz, set MCLK C CTRL.
mock[0x00c].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x00c].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x00c].ExpectRead(0x00000000).ExpectWrite(0x84000009); // Enabled, HIFI PLL, set div to 10.
// HW Initialize with requested 48kHz, set MCLK C CTRL.
mock[0x00c].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x00c].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x00c].ExpectRead(0x00000000).ExpectWrite(0x84000009); // Enabled, HIFI PLL, set div to 10.
// HW Initialize with requested 96kHz, set MCLK C CTRL.
mock[0x00c].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x00c].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x00c].ExpectRead(0x00000000).ExpectWrite(0x84000004); // Enabled, HIFI PLL, set div to 5.
auto controller = CreateController<AmlG12I2sOutTest>(mock, std::move(codec_client_ends));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// Default sets 48'000.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
// Changes to 96'000.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.frame_rate = 96'000; // Change it from the default at 48kHz.
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure we have initialized in the controller driver make a sync call
// (we know the controller is single threaded, initialization is completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
// To make sure we have changed the rate in the codec make a sync call requiring codec reply
// (we know the codec is single threaded, rate change is completed if received a reply).
auto result = stream_client->SetGain(audio_fidl::wire::GainState{});
ASSERT_OK(result.status());
// Check that we set the codec to the new rate.
ASSERT_EQ(codec1->last_frame_rate(), 96'000);
ASSERT_EQ(codec2->last_frame_rate(), 96'000);
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, PcmChangeRates) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12PcmOutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
AmlG12I2sOutTest* test_dev2 = child_dev2->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev2);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// HW Initialize the MCLK pads. EE_AUDIO_MST_PAD_CTRL0.
mock[0x01C].ExpectRead(0xffffffff).ExpectWrite(0xfffffffc); // MCLK A for PAD 0.
// HW Initialize with requested 48kHz, set MCLK A CTRL.
mock[0x004].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x004].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x004].ExpectRead(0x00000000).ExpectWrite(0x84000027); // Enabled, HIFI PLL, set div to 40.
// HW Initialize with requested 96kHz, set MCLK A CTRL.
mock[0x004].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x004].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x004].ExpectRead(0x00000000).ExpectWrite(0x84000013); // Enabled, HIFI PLL, set div to 20.
// HW Initialize with requested 16kHz, set MCLK A CTRL.
mock[0x004].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x004].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x004].ExpectRead(0x00000000).ExpectWrite(0x84000077); // Enabled, HIFI PLL, set div to 120.
// HW Initialize with requested 8kHz, set MCLK A CTRL.
mock[0x004].ExpectWrite(0x0400ffff); // HIFI PLL, and max div.
mock[0x004].ExpectRead(0xffffffff).ExpectWrite(0x7fff0000); // Disable, clear div.
mock[0x004].ExpectRead(0x00000000).ExpectWrite(0x840000EF); // Enabled, HIFI PLL, set div to 240.
// Default sets 48'000 kHz.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
}
// Sets 96'000 kHz.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.frame_rate = 96'000; // Change it from the default at 48kHz.
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
}
// Sets 16'000 kHz.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.frame_rate = 16'000; // Change it from the default at 48kHz.
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure call initialization in the controller, make a sync call
// (we know the controller is single threaded, init completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
// Sets 8'000 kHz.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.frame_rate = 8'000; // Change it from the default at 48kHz.
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure call initialization in the controller, make a sync call
// (we know the controller is single threaded, init completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, EnableAndMuteChannelsPcm1Channel) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12PcmOutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
AmlG12I2sOutTest* test_dev2 = child_dev2->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev2);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// Clear all muting. EE_AUDIO_TDMOUT_A_MUTE.
mock[0x52c].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x530].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x534].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x538].ExpectWrite(0); // TDMOUT MUTE3.
// Enable 1 channel per metadata_.lanes_enable_mask[0] in AmlG12PcmOutTest.
// EE_AUDIO_TDMOUT_A_MASK.
mock[0x50c].ExpectWrite(1); // TDMOUT MASK0.
mock[0x510].ExpectWrite(0); // TDMOUT MASK1.
mock[0x514].ExpectWrite(0); // TDMOUT MASK2.
mock[0x518].ExpectWrite(0); // TDMOUT MASK3.
// Nothing muted. EE_AUDIO_TDMOUT_A_MUTE.
mock[0x52c].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x530].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x534].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x538].ExpectWrite(0); // TDMOUT MUTE3.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = 4;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure call initialization in the controller, make a sync call
// (we know the controller is single threaded, init completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
mock.VerifyAll();
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
}
TEST_F(StreamTest, EnableAndMuteChannelsTdm2Lanes) {
struct AmlG12Tdm2LanesOutMuteTest : public AmlG12I2sOutTest {
AmlG12Tdm2LanesOutMuteTest(zx_device_t* parent,
fidl::ClientEnd<fuchsia_hardware_audio::Codec> codec_client_end,
ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12I2sOutTest(parent, std::move(codec_client_end), region, std::move(pdev),
std::move(enable_gpio)) {
metadata_.ring_buffer.number_of_channels = 4;
metadata_.lanes_enable_mask[0] = 0x3;
metadata_.lanes_enable_mask[1] = 0x3;
metadata_.dai.type = metadata::DaiType::Tdm1;
metadata_.dai.bits_per_slot = 16;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
};
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
auto controller =
CreateController<AmlG12Tdm2LanesOutMuteTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
AmlG12I2sOutTest* test_dev2 = child_dev2->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev2);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// Clear all muting.
mock[0x5ac].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x5b0].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x5b4].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x5b8].ExpectWrite(0); // TDMOUT MUTE3.
// Enable 2 channels in lane 0 and 2 channels in lane 1.
mock[0x58c].ExpectWrite(3); // TDMOUT MASK0.
mock[0x590].ExpectWrite(3); // TDMOUT MASK1.
mock[0x594].ExpectWrite(0); // TDMOUT MASK2.
mock[0x598].ExpectWrite(0); // TDMOUT MASK3.
// Nothing muted.
mock[0x5ac].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x5b0].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x5b4].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x5b8].ExpectWrite(0); // TDMOUT MUTE3.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = 4;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure call initialization in the controller, make a sync call
// (we know the controller is single threaded, init completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, EnableAndMuteChannelsTdm1Lane) {
ASSERT_OK(SimpleCodecServer::CreateAndAddToDdk<CodecTest>(fake_parent().get()));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
auto codec = child_dev->GetDeviceContext<CodecTest>();
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end = codec->GetClient();
ASSERT_OK(codec_client_end.status_value());
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
auto controller = CreateController<AmlG12Tdm1OutTest>(mock, std::move(codec_client_end.value()));
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
AmlG12I2sOutTest* test_dev2 = child_dev2->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev2);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
//
// Configure and keep everything enabled.
mock[0x5ac].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x5b0].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x5b4].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x5b8].ExpectWrite(0); // TDMOUT MUTE3.
// Enable 4 channels in lane 0.
mock[0x58c].ExpectWrite(0xf); // TDMOUT MASK0.
mock[0x590].ExpectWrite(0); // TDMOUT MASK1.
mock[0x594].ExpectWrite(0); // TDMOUT MASK2.
mock[0x598].ExpectWrite(0); // TDMOUT MASK3.
// Nothing muted.
mock[0x5ac].ExpectWrite(0); // TDMOUT MUTE0.
mock[0x5b0].ExpectWrite(0); // TDMOUT MUTE1.
mock[0x5b4].ExpectWrite(0); // TDMOUT MUTE2.
mock[0x5b8].ExpectWrite(0); // TDMOUT MUTE3.
{
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = 4;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// To make sure call initialization in the controller, make a sync call
// (we know the controller is single threaded, init completed if received a reply).
auto props = fidl::WireCall(local)->GetProperties();
ASSERT_OK(props.status());
}
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
struct AmlG12I2sInTest : public AmlG12TdmStream {
AmlG12I2sInTest(zx_device_t* parent, ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12TdmStream(parent, true, std::move(pdev),
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>(std::move(enable_gpio)),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>()) {
metadata_.is_input = true;
metadata_.mClockDivFactor = 10;
metadata_.sClockDivFactor = 25;
metadata_.ring_buffer.number_of_channels = 2;
metadata_.dai.number_of_channels = 2;
metadata_.lanes_enable_mask[0] = 3;
metadata_.bus = metadata::AmlBus::TDM_C;
metadata_.version = metadata::AmlVersion::kS905D2G;
metadata_.dai.type = metadata::DaiType::I2s;
metadata_.dai.bits_per_sample = 16;
metadata_.dai.bits_per_slot = 32;
metadata_.codecs.number_of_codecs = 0;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
zx_status_t Init() __TA_REQUIRES(domain_token()) override {
SimpleAudioStream::SupportedFormat format = {};
format.range.min_channels = 2;
format.range.max_channels = 2;
format.range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
format.range.min_frames_per_second = 48'000;
format.range.max_frames_per_second = 96'000;
format.range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
supported_formats_.push_back(std::move(format));
driver_transfer_bytes_ = 16;
cur_gain_state_ = {};
SetInitialPlugState(AUDIO_PDNF_CAN_NOTIFY);
snprintf(device_name_, sizeof(device_name_), "Testy Device");
snprintf(mfr_name_, sizeof(mfr_name_), "Testy Inc");
snprintf(prod_name_, sizeof(prod_name_), "Testy McTest");
unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS;
constexpr uint64_t channels_to_use = 0x03;
return aml_audio_->InitHW(metadata_, channels_to_use, 48'000);
}
};
struct AmlG12PcmInTest : public AmlG12I2sInTest {
AmlG12PcmInTest(zx_device_t* parent, ddk_mock::MockMmioRegRegion& region, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12I2sInTest(parent, region, std::move(pdev), std::move(enable_gpio)) {
metadata_.ring_buffer.number_of_channels = 1;
metadata_.dai.number_of_channels = 1;
metadata_.lanes_enable_mask[0] = 1;
metadata_.dai.type = metadata::DaiType::Tdm1;
metadata_.dai.bits_per_slot = 16;
metadata_.dai.sclk_on_raising = true;
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, region.GetMmioBuffer());
}
};
TEST_F(StreamTest, InitializeI2sIn) {
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM IN for I2S.
mock[0x380].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM IN CTRL0 disable.
// TDM IN CTRL config, I2S, source TDM IN C, I2S mode, bitoffset 3, 2 slots, 16 bits per slot.
mock[0x380].ExpectWrite(0x4023001f);
mock[0x050].ExpectWrite(0xc1807c3f); // SCLK CTRL, enabled, 24 sdiv, 31 lrduty, 63 lrdiv.
// SCLK CTRL1, clear delay, sclk_invert_ph0.
mock[0x054].ExpectWrite(0x00000000).ExpectWrite(0x00000001);
// CLK TDMIN CTL, enable, sclk_inv, no sclk_ws_inv, mclk_ch 2.
mock[0x088].ExpectWrite(0).ExpectWrite(0xe2200000);
auto controller = CreateController<AmlG12I2sInTest>(mock);
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
TEST_F(StreamTest, InitializePcmIn) {
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion mock(sizeof(uint32_t), kRegSize);
// Configure TDM IN for PCM.
mock[0x380].ExpectRead(0xffffffff).ExpectWrite(0x7fffffff); // TDM IN CTRL0 disable.
// TDM IN CTRL config, TDM, source TDM IN C, TDM mode, bitoffset 3, 1 slot, 16 bits per slot.
mock[0x380].ExpectWrite(0x0023000f);
mock[0x050].ExpectWrite(0xc180000f); // SCLK CTRL, enabled, 24 sdiv, 0 lrduty, 15 lrdiv.
// SCLK CTRL1, clear delay, no sclk_invert_ph0.
mock[0x054].ExpectWrite(0x00000000).ExpectWrite(0x00000000);
// CLK TDMIN CTL, enable, sclk_inv, no sclk_ws_inv, mclk_ch 2.
mock[0x088].ExpectWrite(0).ExpectWrite(0xe2200000);
auto controller = CreateController<AmlG12PcmInTest>(mock);
auto* child_dev2 = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev2);
child_dev2->UnbindOp();
EXPECT_TRUE(child_dev2->UnbindReplyCalled());
mock.VerifyAll();
}
class FakeMmio {
public:
FakeMmio() : mmio_(sizeof(uint32_t), kRegCount) {}
fdf::MmioBuffer mmio() { return fdf::MmioBuffer(mmio_.GetMmioBuffer()); }
ddk_fake::FakeMmioReg& AtIndex(size_t ix) { return mmio_[ix * sizeof(uint32_t)]; }
private:
static constexpr size_t kRegCount =
S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_fake::FakeMmioRegRegion mmio_;
};
struct IncomingNamespace {
fake_pdev::FakePDevFidl pdev_server;
component::OutgoingDirectory outgoing{async_get_default_dispatcher()};
};
class TestAmlG12TdmStream : public AmlG12TdmStream {
public:
explicit TestAmlG12TdmStream(zx_device_t* parent, ddk::PDevFidl pdev,
fidl::ClientEnd<fuchsia_hardware_gpio::Gpio> enable_gpio)
: AmlG12TdmStream(parent, false, std::move(pdev),
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>(std::move(enable_gpio)),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>()) {}
bool AllowNonContiguousRingBuffer() override { return true; }
inspect::Inspector& inspect() { return AmlG12TdmStream::inspect(); }
};
metadata::AmlConfig GetDefaultMetadata() {
metadata::AmlConfig metadata = {};
metadata.is_input = false;
metadata.mClockDivFactor = 10;
metadata.sClockDivFactor = 25;
metadata.ring_buffer.number_of_channels = 2;
metadata.dai.number_of_channels = 2;
metadata.lanes_enable_mask[0] = 3;
metadata.bus = metadata::AmlBus::TDM_C;
metadata.version = metadata::AmlVersion::kS905D2G;
metadata.dai.type = metadata::DaiType::I2s;
metadata.dai.bits_per_sample = 16;
metadata.dai.bits_per_slot = 32;
return metadata;
}
class AmlG12TdmTest : public inspect::InspectTestHelper, public zxtest::Test {
public:
void SetUp() override {
ASSERT_OK(gpio_loop_.StartThread("gpio"));
fake_parent_ = MockDevice::FakeRootParent();
}
zx::result<ddk::PDevFidl> StartPDev() {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_platform_device::Device>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
zx_status_t status = incoming_loop_.StartThread("incoming-ns-thread");
if (status != ZX_OK) {
return zx::error(status);
}
fake_pdev::FakePDevFidl::Config config;
config.mmios[0] = mmio_.mmio();
config.use_fake_bti = true;
zx::interrupt irq;
status = zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq);
if (status != ZX_OK) {
return zx::error(status);
}
config.irqs[0] = std::move(irq);
incoming_.SyncCall([config = std::move(config),
server = std::move(endpoints->server)](IncomingNamespace* infra) mutable {
infra->pdev_server.SetConfig(std::move(config));
infra->pdev_server.Connect(std::move(server));
});
return zx::ok(ddk::PDevFidl(std::move(endpoints->client)));
}
void CreateRingBuffer() {
auto metadata = GetDefaultMetadata();
fake_parent_->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
zx::result pdev = StartPDev();
ASSERT_OK(pdev);
auto controller = audio::SimpleAudioStream::Create<TestAmlG12TdmStream>(
fake_parent_.get(), std::move(pdev.value()), std::move(enable_gpio_client));
auto* child_dev = fake_parent_->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
void TestRingBufferSize(uint8_t number_of_channels, uint32_t frames_req,
uint32_t frames_expected) {
auto metadata = GetDefaultMetadata();
metadata.ring_buffer.number_of_channels = number_of_channels;
metadata.lanes_enable_mask[0] = (1 << number_of_channels) - 1;
fake_parent_->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
zx::result pdev = StartPDev();
ASSERT_OK(pdev);
auto controller = audio::SimpleAudioStream::Create<TestAmlG12TdmStream>(
fake_parent_.get(), std::move(pdev.value()), std::move(enable_gpio_client));
auto* child_dev = fake_parent_->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.number_of_channels = number_of_channels;
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
auto vmo = fidl::WireCall(local)->GetVmo(frames_req, 0);
ASSERT_OK(vmo.status());
ASSERT_EQ(vmo->value()->num_frames, frames_expected);
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
void TestAttributes() {
metadata::AmlConfig metadata = GetDefaultMetadata();
metadata.ring_buffer.frequency_ranges[0].min_frequency = 40;
metadata.ring_buffer.frequency_ranges[0].max_frequency = 200;
metadata.ring_buffer.frequency_ranges[1].min_frequency = 200;
metadata.ring_buffer.frequency_ranges[1].max_frequency = 20'000;
fake_parent_->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
zx::result pdev = StartPDev();
ASSERT_OK(pdev);
auto controller = audio::SimpleAudioStream::Create<TestAmlG12TdmStream>(
fake_parent_.get(), std::move(pdev.value()), std::move(enable_gpio_client));
auto* child_dev = fake_parent_->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
// Check channels attributes.
auto supported = stream_client->GetSupportedFormats();
ASSERT_OK(supported.status());
auto& pcm_supported_formats0 = supported.value().supported_formats[0].pcm_supported_formats();
ASSERT_EQ(pcm_supported_formats0.frame_rates()[0], 8'000);
auto& attributes0 = pcm_supported_formats0.channel_sets()[0].attributes();
ASSERT_EQ(attributes0.count(), 2);
ASSERT_EQ(attributes0[0].min_frequency(), 40);
ASSERT_EQ(attributes0[0].max_frequency(), 200);
ASSERT_EQ(attributes0[1].min_frequency(), 200);
ASSERT_EQ(attributes0[1].max_frequency(), 20'000);
auto& pcm_supported_formats1 = supported.value().supported_formats[1].pcm_supported_formats();
ASSERT_EQ(pcm_supported_formats1.frame_rates()[0], 16'000);
auto& attributes1 = pcm_supported_formats1.channel_sets()[0].attributes();
ASSERT_EQ(attributes1.count(), 2);
ASSERT_EQ(attributes1[0].min_frequency(), 40);
ASSERT_EQ(attributes1[0].max_frequency(), 200);
ASSERT_EQ(attributes1[1].min_frequency(), 200);
ASSERT_EQ(attributes1[1].max_frequency(), 20'000);
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
FakeMmio mmio_;
async::Loop incoming_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{incoming_loop_.dispatcher(),
std::in_place};
protected:
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio>& enable_gpio() { return enable_gpio_; }
std::shared_ptr<zx_device> fake_parent() const { return fake_parent_; }
private:
async::Loop gpio_loop_{&kAsyncLoopConfigNoAttachToCurrentThread};
async_patterns::TestDispatcherBound<fake_gpio::FakeGpio> enable_gpio_{gpio_loop_.dispatcher(),
std::in_place};
std::shared_ptr<zx_device> fake_parent_;
};
// With 16 bits samples, frame size is 2 x number of channels bytes. FIFO is 1024 bytes.
// Frames returned depend on client requested min_frames, HW buffer alignment (8 bytes)
// and FIFO size.
// min_frames + FIFO size. With number of channels = 2 (frame size = 4):
// num_frames = min_frames + (FIFO / frame_size) = 2 + (1024 / 4) = 258.
TEST_F(AmlG12TdmTest, RingBufferSize1) { TestRingBufferSize(2, 2, 258); }
// Rounded to HW buffer alignment (8). With number of channels = 2 (frame size = 4),
// num_frames = min_frames + (FIFO / frame_size) = 257 + (1024 / 4) = 513, but then
// it is rounded to 2 frames alignmend (8 bytes) to 514.
TEST_F(AmlG12TdmTest, RingBufferSize2) { TestRingBufferSize(2, 257, 514); }
// Rounded to HW buffer alignment (8). With number of channels = 3 (frame size = 6),
// num_frames = min_frames + (FIFO / frame_size) = 1 + (1024 / 6) = 171, but then
// it is rounded to 2 frames alignmend (8 bytes) to 172.
TEST_F(AmlG12TdmTest, RingBufferSize3) { TestRingBufferSize(3, 1, 172); }
// Rounded to HW buffer alignment (8). With number of channels = 3 (frame size = 6),
// num_frames = min_frames + (FIFO / frame_size) = 341 + (1024 / 6) = 511, but then
// it is rounded to 2 frames alignmend (8 bytes) to 512.
TEST_F(AmlG12TdmTest, RingBufferSize4) { TestRingBufferSize(3, 341, 512); }
TEST_F(AmlG12TdmTest, Attributes) { TestAttributes(); }
TEST_F(AmlG12TdmTest, Rate) {
uint32_t mclk_ctrl = 0;
uint32_t sclk_ctrl = 0;
mmio_.AtIndex(0x3).SetWriteCallback([&mclk_ctrl](uint64_t value) { mclk_ctrl = value; });
mmio_.AtIndex(0x14).SetWriteCallback([&sclk_ctrl](uint64_t value) { sclk_ctrl = value; });
CreateRingBuffer(); // Defaults to 48kHz rate.
ASSERT_EQ(0x84000009, mclk_ctrl); // clkdiv = 9 for 48kHz rate.
ASSERT_EQ(0xC1807C3F, sclk_ctrl); // enabled, 24 sdiv, 31 lrduty, 63 lrdiv for 48kHz rate.
}
TEST_F(AmlG12TdmTest, Inspect) {
auto metadata = GetDefaultMetadata();
fake_parent()->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
auto enable_gpio_client = enable_gpio().SyncCall(&fake_gpio::FakeGpio::Connect);
zx::result pdev = StartPDev();
ASSERT_OK(pdev);
auto controller = audio::SimpleAudioStream::Create<TestAmlG12TdmStream>(
fake_parent().get(), std::move(pdev.value()), std::move(enable_gpio_client));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding;
binding = fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
format.set_pcm_format(allocator, GetDefaultPcmFormat());
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// Check inspect state.
ASSERT_NO_FATAL_FAILURE(ReadInspect(test_dev->inspect().DuplicateVmo()));
auto* simple_audio = hierarchy().GetByPath({"simple_audio_stream"});
ASSERT_TRUE(simple_audio);
ASSERT_NO_FATAL_FAILURE(
CheckProperty(simple_audio->node(), "state", inspect::StringPropertyValue("created")));
ASSERT_NO_FATAL_FAILURE(
CheckProperty(hierarchy().node(), "status_time", inspect::IntPropertyValue(0)));
ASSERT_NO_FATAL_FAILURE(
CheckProperty(hierarchy().node(), "dma_status", inspect::UintPropertyValue(0)));
ASSERT_NO_FATAL_FAILURE(
CheckProperty(hierarchy().node(), "tdm_status", inspect::UintPropertyValue(0)));
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(AmlG12TdmTest, NoGpio) {
zx::result pdev = StartPDev();
ASSERT_OK(pdev);
auto metadata = GetDefaultMetadata();
fake_parent()->SetMetadata(DEVICE_METADATA_PRIVATE, &metadata, sizeof(metadata));
auto controller = audio::SimpleAudioStream::Create<AmlG12TdmStream>(
fake_parent().get(), false, std::move(pdev.value()),
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>(), // No GPIO.
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>(),
fidl::WireSyncClient<fuchsia_hardware_clock::Clock>());
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
child_dev->UnbindOp();
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
TEST_F(PowerManagementTest, ClockGating) {
constexpr size_t kRegSize = S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
ddk_mock::MockMmioRegRegion unused_mock(sizeof(uint32_t), kRegSize);
auto enable_gpio_client = enable_gpio_.SyncCall(&fake_gpio::FakeGpio::Connect);
auto device = audio::SimpleAudioStream::Create<AmlG12I2sOutTest>(
fake_parent_.get(), std::vector<fidl::ClientEnd<fuchsia_hardware_audio::Codec>>{},
unused_mock, ddk::PDevFidl(), std::move(enable_gpio_client), std::move(clock_gate_client_),
std::move(pll_client_));
auto* child_dev = fake_parent()->GetLatestChild();
ASSERT_NOT_NULL(child_dev);
AmlG12I2sOutTest* test_dev = child_dev->GetDeviceContext<AmlG12I2sOutTest>();
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<audio_fidl::StreamConfigConnector>();
std::optional<fidl::ServerBindingRef<audio_fidl::StreamConfigConnector>> binding =
fidl::BindServer(loop.dispatcher(), std::move(endpoints->server), test_dev);
loop.StartThread("test-server");
auto stream_client = GetStreamClient(std::move(endpoints->client));
ASSERT_TRUE(stream_client.is_valid());
auto [local, remote] = fidl::Endpoints<audio_fidl::RingBuffer>::Create();
fidl::Arena allocator;
audio_fidl::wire::Format format(allocator);
audio_fidl::wire::PcmFormat pcm_format = GetDefaultPcmFormat();
format.set_pcm_format(allocator, std::move(pcm_format));
auto result = stream_client->CreateRingBuffer(std::move(format), std::move(remote));
ASSERT_OK(result.status());
// We need to get a VMO before starting a stream.
constexpr uint32_t kFramesRequested = 4096;
auto vmo = fidl::WireCall(local)->GetVmo(kFramesRequested, 0);
ASSERT_OK(vmo.status());
auto active1 = fidl::WireCall(local)->SetActiveChannels(0x5);
ASSERT_FALSE(active1->is_ok());
ASSERT_FALSE(IsClockGateEnabled()); // Set active channels failed, hence clock gate is disabled.
auto start = fidl::WireCall(local)->Start();
ASSERT_OK(start.status());
auto active2 = fidl::WireCall(local)->SetActiveChannels(0x2);
ASSERT_TRUE(active2->is_ok());
ASSERT_TRUE(IsClockGateEnabled()); // Some channels are active, then clock gate is enabled.
ASSERT_TRUE(IsPllEnabled()); // Some channels are active, then PLL is enabled.
auto active3 = fidl::WireCall(local)->SetActiveChannels(0x0);
ASSERT_TRUE(active3->is_ok());
ASSERT_FALSE(IsClockGateEnabled()); // No channels are active, then clock gate is disabled.
ASSERT_FALSE(IsPllEnabled()); // No channels are active, then PLL is disabled.
auto stop = fidl::WireCall(local)->Stop();
ASSERT_OK(stop.status());
// Ok to set active channels in a stopped state.
auto active4 = fidl::WireCall(local)->SetActiveChannels(0x2);
ASSERT_TRUE(active4->is_ok());
ASSERT_TRUE(IsClockGateEnabled()); // Some channels are active, then clock gate is enabled.
ASSERT_TRUE(IsPllEnabled()); // Some channels are active, then PLL is enabled.
child_dev->UnbindOp();
ASSERT_FALSE(IsClockGateEnabled()); // Shutdown disables the clock gate.
ASSERT_FALSE(IsPllEnabled()); // Shutdown disables the PLL.
EXPECT_TRUE(child_dev->UnbindReplyCalled());
}
} // namespace audio::aml_g12