mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-04 17:44:14 +08:00
fd572393ba
If codec is in runtime suspend, but controller is not, hotplug events
are missed as the codec has no way to alert the controller. Problem does
not occur if both controller and codec are active, or when both are
suspended.
An easy way to reproduce is to play an audio stream on one codec (e.g.
to HDMI/DP display codec), wait for other HDA codec to go to runtime
suspend, and then plug in a headset to the suspended codec. The jack
event is not reported correctly in this case. Another way to reproduce
is to force controller to stay active with
"snd_sof_pci.sof_pci_debug=0x1"
Fix the issue by reconfiguring the WAKEEN register when powering up/down
individual links, and handling control events in the interrupt handler.
Fixes: 87fc20e4a0
("ASoC: SOF: Intel: hda: use hdac_ext fine-grained link management")
Reported-by: Hui Wang <hui.wang@canonical.com>
Signed-off-by: Kai Vehmanen <kai.vehmanen@linux.intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Ranjani Sridharan <ranjani.sridharan@linux.intel.com>
Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com>
Link: https://lore.kernel.org/r/20211105111655.668777-1-kai.vehmanen@linux.intel.com
Signed-off-by: Mark Brown <broonie@kernel.org>
94 lines
2.5 KiB
C
94 lines
2.5 KiB
C
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
|
//
|
|
// This file is provided under a dual BSD/GPLv2 license. When using or
|
|
// redistributing this file, you may do so under either license.
|
|
//
|
|
// Copyright(c) 2018 Intel Corporation. All rights reserved.
|
|
//
|
|
// Authors: Keyon Jie <yang.jie@linux.intel.com>
|
|
|
|
#include <linux/io.h>
|
|
#include <sound/hdaudio.h>
|
|
#include <sound/hda_i915.h>
|
|
#include <sound/hda_codec.h>
|
|
#include <sound/hda_register.h>
|
|
#include "../sof-priv.h"
|
|
#include "hda.h"
|
|
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_AUDIO_CODEC)
|
|
#include "../../codecs/hdac_hda.h"
|
|
#define sof_hda_ext_ops snd_soc_hdac_hda_get_ops()
|
|
#else
|
|
#define sof_hda_ext_ops NULL
|
|
#endif
|
|
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
|
|
static void update_codec_wake_enable(struct hdac_bus *bus, unsigned int addr, bool link_power)
|
|
{
|
|
unsigned int mask = snd_hdac_chip_readw(bus, WAKEEN);
|
|
|
|
if (link_power)
|
|
mask &= ~BIT(addr);
|
|
else
|
|
mask |= BIT(addr);
|
|
|
|
snd_hdac_chip_updatew(bus, WAKEEN, STATESTS_INT_MASK, mask);
|
|
}
|
|
|
|
static void sof_hda_bus_link_power(struct hdac_device *codec, bool enable)
|
|
{
|
|
struct hdac_bus *bus = codec->bus;
|
|
bool oldstate = test_bit(codec->addr, &bus->codec_powered);
|
|
|
|
snd_hdac_ext_bus_link_power(codec, enable);
|
|
|
|
if (enable == oldstate)
|
|
return;
|
|
|
|
/*
|
|
* Both codec driver and controller can hold references to
|
|
* display power. To avoid unnecessary power-up/down cycles,
|
|
* controller doesn't immediately release its reference.
|
|
*
|
|
* If the codec driver powers down the link, release
|
|
* the controller reference as well.
|
|
*/
|
|
if (codec->addr == HDA_IDISP_ADDR && !enable)
|
|
snd_hdac_display_power(bus, HDA_CODEC_IDX_CONTROLLER, false);
|
|
|
|
/* WAKEEN needs to be set for disabled links */
|
|
update_codec_wake_enable(bus, codec->addr, enable);
|
|
}
|
|
|
|
static const struct hdac_bus_ops bus_core_ops = {
|
|
.command = snd_hdac_bus_send_cmd,
|
|
.get_response = snd_hdac_bus_get_response,
|
|
.link_power = sof_hda_bus_link_power,
|
|
};
|
|
#endif
|
|
|
|
/*
|
|
* This can be used for both with/without hda link support.
|
|
*/
|
|
void sof_hda_bus_init(struct hdac_bus *bus, struct device *dev)
|
|
{
|
|
#if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA)
|
|
snd_hdac_ext_bus_init(bus, dev, &bus_core_ops, sof_hda_ext_ops);
|
|
#else /* CONFIG_SND_SOC_SOF_HDA */
|
|
memset(bus, 0, sizeof(*bus));
|
|
bus->dev = dev;
|
|
|
|
INIT_LIST_HEAD(&bus->stream_list);
|
|
|
|
bus->irq = -1;
|
|
|
|
/*
|
|
* There is only one HDA bus atm. keep the index as 0.
|
|
* Need to fix when there are more than one HDA bus.
|
|
*/
|
|
bus->idx = 0;
|
|
|
|
spin_lock_init(&bus->reg_lock);
|
|
#endif /* CONFIG_SND_SOC_SOF_HDA */
|
|
}
|