1// Copyright 2017 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 <audio-proto-utils/format-utils.h>
6#include <ddk/debug.h>
7#include <ddk/device.h>
8#include <fbl/algorithm.h>
9#include <fbl/limits.h>
10#include <zircon/device/audio.h>
11#include <lib/zx/vmar.h>
12
13#include "a113-pdm.h"
14#include "dispatcher-pool/dispatcher-thread-pool.h"
15#include "gauss-pdm-input-stream.h"
16
17namespace audio {
18namespace gauss {
19
20GaussPdmInputStream::~GaussPdmInputStream() {}
21
22// static
23zx_status_t GaussPdmInputStream::Create(zx_device_t* parent) {
24    zxlogf(DEBUG1, "%s\n", __func__);
25
26    auto domain = dispatcher::ExecutionDomain::Create();
27    if (domain == nullptr) {
28        return ZX_ERR_NO_MEMORY;
29    }
30
31    auto stream =
32        fbl::AdoptRef(new GaussPdmInputStream(parent, fbl::move(domain)));
33
34    zx_status_t res = stream->Bind("pdm-audio-driver", parent);
35    if (res == ZX_OK) {
36        // If bind/setup has succeeded, then the devmgr now controls our
37        // lifecycle and will release us when finished with us.  Let go of our
38        // local reference.
39        //
40        __UNUSED auto dummy = stream.leak_ref();
41    }
42
43    return ZX_OK;
44}
45
46zx_status_t GaussPdmInputStream::Bind(const char* devname,
47                                      zx_device_t* parent) {
48    ZX_DEBUG_ASSERT(!supported_formats_.size());
49    a113_audio_device_init(&audio_device_, parent);
50
51    audio_stream_format_range_t range;
52    range.min_channels = 8;
53    range.max_channels = 8;
54    range.min_frames_per_second = 48000;
55    range.max_frames_per_second = 48000;
56    range.flags = ASF_RANGE_FLAG_FPS_48000_FAMILY;
57    range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
58    supported_formats_.push_back(range);
59
60    a113_pdm_arb_config(&audio_device_);
61
62    // Register interrupt and start irq handling thread.
63    zx_status_t status = pdev_map_interrupt(
64        &audio_device_.pdev, 0 /* PDM IRQ */, &audio_device_.pdm_irq);
65
66    if (status != ZX_OK) {
67        zxlogf(ERROR, "Could not map interrupt.\n");
68        goto finished;
69    }
70
71    status = pdev_get_bti(&audio_device_.pdev, 0, &audio_device_.bti);
72    if (status != ZX_OK) {
73        zxlogf(ERROR, "Could not get bti.\n");
74        goto finished;
75    }
76
77    status = thrd_create_with_name(
78        &irqthrd_,
79        [](void* thiz) -> int {
80            return reinterpret_cast<GaussPdmInputStream*>(thiz)->IrqThread();
81        },
82        this, "pdm_irq_thread");
83
84    if (status != ZX_OK) {
85        zxlogf(ERROR, "Could not start irq thread.\n");
86    }
87
88finished:
89    if (status != ZX_OK) {
90        zx_handle_close(audio_device_.pdm_irq);
91        zx_handle_close(audio_device_.bti);
92        return status;
93    }
94
95    return GaussPdmInputStreamBase::DdkAdd(devname);
96}
97
98void GaussPdmInputStream::DdkUnbind() {
99    zxlogf(DEBUG1, "%s\n", __func__);
100    // Close all of our client event sources if we have not already.
101    default_domain_->Deactivate();
102
103    // Unpublish our device node.
104    DdkRemove();
105}
106
107void GaussPdmInputStream::DdkRelease() {
108    zxlogf(DEBUG1, "%s\n", __func__);
109
110    // Shutdown irq thread.
111    zx_interrupt_destroy(audio_device_.pdm_irq);
112    thrd_join(irqthrd_, nullptr);
113
114    zx_handle_close(audio_device_.pdm_irq);
115    zx_handle_close(audio_device_.bti);
116
117    // Reclaim our reference from the driver framework and let it go out of
118    // scope.  If this is our last reference (it should be), we will destruct
119    // immediately afterwards.
120    auto thiz = fbl::internal::MakeRefPtrNoAdopt(this);
121}
122
123zx_status_t GaussPdmInputStream::DdkIoctl(uint32_t op, const void* in_buf,
124                                          size_t in_len, void* out_buf,
125                                          size_t out_len, size_t* out_actual) {
126    zxlogf(DEBUG1, "%s\n", __func__);
127    // The only IOCTL we support is get channel.
128    if (op != AUDIO_IOCTL_GET_CHANNEL) {
129        return ZX_ERR_NOT_SUPPORTED;
130    }
131
132    if ((out_buf == nullptr) || (out_actual == nullptr) ||
133        (out_len != sizeof(zx_handle_t))) {
134        return ZX_ERR_INVALID_ARGS;
135    }
136
137    fbl::AutoLock lock(&lock_);
138
139    // Attempt to allocate a new driver channel and bind it to us.  If we don't
140    // already have an stream_channel_, flag this channel is the privileged
141    // connection (The connection which is allowed to do things like change
142    // formats).
143    bool privileged = (stream_channel_ == nullptr);
144    auto channel = dispatcher::Channel::Create();
145    if (channel == nullptr)
146        return ZX_ERR_NO_MEMORY;
147
148    dispatcher::Channel::ProcessHandler phandler([
149        stream = fbl::WrapRefPtr(this), privileged
150    ](dispatcher::Channel * channel)->zx_status_t {
151        OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
152        return stream->ProcessStreamChannel(channel, privileged);
153    });
154
155    dispatcher::Channel::ChannelClosedHandler chandler;
156    if (privileged) {
157        chandler = dispatcher::Channel::ChannelClosedHandler(
158            [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel)
159                ->void {
160                    OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
161                    stream->DeactivateStreamChannel(channel);
162                });
163    }
164
165    zx::channel client_endpoint;
166    zx_status_t res =
167        channel->Activate(&client_endpoint, default_domain_,
168                          fbl::move(phandler), fbl::move(chandler));
169    if (res == ZX_OK) {
170        if (privileged) {
171            ZX_DEBUG_ASSERT(stream_channel_ == nullptr);
172            stream_channel_ = fbl::move(channel);
173        }
174
175        *(reinterpret_cast<zx_handle_t*>(out_buf)) = client_endpoint.release();
176        *out_actual = sizeof(zx_handle_t);
177    }
178
179    return res;
180}
181
182#define HREQ(_cmd, _payload, _handler, _allow_noack, ...)                      \
183    case _cmd:                                                                 \
184        if (req_size != sizeof(req._payload)) {                                \
185            zxlogf(ERROR, "Bad " #_cmd " response length (%u != %zu)\n",       \
186                   req_size, sizeof(req._payload));                            \
187            return ZX_ERR_INVALID_ARGS;                                        \
188        }                                                                      \
189        if (!_allow_noack && (req.hdr.cmd & AUDIO_FLAG_NO_ACK)) {              \
190            zxlogf(ERROR, "NO_ACK flag not allowed for " #_cmd "\n");          \
191            return ZX_ERR_INVALID_ARGS;                                        \
192        }                                                                      \
193        return _handler(channel, req._payload, ##__VA_ARGS__);
194zx_status_t
195GaussPdmInputStream::ProcessStreamChannel(dispatcher::Channel* channel,
196                                          bool privileged) {
197    ZX_DEBUG_ASSERT(channel != nullptr);
198
199    union {
200        audio_proto::CmdHdr hdr;
201        audio_proto::StreamGetFmtsReq get_formats;
202        audio_proto::StreamSetFmtReq set_format;
203        audio_proto::GetGainReq get_gain;
204        audio_proto::SetGainReq set_gain;
205        audio_proto::PlugDetectReq plug_detect;
206        audio_proto::GetUniqueIdReq get_unique_id;
207        audio_proto::GetStringReq get_string;
208    } req;
209
210    static_assert(
211        sizeof(req) <= 256,
212        "Request buffer is getting to be too large to hold on the stack!");
213
214    uint32_t req_size;
215    zx_status_t res = channel->Read(&req, sizeof(req), &req_size);
216    if (res != ZX_OK)
217        return res;
218
219    if ((req_size < sizeof(req.hdr) ||
220         (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID)))
221        return ZX_ERR_INVALID_ARGS;
222
223    // Strip the NO_ACK flag from the request before selecting the dispatch
224    // target.
225    auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK);
226    switch (cmd) {
227        HREQ(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, OnGetStreamFormats,
228             false);
229        HREQ(AUDIO_STREAM_CMD_SET_FORMAT, set_format, OnSetStreamFormat, false,
230             privileged);
231        HREQ(AUDIO_STREAM_CMD_GET_GAIN, get_gain, OnGetGain, false);
232        HREQ(AUDIO_STREAM_CMD_SET_GAIN, set_gain, OnSetGain, true);
233        HREQ(AUDIO_STREAM_CMD_PLUG_DETECT, plug_detect, OnPlugDetect, true);
234        HREQ(AUDIO_STREAM_CMD_GET_UNIQUE_ID, get_unique_id, OnGetUniqueId, false);
235        HREQ(AUDIO_STREAM_CMD_GET_STRING, get_string, OnGetString, false);
236    default:
237        zxlogf(ERROR, "Unrecognized stream command 0x%04x\n", req.hdr.cmd);
238        return ZX_ERR_NOT_SUPPORTED;
239    }
240}
241
242zx_status_t
243GaussPdmInputStream::ProcessRingBufferChannel(dispatcher::Channel* channel) {
244    zxlogf(DEBUG1, "%s\n", __func__);
245    ZX_DEBUG_ASSERT(channel != nullptr);
246
247    fbl::AutoLock lock(&lock_);
248
249    union {
250        audio_proto::CmdHdr hdr;
251        audio_proto::RingBufGetFifoDepthReq get_fifo_depth;
252        audio_proto::RingBufGetBufferReq get_buffer;
253        audio_proto::RingBufStartReq rb_start;
254        audio_proto::RingBufStopReq rb_stop;
255    } req;
256
257    static_assert(
258        sizeof(req) <= 256,
259        "Request buffer is getting to be too large to hold on the stack!");
260
261    uint32_t req_size;
262    zx_status_t res = channel->Read(&req, sizeof(req), &req_size);
263    if (res != ZX_OK)
264        return res;
265
266    if ((req_size < sizeof(req.hdr) ||
267         (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID)))
268        return ZX_ERR_INVALID_ARGS;
269
270    // Strip the NO_ACK flag from the request before selecting the dispatch
271    // target.
272    auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK);
273    switch (cmd) {
274        HREQ(AUDIO_RB_CMD_GET_FIFO_DEPTH, get_fifo_depth, OnGetFifoDepth,
275             false);
276        HREQ(AUDIO_RB_CMD_GET_BUFFER, get_buffer, OnGetBuffer, false);
277        HREQ(AUDIO_RB_CMD_START, rb_start, OnStart, false);
278        HREQ(AUDIO_RB_CMD_STOP, rb_stop, OnStop, false);
279    default:
280        zxlogf(ERROR, "Unrecognized ring buffer command 0x%04x\n", req.hdr.cmd);
281        return ZX_ERR_NOT_SUPPORTED;
282    }
283
284    return ZX_ERR_NOT_SUPPORTED;
285}
286#undef HREQ
287
288zx_status_t GaussPdmInputStream::OnGetStreamFormats(
289    dispatcher::Channel* channel, const audio_proto::StreamGetFmtsReq& req) {
290    zxlogf(DEBUG1, "%s\n", __func__);
291    ZX_DEBUG_ASSERT(channel != nullptr);
292    uint16_t formats_sent = 0;
293    audio_proto::StreamGetFmtsResp resp = { };
294
295    if (supported_formats_.size() > fbl::numeric_limits<uint16_t>::max()) {
296        zxlogf(ERROR, "Too many formats (%zu) to send during "
297                      "AUDIO_STREAM_CMD_GET_FORMATS request!\n",
298               supported_formats_.size());
299        return ZX_ERR_INTERNAL;
300    }
301
302    resp.hdr = req.hdr;
303    resp.format_range_count = static_cast<uint16_t>(supported_formats_.size());
304
305    do {
306        uint16_t todo, payload_sz;
307        zx_status_t res;
308
309        todo = fbl::min<uint16_t>(
310            static_cast<uint16_t>(supported_formats_.size() - formats_sent),
311            AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE);
312        payload_sz =
313            static_cast<uint16_t>(sizeof(resp.format_ranges[0]) * todo);
314
315        resp.first_format_range_ndx = formats_sent;
316        ::memcpy(resp.format_ranges, supported_formats_.get() + formats_sent,
317                 payload_sz);
318
319        res = channel->Write(&resp, sizeof(resp));
320        if (res != ZX_OK) {
321            zxlogf(ERROR,
322                   "Failed to send get stream formats response (res %d)\n",
323                   res);
324            return res;
325        }
326
327        formats_sent = (uint16_t)(formats_sent + todo);
328    } while (formats_sent < supported_formats_.size());
329
330    return ZX_OK;
331}
332
333zx_status_t
334GaussPdmInputStream::OnSetStreamFormat(dispatcher::Channel* channel,
335                                       const audio_proto::StreamSetFmtReq& req,
336                                       bool privileged) {
337    zxlogf(DEBUG1, "%s\n", __func__);
338    ZX_DEBUG_ASSERT(channel != nullptr);
339
340    zx::channel client_rb_channel;
341    audio_proto::StreamSetFmtResp resp = { };
342
343    resp.hdr = req.hdr;
344
345    // Only the privileged stream channel is allowed to change the format.
346    if (!privileged) {
347        ZX_DEBUG_ASSERT(channel == stream_channel_.get());
348        resp.result = ZX_ERR_ACCESS_DENIED;
349        goto finished;
350    }
351
352    // TODO(almasrymina): for now, we only support this one frame rate.
353    if (req.frames_per_second != frame_rate_) {
354        resp.result = ZX_ERR_INVALID_ARGS;
355        goto finished;
356    }
357
358    // Determine the frame size.
359    frame_size_ =
360        audio::utils::ComputeFrameSize(req.channels, req.sample_format);
361    if (!frame_size_) {
362        zxlogf(ERROR, "Failed to compute frame size (ch %hu fmt 0x%08x)\n",
363               req.channels, req.sample_format);
364        resp.result = ZX_ERR_INTERNAL;
365        goto finished;
366    }
367
368    // Looks like we are going ahead with this format change.  Tear down any
369    // exiting ring buffer interface before proceeding.
370    {
371        fbl::AutoLock lock(&lock_);
372        if (rb_channel_ != nullptr) {
373            rb_channel_->Deactivate();
374            rb_channel_.reset();
375        }
376
377        // Create a new ring buffer channel which can be used to move bulk data
378        // and bind it to us.
379        rb_channel_ = dispatcher::Channel::Create();
380        if (rb_channel_ == nullptr) {
381            resp.result = ZX_ERR_NO_MEMORY;
382        } else {
383            dispatcher::Channel::ProcessHandler
384                phandler([stream = fbl::WrapRefPtr(this)](dispatcher::Channel *
385                                                          channel)
386                             ->zx_status_t {
387                                 OBTAIN_EXECUTION_DOMAIN_TOKEN(
388                                     t, stream->default_domain_);
389                                 return stream->ProcessRingBufferChannel(
390                                     channel);
391                             });
392
393            dispatcher::Channel::ChannelClosedHandler
394            chandler([stream = fbl::WrapRefPtr(this)](
395                         const dispatcher::Channel* channel)
396                         ->void {
397                             OBTAIN_EXECUTION_DOMAIN_TOKEN(
398                                 t, stream->default_domain_);
399                             stream->DeactivateRingBufferChannel(channel);
400                         });
401
402            resp.result =
403                rb_channel_->Activate(&client_rb_channel, default_domain_,
404                                      fbl::move(phandler), fbl::move(chandler));
405            if (resp.result != ZX_OK) {
406                rb_channel_.reset();
407            }
408        }
409    }
410
411    a113_audio_register_toddr(&audio_device_);
412
413finished:
414    if (resp.result == ZX_OK) {
415        // TODO(johngro): Report the actual external delay.
416        resp.external_delay_nsec = 0;
417    } else {
418        fbl::AutoLock lock(&lock_);
419
420        if (rb_channel_) {
421            rb_channel_->Deactivate();
422            rb_channel_.reset();
423        }
424    }
425    return channel->Write(&resp, sizeof(resp), fbl::move(client_rb_channel));
426}
427
428int GaussPdmInputStream::IrqThread() {
429    zxlogf(DEBUG1, "Starting irq thread.\n");
430
431    zx_status_t status;
432
433    uint32_t last_notification_offset = 0;
434
435    for (;;) {
436        status = zx_interrupt_wait(audio_device_.pdm_irq, nullptr);
437        if (status != ZX_OK) {
438            zxlogf(DEBUG1, "audio_pdm_input: interrupt error: %d.\n", status);
439            break;
440        }
441
442        a113_toddr_clear_interrupt(&audio_device_, 0x4);
443
444        uint32_t offset =
445            a113_toddr_get_position(&audio_device_) -
446            a113_ee_audio_read(&audio_device_, EE_AUDIO_TODDR_B_START_ADDR);
447
448        vmo_helper_.printoffsetinvmo(offset);
449
450        audio_proto::RingBufPositionNotify resp;
451        resp.ring_buffer_pos = offset;
452        resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
453        resp.hdr.transaction_id = AUDIO_INVALID_TRANSACTION_ID;
454
455        size_t data_available =
456            offset >= last_notification_offset
457                ? offset - last_notification_offset
458                : offset + ring_buffer_size_.load() - last_notification_offset;
459
460        if (notifications_per_ring_.load() &&
461            data_available >=
462                ring_buffer_size_.load() / notifications_per_ring_.load()) {
463            fbl::AutoLock lock(&lock_);
464            if (!rb_channel_) {
465                zxlogf(DEBUG1, "No rb_channel. Ignoring spurious interrupt.\n");
466                continue;
467            }
468            rb_channel_->Write(&resp, sizeof(resp));
469        }
470    }
471
472    zxlogf(DEBUG1, "Leaving irq thread.\n");
473
474    return ZX_OK;
475}
476
477zx_status_t GaussPdmInputStream::OnGetGain(dispatcher::Channel* channel,
478                                           const audio_proto::GetGainReq& req) {
479    zxlogf(DEBUG1, "%s\n", __func__);
480
481    ZX_DEBUG_ASSERT(channel != nullptr);
482    audio_proto::GetGainResp resp = { };
483
484    resp.hdr = req.hdr;
485    resp.cur_mute = false;
486    resp.cur_gain = 0.0;
487    resp.can_mute = false;
488    resp.min_gain = 0.0;
489    resp.max_gain = 0.0;
490    resp.gain_step = 0.0;
491
492    return channel->Write(&resp, sizeof(resp));
493}
494
495zx_status_t GaussPdmInputStream::OnSetGain(dispatcher::Channel* channel,
496                                           const audio_proto::SetGainReq& req) {
497    ZX_DEBUG_ASSERT(channel != nullptr);
498    if (req.hdr.cmd & AUDIO_FLAG_NO_ACK)
499        return ZX_OK;
500
501    audio_proto::SetGainResp resp = { };
502    resp.hdr = req.hdr;
503
504    // We don't support setting gain for now.
505    resp.result = ZX_ERR_INVALID_ARGS;
506
507    return channel->Write(&resp, sizeof(resp));
508}
509
510zx_status_t
511GaussPdmInputStream::OnPlugDetect(dispatcher::Channel* channel,
512                                  const audio_proto::PlugDetectReq& req) {
513    zxlogf(DEBUG1, "%s\n", __func__);
514
515    if (req.hdr.cmd & AUDIO_FLAG_NO_ACK)
516        return ZX_OK;
517
518    audio_proto::PlugDetectResp resp = { };
519    resp.hdr = req.hdr;
520    resp.flags = static_cast<audio_pd_notify_flags_t>(AUDIO_PDNF_HARDWIRED |
521                                                      AUDIO_PDNF_PLUGGED);
522    return channel->Write(&resp, sizeof(resp));
523}
524
525zx_status_t GaussPdmInputStream::OnGetUniqueId(dispatcher::Channel* channel,
526                                               const audio_proto::GetUniqueIdReq& req) {
527    audio_proto::GetUniqueIdResp resp;
528
529    static const audio_stream_unique_id_t mic_id = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE;
530    resp.hdr = req.hdr;
531    resp.unique_id = mic_id;
532
533    return channel->Write(&resp, sizeof(resp));
534}
535
536zx_status_t GaussPdmInputStream::OnGetString(dispatcher::Channel* channel,
537                                             const audio_proto::GetStringReq& req) {
538    audio_proto::GetStringResp resp;
539
540    resp.hdr = req.hdr;
541    resp.id = req.id;
542
543    const char* str;
544    switch (req.id) {
545        case AUDIO_STREAM_STR_ID_MANUFACTURER: str = "Gauss"; break;
546        case AUDIO_STREAM_STR_ID_PRODUCT:      str = "Builtin Microphone"; break;
547        default:                               str = nullptr; break;
548    }
549
550    if (str == nullptr) {
551        resp.result = ZX_ERR_NOT_FOUND;
552        resp.strlen = 0;
553    } else {
554        int res = snprintf(reinterpret_cast<char*>(resp.str), sizeof(resp.str), "%s", str);
555        ZX_DEBUG_ASSERT(res >= 0);
556        resp.result = ZX_OK;
557        resp.strlen = fbl::min<uint32_t>(res, sizeof(resp.str) - 1);
558    }
559
560    return channel->Write(&resp, sizeof(resp));
561}
562
563zx_status_t GaussPdmInputStream::OnGetFifoDepth(
564    dispatcher::Channel* channel,
565    const audio_proto::RingBufGetFifoDepthReq& req) {
566    zxlogf(DEBUG1, "%s\n", __func__);
567
568    audio_proto::RingBufGetFifoDepthResp resp = { };
569
570    resp.hdr = req.hdr;
571    resp.result = ZX_OK;
572    resp.fifo_depth = static_cast<uint32_t>(fifo_depth_);
573
574    return channel->Write(&resp, sizeof(resp));
575}
576
577zx_status_t
578GaussPdmInputStream::OnGetBuffer(dispatcher::Channel* channel,
579                                 const audio_proto::RingBufGetBufferReq& req) {
580    zxlogf(DEBUG1, "%s\n", __func__);
581
582    audio_proto::RingBufGetBufferResp resp = { };
583    zx::vmo client_rb_handle;
584    uint32_t client_rights;
585
586    resp.hdr = req.hdr;
587    resp.result = ZX_ERR_INTERNAL;
588
589    vmo_helper_.DestroyVmo();
590
591    uint32_t notifications_per_ring =
592        req.notifications_per_ring ? req.notifications_per_ring : 1;
593
594    uint32_t requested_period_size =
595        req.min_ring_buffer_frames * frame_size_ / notifications_per_ring;
596
597    uint32_t period_size = fbl::round_up(requested_period_size,
598                                         static_cast<uint32_t>(fifo_depth_));
599
600    ring_buffer_size_.store(fbl::round_up(period_size * notifications_per_ring,
601                                          static_cast<uint32_t>(PAGE_SIZE)));
602
603    // TODO(johngro) : Come back here and fix this.  Right now, we know that our
604    // frame size is always going to be 16 bytes (8 channels, 2 bytes per
605    // channel), and that our ring buffer size is always going to be a multiple
606    // of pages (4k, hence divisible by 16), so this should always be the case.
607    //
608    // Moving forward, if we ever want to support other frame sizes (in
609    // particular, things which may not be a power of two), it would be good to
610    // make this code more generic.  We have a few requirements to obey,
611    // however.  Not only must the ring buffer size be a multiple of frame size,
612    // it must also be a multiple of 8; hence a multiple of LCM(frame_size, 8).
613    // It would be really handy to have a fbl:: version of fbl::gcd and fbl::lcm
614    // to handle these calulations.  Perhaps, by the time that I come back and
615    // address this, we will.
616    if (ring_buffer_size_.load() % frame_size_) {
617        zxlogf(ERROR, "Frame size (%u) does not divide ring buffer size (%zu)\n",
618                frame_size_, ring_buffer_size_.load());
619        goto finished;
620    }
621
622    notifications_per_ring_.store(req.notifications_per_ring);
623
624    zxlogf(DEBUG1, "ring_buffer_size=%lu\n", ring_buffer_size_.load());
625    zxlogf(DEBUG1, "req.notifications_per_ring=%u\n",
626           req.notifications_per_ring);
627
628    // Create the ring buffer vmo we will use to share memory with the client.
629    resp.result = vmo_helper_.AllocateVmo(audio_device_.bti,
630                                          ring_buffer_size_.load());
631    if (resp.result != ZX_OK) {
632        zxlogf(ERROR, "Failed to create ring buffer (size %lu)\n",
633               ring_buffer_size_.load());
634        goto finished;
635    }
636
637    zx_paddr_t start_address;
638    zx_paddr_t end_address;
639
640    resp.result = vmo_helper_.GetVmoRange(&start_address);
641    if (resp.result != ZX_OK) {
642        zxlogf(ERROR, "Failed to get range.\n");
643        goto finished;
644    }
645
646    // -8 because the addresses are indexed 0 -> size-8. The TODDR processes
647    // data in chunks of 8 bytes.
648    end_address = start_address + ring_buffer_size_.load() - 8;
649
650    a113_toddr_set_buf(&audio_device_, (uint32_t)start_address,
651                       (uint32_t)end_address);
652    a113_toddr_set_intrpt(&audio_device_,
653                          static_cast<uint32_t>(period_size / 8));
654
655    // TODO(almasrymina): TODDR and pdm configuration is hardcoded for now,
656    // since we only support the one format. Need to revisit this when we
657    // support more.
658    a113_toddr_select_src(&audio_device_, PDMIN);
659    a113_toddr_set_format(&audio_device_, RJ_16BITS, 31, 16);
660
661    a113_toddr_set_fifos(&audio_device_, 0x40);
662    a113_pdm_ctrl(&audio_device_, 16);
663    a113_pdm_filter_ctrl(&audio_device_);
664
665    // Create the client's handle to the ring buffer vmo and set it back to
666    // them.
667    client_rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_MAP | ZX_RIGHT_READ;
668
669    resp.result = vmo_helper_.Duplicate(client_rights, &client_rb_handle);
670    if (resp.result != ZX_OK) {
671        zxlogf(ERROR, "Failed to duplicate ring buffer handle (res %d)\n",
672               resp.result);
673        goto finished;
674    }
675
676    ZX_DEBUG_ASSERT((ring_buffer_size_.load() / frame_size_) <=
677                    fbl::numeric_limits<decltype(resp.num_ring_buffer_frames)>::max());
678    resp.num_ring_buffer_frames =
679        static_cast<decltype(resp.num_ring_buffer_frames)>(ring_buffer_size_.load() / frame_size_);
680
681finished:
682    zx_status_t res;
683    if (resp.result == ZX_OK) {
684        ZX_DEBUG_ASSERT(client_rb_handle.is_valid());
685        res = channel->Write(&resp, sizeof(resp), fbl::move(client_rb_handle));
686    } else {
687        res = channel->Write(&resp, sizeof(resp));
688    }
689
690    return res;
691}
692
693zx_status_t
694GaussPdmInputStream::OnStart(dispatcher::Channel* channel,
695                             const audio_proto::RingBufStartReq& req) {
696    zxlogf(DEBUG1, "%s\n", __func__);
697
698    audio_proto::RingBufStartResp resp = { };
699    resp.hdr = req.hdr;
700
701    a113_pdm_fifo_reset(&audio_device_);
702    a113_toddr_enable(&audio_device_, 1);
703    a113_pdm_enable(&audio_device_, 1);
704    resp.start_time = zx_clock_get_monotonic();
705
706    resp.result = ZX_OK;
707    return channel->Write(&resp, sizeof(resp));
708}
709
710zx_status_t
711GaussPdmInputStream::OnStop(dispatcher::Channel* channel,
712                            const audio_proto::RingBufStopReq& req) {
713    zxlogf(DEBUG1, "%s\n", __func__);
714
715    audio_proto::RingBufStopResp resp = { };
716
717    a113_toddr_enable(&audio_device_, 0);
718    a113_pdm_enable(&audio_device_, 0);
719
720    resp.hdr = req.hdr;
721
722    return channel->Write(&resp, sizeof(resp));
723}
724
725void GaussPdmInputStream::DeactivateStreamChannel(
726    const dispatcher::Channel* channel) {
727    zxlogf(DEBUG1, "%s\n", __func__);
728
729    fbl::AutoLock lock(&lock_);
730
731    ZX_DEBUG_ASSERT(stream_channel_.get() == channel);
732    ZX_DEBUG_ASSERT(rb_channel_.get() != channel);
733
734    stream_channel_.reset();
735}
736
737void GaussPdmInputStream::DeactivateRingBufferChannel(
738    const dispatcher::Channel* channel) {
739    zxlogf(DEBUG1, "%s\n", __func__);
740
741    fbl::AutoLock lock(&lock_);
742
743    ZX_DEBUG_ASSERT(stream_channel_.get() != channel);
744    ZX_DEBUG_ASSERT(rb_channel_.get() == channel);
745
746    a113_audio_unregister_toddr(&audio_device_);
747
748    rb_channel_->Deactivate();
749    rb_channel_.reset();
750}
751
752} // namespace gauss
753} // namespace audio
754
755extern "C" zx_status_t gauss_pdm_input_bind(void* ctx, zx_device_t* device,
756                                            void** cookie) {
757    zxlogf(DEBUG1, "gauss_pdm_input_bind\n");
758    audio::gauss::GaussPdmInputStream::Create(device);
759    return ZX_OK;
760}
761
762extern "C" void gauss_pdm_input_release(void*) {
763    zxlogf(DEBUG1, "gauss_pdm_input_release\n");
764    dispatcher::ThreadPool::ShutdownAll();
765}
766