blob: 6ca8ea595d57185362cbefe3655cca67cc40702b [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 "tas5720.h"
#include <fuchsia/hardware/gpio/cpp/banjo-mock.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/mock-i2c/mock-i2c.h>
#include <lib/simple-codec/simple-codec-client.h>
#include <lib/simple-codec/simple-codec-helper.h>
#include <lib/sync/completion.h>
#include <lib/ddk/metadata.h>
#include <zxtest/zxtest.h>
namespace audio {
audio::DaiFormat GetDefaultDaiFormat() {
return {
.number_of_channels = 2,
.channels_to_use_bitmask = 1, // Use one channel in this mono codec.
.sample_format = SampleFormat::PCM_SIGNED,
.frame_format = FrameFormat::STEREO_LEFT,
.frame_rate = 24'000,
.bits_per_slot = 32,
.bits_per_sample = 16,
};
}
class Tas5720Test : public zxtest::Test {
public:
void SetUp() override {
// Reset by the TAS driver initialization.
mock_i2c_.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // Exit shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of stop).
.ExpectWriteStop({0x02, 0x45}) // Digital control defaults. Left justified.
.ExpectWriteStop({0x03, 0x10}) // Digital control defaults. Slot 0, muted.
.ExpectWriteStop({0x06, 0x5d}) // Analog defaults.
.ExpectWriteStop({0x10, 0xff}) // clippers disabled.
.ExpectWriteStop({0x11, 0xfc}) // clippers disabled.
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // exit shutdown (part of start).
.ExpectWriteStop({0x06, 0x51}) // Default gain.
.ExpectWriteStop({0x04, 0xa1}) // Default gain.
.ExpectWrite({0x03})
.ExpectReadStop({0x00})
.ExpectWriteStop({0x03, 0x10}); // Muted.
}
mock_i2c::MockI2c mock_i2c_;
};
struct Tas5720Codec : public Tas5720 {
explicit Tas5720Codec(ddk::I2cChannel i2c) : Tas5720(fake_ddk::kFakeParent, std::move(i2c)) {}
codec_protocol_t GetProto() { return {&this->codec_protocol_ops_, this}; }
};
TEST_F(Tas5720Test, CodecInitGood) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
// Shutdown.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe});
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST(Tas5720Test, CodecInitBad) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
mock_i2c::MockI2c mock_i2c;
// Bad replies (2 retries) to enter shutdown (part of reset).
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT);
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT); // Retry 1.
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT); // Retry 2.
// Bad replies (2 retries) to enter shutdown (part of shutdown becuase init failed).
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT);
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT); // Retry 1.
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}, ZX_ERR_TIMED_OUT); // Retry 2.
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c.GetProto());
ASSERT_NULL(codec);
mock_i2c.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecGetInfo) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
auto info = client.GetInfo();
ASSERT_EQ(info->unique_id.compare(""), 0);
ASSERT_EQ(info->manufacturer.compare("Texas Instruments"), 0);
ASSERT_EQ(info->product_name.compare("TAS5720"), 0);
// Shutdown.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe});
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecReset) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
// We complete all i2c mock setup before executing server methods in a different thread.
// Reset by the call to Reset.
mock_i2c_.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // Exit shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of stop).
.ExpectWriteStop({0x02, 0x45}) // Digital control defaults. TODO set I2S.
.ExpectWriteStop({0x03, 0x10}) // Digital control defaults. Slot 0, muted.
.ExpectWriteStop({0x06, 0x5d}) // Analog defaults.
.ExpectWriteStop({0x10, 0xff}) // clippers disabled.
.ExpectWriteStop({0x11, 0xfc}) // clippers disabled.
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // exit shutdown (part of start).
.ExpectWriteStop({0x06, 0x51}) // Default gain.
.ExpectWriteStop({0x04, 0xa1}) // Default gain.
.ExpectWrite({0x03})
.ExpectReadStop({0x00})
.ExpectWriteStop({0x03, 0x10}); // Muted.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe}); // Shutdown.
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
ASSERT_OK(client.Reset());
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecBridgedMode) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
{
auto bridgeable = client.IsBridgeable();
ASSERT_FALSE(bridgeable.value());
}
{ client.SetBridgedMode(false); }
// Shutdown.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe});
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecDaiFormat) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
// We complete all i2c mock setup before executing server methods in a different thread.
mock_i2c_.ExpectWrite({0x03}).ExpectReadStop({0xff});
mock_i2c_.ExpectWriteStop({0x03, 0xfc}); // Set slot to 0.
mock_i2c_.ExpectWriteStop({0x02, 0x45}); // Set rate to 48kHz.
mock_i2c_.ExpectWrite({0x03}).ExpectReadStop({0xff});
mock_i2c_.ExpectWriteStop({0x03, 0xfc}); // Set slot to 0.
mock_i2c_.ExpectWriteStop({0x02, 0x4d}); // Set rate to 96kHz.
mock_i2c_.ExpectWrite({0x03}).ExpectReadStop({0xff});
mock_i2c_.ExpectWriteStop({0x03, 0xfc}); // Set slot to 0.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe}); // Shutdown.
// Check getting DAI formats.
{
auto formats = client.GetDaiFormats();
ASSERT_EQ(formats.value().number_of_channels.size(), 1);
ASSERT_EQ(formats.value().number_of_channels[0], 2);
ASSERT_EQ(formats.value().sample_formats.size(), 1);
ASSERT_EQ(formats.value().sample_formats[0], SampleFormat::PCM_SIGNED);
ASSERT_EQ(formats.value().frame_formats.size(), 2);
ASSERT_EQ(formats.value().frame_formats[0], FrameFormat::STEREO_LEFT);
ASSERT_EQ(formats.value().frame_formats[1], FrameFormat::I2S);
ASSERT_EQ(formats.value().frame_rates.size(), 2);
ASSERT_EQ(formats.value().frame_rates[0], 48000);
ASSERT_EQ(formats.value().frame_rates[1], 96000);
ASSERT_EQ(formats.value().bits_per_slot.size(), 1);
ASSERT_EQ(formats.value().bits_per_slot[0], 32);
ASSERT_EQ(formats.value().bits_per_sample.size(), 1);
ASSERT_EQ(formats.value().bits_per_sample[0], 16);
}
// Check setting DAI formats.
{
DaiFormat format = GetDefaultDaiFormat();
format.frame_rate = 48'000;
auto formats = client.GetDaiFormats();
ASSERT_TRUE(IsDaiFormatSupported(format, formats.value()));
ASSERT_OK(client.SetDaiFormat(std::move(format)));
}
{
DaiFormat format = GetDefaultDaiFormat();
format.frame_rate = 96'000;
auto formats = client.GetDaiFormats();
ASSERT_TRUE(IsDaiFormatSupported(format, formats.value()));
ASSERT_OK(client.SetDaiFormat(std::move(format)));
}
{
DaiFormat format = GetDefaultDaiFormat();
format.frame_rate = 192'000;
auto formats = client.GetDaiFormats();
ASSERT_FALSE(IsDaiFormatSupported(format, formats.value()));
ASSERT_NOT_OK(client.SetDaiFormat(std::move(format)));
}
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecGain) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
mock_i2c_
.ExpectWriteStop({0x06, 0x51}) // Analog 19.2dBV.
.ExpectWriteStop({0x04, 0x9d}) // Digital -32dB.
.ExpectWrite({0x03})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x03, 0xef}); // Not muted.
// Lower than min gain.
mock_i2c_
.ExpectWriteStop({0x06, 0x51}) // Analog 19.2dBV (min)
.ExpectWriteStop({0x04, 0x00}) // Digital -110.6dB.
.ExpectWrite({0x03})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x03, 0xef}); // Not muted.
// Higher than max gain.
mock_i2c_
.ExpectWriteStop({0x06, 0x5d}) // Analog 23.5dBV (max).
.ExpectWriteStop({0x04, 0xff}) // Digital +24dB.
.ExpectWrite({0x03})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x03, 0xef}); // Not muted.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe}); // Shutdown.
client.SetGainState({
.gain = -32.f,
.muted = false,
.agc_enabled = false,
});
client.SetGainState({
.gain = -999.f,
.muted = false,
.agc_enabled = false,
});
client.SetGainState({
.gain = 111.f,
.muted = false,
.agc_enabled = false,
});
// Make a 2-wal call to make sure the server (we know single threaded) completed previous calls.
auto unused = client.GetInfo();
static_cast<void>(unused);
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok()); // Guarantees Unbind called so we can test for shutdown.
codec.release()->DdkRelease(); // codec release managed by the DDK.
mock_i2c_.VerifyAndClear();
}
TEST_F(Tas5720Test, CodecPlugState) {
fake_ddk::Bind tester;
uint32_t instance_count = 0;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c_.GetProto());
ASSERT_NOT_NULL(codec);
auto codec_proto = codec->GetProto();
SimpleCodecClient client;
client.SetProtocol(&codec_proto);
// Shutdown.
mock_i2c_.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe});
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c_.VerifyAndClear();
}
TEST(Tas5720Test, InstanceCount) {
fake_ddk::Bind tester;
uint32_t instance_count = 2;
tester.SetMetadata(DEVICE_METADATA_PRIVATE, &instance_count, sizeof(instance_count));
mock_i2c::MockI2c mock_i2c;
// Reset by the TAS driver initialization setting slot to 2.
mock_i2c.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // Exit shutdown (part of reset).
.ExpectWrite({0x01})
.ExpectReadStop({0xff})
.ExpectWriteStop({0x01, 0xfe}) // Enter shutdown (part of stop).
.ExpectWriteStop({0x02, 0x45}) // Digital control defaults. TODO set I2S.
.ExpectWriteStop({0x03, 0x10}) // Digital control defaults. Slot 2, muted.
.ExpectWriteStop({0x06, 0x5d}) // Analog defaults.
.ExpectWriteStop({0x10, 0xff}) // clippers disabled.
.ExpectWriteStop({0x11, 0xfc}) // clippers disabled.
.ExpectWrite({0x01})
.ExpectReadStop({0xfe})
.ExpectWriteStop({0x01, 0xff}) // exit shutdown (part of start).
.ExpectWriteStop({0x06, 0x51}) // Default gain.
.ExpectWriteStop({0x04, 0xa1}) // Default gain.
.ExpectWrite({0x03})
.ExpectReadStop({0x00})
.ExpectWriteStop({0x03, 0x10}); // Muted.
auto codec = SimpleCodecServer::Create<Tas5720Codec>(mock_i2c.GetProto());
ASSERT_NOT_NULL(codec);
// Shutdown.
mock_i2c.ExpectWrite({0x01}).ExpectReadStop({0xff}).ExpectWriteStop({0x01, 0xfe});
codec->DdkAsyncRemove();
ASSERT_TRUE(tester.Ok());
codec.release()->DdkRelease(); // codec release managed by the DDK
mock_i2c.VerifyAndClear();
}
} // namespace audio