mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00

During the hibernate entry sequence, ACP registers will be reset to
default values and acp ip will be completely powered off including acp
SoundWire pads. During resume sequence, if acp SoundWire pad keeper enable
register is not restored along with pad pulldown control register value,
then SoundWire manager links won't be powered on correctly results in
peripheral register access failures and completely audio function is
broken.
Add code to store the acp SoundWire pad keeper enable register and acp pad
pulldown ctrl register values before entering into suspend state and
restore the register values during resume sequence based on condition check
for acp SoundWire pad keeper enable register for ACP6.3, ACP7.0 & ACP7.1
platforms.
Fixes: 4916283880
("ASoC: amd: ps: add callback functions for acp pci driver pm ops")
Signed-off-by: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
Link: https://patch.msgid.link/20250623084630.3100279-1-Vijendar.Mukunda@amd.com
Signed-off-by: Mark Brown <broonie@kernel.org>
493 lines
12 KiB
C
493 lines
12 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* AMD ACP PCI driver callback routines for ACP6.3, ACP7.0 & ACP7.1
|
|
* platforms.
|
|
*
|
|
* Copyright 2025 Advanced Micro Devices, Inc.
|
|
* Authors: Vijendar Mukunda <Vijendar.Mukunda@amd.com>
|
|
*/
|
|
|
|
#include <linux/bitops.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/export.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <sound/pcm_params.h>
|
|
|
|
#include "acp63.h"
|
|
|
|
static int acp63_power_on(void __iomem *acp_base)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl(acp_base + ACP_PGFSM_STATUS);
|
|
|
|
if (!val)
|
|
return val;
|
|
|
|
if ((val & ACP63_PGFSM_STATUS_MASK) != ACP63_POWER_ON_IN_PROGRESS)
|
|
writel(ACP63_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL);
|
|
|
|
return readl_poll_timeout(acp_base + ACP_PGFSM_STATUS, val, !val, DELAY_US, ACP63_TIMEOUT);
|
|
}
|
|
|
|
static int acp63_reset(void __iomem *acp_base)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
writel(1, acp_base + ACP_SOFT_RESET);
|
|
|
|
ret = readl_poll_timeout(acp_base + ACP_SOFT_RESET, val,
|
|
val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK,
|
|
DELAY_US, ACP63_TIMEOUT);
|
|
if (ret)
|
|
return ret;
|
|
|
|
writel(0, acp_base + ACP_SOFT_RESET);
|
|
|
|
return readl_poll_timeout(acp_base + ACP_SOFT_RESET, val, !val, DELAY_US, ACP63_TIMEOUT);
|
|
}
|
|
|
|
static void acp63_enable_interrupts(void __iomem *acp_base)
|
|
{
|
|
writel(1, acp_base + ACP_EXTERNAL_INTR_ENB);
|
|
writel(ACP_ERROR_IRQ, acp_base + ACP_EXTERNAL_INTR_CNTL);
|
|
}
|
|
|
|
static void acp63_disable_interrupts(void __iomem *acp_base)
|
|
{
|
|
writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base + ACP_EXTERNAL_INTR_STAT);
|
|
writel(0, acp_base + ACP_EXTERNAL_INTR_CNTL);
|
|
writel(0, acp_base + ACP_EXTERNAL_INTR_ENB);
|
|
}
|
|
|
|
static int acp63_init(void __iomem *acp_base, struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = acp63_power_on(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP power on failed\n");
|
|
return ret;
|
|
}
|
|
writel(0x01, acp_base + ACP_CONTROL);
|
|
ret = acp63_reset(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP reset failed\n");
|
|
return ret;
|
|
}
|
|
acp63_enable_interrupts(acp_base);
|
|
writel(0, acp_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
|
|
static int acp63_deinit(void __iomem *acp_base, struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
acp63_disable_interrupts(acp_base);
|
|
ret = acp63_reset(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP reset failed\n");
|
|
return ret;
|
|
}
|
|
writel(0, acp_base + ACP_CONTROL);
|
|
writel(1, acp_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
|
|
static void acp63_get_config(struct pci_dev *pci, struct acp63_dev_data *acp_data)
|
|
{
|
|
u32 config;
|
|
|
|
config = readl(acp_data->acp63_base + ACP_PIN_CONFIG);
|
|
dev_dbg(&pci->dev, "ACP config value: %d\n", config);
|
|
switch (config) {
|
|
case ACP_CONFIG_4:
|
|
case ACP_CONFIG_5:
|
|
case ACP_CONFIG_10:
|
|
case ACP_CONFIG_11:
|
|
acp_data->is_pdm_config = true;
|
|
break;
|
|
case ACP_CONFIG_2:
|
|
case ACP_CONFIG_3:
|
|
acp_data->is_sdw_config = true;
|
|
break;
|
|
case ACP_CONFIG_6:
|
|
case ACP_CONFIG_7:
|
|
case ACP_CONFIG_12:
|
|
case ACP_CONFIG_8:
|
|
case ACP_CONFIG_13:
|
|
case ACP_CONFIG_14:
|
|
acp_data->is_pdm_config = true;
|
|
acp_data->is_sdw_config = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bool check_acp_sdw_enable_status(struct acp63_dev_data *adata)
|
|
{
|
|
u32 sdw0_en, sdw1_en;
|
|
|
|
sdw0_en = readl(adata->acp63_base + ACP_SW0_EN);
|
|
sdw1_en = readl(adata->acp63_base + ACP_SW1_EN);
|
|
return (sdw0_en || sdw1_en);
|
|
}
|
|
|
|
static void handle_acp63_sdw_pme_event(struct acp63_dev_data *adata)
|
|
{
|
|
u32 val;
|
|
|
|
val = readl(adata->acp63_base + ACP_SW0_WAKE_EN);
|
|
if (val && adata->sdw->pdev[0])
|
|
pm_request_resume(&adata->sdw->pdev[0]->dev);
|
|
|
|
val = readl(adata->acp63_base + ACP_SW1_WAKE_EN);
|
|
if (val && adata->sdw->pdev[1])
|
|
pm_request_resume(&adata->sdw->pdev[1]->dev);
|
|
}
|
|
|
|
static int __maybe_unused snd_acp63_suspend(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
if (adata->is_sdw_dev) {
|
|
adata->acp_sw_pad_keeper_en = readl(adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
adata->acp_pad_pulldown_ctrl = readl(adata->acp63_base + ACP_PAD_PULLDOWN_CTRL);
|
|
adata->sdw_en_stat = check_acp_sdw_enable_status(adata);
|
|
if (adata->sdw_en_stat) {
|
|
writel(1, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
}
|
|
ret = acp_hw_deinit(adata, dev);
|
|
if (ret)
|
|
dev_err(dev, "ACP de-init failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __maybe_unused snd_acp63_runtime_resume(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
if (adata->sdw_en_stat) {
|
|
writel(0, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
ret = acp_hw_init(adata, dev);
|
|
if (ret) {
|
|
dev_err(dev, "ACP init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!adata->sdw_en_stat)
|
|
handle_acp63_sdw_pme_event(adata);
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused snd_acp63_resume(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
u32 acp_sw_pad_keeper_en;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
if (adata->sdw_en_stat) {
|
|
writel(0, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
|
|
ret = acp_hw_init(adata, dev);
|
|
if (ret)
|
|
dev_err(dev, "ACP init failed\n");
|
|
|
|
acp_sw_pad_keeper_en = readl(adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
dev_dbg(dev, "ACP_SW0_PAD_KEEPER_EN:0x%x\n", acp_sw_pad_keeper_en);
|
|
if (!acp_sw_pad_keeper_en) {
|
|
writel(adata->acp_sw_pad_keeper_en, adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
writel(adata->acp_pad_pulldown_ctrl, adata->acp63_base + ACP_PAD_PULLDOWN_CTRL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void acp63_sdw_dma_irq_thread(struct acp63_dev_data *adata)
|
|
{
|
|
struct sdw_dma_dev_data *sdw_data;
|
|
u32 stream_id;
|
|
|
|
sdw_data = dev_get_drvdata(&adata->sdw_dma_dev->dev);
|
|
|
|
for (stream_id = 0; stream_id < ACP63_SDW0_DMA_MAX_STREAMS; stream_id++) {
|
|
if (adata->acp63_sdw0_dma_intr_stat[stream_id]) {
|
|
if (sdw_data->acp63_sdw0_dma_stream[stream_id])
|
|
snd_pcm_period_elapsed(sdw_data->acp63_sdw0_dma_stream[stream_id]);
|
|
adata->acp63_sdw0_dma_intr_stat[stream_id] = 0;
|
|
}
|
|
}
|
|
for (stream_id = 0; stream_id < ACP63_SDW1_DMA_MAX_STREAMS; stream_id++) {
|
|
if (adata->acp63_sdw1_dma_intr_stat[stream_id]) {
|
|
if (sdw_data->acp63_sdw1_dma_stream[stream_id])
|
|
snd_pcm_period_elapsed(sdw_data->acp63_sdw1_dma_stream[stream_id]);
|
|
adata->acp63_sdw1_dma_intr_stat[stream_id] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void acp63_hw_init_ops(struct acp_hw_ops *hw_ops)
|
|
{
|
|
hw_ops->acp_init = acp63_init;
|
|
hw_ops->acp_deinit = acp63_deinit;
|
|
hw_ops->acp_get_config = acp63_get_config;
|
|
hw_ops->acp_sdw_dma_irq_thread = acp63_sdw_dma_irq_thread;
|
|
hw_ops->acp_suspend = snd_acp63_suspend;
|
|
hw_ops->acp_resume = snd_acp63_resume;
|
|
hw_ops->acp_suspend_runtime = snd_acp63_suspend;
|
|
hw_ops->acp_resume_runtime = snd_acp63_runtime_resume;
|
|
}
|
|
|
|
static int acp70_power_on(void __iomem *acp_base)
|
|
{
|
|
u32 val = 0;
|
|
|
|
val = readl(acp_base + ACP_PGFSM_STATUS);
|
|
|
|
if (!val)
|
|
return 0;
|
|
if (val & ACP70_PGFSM_STATUS_MASK)
|
|
writel(ACP70_PGFSM_CNTL_POWER_ON_MASK, acp_base + ACP_PGFSM_CONTROL);
|
|
|
|
return readl_poll_timeout(acp_base + ACP_PGFSM_STATUS, val, !val, DELAY_US, ACP70_TIMEOUT);
|
|
}
|
|
|
|
static int acp70_reset(void __iomem *acp_base)
|
|
{
|
|
u32 val;
|
|
int ret;
|
|
|
|
writel(1, acp_base + ACP_SOFT_RESET);
|
|
|
|
ret = readl_poll_timeout(acp_base + ACP_SOFT_RESET, val,
|
|
val & ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK,
|
|
DELAY_US, ACP70_TIMEOUT);
|
|
if (ret)
|
|
return ret;
|
|
|
|
writel(0, acp_base + ACP_SOFT_RESET);
|
|
|
|
return readl_poll_timeout(acp_base + ACP_SOFT_RESET, val, !val, DELAY_US, ACP70_TIMEOUT);
|
|
}
|
|
|
|
static void acp70_enable_sdw_host_wake_interrupts(void __iomem *acp_base)
|
|
{
|
|
u32 ext_intr_cntl1;
|
|
|
|
ext_intr_cntl1 = readl(acp_base + ACP_EXTERNAL_INTR_CNTL1);
|
|
ext_intr_cntl1 |= ACP70_SDW_HOST_WAKE_MASK;
|
|
writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1);
|
|
}
|
|
|
|
static void acp70_enable_interrupts(void __iomem *acp_base)
|
|
{
|
|
u32 sdw0_wake_en, sdw1_wake_en;
|
|
|
|
writel(1, acp_base + ACP_EXTERNAL_INTR_ENB);
|
|
writel(ACP_ERROR_IRQ, acp_base + ACP_EXTERNAL_INTR_CNTL);
|
|
sdw0_wake_en = readl(acp_base + ACP_SW0_WAKE_EN);
|
|
sdw1_wake_en = readl(acp_base + ACP_SW1_WAKE_EN);
|
|
if (sdw0_wake_en || sdw1_wake_en)
|
|
acp70_enable_sdw_host_wake_interrupts(acp_base);
|
|
}
|
|
|
|
static void acp70_disable_interrupts(void __iomem *acp_base)
|
|
{
|
|
writel(ACP_EXT_INTR_STAT_CLEAR_MASK, acp_base + ACP_EXTERNAL_INTR_STAT);
|
|
writel(0, acp_base + ACP_EXTERNAL_INTR_CNTL);
|
|
writel(0, acp_base + ACP_EXTERNAL_INTR_ENB);
|
|
}
|
|
|
|
static int acp70_init(void __iomem *acp_base, struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
ret = acp70_power_on(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP power on failed\n");
|
|
return ret;
|
|
}
|
|
writel(0x01, acp_base + ACP_CONTROL);
|
|
ret = acp70_reset(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP reset failed\n");
|
|
return ret;
|
|
}
|
|
writel(0, acp_base + ACP_ZSC_DSP_CTRL);
|
|
acp70_enable_interrupts(acp_base);
|
|
writel(0x1, acp_base + ACP_PME_EN);
|
|
return 0;
|
|
}
|
|
|
|
static int acp70_deinit(void __iomem *acp_base, struct device *dev)
|
|
{
|
|
int ret;
|
|
|
|
acp70_disable_interrupts(acp_base);
|
|
ret = acp70_reset(acp_base);
|
|
if (ret) {
|
|
dev_err(dev, "ACP reset failed\n");
|
|
return ret;
|
|
}
|
|
writel(0x01, acp_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
|
|
static void acp70_get_config(struct pci_dev *pci, struct acp63_dev_data *acp_data)
|
|
{
|
|
u32 config;
|
|
|
|
config = readl(acp_data->acp63_base + ACP_PIN_CONFIG);
|
|
dev_dbg(&pci->dev, "ACP config value: %d\n", config);
|
|
switch (config) {
|
|
case ACP_CONFIG_4:
|
|
case ACP_CONFIG_5:
|
|
case ACP_CONFIG_10:
|
|
case ACP_CONFIG_11:
|
|
case ACP_CONFIG_20:
|
|
acp_data->is_pdm_config = true;
|
|
break;
|
|
case ACP_CONFIG_2:
|
|
case ACP_CONFIG_3:
|
|
case ACP_CONFIG_16:
|
|
acp_data->is_sdw_config = true;
|
|
break;
|
|
case ACP_CONFIG_6:
|
|
case ACP_CONFIG_7:
|
|
case ACP_CONFIG_12:
|
|
case ACP_CONFIG_8:
|
|
case ACP_CONFIG_13:
|
|
case ACP_CONFIG_14:
|
|
case ACP_CONFIG_17:
|
|
case ACP_CONFIG_18:
|
|
case ACP_CONFIG_19:
|
|
acp_data->is_pdm_config = true;
|
|
acp_data->is_sdw_config = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void acp70_sdw_dma_irq_thread(struct acp63_dev_data *adata)
|
|
{
|
|
struct sdw_dma_dev_data *sdw_data;
|
|
u32 stream_id;
|
|
|
|
sdw_data = dev_get_drvdata(&adata->sdw_dma_dev->dev);
|
|
|
|
for (stream_id = 0; stream_id < ACP70_SDW0_DMA_MAX_STREAMS; stream_id++) {
|
|
if (adata->acp70_sdw0_dma_intr_stat[stream_id]) {
|
|
if (sdw_data->acp70_sdw0_dma_stream[stream_id])
|
|
snd_pcm_period_elapsed(sdw_data->acp70_sdw0_dma_stream[stream_id]);
|
|
adata->acp70_sdw0_dma_intr_stat[stream_id] = 0;
|
|
}
|
|
}
|
|
for (stream_id = 0; stream_id < ACP70_SDW1_DMA_MAX_STREAMS; stream_id++) {
|
|
if (adata->acp70_sdw1_dma_intr_stat[stream_id]) {
|
|
if (sdw_data->acp70_sdw1_dma_stream[stream_id])
|
|
snd_pcm_period_elapsed(sdw_data->acp70_sdw1_dma_stream[stream_id]);
|
|
adata->acp70_sdw1_dma_intr_stat[stream_id] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __maybe_unused snd_acp70_suspend(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
if (adata->is_sdw_dev) {
|
|
adata->acp_sw_pad_keeper_en = readl(adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
adata->acp_pad_pulldown_ctrl = readl(adata->acp63_base + ACP_PAD_PULLDOWN_CTRL);
|
|
adata->sdw_en_stat = check_acp_sdw_enable_status(adata);
|
|
if (adata->sdw_en_stat) {
|
|
writel(1, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
return 0;
|
|
}
|
|
}
|
|
ret = acp_hw_deinit(adata, dev);
|
|
if (ret)
|
|
dev_err(dev, "ACP de-init failed\n");
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int __maybe_unused snd_acp70_runtime_resume(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
|
|
if (adata->sdw_en_stat) {
|
|
writel(0, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
writel(1, adata->acp63_base + ACP_PME_EN);
|
|
return 0;
|
|
}
|
|
|
|
ret = acp_hw_init(adata, dev);
|
|
if (ret) {
|
|
dev_err(dev, "ACP init failed\n");
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __maybe_unused snd_acp70_resume(struct device *dev)
|
|
{
|
|
struct acp63_dev_data *adata;
|
|
u32 acp_sw_pad_keeper_en;
|
|
int ret;
|
|
|
|
adata = dev_get_drvdata(dev);
|
|
|
|
if (adata->sdw_en_stat) {
|
|
writel(0, adata->acp63_base + ACP_ZSC_DSP_CTRL);
|
|
writel(1, adata->acp63_base + ACP_PME_EN);
|
|
return 0;
|
|
}
|
|
|
|
ret = acp_hw_init(adata, dev);
|
|
if (ret)
|
|
dev_err(dev, "ACP init failed\n");
|
|
|
|
acp_sw_pad_keeper_en = readl(adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
dev_dbg(dev, "ACP_SW0_PAD_KEEPER_EN:0x%x\n", acp_sw_pad_keeper_en);
|
|
if (!acp_sw_pad_keeper_en) {
|
|
writel(adata->acp_sw_pad_keeper_en, adata->acp63_base + ACP_SW0_PAD_KEEPER_EN);
|
|
writel(adata->acp_pad_pulldown_ctrl, adata->acp63_base + ACP_PAD_PULLDOWN_CTRL);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void acp70_hw_init_ops(struct acp_hw_ops *hw_ops)
|
|
{
|
|
hw_ops->acp_init = acp70_init;
|
|
hw_ops->acp_deinit = acp70_deinit;
|
|
hw_ops->acp_get_config = acp70_get_config;
|
|
hw_ops->acp_sdw_dma_irq_thread = acp70_sdw_dma_irq_thread;
|
|
hw_ops->acp_suspend = snd_acp70_suspend;
|
|
hw_ops->acp_resume = snd_acp70_resume;
|
|
hw_ops->acp_suspend_runtime = snd_acp70_suspend;
|
|
hw_ops->acp_resume_runtime = snd_acp70_runtime_resume;
|
|
}
|