diff --git a/sound/soc/amd/acp/soc_amd_sdw_common.h b/sound/soc/amd/acp/soc_amd_sdw_common.h index 3930cc46fa5871..17e4e97fb3d1d6 100644 --- a/sound/soc/amd/acp/soc_amd_sdw_common.h +++ b/sound/soc/amd/acp/soc_amd_sdw_common.h @@ -23,6 +23,19 @@ #define ACP71_PCI_REV 0x71 #define ACP72_PCI_REV 0x72 +/** + * struct amd_pdm_pdata - platform data passed via mach->pdata to machine driver + * @pdm_sel: active PDM controller (ACP7X_PDM_DMIC0 or ACP7X_PDM_DMIC1), + * non-zero when a PDM controller was identified via ACPI _DSD + * + * Carries the PDM controller selection for ACP7.B/7.F platforms, derived + * from the acp-audio-ep-port ACPI _DSD property and passed via mach->pdata + * to the machine driver. + */ +struct amd_pdm_pdata { + unsigned int pdm_sel; +}; + #define SOC_JACK_JDSRC(quirk) ((quirk) & GENMASK(3, 0)) #define ASOC_SDW_FOUR_SPK BIT(4) #define ASOC_SDW_ACP_DMIC BIT(5) diff --git a/sound/soc/sof/amd/Kconfig b/sound/soc/sof/amd/Kconfig index 903e7ec3b3ba41..d64f682cfbea68 100644 --- a/sound/soc/sof/amd/Kconfig +++ b/sound/soc/sof/amd/Kconfig @@ -109,6 +109,7 @@ config SND_SOC_SOF_AMD_ACP7X depends on SND_SOC_SOF_PCI depends on AMD_NODE select SND_SOC_SOF_AMD_COMMON + select SND_SOC_SOF_AMD_SOUNDWIRE_LINK_BASELINE help Select this option for SOF support on AMD ACP7.B and ACP7.F PCI revision based platforms. diff --git a/sound/soc/sof/amd/acp-common.c b/sound/soc/sof/amd/acp-common.c index df656cdc152773..33540f7c421b19 100644 --- a/sound/soc/sof/amd/acp-common.c +++ b/sound/soc/sof/amd/acp-common.c @@ -16,6 +16,7 @@ #include "acp.h" #include "acp-dsp-offset.h" #include +#include "../../amd/acp/soc_amd_sdw_common.h" /** * amd_sof_ipc_dump() - This function is called when IPC tx times out. @@ -177,6 +178,7 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; const struct sof_dev_desc *desc = sof_pdata->desc; struct snd_soc_acpi_mach *mach = NULL; + struct amd_pdm_pdata *pdm_pdata; if (desc->machines) mach = snd_soc_acpi_find_machine(desc->machines); @@ -188,7 +190,22 @@ struct snd_soc_acpi_mach *amd_sof_machine_select(struct snd_sof_dev *sdev) } } + mach = devm_kmemdup(sdev->dev, mach, sizeof(*mach), GFP_KERNEL); + if (!mach) { + dev_err(sdev->dev, "failed to allocate machine entry copy\n"); + return NULL; + } + mach->mach_params.subsystem_rev = acp_data->pci_rev; + + if (acp_data->pdm_sel) { + pdm_pdata = devm_kzalloc(sdev->dev, sizeof(*pdm_pdata), GFP_KERNEL); + if (!pdm_pdata) + return NULL; + pdm_pdata->pdm_sel = acp_data->pdm_sel; + mach->pdata = pdm_pdata; + } + sof_pdata->tplg_filename = mach->sof_tplg_filename; sof_pdata->fw_filename = mach->fw_filename; diff --git a/sound/soc/sof/amd/acp-dsp-offset.h b/sound/soc/sof/amd/acp-dsp-offset.h index bea1bd3afa70d2..3984cd627db6df 100644 --- a/sound/soc/sof/amd/acp-dsp-offset.h +++ b/sound/soc/sof/amd/acp-dsp-offset.h @@ -156,5 +156,22 @@ #define ACP7X_IDMA_ERROR_MASK 0x1FF9FF #define ACP7X_ZSC_DSP_CTRL 0x001014 #define ACP7X_PME_EN ACP70_PME_EN +/* SW enable base for SDW0; manager N uses ACP7X_SW_EN + (N * 0x2000) */ +#define ACP7X_SW_EN 0x5200 + +/* ACP7X SoundWire IO registers (non-gsync, up to 4 managers SW0-SW3) */ +#define ACP7X_EXTERNAL_SW_INTR_STAT 0x001A24 +#define ACP7X_SW_WAKE_EN 0x001458 +#define ACP7X_SW_PME_STS 0x001474 +#define ACP7X_SDW_STAT BIT(23) +#define ACP7X_ERROR_IRQ BIT(29) +#define ACP7X_SW_WAKE_EN_MASK BIT(0) +#define ACP7X_SW_HOST_WAKE_MASK BIT(22) +#define ACP7X_SDW_HOST_WAKE_STAT BIT(24) +#define ACP7X_SW_FIFO_ERROR_REASON 0x50C4 +#define ACP7X_SW_ERROR_REASON1 0x50CC +#define ACP7X_SW_ERROR_REASON2 0x50D4 +#define ACP7X_EXTERNAL_SDW_STAT BIT(16) +#define ACP7X_SW_ERR_STAT_MASK BIT(11) #endif diff --git a/sound/soc/sof/amd/acp.c b/sound/soc/sof/amd/acp.c index f89ad86260b4ed..b059039c9e0d1e 100644 --- a/sound/soc/sof/amd/acp.c +++ b/sound/soc/sof/amd/acp.c @@ -22,11 +22,20 @@ #include "../ops.h" #include "acp.h" #include "acp-dsp-offset.h" +#include "../../amd/acp/soc_amd_sdw_common.h" +#include "acp7x.h" static bool enable_fw_debug; module_param(enable_fw_debug, bool, 0444); MODULE_PARM_DESC(enable_fw_debug, "Enable Firmware debug"); +static const u32 acp7x_sof_sdw_ext_stat[ACP7X_SDW_MAX_MANAGER_COUNT] = { + ACP7X_SDW_STAT << 0, + ACP7X_SDW_STAT << 1, + ACP7X_SDW_STAT << 2, + ACP7X_SDW_STAT << 3, +}; + static struct acp_quirk_entry quirk_valve_galileo = { .signed_fw_image = true, .skip_iram_dram_size_mod = true, @@ -581,30 +590,196 @@ static irqreturn_t acp_irq_handler(int irq, void *dev_id) return IRQ_NONE; } +/* ACP7X SoundWire IO data tables */ + +const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT] = { + { ACP7X_SW_ERR_STAT_MASK << 0, + ACP7X_SW_FIFO_ERROR_REASON + (0 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (0 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (0 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 1, + ACP7X_SW_FIFO_ERROR_REASON + (1 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (1 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (1 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 2, + ACP7X_SW_FIFO_ERROR_REASON + (2 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (2 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (2 * 0x2000) }, + { ACP7X_SW_ERR_STAT_MASK << 3, + ACP7X_SW_FIFO_ERROR_REASON + (3 * 0x2000), + ACP7X_SW_ERROR_REASON1 + (3 * 0x2000), + ACP7X_SW_ERROR_REASON2 + (3 * 0x2000) }, +}; + +const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[] = { + { ACP7X_SDW_HOST_WAKE_STAT << 0, ACP7X_SW_PME_STS + (0 * 4), + ACP7X_SW_WAKE_EN_MASK << 0, 0 }, + { ACP7X_SDW_HOST_WAKE_STAT << 1, ACP7X_SW_PME_STS + (1 * 4), + ACP7X_SW_WAKE_EN_MASK << 1, 1 }, + { ACP7X_SDW_HOST_WAKE_STAT << 2, ACP7X_SW_PME_STS + (2 * 4), + ACP7X_SW_WAKE_EN_MASK << 2, 2 }, + { ACP7X_SDW_HOST_WAKE_STAT << 3, ACP7X_SW_PME_STS + (3 * 4), + ACP7X_SW_WAKE_EN_MASK << 3, 3 }, +}; + +/* ACP7X SoundWire IO interrupt and wake helpers */ + +static void sof_amd_acp7x_handle_one_sdw_err(struct snd_sof_dev *sdev, u32 *err_stat, + u32 acp_error_stat_reg, + const struct sof_amd_acp7x_sdw_err_regs *regs) +{ + if (!(*err_stat & regs->err_stat_mask)) + return; + + *err_stat &= ~regs->err_stat_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, acp_error_stat_reg, *err_stat); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->fifo_err_reason, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason1, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, regs->err_reason2, 0); +} + +/* Clears SoundWire error registers for all managers; runs in hard IRQ context. */ +static void sof_amd_acp7x_clear_sdw_err_regs(struct snd_sof_dev *sdev) +{ + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + u32 err_stat; + unsigned int instance; + + err_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->acp_error_stat); + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) + sof_amd_acp7x_handle_one_sdw_err(sdev, &err_stat, desc->acp_error_stat, + &acp7x_sdw_err_regs[instance]); +} + +static void sof_amd_handle_acp7x_sdw_wake_event(struct acp_dev_data *adata) +{ + struct amd_sdw_manager *amd_manager; + unsigned int instance; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (!adata->acp7x_sdw_wake_event[instance]) + continue; + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + adata->acp7x_sdw_wake_event[instance] = false; + } +} + +static int sof_amd_check_and_handle_acp7x_sdw_wake_irq(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata = sdev->pdata->hw_pdata; + const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); + unsigned int ext_intr_stat1; + unsigned int sdw_pme_stat, sdw_wake_en; + unsigned int i; + bool sdw_wake_irq = false; + + ext_intr_stat1 = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat1); + + for (i = 0; i < ARRAY_SIZE(acp7x_sdw_wake_sources); i++) { + const struct acp7x_sdw_wake_src *src = &acp7x_sdw_wake_sources[i]; + + bool woke = false; + + if (ext_intr_stat1 & src->host_stat_mask) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat1, + src->host_stat_mask); + woke = true; + } + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, src->pme_sts_reg); + if (sdw_pme_stat) { + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~src->wake_en_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, src->pme_sts_reg, sdw_pme_stat); + woke = true; + } + + if (woke) { + adata->acp7x_sdw_wake_event[src->instance] = true; + sdw_wake_irq = true; + } + } + + if (sdw_wake_irq) { + sof_amd_handle_acp7x_sdw_wake_event(adata); + return WAKE_IRQ_HANDLED; + } + return WAKE_IRQ_NONE; +} + +static void sof_acp7x_handle_sdw_manager_irq(struct snd_sof_dev *sdev, + struct acp_dev_data *adata, + const struct sof_amd_acp_desc *desc, + unsigned int instance, + u32 ext_stat_mask) +{ + struct amd_sdw_manager *amd_manager; + unsigned int sdw_intr_stat; + u32 sw_intr_reg = ACP7X_EXTERNAL_SW_INTR_STAT + (instance * 4); + + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ext_stat_mask); + sdw_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, sw_intr_reg); + if (sdw_intr_stat & ACP7X_EXTERNAL_SDW_STAT) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, sw_intr_reg, ACP7X_EXTERNAL_SDW_STAT); + if (!adata->sdw->pdev[instance]) + return; + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + schedule_work(&amd_manager->amd_sdw_irq_thread); + } +} + static irqreturn_t acp7x_irq_handler(int irq, void *dev_id) { struct snd_sof_dev *sdev = dev_id; + struct acp_dev_data *adata = sdev->pdata->hw_pdata; const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); unsigned int base = desc->dsp_intr_base; unsigned int val; unsigned int ext_intr_stat; - int irq_flag = 0; + unsigned int instance; + int irq_flag = 0, wake_irq_flag = 0; + bool dsp_irq = false; val = snd_sof_dsp_read(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET); if (val & ACP_DSP_TO_HOST_IRQ) { snd_sof_dsp_write(sdev, ACP_DSP_BAR, base + DSP_SW_INTR_STAT_OFFSET, ACP_DSP_TO_HOST_IRQ); - return IRQ_WAKE_THREAD; + dsp_irq = true; } ext_intr_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, desc->ext_intr_stat); - if (ext_intr_stat & ACP_ERROR_IRQ_MASK) { - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP_ERROR_IRQ_MASK); - snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->acp_error_stat, 0); + + if (adata->sdw) { + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + if (ext_intr_stat & acp7x_sof_sdw_ext_stat[instance]) { + sof_acp7x_handle_sdw_manager_irq(sdev, adata, desc, instance, + acp7x_sof_sdw_ext_stat[instance]); + irq_flag = 1; + } + } + } + + if (adata->sdw) + wake_irq_flag = sof_amd_check_and_handle_acp7x_sdw_wake_irq(sdev); + + if (ext_intr_stat & ACP7X_ERROR_IRQ) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, desc->ext_intr_stat, ACP7X_ERROR_IRQ); + sof_amd_acp7x_clear_sdw_err_regs(sdev); irq_flag = 1; } - if (irq_flag) + if (dsp_irq) + return IRQ_WAKE_THREAD; + + if (irq_flag || wake_irq_flag) return IRQ_HANDLED; return IRQ_NONE; @@ -728,6 +903,8 @@ static int acp_init(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *desc = get_chip_info(sdev->pdata); struct acp_dev_data *acp_data; unsigned int sdw0_wake_en, sdw1_wake_en; + u32 sdw_wake_en, intr_mask; + unsigned int i; int ret; /* power on */ @@ -771,6 +948,20 @@ static int acp_init(struct snd_sof_dev *sdev) snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_DSP0_IDMA_ERROR_MASK, ACP7X_IDMA_ERROR_MASK); + /* + * Enable host-wake interrupt per manager based on SW_WAKE_EN: + * SW_WAKE_EN bit i enables ACP7X_SW_HOST_WAKE_MASK << i in INTR_CNTL1. + */ + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + intr_mask = 0; + for (i = 0; i < ACP7X_SDW_MAX_MANAGER_COUNT; i++) { + if (sdw_wake_en & BIT(i)) + intr_mask |= ACP7X_SW_HOST_WAKE_MASK << i; + } + if (intr_mask) + snd_sof_dsp_update_bits(sdev, ACP_DSP_BAR, + ACP7X_EXTERNAL_INTR_CNTL1, + intr_mask, intr_mask); break; } return 0; @@ -779,15 +970,30 @@ static int acp_init(struct snd_sof_dev *sdev) static bool check_acp_sdw_enable_status(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; - u32 sdw0_en, sdw1_en; + u32 sdw0_en, sdw1_en, sdw2_en, sdw3_en; acp_data = sdev->pdata->hw_pdata; if (!acp_data->sdw) return false; - sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); - sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); - acp_data->sdw_en_stat = sdw0_en || sdw1_en; + switch (acp_data->pci_rev) { + case ACP63_PCI_ID: + case ACP70_PCI_ID: + case ACP71_PCI_ID: + case ACP72_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW0_EN); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP_SW1_EN); + acp_data->sdw_en_stat = sdw0_en || sdw1_en; + break; + case ACP7B_PCI_ID: + case ACP7F_PCI_ID: + sdw0_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (0 * 0x2000)); + sdw1_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (1 * 0x2000)); + sdw2_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (2 * 0x2000)); + sdw3_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_EN + (3 * 0x2000)); + acp_data->sdw_en_stat = sdw0_en || sdw1_en || sdw2_en || sdw3_en; + break; + } return acp_data->sdw_en_stat; } @@ -853,6 +1059,31 @@ int amd_sof_acp_resume(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp_resume, "SND_SOC_SOF_AMD_COMMON"); +static void acp_sof_scan_pdm_devices(struct snd_sof_dev *sdev, + struct acpi_device *pdm_dev) +{ + struct acp_dev_data *acp_data = sdev->pdata->hw_pdata; + struct fwnode_handle *fwnode, *child; + u32 ep_port_val; + + fwnode = acpi_fwnode_handle(pdm_dev); + child = fwnode_get_next_child_node(fwnode, NULL); + if (!child) + return; + + if (!fwnode_property_read_u32(child, "acp-audio-ep-port", &ep_port_val)) { + if (ep_port_val == ACP_DEV_PORT_PDM) + acp_data->pdm_sel = ACP7X_PDM_DMIC0; + else if (ep_port_val == ACP_DEV_PORT_PDM2) + acp_data->pdm_sel = ACP7X_PDM_DMIC1; + else + dev_warn(sdev->dev, + "acp-audio-ep-port: unrecognized value %u\n", + ep_port_val); + } + fwnode_handle_put(child); +} + #if IS_ENABLED(CONFIG_SND_SOC_SOF_AMD_SOUNDWIRE) static int acp_sof_scan_sdw_devices(struct snd_sof_dev *sdev, u64 addr) { @@ -1038,6 +1269,8 @@ int amd_sof_acp_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1048,12 +1281,12 @@ void amd_sof_acp_remove(struct snd_sof_dev *sdev) { struct acp_dev_data *adata = sdev->pdata->hw_pdata; - if (adata->sdw) - amd_sof_sdw_exit(sdev); - if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); @@ -1068,6 +1301,7 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) const struct sof_amd_acp_desc *chip; const union acpi_object *obj; struct acpi_device *adev; + struct acpi_device *pdm_dev; unsigned int addr; unsigned int irqflags; int ret; @@ -1120,7 +1354,27 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) goto unregister_dev; } + /* scan SoundWire capabilities exposed by DSDT */ + ret = acp_sof_scan_sdw_devices(sdev, chip->sdw_acpi_dev_addr); + if (ret < 0) { + dev_dbg(sdev->dev, "skipping SoundWire, not detected with ACPI scan\n"); + goto skip_soundwire; + } + ret = amd_sof_sdw_probe(sdev); + if (ret < 0) { + dev_err(sdev->dev, "error: SoundWire probe error\n"); + goto free_ipc_irq; + } + if (adata->info.link_mask) + adata->is_sdw_dev = true; + +skip_soundwire: if (adev) { + /* DMIC ACPI child address is 2 on ACP7x platforms */ + pdm_dev = acpi_find_child_device(adev, ACP7X_DMIC_ADDR, 0); + if (pdm_dev) + acp_sof_scan_pdm_devices(sdev, pdm_dev); + if (!acpi_dev_get_property(adev, "acp-sof-signed-firmware-image", ACPI_TYPE_INTEGER, &obj)) adata->acp_sof_signed_firmware_image = obj->integer.value; @@ -1158,6 +1412,8 @@ int amd_sof_acp7x_probe(struct snd_sof_dev *sdev) free_ipc_irq: free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); unregister_dev: platform_device_unregister(adata->dmic_dev); return ret; @@ -1171,6 +1427,9 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) if (sdev->ipc_irq) free_irq(sdev->ipc_irq, sdev); + if (adata->sdw) + amd_sof_sdw_exit(sdev); + if (adata->dmic_dev) platform_device_unregister(adata->dmic_dev); @@ -1178,6 +1437,44 @@ void amd_sof_acp7x_remove(struct snd_sof_dev *sdev) } EXPORT_SYMBOL_NS(amd_sof_acp7x_remove, "SND_SOC_SOF_AMD_COMMON"); +static void handle_amd_sof_acp7x_sdw_pme_event(struct snd_sof_dev *sdev) +{ + struct acp_dev_data *adata; + struct amd_sdw_manager *amd_manager; + u32 sdw_pme_stat; + u32 sdw_wake_en; + u32 pme_reg; + u32 wake_mask; + unsigned int instance; + + adata = sdev->pdata->hw_pdata; + if (!adata->sdw) + return; + + for (instance = 0; instance < ACP7X_SDW_MAX_MANAGER_COUNT; instance++) { + pme_reg = ACP7X_SW_PME_STS + (instance * 4); + wake_mask = ACP7X_SW_WAKE_EN_MASK << instance; + + sdw_pme_stat = snd_sof_dsp_read(sdev, ACP_DSP_BAR, pme_reg); + if (!sdw_pme_stat) + continue; + + mutex_lock(&adata->acp_lock); + sdw_wake_en = snd_sof_dsp_read(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN); + sdw_wake_en &= ~wake_mask; + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_SW_WAKE_EN, sdw_wake_en); + mutex_unlock(&adata->acp_lock); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, pme_reg, sdw_pme_stat); + + if (!adata->sdw->pdev[instance]) + continue; + + amd_manager = dev_get_drvdata(&adata->sdw->pdev[instance]->dev); + if (amd_manager) + pm_request_resume(amd_manager->dev); + } +} + int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) { struct acp_dev_data *acp_data; @@ -1186,6 +1483,11 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) acp_data = sdev->pdata->hw_pdata; + if (acp_data->is_sdw_dev && check_acp_sdw_enable_status(sdev)) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); + return acp_dsp_reset(sdev); + } + ret = acp_reset(sdev); if (ret) { dev_err(sdev->dev, "ACP Reset failed\n"); @@ -1197,7 +1499,8 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) enable = true; break; default: - break; + dev_err(sdev->dev, "Unexpected PCI revision: 0x%x\n", acp_data->pci_rev); + return -EINVAL; } snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP_CONTROL, enable); snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 1); @@ -1206,13 +1509,25 @@ int amd_sof_acp7x_suspend(struct snd_sof_dev *sdev, u32 target_state) } EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) +int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +{ + return amd_sof_acp7x_suspend(sdev, 0); +} +EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + +int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) { struct acp_dev_data *acp_data; int ret; acp_data = sdev->pdata->hw_pdata; + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + ret = acp_init(sdev); if (ret) { dev_err(sdev->dev, "ACP Init failed\n"); @@ -1224,30 +1539,40 @@ int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) return ret; } - switch (acp_data->pci_rev) { - case ACP7B_PCI_ID: - case ACP7F_PCI_ID: - snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); - break; - default: - break; - } + if (acp_data->is_sdw_dev) + handle_amd_sof_acp7x_sdw_pme_event(sdev); return 0; } -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); -int amd_sof_acp7x_suspend_runtime(struct snd_sof_dev *sdev) +int amd_sof_acp7x_resume(struct snd_sof_dev *sdev) { - return amd_sof_acp7x_suspend(sdev, 0); -} -EXPORT_SYMBOL_NS(amd_sof_acp7x_suspend_runtime, "SND_SOC_SOF_AMD_COMMON"); + struct acp_dev_data *acp_data; + int ret; -int amd_sof_acp7x_resume_runtime(struct snd_sof_dev *sdev) -{ - return amd_sof_acp7x_resume(sdev); + acp_data = sdev->pdata->hw_pdata; + + if (acp_data->sdw_en_stat) { + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_ZSC_DSP_CTRL, 0); + snd_sof_dsp_write(sdev, ACP_DSP_BAR, ACP7X_PME_EN, 1); + return acp_dsp_reset(sdev); + } + + ret = acp_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Init failed\n"); + return ret; + } + ret = acp_memory_init(sdev); + if (ret) { + dev_err(sdev->dev, "ACP Memory init failed\n"); + return ret; + } + + return 0; } -EXPORT_SYMBOL_NS(amd_sof_acp7x_resume_runtime, "SND_SOC_SOF_AMD_COMMON"); +EXPORT_SYMBOL_NS(amd_sof_acp7x_resume, "SND_SOC_SOF_AMD_COMMON"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("AMD ACP sof driver"); diff --git a/sound/soc/sof/amd/acp.h b/sound/soc/sof/amd/acp.h index 1cd9904c2908f2..5a887959461a26 100644 --- a/sound/soc/sof/amd/acp.h +++ b/sound/soc/sof/amd/acp.h @@ -125,6 +125,17 @@ #define ACP_SRAM_PAGE_COUNT 128 #define ACP6X_SDW_MAX_MANAGER_COUNT 2 #define ACP70_SDW_MAX_MANAGER_COUNT ACP6X_SDW_MAX_MANAGER_COUNT +/* ACPI _DSD acp-audio-ep-port values for PDM controller selection */ +#define ACP_DEV_PORT_PDM 4 +#define ACP_DEV_PORT_PDM2 5 +#define SDW_ACPI_ADDR_ACP7X SDW_ACPI_ADDR_ACP63 +#define ACP7X_SDW_MAX_MANAGER_COUNT 4 + +/* ACPI child device address for the ACP7x PDM/DMIC device */ +#define ACP7X_DMIC_ADDR 2 +/* ACP7X PDM controller selection values for acp_dev_data.pdm_sel; 0 = not set */ +#define ACP7X_PDM_DMIC0 1 +#define ACP7X_PDM_DMIC1 2 #define ACP_DSP_MSG_SET 1 #define ACP_DSP_ACK_SET 1 @@ -279,6 +290,11 @@ struct acp_dev_data { bool acp70_sdw0_wake_event; /* acp70_sdw1_wake_event flag set to true when wake irq asserted for SW1 instance */ bool acp70_sdw1_wake_event; + /* per-manager wake event flags; indexed by SoundWire manager instance (0-3) */ + bool acp7x_sdw_wake_event[ACP7X_SDW_MAX_MANAGER_COUNT]; + /* PDM controller index selected from ACPI acp-audio-ep-port; passed to machine driver */ + unsigned int pdm_sel; + bool is_sdw_dev; unsigned int pci_rev; int acp_sof_signed_firmware_image; }; diff --git a/sound/soc/sof/amd/acp7x.h b/sound/soc/sof/amd/acp7x.h new file mode 100644 index 00000000000000..5151d8757c12d1 --- /dev/null +++ b/sound/soc/sof/amd/acp7x.h @@ -0,0 +1,37 @@ +/* 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) 2026 Advanced Micro Devices, Inc. All rights reserved. + * + * Author: Vijendar Mukunda + */ + +#ifndef __SOF_AMD_ACP7X_H +#define __SOF_AMD_ACP7X_H + +/* Return values for sof_amd_check_and_handle_acp7x_sdw_wake_irq() */ +#define WAKE_IRQ_HANDLED 1 +#define WAKE_IRQ_NONE 0 + +/* ACP7X SoundWire IO structures */ + +struct sof_amd_acp7x_sdw_err_regs { + u32 err_stat_mask; + u32 fifo_err_reason; + u32 err_reason1; + u32 err_reason2; +}; + +struct acp7x_sdw_wake_src { + u32 host_stat_mask; + u32 pme_sts_reg; + u32 wake_en_mask; + u8 instance; +}; + +extern const struct sof_amd_acp7x_sdw_err_regs acp7x_sdw_err_regs[ACP7X_SDW_MAX_MANAGER_COUNT]; +extern const struct acp7x_sdw_wake_src acp7x_sdw_wake_sources[]; + +#endif /* __SOF_AMD_ACP7X_H */ diff --git a/sound/soc/sof/amd/pci-acp7x.c b/sound/soc/sof/amd/pci-acp7x.c index 532e1531379591..0594c1f3deb6bb 100644 --- a/sound/soc/sof/amd/pci-acp7x.c +++ b/sound/soc/sof/amd/pci-acp7x.c @@ -40,6 +40,8 @@ static const struct sof_amd_acp_desc acp7x_chip_info = { .fusion_dsp_offset = ACP7X_DSP_FUSION_RUNSTALL, .probe_reg_offset = ACP7X_FUTURE_REG_ACLK_0, .reg_start_addr = ACP7X_REG_START, + .sdw_max_link_count = ACP7X_SDW_MAX_MANAGER_COUNT, + .sdw_acpi_dev_addr = SDW_ACPI_ADDR_ACP7X, .reg_end_addr = ACP7X_REG_END, };