| // 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 "vim-audio.h" | |
| #include <math.h> | |
| #include <limits> | |
| #include <new> | |
| #include <utility> | |
| #include <fbl/algorithm.h> | |
| #include <fbl/alloc_checker.h> | |
| #include "hdmitx.h" | |
| #include "vim-display.h" | |
| namespace audio { | |
| namespace vim2 { | |
| // Create a ring buffer large enough to hold 1 second of 48kHz stereo 16-bit | |
| // audio. | |
| // | |
| // TODO(johngro): Look into what it would take to remove the restriction that | |
| // this buffer be contiguous so that we can more easily map the buffer on the | |
| // fly without needing to take precious contiguous memory. | |
| constexpr size_t SPDIF_RB_SIZE = fbl::round_up<size_t, size_t>(48000 * 2 * 2u, PAGE_SIZE); | |
| Vim2Audio::~Vim2Audio() {} | |
| zx_status_t Vim2Audio::Init(const pdev_protocol_t* pdev) { | |
| zx_status_t res; | |
| // Get a hold of our registers. | |
| regs_ = Registers::Create(pdev, MMIO_AUD_OUT, &res); | |
| if (res != ZX_OK) { | |
| DISP_ERROR("Error mapping registers (mmio_id %u, res %d)\n", MMIO_AUD_OUT, res); | |
| return res; | |
| } | |
| ZX_DEBUG_ASSERT(regs_ != nullptr); | |
| // Place the various units into reset | |
| // | |
| // TODO(johngro): Add I2S to this list, right now we are only managing SPDIF | |
| Vim2SpdifAudioStream::Disable(*regs_); | |
| // Obtain our BTI from the platform manager | |
| res = pdev_get_bti(pdev, 0, audio_bti_.reset_and_get_address()); | |
| if (res != ZX_OK) { | |
| DISP_ERROR("Failed to get audio BTI handle! (res = %d)\n", res); | |
| return res; | |
| } | |
| // Now that we have our BTI, and we have quiesced our hardware, we can | |
| // release any quarantined VMOs which may be lingering from a previous | |
| // crash. Note, it should be impossible for this to fail. | |
| res = audio_bti_.release_quarantine(); | |
| ZX_DEBUG_ASSERT(res == ZX_OK); | |
| // Allocate the buffer we will use for SPDIF | |
| // | |
| // TODO(johngro): How do we guarantee that this memory's phys location is | |
| // below the 4GB mark? | |
| zx::vmo spdif_rb_vmo; | |
| res = zx_vmo_create_contiguous(audio_bti_.get(), SPDIF_RB_SIZE, 0, | |
| spdif_rb_vmo.reset_and_get_address()); | |
| if (res != ZX_OK) { | |
| DISP_ERROR("Failed to allocate %zu byte ring buffer! (res = %d)\n", SPDIF_RB_SIZE, res); | |
| return res; | |
| } | |
| spdif_rb_vmo_ = RefCountedVmo::Create(std::move(spdif_rb_vmo)); | |
| if (spdif_rb_vmo_ == nullptr) { | |
| DISP_ERROR("Failed to allocate RefCountedVmo\n"); | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| return ZX_OK; | |
| } | |
| void Vim2Audio::OnDisplayAdded(const vim2_display_t* display, uint64_t display_id) { | |
| if (spdif_stream_ != nullptr) { | |
| ZX_DEBUG_ASSERT(spdif_stream_->display_id() != display_id); | |
| return; | |
| } | |
| if (!display->p) { | |
| zxlogf(WARNING, "HDMI parameters are not set up. Cannot enable audio!"); | |
| return; | |
| } | |
| // Pin our VMO so that HW can access it. | |
| fzl::PinnedVmo pinned_spdif_rb; | |
| zx_status_t res; | |
| res = pinned_spdif_rb.Pin(spdif_rb_vmo_->vmo(), audio_bti_, ZX_VM_PERM_READ); | |
| if (res != ZX_OK) { | |
| DISP_ERROR("Failed to pin %zu byte ring buffer! (res = %d)\n", SPDIF_RB_SIZE, res); | |
| return; | |
| } | |
| // Sanity check the pinned VMO. | |
| if (pinned_spdif_rb.region_count() != 1) { | |
| DISP_ERROR("Audio ring buffer VMO is not contiguous! (regions = %u)\n", | |
| pinned_spdif_rb.region_count()); | |
| return; | |
| } | |
| const auto& r = pinned_spdif_rb.region(0); | |
| if ((r.phys_addr + r.size - 1) > std::numeric_limits<uint32_t>::max()) { | |
| DISP_ERROR("Audio ring buffer VMO is not below 4GB! [0x%zx, 0x%zx]\n", r.phys_addr, | |
| r.phys_addr + r.size); | |
| return; | |
| } | |
| spdif_stream_ = SimpleAudioStream::Create<Vim2SpdifAudioStream>( | |
| display, regs_, spdif_rb_vmo_, std::move(pinned_spdif_rb), display_id); | |
| } | |
| void Vim2Audio::OnDisplayRemoved(uint64_t display_id) { | |
| if (spdif_stream_ && (spdif_stream_->display_id() == display_id)) { | |
| spdif_stream_->Shutdown(); | |
| spdif_stream_ = nullptr; | |
| } | |
| } | |
| } // namespace vim2 | |
| } // namespace audio | |
| extern "C" { | |
| zx_status_t vim2_audio_create(const pdev_protocol_t* pdev, vim2_audio_t** out_audio) { | |
| ZX_DEBUG_ASSERT(pdev != nullptr); | |
| ZX_DEBUG_ASSERT(out_audio != nullptr); | |
| *out_audio = nullptr; | |
| if (*out_audio != nullptr) { | |
| return ZX_ERR_BAD_STATE; | |
| } | |
| fbl::AllocChecker ac; | |
| auto audio = fbl::make_unique_checked<audio::vim2::Vim2Audio>(&ac); | |
| if (!ac.check()) { | |
| return ZX_ERR_NO_MEMORY; | |
| } | |
| zx_status_t res = audio->Init(pdev); | |
| if (res != ZX_OK) { | |
| return res; | |
| } | |
| *out_audio = reinterpret_cast<vim2_audio_t*>(audio.release()); | |
| return ZX_OK; | |
| } | |
| void vim2_audio_shutdown(vim2_audio_t** inout_audio) { | |
| ZX_DEBUG_ASSERT(inout_audio); | |
| delete reinterpret_cast<audio::vim2::Vim2Audio*>(*inout_audio); | |
| *inout_audio = nullptr; | |
| } | |
| void vim2_audio_on_display_added(const vim2_display_t* display, uint64_t display_id) { | |
| if (!display->audio) { | |
| zxlogf(WARNING, "Failed to add audio stream; missing Vim2Audio instance!"); | |
| return; | |
| } | |
| auto cpp_audio = reinterpret_cast<audio::vim2::Vim2Audio*>(display->audio); | |
| cpp_audio->OnDisplayAdded(display, display_id); | |
| } | |
| void vim2_audio_on_display_removed(const vim2_display_t* display, uint64_t display_id) { | |
| if (!display->audio) { | |
| zxlogf(WARNING, "Failed to add audio stream; missing Vim2Audio instance!"); | |
| return; | |
| } | |
| auto cpp_audio = reinterpret_cast<audio::vim2::Vim2Audio*>(display->audio); | |
| cpp_audio->OnDisplayRemoved(display_id); | |
| } | |
| } // extern "C" |