thesofproject / sof

Sound Open Firmware
Other
528 stars 307 forks source link

[FEATURE] Add second playback PCM device for HDA hardware with different stream id #8834

Open perexg opened 7 months ago

perexg commented 7 months ago

For Realtek codecs, we need to split Speaker and Headphone (thus use both analog DAC outputs separately). The limitation (on some hardware) is that the speaker is usually connected to one DAC with a fixed rate (48000Hz) and the headphones to other (more rates up to 192kHz are available). Note that the fixed rate for speaker is defined mostly by hardware behind the codec (analog amplifiers) which may be preset by BIOS and Linux drivers are not able to touch them (missing docs/code) at the moment.

The generic driver may create an "Alt Analog" device like:

Card 0: PCH [HDA Intel PCH], device 0: ALC285 Analog [ALC285 Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 0: PCH [HDA Intel PCH], device 2: ALC285 Alt Analog [ALC285 Alt Analog]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Unfortunately, this feature is missing for the SOF HDA driver.

@mrhpearson : FYI

plbossart commented 7 months ago

@perexg The main reason why we have a single stream is that most codecs have INTERNAL switching between speakers and headphone. We would need to have information to detect and disable this switch, which is usually configured by obscure platform-specific verbs

We looked at this for @singalsu 's planned addition of endpoint-specific processing and we couldn't figure out how to add support for independent streams.

Also it's not clear if the 'Alt Analog' is actually used by any userspace apps?

Adding @kv2019i @ranj063 @ujfalusi for more comments.

perexg commented 7 months ago

Thank you for this information. I am actually evaluating a way to add support for 192kHz playback through the headphone DAC. With legacy driver, it works with indep_hp hint (and some patches to move the Alt PCM to assigned HP jack and standard PCM to assigned Speaker DAC). I assume that the second PCM will be activated through UCM when the 'Independent HP' control exists and can be set to 'Enabled' state.

Perhaps, there may be a different solution for this issue, but this one looked like a simplest one. The key is that the rate for DACs is shared thus fixed at the moment. Realtek sets this rate to 48kHz in BIOS, because the Linux driver is not able to handle this split properly.

plbossart commented 7 months ago

The problem with the 192kHz stuff is that it needs to be used as an 'exclusive' endpoint where the high-resolution content is played as is without mixers/processing on the host. That's how it's done on Windows. That removes the need to sample-rate convert all notifications/beeps, which is a real hassle. If there is processing/mixer on this high-resolution stream then there will be a power hit for 'regular' applications, and it's not possible to switch dynamically (same issue at 48/44.1kHz really).

perexg commented 7 months ago

Ok, can SOF driver just create second "legacy"/"direct" playback stream with different stream id on the HDA bus without DSP in the middle ? We can configure the codec DAC (assign the stream id) as we need on demand.

The resampling is handled in the sound server on linux for the desktop apps.

plbossart commented 7 months ago

For Intel platfoms, firmware is either not used (snd-hda-intel) or it controls all of the HDaudio streams. we don't have the ability to selectively use the DSP for specific streams and let the host bypass the DSP for other streams.

ranj063 commented 7 months ago

For Intel platfoms, firmware is either not used (snd-hda-intel) or it controls all of the HDaudio streams. we don't have the ability to selectively use the DSP for specific streams and let the host bypass the DSP for other streams.

@plbossart just thinking out loud, can chain DMA help in this case?

perexg commented 7 months ago

The rate list is set by topology ? I don't see limiting in the SOF HDA driver for the analog paths based on codec capabilities. The snd_hdac_query_supported_pcm is used only for hdmi.

I found it. snd_hda_codec_parse_pcms call in hdac_hda.c

plbossart commented 7 months ago

For Intel platfoms, firmware is either not used (snd-hda-intel) or it controls all of the HDaudio streams. we don't have the ability to selectively use the DSP for specific streams and let the host bypass the DSP for other streams.

@plbossart just thinking out loud, can chain DMA help in this case?

no, with the chain DMA the stream is still in de-coupled mode.

technically the HDaudio DMA can be programmed as coupled or de-coupled at any time, but it leads to another well-known resource management issue, specifically the DSP memory.

when a DMA is in coupled mode, the hardware uses the DSP memory as a FIFO. when a DMA is in de-coupled mode, the firmware assumes all the memory is available.

there is no mechanism to tell the firmware what is used by hardware, and since it can be decided at run-time, the firmware would need to write-off the worst-case memory use by the hardware. That's likely to impact product capabilities, so not going to happen.

We could assume that some of the streams are available for coupled-mode only, but we would need to special-case some streams, e.g. 1..4, and require additional changes in kernel and firmware. It's possible technically but it's just not going to happen because of other priorities and shepherding cats.

perexg commented 7 months ago

Ok, at the moment, I just need input if the alternate PCM stream is an option also for SOF or I should dig to find another way to handle the fixed rate constraint for some codec DACs. We can probably detach "unwanted" codec DACs from the active stream id (tag) on HDA codec/bus to not receive a stream with different rate to avoid a wrong output. The question is which stream id (tag) should be used in this situation. Probably an unused one.

EDIT: But in this case, we need to create a notification for the sound server that the rate list changed when speaker / headphone output is switched. We can use probably UCM conflicting device list.

plbossart commented 7 months ago

There's nothing specific about SOF that would prevent the use of an alternate stream. We have support for more DMAs than we really need. If you look at Chrome devices, they all have separate PCM devices for headphone and speakers so that's a well-known configuration.

The difficult point is detecting that this alternate path exists, and the connection with the topology, i.e. if we expose an alternate stream in the topology and it's not physically enabled in the codec then we have a problem at run-time.

Ideally we would need a 'platform' layer that would tell the topology which streams are supported or not, and disable the 'alternate path' somehow.

plbossart commented 7 months ago

I should also add that the direction of travel for SoundWire-based devices is also to have independent 'Functions' for jack and amplifiers, the mutually exclusive use of jack/amps is a thing of the past. Retrofitting HDaudio to use that model is a tough job though, there are so many variants and configurations that we can almost guarantee some devices will not have the right settings.

perexg commented 7 months ago

if we expose an alternate stream in the topology and it's not physically enabled in the codec then we have a problem at run-time.

I think that you're thinking about a more complex model. I think that it's possible to have the alternate stream which may be used only when the DAC is configured to consume samples from the HDA bus with the proper stream id (tag). When the alternate PCM cannot be used, the open syscall may return -EBUSY or -ENXIO error to tell user space that this PCM stream is not usable. Eventually, we can create a Jack status like for HDMI for this PCM using the ALSA control API. The code which is handling the codec configuration should be able to react on this in struct hda_pcm_stream -> ops.open callback (hda_generic.c).

When I'm looking to the ASoC / SOF code, "Alt Analog" PCM components are ready in the drivers, so it seems really like a missing topology PCM configuration thing. Could you provide signed topologies sof-hda-generic-*.tplg with Alt Analog Out for tests?

plbossart commented 7 months ago

@perexg I was talking about a limitation of the topology framework.

The machine driver and the topology need to be aligned, and the endpoint listed in a topology file MUST be matching a BackEnd dailink created by the machine driver. The other way around is fine, it's perfectly fine to have an extra BE dailink that's not used by the topology.

It's not practical to issue one topology for each possible configuration. We already have this problem with 2ch and 4ch DMIC, every time we have a new variation that's yet another topology that needs to be used. That's not sustainable.

perexg commented 7 months ago

I expect that BE and LE for the second HDA playback stream will be created together. The difference will be that the alternate PCM device is usable at runtime or open syscall returns an error. So, from the driver POV, all should be set. The dmic seems like another issue where you need to link physical inputs, but I expect that for HDA stream, it's just FIFO copy operation in DSP without any data altering.

So, the number of topologies will remain same, but this request is to add a second HDA playback stream there.

plbossart commented 7 months ago

I don't think it's viable to expose a PCM device that returns an error on open. That should be handled at a lower level and only expose the alternate PCM path if it's actually usable.

perexg commented 7 months ago

This logic does not match HDMI devices. They are not usable until something is connected, thus samples are ending in nowhere, too.

plbossart commented 7 months ago

HDMI is different, there is a Jack status that tells that the hardware is connected, and having 'no sound' v. having an error code is very different from a support perspective. Explaining why the error is normal because of low-level hardware issues isn't going to fly with most users, it'll be just very very confusing.

We could follow the same model as HDMI and always add the alternate path, but we would need a similar indicator that the path is functional. That's somewhat problematic though because userspace would need to be modified to check this jack status.

Guessing which paths can be used at the userspace level is a recipe for more support requests and hellish work-arounds in applications.

perexg commented 7 months ago

This path won't be functional until the 'Independent HP' control is turned on, thus some code must enable it (UCM will handle this and UCM will define the different route for HP when it's available). The old behaviour and functionality will stay even with alternate stream. The Jack detection for the alternate PCM may be an option, but actually the Independent HP control presence check is sufficient. BTW, 16k MIC stream is also alternate stream, isn't? Who tells apps to use it?

