blob: c91b634643395e0639878f0407af7c3b34dac69e [file] [log] [blame]
// Copyright 2018 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 "audio-stream.h"
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/metadata.h>
#include <lib/ddk/platform-defs.h>
#include <lib/fit/defer.h>
#include <lib/simple-codec/simple-codec-helper.h>
#include <lib/zx/clock.h>
#include <math.h>
#include <string.h>
#include <zircon/errors.h>
#include <numeric>
#include <optional>
#include <set>
#include <utility>
#include <pretty/hexdump.h>
namespace audio {
namespace aml_g12 {
AmlG12TdmStream::AmlG12TdmStream(
zx_device_t* parent, bool is_input, ddk::PDevFidl pdev,
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio> gpio_enable_client,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_client,
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> pll_client)
: SimpleAudioStream(parent, is_input),
pdev_(std::move(pdev)),
enable_gpio_(std::move(gpio_enable_client)),
clock_gate_(std::move(clock_gate_client)),
pll_(std::move(pll_client)) {
status_time_ = inspect().GetRoot().CreateInt("status_time", 0);
dma_status_ = inspect().GetRoot().CreateUint("dma_status", 0);
tdm_status_ = inspect().GetRoot().CreateUint("tdm_status", 0);
ring_buffer_physical_address_ = inspect().GetRoot().CreateUint("ring_buffer_physical_address", 0);
}
int AmlG12TdmStream::Thread() {
while (1) {
zx::time timestamp;
irq_.wait(&timestamp);
if (!running_.load()) {
break;
}
zxlogf(ERROR, "DMA status: 0x%08X TDM status: 0x%08X", aml_audio_->GetDmaStatus(),
aml_audio_->GetTdmStatus());
status_time_.Set(timestamp.get());
dma_status_.Set(aml_audio_->GetDmaStatus());
tdm_status_.Set(aml_audio_->GetTdmStatus());
}
zxlogf(INFO, "Exiting interrupt thread");
return 0;
}
void AmlG12TdmStream::InitDaiFormats() {
frame_rate_ = AmlTdmConfigDevice::GetDefaultFrameRate();
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
// Only the PCM signed sample format is supported.
dai_formats_[i].sample_format = SampleFormat::PCM_SIGNED;
dai_formats_[i].frame_rate = frame_rate_;
dai_formats_[i].bits_per_sample = metadata_.dai.bits_per_sample;
dai_formats_[i].bits_per_slot = metadata_.dai.bits_per_slot;
dai_formats_[i].number_of_channels = metadata_.dai.number_of_channels;
dai_formats_[i].channels_to_use_bitmask = metadata_.codecs.channels_to_use_bitmask[i];
switch (metadata_.dai.type) {
case metadata::DaiType::I2s:
dai_formats_[i].frame_format = FrameFormat::I2S;
break;
case metadata::DaiType::StereoLeftJustified:
dai_formats_[i].frame_format = FrameFormat::STEREO_LEFT;
break;
case metadata::DaiType::Tdm1:
dai_formats_[i].frame_format = FrameFormat::TDM1;
break;
case metadata::DaiType::Tdm2:
dai_formats_[i].frame_format = FrameFormat::TDM2;
break;
case metadata::DaiType::Tdm3:
dai_formats_[i].frame_format = FrameFormat::TDM3;
break;
}
}
}
zx_status_t AmlG12TdmStream::InitPDev() {
size_t actual = 0;
zx_status_t status = device_get_fragment_metadata(
parent(), "pdev", DEVICE_METADATA_PRIVATE, &metadata_, sizeof(metadata::AmlConfig), &actual);
if (status != ZX_OK || sizeof(metadata::AmlConfig) != actual) {
zxlogf(
ERROR,
"device_get_metadata failed %d. Expected size %zu, got size %zu. Got metadata with value",
status, sizeof(metadata::AmlConfig), actual);
char output_buffer[80];
for (size_t count = 0; count < actual; count += 16) {
FILE* f = fmemopen(output_buffer, sizeof(output_buffer), "w");
if (!f) {
zxlogf(ERROR, "Couldn't open buffer. Returning.");
return status;
}
hexdump_very_ex(reinterpret_cast<uint8_t*>(&metadata_) + count,
std::min(actual - count, 16UL), count, hexdump_stdio_printf, f);
fclose(f);
zxlogf(ERROR, "%s", output_buffer);
}
return status;
}
status = AmlTdmConfigDevice::Normalize(metadata_);
if (status != ZX_OK) {
return status;
}
InitDaiFormats();
if (!pdev_.is_valid()) {
zxlogf(ERROR, "could not get pdev");
return ZX_ERR_NO_RESOURCES;
}
status = pdev_.GetBti(0, &bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "could not obtain bti - %d", status);
return status;
}
ZX_ASSERT(metadata_.codecs.number_of_codecs <= 8);
codecs_.reserve(metadata_.codecs.number_of_codecs);
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
codecs_.push_back(std::make_unique<SimpleCodecClient>());
char fragment_name[32] = {};
snprintf(fragment_name, 32, "codec-%02lu", i + 1);
zx::result<fidl::ClientEnd<fuchsia_hardware_audio::Codec>> codec_client_end =
DdkConnectFragmentFidlProtocol<fuchsia_hardware_audio::CodecService::Codec>(fragment_name);
if (codec_client_end.is_error()) {
zxlogf(ERROR, "fuchsia.hardware.audio.codec/Device not found");
return codec_client_end.status_value();
}
status = codecs_[i]->SetCodec(std::move(*codec_client_end));
if (status != ZX_OK) {
zxlogf(ERROR, "could set protocol - %s - %d", fragment_name, status);
return status;
}
}
std::optional<fdf::MmioBuffer> mmio;
status = pdev_.MapMmio(0, &mmio);
if (status != ZX_OK) {
zxlogf(ERROR, "could not get mmio %d", status);
return status;
}
status = pdev_.GetInterrupt(0, 0, &irq_);
if (status != ZX_ERR_OUT_OF_RANGE) { // Not specified in the board file.
if (status != ZX_OK) {
zxlogf(ERROR, "could not get IRQ %d", status);
return status;
}
auto irq_thread = [](void* arg) -> int {
return reinterpret_cast<AmlG12TdmStream*>(arg)->Thread();
};
running_.store(true);
int rc = thrd_create_with_name(&thread_, irq_thread, this, "aml_tdm_irq_thread");
if (rc != thrd_success) {
zxlogf(ERROR, "could not create thread %d", rc);
return status;
}
}
aml_audio_ = std::make_unique<AmlTdmConfigDevice>(metadata_, *std::move(mmio));
if (aml_audio_ == nullptr) {
zxlogf(ERROR, "failed to create TDM device with config");
return ZX_ERR_NO_MEMORY;
}
// Initial setup of one page of buffer, just to be safe.
status = InitBuffer(zx_system_get_page_size());
if (status != ZX_OK) {
zxlogf(ERROR, "failed to init buffer %d", status);
return status;
}
status = aml_audio_->SetBuffer(pinned_ring_buffer_.region(0).phys_addr,
pinned_ring_buffer_.region(0).size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to set buffer %d", status);
return status;
}
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
auto info = codecs_[i]->GetInfo();
if (info.is_error()) {
zxlogf(ERROR, "Failed to get codec info: %s", info.status_string());
return info.error_value();
}
// Reset and initialize codec after we have configured I2S.
status = codecs_[i]->Reset();
if (status != ZX_OK) {
zxlogf(ERROR, "could not reset codec %d", status);
return status;
}
auto supported_formats = codecs_[i]->GetDaiFormats();
if (supported_formats.is_error()) {
zxlogf(ERROR, "supported formats error %d", status);
return supported_formats.error_value();
}
if (!IsDaiFormatSupported(dai_formats_[i], supported_formats.value())) {
zxlogf(ERROR, "codec does not support DAI format");
return ZX_ERR_NOT_SUPPORTED;
}
zx::result<CodecFormatInfo> format_info = codecs_[i]->SetDaiFormat(dai_formats_[i]);
if (!format_info.is_ok()) {
zxlogf(ERROR, "could not set DAI format %s", format_info.status_string());
return format_info.status_value();
}
}
// Put codecs in stopped state before starting the AMLogic engine.
// Codecs are started after format is set via ChangeFormat() or the stream is explicitly started.
status = StopAllCodecs();
if (status != ZX_OK) {
return status;
}
status = StartSocPower();
if (status != ZX_OK) {
return status;
}
status = aml_audio_->InitHW(metadata_, std::numeric_limits<uint64_t>::max(), frame_rate_);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to init tdm hardware %d", status);
return status;
}
zxlogf(INFO, "audio: %s initialized", metadata_.is_input ? "input" : "output");
return ZX_OK;
}
void AmlG12TdmStream::UpdateCodecsGainStateFromCurrent() {
UpdateCodecsGainState({.gain = cur_gain_state_.cur_gain,
.muted = cur_gain_state_.cur_mute,
.agc_enabled = cur_gain_state_.cur_agc});
}
void AmlG12TdmStream::UpdateCodecsGainState(GainState state) {
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
auto state2 = state;
state2.gain += metadata_.codecs.delta_gains[i];
if (override_mute_) {
state2.muted = true;
}
codecs_[i]->SetGainState(state2);
}
}
zx_status_t AmlG12TdmStream::InitCodecsGain() {
if (metadata_.codecs.number_of_codecs) {
// Set our gain capabilities.
float min_gain = std::numeric_limits<float>::lowest();
float max_gain = std::numeric_limits<float>::max();
float gain_step = std::numeric_limits<float>::lowest();
bool can_all_mute = true;
bool can_all_agc = true;
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
auto format = codecs_[i]->GetGainFormat();
if (format.is_error()) {
zxlogf(ERROR, "Could not get gain format %d", format.error_value());
return format.error_value();
}
min_gain = std::max(min_gain, format->min_gain);
max_gain = std::min(max_gain, format->max_gain);
gain_step = std::max(gain_step, format->gain_step);
can_all_mute = (can_all_mute && format->can_mute);
can_all_agc = (can_all_agc && format->can_agc);
}
// Use first codec as reference initial gain.
auto state = codecs_[0]->GetGainState();
if (state.is_error()) {
zxlogf(ERROR, "Could not get gain state %d", state.error_value());
return state.error_value();
}
cur_gain_state_.cur_gain = state->gain;
cur_gain_state_.cur_mute = false;
cur_gain_state_.cur_agc = false;
UpdateCodecsGainState(state.value());
cur_gain_state_.min_gain = min_gain;
cur_gain_state_.max_gain = max_gain;
cur_gain_state_.gain_step = gain_step;
cur_gain_state_.can_mute = can_all_mute;
cur_gain_state_.can_agc = can_all_agc;
} else {
cur_gain_state_.cur_gain = 0.f;
cur_gain_state_.cur_mute = false;
cur_gain_state_.cur_agc = false;
cur_gain_state_.min_gain = 0.f;
cur_gain_state_.max_gain = 0.f;
cur_gain_state_.gain_step = .0f;
cur_gain_state_.can_mute = false;
cur_gain_state_.can_agc = false;
}
return ZX_OK;
}
zx_status_t AmlG12TdmStream::Init() {
zx_status_t status;
status = InitPDev();
if (status != ZX_OK) {
return status;
}
status = AddFormats();
if (status != ZX_OK) {
return status;
}
status = InitCodecsGain();
if (status != ZX_OK) {
return status;
}
const char* in_out = "out";
if (metadata_.is_input) {
in_out = "in";
}
strncpy(mfr_name_, metadata_.manufacturer, sizeof(mfr_name_));
strncpy(prod_name_, metadata_.product_name, sizeof(prod_name_));
unique_id_ = metadata_.unique_id;
const char* tdm_type = nullptr;
switch (metadata_.dai.type) {
case metadata::DaiType::I2s:
tdm_type = "i2s";
break;
case metadata::DaiType::StereoLeftJustified:
tdm_type = "ljt";
break;
case metadata::DaiType::Tdm1:
tdm_type = "tdm1";
break;
case metadata::DaiType::Tdm2:
tdm_type = "tdm2";
break;
case metadata::DaiType::Tdm3:
tdm_type = "tdm3";
break;
}
snprintf(device_name_, sizeof(device_name_), "%s-audio-%s-%s", prod_name_, tdm_type, in_out);
// This audio subdevice is in the MONOTONIC clock domain.
clock_domain_ = fuchsia_hardware_audio::wire::kClockDomainMonotonic;
return ZX_OK;
}
// Timer handler for sending out position notifications
void AmlG12TdmStream::ProcessRingNotification() {
ScopedToken t(domain_token());
if (us_per_notification_) {
notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_));
} else {
notify_timer_.Cancel();
return;
}
audio_proto::RingBufPositionNotify resp = {};
resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
resp.monotonic_time = zx::clock::get_monotonic().get();
resp.ring_buffer_pos = aml_audio_->GetRingPosition();
NotifyPosition(resp);
}
zx_status_t AmlG12TdmStream::UpdateHardwareSettings() {
zx_status_t status = StopAllCodecs(); // Put codecs in safe state for format changes.
if (status != ZX_OK) {
return status;
}
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
dai_formats_[i].frame_rate = frame_rate_;
}
status = aml_audio_->InitHW(metadata_, active_channels_, frame_rate_);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to reinitialize the HW");
return status;
}
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
zx::result<CodecFormatInfo> format_info = codecs_[i]->SetDaiFormat(dai_formats_[i]);
if (!format_info.is_ok()) {
zxlogf(ERROR, "failed to set the DAI format");
return format_info.status_value();
}
if (format_info->has_turn_on_delay()) {
int64_t delay = format_info->turn_on_delay();
codecs_turn_on_delay_nsec_ = std::max(codecs_turn_on_delay_nsec_, delay);
}
if (format_info->has_turn_off_delay()) {
int64_t delay = format_info->turn_off_delay();
codecs_turn_off_delay_nsec_ = std::max(codecs_turn_off_delay_nsec_, delay);
}
}
status = StartAllEnabledCodecs();
if (status != ZX_OK) {
return status;
}
hardware_configured_ = true;
return ZX_OK;
}
zx_status_t AmlG12TdmStream::StartSocPower() {
// Only if needed (not done previously) so voting on relevant clock ids is not repeated.
// Each driver instance may vote independently.
if (soc_power_started_ == true) {
return ZX_OK;
}
if (clock_gate_.is_valid()) {
fidl::WireResult result = clock_gate_->Enable();
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request to enable clock gate: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Send request to enable clock gate error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
if (pll_.is_valid()) {
fidl::WireResult result = pll_->Enable();
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request to enable PLL: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Send request to enable PLL error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
soc_power_started_ = true;
return ZX_OK;
}
zx_status_t AmlG12TdmStream::StopSocPower() {
// Only if needed (not done previously) so voting on relevant clock ids is not repeated.
// Each driver instance may vote independently.
if (soc_power_started_ == false) {
return ZX_OK;
}
if (clock_gate_.is_valid()) {
fidl::WireResult result = clock_gate_->Disable();
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request to disable clock gate: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Send request to disable clock gate error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
if (pll_.is_valid()) {
fidl::WireResult result = pll_->Disable();
if (!result.ok()) {
zxlogf(ERROR, "Failed to send request to disable PLL: %s", result.status_string());
return result.status();
}
if (result->is_error()) {
zxlogf(ERROR, "Send request to disable PLL error: %s",
zx_status_get_string(result->error_value()));
return result->error_value();
}
}
soc_power_started_ = false;
return ZX_OK;
}
zx_status_t AmlG12TdmStream::ChangeActiveChannels(uint64_t mask, zx_time_t* set_time_out) {
if (mask == active_channels_) {
*set_time_out = active_channels_set_time_.get();
return ZX_OK;
}
if (mask > active_channels_bitmask_max_) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status = ZX_OK;
uint64_t old_mask = active_channels_;
active_channels_ = mask;
if (active_channels_ != 0) {
status = StartSocPower(); // Start SoC power before enabling codecs.
if (status != ZX_OK) {
return status;
}
}
// Stop the codecs for channels not active.
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
uint64_t codec_mask = metadata_.codecs.ring_buffer_channels_to_use_bitmask[i];
bool enabled = mask & codec_mask;
bool old_enabled = old_mask & codec_mask;
if (enabled != old_enabled) {
if (enabled) {
status = StartCodecIfEnabled(i);
if (status != ZX_OK) {
return status;
}
} else {
status = codecs_[i]->Stop();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to stop the codec");
return status;
}
constexpr uint32_t codecs_turn_off_delay_if_unknown_msec = 50;
zx::duration delay = codecs_turn_off_delay_nsec_
? zx::nsec(codecs_turn_off_delay_nsec_)
: zx::msec(codecs_turn_off_delay_if_unknown_msec);
zx::nanosleep(zx::deadline_after(delay));
}
}
}
if (active_channels_ == 0) {
status = StopSocPower(); // Stop SoC power after disabling codecs.
if (status != ZX_OK) {
return status;
}
}
active_channels_set_time_ = zx::clock::get_monotonic();
*set_time_out = active_channels_set_time_.get();
return ZX_OK;
}
zx_status_t AmlG12TdmStream::ChangeFormat(const audio_proto::StreamSetFmtReq& req) {
auto cleanup = fit::defer([this, old_codecs_turn_on_delay_nsec = codecs_turn_on_delay_nsec_,
old_codecs_turn_off_delay_nsec = codecs_turn_off_delay_nsec_] {
codecs_turn_on_delay_nsec_ = old_codecs_turn_on_delay_nsec;
codecs_turn_off_delay_nsec_ = old_codecs_turn_off_delay_nsec;
});
driver_transfer_bytes_ = aml_audio_->fifo_depth();
codecs_turn_on_delay_nsec_ = 0;
codecs_turn_off_delay_nsec_ = 0;
for (size_t i = 0; i < metadata_.codecs.number_of_external_delays; ++i) {
if (metadata_.codecs.external_delays[i].frequency == req.frames_per_second) {
external_delay_nsec_ = metadata_.codecs.external_delays[i].nsecs;
break;
}
}
if (!hardware_configured_ || req.frames_per_second != frame_rate_) {
frame_rate_ = req.frames_per_second;
zx_status_t status = UpdateHardwareSettings();
if (status != ZX_OK) {
return status;
}
}
active_channels_bitmask_max_ = (1 << req.channels) - 1;
SetTurnOnDelay(codecs_turn_on_delay_nsec_);
cleanup.cancel();
return ZX_OK;
}
void AmlG12TdmStream::ShutdownHook() {
if (running_.load()) {
running_.store(false);
irq_.destroy();
thrd_join(thread_, NULL);
}
// safe the codec so it won't throw clock errors when tdm bus shuts down
StopAllCodecs();
if (enable_gpio_.is_valid()) {
fidl::WireResult result = enable_gpio_->Write(0);
if (!result.ok()) {
zxlogf(ERROR, "Failed to send Write request to enable gpio: %s", result.status_string());
} else if (result->is_error()) {
zxlogf(ERROR, "Failed to write to enable gpio: %s",
zx_status_get_string(result->error_value()));
}
}
aml_audio_->Shutdown();
pinned_ring_buffer_.Unpin();
[[maybe_unused]] zx_status_t unused_status = StopSocPower();
}
zx_status_t AmlG12TdmStream::SetGain(const audio_proto::SetGainReq& req) {
// Modify parts of the gain state we have received in the request.
if (req.flags & AUDIO_SGF_MUTE_VALID) {
cur_gain_state_.cur_mute = req.flags & AUDIO_SGF_MUTE;
}
if (req.flags & AUDIO_SGF_AGC_VALID) {
cur_gain_state_.cur_agc = req.flags & AUDIO_SGF_AGC;
};
cur_gain_state_.cur_gain = req.gain;
UpdateCodecsGainStateFromCurrent();
return ZX_OK;
}
zx_status_t AmlG12TdmStream::GetBuffer(const audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames, zx::vmo* out_buffer) {
size_t ring_buffer_size =
fbl::round_up<size_t, size_t>(req.min_ring_buffer_frames * frame_size_,
std::lcm(frame_size_, aml_audio_->GetBufferAlignment()));
size_t out_frames = ring_buffer_size / frame_size_;
if (out_frames > std::numeric_limits<uint32_t>::max()) {
return ZX_ERR_INVALID_ARGS;
}
size_t vmo_size = fbl::round_up<size_t, size_t>(ring_buffer_size, zx_system_get_page_size());
auto status = InitBuffer(vmo_size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to init buffer %d", status);
return status;
}
constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER;
status = ring_buffer_vmo_.duplicate(rights, out_buffer);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to duplicate VMO %d", status);
return status;
}
status = aml_audio_->SetBuffer(pinned_ring_buffer_.region(0).phys_addr, ring_buffer_size);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to set buffer %d", status);
return status;
}
ring_buffer_physical_address_.Set(pinned_ring_buffer_.region(0).phys_addr);
// This is safe because of the overflow check we made above.
*out_num_rb_frames = static_cast<uint32_t>(out_frames);
return ZX_OK;
}
zx_status_t AmlG12TdmStream::Start(uint64_t* out_start_time) {
*out_start_time = aml_audio_->Start();
zx_status_t status = StartAllEnabledCodecs();
if (status != ZX_OK) {
aml_audio_->Stop();
return status;
}
uint32_t notifs = LoadNotificationsPerRing();
if (notifs) {
us_per_notification_ = static_cast<uint32_t>(1000 * pinned_ring_buffer_.region(0).size /
(frame_size_ * frame_rate_ / 1000 * notifs));
notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_));
} else {
us_per_notification_ = 0;
}
override_mute_ = false;
UpdateCodecsGainStateFromCurrent();
return ZX_OK;
}
zx_status_t AmlG12TdmStream::StartCodecIfEnabled(size_t index) {
if (!metadata_.codecs.ring_buffer_channels_to_use_bitmask[index]) {
zxlogf(ERROR, "Codec %zu must configure ring_buffer_channels_to_use_bitmask", index);
return ZX_ERR_NOT_SUPPORTED;
}
// Enable codec depending on the mapping provided by metadata_
// metadata_.ring_buffer.number_of_channels are mapped into metadata_.codecs.number_of_codecs
// The active_channels_ bitmask defines which ring buffer channels corresponding codecs to start
// metadata_.codecs.ring_buffer_channels_to_use_bitmask[] specifies which channel in the ring
// buffer a codec is associated with. If the codecs ring_buffer_channels_to_use_bitmask intersects
// with active_channels_ then start the codec.
if (active_channels_ & metadata_.codecs.ring_buffer_channels_to_use_bitmask[index]) {
zx_status_t status = codecs_[index]->Start();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to start the codec");
return status;
}
}
return ZX_OK;
}
zx_status_t AmlG12TdmStream::StartAllEnabledCodecs() {
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
zx_status_t status = StartCodecIfEnabled(i);
if (status != ZX_OK) {
return status;
}
}
return ZX_OK;
}
zx_status_t AmlG12TdmStream::Stop() {
override_mute_ = true;
UpdateCodecsGainStateFromCurrent();
notify_timer_.Cancel();
us_per_notification_ = 0;
zx_status_t status = StopAllCodecs();
if (status != ZX_OK) {
return status;
}
aml_audio_->Stop();
return ZX_OK;
}
zx_status_t AmlG12TdmStream::StopAllCodecs() {
for (size_t i = 0; i < metadata_.codecs.number_of_codecs; ++i) {
auto status = codecs_[i]->Stop();
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to stop the codec");
return status;
}
}
constexpr uint32_t codecs_turn_off_delay_if_unknown_msec = 50;
zx::duration delay = codecs_turn_off_delay_nsec_
? zx::nsec(codecs_turn_off_delay_nsec_)
: zx::msec(codecs_turn_off_delay_if_unknown_msec);
zx::nanosleep(zx::deadline_after(delay));
return ZX_OK;
}
zx_status_t AmlG12TdmStream::AddFormats() {
fbl::AllocChecker ac;
supported_formats_.reserve(1, &ac);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory, can not create supported formats list");
return ZX_ERR_NO_MEMORY;
}
SimpleAudioStream::SupportedFormat format = {};
format.range.min_channels = metadata_.ring_buffer.number_of_channels;
format.range.max_channels = metadata_.ring_buffer.number_of_channels;
ZX_ASSERT(metadata_.ring_buffer.bytes_per_sample == 2);
format.range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
for (size_t i = 0; i < metadata_.ring_buffer.number_of_channels; ++i) {
if (metadata_.ring_buffer.frequency_ranges[i].min_frequency ||
metadata_.ring_buffer.frequency_ranges[i].max_frequency) {
SimpleAudioStream::FrequencyRange range = {};
range.min_frequency = metadata_.ring_buffer.frequency_ranges[i].min_frequency;
range.max_frequency = metadata_.ring_buffer.frequency_ranges[i].max_frequency;
format.frequency_ranges.push_back(std::move(range));
}
}
// For each codec, get the supported frame rates into sets.
// These sets will be used to only add frame rates which are fully supported
// by all codecs.
std::vector<std::set<uint32_t>> codec_frame_rates;
for (auto& codec : codecs_) {
auto supported_formats = codec->GetDaiFormats();
if (supported_formats.is_error()) {
zxlogf(ERROR, "supported formats error %d", supported_formats.status_value());
return supported_formats.error_value();
}
codec_frame_rates.emplace_back().insert(supported_formats->frame_rates.begin(),
supported_formats->frame_rates.end());
}
for (auto& i : AmlTdmConfigDevice::GetSupportedFrameRates()) {
bool fully_supported = true;
for (const auto& frame_rate_set : codec_frame_rates) {
if (frame_rate_set.count(i) == 0) {
fully_supported = false;
break;
}
}
if (!fully_supported)
continue;
format.range.min_frames_per_second = i;
format.range.max_frames_per_second = i;
format.range.flags =
ASF_RANGE_FLAG_FPS_CONTINUOUS; // No need to specify family when min == max.
supported_formats_.push_back(format);
}
return ZX_OK;
}
zx_status_t AmlG12TdmStream::InitBuffer(size_t size) {
// Make sure the DMA is stopped before releasing quarantine.
aml_audio_->Stop();
// Make sure that all reads/writes have gone through.
#if defined(__aarch64__)
__asm__ volatile("dsb sy" : : : "memory");
#endif
auto status = bti_.release_quarantine();
if (status != ZX_OK) {
zxlogf(ERROR, "could not release quarantine bti - %d", status);
return status;
}
pinned_ring_buffer_.Unpin();
status = zx_vmo_create_contiguous(bti_.get(), size, 0, ring_buffer_vmo_.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(ERROR, "failed to allocate ring buffer vmo - %d", status);
return status;
}
status = pinned_ring_buffer_.Pin(ring_buffer_vmo_, bti_, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE);
if (status != ZX_OK) {
zxlogf(ERROR, "failed to pin ring buffer vmo - %d", status);
return status;
}
if (pinned_ring_buffer_.region_count() != 1) {
if (!AllowNonContiguousRingBuffer()) {
zxlogf(ERROR, "buffer is not contiguous");
return ZX_ERR_NO_MEMORY;
}
}
return ZX_OK;
}
static zx_status_t audio_bind(void* ctx, zx_device_t* device) {
size_t actual = 0;
metadata::AmlConfig metadata = {};
auto status = device_get_fragment_metadata(device, "pdev", DEVICE_METADATA_PRIVATE, &metadata,
sizeof(metadata::AmlConfig), &actual);
if (status != ZX_OK || sizeof(metadata::AmlConfig) != actual) {
zxlogf(ERROR, "device_get_metadata failed %d", status);
return status;
}
const char* kGpioEnableFragmentName = "gpio-enable";
zx::result gpio_enable =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_gpio::Service::Device>(
device, kGpioEnableFragmentName);
if (gpio_enable.is_error() && gpio_enable.status_value() != ZX_ERR_NOT_FOUND) {
zxlogf(ERROR, "Failed to get gpio protocol from fragment %s: %s", kGpioEnableFragmentName,
gpio_enable.status_string());
return gpio_enable.status_value();
}
fidl::WireSyncClient<fuchsia_hardware_gpio::Gpio> gpio_enable_client;
if (gpio_enable.is_ok()) {
gpio_enable_client.Bind(std::move(gpio_enable.value()));
}
zx::result clock_gate =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_clock::Service::Clock>(
device, "clock-gate");
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> clock_gate_client;
if (clock_gate.is_error()) {
zxlogf(ERROR, "Failed to get clock protocol from fragment clock-gate: %s",
clock_gate.status_string());
// Continue, driver configurations without clock-gate are also supported.
} else {
clock_gate_client.Bind(std::move(clock_gate.value()));
}
zx::result pll =
ddk::Device<void>::DdkConnectFragmentFidlProtocol<fuchsia_hardware_clock::Service::Clock>(
device, "clock-pll");
fidl::WireSyncClient<fuchsia_hardware_clock::Clock> pll_client;
if (pll.is_error()) {
zxlogf(ERROR, "Failed to get clock protocol from fragment clock-pll: %s", pll.status_string());
// Continue, driver configurations without pll are also supported.
} else {
pll_client.Bind(std::move(pll.value()));
}
auto stream = audio::SimpleAudioStream::Create<audio::aml_g12::AmlG12TdmStream>(
device, metadata.is_input, ddk::PDevFidl::FromFragment(device), std::move(gpio_enable_client),
std::move(clock_gate_client), std::move(pll_client));
if (stream == nullptr) {
zxlogf(ERROR, "Could not create aml-g12-tdm driver");
return ZX_ERR_NO_MEMORY;
}
[[maybe_unused]] auto unused = fbl::ExportToRawPtr(&stream);
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = audio_bind;
return ops;
}();
} // namespace aml_g12
} // namespace audio
// clang-format off
ZIRCON_DRIVER(aml_tdm, audio::aml_g12::driver_ops, "aml-tdm", "0.1");