On Debugging Intel High Definition Audio in Linux and the Beats Audio Conspiracy Part I

A few months ago, while looking into some display issues with my HP Envy 15 laptop, I somehow become aware that the subwoofer was not active when I ran Linux. The sound worked perfectly well otherwise, but there was no bass and it was not nearly as loud as I remembered from Windows. I initially fiddled with the “model” parameter for the driver, as the Internet will always suggest, without any understanding of what it was doing. None of the configurations worked right for my laptop, so last week I decided to dive in and really understand what was wrong.

The task led me on an adventure  to places far more obscure than I might have ever expected to go, and proved over and over again that I am really, really dumb. Nonetheless, I succeeded in fixing my audio problems well-enough that I also understand why they are not fully fixed, and why that doesn’t matter.

Intel High Definition Audio

Modern laptops and sound cards implement a standard known as Intel High Definition Audio. This is really quite an elegant solution, as it is quite capable of supporting almost any sound configuration you could imagine, and means that we only need one driver to support any compliant sound card. That’s not how it really works, but that’s the idea, and it still provides a lot of commonality despite differences and bugs between vendors.

The standard is governed by a publicly available specification: Intel High Definition Audio Specification. When I first went through this spec, I learned almost nothing from it. It’s extremely generic and I am just slow to absorb new information. It’s main use at the level of debugging I want to do is as a reference guide for HDA Verbs.

HP Envy 15 Beats Audio

The HP Envy 15 comes with Beats-branded audio. This was not a selling point for me, but the general large improvement in audio over a typical laptop (on my previous laptop, I had trouble hearing it even at maximum volume) was. HP doesn’t put a lot of detail in their description of this feature, nor provide that many options in the Beats Audio control panel for Windows. So, I had little idea what it really was, other than the bass disappeared when I turned off the checkbox in Windows.

It turns out Beats Audio is essentially more speakers and a subwoofer, along with possible some hardware-assisted or software-only audio processing. I suspect the technology is purely branded, rather than developed in any way by Beats.

The HDA implementation is powered by an IDT 92HD91B1 SoC. Note: IDT purchased audio assets from Sigmatel, so you may see this name in places like the Linux Kernel, which were written prior to this acquisition.

The Problem

On Linux, only the front speakers outputted any sound, so it was quite adequate sounding for a laptop, but lacked the fullness and loudness of the audio I got when running Windows.

Envy 15 Speaker Configuration

Envy 15 Speaker Configuration

So, the task was fairly straightforward  enable the subwoofer and rear speakers. This was slightly complicated by my lack of awareness that these speakers all existed. In fact, I’m still not sure if these are all the speakers on the Envy 15. The Internet has told me it has 6.1 speakers, and one experiment I ran led me to believe I was hearing sound from two speakers on the bottom of the laptop. Empirical evidence has told me, however, that these are the only speakers.

Intel High Definition Audio Programming Interface

HDA provides a standard bus protocol that implement commands called verbs. One or more verb types may be transmitted to each node, represented by a node identifier (NID). Set verbs take a parameter. So, the protocol is dead-simple. Here’s an example:

0x13 0x71F 0x92

This command is decoded as:

NID = 0x13
Verb = 0x71F (Set Configuration Default bits [31:24])
Payload = 0x92 (Port connectivity = Fixed function; Location = Front)

This command programs the pin with NID=0x13 to be a Front speaker. The operating system uses this configuration to properly configure your audio devices. Verbs and their payload and response format can be found in the Intel HDA specification.

The trickier part is determining what NID has what purpose.

It did not occur to me to search for my chip on the web until I was about a week into this project. I’m used to public specs rarely being available for consumer product devices, so I just assumed it would not. To my surprise, when I finally did search, I found a data sheet over 300 pages long! Hopefully the IDT Confidential marking is an oversight, rather than the availability of the datasheet.

The key resource in this document was the block diagram of my chip.

IDT 92HD91B1 Block Diagram

IDT 92HD91B1 Block Diagram

This widget diagram, along with the block diagram, spells out exactly how the HDA interface relates to the chip.

