blob: b7daf1608a443fee8427183393ea966624aa10d3 [file] [log] [blame]
// 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 <fbl/auto_lock.h>
#include <dispatcher-pool/dispatcher-thread-pool.h>
#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> 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