blob: be2d9278dcf3dc4ed5e03959681388ea75414ee3 [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 <fuchsia/hardware/composite/cpp/banjo.h>
#include <lib/zx/clock.h>
#include <limits.h>
#include <optional>
#include <utility>
#include <ddk/debug.h>
#include <ddk/driver.h>
#include <ddk/platform-defs.h>
#include <soc/mt8167/mt8167-clk-regs.h>
#include "src/media/audio/drivers/mt8167-tdm-input/mt8167_audio_in_bind.h"
namespace audio {
namespace mt8167 {
// Expects 2 mics.
constexpr size_t kNumberOfChannels = 2;
constexpr size_t kMinSampleRate = 8000;
constexpr size_t kMaxSampleRate = 192000;
// Calculate ring buffer size for 1 second of 16-bit.
constexpr size_t kRingBufferSize =
fbl::round_up<size_t, size_t>(kMaxSampleRate * 2 * kNumberOfChannels, PAGE_SIZE);
Mt8167AudioStreamIn::Mt8167AudioStreamIn(zx_device_t* parent)
: SimpleAudioStream(parent, true /* is input */) {}
zx_status_t Mt8167AudioStreamIn::Init() {
zx_status_t status;
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_), "mt8167-audio-in");
snprintf(mfr_name_, sizeof(mfr_name_), "unknown");
snprintf(prod_name_, sizeof(prod_name_), "mt8167");
unique_id_ = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE;
// TODO(mpuryear): change this to the domain of the clock received from the board driver
clock_domain_ = 0;
return ZX_OK;
}
zx_status_t Mt8167AudioStreamIn::InitPdev() {
ddk::CompositeProtocolClient composite(parent());
if (!composite.is_valid()) {
zxlogf(ERROR, "Could not get composite protocol");
return ZX_ERR_NO_RESOURCES;
}
pdev_ = ddk::PDev(composite);
if (!pdev_.is_valid()) {
return ZX_ERR_NO_RESOURCES;
}
codec_reset_ = ddk::GpioProtocolClient(composite, "gpio");
if (!codec_reset_.is_valid()) {
zxlogf(ERROR, "%s failed to allocate gpio", __FUNCTION__);
return ZX_ERR_NO_RESOURCES;
}
ddk::I2cChannel i2c(composite, "i2c");
codec_ = Tlv320adc::Create(i2c, 0); // ADC for TDM in.
if (!codec_) {
zxlogf(ERROR, "%s could not get Tlv320adc", __func__);
return ZX_ERR_NO_RESOURCES;
}
zx_status_t status = pdev_.GetBti(0, &bti_);
if (status != ZX_OK) {
zxlogf(ERROR, "%s could not obtain bti %d", __func__, status);
return status;
}
std::optional<ddk::MmioBuffer> mmio_audio, mmio_clk, mmio_pll;
status = pdev_.MapMmio(0, &mmio_audio);
if (status != ZX_OK) {
return status;
}
status = pdev_.MapMmio(1, &mmio_clk);
if (status != ZX_OK) {
return status;
}
status = pdev_.MapMmio(2, &mmio_pll);
if (status != ZX_OK) {
return status;
}
mt_audio_ = MtAudioInDevice::Create(*std::move(mmio_audio), *std::move(mmio_clk),
*std::move(mmio_pll), MtAudioInDevice::I2S6);
if (mt_audio_ == nullptr) {
zxlogf(ERROR, "%s failed to create device", __FUNCTION__);
return ZX_ERR_NO_MEMORY;
}
// Reset Codec. "After all power supplies are at their specified values, the RESET pin must
// be driven low for at least 10 ns".
codec_reset_.Write(0); // Reset.
zx_nanosleep(zx_deadline_after(ZX_NSEC(10)));
codec_reset_.Write(1); // Set to "not reset".
codec_->Init();
// Initialize the ring buffer
InitBuffer(kRingBufferSize);
mt_audio_->SetBuffer(pinned_ring_buffer_.region(0).phys_addr, pinned_ring_buffer_.region(0).size);
return ZX_OK;
}
zx_status_t Mt8167AudioStreamIn::ChangeFormat(const audio_proto::StreamSetFmtReq& req) {
fifo_depth_ = mt_audio_->fifo_depth();
external_delay_nsec_ = 0;
auto status = mt_audio_->SetRate(req.frames_per_second);
if (status != ZX_OK) {
return status;
}
return mt_audio_->SetBitsPerSample(16);
}
zx_status_t Mt8167AudioStreamIn::GetBuffer(const audio_proto::RingBufGetBufferReq& req,
uint32_t* out_num_rb_frames, zx::vmo* out_buffer) {
uint32_t rb_frames = static_cast<uint32_t>(pinned_ring_buffer_.region(0).size) / frame_size_;
if (req.min_ring_buffer_frames > rb_frames) {
return ZX_ERR_OUT_OF_RANGE;
}
zx_status_t 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) {
return status;
}
*out_num_rb_frames = rb_frames;
mt_audio_->SetBuffer(pinned_ring_buffer_.region(0).phys_addr, rb_frames * frame_size_);
return ZX_OK;
}
zx_status_t Mt8167AudioStreamIn::Start(uint64_t* out_start_time) {
*out_start_time = mt_audio_->Start();
uint32_t notifs = LoadNotificationsPerRing();
if (notifs) {
us_per_notification_ = static_cast<uint32_t>(1000 * pinned_ring_buffer_.region(0).size /
(frame_size_ * kMaxSampleRate / 1000 * notifs));
notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_));
} else {
us_per_notification_ = 0;
}
return ZX_OK;
}
// Timer handler for sending out position notifications.
void Mt8167AudioStreamIn::ProcessRingNotification() {
ScopedToken t(domain_token());
ZX_ASSERT(us_per_notification_ != 0);
notify_timer_.PostDelayed(dispatcher(), zx::usec(us_per_notification_));
audio_proto::RingBufPositionNotify resp = {};
resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
resp.monotonic_time = zx::clock::get_monotonic().get();
resp.ring_buffer_pos = mt_audio_->GetRingPosition();
NotifyPosition(resp);
}
zx_status_t Mt8167AudioStreamIn::Stop() {
notify_timer_.Cancel();
us_per_notification_ = 0;
mt_audio_->Stop();
return ZX_OK;
}
zx_status_t Mt8167AudioStreamIn::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;
}
audio_stream_format_range_t range;
range.min_channels = kNumberOfChannels;
range.max_channels = kNumberOfChannels;
range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
range.min_frames_per_second = kMinSampleRate;
range.max_frames_per_second = kMaxSampleRate;
range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY | ASF_RANGE_FLAG_FPS_44100_FAMILY;
supported_formats_.push_back(range);
return ZX_OK;
}
zx_status_t Mt8167AudioStreamIn::InitBuffer(size_t size) {
zx_status_t status =
zx_vmo_create_contiguous(bti_.get(), size, 0, ring_buffer_vmo_.reset_and_get_address());
if (status != ZX_OK) {
zxlogf(ERROR, "%s failed to allocate ring buffer vmo - %d", __func__, 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, "%s failed to pin ring buffer vmo - %d", __func__, status);
return status;
}
if (pinned_ring_buffer_.region_count() != 1) {
zxlogf(ERROR, "%s buffer is not contiguous", __func__);
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
} // namespace mt8167
} // namespace audio
__BEGIN_CDECLS
zx_status_t mt_audio_in_bind(void* ctx, zx_device_t* device) {
auto stream = audio::SimpleAudioStream::Create<audio::mt8167::Mt8167AudioStreamIn>(device);
if (stream == nullptr) {
return ZX_ERR_NO_MEMORY;
}
return ZX_OK;
}
static constexpr zx_driver_ops_t mt_audio_in_driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = mt_audio_in_bind;
return ops;
}();
__END_CDECLS
ZIRCON_DRIVER(mt8167_audio_in, mt_audio_in_driver_ops, "zircon", "0.1");