I do not propose to break the current systems - it would be just an extension which will be described through UCM devices like we do for SOF/HDA/dmic/HDMI...

plbossart commented 7 months ago

The DMIC 16kHz is a different case, it's ALWAYS available in the hardware. The IP does support two streams generated from the same input. So there's no need for discovery, it's always present.

I must be missing something on this "Independent HP" control.

Can we rely on a control set by userspace without any information on what the hardware configuration is? And would this binary control really work on all platforms? Or is the presence of the control already an indication that the second path is available.

I think there are two different steps, one is hardware discovery and the other is selection.

The hardware should expose whether or not the second path is possible userspace should decide whether to enable it or not.

Where does the "independent HP" control fit?

perexg commented 7 months ago

Or is the presence of the control already an indication that the second path is available.

Yes, look for alt_dac_nid handling in hda_generic.c . If this variable is set, the alternate PCM is created with Independent HP control. The path (stream id) is activated only when this control is set to Enabled. Because for SOF, there is no handshake between codec driver and the PCM driver, we can create alternate PCM even if the alt_dac_nid is not set. This may be optimized later (when you add a possibility to disable or select some topology blocks according the runtime driver configuration).

plbossart commented 7 months ago

Thanks @perexg for the pointers.

Do you actually have a platform where this "Independent HP" is exposed? I tried on my local devices and don't see it.

