blob: d9e82fd1970a1436129e45a1ca37687b319518a8 [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/media/audio/drivers/aml-g12-tdm/composite-server.h"
#include <lib/driver/component/cpp/driver_base.h>
#include <zircon/errors.h>
#include <algorithm>
#include <numeric>
#include <fbl/algorithm.h>
#include "src/lib/memory_barriers/memory_barriers.h"
namespace audio::aml_g12 {
// In C++23 we can remove this and just use std::ranges::contains.
template <typename Container, typename T>
bool contains(Container c, T v) {
return std::find(std::begin(c), std::end(c), v) != std::end(c);
}
// The Composite interface returns DriverError upon error, not zx_status_t, so convert any error.
fuchsia_hardware_audio::DriverError ZxStatusToDriverError(zx_status_t status) {
switch (status) {
case ZX_ERR_NOT_SUPPORTED:
return fuchsia_hardware_audio::DriverError::kNotSupported;
case ZX_ERR_INVALID_ARGS:
return fuchsia_hardware_audio::DriverError::kInvalidArgs;
case ZX_ERR_WRONG_TYPE:
return fuchsia_hardware_audio::DriverError::kWrongType;
case ZX_ERR_SHOULD_WAIT:
return fuchsia_hardware_audio::DriverError::kShouldWait;
default:
return fuchsia_hardware_audio::DriverError::kInternalError;
}
}
AudioCompositeServer::AudioCompositeServer(
std::array<std::optional<fdf::MmioBuffer>, kNumberOfTdmEngines> mmios, zx::bti bti,
async_dispatcher_t* dispatcher, metadata::AmlVersion aml_version,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_client,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> pll_client,
std::vector<fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio>> gpio_sclk_clients)
: dispatcher_(dispatcher),
bti_(std::move(bti)),
clock_gate_(std::move(clock_gate_client)),
pll_(std::move(pll_client)),
gpio_sclk_clients_(std::move(gpio_sclk_clients)) {
for (auto& dai : kDaiIds) {
element_completers_[dai].first_response_sent = false;
element_completers_[dai].completer = {};
}
for (auto& ring_buffer : kRingBufferIds) {
element_completers_[ring_buffer].first_response_sent = false;
element_completers_[ring_buffer].completer = {};
}
topology_completer_.first_response_sent = false;
for (size_t i = 0; i < kNumberOfPipelines; ++i) {
// Default supported DAI formats.
supported_dai_formats_[i].number_of_channels(
AmlTdmConfigDevice::GetSupportedNumberOfChannels());
supported_dai_formats_[i].sample_formats(AmlTdmConfigDevice::GetFidlSupportedSampleFormats());
supported_dai_formats_[i].frame_formats(AmlTdmConfigDevice::GetFidlSupportedFrameFormats());
supported_dai_formats_[i].frame_rates(AmlTdmConfigDevice::GetSupportedFrameRates());
supported_dai_formats_[i].bits_per_slot(AmlTdmConfigDevice::GetSupportedBitsPerSlot());
supported_dai_formats_[i].bits_per_sample(AmlTdmConfigDevice::GetSupportedBitsPerSample());
// Take values from supported list to define current DAI format.
current_dai_formats_[i].emplace(
supported_dai_formats_[i].number_of_channels()[0],
(1 << supported_dai_formats_[i].number_of_channels()[0]) - 1, // enable all channels.
supported_dai_formats_[i].sample_formats()[0], supported_dai_formats_[i].frame_formats()[0],
supported_dai_formats_[i].frame_rates()[0], supported_dai_formats_[i].bits_per_slot()[0],
supported_dai_formats_[i].bits_per_sample()[0]);
}
ZX_ASSERT(StartSocPower() == ZX_OK);
// Output engines.
ZX_ASSERT(ConfigEngine(0, 0, false, std::move(mmios[0].value()), aml_version) == ZX_OK);
ZX_ASSERT(ConfigEngine(1, 1, false, std::move(mmios[1].value()), aml_version) == ZX_OK);
ZX_ASSERT(ConfigEngine(2, 2, false, std::move(mmios[2].value()), aml_version) == ZX_OK);
// Input engines.
ZX_ASSERT(ConfigEngine(3, 0, true, std::move(mmios[3].value()), aml_version) == ZX_OK);
ZX_ASSERT(ConfigEngine(4, 1, true, std::move(mmios[4].value()), aml_version) == ZX_OK);
ZX_ASSERT(ConfigEngine(5, 2, true, std::move(mmios[5].value()), aml_version) == ZX_OK);
// Unconditional reset on construction.
for (size_t i = 0; i < kNumberOfTdmEngines; ++i) {
ZX_ASSERT(ResetEngine(i) == ZX_OK);
}
// Make sure the DMAs are stopped before releasing quarantine.
for (auto& engine : engines_) {
engine.device->Stop();
}
// Make sure that all reads/writes have gone through.
BarrierBeforeRelease();
ZX_ASSERT(bti_.release_quarantine() == ZX_OK);
#if 0
// TODO(b/309153055): Once integration with the Power Framework is completed, we can remove
// this testing code.
TestPowerManagement();
#endif
}
zx_status_t AudioCompositeServer::ConfigEngine(size_t index, size_t dai_index, bool input,
fdf::MmioBuffer mmio,
metadata::AmlVersion aml_version) {
// Common configuration.
engines_[index].config = {};
engines_[index].config.version = aml_version;
engines_[index].config.mClockDivFactor = 10;
engines_[index].config.sClockDivFactor = 25;
// Supported ring buffer formats.
// We take some values from supported DAI formats.
fuchsia_hardware_audio::PcmSupportedFormats pcm_formats;
// Vector with number_of_channels empty attributes supported in Ring Buffer
// equal to the number of channels supported on DAI.
std::vector<fuchsia_hardware_audio::ChannelAttributes> attributes(
supported_dai_formats_[dai_index].number_of_channels()[0]);
fuchsia_hardware_audio::ChannelSet channel_set;
channel_set.attributes(std::move(attributes));
pcm_formats.channel_sets(std::vector{channel_set});
// Frame rates supported on DAI are supported in Ring Buffer.
pcm_formats.frame_rates(supported_dai_formats_[dai_index].frame_rates());
// Sample format is PCM signed.
pcm_formats.sample_formats(std::vector{fuchsia_hardware_audio::SampleFormat::kPcmSigned});
// Bits per slot supported on Ring Buffer.
pcm_formats.bytes_per_sample(AmlTdmConfigDevice::GetSupportedRingBufferBytesPerSlot());
// Valid bits per sample supported on Ring Buffer.
auto v = AmlTdmConfigDevice::GetSupportedRingBufferBytesPerSlot();
std::transform(v.begin(), v.end(), v.begin(),
[](const uint8_t bytes_per_slot) -> uint8_t { return bytes_per_slot * 8; });
pcm_formats.valid_bits_per_sample(v);
supported_ring_buffer_formats_[index] = std::move(pcm_formats);
engines_[index].dai_index = dai_index;
engines_[index].config.is_input = input;
switch (dai_index) {
// clang-format off
case 0: engines_[index].config.bus = metadata::AmlBus::TDM_A; break;
case 1: engines_[index].config.bus = metadata::AmlBus::TDM_B; break;
case 2: engines_[index].config.bus = metadata::AmlBus::TDM_C; break;
// clang-format on
default:
return ZX_ERR_INVALID_ARGS;
}
engines_[index].device.emplace(engines_[index].config, std::move(mmio));
// Unconditional reset on configuration.
return ResetEngine(index);
}
zx_status_t AudioCompositeServer::ResetEngine(size_t index) {
// Resets engine using AmlTdmConfigDevice, so we need to translate the current state
// into parameters used by AmlTdmConfigDevice.
ZX_ASSERT(engines_[index].dai_index < kNumberOfPipelines);
ZX_ASSERT(current_dai_formats_[engines_[index].dai_index].has_value());
const fuchsia_hardware_audio::DaiFormat& dai_format =
*current_dai_formats_[engines_[index].dai_index];
if (dai_format.sample_format() != fuchsia_hardware_audio::DaiSampleFormat::kPcmSigned) {
FDF_LOG(ERROR, "Sample format not supported");
return ZX_ERR_NOT_SUPPORTED;
}
auto& dai_type = engines_[index].config.dai.type;
using StandardFormat = fuchsia_hardware_audio::DaiFrameFormatStandard;
using DaiType = metadata::DaiType;
switch (dai_format.frame_format().frame_format_standard().value()) {
// clang-format off
case StandardFormat::kI2S: dai_type = DaiType::I2s; break;
case StandardFormat::kStereoLeft: dai_type = DaiType::StereoLeftJustified; break;
case StandardFormat::kTdm1: dai_type = DaiType::Tdm1; break;
case StandardFormat::kTdm2: dai_type = DaiType::Tdm2; break;
case StandardFormat::kTdm3: dai_type = DaiType::Tdm3; break;
// clang-format on
case StandardFormat::kNone:
[[fallthrough]];
case StandardFormat::kStereoRight:
[[fallthrough]];
default:
FDF_LOG(ERROR, "Frame format not supported");
return ZX_ERR_NOT_SUPPORTED;
}
engines_[index].config.dai.bits_per_sample = dai_format.bits_per_sample();
engines_[index].config.dai.bits_per_slot = dai_format.bits_per_slot();
engines_[index].config.dai.number_of_channels =
static_cast<uint8_t>(dai_format.number_of_channels());
engines_[index].config.ring_buffer.number_of_channels =
engines_[index].config.dai.number_of_channels;
// AMLogic allows channel swapping with a channel number per nibble in registers like
// EE_AUDIO_TDMOUT_A_SWAP and EE_AUDIO_TDMIN_A_SWAP.
constexpr uint32_t kNoSwaps = 0x76543210; // Default channel numbers for no swaps.
engines_[index].config.swaps = kNoSwaps;
size_t lane = engines_[index].config.is_input ? 1 : 0;
engines_[index].config.lanes_enable_mask[lane] =
(1 << dai_format.number_of_channels()) - 1; // enable all channels.
zx_status_t status = AmlTdmConfigDevice::Normalize(engines_[index].config);
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to normalize config: %s", zx_status_get_string(status));
return status;
}
status = engines_[index].device->InitHW(
engines_[index].config, dai_format.channels_to_use_bitmask(), dai_format.frame_rate());
if (status != ZX_OK) {
FDF_LOG(ERROR, "Failed to init hardware: %s", zx_status_get_string(status));
}
return status;
}
void AudioCompositeServer::Reset(ResetCompleter::Sync& completer) {
for (size_t i = 0; i < kNumberOfTdmEngines; ++i) {
if (zx_status_t status = ResetEngine(i); status != ZX_OK) {
completer.Reply(zx::error(ZxStatusToDriverError(status)));
return;
}
}
completer.Reply(zx::ok());
}
void AudioCompositeServer::GetProperties(
fidl::Server<fuchsia_hardware_audio::Composite>::GetPropertiesCompleter::Sync& completer) {
fuchsia_hardware_audio::CompositeProperties props;
props.clock_domain(fuchsia_hardware_audio::kClockDomainMonotonic);
completer.Reply(std::move(props));
}
void AudioCompositeServer::GetHealthState(GetHealthStateCompleter::Sync& completer) {
completer.Reply(fuchsia_hardware_audio::HealthState{}.healthy(true));
}
// Note that if already bound, we close the NEW channel (not the one on which we were called).
void AudioCompositeServer::SignalProcessingConnect(
SignalProcessingConnectRequest& request, SignalProcessingConnectCompleter::Sync& completer) {
if (signal_) {
request.protocol().Close(ZX_ERR_ALREADY_BOUND);
return;
}
// Reset all completion state related to signalprocessing.
topology_completer_.completer.reset();
topology_completer_.first_response_sent = false;
for (auto& element_entry : element_completers_) {
element_entry.second.completer.reset();
element_entry.second.first_response_sent = false;
}
signal_.emplace(dispatcher(), std::move(request.protocol()), this,
std::mem_fn(&AudioCompositeServer::OnSignalProcessingClosed));
}
void AudioCompositeServer::OnSignalProcessingClosed(fidl::UnbindInfo info) {
if (info.is_peer_closed()) {
FDF_LOG(INFO, "Client disconnected");
} else if (!info.is_user_initiated()) {
// Do not log canceled cases; these happen particularly frequently in certain test cases.
if (info.status() != ZX_ERR_CANCELED) {
FDF_LOG(ERROR, "Client connection unbound: %s", info.status_string());
}
}
if (signal_) {
signal_.reset();
}
}
void AudioCompositeServer::GetRingBufferFormats(GetRingBufferFormatsRequest& request,
GetRingBufferFormatsCompleter::Sync& completer) {
auto ring_buffer =
std::find(kRingBufferIds.begin(), kRingBufferIds.end(), request.processing_element_id());
if (ring_buffer == kRingBufferIds.end()) {
FDF_LOG(ERROR, "Unknown Ring Buffer id (%lu) for format retrieval",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
size_t ring_buffer_index = ring_buffer - kRingBufferIds.begin();
ZX_ASSERT(ring_buffer_index < kNumberOfTdmEngines);
fuchsia_hardware_audio::SupportedFormats formats_entry;
formats_entry.pcm_supported_formats(supported_ring_buffer_formats_[ring_buffer_index]);
completer.Reply(zx::ok(std::vector{std::move(formats_entry)}));
}
void AudioCompositeServer::CreateRingBuffer(CreateRingBufferRequest& request,
CreateRingBufferCompleter::Sync& completer) {
auto ring_buffer =
std::find(kRingBufferIds.begin(), kRingBufferIds.end(), request.processing_element_id());
if (ring_buffer == kRingBufferIds.end()) {
FDF_LOG(ERROR, "Unknown Ring Buffer id (%lu) for creation", request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
size_t ring_buffer_index = ring_buffer - kRingBufferIds.begin();
ZX_ASSERT(ring_buffer_index < kNumberOfTdmEngines);
auto& supported = supported_ring_buffer_formats_[ring_buffer_index];
if (!request.format().pcm_format().has_value()) {
FDF_LOG(ERROR, "No PCM formats provided for Ring Buffer id (%lu)",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
ZX_ASSERT(supported.channel_sets().has_value());
ZX_ASSERT(supported.sample_formats().has_value());
ZX_ASSERT(supported.bytes_per_sample().has_value());
ZX_ASSERT(supported.valid_bits_per_sample().has_value());
ZX_ASSERT(supported.frame_rates().has_value());
auto& requested = *request.format().pcm_format();
bool number_of_channels_found = false;
for (auto& supported_channel_set : *supported.channel_sets()) {
ZX_ASSERT(supported_channel_set.attributes().has_value());
if (supported_channel_set.attributes()->size() == requested.number_of_channels()) {
number_of_channels_found = true;
break;
}
}
if (!number_of_channels_found) {
FDF_LOG(ERROR, "Ring Buffer number of channels for Ring Buffer id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(*supported.sample_formats(), requested.sample_format())) {
FDF_LOG(ERROR, "Ring Buffer sample format for Ring Buffer id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(*supported.bytes_per_sample(), requested.bytes_per_sample())) {
FDF_LOG(ERROR, "Ring Buffer bytes per sample for Ring Buffer id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(*supported.valid_bits_per_sample(), requested.valid_bits_per_sample())) {
FDF_LOG(ERROR, "Ring Buffer valid bits per sample for Ring Buffer id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(*supported.frame_rates(), requested.frame_rate())) {
FDF_LOG(ERROR, "Ring Buffer frame rate for Ring Buffer id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
auto& engine = engines_[ring_buffer_index];
if (engine.ring_buffer) {
// If it exists, we unbind the previous ring buffer channel with a ZX_ERR_NO_RESOURCES
// epitaph to convey that the previous ring buffer resource is not there anymore.
engine.ring_buffer->Unbind(ZX_ERR_NO_RESOURCES);
}
current_ring_buffer_formats_[ring_buffer_index].emplace(request.format());
engine.ring_buffer = RingBufferServer::CreateRingBufferServer(dispatcher(), *this, engine,
std::move(request.ring_buffer()));
completer.Reply(zx::ok());
}
void AudioCompositeServer::GetDaiFormats(GetDaiFormatsRequest& request,
GetDaiFormatsCompleter::Sync& completer) {
auto dai = std::find(kDaiIds.begin(), kDaiIds.end(), request.processing_element_id());
if (dai == kDaiIds.end()) {
FDF_LOG(ERROR, "Unknown DAI id (%lu) for GetDaiFormats", request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
size_t dai_index = dai - kDaiIds.begin();
ZX_ASSERT(dai_index < kNumberOfPipelines);
completer.Reply(zx::ok(std::vector{supported_dai_formats_[dai_index]}));
}
void AudioCompositeServer::SetDaiFormat(SetDaiFormatRequest& request,
SetDaiFormatCompleter::Sync& completer) {
auto dai = std::find(kDaiIds.begin(), kDaiIds.end(), request.processing_element_id());
if (dai == kDaiIds.end()) {
FDF_LOG(ERROR, "Unknown DAI id (%lu) for SetDaiFormat", request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
size_t dai_index = dai - kDaiIds.begin();
ZX_ASSERT(dai_index < kNumberOfPipelines);
auto& supported = supported_dai_formats_[dai_index];
if (!contains(supported.number_of_channels(), request.format().number_of_channels())) {
FDF_LOG(ERROR, "DAI format number of channels for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (request.format().channels_to_use_bitmask() >= (1u << request.format().number_of_channels())) {
FDF_LOG(ERROR, "DAI format channels-to-use 0x%zx out of range, for DAI id (%lu)",
request.format().channels_to_use_bitmask(), request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(supported.sample_formats(), request.format().sample_format())) {
FDF_LOG(ERROR, "DAI format sample format for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(supported.frame_formats(), request.format().frame_format())) {
FDF_LOG(ERROR, "DAI format frame format for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(supported.frame_rates(), request.format().frame_rate())) {
FDF_LOG(ERROR, "DAI format frame rate for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(supported.bits_per_slot(), request.format().bits_per_slot())) {
FDF_LOG(ERROR, "DAI format bits per slot for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
if (!contains(supported.bits_per_sample(), request.format().bits_per_sample())) {
FDF_LOG(ERROR, "DAI format bits per sample for DAI id (%lu) not supported",
request.processing_element_id());
completer.Reply(zx::error(fuchsia_hardware_audio::DriverError::kInvalidArgs));
return;
}
current_dai_formats_[dai_index] = request.format();
for (size_t i = 0; i < kNumberOfTdmEngines; ++i) {
if (engines_[i].dai_index == dai_index) {
if (zx_status_t status = ResetEngine(i); status != ZX_OK) {
completer.Reply(zx::error(ZxStatusToDriverError(status)));
return;
}
}
}
completer.Reply(zx::ok());
}
zx_status_t AudioCompositeServer::StartSocPower() {
// TODO(b/309153055): Use this method when we integrate with Power Framework.
// Only if needed (not done previously) so voting on relevant clock ids is not repeated.
// Each driver instance (audio or any other) may vote independently.
if (soc_power_started_ == true) {
return ZX_OK;
}
fidl::WireResult clock_gate_result = clock_gate_->Enable();
if (!clock_gate_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to enable clock gate: %s",
clock_gate_result.status_string());
return clock_gate_result.status();
}
if (clock_gate_result->is_error()) {
FDF_LOG(ERROR, "Send request to enable clock gate error: %s",
zx_status_get_string(clock_gate_result->error_value()));
return clock_gate_result->error_value();
}
fidl::WireResult pll_result = pll_->Enable();
if (!pll_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to enable PLL: %s", pll_result.status_string());
return pll_result.status();
}
if (pll_result->is_error()) {
FDF_LOG(ERROR, "Send request to enable PLL error: %s",
zx_status_get_string(pll_result->error_value()));
return pll_result->error_value();
}
constexpr uint32_t kMsecsToStabilizePll = 10;
zx::nanosleep(zx::deadline_after(zx::msec(kMsecsToStabilizePll)));
for (auto& gpio_sclk_client : gpio_sclk_clients_) {
constexpr uint32_t kSclkAltFunction = 1;
fidl::WireResult result = gpio_sclk_client->SetAltFunction(kSclkAltFunction);
if (!result.ok()) {
FDF_LOG(ERROR, "Failed to send request to set GPIO function: %s", result.status_string());
return result.status();
}
}
soc_power_started_ = true;
return ZX_OK;
}
zx_status_t AudioCompositeServer::StopSocPower() {
// TODO(b/309153055): Use this method when we integrate with Power Framework.
// Only if needed (not done previously) so voting on relevant clock ids is not repeated.
// Each driver instance (audio or any other) may vote independently.
if (soc_power_started_ == false) {
return ZX_OK;
}
for (auto& gpio_sclk_client : gpio_sclk_clients_) {
constexpr uint32_t kGpioAltFunction = 0;
fidl::WireResult alt_function_result = gpio_sclk_client->SetAltFunction(kGpioAltFunction);
if (!alt_function_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to set GPIO function: %s",
alt_function_result.status_string());
return alt_function_result.status();
}
fidl::WireResult config_out_result = gpio_sclk_client->ConfigOut(0);
if (!config_out_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to set GPIO output: %s",
config_out_result.status_string());
return config_out_result.status();
}
}
// MMIO access is still valid after clock gating the audio subsystem.
fidl::WireResult clock_gate_result = clock_gate_->Disable();
if (!clock_gate_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to disable clock gate: %s",
clock_gate_result.status_string());
return clock_gate_result.status();
}
if (clock_gate_result->is_error()) {
FDF_LOG(ERROR, "Send request to disable clock gate error: %s",
zx_status_get_string(clock_gate_result->error_value()));
return clock_gate_result->error_value();
}
// MMIO access is still valid after disableing the PLL used.
fidl::WireResult pll_result = pll_->Disable();
if (!pll_result.ok()) {
FDF_LOG(ERROR, "Failed to send request to disable PLL: %s", pll_result.status_string());
return pll_result.status();
}
if (pll_result->is_error()) {
FDF_LOG(ERROR, "Send request to disable PLL error: %s",
zx_status_get_string(pll_result->error_value()));
return pll_result->error_value();
}
soc_power_started_ = false;
return ZX_OK;
}
// static
std::unique_ptr<RingBufferServer> RingBufferServer::CreateRingBufferServer(
async_dispatcher_t* dispatcher, AudioCompositeServer& owner, Engine& engine,
fidl::ServerEnd<fuchsia_hardware_audio::RingBuffer> ring_buffer) {
return std::make_unique<RingBufferServer>(dispatcher, owner, engine, std::move(ring_buffer));
}
RingBufferServer::RingBufferServer(async_dispatcher_t* dispatcher, AudioCompositeServer& owner,
Engine& engine,
fidl::ServerEnd<fuchsia_hardware_audio::RingBuffer> ring_buffer)
: engine_(engine),
dispatcher_(dispatcher),
owner_(owner),
binding_(fidl::ServerBinding<fuchsia_hardware_audio::RingBuffer>(
dispatcher, std::move(ring_buffer), this,
std::mem_fn(&RingBufferServer::OnRingBufferClosed))) {
ResetRingBuffer();
}
void RingBufferServer::OnRingBufferClosed(fidl::UnbindInfo info) {
if (info.is_peer_closed()) {
FDF_LOG(INFO, "Client disconnected");
} else if (!info.is_user_initiated()) {
// Do not log canceled cases; these happen particularly frequently in certain test cases.
if (info.status() != ZX_ERR_CANCELED) {
FDF_LOG(ERROR, "Client connection unbound: %s", info.status_string());
}
}
ResetRingBuffer();
}
void RingBufferServer::ResetRingBuffer() {
delay_completer_.first_response_sent = false;
delay_completer_.completer = {};
position_completer_.reset();
expected_notifications_per_ring_ = 0;
notify_timer_.Cancel();
notification_period_ = {};
fetched_ = false;
engine_.device->Stop();
started_ = false;
pinned_ring_buffer_.Unpin();
}
void RingBufferServer::GetProperties(
fidl::Server<fuchsia_hardware_audio::RingBuffer>::GetPropertiesCompleter::Sync& completer) {
fuchsia_hardware_audio::RingBufferProperties properties;
properties.needs_cache_flush_or_invalidate(true).driver_transfer_bytes(
engine_.device->fifo_depth());
completer.Reply(std::move(properties));
}
void RingBufferServer::GetVmo(
GetVmoRequest& request,
fidl::Server<fuchsia_hardware_audio::RingBuffer>::GetVmoCompleter::Sync& completer) {
if (started_) {
FDF_LOG(ERROR, "GetVmo failed, ring buffer started");
binding_.Close(ZX_ERR_BAD_STATE);
return;
}
if (fetched_) {
FDF_LOG(ERROR, "GetVmo failed, VMO already retrieved");
binding_.Close(ZX_ERR_BAD_STATE);
return;
}
uint32_t frame_size =
engine_.config.ring_buffer.number_of_channels * engine_.config.ring_buffer.bytes_per_sample;
size_t ring_buffer_size = fbl::round_up<size_t, size_t>(
request.min_frames() * frame_size + engine_.device->fifo_depth(),
std::lcm(frame_size, engine_.device->GetBufferAlignment()));
size_t out_frames = ring_buffer_size / frame_size;
if (out_frames > std::numeric_limits<uint32_t>::max()) {
FDF_LOG(ERROR, "out frames too big: %zu", out_frames);
completer.Close(ZX_ERR_INTERNAL);
return;
}
zx_status_t status = InitBuffer(ring_buffer_size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "failed to init buffer: %s", zx_status_get_string(status));
completer.Close(status);
return;
}
constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER;
zx::vmo buffer;
status = ring_buffer_vmo_.duplicate(rights, &buffer);
if (status != ZX_OK) {
FDF_LOG(ERROR, "GetVmo failed, could not duplicate VMO: %s", zx_status_get_string(status));
completer.Close(status);
return;
}
status = engine_.device->SetBuffer(pinned_ring_buffer_.region(0).phys_addr, ring_buffer_size);
if (status != ZX_OK) {
FDF_LOG(ERROR, "failed to set buffer: %s", zx_status_get_string(status));
completer.Close(status);
return;
}
expected_notifications_per_ring_.store(request.clock_recovery_notifications_per_ring());
fetched_ = true;
// This is safe because of the overflow check we made above.
auto out_num_rb_frames = static_cast<uint32_t>(out_frames);
completer.Reply(zx::ok(
fuchsia_hardware_audio::RingBufferGetVmoResponse(out_num_rb_frames, std::move(buffer))));
}
void RingBufferServer::Start(StartCompleter::Sync& completer) {
int64_t start_time = 0;
if (started_) {
FDF_LOG(ERROR, "Could not start: already started");
binding_.Close(ZX_ERR_BAD_STATE);
return;
}
if (!fetched_) {
FDF_LOG(ERROR, "Could not start: first, GetVmo must successfully complete");
binding_.Close(ZX_ERR_BAD_STATE);
return;
}
start_time = engine_.device->Start();
started_ = true;
uint32_t notifs = expected_notifications_per_ring_.load();
if (notifs) {
uint32_t frame_size =
engine_.config.ring_buffer.number_of_channels * engine_.config.ring_buffer.bytes_per_sample;
ZX_ASSERT(engine_.dai_index < kNumberOfPipelines);
uint32_t frame_rate = owner_.current_dai_formats(engine_.dai_index).frame_rate();
// Notification period in usecs scaling by 1'000s to provide good enough resolution in the
// integer calculation.
notification_period_ = zx::usec(1'000 * pinned_ring_buffer_.region(0).size /
(frame_size * frame_rate / 1'000 * notifs));
notify_timer_.PostDelayed(dispatcher_, notification_period_);
} else {
notification_period_ = {};
}
completer.Reply(start_time);
}
void RingBufferServer::Stop(StopCompleter::Sync& completer) {
if (!fetched_) {
FDF_LOG(ERROR, "GetVmo must successfully complete before calling Start or Stop");
completer.Close(ZX_ERR_BAD_STATE);
return;
}
if (!started_) {
FDF_LOG(DEBUG, "Stop called while stopped; this is allowed");
}
notify_timer_.Cancel();
notification_period_ = {};
engine_.device->Stop();
started_ = false;
completer.Reply();
}
zx_status_t RingBufferServer::InitBuffer(size_t size) {
pinned_ring_buffer_.Unpin();
zx_status_t status = zx_vmo_create_contiguous(owner_.bti().get(), size, 0,
ring_buffer_vmo_.reset_and_get_address());
if (status != ZX_OK) {
FDF_LOG(ERROR, "failed to allocate ring buffer vmo: %s", zx_status_get_string(status));
return status;
}
status =
pinned_ring_buffer_.Pin(ring_buffer_vmo_, owner_.bti(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
if (status != ZX_OK) {
FDF_LOG(ERROR, "failed to pin ring buffer vmo: %s", zx_status_get_string(status));
return status;
}
return ZX_OK;
}
void AudioCompositeServer::TestPowerManagement() {
pm_timer_.PostDelayed(dispatcher_, zx::sec(1));
static bool enabled = true;
if (enabled) {
StartSocPower();
} else {
StopSocPower();
}
enabled = !enabled;
}
void RingBufferServer::ProcessRingNotification() {
if (notification_period_.get()) {
notify_timer_.PostDelayed(dispatcher_, notification_period_);
} else {
notify_timer_.Cancel();
return;
}
if (position_completer_) {
fuchsia_hardware_audio::RingBufferPositionInfo info;
info.position(engine_.device->GetRingPosition());
info.timestamp(zx::clock::get_monotonic().get());
position_completer_->Reply(std::move(info));
position_completer_.reset();
}
}
void RingBufferServer::WatchClockRecoveryPositionInfo(
WatchClockRecoveryPositionInfoCompleter::Sync& completer) {
if (position_completer_) {
// The client called WatchClockRecoveryPositionInfo when another hanging get was pending.
// This is an error condition and hence we unbind the channel.
FDF_LOG(ERROR,
"WatchClockRecoveryPositionInfo was re-called while the previous call was still"
" pending");
completer.Close(ZX_ERR_BAD_STATE);
} else {
// This completer is kept and responded in ProcessRingNotification.
position_completer_.emplace(completer.ToAsync());
}
}
void RingBufferServer::WatchDelayInfo(WatchDelayInfoCompleter::Sync& completer) {
if (!delay_completer_.first_response_sent) {
delay_completer_.first_response_sent = true;
fuchsia_hardware_audio::DelayInfo delay_info = {};
// No external delay information is provided by this driver.
delay_info.internal_delay(internal_delay_.to_nsecs());
completer.Reply(std::move(delay_info));
} else if (delay_completer_.completer) {
// The client called WatchDelayInfo when another hanging get was pending.
// This is an error condition and hence we unbind the channel.
FDF_LOG(ERROR, "WatchDelayInfo was re-called while the previous call was still pending");
completer.Close(ZX_ERR_BAD_STATE);
} else {
// This completer is kept but never used since we are not updating the delay info.
delay_completer_.completer.emplace(completer.ToAsync());
}
}
void RingBufferServer::SetActiveChannels(
fuchsia_hardware_audio::RingBufferSetActiveChannelsRequest& request,
SetActiveChannelsCompleter::Sync& completer) {
completer.Reply(zx::error(ZX_ERR_NOT_SUPPORTED));
}
void AudioCompositeServer::GetElements(GetElementsCompleter::Sync& completer) {
std::vector<fuchsia_hardware_audio_signalprocessing::Element> elements;
// One ring buffer per TDM engine.
for (size_t i = 0; i < kNumberOfTdmEngines; ++i) {
fuchsia_hardware_audio_signalprocessing::Element ring_buffer;
fuchsia_hardware_audio_signalprocessing::Endpoint ring_buffer_endpoint;
ring_buffer_endpoint.type(fuchsia_hardware_audio_signalprocessing::EndpointType::kRingBuffer)
.plug_detect_capabilities(
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kHardwired);
ring_buffer.id(kRingBufferIds[i])
.type(fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint)
.type_specific(fuchsia_hardware_audio_signalprocessing::TypeSpecificElement::WithEndpoint(
std::move(ring_buffer_endpoint)));
elements.push_back(std::move(ring_buffer));
}
// One DAI per pipeline.
for (size_t i = 0; i < kNumberOfPipelines; ++i) {
fuchsia_hardware_audio_signalprocessing::Element dai;
fuchsia_hardware_audio_signalprocessing::Endpoint dai_endpoint;
dai_endpoint.type(fuchsia_hardware_audio_signalprocessing::EndpointType::kDaiInterconnect)
.plug_detect_capabilities(
fuchsia_hardware_audio_signalprocessing::PlugDetectCapabilities::kHardwired);
dai.id(kDaiIds[i])
.type(fuchsia_hardware_audio_signalprocessing::ElementType::kEndpoint)
.type_specific(fuchsia_hardware_audio_signalprocessing::TypeSpecificElement::WithEndpoint(
std::move(dai_endpoint)));
elements.push_back(std::move(dai));
}
completer.Reply(zx::ok(elements));
}
void AudioCompositeServer::WatchElementState(WatchElementStateRequest& request,
WatchElementStateCompleter::Sync& completer) {
auto element_completer = element_completers_.find(request.processing_element_id());
if (element_completer == element_completers_.end()) {
FDF_LOG(ERROR, "Unknown process element id (%lu) for WatchElementState",
request.processing_element_id());
completer.Close(ZX_ERR_INVALID_ARGS);
return;
}
auto& element = element_completer->second;
if (!element.first_response_sent) {
element.first_response_sent = true;
// All elements are endpoints, hardwired hence plugged at time 0.
fuchsia_hardware_audio_signalprocessing::PlugState plug_state;
plug_state.plugged(true).plug_state_time(0);
fuchsia_hardware_audio_signalprocessing::EndpointElementState endpoint_state;
endpoint_state.plug_state(std::move(plug_state));
fuchsia_hardware_audio_signalprocessing::ElementState element_state;
element_state.type_specific(
fuchsia_hardware_audio_signalprocessing::TypeSpecificElementState::WithEndpoint(
std::move(endpoint_state)));
completer.Reply(std::move(element_state));
} else if (element.completer) {
// The client called WatchElementState when another hanging get was pending.
// This is an error condition and hence we unbind the channel.
FDF_LOG(ERROR, "WatchElementState was re-called while the previous call was still pending");
completer.Close(ZX_ERR_BAD_STATE);
} else {
// This completer is kept but never used since we are not updating the state of the elements.
element.completer = completer.ToAsync();
}
}
void AudioCompositeServer::SetElementState(SetElementStateRequest& request,
SetElementStateCompleter::Sync& completer) {
auto element_completer = element_completers_.find(request.processing_element_id());
if (element_completer == element_completers_.end()) {
FDF_LOG(ERROR, "Unknown process element id (%lu) for SetElementState",
request.processing_element_id());
// Return an error, but no need to close down the entire protocol channel.
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
return;
}
// All elements are endpoints, no field is expected or acted upon.
completer.Reply(zx::ok());
}
void AudioCompositeServer::GetTopologies(GetTopologiesCompleter::Sync& completer) {
fuchsia_hardware_audio_signalprocessing::Topology topology;
topology.id(kTopologyId);
std::vector<fuchsia_hardware_audio_signalprocessing::EdgePair> edges;
for (size_t i = 0; i < kNumberOfTdmEngines; ++i) {
if (!engines_[i].config.is_input) {
// +-----------+ +-----------+
// Source -> | ENDPOINT | -> + ENDPOINT | -> Destination
// from client | RingBuffer| + DAI | e.g. Bluetooth chip
// +-----------+ +-----------+
fuchsia_hardware_audio_signalprocessing::EdgePair pair;
pair.processing_element_id_from(kRingBufferIds[i])
.processing_element_id_to(kDaiIds[engines_[i].dai_index]);
edges.push_back(std::move(pair));
} else {
// +-----------+ +-----------+
// Source -> | ENDPOINT | -> + ENDPOINT | -> Destination
// e.g. Bluetooth chip | DAI | + RingBuffer| to client
// +-----------+ +-----------+
fuchsia_hardware_audio_signalprocessing::EdgePair pair;
pair.processing_element_id_from(kDaiIds[engines_[i].dai_index])
.processing_element_id_to(kRingBufferIds[i]);
edges.push_back(std::move(pair));
}
}
topology.processing_elements_edge_pairs(edges);
completer.Reply(zx::ok(std::vector{std::move(topology)}));
}
void AudioCompositeServer::WatchTopology(WatchTopologyCompleter::Sync& completer) {
if (!topology_completer_.first_response_sent) {
topology_completer_.first_response_sent = true;
completer.Reply(kTopologyId);
} else if (topology_completer_.completer) {
// The client called WatchTopology when another hanging get was pending.
// This is an error condition and hence we unbind the channel.
FDF_LOG(ERROR, "WatchTopology was re-called while the previous call was still pending");
completer.Close(ZX_ERR_BAD_STATE);
} else {
// This completer is kept but never used since we are not updating the topology.
topology_completer_.completer.emplace(completer.ToAsync());
}
}
// This device has only one signalprocessing topology.
void AudioCompositeServer::SetTopology(SetTopologyRequest& request,
SetTopologyCompleter::Sync& completer) {
if (request.topology_id() == kTopologyId) {
completer.Reply(zx::ok());
} else {
completer.Reply(zx::error(ZX_ERR_INVALID_ARGS));
}
}
} // namespace audio::aml_g12