1 // Copyright 2018 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <fbl/algorithm.h>
6 #include <fbl/alloc_checker.h>
7 #include <fbl/unique_ptr.h>
8 #include <limits>
9 #include <math.h>
10 #include <new>
11 #include <utility>
12 
13 #include "hdmitx.h"
14 #include "vim-audio.h"
15 #include "vim-display.h"
16 
17 namespace audio {
18 namespace vim2 {
19 
20 // Create a ring buffer large enough to hold 1 second of 48kHz stereo 16-bit
21 // audio.
22 //
23 // TODO(johngro): Look into what it would take to remove the restriction that
24 // this buffer be contiguous so that we can more easily map the buffer on the
25 // fly without needing to take precious contiguous memory.
26 constexpr size_t SPDIF_RB_SIZE = fbl::round_up<size_t, size_t>(48000 * 2 * 2u, PAGE_SIZE);
27 
~Vim2Audio()28 Vim2Audio::~Vim2Audio() {
29 }
30 
Init(const pdev_protocol_t * pdev)31 zx_status_t Vim2Audio::Init(const pdev_protocol_t* pdev) {
32     zx_status_t res;
33 
34     // Get a hold of our registers.
35     regs_ = Registers::Create(pdev, MMIO_AUD_OUT, &res);
36     if (res != ZX_OK) {
37         DISP_ERROR("Error mapping registers (mmio_id %u, res %d)\n", MMIO_AUD_OUT, res);
38         return res;
39     }
40     ZX_DEBUG_ASSERT(regs_ != nullptr);
41 
42     // Place the various units into reset
43     //
44     // TODO(johngro): Add I2S to this list, right now we are only managing SPDIF
45     Vim2SpdifAudioStream::Disable(*regs_);
46 
47     // Obtain our BTI from the platform manager
48     res = pdev_get_bti(pdev, 0, audio_bti_.reset_and_get_address());
49     if (res != ZX_OK) {
50         DISP_ERROR("Failed to get audio BTI handle! (res = %d)\n", res);
51         return res;
52     }
53 
54     // Now that we have our BTI, and we have quiesced our hardware, we can
55     // release any quarantined VMOs which may be lingering from a previous
56     // crash.  Note, it should be impossible for this to fail.
57     res = audio_bti_.release_quarantine();
58     ZX_DEBUG_ASSERT(res == ZX_OK);
59 
60     // Allocate the buffer we will use for SPDIF
61     //
62     // TODO(johngro): How do we guarantee that this memory's phys location is
63     // below the 4GB mark?
64     zx::vmo spdif_rb_vmo;
65     res = zx_vmo_create_contiguous(audio_bti_.get(),
66                                    SPDIF_RB_SIZE,
67                                    0,
68                                    spdif_rb_vmo.reset_and_get_address());
69     if (res != ZX_OK) {
70         DISP_ERROR("Failed to allocate %zu byte ring buffer! (res = %d)\n", SPDIF_RB_SIZE, res);
71         return res;
72     }
73 
74     spdif_rb_vmo_ = RefCountedVmo::Create(std::move(spdif_rb_vmo));
75     if (spdif_rb_vmo_ == nullptr) {
76         DISP_ERROR("Failed to allocate RefCountedVmo\n");
77         return ZX_ERR_NO_MEMORY;
78     }
79 
80     return ZX_OK;
81 }
82 
OnDisplayAdded(const vim2_display_t * display,uint64_t display_id)83 void Vim2Audio::OnDisplayAdded(const vim2_display_t* display, uint64_t display_id) {
84     if (spdif_stream_ != nullptr) {
85         ZX_DEBUG_ASSERT(spdif_stream_->display_id() != display_id);
86         return;
87     }
88 
89     if (!display->p) {
90         zxlogf(WARN, "HDMI parameters are not set up.  Cannot enable audio!\n");
91         return;
92     }
93 
94     // Pin our VMO so that HW can access it.
95     fzl::PinnedVmo pinned_spdif_rb;
96     zx_status_t res;
97     res = pinned_spdif_rb.Pin(spdif_rb_vmo_->vmo(), audio_bti_, ZX_VM_PERM_READ);
98     if (res != ZX_OK) {
99         DISP_ERROR("Failed to pin %zu byte ring buffer! (res = %d)\n", SPDIF_RB_SIZE, res);
100         return;
101     }
102 
103     // Sanity check the pinned VMO.
104     if (pinned_spdif_rb.region_count() != 1) {
105         DISP_ERROR("Audio ring buffer VMO is not contiguous! (regions = %u)\n",
106                    pinned_spdif_rb.region_count());
107         return;
108     }
109 
110     const auto& r = pinned_spdif_rb.region(0);
111     if ((r.phys_addr + r.size - 1) > std::numeric_limits<uint32_t>::max()) {
112         DISP_ERROR("Audio ring buffer VMO is not below 4GB! [0x%zx, 0x%zx]\n",
113                    r.phys_addr,
114                    r.phys_addr + r.size);
115         return;
116     }
117 
118     spdif_stream_ = SimpleAudioStream::Create<Vim2SpdifAudioStream>(display,
119                                                                     regs_,
120                                                                     spdif_rb_vmo_,
121                                                                     std::move(pinned_spdif_rb),
122                                                                     display_id);
123 }
124 
OnDisplayRemoved(uint64_t display_id)125 void Vim2Audio::OnDisplayRemoved(uint64_t display_id) {
126     if (spdif_stream_ && (spdif_stream_->display_id() == display_id)) {
127         spdif_stream_->Shutdown();
128         spdif_stream_ = nullptr;
129     }
130 }
131 
132 }  // namespace vim2
133 }  // namespace audio
134 
135 extern "C" {
136 
vim2_audio_create(const pdev_protocol_t * pdev,vim2_audio_t ** out_audio)137 zx_status_t vim2_audio_create(const pdev_protocol_t* pdev,
138                               vim2_audio_t **out_audio) {
139     ZX_DEBUG_ASSERT(pdev != nullptr);
140     ZX_DEBUG_ASSERT(out_audio != nullptr);
141     *out_audio = nullptr;
142 
143     if (*out_audio != nullptr) {
144         return ZX_ERR_BAD_STATE;
145     }
146 
147     fbl::AllocChecker ac;
148     auto audio = fbl::make_unique_checked<audio::vim2::Vim2Audio>(&ac);
149     if (!ac.check()) {
150         return ZX_ERR_NO_MEMORY;
151     }
152 
153     zx_status_t res = audio->Init(pdev);
154     if (res != ZX_OK) {
155         return res;
156     }
157 
158     *out_audio = reinterpret_cast<vim2_audio_t*>(audio.release());
159     return ZX_OK;
160 }
161 
vim2_audio_shutdown(vim2_audio_t ** inout_audio)162 void vim2_audio_shutdown(vim2_audio_t** inout_audio) {
163     ZX_DEBUG_ASSERT(inout_audio);
164     delete reinterpret_cast<audio::vim2::Vim2Audio*>(*inout_audio);
165     *inout_audio = nullptr;
166 }
167 
vim2_audio_on_display_added(const vim2_display_t * display,uint64_t display_id)168 void vim2_audio_on_display_added(const vim2_display_t* display, uint64_t display_id) {
169     if (!display->audio) {
170         zxlogf(WARN, "Failed to add audio stream; missing Vim2Audio instance!\n");
171         return;
172     }
173 
174     auto cpp_audio = reinterpret_cast<audio::vim2::Vim2Audio*>(display->audio);
175     cpp_audio->OnDisplayAdded(display, display_id);
176 }
177 
vim2_audio_on_display_removed(const vim2_display_t * display,uint64_t display_id)178 void vim2_audio_on_display_removed(const vim2_display_t* display, uint64_t display_id) {
179     if (!display->audio) {
180         zxlogf(WARN, "Failed to add audio stream; missing Vim2Audio instance!\n");
181         return;
182     }
183 
184     auto cpp_audio = reinterpret_cast<audio::vim2::Vim2Audio*>(display->audio);
185     cpp_audio->OnDisplayRemoved(display_id);
186 }
187 
188 }  // extern "C"
189