mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/xiang/erofs.git
synced 2026-10-31 14:23:49 +01:00
On ACE2+ platforms the link DMA stream allocator must avoid two hardware errata in mlink-capable systems: - Concurrent (cross-direction) hazard: when SoundWire shares a physical link DMA stream index with HDaudio, iDisp or UAOL across the two directions, the LLP and timestamp values for the affected stream are wrong. SSP and DMIC are not affected because every DMA request from those links carries one sample block. - Sequential (playback only) hazard: once a HDaudio or iDisp link has used a playback stream index, that index cannot drive any non HDA/iDisp link in the same direction until the next controller reset (CRST#). Track the active link type per direction in two masks (one for SoundWire, one for HDA/iDisp/UAOL) and the persistent set of playback stream indices touched by HDA/iDisp in a third mask. The link DMA allocator skips streams that would violate either rule. Streams are released from the active masks when the stream is released; all masks are cleared in hda_dsp_ctrl_init_chip() because the CRST# performed there clears the hardware state as well. A new helper hda_bus_ml_link_get_type() returns the link type from the existing extended link descriptor so the SOF allocator can tell SoundWire, HDA/iDisp and UAOL apart without duplicating the parsing. The implementation is generic. On platforms older than ACE2 every link is reported as HDA, only the sequential mask is ever set and it has no effect because no other link types are present, so behavior is unchanged. Signed-off-by: Peter Ujfalusi <[email protected]> Reviewed-by: Kai Vehmanen <[email protected]> Reviewed-by: Bard Liao <[email protected]> Reviewed-by: Liam Girdwood <[email protected]> Link: https://patch.msgid.link/[email protected] Signed-off-by: Mark Brown <[email protected]>
358 lines
9.9 KiB
C
358 lines
9.9 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
|
|
//
|
|
// Authors: Liam Girdwood <[email protected]>
|
|
// Ranjani Sridharan <[email protected]>
|
|
// Rander Wang <[email protected]>
|
|
// Keyon Jie <[email protected]>
|
|
//
|
|
|
|
/*
|
|
* Hardware interface for generic Intel audio DSP HDA IP
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <sound/hdaudio_ext.h>
|
|
#include <sound/hda_register.h>
|
|
#include <sound/hda_component.h>
|
|
#include <sound/hda-mlink.h>
|
|
#include "../ops.h"
|
|
#include "hda.h"
|
|
|
|
/*
|
|
* HDA Operations.
|
|
*/
|
|
|
|
int hda_dsp_ctrl_link_reset(struct snd_sof_dev *sdev, bool reset)
|
|
{
|
|
unsigned long timeout;
|
|
u32 gctl = 0;
|
|
u32 val;
|
|
|
|
/* 0 to enter reset and 1 to exit reset */
|
|
val = reset ? 0 : SOF_HDA_GCTL_RESET;
|
|
|
|
/* enter/exit HDA controller reset */
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL,
|
|
SOF_HDA_GCTL_RESET, val);
|
|
|
|
/* wait to enter/exit reset */
|
|
timeout = jiffies + msecs_to_jiffies(HDA_DSP_CTRL_RESET_TIMEOUT);
|
|
while (time_before(jiffies, timeout)) {
|
|
gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);
|
|
if ((gctl & SOF_HDA_GCTL_RESET) == val)
|
|
return 0;
|
|
usleep_range(500, 1000);
|
|
}
|
|
|
|
/* enter/exit reset failed */
|
|
dev_err(sdev->dev, "error: failed to %s HDA controller gctl 0x%x\n",
|
|
reset ? "reset" : "ready", gctl);
|
|
return -EIO;
|
|
}
|
|
|
|
int hda_dsp_ctrl_get_caps(struct snd_sof_dev *sdev)
|
|
{
|
|
struct hdac_bus *bus = sof_to_bus(sdev);
|
|
u32 cap, offset, feature;
|
|
int count = 0;
|
|
int ret;
|
|
|
|
/*
|
|
* On some devices, one reset cycle is necessary before reading
|
|
* capabilities
|
|
*/
|
|
ret = hda_dsp_ctrl_link_reset(sdev, true);
|
|
if (ret < 0)
|
|
return ret;
|
|
ret = hda_dsp_ctrl_link_reset(sdev, false);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
offset = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_LLCH);
|
|
|
|
do {
|
|
dev_dbg(sdev->dev, "checking for capabilities at offset 0x%x\n",
|
|
offset & SOF_HDA_CAP_NEXT_MASK);
|
|
|
|
cap = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, offset);
|
|
|
|
if (cap == -1) {
|
|
dev_dbg(bus->dev, "Invalid capability reg read\n");
|
|
break;
|
|
}
|
|
|
|
feature = (cap & SOF_HDA_CAP_ID_MASK) >> SOF_HDA_CAP_ID_OFF;
|
|
|
|
switch (feature) {
|
|
case SOF_HDA_PP_CAP_ID:
|
|
dev_dbg(sdev->dev, "found DSP capability at 0x%x\n",
|
|
offset);
|
|
bus->ppcap = bus->remap_addr + offset;
|
|
sdev->bar[HDA_DSP_PP_BAR] = bus->ppcap;
|
|
break;
|
|
case SOF_HDA_SPIB_CAP_ID:
|
|
dev_dbg(sdev->dev, "found SPIB capability at 0x%x\n",
|
|
offset);
|
|
bus->spbcap = bus->remap_addr + offset;
|
|
sdev->bar[HDA_DSP_SPIB_BAR] = bus->spbcap;
|
|
break;
|
|
case SOF_HDA_DRSM_CAP_ID:
|
|
dev_dbg(sdev->dev, "found DRSM capability at 0x%x\n",
|
|
offset);
|
|
bus->drsmcap = bus->remap_addr + offset;
|
|
sdev->bar[HDA_DSP_DRSM_BAR] = bus->drsmcap;
|
|
break;
|
|
case SOF_HDA_GTS_CAP_ID:
|
|
dev_dbg(sdev->dev, "found GTS capability at 0x%x\n",
|
|
offset);
|
|
bus->gtscap = bus->remap_addr + offset;
|
|
break;
|
|
case SOF_HDA_ML_CAP_ID:
|
|
dev_dbg(sdev->dev, "found ML capability at 0x%x\n",
|
|
offset);
|
|
bus->mlcap = bus->remap_addr + offset;
|
|
break;
|
|
default:
|
|
dev_dbg(sdev->dev, "found capability %d at 0x%x\n",
|
|
feature, offset);
|
|
break;
|
|
}
|
|
|
|
offset = cap & SOF_HDA_CAP_NEXT_MASK;
|
|
} while (count++ <= SOF_HDA_MAX_CAPS && offset);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS(hda_dsp_ctrl_get_caps, "SND_SOC_SOF_INTEL_HDA_COMMON");
|
|
|
|
void hda_dsp_ctrl_ppcap_enable(struct snd_sof_dev *sdev, bool enable)
|
|
{
|
|
u32 val = enable ? SOF_HDA_PPCTL_GPROCEN : 0;
|
|
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
|
|
SOF_HDA_PPCTL_GPROCEN, val);
|
|
}
|
|
EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_enable, "SND_SOC_SOF_INTEL_HDA_COMMON");
|
|
|
|
void hda_dsp_ctrl_ppcap_int_enable(struct snd_sof_dev *sdev, bool enable)
|
|
{
|
|
u32 val = enable ? SOF_HDA_PPCTL_PIE : 0;
|
|
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL,
|
|
SOF_HDA_PPCTL_PIE, val);
|
|
}
|
|
EXPORT_SYMBOL_NS(hda_dsp_ctrl_ppcap_int_enable, "SND_SOC_SOF_INTEL_HDA_COMMON");
|
|
|
|
void hda_dsp_ctrl_misc_clock_gating(struct snd_sof_dev *sdev, bool enable)
|
|
{
|
|
u32 val = enable ? PCI_CGCTL_MISCBDCGE_MASK : 0;
|
|
|
|
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_MISCBDCGE_MASK, val);
|
|
}
|
|
|
|
/*
|
|
* enable/disable audio dsp clock gating and power gating bits.
|
|
* This allows the HW to opportunistically power and clock gate
|
|
* the audio dsp when it is idle
|
|
*/
|
|
int hda_dsp_ctrl_clock_power_gating(struct snd_sof_dev *sdev, bool enable)
|
|
{
|
|
struct sof_intel_hda_dev *hda = sdev->pdata->hw_pdata;
|
|
u32 val;
|
|
|
|
/* enable/disable audio dsp clock gating */
|
|
val = enable ? PCI_CGCTL_ADSPDCGE : 0;
|
|
snd_sof_pci_update_bits(sdev, PCI_CGCTL, PCI_CGCTL_ADSPDCGE, val);
|
|
|
|
/* disable the DMI link when requested. But enable only if it wasn't disabled previously */
|
|
val = enable ? HDA_VS_INTEL_EM2_L1SEN : 0;
|
|
if (!enable || !hda->l1_disabled)
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, HDA_VS_INTEL_EM2,
|
|
HDA_VS_INTEL_EM2_L1SEN, val);
|
|
|
|
/* enable/disable audio dsp power gating */
|
|
val = enable ? 0 : PCI_PGCTL_ADSPPGD;
|
|
snd_sof_pci_update_bits(sdev, PCI_PGCTL, PCI_PGCTL_ADSPPGD, val);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_NS(hda_dsp_ctrl_clock_power_gating, "SND_SOC_SOF_INTEL_HDA_COMMON");
|
|
|
|
int hda_dsp_ctrl_init_chip(struct snd_sof_dev *sdev, bool detect_codec)
|
|
{
|
|
struct hdac_bus *bus = sof_to_bus(sdev);
|
|
struct sof_intel_hda_dev *sof_hda = bus_to_sof_hda(bus);
|
|
struct hdac_stream *stream;
|
|
int sd_offset, ret = 0;
|
|
u32 gctl;
|
|
|
|
if (bus->chip_init)
|
|
return 0;
|
|
|
|
/*
|
|
* The controller reset clears the ACE2+ link DMA stream allocation
|
|
* constraints; reset the masks to reflect this.
|
|
*/
|
|
memset(sof_hda->link_dma_active_sdw_mask, 0,
|
|
sizeof(sof_hda->link_dma_active_sdw_mask));
|
|
memset(sof_hda->link_dma_active_multi_mask, 0,
|
|
sizeof(sof_hda->link_dma_active_multi_mask));
|
|
sof_hda->link_dma_out_hda_used_mask = 0;
|
|
|
|
hda_codec_set_codec_wakeup(sdev, true);
|
|
|
|
hda_dsp_ctrl_misc_clock_gating(sdev, false);
|
|
|
|
/* clear WAKE_STS if not in reset */
|
|
gctl = snd_sof_dsp_read(sdev, HDA_DSP_HDA_BAR, SOF_HDA_GCTL);
|
|
if (gctl & SOF_HDA_GCTL_RESET)
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
|
|
SOF_HDA_WAKESTS, SOF_HDA_WAKESTS_INT_MASK);
|
|
|
|
/* reset HDA controller */
|
|
ret = hda_dsp_ctrl_link_reset(sdev, true);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed to reset HDA controller\n");
|
|
goto err;
|
|
}
|
|
|
|
usleep_range(500, 1000);
|
|
|
|
/* exit HDA controller reset */
|
|
ret = hda_dsp_ctrl_link_reset(sdev, false);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "error: failed to exit HDA controller reset\n");
|
|
goto err;
|
|
}
|
|
usleep_range(1000, 1200);
|
|
|
|
/* Accept unsolicited responses */
|
|
snd_hdac_chip_updatel(bus, GCTL, AZX_GCTL_UNSOL, AZX_GCTL_UNSOL);
|
|
|
|
/* Perform a one-time enumeration of the Multi-Link capability */
|
|
ret = hda_bus_ml_init(bus);
|
|
if (ret < 0) {
|
|
dev_err(sdev->dev, "%s: failed to enumerate multi-links\n",
|
|
__func__);
|
|
goto err;
|
|
}
|
|
|
|
if (detect_codec)
|
|
hda_codec_detect_mask(sdev);
|
|
|
|
/* clear stream status */
|
|
list_for_each_entry(stream, &bus->stream_list, list) {
|
|
sd_offset = SOF_STREAM_SD_OFFSET(stream);
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
|
|
sd_offset + SOF_HDA_ADSP_REG_SD_STS,
|
|
SOF_HDA_CL_DMA_SD_INT_MASK);
|
|
}
|
|
|
|
/* clear WAKESTS */
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
|
|
bus->codec_mask);
|
|
|
|
hda_codec_rirb_status_clear(sdev);
|
|
|
|
/* clear interrupt status register */
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
|
|
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
|
|
|
|
hda_codec_init_cmd_io(sdev);
|
|
|
|
/* enable CIE and GIE interrupts */
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
|
|
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
|
|
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN);
|
|
|
|
/* program the position buffer */
|
|
if (bus->use_posbuf && bus->posbuf.addr) {
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPLBASE,
|
|
(u32)bus->posbuf.addr);
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_ADSP_DPUBASE,
|
|
upper_32_bits(bus->posbuf.addr));
|
|
}
|
|
|
|
hda_bus_ml_reset_losidv(bus);
|
|
|
|
bus->chip_init = true;
|
|
|
|
err:
|
|
hda_dsp_ctrl_misc_clock_gating(sdev, true);
|
|
|
|
hda_codec_set_codec_wakeup(sdev, false);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_NS(hda_dsp_ctrl_init_chip, "SND_SOC_SOF_INTEL_HDA_COMMON");
|
|
|
|
void hda_dsp_ctrl_stop_chip(struct snd_sof_dev *sdev)
|
|
{
|
|
struct hdac_bus *bus = sof_to_bus(sdev);
|
|
struct hdac_stream *stream;
|
|
int sd_offset;
|
|
|
|
if (!bus->chip_init)
|
|
return;
|
|
|
|
/* disable interrupts in stream descriptor */
|
|
list_for_each_entry(stream, &bus->stream_list, list) {
|
|
sd_offset = SOF_STREAM_SD_OFFSET(stream);
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR,
|
|
sd_offset +
|
|
SOF_HDA_ADSP_REG_SD_CTL,
|
|
SOF_HDA_CL_DMA_SD_INT_MASK,
|
|
0);
|
|
}
|
|
|
|
/* disable SIE for all streams */
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
|
|
SOF_HDA_INT_ALL_STREAM, 0);
|
|
|
|
/* disable controller CIE and GIE */
|
|
snd_sof_dsp_update_bits(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTCTL,
|
|
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_GLOBAL_EN,
|
|
0);
|
|
|
|
/* clear stream status */
|
|
list_for_each_entry(stream, &bus->stream_list, list) {
|
|
sd_offset = SOF_STREAM_SD_OFFSET(stream);
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
|
|
sd_offset + SOF_HDA_ADSP_REG_SD_STS,
|
|
SOF_HDA_CL_DMA_SD_INT_MASK);
|
|
}
|
|
|
|
/* clear WAKESTS */
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_WAKESTS,
|
|
SOF_HDA_WAKESTS_INT_MASK);
|
|
|
|
hda_codec_rirb_status_clear(sdev);
|
|
|
|
/* clear interrupt status register */
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR, SOF_HDA_INTSTS,
|
|
SOF_HDA_INT_CTRL_EN | SOF_HDA_INT_ALL_STREAM);
|
|
|
|
hda_codec_stop_cmd_io(sdev);
|
|
|
|
/* disable position buffer */
|
|
if (bus->use_posbuf && bus->posbuf.addr) {
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
|
|
SOF_HDA_ADSP_DPLBASE, 0);
|
|
snd_sof_dsp_write(sdev, HDA_DSP_HDA_BAR,
|
|
SOF_HDA_ADSP_DPUBASE, 0);
|
|
}
|
|
|
|
bus->chip_init = false;
|
|
}
|
|
|
|
MODULE_LICENSE("Dual BSD/GPL");
|
|
MODULE_DESCRIPTION("SOF helpers for HDaudio platforms");
|
|
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
|
|
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC");
|
|
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_AUDIO_CODEC_I915");
|