HDA defines a variety of widget types identified by a NID. For debugging purposes, the most important widgets are Pin Widgets. These correspond to audio sinks and sources such as speakers, headphone jacks, line-out, line-in and microphone jacks.

Widgets are interconnected to ultimately stream audio in and out of the chip. As an example, Port D (NID 0xD) on my chip happens to be connected to the front Speakers on my laptop. The only certain way to determine this is from the OEM or experimentation. While not all Pins are suitable for output, the ones that are can be freely connected by the OEM. The chip has a default configured programmed in it, but it certainly will not match the OEM configuration. Later, I’ll discuss how Linux determines this information automatically.

Port D is a Pin Widget that outputs analog audio data to two outputs: left and right. Pin Widgets can support up to two speakers. For surround sound, multiple Pin Widgets are required.

Port D has a Connection List, hard-coded by IDT. This indicates the possible sources of the analog audio data to the pin. The block diagram shows three possible inputs for Port D: DAC0, DAC1 and MixerOutVol. Following these nets, you can see that these correspond to NIDs 0x13, 0x14 and 0x1c respectively. Since there are multiple sources, it is possible to select a connection with the Connection Select Control verb. Both DAC0 and DAC1 are suitable sources for digital audio data from the OS. Having two sources simply means that we can stream multiple audio sources simultaneously (though only one may be connected to this output at a time).

Note that DAC0 and DAC1 are Audio Output Converter widgets. As such, they contain two particularly important features: volume and mute. These are controlled per channel using the Amplifier Gain/Mute verbs.  MixerOutVol is more complicated, though a look at the block diagram makes it fairly clear what it does. It can sum the output from up to six widgets. So, as an example, if you were streaming two different audio files on DAC0 and DAC1, if you connect Port D to MixerOutVol, you would hear both streams simultaneously from the front speakers.

There are various other specialized widgets for which I won’t go into detail but will point out for further study:

  1. Digital PC_BEEP – used for the equivalent of the PC Speaker
  2. Mono Mux -> Mono Mix -> Mono – single channel output suitable for something monaural  Any guesses on which NID the subwoofer is connected to?
  3. SPDIF – Digital output. Not covered in this article, but they are part of HDA too.

ACPI BIOS Configuration

How can an OS figure out the pin configuration? It’s not programmed into an EEPROM somewhere by the OEM. The chip defaults to what IDT programs in it, which will not correspond to the actual connections.

ACPI BIOSes can provide HDA pin configuration. This corresponds to the Configuration Default register data for each Pin Widget in the system.

This is why my Envy works perfectly when I run Linux. I get two channels of sound from the front speakers because that’s exactly what the HP BIOS told Linux it had.

Much later, I confirmed that Windows fares no better with its generic HDA driver. I get the same output and same configuration.

In Linux kernel parlance, this is called a buggy BIOS. It is lying about the actual hardware configuration, and thus makes the user think something is broken. Of course, you can see what HP might be thinking. Beats is a value-add and presumably requires royalties. So, on Windows you only get the full sound output when you install the IDT driver (distributed by HP). It must reconfigure the pins when you click “Beats Audio”. Linux users are just out of luck.

Linux HDA Drivers

The Linux HDA driver is quite robust. It knows what to do with pin configurations, but it is also filled with workaround after workaround for “quirks” in certain hardware. It turned out my hardware had no quirk defined and so speaker configuration was coming straight from ACPI.

Trying some other configurations, such as “hp-zephyr” (what is an HP Zephyr? I don’t think it exists, but perhaps it’s an internal code-name at HP?) enabled the subwoofer. However, it also disabled the front speakers.

This model override can be done with modprobe, and Ubuntu has a place already set up in /etc/modprobe.d/alsa-base.conf

Adding an option to snd-hda-intel model=<modelname> will force the driver to use a quirks mode instead of ACPI configuration.

Debugging HDA

I really wanted to debug the configuration directly to figure out this problem. I came upon the tool HDA-Analyzer, which is a fantastic tool, but I do not recommend it as the first tool you use. This tool completely skewed my limited understanding of HDA, and I simply deemed it impossible to do things like change the location of a Pin Widget because this isn’t possible in the tool.

