// Copyright 2017 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 #include #include "debug-logging.h" #include "realtek-codec.h" #include "realtek-stream.h" namespace audio { namespace intel_hda { namespace codecs { static constexpr float DEFAULT_HEADPHONE_GAIN = 0.0; static constexpr float DEFAULT_SPEAKER_GAIN = 0.0; void RealtekCodec::PrintDebugPrefix() const { printf("RealtekCodec : "); } fbl::RefPtr RealtekCodec::Create() { return fbl::AdoptRef(new RealtekCodec); } zx_status_t RealtekCodec::Init(zx_device_t* codec_dev) { zx_status_t res = Bind(codec_dev, "realtek-codec"); if (res != ZX_OK) return res; res = Start(); if (res != ZX_OK) { Shutdown(); return res; } return ZX_OK; } zx_status_t RealtekCodec::Start() { zx_status_t res; // Fetch the implementation ID register from the main audio function group res = SendCodecCommand(1u, GET_IMPLEMENTATION_ID, false); if (res != ZX_OK) LOG("Failed to send get impl id command (res %d)\n", res); return res; } zx_status_t RealtekCodec::ProcessSolicitedResponse(const CodecResponse& resp) { if (!waiting_for_impl_id_) { LOG("Unexpected solicited codec response %08x\n", resp.data); return ZX_ERR_BAD_STATE; } waiting_for_impl_id_ = false; // TODO(johngro) : Don't base this setup behavior on exact matches in the // implementation ID register. We should move in the direction of // implementing a universal driver which depends mostly on codec VID/DID and // BIOS provided configuration hints to make the majority of configuration // decisions, and to rely on the impl ID as little as possible. // // At the very least, we should break this field down into its sub-fields // (mfr ID, board SKU, assembly ID) and match based on those. I'm willing // to bet that not all NUCs in the world are currently using the exact // same bits for this register. zx_status_t res; switch (resp.data) { // Intel NUC case 0x80862068: // Kaby Lake NUC Impl ID case 0x80862063: // Skylake NUC Impl ID res = SetupIntelNUC(); break; case 0x1025111e: res = SetupAcer12(); break; default: LOG("Unrecognized implementation ID %08x! No streams will be published.\n", resp.data); res = ZX_OK; break; } // TODO(johngro) : Begin the process of tearing down and cleaning up if setup fails return res; } zx_status_t RealtekCodec::SetupCommon() { // Common startup commands static const CommandListEntry START_CMDS[] = { // Start powering down the function group. { 1u, SET_POWER_STATE(HDA_PS_D3HOT) }, // Converters. Place all converters into D3HOT and mute/attenuate their outputs. // Output converters. { 2u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 2u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 3u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 3u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 6u, SET_POWER_STATE(HDA_PS_D3HOT) }, // Input converters. { 8u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 8u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 9u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 9u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, // Pin complexes. Place all complexes into powered down states. Disable all // inputs/outputs/external amps, etc... // DMIC input { 18u, SET_POWER_STATE(HDA_PS_D3HOT) }, // Input { 18u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // Class-D Power Amp output { 20u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 20u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 20u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, { 20u, SET_EAPD_BTL_ENABLE(0) }, // Mono output { 23u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 23u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 23u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // Undocumented input... { 24u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 24u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0), }, { 24u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // MIC2 input { 25u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 25u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0), }, { 25u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // LINE1 input { 26u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 26u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0), }, { 26u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // LINE2 in/out { 27u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 27u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0), }, { 27u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 27u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, { 27u, SET_EAPD_BTL_ENABLE(0) }, // PC Beep input { 29u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 29u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, // S/PDIF out { 30u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 30u, SET_DIGITAL_PIN_WIDGET_CTRL(false, false) }, // Headphone out { 33u, SET_POWER_STATE(HDA_PS_D3HOT) }, { 33u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 0), }, { 33u, SET_ANALOG_PIN_WIDGET_CTRL(false, false, false) }, { 33u, SET_EAPD_BTL_ENABLE(0) }, }; zx_status_t res = RunCommandList(START_CMDS, countof(START_CMDS)); if (res != ZX_OK) LOG("Failed to send common startup commands (res %d)\n", res); return res; } zx_status_t RealtekCodec::SetupAcer12() { zx_status_t res; DEBUG_LOG("Setting up for Acer12\n"); res = SetupCommon(); if (res != ZX_OK) return res; static const CommandListEntry START_CMDS[] = { // Set up the routing that we will use for the headphone output. { 13u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(false, 0, 0), }, // Mix NID 13, In-0 (nid 3) un-muted { 13u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 1, 0), }, // Mix NID 13, In-1 (nid 11) muted { 33u, SET_CONNECTION_SELECT_CONTROL(1u) }, // HP Pin source from ndx 0 (nid 13) // Set up the routing that we will use for the speaker output. { 12u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(false, 0, 0), }, // Mix NID 12, In-0 (nid 2) un-muted { 12u, SET_OUTPUT_AMPLIFIER_GAIN_MUTE(true, 1, 0), }, // Mix NID 12, In-1 (nid 11) muted // Set up the routing that we will use for the builtin mic { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 0), }, // Mix NID 35, In-0 (nid 24) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 1), }, // Mix NID 35, In-1 (nid 25) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 2), }, // Mix NID 35, In-2 (nid 26) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 3), }, // Mix NID 35, In-3 (nid 27) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 4), }, // Mix NID 35, In-4 (nid 29) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 5), }, // Mix NID 35, In-5 (nid 11) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0, 6), }, // Mix NID 35, In-6 (nid 18) unmute // Enable MIC2's input. Failure to keep this enabled causes the positive half of // the headphone output to be destroyed. // // TODO(johngro) : figure out why { 25u, SET_ANALOG_PIN_WIDGET_CTRL(false, true, false) }, // Power up the top level Audio Function group. { 1u, SET_POWER_STATE(HDA_PS_D0) }, }; res = RunCommandList(START_CMDS, countof(START_CMDS)); if (res != ZX_OK) { LOG("Failed to send startup command for Acer12 (res %d)\n", res); return res; } // Create and publish the streams we will use. static const StreamProperties STREAMS[] = { // Headphones { .stream_id = 1, .afg_nid = 1, .conv_nid = 3, .pc_nid = 33, .is_input = false, .default_conv_gain = DEFAULT_HEADPHONE_GAIN, .default_pc_gain = 0.0f, .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_HEADPHONE_JACK, .mfr_name = "Acer", .product_name = "Headphone Jack", }, // Speakers { .stream_id = 2, .afg_nid = 1, .conv_nid = 2, .pc_nid = 20, .is_input = false, .default_conv_gain = DEFAULT_SPEAKER_GAIN, .default_pc_gain = 0.0f, .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_SPEAKERS, .mfr_name = "Acer", .product_name = "Built-in Speakers", }, // Builtin Mic { .stream_id = 3, .afg_nid = 1, .conv_nid = 8, .pc_nid = 18, .is_input = true, .default_conv_gain = 0.0f, .default_pc_gain = 20.0f, .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_MICROPHONE, .mfr_name = "Acer", .product_name = "Built-in Microphone", }, }; res = CreateAndStartStreams(STREAMS, countof(STREAMS)); if (res != ZX_OK) { LOG("Failed to create and publish streams for Acer12 (res %d)\n", res); return res; } return ZX_OK; } zx_status_t RealtekCodec::SetupIntelNUC() { zx_status_t res; DEBUG_LOG("Setting up for Intel NUC\n"); res = SetupCommon(); if (res != ZX_OK) return res; static const CommandListEntry START_CMDS[] = { // Set up the routing that we will use for the headphone output. { 12u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0, 0), }, // Mix NID 12, In-0 (nid 2) unmute { 12u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 1), }, // Mix NID 12, In-1 (nid 11) mute { 33u, SET_CONNECTION_SELECT_CONTROL(0u) }, // HP Pin source from ndx 0 (nid 12) // Set up the routing that we will use for the headset input. { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 0), }, // Mix NID 35, In-0 (nid 24) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(false, 0, 1), }, // Mix NID 35, In-1 (nid 25) unmute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 2), }, // Mix NID 35, In-2 (nid 26) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 3), }, // Mix NID 35, In-3 (nid 27) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 4), }, // Mix NID 35, In-4 (nid 29) mute { 35u, SET_INPUT_AMPLIFIER_GAIN_MUTE(true, 0, 5), }, // Mix NID 35, In-5 (nid 11) mute // Enable MIC2's input. Failure to keep this enabled causes the positive half of // the headphone output to be destroyed. // // TODO(johngro) : figure out why { 25u, SET_ANALOG_PIN_WIDGET_CTRL(false, true, false) }, // Power up the top level Audio Function group. { 1u, SET_POWER_STATE(HDA_PS_D0) }, }; res = RunCommandList(START_CMDS, countof(START_CMDS)); if (res != ZX_OK) { LOG("Failed to send startup command for Intel NUC (res %d)\n", res); return res; } // Create and publish the streams we will use. static const StreamProperties STREAMS[] = { // Headphones { .stream_id = 1, .afg_nid = 1, .conv_nid = 2, .pc_nid = 33, .is_input = false, .default_conv_gain = DEFAULT_HEADPHONE_GAIN, .default_pc_gain = 0.0f, .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_HEADPHONE_JACK, .mfr_name = "Intel", .product_name = "Headphone Jack", }, // Headset Mic { .stream_id = 2, .afg_nid = 1, .conv_nid = 8, .pc_nid = 25, .is_input = true, .default_conv_gain = 0.0f, .default_pc_gain = 36.0f, .uid = AUDIO_STREAM_UNIQUE_ID_BUILTIN_HEADSET_JACK, .mfr_name = "Intel", .product_name = "Headset Jack", }, }; res = CreateAndStartStreams(STREAMS, countof(STREAMS)); if (res != ZX_OK) { LOG("Failed to create and publish streams for Intel NUC (res %d)\n", res); return res; } return ZX_OK; } zx_status_t RealtekCodec::RunCommandList(const CommandListEntry* cmds, size_t cmd_count) { zx_status_t res; if (cmds == nullptr) return ZX_ERR_INVALID_ARGS; for (size_t i = 0; i < cmd_count; ++i) { const auto& cmd = cmds[i]; VERBOSE_LOG("SEND: nid %2hu verb 0x%05x\n", cmd.nid, cmd.verb.val); res = SendCodecCommand(cmd.nid, cmd.verb, true); if (res != ZX_OK) { LOG("Failed to send codec command %zu/%zu (nid %hu verb 0x%05x) (res %d)\n", i + 1, cmd_count, cmd.nid, cmd.verb.val, res); return res; } } return ZX_OK; } zx_status_t RealtekCodec::CreateAndStartStreams(const StreamProperties* streams, size_t stream_cnt) { zx_status_t res; if (streams == nullptr) return ZX_ERR_INVALID_ARGS; for (size_t i = 0; i < stream_cnt; ++i) { const auto& stream_def = streams[i]; auto stream = fbl::AdoptRef(new RealtekStream(stream_def)); res = ActivateStream(stream); if (res != ZX_OK) { LOG("Failed to activate %s stream id #%u (res %d)!", stream_def.is_input ? "input" : "output", stream_def.stream_id, res); return res; } } return ZX_OK; } extern "C" zx_status_t realtek_ihda_codec_bind_hook(void* ctx, zx_device_t* codec_dev) { auto codec = RealtekCodec::Create(); ZX_DEBUG_ASSERT(codec != nullptr); // Init our codec. return codec->Init(codec_dev); } } // namespace codecs } // namespace audio } // namespace intel_hda