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 <fbl/vector.h>
6
7#include <intel-hda/utils/codec-caps.h>
8#include <intel-hda/utils/codec-commands.h>
9#include <intel-hda/utils/codec-state.h>
10#include <intel-hda/utils/utils.h>
11
12#include "debug-logging.h"
13#include "realtek-stream.h"
14
15DECLARE_STATIC_SLAB_ALLOCATOR_STORAGE(audio::intel_hda::codecs::RealtekStream::PCAT, 16);
16
17namespace audio {
18namespace intel_hda {
19namespace codecs {
20
21RealtekStream::RealtekStream(const StreamProperties& props)
22    : IntelHDAStreamBase(props.stream_id, props.is_input),
23      props_(props) {
24  SetPersistentUniqueId(props.uid);
25}
26
27zx_status_t RealtekStream::DisableConverterLocked(bool force_all) {
28    const Command DISABLE_CONVERTER_VERBS[] = {
29        { props_.conv_nid, SET_AMPLIFIER_GAIN_MUTE(true, 0, is_input(), !is_input()) },
30        { props_.pc_nid,   SET_AMPLIFIER_GAIN_MUTE(true, 0, is_input(), !is_input()) },
31        { props_.conv_nid, SET_CONVERTER_STREAM_CHAN(IHDA_INVALID_STREAM_TAG, 0) },
32        { props_.conv_nid, SET_POWER_STATE(HDA_PS_D3HOT) },
33        { props_.pc_nid,   SET_POWER_STATE(HDA_PS_D3HOT) },
34    };
35
36    return RunCmdListLocked(DISABLE_CONVERTER_VERBS, countof(DISABLE_CONVERTER_VERBS), force_all);
37}
38
39zx_status_t RealtekStream::UpdateConverterGainLocked(float target_gain) {
40    if (!conv_.has_amp)
41        return ZX_ERR_NOT_SUPPORTED;
42
43    if ((target_gain < conv_.min_gain) || (target_gain > conv_.max_gain))
44        return ZX_ERR_INVALID_ARGS;
45
46    ZX_DEBUG_ASSERT(conv_.gain_step > 0);
47
48    float tmp = ((target_gain - conv_.min_gain) + (conv_.gain_step / 2)) / conv_.gain_step;
49    ZX_DEBUG_ASSERT(static_cast<uint32_t>(tmp) <= conv_.amp_caps.num_steps());
50
51    cur_conv_gain_steps_ = ComputeGainSteps(conv_, target_gain);
52    return ZX_OK;
53}
54
55float RealtekStream::ComputeCurrentGainLocked() {
56    return conv_.has_amp
57        ? conv_.min_gain + (cur_conv_gain_steps_ * conv_.gain_step)
58        : 0.0f;
59}
60
61zx_status_t RealtekStream::SendGainUpdatesLocked() {
62    zx_status_t res;
63
64    if (conv_.has_amp) {
65        bool mute = conv_.amp_caps.can_mute() ? cur_mute_ : false;
66        res = RunCmdLocked({ props_.conv_nid, SET_AMPLIFIER_GAIN_MUTE(mute,
67                                                                      cur_conv_gain_steps_,
68                                                                      is_input(), !is_input()) });
69        if (res != ZX_OK)
70            return res;
71    }
72
73    if (pc_.has_amp) {
74        bool mute = pc_.amp_caps.can_mute() ? cur_mute_ : false;
75        res = RunCmdLocked({ props_.pc_nid, SET_AMPLIFIER_GAIN_MUTE(mute,
76                                                                    cur_pc_gain_steps_,
77                                                                    is_input(), !is_input()) });
78        if (res != ZX_OK)
79            return res;
80    }
81
82    return ZX_OK;
83}
84
85// TODO(johngro) : re: the plug_notify_targets_ list.  In theory, we could put
86// this in a tree indexed by the channel's owner context, or by the pointer
87// itself.  This would make add/remove operations simpler, and faster in the
88// case that we had lots of clients.  In reality, however, we are likely to
89// limit the interface moving forward so that we have only one client at a time
90// (in which case this becomes much simpler).  Moving forward, we need to come
91// back and either simplify or optimize (as the situation warrents) once we know
92// how we are proceeding.
93void RealtekStream::AddPDNotificationTgtLocked(dispatcher::Channel* channel) {
94    bool duplicate = false;
95    for (auto& tgt : plug_notify_targets_) {
96        duplicate = (tgt.channel.get() == channel);
97        if (duplicate)
98            break;
99    }
100
101    if (!duplicate) {
102        fbl::RefPtr<dispatcher::Channel> c(channel);
103        fbl::unique_ptr<NotifyTarget> tgt(new NotifyTarget(fbl::move(c)));
104        plug_notify_targets_.push_back(fbl::move(tgt));
105    }
106}
107
108void RealtekStream::RemovePDNotificationTgtLocked(const dispatcher::Channel& channel) {
109    for (auto& tgt : plug_notify_targets_) {
110        if (tgt.channel.get() == &channel) {
111            plug_notify_targets_.erase(tgt);
112            break;
113        }
114    }
115}
116
117// static
118uint8_t RealtekStream::ComputeGainSteps(const CommonCaps& caps, float target_gain) {
119    if (!caps.has_amp || !caps.amp_caps.num_steps())
120        return 0;
121
122    if (target_gain < caps.min_gain)
123        return 0;
124
125    if (target_gain > caps.max_gain)
126        return static_cast<uint8_t>(caps.amp_caps.num_steps() - 1);
127
128    ZX_DEBUG_ASSERT(caps.gain_step > 0);
129    float tmp = ((target_gain - caps.min_gain) + (caps.gain_step / 2)) / caps.gain_step;
130    ZX_DEBUG_ASSERT(static_cast<uint32_t>(tmp) < caps.amp_caps.num_steps());
131
132    return static_cast<uint8_t>(tmp);
133}
134
135zx_status_t RealtekStream::RunCmdLocked(const Command& cmd) {
136    fbl::unique_ptr<PendingCommand> pending_cmd;
137    bool want_response = (cmd.thunk != nullptr);
138
139    if (want_response) {
140        pending_cmd = PendingCommandAllocator::New(cmd);
141        if (pending_cmd == nullptr)
142            return ZX_ERR_NO_MEMORY;
143    }
144
145    zx_status_t res = SendCodecCommandLocked(cmd.nid,
146                                             cmd.verb,
147                                             want_response ? Ack::YES : Ack::NO);
148    VERBOSE_LOG("SEND: nid %2hu verb 0x%05x%s\n", cmd.nid, cmd.verb.val, want_response ? "*" : "");
149
150    if ((res == ZX_OK) && want_response)
151        pending_cmds_.push_back(fbl::move(pending_cmd));
152
153    return res;
154}
155
156zx_status_t RealtekStream::RunCmdListLocked(const Command* list, size_t count, bool force_all) {
157    ZX_DEBUG_ASSERT(list);
158
159    zx_status_t total_res = ZX_OK;
160    for (size_t i = 0; i < count; ++i) {
161        zx_status_t res = RunCmdLocked(list[i]);
162
163        if (res != ZX_OK) {
164            if (!force_all)
165                return res;
166
167            if (total_res == ZX_OK)
168                total_res = res;
169        }
170    }
171
172    return total_res;
173}
174
175void RealtekStream::OnDeactivateLocked() {
176    plug_notify_targets_.clear();
177    DisableConverterLocked(true);
178}
179
180void RealtekStream::OnChannelDeactivateLocked(const dispatcher::Channel& channel) {
181    RemovePDNotificationTgtLocked(channel);
182}
183
184zx_status_t RealtekStream::OnDMAAssignedLocked() {
185    return UpdateSetupProgressLocked(DMA_ASSIGNMENT_COMPLETE);
186}
187
188zx_status_t RealtekStream::OnSolicitedResponseLocked(const CodecResponse& resp) {
189    if (pending_cmds_.is_empty()) {
190        LOG("Received solicited response (0x%08x), but no commands are pending!\n", resp.data);
191        return ZX_ERR_BAD_STATE;
192    }
193
194    auto pending_cmd = pending_cmds_.pop_front();
195    VERBOSE_LOG("RECV: nid %2hu verb 0x%05x --> 0x%08x\n",
196                pending_cmd->cmd().nid,
197                pending_cmd->cmd().verb.val,
198                resp.data);
199    return pending_cmd->Invoke(this, resp);
200}
201
202zx_status_t RealtekStream::OnUnsolicitedResponseLocked(const CodecResponse& resp) {
203    // TODO(johngro) : Which bit should we be using as the pin sense bit?  The
204    // Intel HDA spec only specifies what digital display pins are required to
205    // use; generally speaking unsolicited response payloads are supposed to be
206    // vendor specific.
207    //
208    // The only Realtek datasheets I have seen do not define which bit they will
209    // use.  Experimentally, it seems like Realtek codecs use bit 3 for the pin
210    // sense bit, so this is what we use for now.
211    bool plugged = resp.data & (1u << 3);
212
213    if (plug_state_ != plugged) {
214        // Update our internal state.
215        plug_state_ = plugged;
216        last_plug_time_ = zx_clock_get_monotonic();
217
218        // Inform anyone who has registered for notification.
219        ZX_DEBUG_ASSERT(pc_.async_plug_det);
220        if (!plug_notify_targets_.is_empty()) {
221            audio_proto::PlugDetectNotify notif;
222
223            notif.hdr.cmd = AUDIO_STREAM_PLUG_DETECT_NOTIFY;
224            notif.hdr.transaction_id = AUDIO_INVALID_TRANSACTION_ID;
225            notif.flags = static_cast<audio_pd_notify_flags_t>(
226                    (plug_state_ ? (uint32_t)AUDIO_PDNF_PLUGGED : 0) | AUDIO_PDNF_CAN_NOTIFY);
227            notif.plug_state_time = last_plug_time_;
228
229            for (auto iter = plug_notify_targets_.begin(); iter != plug_notify_targets_.end(); ) {
230                zx_status_t res;
231
232                ZX_DEBUG_ASSERT(iter->channel != nullptr);
233                res = iter->channel->Write(&notif, sizeof(notif));
234                if (res != ZX_OK) {
235                    // If we have failed to send the notification over our
236                    // client channel, something has gone fairly wrong.  Remove
237                    // the client from the notification list.
238                    plug_notify_targets_.erase(iter++);
239                } else {
240                    ++iter;
241                }
242            }
243        }
244    }
245
246    return ZX_OK;
247}
248
249zx_status_t RealtekStream::BeginChangeStreamFormatLocked(const audio_proto::StreamSetFmtReq& fmt) {
250    // Check the format arguments.
251    //
252    // Note: in the limited number of Realtek codecs I have seen so far, the
253    // channel count given by a converter's widget caps is *the* number of
254    // channels supported, not a maximum number of channels supported (as
255    // indicated by the Intel HDA specification).  One can configure the number
256    // of channels in the format specifier to be less than the number maximum
257    // number of channels supported  by the converter, but it will ignore you.
258    //
259    // For inputs, configuring a stereo input converter for mono will cause the
260    // converter to produce stereo frames anyway.  The controller side DMA
261    // engine also does not seem smart enough to discard the extra sample (even
262    // though it was configured for mono as well) and you will end up capturing
263    // data an twice the rate you expected.
264    //
265    // For output, configuring a stereo output converter for mono seems to have
266    // no real effect on its behavior.  It is still expecting stereo frames.
267    // When you configure the DMA engine for mono (as is the requirement given
268    // by Intel), the converter appears to be unhappy about the lack of samples
269    // in the frame and simply never produces any output.  The Converter Channel
270    // Count control (section 7.3.3.35 of the Intel HDA spec) also appears to
271    // have no effect.  This is not particularly surprising as it is supposed to
272    // only effect output converters, and only those with support for more than
273    // 2 channels, but I tried it anyway.
274    //
275    // Perhaps this is different for the 6xx series of codecs from Realtek (the
276    // 6 channel "surround sound ready" codecs); so far I have only worked with
277    // samples from the 2xx series (the stereo codec family).  For now, however,
278    // insist that the format specified by the user exactly match the number of
279    // channels present in the converter we are using for this pipeline.
280    if (!fmt.channels || (fmt.channels != conv_.widget_caps.ch_count()))
281        return ZX_ERR_NOT_SUPPORTED;
282
283    if (!conv_.sample_caps.SupportsRate(fmt.frames_per_second) ||
284        !conv_.sample_caps.SupportsFormat(fmt.sample_format))
285        return ZX_ERR_NOT_SUPPORTED;
286
287    // Looks good, make sure that the converter is muted and not processing any stream tags.
288    format_set_ = false;
289    return DisableConverterLocked();
290}
291
292zx_status_t RealtekStream::FinishChangeStreamFormatLocked(uint16_t encoded_fmt) {
293    zx_status_t res;
294    const Command ENABLE_CONVERTER_VERBS[] = {
295        { props_.conv_nid, SET_CONVERTER_FORMAT(encoded_fmt) },
296        { props_.conv_nid, SET_CONVERTER_STREAM_CHAN(dma_stream_tag(), 0) },
297        { props_.pc_nid,   SET_POWER_STATE(HDA_PS_D0) },
298        { props_.conv_nid, SET_POWER_STATE(HDA_PS_D0) },
299        { props_.pc_nid,   SET_ANALOG_PIN_WIDGET_CTRL(!is_input(), is_input(),
300                                                       pc_.pin_caps.can_drive_headphones() ) },
301    };
302
303    res = RunCmdListLocked(ENABLE_CONVERTER_VERBS, countof(ENABLE_CONVERTER_VERBS));
304    if (res != ZX_OK)
305        return res;
306
307    res = SendGainUpdatesLocked();
308    if (res != ZX_OK)
309        return res;
310
311    format_set_ = true;
312    return ZX_OK;
313}
314
315void RealtekStream::OnGetGainLocked(audio_proto::GetGainResp* out_resp) {
316    ZX_DEBUG_ASSERT(out_resp);
317
318    if (conv_.has_amp) {
319        out_resp->cur_gain  = ComputeCurrentGainLocked();
320        out_resp->min_gain  = conv_.min_gain;
321        out_resp->max_gain  = conv_.max_gain;
322        out_resp->gain_step = conv_.gain_step;
323    } else {
324        out_resp->cur_gain  = 0.0;
325        out_resp->min_gain  = 0.0;
326        out_resp->max_gain  = 0.0;
327        out_resp->gain_step = 0.0;
328    }
329
330    out_resp->cur_mute = cur_mute_;
331    out_resp->can_mute = can_mute();
332}
333
334void RealtekStream::OnSetGainLocked(const audio_proto::SetGainReq& req,
335                                    audio_proto::SetGainResp* out_resp) {
336    zx_status_t res  = ZX_OK;
337    bool mute_target = cur_mute_;
338    bool set_mute    = req.flags & AUDIO_SGF_MUTE_VALID;
339    bool set_gain    = req.flags & AUDIO_SGF_GAIN_VALID;
340
341    if (set_mute || set_gain) {
342        if (set_mute) {
343            if (!can_mute()) {
344                res = ZX_ERR_INVALID_ARGS;
345            } else {
346                mute_target = req.flags & AUDIO_SGF_MUTE;
347            }
348        }
349
350        if ((res == ZX_OK) && set_gain)
351            res = UpdateConverterGainLocked(req.gain);
352    }
353
354    if (res == ZX_OK) {
355        cur_mute_ = mute_target;
356
357        // Don't bother sending any update to the converter if the format is not currently set.
358        if (format_set_)
359            res = SendGainUpdatesLocked();
360    }
361
362    if (out_resp != nullptr) {
363        out_resp->result    = res;
364        out_resp->cur_mute  = cur_mute_;
365        out_resp->cur_gain  = ComputeCurrentGainLocked();
366    }
367}
368
369void RealtekStream::OnPlugDetectLocked(dispatcher::Channel* response_channel,
370                                       const audio_proto::PlugDetectReq& req,
371                                       audio_proto::PlugDetectResp* out_resp) {
372    ZX_DEBUG_ASSERT(response_channel != nullptr);
373
374    // If our pin cannot perform presence detection, just fall back on the base class impl.
375    if (!pc_.pin_caps.can_pres_detect()) {
376        IntelHDAStreamBase::OnPlugDetectLocked(response_channel, req, out_resp);
377        return;
378    }
379
380    if (pc_.async_plug_det) {
381        // If we are capible of asynch plug detection, add or remove this client
382        // to/from the notify list before reporting the current state.  Apps
383        // should not be setting both flags, but if they do, disable wins.
384        if (req.flags & AUDIO_PDF_DISABLE_NOTIFICATIONS) {
385            RemovePDNotificationTgtLocked(*response_channel);
386        } else if (req.flags & AUDIO_PDF_ENABLE_NOTIFICATIONS) {
387            AddPDNotificationTgtLocked(response_channel);
388        }
389
390        // Report the current plug detection state if the client expects a response.
391        if (out_resp) {
392            out_resp->flags  = static_cast<audio_pd_notify_flags_t>(
393                               (plug_state_ ? (uint32_t)AUDIO_PDNF_PLUGGED : 0) |
394                               (pc_.async_plug_det ? (uint32_t)AUDIO_PDNF_CAN_NOTIFY : 0));
395            out_resp->plug_state_time = last_plug_time_;
396        }
397    } else {
398        // TODO(johngro): In order to do proper polling support, we need to add
399        // the concept of a pending client request to the system.  IOW - we need
400        // to create and run a state machine where we hold a reference to the
401        // client's response channel, and eventually respond to the client using
402        // the same transaction ID they requested state with.
403        //
404        // For now, if our hardware does not support async plug detect, we
405        // simply fall back on the default implementation which reports that we
406        // are hardwired and always plugged in.
407        IntelHDAStreamBase::OnPlugDetectLocked(response_channel, req, out_resp);
408        return;
409    }
410}
411
412void RealtekStream::OnGetStringLocked(const audio_proto::GetStringReq& req,
413                                      audio_proto::GetStringResp* out_resp) {
414    ZX_DEBUG_ASSERT(out_resp);
415    const char* requested_string = nullptr;
416
417    switch (req.id) {
418        case AUDIO_STREAM_STR_ID_MANUFACTURER:
419            requested_string = props_.mfr_name;
420            break;
421
422        case AUDIO_STREAM_STR_ID_PRODUCT:
423            requested_string = props_.product_name;
424            break;
425
426        default:
427            IntelHDAStreamBase::OnGetStringLocked(req, out_resp);
428            return;
429    }
430
431    int res = snprintf(reinterpret_cast<char*>(out_resp->str), sizeof(out_resp->str), "%s",
432                       requested_string ? requested_string : "<unassigned>");
433    ZX_DEBUG_ASSERT(res >= 0);
434    out_resp->result = ZX_OK;
435    out_resp->strlen = fbl::min<uint32_t>(res, sizeof(out_resp->str) - 1);
436    out_resp->id = req.id;
437}
438
439zx_status_t RealtekStream::UpdateSetupProgressLocked(uint32_t stage) {
440    ZX_DEBUG_ASSERT(!(setup_progress_ & STREAM_PUBLISHED));
441    ZX_DEBUG_ASSERT(!(setup_progress_ & stage));
442
443    setup_progress_ |= stage;
444
445    if (setup_progress_ == ALL_SETUP_COMPLETE) {
446        zx_status_t res = FinalizeSetupLocked();
447        if (res != ZX_OK)
448            return res;
449
450        setup_progress_ |= STREAM_PUBLISHED;
451        DumpStreamPublishedLocked();
452        return PublishDeviceLocked();
453    }
454
455    return ZX_OK;
456}
457
458zx_status_t RealtekStream::FinalizeSetupLocked() {
459    // Stash the number of gain steps to use in the pin converter.  This allows
460    // us to hardcode gain targets for things like mic boost.  Eventually, we
461    // need to expose a way to detect this capability and control it via APIs,
462    // but for now we can get away with just setting it as part of the finalize
463    // step for setup.
464    cur_pc_gain_steps_ = ComputeGainSteps(pc_, props_.default_pc_gain);
465
466    // Compute the list of formats we support.
467    fbl::Vector<audio_proto::FormatRange> supported_formats;
468    zx_status_t res =  MakeFormatRangeList(conv_.sample_caps,
469                                           conv_.widget_caps.ch_count(),
470                                           &supported_formats);
471    if (res != ZX_OK) {
472        DEBUG_LOG("Failed to compute supported format ranges!  (res = %d)\n", res);
473        return res;
474    }
475
476    // At this point, we should have at least one sample encoding that we
477    // support.  If we don't, then this output stream is pretty worthless.
478    if (!supported_formats.size()) {
479        DEBUG_LOG("WARNING - no sample encodings are supported by this audio stream!  "
480                  "(formats = 0x%08x, size/rates = 0x%08x)\n",
481                  conv_.sample_caps.pcm_formats_,
482                  conv_.sample_caps.pcm_size_rate_);
483        return ZX_ERR_NOT_SUPPORTED;
484    }
485
486    // Go over the list of format ranges produced and tweak it to account for
487    // seemingly non-standard Realtek codec behavior.  Usually, when a converter
488    // says that it supports a maximum of N channels, you are supposed to be
489    // able to configure it for any number of channels in the set [1, N].  The
490    // Realtek codecs I have encountered so far, however, only support the
491    // number of channels they claim to support.  IOW - If the converter says
492    // that max_channels == 2, and you configure it for 1 channel, it will still
493    // produce 2 audio frames per frame period.
494    for (auto& format : supported_formats)
495        format.min_channels = format.max_channels;
496
497    SetSupportedFormatsLocked(fbl::move(supported_formats));
498
499    return ZX_OK;
500}
501
502void RealtekStream::DumpStreamPublishedLocked() {
503    if (!DEBUG_LOGGING)
504        return;
505
506    static const struct {
507        uint32_t flag;
508        uint32_t rate;
509    } RATE_LUT[] = {
510        { IHDA_PCM_RATE_384000, 384000 },
511        { IHDA_PCM_RATE_192000, 192000 },
512        { IHDA_PCM_RATE_176400, 176400 },
513        { IHDA_PCM_RATE_96000,  96000 },
514        { IHDA_PCM_RATE_88200,  88200 },
515        { IHDA_PCM_RATE_48000,  48000 },
516        { IHDA_PCM_RATE_44100,  44100 },
517        { IHDA_PCM_RATE_32000,  32000 },
518        { IHDA_PCM_RATE_22050,  22050 },
519        { IHDA_PCM_RATE_16000,  16000 },
520        { IHDA_PCM_RATE_11025,  11025 },
521        { IHDA_PCM_RATE_8000,   8000 },
522    };
523
524    static const struct {
525        uint32_t flag;
526        uint32_t bits;
527    } BITS_LUT[] = {
528        { IHDA_PCM_SIZE_32BITS, 32 },
529        { IHDA_PCM_SIZE_24BITS, 24 },
530        { IHDA_PCM_SIZE_20BITS, 20 },
531        { IHDA_PCM_SIZE_16BITS, 16 },
532        { IHDA_PCM_SIZE_8BITS,  8 },
533    };
534
535    LOG("Setup complete, publishing %s stream\n", props_.is_input ? "input" : "output");
536    LOG("Channels          : %u\n", conv_.widget_caps.ch_count());
537
538    LOG("Sample rates      :");
539    for (size_t i = 0; i < countof(RATE_LUT); ++i) {
540        const auto& entry = RATE_LUT[i];
541        if (conv_.sample_caps.pcm_size_rate_ & entry.flag)
542            printf(" %u", entry.rate);
543    }
544    printf("\n");
545
546    LOG("Sample bits       :");
547    for (size_t i = 0; i < countof(BITS_LUT); ++i) {
548        const auto& entry = BITS_LUT[i];
549        if (conv_.sample_caps.pcm_size_rate_ & entry.flag)
550            printf(" %u", entry.bits);
551    }
552    printf("\n");
553
554    DumpAmpCaps(conv_, "Conv");
555    DumpAmpCaps(pc_,   "PC");
556
557    if (pc_.pin_caps.can_pres_detect()) {
558        LOG("Plug Detect       : %s (current state %s)\n",
559            pc_.async_plug_det ? "Asynchronous" : "Poll-only",
560            plug_state_ ? "Plugged" : "Unplugged");
561    } else {
562        LOG("Plug Detect       : No\n");
563    }
564
565}
566
567void RealtekStream::DumpAmpCaps(const CommonCaps& caps, const char* tag) {
568    if (caps.has_amp) {
569        LOG("%4s Gain control : [%.2f, %.2f] dB in %.2f dB steps (%s mute).\n",
570            tag,
571            caps.min_gain,
572            caps.max_gain,
573            caps.gain_step,
574            caps.amp_caps.can_mute() ? "can" : "cannot");
575    } else {
576        LOG("%4s Gain control : 0dB fixed (cannot mute)\n", tag);
577    }
578}
579
580#define THUNK(_method) (&RealtekStream::_method)
581
582zx_status_t RealtekStream::OnActivateLocked() {
583    // Start by attempting to put our pin complex and converter into a disabled
584    // state.
585    zx_status_t res = DisableConverterLocked();
586    if (res != ZX_OK)
587        return res;
588
589    // Start the setup process by fetching the widget caps for our converter and
590    // pin complex.  This will let us know where various parameters (sample
591    // size/rate, stream format, amplifier caps, etc...) come from.  Also, go
592    // ahead and fetch the pin caps so we have an idea of our presence detection
593    // capabilities.
594    const Command SETUP[] = {
595        { props_.pc_nid,   GET_PARAM(CodecParam::AW_CAPS),  THUNK(ProcessPinWidgetCaps) },
596        { props_.pc_nid,   GET_CONFIG_DEFAULT,              THUNK(ProcessPinCfgDefaults) },
597        { props_.pc_nid,   GET_PARAM(CodecParam::PIN_CAPS), THUNK(ProcessPinCaps) },
598        { props_.conv_nid, GET_PARAM(CodecParam::AW_CAPS),  THUNK(ProcessConverterWidgetCaps) },
599    };
600
601    return RunCmdListLocked(SETUP, countof(SETUP));
602}
603
604zx_status_t RealtekStream::ProcessPinWidgetCaps(const Command& cmd, const CodecResponse& resp) {
605    // Stash the pin's audio-widget caps.  We will need it while processing the
606    // pin caps to determine if we need to register for async plug detection
607    // notifications before querying the initial pin state.
608    pc_.widget_caps.raw_data_ = resp.data;
609
610    // Does this pin complex have an amplifier?  If so, we need to query what
611    // it's caps, so we know what it's mute capabilities and unity gain are.  If
612    // not, we are done.
613    pc_.has_amp = is_input()
614                ? pc_.widget_caps.input_amp_present()
615                : pc_.widget_caps.output_amp_present();
616
617    if (!pc_.has_amp)
618        return UpdateSetupProgressLocked(PIN_COMPLEX_SETUP_COMPLETE);
619
620    return RunCmdLocked({ pc_.widget_caps.amp_param_override() ? props_.pc_nid : props_.afg_nid,
621                         GET_PARAM(AMP_CAPS(is_input())),
622                         THUNK(ProcessPinAmpCaps) });
623}
624
625zx_status_t RealtekStream::ProcessPinAmpCaps(const Command& cmd, const CodecResponse& resp) {
626    pc_.amp_caps.raw_data_ = resp.data;
627
628    pc_.gain_step = pc_.amp_caps.step_size_db();
629    pc_.min_gain  = pc_.amp_caps.min_gain_db();
630    pc_.max_gain  = pc_.amp_caps.max_gain_db();
631
632    return UpdateSetupProgressLocked(PIN_COMPLEX_SETUP_COMPLETE);
633}
634
635zx_status_t RealtekStream::ProcessPinCfgDefaults(const Command& cmd, const CodecResponse& resp) {
636    pc_.cfg_defaults.raw_data_ = resp.data;
637    return ZX_OK;
638}
639
640zx_status_t RealtekStream::ProcessPinCaps(const Command& cmd, const CodecResponse& resp) {
641    pc_.pin_caps.raw_data_ = resp.data;
642
643    // Sanity check out input/output configuration.
644    if ((is_input() ? pc_.pin_caps.can_input() : pc_.pin_caps.can_output()) == false) {
645        const char* tag = is_input() ? "input" : "output";
646        LOG("ERROR: Stream configured for %s, but pin complex cannot %s\n", tag, tag);
647        return ZX_ERR_BAD_STATE;
648    }
649
650    // Is the Jack Detect Override bit set in our config defaults?  If so,
651    // force-clear all of the bits in the pin caps which indicate an ability to
652    // perform presence detection and impedence sensing.  Even though hardware
653    // technically has the ability to perform presence detection, the
654    // BIOS/Device manufacturer is trying to tell us that presence detection
655    // circuitry has not been wired up, and that this stream is hardwired.
656    //
657    if (pc_.cfg_defaults.jack_detect_override()) {
658        static constexpr uint32_t mask = AW_PIN_CAPS_FLAG_CAN_IMPEDANCE_SENSE
659                                       | AW_PIN_CAPS_FLAG_TRIGGER_REQUIRED
660                                       | AW_PIN_CAPS_FLAG_CAN_PRESENCE_DETECT;
661        pc_.pin_caps.raw_data_ &= ~mask;
662    }
663
664    // Can this stream determine if it is connected or not?  If not, then we
665    // just assume that we are always plugged in.
666    if (!pc_.pin_caps.can_pres_detect() || pc_.pin_caps.trig_required()) {
667        if (pc_.pin_caps.trig_required()) {
668            LOG("WARNING : Triggered impedence sense plug detect not supported.  "
669                "Stream will always appear to be plugged in.\n");
670        }
671        return UpdateSetupProgressLocked(PLUG_STATE_SETUP_COMPLETE);
672    }
673
674    // Looks like we support presence detection.  Enable unsolicited
675    // notifications of pin state if supported, then query the initial pin
676    // state.
677    pc_.async_plug_det = pc_.widget_caps.can_send_unsol();
678    if (pc_.async_plug_det) {
679        zx_status_t res = AllocateUnsolTagLocked(&pc_.unsol_tag);
680        if (res == ZX_OK) {
681            zx_status_t res = RunCmdLocked({ props_.pc_nid,
682                                             SET_UNSOLICITED_RESP_CTRL(true, pc_.unsol_tag) });
683            if (res != ZX_OK)
684                return res;
685        } else {
686            LOG("WARNING : Failed to allocate unsolicited response tag from "
687                "codec pool (res %d).  Asynchronous plug detection will be "
688                "disabled.\n", res);
689            pc_.async_plug_det = false;
690        }
691    }
692
693    // Now that notifications have been enabled (or not), query the initial pin state.
694    return RunCmdLocked({ props_.pc_nid, GET_PIN_SENSE, THUNK(ProcessPinState) });
695}
696
697zx_status_t RealtekStream::ProcessPinState(const Command& cmd, const CodecResponse& resp) {
698    plug_state_ = PinSenseState(resp.data).presence_detect();
699    last_plug_time_ = zx_clock_get_monotonic();
700    return UpdateSetupProgressLocked(PLUG_STATE_SETUP_COMPLETE);
701}
702
703zx_status_t RealtekStream::ProcessConverterWidgetCaps(const Command& cmd,
704                                                      const CodecResponse& resp) {
705    zx_status_t res;
706
707    conv_.widget_caps.raw_data_ = resp.data;
708    conv_.has_amp = is_input() ? conv_.widget_caps.input_amp_present()
709                               : conv_.widget_caps.output_amp_present();
710
711    // Fetch the amp caps (if any) either from the converter or the defaults
712    // from the function group if the converter has not overridden them.
713    if (conv_.has_amp) {
714        uint16_t nid = conv_.widget_caps.amp_param_override() ? props_.conv_nid : props_.afg_nid;
715        res = RunCmdLocked({ nid,
716                             GET_PARAM(AMP_CAPS(is_input())),
717                             THUNK(ProcessConverterAmpCaps) });
718        if (res != ZX_OK)
719            return res;
720    }
721
722    // Fetch the supported sample rates, bit depth, and formats.
723    uint16_t nid = conv_.widget_caps.format_override() ? props_.conv_nid : props_.afg_nid;
724    const Command FETCH_FORMATS[] = {
725     { nid, GET_PARAM(CodecParam::SUPPORTED_PCM_SIZE_RATE), THUNK(ProcessConverterSampleSizeRate) },
726     { nid, GET_PARAM(CodecParam::SUPPORTED_STREAM_FORMATS), THUNK(ProcessConverterSampleFormats) },
727    };
728
729    res = RunCmdListLocked(FETCH_FORMATS, countof(FETCH_FORMATS));
730    if (res != ZX_OK)
731        return res;
732
733    return ZX_OK;
734}
735
736zx_status_t RealtekStream::ProcessConverterAmpCaps(const Command& cmd, const CodecResponse& resp) {
737    conv_.amp_caps.raw_data_ = resp.data;
738
739    conv_.gain_step = conv_.amp_caps.step_size_db();
740    conv_.min_gain  = conv_.amp_caps.min_gain_db();
741    conv_.max_gain  = conv_.amp_caps.max_gain_db();
742
743    return UpdateConverterGainLocked(fbl::max(props_.default_conv_gain, conv_.min_gain));
744}
745
746zx_status_t RealtekStream::ProcessConverterSampleSizeRate(const Command& cmd,
747                                                          const CodecResponse& resp) {
748    conv_.sample_caps.pcm_size_rate_ = resp.data;
749    return ZX_OK;
750}
751
752zx_status_t RealtekStream::ProcessConverterSampleFormats(const Command& cmd,
753                                                         const CodecResponse& resp) {
754    conv_.sample_caps.pcm_formats_ = resp.data;
755    return UpdateSetupProgressLocked(CONVERTER_SETUP_COMPLETE);
756}
757#undef THUNK
758
759}  // namespace codecs
760}  // namespace intel_hda
761}  // namespace audio
762