Instead, I highly recommend the more obscure tool from the Linux Sound maintainer called hda-verb. From the discussion above, you can guess that it will let you send verbs directly to your chip (or rather codec, as a chip may contain more than one codec). As long as you’ve got a linux kernel headers installed, it should just build with make.

Now, you can execute commands directly using either the raw values or friendly named provided by hda-verb -l. So, for the example I provided above:

# hda-verb 0x13 0x71F 0x92

For verbs that do not take a payload, simply provide 0, as it will still require a third field.

You can look at the ACPI pin configuration via sysfs:

$ cat /sys/class/sound/hwC0D0/init_pin_config

Note that the exact device name may vary if you have more than one HDA device in your system. In my case hwC0D0 is present along with HwC0D3 for Intel Cougarpoint HDMI.

My pin configuration was as follows:

The extra speakers are connected to Pin Widget NID=0x0F and Pin Widget NID=0x10. So, an easy way to start is to simply copy the pin config from NID=0x0D, which was already working properly.

Reviewing the datasheet, you can see what this data means. 0x92XXX decodes to:

Port Connectivity = Fixed function (0x1)
Location = Internal (0x1), Front (0x2)
Device = Speaker (0x1)
Connection Type = Other analog (0x7)
Color = Unknown (0x0)
Misc = No Jack detect override (0x0)
Association = 0x1
Sequence = 0x0

To be thorough, you can change the location information so that hopefully Linux will ultimately determine correctly which speakers are which. To set NID 0xF, you use the Set Configuration Default commands. It is actually four commands – one for each byte of data.

# hda-verb 0x0f 0x71c 0x10
# hda-verb 0x0f 0x71d 0x0
# hda-verb 0x0f 0x71e 0x17
# hda-verb 0x0f 0x71f 0x91

In order for this to take effect, we need to reload the drivers, as well as the audio system (pulseaudio).

If you google it, you’ll find that you can kill pulseaudio simply by running pulseaudio -k. This does kill the process, but it will instantly restart, at least on Ubuntu 13.04. I struggled to figure out how this was happening, but while I still don’t know, I did figure out how to stop it. Pulseaudio provides a config file option to disable this. In /etc/pulse/client.conf you can set autospawn = no. Now just kill the process or run pulseaudio -k.

Next, you need to reload the drivers. This will seem impossible if you try to do it manually. All the drivers depend on one another and it seems there is no order you can unload them in, and if there were, it would take forever. Instead, ALSA provides a script to do this:

# alsa force-reload

You need to look carefully at the output of this command. It will list drivers not unloaded in parentheses on the first line, and then list the drivers that were reloaded. It is easy to skip over in the long list of modules if you’re not careful.

If pulseaudio is still running, this will fail. If you have anything accessing sound devices like HDA-Analyzer it will fail. Close whatever you can. In my experience, this commands always fails the first time no matter what. However, if I have followed the proper procedure and closed audio apps and pulseaudio, it generally works on the second attempt. If you can’t get it to work, you’ll have to reboot. This won’t help you in this experiment since these hda-verb commands above will not persist across a reboot.

If all went well, the new speakers are now enabled and you will hear audio from them. At this point, this is all you can really do. To debug further, you’ll need to use some additional tools. You can use some hda-verb queries to turn on and off the speakers, but this is tedious. Now it is a good time to try out HDA-Analyzer.

HDA-analyzer provides a GUI interface with a tree of all the widgets in your codecs. You can browse to widget 0x0F which you will see is labeled as an AUDIO_OUT, as you would expect. There is a checkbox “OUT” on the page which can be toggled. This will instantly turn on and off these speakers. If you notice a difference, you know that audio is actually being streamed out this widget. You can also play with a limited number of other items such as the connection list selection.

Digging Deeping into Linux

The procedure above is a good way to go about debugging problems. However, to make it permanent you really need to fix the driver. I’ll go into this process in the next article in this series.

This entry was posted in linux and tagged , , , . Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

