| // 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 <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/unique_ptr.h> |
| #include <limits> |
| #include <math.h> |
| #include <new> |
| #include <utility> |
| |
| #include "hdmitx.h" |
| #include "vim-audio.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(WARN, "HDMI parameters are not set up. Cannot enable audio!\n"); |
| 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(WARN, "Failed to add audio stream; missing Vim2Audio instance!\n"); |
| 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(WARN, "Failed to add audio stream; missing Vim2Audio instance!\n"); |
| return; |
| } |
| |
| auto cpp_audio = reinterpret_cast<audio::vim2::Vim2Audio*>(display->audio); |
| cpp_audio->OnDisplayRemoved(display_id); |
| } |
| |
| } // extern "C" |