Or did you force the codec to expose it via a patch or sysfs?

perexg commented 7 months ago

Yes, create /lib/firmware/hda-test.patch with (you may need to correct codec IDs from /proc/asound/card?/codec#? file):

[codec]
0x10ec0289 0x10280926 0

[hint]
indep_hp = yes

Force generic driver on SOF and patch:

echo "options snd-intel-dspcfg dsp_driver=1" /etc/modprobe.d/alsa.conf
echo "options snd-hda-intel patch=hda-test.patch" >> /etc/modprobe.d/alsa.conf

Also, for tests to separate DAC <-> PCM path capabilities, apply this fix to kernel:

From 3733ca15bee3cb98e18cf4ee60fa583704513b30 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela <perex@perex.cz>
Date: Thu, 1 Feb 2024 16:47:33 +0100
Subject: [PATCH] ALSA: hda: keep alt DAC separate from multiout PCM for alt
 PCM

This change is required to add support for an independent stream for
headphone output.

Without:

  Node 0x02 [Audio Output] wcaps 0x41d: Stereo Amp-Out
    Control: name="Speaker Playback Volume", index=0, device=0
  Node 0x03 [Audio Output] wcaps 0x41d: Stereo Amp-Out
    Control: name="Headphone Playback Volume", index=0, device=0
    Device: name="ALC289 Analog", type="Audio", device=0
    Device: name="ALC289 Alt Analog", type="Audio", device=2

With:

  Node 0x02 [Audio Output] wcaps 0x41d: Stereo Amp-Out
    Control: name="Speaker Playback Volume", index=0, device=0
    Device: name="ALC289 Analog", type="Audio", device=0
  Node 0x03 [Audio Output] wcaps 0x41d: Stereo Amp-Out
    Control: name="Headphone Playback Volume", index=0, device=0
    Device: name="ALC289 Alt Analog", type="Audio", device=2

Config:

    autoconfig for ALC289: line_outs=1 (0x14/0x0/0x0/0x0/0x0) type:speaker
      speaker_outs=0 (0x0/0x0/0x0/0x0/0x0)
      hp_outs=1 (0x21/0x0/0x0/0x0/0x0)

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 sound/pci/hda/hda_generic.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c
index de2a3d08c73c..6a591ac4db3c 100644
--- a/sound/pci/hda/hda_generic.c
+++ b/sound/pci/hda/hda_generic.c
@@ -5687,6 +5687,7 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
        struct hda_gen_spec *spec = codec->spec;
        struct hda_pcm *info;
        bool have_multi_adcs;
+       hda_nid_t nid;

        if (spec->no_analog)
                goto skip_analog;
@@ -5700,10 +5701,15 @@ int snd_hda_gen_build_pcms(struct hda_codec *codec)
        spec->pcm_rec[0] = info;

        if (spec->multiout.num_dacs > 0) {
+               nid = spec->multiout.dac_nids[0];
+               /* alt dac nid may be assigned to alt pcm at runtime, use differrent dac */
+               if (spec->multiout.num_dacs == 1 && nid &&
+                   spec->alt_dac_nid == nid && spec->multiout.extra_out_nid[0])
+                       nid = spec->multiout.extra_out_nid[0];
                setup_pcm_stream(&info->stream[SNDRV_PCM_STREAM_PLAYBACK],
                                 &pcm_analog_playback,
                                 spec->stream_analog_playback,
-                                spec->multiout.dac_nids[0]);
+                                nid);
                info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max =
                        spec->multiout.max_channels;
                if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT &&
-- 
2.43.0

Testing:

amixer -c 0 cset name="Independent HP" Enabled speaker-test -D hw:0,0 -c 2 # Speakers speaker-test -D hw:0,2 -c 2 # HP out

Procfs dump (codec#0) for when both streams are active:

Node 0x02 [Audio Output] wcaps 0x41d: Stereo Amp-Out
  Control: name="Speaker Playback Volume", index=0, device=0
  Converter: stream=2, channel=0
Node 0x03 [Audio Output] wcaps 0x41d: Stereo Amp-Out
  Control: name="Headphone Playback Volume", index=0, device=0
  Converter: stream=1, channel=0
plbossart commented 7 months ago

Yes, create /lib/firmware/hda-test.patch with (you may need to correct codec IDs from /proc/asound/card?/codec#? file):

[codec]
0x10ec0289 0x10280926 0

[hint]
indep_hp = yes

ok, so I'll have to lower expectations here: no one in our team has the bandwidth to support such custom configurations....

It's difficult enough to deal with standard out-of-the-box proliferation of configurations, having configurations that are user-generated is one bridge too far. I have no objections if people in the community want to enable this capability but I don't think anyone on the SOF team will actively support this.

It's not that the feature is silly, just no time and other priorities. Thanks for your understanding @perexg.

perexg commented 7 months ago

I'd like to propose this code change for Legacy HDA:

From 52d43aafd9d7cec0bc8112760db4a45fa42a2cd9 Mon Sep 17 00:00:00 2001
From: Jaroslav Kysela <perex@perex.cz>
Date: Mon, 5 Feb 2024 11:58:41 +0100
Subject: [PATCH] ALSA: hda: realtek - enable independent headphone out for
 ALC269+

Activate possibility to separate the headphone output to allow the high rate use.

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
---
 sound/pci/hda/patch_realtek.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index 84963626ed75..7069b782aa7a 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -3245,6 +3245,7 @@ static int alc269_parse_auto_config(struct hda_codec *codec)
                break;
        }

+       spec->gen.indep_hp = 1;
        return alc_parse_auto_config(codec, alc269_ignore, ssids);
 }

-- 
2.43.0

In this way, the independent HP will be activated for wide range of Realtek hardware by default and SOF is affected, too.

I just created this bug to let know ahead that SOF will not support this configuration properly.