5 Comments

  1. Fred
    Posted May 18, 2013 at 3:20 pm | Permalink

    Nice post …
    When you use hda-verb you need to set the dev device to.
    instead..
    # hda-verb 0x0f 0x71c 0×10
    # hda-verb 0x0f 0x71d 0×0
    # hda-verb 0x0f 0x71e 0×17
    # hda-verb 0x0f 0x71f 0×91

    this?
    # hda-verb /dev/snd/hwC0D0 0x0f 0x71c 0×10
    # hda-verb /dev/snd/hwC0D0 0x0f 0x71d 0×0
    # hda-verb /dev/snd/hwC0D0 0x0f 0x71e 0×17
    # hda-verb /dev/snd/hwC0D0 0x0f 0x71f 0×91

    And for some reason most of time after use the hda-ver the alsa freeze when try unload.

    Can you explain better the Port Connectivity[1:0] and Location[5:0] ? i’m not an engine and the concept of “bin” used fly out me.. 😉

    Maybe if help you..
    If you download yout hp audio driver ( SPxxx).exe file and extrat. Inside the wdm folder you have a lot of .ini files specific for your model of hardware. Inside at [hkr] is possible to find out the model basic parameters for audio card.. Example mine one ( envy 17 3d 2100 cto..0

    [HKR]
    CodecName = “92HD81″
    [HKR\PowerSettings]
    ConservationIdleTime = hex: 0
    IdlePowerState = hex: 0
    PerformanceIdleTime = hex: 0
    [HKR\Settings]
    InitVerbs = hex: 0x16, 0xF4, 0x17, 0x00
    TraceFlags = dword: 0x89B3
    PmEnabledWidget = hex: 0D,0B
    CPL_SaveExtraPower = hex: 1
    CPL_SecondsAfter = dword: 10
    PlaybackCaptureAssociations = hex: 31
    KeepPcBeepAlwaysOn = hex:0
    DelayInMsAfterPortDisabling = dword: 0x00
    DelayAfterAdcStartMs = dword: 0x300
    EnableDevicePresenceAutoSearch = hex: 0
    DisablePcBeepInS3 = hex: 1
    BtlPort = hex: 0xD
    NoJackInfoForStereoMix = hex: 0
    [HKR\Settings\GPIO]
    00 = hex: 0xc2
    [HKR\Settings\pin]
    [HKR\Settings\PinA]
    CfgDflt = dword: 0x03A12030
    Bias = hex: FF,04,02
    [HKR\Settings\PinB]
    CfgDflt = dword: 0x03212020
    CfgCurr = dword: 0x0321201F
    AltCfg = hex: 20,20,21,03,1F,20,21,03
    [HKR\Settings\PinC]
    CfgDflt = dword: 0x40F000F0
    [HKR\Settings\PinD]
    CfgDflt = dword: 0x92170110
    [HKR\Settings\PinE]
    CfgDflt = dword: 0x40F000F0
    [HKR\Settings\PinF]
    CfgDflt = dword: 0x40F000F0
    [HKR\Settings\Pin\10]
    CfgDflt = dword: 0x96170110
    [HKR\Settings\Pin\11]
    CfgDflt = dword: 0xD5A30140
    Mic0Type = dword: 0
    Mic1Type = dword: 0
    Mic0XCoord = dword: 0
    Mic1XCoord = dword: 0
    Mic0YCoord = dword: 0xffffffab
    Mic1YCoord = dword: 0x00000055
    Mic0ZCoord = dword: 0
    Mic1ZCoord = dword: 0
    Mic0VerticalAngle = dword: 0
    Mic1VerticalAngle = dword: 0
    Mic0HorizontalAngle = dword: 0x00000000
    Mic1HorizontalAngle = dword: 0x00000000
    [HKR\Settings\Pin\1F]
    CfgDflt = dword: 0x40F000F0
    [HKR\Settings\Pin\20]
    CfgDflt = dword: 0x40F000F0
    [HKR\Settings\Connselector]
    [HKR\Settings\filter\LineOut]
    UniqueID = hex: 0
    Associations = hex: 1
    Mixer = hex: 0x1B
    PCBeepVolume = dword: 0xfff59026
    ReportInfoForInternalDevPinsToo = hex: 1
    [HKR\Settings\filter\DockHpOut]
    UniqueID = hex: 1
    Associations = hex: 2
    EnableOnAltCfgDevPresence = hex: 0
    [HKR\settings\filter\MicIn]
    UniqueID = hex: 2
    EnableCompositeMuxAndAdcCtrl = hex: 0
    Associations = hex: 4
    MicArrayBoost = hex: 1
    MicArrayBoostLevel = dword: 0x000A0000
    MicArrayRecVolume = dword: 0x00111b84
    MicArrayVersion = dword: 0x100
    MicArrayType = dword: 0
    MicArrayVerticalAngleBegin = dword: 0
    MicArrayVerticalAngleEnd = dword: 0
    MicArrayHorizontalAngleBegin = dword: 0x00002054
    MicArrayHorizontalAngleEnd = dword: 0x00002054
    MicArrayFrequencyBandLo = dword: 0x14
    MicArrayFrequencyBandHi = dword: 0x4E20
    EnabledPcmCaptureBitsAndRates = dword: 0x0A0060
    [HKR\Settings\filter\MicIn\MaxLvlLimiters]
    MicArrayBoostLevel = dword: 0x00140000
    [HKR\settings\filter\MuxedIn1]
    UniqueID = hex: 3
    EnableCompositeMuxAndAdcCtrl = hex: 0
    Associations = hex: 3
    MicBoostLevel = dword: 0x00000000
    MicInRecVolume = dword: 0x00111b84
    PlaybackCaptureVolume = dword: 0x00038CCC
    [HKR\Settings\filter\MuxedIn1\MaxLvlLimiters]
    MicBoostLevel = dword: 0x00140000
    PlaybackCapture = dword: 0x60000
    [HKR\Settings\filter\MuxedIn1\TopoNames]
    MicIn = “{22B47F55-476C-4a44-9811-CFD743125BC2}”

    Maybe this help to…

    • Posted June 6, 2013 at 4:57 pm | Permalink

      Sorry I took forever to approve this comment, I guess it got lost in my inbox! Thanks for pointing out the error in the article. I’ve updated the hda-verb commands to specify a device name.

      The port connectivity and location are just bitfields in this command byte. You can refer to the Intel HDA spec section 7.3.3.31. The verb 0x71f sets bits [31:24] of the 4-byte configuration default register. So, when we set it to 0x92, we map that to bits [31:24]. Bits [31:30] are port connectivity and [29:24] are location. So, that means bits the lower 6 bits of are location: 0x92 & 0x3F == 0x12. Location is once again a bitmap, as specified in Table 110, so bits [5:4] of 0x12 == 01b for ‘internal’ and bits [3:0] are 0x2, for ‘Front’.

      That’s a good point about the Windows drivers. I was going to look and see if I could extract anything from it, but I never had a chance. I did fiddle with the HD Audio Tool from Microsoft which will display the current pin configuration for your driver, but only for the standard mode driver.

  2. dark
    Posted July 18, 2013 at 4:32 pm | Permalink

    I’ve been waiting for the Part 2.

    • Posted July 18, 2013 at 7:43 pm | Permalink

      Yeah, that would be good, wouldn’t it? I don’t have a great record of completing part IIs. That said, I went far enough with this to be able to write a part two, so there’s no excuse not to write it. I’ll see what I can do :)

  3. Mark
    Posted May 3, 2014 at 12:34 am | Permalink

    I know this article is a year old and probably no longer maintained but I am struggling with Beats Audio on Fedora 20. I saw your article and thought it was very thorough but was hoping to be able to read part 2 for a real solution. I have used hda-jack-retask and the instructions at http://www.linux.org/threads/beats-audio-on-linux.4443/ to successfully get most of the audio functionality out of my Envy 17.

    However, I’m having a problem with my headphone jack. Plugging in headphones doesn’t trigger the speakers to disable and also don’t receive all of the audio channels, particularly the one containing dialog, which is very annoying.

    If you’re still considering publishing part 2 maybe it would help me and some other people out. Otherwise, have you looked at hda-jack-retask? It works for the most part, just not properly with headphones.

    –Mark

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>