blob: 2c0553631ee821516df7acb0465d66a949bb156a [file] [log] [blame]
// Copyright 2019 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-in.h"
#include <math.h>
#include <optional>
#include <utility>
#include <ddk/binding.h>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <ddk/protocol/composite.h>
namespace {
enum {
COMPONENT_PDEV,
COMPONENT_SHARED_DMA,
COMPONENT_APLL_CLOCK,
COMPONENT_COUNT,
};
} // namespace
namespace audio {
namespace as370 {
As370AudioStreamIn::As370AudioStreamIn(zx_device_t* parent)
: SimpleAudioStream(parent, true /* is input */) {}
zx_status_t As370AudioStreamIn::Create(void* ctx, zx_device_t* parent) {
auto stream = audio::SimpleAudioStream::Create<As370AudioStreamIn>(parent);
if (stream == nullptr) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
zx_status_t As370AudioStreamIn::Init() {
auto status = InitPDev();
if (status != ZX_OK) {
return status;
}
status = AddFormats();
if (status != ZX_OK) {
return status;
}
// Set our gain capabilities.
cur_gain_state_.cur_gain = 0;
cur_gain_state_.cur_mute = false;
cur_gain_state_.cur_agc = false;
cur_gain_state_.min_gain = 0;
cur_gain_state_.max_gain = 0;
cur_gain_state_.gain_step = 0;
cur_gain_state_.can_mute = false;
cur_gain_state_.can_agc = false;
snprintf(device_name_, sizeof(device_name_), "as370-audio-in");
snprintf(mfr_name_, sizeof(mfr_name_), "unknown");
snprintf(prod_name_, sizeof(prod_name_), "as370");
unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE;
return ZX_OK;
}
zx_status_t As370AudioStreamIn::InitPDev() {
composite_protocol_t composite = {};
auto status = device_get_protocol(parent(), ZX_PROTOCOL_COMPOSITE, &composite);
if (status != ZX_OK) {
zxlogf(ERROR, "%s Could not get composite protocol\n", __FILE__);
return status;
}
zx_device_t* components[COMPONENT_COUNT] = {};
size_t actual = 0;
composite_get_components(&composite, components, countof(components), &actual);
if (actual != COMPONENT_COUNT) {
zxlogf(ERROR, "%s could not get components\n", __FILE__);
return ZX_ERR_NOT_SUPPORTED;
}
pdev_ = components[COMPONENT_PDEV];
if (!pdev_.is_valid()) {
zxlogf(ERROR, "%s could not get pdev\n", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
clks_[kAvpll0Clk] = components[COMPONENT_APLL_CLOCK];
if (!clks_[kAvpll0Clk].is_valid()) {
zxlogf(ERROR, "%s could not get clk\n", __FILE__);
return status;
}
// PLL0 = 196.608MHz = e.g. 48K (FSYNC) * 64 (BCLK) * 8 (MCLK) * 8.
clks_[kAvpll0Clk].SetRate(kMaxRate * 64 * 8 * 8);
clks_[kAvpll0Clk].Enable();
ddk::SharedDmaProtocolClient dma;
dma = components[COMPONENT_SHARED_DMA];
if (!dma.is_valid()) {
zxlogf(ERROR, "%s could not get DMA\n", __FILE__);
return ZX_ERR_NO_RESOURCES;
}
std::optional<ddk::MmioBuffer> mmio_global, mmio_avio_global, mmio_i2s;
status = pdev_.MapMmio(0, &mmio_global);
if (status != ZX_OK) {
return status;
}
status = pdev_.MapMmio(1, &mmio_avio_global);
if (status != ZX_OK) {
return status;
}
status = pdev_.MapMmio(2, &mmio_i2s);
if (status != ZX_OK) {
return status;
}
lib_ = SynAudioInDevice::Create(*std::move(mmio_global), *std::move(mmio_avio_global),
*std::move(mmio_i2s), dma);
if (lib_ == nullptr) {
zxlogf(ERROR, "%s failed to create Syn audio device\n", __FUNCTION__);
return ZX_ERR_NO_MEMORY;
}
// Calculate ring buffer size for 1 second of 16-bit at kMaxRate.
const size_t kRingBufferSize = fbl::round_up<size_t, size_t>(
kMaxRate * sizeof(uint16_t) * SynAudioInDevice::kNumberOfChannels, ZX_PAGE_SIZE);
status = lib_->GetBuffer(kRingBufferSize, &ring_buffer_vmo_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to Init buffer %d\n", __FILE__, status);
return status;
}
size_t size;
status = ring_buffer_vmo_.get_size(&size);
zxlogf(INFO, "audio: as370 audio input initialized %lX\n", size);
return ZX_OK;
}
zx_status_t As370AudioStreamIn::ChangeFormat(const audio_proto::StreamSetFmtReq& req) {
fifo_depth_ = lib_->fifo_depth();
external_delay_nsec_ = 0;
// At this time only one format is supported, and hardware is initialized
// during driver binding, so nothing to do at this time.
return ZX_OK;
}
zx_status_t As370AudioStreamIn::GetBuffer(const audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames, zx::vmo* out_buffer) {
constexpr uint32_t rights = ZX_RIGHT_READ | ZX_RIGHT_WRITE | ZX_RIGHT_MAP | ZX_RIGHT_TRANSFER;
auto status = ring_buffer_vmo_.duplicate(rights, out_buffer);
if (status != ZX_OK) {
return status;
}
size_t size;
status = ring_buffer_vmo_.get_size(&size);
*out_num_rb_frames = static_cast<uint32_t>(size) / frame_size_;
return status;
}
void As370AudioStreamIn::RingBufferShutdown() { lib_->Shutdown(); }
zx_status_t As370AudioStreamIn::Start(uint64_t* out_start_time) {
*out_start_time = lib_->Start();
uint32_t notifs = LoadNotificationsPerRing();
if (notifs) {
size_t size = 0;
ring_buffer_vmo_.get_size(&size);
notification_rate_ =
zx::duration(zx_duration_from_msec(size / (frame_size_ * kMaxRate / 1000 * notifs)));
notify_timer_->Arm(zx::deadline_after(notification_rate_).get());
} else {
notification_rate_ = {};
}
return ZX_OK;
}
// Timer handler for sending out position notifications.
zx_status_t As370AudioStreamIn::ProcessRingNotification() {
ZX_ASSERT(notification_rate_ != zx::duration());
notify_timer_->Arm(zx::deadline_after(notification_rate_).get());
audio_proto::RingBufPositionNotify resp = {};
resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
resp.ring_buffer_pos = lib_->GetRingPosition();
return NotifyPosition(resp);
}
zx_status_t As370AudioStreamIn::InitPost() {
notify_timer_ = dispatcher::Timer::Create();
if (notify_timer_ == nullptr) {
return ZX_ERR_NO_MEMORY;
}
dispatcher::Timer::ProcessHandler thandler([pdm = this](dispatcher::Timer* timer) -> zx_status_t {
OBTAIN_EXECUTION_DOMAIN_TOKEN(t, pdm->domain_);
return pdm->ProcessRingNotification();
});
return notify_timer_->Activate(domain_, std::move(thandler));
}
void As370AudioStreamIn::ShutdownHook() { lib_->Shutdown(); }
zx_status_t As370AudioStreamIn::Stop() {
notify_timer_->Cancel();
notification_rate_ = {};
lib_->Stop();
return ZX_OK;
}
zx_status_t As370AudioStreamIn::AddFormats() {
fbl::AllocChecker ac;
supported_formats_.reserve(1, &ac);
if (!ac.check()) {
zxlogf(ERROR, "Out of memory, can not create supported formats list\n");
return ZX_ERR_NO_MEMORY;
}
audio_stream_format_range_t range;
range.min_channels = SynAudioInDevice::kNumberOfChannels;
range.max_channels = SynAudioInDevice::kNumberOfChannels;
range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
range.min_frames_per_second = kMaxRate;
range.max_frames_per_second = kMaxRate;
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
supported_formats_.push_back(range);
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = As370AudioStreamIn::Create;
return ops;
}();
} // namespace as370
} // namespace audio
// clang-format off
ZIRCON_DRIVER_BEGIN(as370_audio_in, audio::as370::driver_ops, "zircon", "0.1", 4)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_COMPOSITE),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_SYNAPTICS),
BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_SYNAPTICS_AS370),
BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_AS370_AUDIO_IN),
ZIRCON_DRIVER_END(as370_audio_in)
// clang-format on