Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0-only
# Pink Sardine platform Support
snd-pci-ps-y := pci-ps.o ps-common.o
snd-ps-pdm-dma-y := ps-pdm-dma.o
snd-soc-ps-mach-y := ps-mach.o
snd-ps-sdw-dma-y := ps-sdw-dma.o
obj-$(CONFIG_SND_SOC_AMD_PS) += snd-pci-ps.o
obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-pdm-dma.o
obj-$(CONFIG_SND_SOC_AMD_PS) += snd-ps-sdw-dma.o
obj-$(CONFIG_SND_SOC_AMD_PS_MACH) += snd-soc-ps-mach.o
+448
View File
@@ -0,0 +1,448 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* AMD Common ACP header file for ACP6.3, ACP7.0 & ACP7.1 platforms
*
* Copyright (C) 2022, 2023, 2025 Advanced Micro Devices, Inc. All rights reserved.
*/
#include <linux/soundwire/sdw_amd.h>
#include <sound/acp63_chip_offset_byte.h>
#define ACP_DEVICE_ID 0x15E2
#define ACP63_REG_START 0x1240000
#define ACP63_REG_END 0x125C000
#define ACP63_PCI_REV 0x63
#define ACP70_PCI_REV 0x70
#define ACP71_PCI_REV 0x71
#define ACP72_PCI_REV 0x72
#define ACP_SOFT_RESET_SOFTRESET_AUDDONE_MASK 0x00010001
#define ACP63_PGFSM_CNTL_POWER_ON_MASK 1
#define ACP63_PGFSM_CNTL_POWER_OFF_MASK 0
#define ACP63_PGFSM_STATUS_MASK 3
#define ACP63_POWERED_ON 0
#define ACP63_POWER_ON_IN_PROGRESS 1
#define ACP63_POWERED_OFF 2
#define ACP63_POWER_OFF_IN_PROGRESS 3
#define ACP_ERROR_MASK 0x20000000
#define ACP_EXT_INTR_STAT_CLEAR_MASK 0xFFFFFFFF
#define PDM_DMA_STAT 0x10
#define PDM_DMA_INTR_MASK 0x10000
#define ACP_ERROR_STAT 29
#define PDM_DECIMATION_FACTOR 2
#define ACP_PDM_CLK_FREQ_MASK 7
#define ACP_WOV_GAIN_CONTROL GENMASK(4, 3)
#define ACP_PDM_ENABLE 1
#define ACP_PDM_DISABLE 0
#define ACP_PDM_DMA_EN_STATUS 2
#define TWO_CH 2
#define DELAY_US 5
#define ACP_COUNTER 20000
#define ACP_SRAM_PTE_OFFSET 0x03800000
#define PAGE_SIZE_4K_ENABLE 2
#define PDM_PTE_OFFSET 0
#define PDM_MEM_WINDOW_START 0x4000000
#define CAPTURE_MIN_NUM_PERIODS 4
#define CAPTURE_MAX_NUM_PERIODS 4
#define CAPTURE_MAX_PERIOD_SIZE 8192
#define CAPTURE_MIN_PERIOD_SIZE 4096
#define MAX_BUFFER (CAPTURE_MAX_PERIOD_SIZE * CAPTURE_MAX_NUM_PERIODS)
#define MIN_BUFFER MAX_BUFFER
/* time in ms for runtime suspend delay */
#define ACP_SUSPEND_DELAY_MS 2000
#define ACP_DMIC_DEV 2
#define ACP63_DMIC_ADDR 2
#define ACP63_SDW_ADDR 5
#define AMD_SDW_MAX_MANAGERS 2
/* time in ms for acp timeout */
#define ACP63_TIMEOUT 500
#define ACP_SDW0_STAT BIT(21)
#define ACP_SDW1_STAT BIT(2)
#define ACP_ERROR_IRQ BIT(29)
#define ACP_AUDIO0_TX_THRESHOLD 0x1c
#define ACP_AUDIO1_TX_THRESHOLD 0x1a
#define ACP_AUDIO2_TX_THRESHOLD 0x18
#define ACP_AUDIO0_RX_THRESHOLD 0x1b
#define ACP_AUDIO1_RX_THRESHOLD 0x19
#define ACP_AUDIO2_RX_THRESHOLD 0x17
#define ACP63_P1_AUDIO1_TX_THRESHOLD BIT(6)
#define ACP63_P1_AUDIO1_RX_THRESHOLD BIT(5)
#define ACP63_SDW_DMA_IRQ_MASK 0x1F800000
#define ACP63_P1_SDW_DMA_IRQ_MASK 0x60
#define ACP63_SDW0_DMA_MAX_STREAMS 6
#define ACP63_SDW1_DMA_MAX_STREAMS 2
#define ACP63_P1_AUDIO_TX_THRESHOLD 6
/*
* Below entries describes SDW0 instance DMA stream id and DMA irq bit mapping
* in ACP_EXTENAL_INTR_CNTL register.
* Stream id IRQ Bit
* 0 (SDW0_AUDIO0_TX) 28
* 1 (SDW0_AUDIO1_TX) 26
* 2 (SDW0_AUDIO2_TX) 24
* 3 (SDW0_AUDIO0_RX) 27
* 4 (SDW0_AUDIO1_RX) 25
* 5 (SDW0_AUDIO2_RX) 23
*/
#define ACP63_SDW0_DMA_TX_IRQ_MASK(i) (ACP_AUDIO0_TX_THRESHOLD - (2 * (i)))
#define ACP63_SDW0_DMA_RX_IRQ_MASK(i) (ACP_AUDIO0_RX_THRESHOLD - (2 * ((i) - 3)))
/*
* Below entries describes SDW1 instance DMA stream id and DMA irq bit mapping
* in ACP_EXTENAL_INTR_CNTL1 register.
* Stream id IRQ Bit
* 0 (SDW1_AUDIO1_TX) 6
* 1 (SDW1_AUDIO1_RX) 5
*/
#define ACP63_SDW1_DMA_IRQ_MASK(i) (ACP63_P1_AUDIO_TX_THRESHOLD - (i))
#define ACP_DELAY_US 5
#define ACP_SDW_RING_BUFF_ADDR_OFFSET (128 * 1024)
#define SDW0_MEM_WINDOW_START 0x4800000
#define ACP_SDW_SRAM_PTE_OFFSET 0x03800400
#define SDW0_PTE_OFFSET 0x400
#define SDW_FIFO_SIZE 0x100
#define SDW_DMA_SIZE 0x40
#define ACP_SDW0_FIFO_OFFSET 0x100
#define ACP_SDW_PTE_OFFSET 0x100
#define SDW_FIFO_OFFSET 0x100
#define SDW_PTE_OFFSET(i) (SDW0_PTE_OFFSET + ((i) * 0x600))
#define ACP_SDW_FIFO_OFFSET(i) (ACP_SDW0_FIFO_OFFSET + ((i) * 0x500))
#define SDW_MEM_WINDOW_START(i) (SDW0_MEM_WINDOW_START + ((i) * 0xC0000))
#define SDW_PLAYBACK_MIN_NUM_PERIODS 2
#define SDW_PLAYBACK_MAX_NUM_PERIODS 8
#define SDW_PLAYBACK_MAX_PERIOD_SIZE 8192
#define SDW_PLAYBACK_MIN_PERIOD_SIZE 1024
#define SDW_CAPTURE_MIN_NUM_PERIODS 2
#define SDW_CAPTURE_MAX_NUM_PERIODS 8
#define SDW_CAPTURE_MAX_PERIOD_SIZE 8192
#define SDW_CAPTURE_MIN_PERIOD_SIZE 1024
#define SDW_MAX_BUFFER (SDW_PLAYBACK_MAX_PERIOD_SIZE * SDW_PLAYBACK_MAX_NUM_PERIODS)
#define SDW_MIN_BUFFER SDW_MAX_BUFFER
#define ACP_HW_OPS(acp_data, cb) ((acp_data)->hw_ops->cb)
#define ACP70_PGFSM_CNTL_POWER_ON_MASK 0x1F
#define ACP70_PGFSM_CNTL_POWER_OFF_MASK 0
#define ACP70_PGFSM_STATUS_MASK 0xFF
#define ACP70_TIMEOUT 2000
#define ACP70_SDW_HOST_WAKE_MASK 0x0C00000
#define ACP70_SDW0_HOST_WAKE_STAT BIT(24)
#define ACP70_SDW1_HOST_WAKE_STAT BIT(25)
#define ACP70_SDW0_PME_STAT BIT(26)
#define ACP70_SDW1_PME_STAT BIT(27)
#define ACP70_SDW0_DMA_MAX_STREAMS 6
#define ACP70_SDW1_DMA_MAX_STREAMS ACP70_SDW0_DMA_MAX_STREAMS
#define ACP70_SDW_DMA_IRQ_MASK 0x1F800000
#define ACP70_P1_SDW_DMA_IRQ_MASK 0x1F8
#define ACP70_P1_AUDIO0_TX_THRESHOLD 0x8
#define ACP70_P1_AUDIO1_TX_THRESHOLD 0x6
#define ACP70_P1_AUDIO2_TX_THRESHOLD 0x4
#define ACP70_P1_AUDIO0_RX_THRESHOLD 0x7
#define ACP70_P1_AUDIO1_RX_THRESHOLD 0x5
#define ACP70_P1_AUDIO2_RX_THRESHOLD 0x3
#define ACP70_SDW0_DMA_TX_IRQ_MASK(i) (ACP_AUDIO0_TX_THRESHOLD - (2 * (i)))
#define ACP70_SDW0_DMA_RX_IRQ_MASK(i) (ACP_AUDIO0_RX_THRESHOLD - (2 * ((i) - 3)))
/*
* Below entries describes SDW1 instance DMA stream id and DMA irq bit mapping
* in ACP_EXTENAL_INTR_CNTL1 register for ACP70/ACP71 platforms
* Stream id IRQ Bit
* 0 (SDW1_AUDIO0_TX) 8
* 1 (SDW1_AUDIO1_TX) 6
* 2 (SDW1_AUDIO2_TX) 4
* 3 (SDW1_AUDIO0_RX) 7
* 4 (SDW1_AUDIO1_RX) 5
* 5 (SDW1_AUDIO2_RX) 3
*/
#define ACP70_SDW1_DMA_TX_IRQ_MASK(i) (ACP70_P1_AUDIO0_TX_THRESHOLD - (2 * (i)))
#define ACP70_SDW1_DMA_RX_IRQ_MASK(i) (ACP70_P1_AUDIO0_RX_THRESHOLD - (2 * ((i) - 3)))
#define ACP70_SW0_AUDIO0_TX_EN ACP_SW0_AUDIO0_TX_EN
#define ACP70_SW0_AUDIO1_TX_EN ACP_SW0_AUDIO1_TX_EN
#define ACP70_SW0_AUDIO2_TX_EN ACP_SW0_AUDIO2_TX_EN
#define ACP70_SW0_AUDIO0_RX_EN ACP_SW0_AUDIO0_RX_EN
#define ACP70_SW0_AUDIO1_RX_EN ACP_SW0_AUDIO1_RX_EN
#define ACP70_SW0_AUDIO2_RX_EN ACP_SW0_AUDIO2_RX_EN
#define ACP70_SW1_AUDIO0_TX_EN 0x0003C10
#define ACP70_SW1_AUDIO1_TX_EN 0x0003C50
#define ACP70_SW1_AUDIO2_TX_EN 0x0003C6C
#define ACP70_SW1_AUDIO0_RX_EN 0x0003C88
#define ACP70_SW1_AUDIO1_RX_EN 0x0003D28
#define ACP70_SW1_AUDIO2_RX_EN 0x0003D44
enum acp_config {
ACP_CONFIG_0 = 0,
ACP_CONFIG_1,
ACP_CONFIG_2,
ACP_CONFIG_3,
ACP_CONFIG_4,
ACP_CONFIG_5,
ACP_CONFIG_6,
ACP_CONFIG_7,
ACP_CONFIG_8,
ACP_CONFIG_9,
ACP_CONFIG_10,
ACP_CONFIG_11,
ACP_CONFIG_12,
ACP_CONFIG_13,
ACP_CONFIG_14,
ACP_CONFIG_15,
ACP_CONFIG_16,
ACP_CONFIG_17,
ACP_CONFIG_18,
ACP_CONFIG_19,
ACP_CONFIG_20,
};
enum amd_acp63_sdw0_channel {
ACP63_SDW0_AUDIO0_TX = 0,
ACP63_SDW0_AUDIO1_TX,
ACP63_SDW0_AUDIO2_TX,
ACP63_SDW0_AUDIO0_RX,
ACP63_SDW0_AUDIO1_RX,
ACP63_SDW0_AUDIO2_RX,
};
enum amd_acp63_sdw1_channel {
ACP63_SDW1_AUDIO1_TX,
ACP63_SDW1_AUDIO1_RX,
};
enum amd_acp70_sdw_channel {
ACP70_SDW_AUDIO0_TX = 0,
ACP70_SDW_AUDIO1_TX,
ACP70_SDW_AUDIO2_TX,
ACP70_SDW_AUDIO0_RX,
ACP70_SDW_AUDIO1_RX,
ACP70_SDW_AUDIO2_RX,
};
struct pdm_stream_instance {
u16 num_pages;
u16 channels;
dma_addr_t dma_addr;
u64 bytescount;
void __iomem *acp63_base;
};
struct pdm_dev_data {
u32 pdm_irq;
void __iomem *acp63_base;
struct mutex *acp_lock;
struct snd_pcm_substream *capture_stream;
};
struct sdw_dma_dev_data {
void __iomem *acp_base;
struct mutex *acp_lock; /* used to protect acp common register access */
u32 acp_rev;
struct snd_pcm_substream *acp63_sdw0_dma_stream[ACP63_SDW0_DMA_MAX_STREAMS];
struct snd_pcm_substream *acp63_sdw1_dma_stream[ACP63_SDW1_DMA_MAX_STREAMS];
struct snd_pcm_substream *acp70_sdw0_dma_stream[ACP70_SDW0_DMA_MAX_STREAMS];
struct snd_pcm_substream *acp70_sdw1_dma_stream[ACP70_SDW1_DMA_MAX_STREAMS];
};
struct acp_sdw_dma_stream {
u16 num_pages;
u16 channels;
u32 stream_id;
u32 instance;
dma_addr_t dma_addr;
u64 bytescount;
};
union acp_sdw_dma_count {
struct {
u32 low;
u32 high;
} bcount;
u64 bytescount;
};
struct sdw_dma_ring_buf_reg {
u32 reg_dma_size;
u32 reg_fifo_addr;
u32 reg_fifo_size;
u32 reg_ring_buf_size;
u32 reg_ring_buf_addr;
u32 water_mark_size_reg;
u32 pos_low_reg;
u32 pos_high_reg;
};
struct acp63_dev_data;
/**
* struct acp_hw_ops - ACP PCI driver platform specific ops
* @acp_init: ACP initialization
* @acp_deinit: ACP de-initialization
* @acp_get_config: function to read the acp pin configuration
* @acp_sdw_dma_irq_thread: ACP SoundWire DMA interrupt thread
* acp_suspend: ACP system level suspend callback
* acp_resume: ACP system level resume callback
* acp_suspend_runtime: ACP runtime suspend callback
* acp_resume_runtime: ACP runtime resume callback
*/
struct acp_hw_ops {
int (*acp_init)(void __iomem *acp_base, struct device *dev);
int (*acp_deinit)(void __iomem *acp_base, struct device *dev);
void (*acp_get_config)(struct pci_dev *pci, struct acp63_dev_data *acp_data);
void (*acp_sdw_dma_irq_thread)(struct acp63_dev_data *acp_data);
int (*acp_suspend)(struct device *dev);
int (*acp_resume)(struct device *dev);
int (*acp_suspend_runtime)(struct device *dev);
int (*acp_resume_runtime)(struct device *dev);
};
/**
* struct acp63_dev_data - acp pci driver context
* @acp63_base: acp mmio base
* @res: resource
* @hw_ops: ACP pci driver platform-specific ops
* @pdm_dev: ACP PDM controller platform device
* @dmic_codec: platform device for DMIC Codec
* sdw_dma_dev: platform device for SoundWire DMA controller
* @mach_dev: platform device for machine driver to support ACP PDM/SoundWire configuration
* @acp_lock: used to protect acp common registers
* @info: SoundWire AMD information found in ACPI tables
* @sdw: SoundWire context for all SoundWire manager instances
* @machine: ACPI machines for SoundWire interface
* @is_sdw_dev: flag set to true when any SoundWire manager instances are available
* @is_pdm_dev: flag set to true when ACP PDM controller exists
* @is_pdm_config: flat set to true when PDM configuration is selected from BIOS
* @is_sdw_config: flag set to true when SDW configuration is selected from BIOS
* @sdw_en_stat: flag set to true when any one of the SoundWire manager instance is enabled
* @acp70_sdw0_wake_event: flag set to true when wake irq asserted for SW0 instance
* @acp70_sdw1_wake_event: flag set to true when wake irq asserted for SW1 instance
* @addr: pci ioremap address
* @reg_range: ACP reigister range
* @acp_rev: ACP PCI revision id
* @acp_sw_pad_keeper_en: store acp SoundWire pad keeper enable register value
* @acp_pad_pulldown_ctrl: store acp pad pulldown control register value
* @acp63_sdw0-dma_intr_stat: DMA interrupt status array for ACP6.3 platform SoundWire
* manager-SW0 instance
* @acp63_sdw_dma_intr_stat: DMA interrupt status array for ACP6.3 platform SoundWire
* manager-SW1 instance
* @acp70_sdw0-dma_intr_stat: DMA interrupt status array for ACP7.0 platform SoundWire
* manager-SW0 instance
* @acp70_sdw_dma_intr_stat: DMA interrupt status array for ACP7.0 platform SoundWire
* manager-SW1 instance
*/
struct acp63_dev_data {
void __iomem *acp63_base;
struct resource *res;
struct acp_hw_ops *hw_ops;
struct platform_device *pdm_dev;
struct platform_device *dmic_codec_dev;
struct platform_device *sdw_dma_dev;
struct platform_device *mach_dev;
struct mutex acp_lock; /* protect shared registers */
struct sdw_amd_acpi_info info;
/* sdw context allocated by SoundWire driver */
struct sdw_amd_ctx *sdw;
struct snd_soc_acpi_mach *machines;
bool is_sdw_dev;
bool is_pdm_dev;
bool is_pdm_config;
bool is_sdw_config;
bool sdw_en_stat;
bool acp70_sdw0_wake_event;
bool acp70_sdw1_wake_event;
u32 addr;
u32 reg_range;
u32 acp_rev;
u32 subsystem_vendor;
u32 subsystem_device;
u32 acp_sw_pad_keeper_en;
u32 acp_pad_pulldown_ctrl;
u16 acp63_sdw0_dma_intr_stat[ACP63_SDW0_DMA_MAX_STREAMS];
u16 acp63_sdw1_dma_intr_stat[ACP63_SDW1_DMA_MAX_STREAMS];
u16 acp70_sdw0_dma_intr_stat[ACP70_SDW0_DMA_MAX_STREAMS];
u16 acp70_sdw1_dma_intr_stat[ACP70_SDW1_DMA_MAX_STREAMS];
};
void acp63_hw_init_ops(struct acp_hw_ops *hw_ops);
void acp70_hw_init_ops(struct acp_hw_ops *hw_ops);
static inline int acp_hw_init(struct acp63_dev_data *adata, struct device *dev)
{
if (adata && adata->hw_ops && adata->hw_ops->acp_init)
return ACP_HW_OPS(adata, acp_init)(adata->acp63_base, dev);
return -EOPNOTSUPP;
}
static inline int acp_hw_deinit(struct acp63_dev_data *adata, struct device *dev)
{
if (adata && adata->hw_ops && adata->hw_ops->acp_deinit)
return ACP_HW_OPS(adata, acp_deinit)(adata->acp63_base, dev);
return -EOPNOTSUPP;
}
static inline void acp_hw_get_config(struct pci_dev *pci, struct acp63_dev_data *adata)
{
if (adata && adata->hw_ops && adata->hw_ops->acp_get_config)
ACP_HW_OPS(adata, acp_get_config)(pci, adata);
}
static inline void acp_hw_sdw_dma_irq_thread(struct acp63_dev_data *adata)
{
if (adata && adata->hw_ops && adata->hw_ops->acp_sdw_dma_irq_thread)
ACP_HW_OPS(adata, acp_sdw_dma_irq_thread)(adata);
}
static inline int acp_hw_suspend(struct device *dev)
{
struct acp63_dev_data *adata = dev_get_drvdata(dev);
if (adata && adata->hw_ops && adata->hw_ops->acp_suspend)
return ACP_HW_OPS(adata, acp_suspend)(dev);
return -EOPNOTSUPP;
}
static inline int acp_hw_resume(struct device *dev)
{
struct acp63_dev_data *adata = dev_get_drvdata(dev);
if (adata && adata->hw_ops && adata->hw_ops->acp_resume)
return ACP_HW_OPS(adata, acp_resume)(dev);
return -EOPNOTSUPP;
}
static inline int acp_hw_suspend_runtime(struct device *dev)
{
struct acp63_dev_data *adata = dev_get_drvdata(dev);
if (adata && adata->hw_ops && adata->hw_ops->acp_suspend_runtime)
return ACP_HW_OPS(adata, acp_suspend_runtime)(dev);
return -EOPNOTSUPP;
}
static inline int acp_hw_runtime_resume(struct device *dev)
{
struct acp63_dev_data *adata = dev_get_drvdata(dev);
if (adata && adata->hw_ops && adata->hw_ops->acp_resume_runtime)
return ACP_HW_OPS(adata, acp_resume_runtime)(dev);
return -EOPNOTSUPP;
}
int snd_amd_acp_find_config(struct pci_dev *pci);
+785
View File
@@ -0,0 +1,785 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD common ACP PCI driver for ACP6.3, ACP7.0 & ACP7.1 platforms.
*
* Copyright 2022, 2025 Advanced Micro Devices, Inc.
*/
#include <linux/pci.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/interrupt.h>
#include <sound/pcm_params.h>
#include <linux/pm_runtime.h>
#include <linux/iopoll.h>
#include <linux/soundwire/sdw_amd.h>
#include "../mach-config.h"
#include "acp63.h"
static void handle_acp70_sdw_wake_event(struct acp63_dev_data *adata)
{
struct amd_sdw_manager *amd_manager;
if (adata->acp70_sdw0_wake_event) {
amd_manager = dev_get_drvdata(&adata->sdw->pdev[0]->dev);
if (amd_manager)
pm_request_resume(amd_manager->dev);
adata->acp70_sdw0_wake_event = 0;
}
if (adata->acp70_sdw1_wake_event) {
amd_manager = dev_get_drvdata(&adata->sdw->pdev[1]->dev);
if (amd_manager)
pm_request_resume(amd_manager->dev);
adata->acp70_sdw1_wake_event = 0;
}
}
static short int check_and_handle_acp70_sdw_wake_irq(struct acp63_dev_data *adata)
{
u32 ext_intr_stat1;
int irq_flag = 0;
bool sdw_wake_irq = false;
ext_intr_stat1 = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
if (ext_intr_stat1 & ACP70_SDW0_HOST_WAKE_STAT) {
writel(ACP70_SDW0_HOST_WAKE_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp70_sdw0_wake_event = true;
sdw_wake_irq = true;
}
if (ext_intr_stat1 & ACP70_SDW1_HOST_WAKE_STAT) {
writel(ACP70_SDW1_HOST_WAKE_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp70_sdw1_wake_event = true;
sdw_wake_irq = true;
}
if (ext_intr_stat1 & ACP70_SDW0_PME_STAT) {
writel(0, adata->acp63_base + ACP_SW0_WAKE_EN);
writel(ACP70_SDW0_PME_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp70_sdw0_wake_event = true;
sdw_wake_irq = true;
}
if (ext_intr_stat1 & ACP70_SDW1_PME_STAT) {
writel(0, adata->acp63_base + ACP_SW1_WAKE_EN);
writel(ACP70_SDW1_PME_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp70_sdw1_wake_event = true;
sdw_wake_irq = true;
}
if (sdw_wake_irq) {
handle_acp70_sdw_wake_event(adata);
irq_flag = 1;
}
return irq_flag;
}
static short int check_and_handle_sdw_dma_irq(struct acp63_dev_data *adata, u32 ext_intr_stat,
u32 ext_intr_stat1)
{
u32 stream_id = 0;
u16 sdw_dma_irq_flag = 0;
u16 index;
if (ext_intr_stat & ACP63_SDW_DMA_IRQ_MASK) {
for (index = ACP_AUDIO2_RX_THRESHOLD; index <= ACP_AUDIO0_TX_THRESHOLD; index++) {
if (ext_intr_stat & BIT(index)) {
writel(BIT(index), adata->acp63_base + ACP_EXTERNAL_INTR_STAT);
switch (index) {
case ACP_AUDIO0_TX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO0_TX;
break;
case ACP_AUDIO1_TX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO1_TX;
break;
case ACP_AUDIO2_TX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO2_TX;
break;
case ACP_AUDIO0_RX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO0_RX;
break;
case ACP_AUDIO1_RX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO1_RX;
break;
case ACP_AUDIO2_RX_THRESHOLD:
stream_id = ACP63_SDW0_AUDIO2_RX;
break;
}
switch (adata->acp_rev) {
case ACP63_PCI_REV:
adata->acp63_sdw0_dma_intr_stat[stream_id] = 1;
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
adata->acp70_sdw0_dma_intr_stat[stream_id] = 1;
break;
}
sdw_dma_irq_flag = 1;
}
}
}
switch (adata->acp_rev) {
case ACP63_PCI_REV:
if (ext_intr_stat1 & ACP63_P1_AUDIO1_RX_THRESHOLD) {
writel(ACP63_P1_AUDIO1_RX_THRESHOLD,
adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp63_sdw1_dma_intr_stat[ACP63_SDW1_AUDIO1_RX] = 1;
sdw_dma_irq_flag = 1;
}
if (ext_intr_stat1 & ACP63_P1_AUDIO1_TX_THRESHOLD) {
writel(ACP63_P1_AUDIO1_TX_THRESHOLD,
adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
adata->acp63_sdw1_dma_intr_stat[ACP63_SDW1_AUDIO1_TX] = 1;
sdw_dma_irq_flag = 1;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
if (ext_intr_stat1 & ACP70_P1_SDW_DMA_IRQ_MASK) {
for (index = ACP70_P1_AUDIO2_RX_THRESHOLD;
index <= ACP70_P1_AUDIO0_TX_THRESHOLD; index++) {
if (ext_intr_stat1 & BIT(index)) {
writel(BIT(index),
adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
switch (index) {
case ACP70_P1_AUDIO0_TX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO0_TX;
break;
case ACP70_P1_AUDIO1_TX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO1_TX;
break;
case ACP70_P1_AUDIO2_TX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO2_TX;
break;
case ACP70_P1_AUDIO0_RX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO0_RX;
break;
case ACP70_P1_AUDIO1_RX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO1_RX;
break;
case ACP70_P1_AUDIO2_RX_THRESHOLD:
stream_id = ACP70_SDW_AUDIO2_RX;
break;
}
adata->acp70_sdw1_dma_intr_stat[stream_id] = 1;
sdw_dma_irq_flag = 1;
}
}
}
break;
}
return sdw_dma_irq_flag;
}
static irqreturn_t acp63_irq_thread(int irq, void *context)
{
struct acp63_dev_data *adata = context;
acp_hw_sdw_dma_irq_thread(adata);
return IRQ_HANDLED;
}
static irqreturn_t acp63_irq_handler(int irq, void *dev_id)
{
struct acp63_dev_data *adata;
struct pdm_dev_data *ps_pdm_data;
struct amd_sdw_manager *amd_manager;
u32 ext_intr_stat, ext_intr_stat1;
u16 irq_flag = 0;
u16 wake_irq_flag = 0;
u16 sdw_dma_irq_flag = 0;
adata = dev_id;
if (!adata)
return IRQ_NONE;
/* ACP interrupts will be cleared by reading particular bit and writing
* same value to the status register. writing zero's doesn't have any
* effect.
* Bit by bit checking of IRQ field is implemented.
*/
ext_intr_stat = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT);
if (ext_intr_stat & ACP_SDW0_STAT) {
writel(ACP_SDW0_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT);
amd_manager = dev_get_drvdata(&adata->sdw->pdev[0]->dev);
if (amd_manager)
schedule_work(&amd_manager->amd_sdw_irq_thread);
irq_flag = 1;
}
ext_intr_stat1 = readl(adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
if (ext_intr_stat1 & ACP_SDW1_STAT) {
writel(ACP_SDW1_STAT, adata->acp63_base + ACP_EXTERNAL_INTR_STAT1);
amd_manager = dev_get_drvdata(&adata->sdw->pdev[1]->dev);
if (amd_manager)
schedule_work(&amd_manager->amd_sdw_irq_thread);
irq_flag = 1;
}
if (ext_intr_stat & ACP_ERROR_IRQ) {
writel(ACP_ERROR_IRQ, adata->acp63_base + ACP_EXTERNAL_INTR_STAT);
/* TODO: Report SoundWire Manager instance errors */
writel(0, adata->acp63_base + ACP_SW0_I2S_ERROR_REASON);
writel(0, adata->acp63_base + ACP_SW1_I2S_ERROR_REASON);
writel(0, adata->acp63_base + ACP_ERROR_STATUS);
irq_flag = 1;
}
if (adata->acp_rev >= ACP70_PCI_REV)
wake_irq_flag = check_and_handle_acp70_sdw_wake_irq(adata);
if (ext_intr_stat & BIT(PDM_DMA_STAT)) {
ps_pdm_data = dev_get_drvdata(&adata->pdm_dev->dev);
writel(BIT(PDM_DMA_STAT), adata->acp63_base + ACP_EXTERNAL_INTR_STAT);
if (ps_pdm_data->capture_stream)
snd_pcm_period_elapsed(ps_pdm_data->capture_stream);
irq_flag = 1;
}
sdw_dma_irq_flag = check_and_handle_sdw_dma_irq(adata, ext_intr_stat, ext_intr_stat1);
if (sdw_dma_irq_flag)
return IRQ_WAKE_THREAD;
if (irq_flag || wake_irq_flag)
return IRQ_HANDLED;
else
return IRQ_NONE;
}
#if IS_ENABLED(CONFIG_SND_SOC_AMD_SOUNDWIRE)
static int acp_scan_sdw_devices(struct device *dev, u64 addr)
{
struct acpi_device *sdw_dev;
struct acp63_dev_data *acp_data;
acp_data = dev_get_drvdata(dev);
if (!addr)
return -ENODEV;
sdw_dev = acpi_find_child_device(ACPI_COMPANION(dev), addr, 0);
if (!sdw_dev)
return -ENODEV;
acp_data->info.handle = sdw_dev->handle;
acp_data->info.count = AMD_SDW_MAX_MANAGERS;
return amd_sdw_scan_controller(&acp_data->info);
}
static int amd_sdw_probe(struct device *dev)
{
struct acp63_dev_data *acp_data;
struct sdw_amd_res sdw_res;
int ret;
acp_data = dev_get_drvdata(dev);
memset(&sdw_res, 0, sizeof(sdw_res));
sdw_res.addr = acp_data->addr;
sdw_res.reg_range = acp_data->reg_range;
sdw_res.handle = acp_data->info.handle;
sdw_res.parent = dev;
sdw_res.dev = dev;
sdw_res.acp_lock = &acp_data->acp_lock;
sdw_res.count = acp_data->info.count;
sdw_res.mmio_base = acp_data->acp63_base;
sdw_res.acp_rev = acp_data->acp_rev;
sdw_res.link_mask = acp_data->info.link_mask;
ret = sdw_amd_probe(&sdw_res, &acp_data->sdw);
if (ret)
dev_err(dev, "error: SoundWire probe failed\n");
return ret;
}
static int amd_sdw_exit(struct acp63_dev_data *acp_data)
{
if (acp_data->sdw)
sdw_amd_exit(acp_data->sdw);
acp_data->sdw = NULL;
return 0;
}
static struct snd_soc_acpi_mach *acp63_sdw_machine_select(struct device *dev)
{
struct snd_soc_acpi_mach *mach;
const struct snd_soc_acpi_link_adr *link;
struct acp63_dev_data *acp_data = dev_get_drvdata(dev);
int ret, i;
if (acp_data->info.count) {
ret = sdw_amd_get_slave_info(acp_data->sdw);
if (ret) {
dev_dbg(dev, "failed to read slave information\n");
return NULL;
}
for (mach = acp_data->machines; mach; mach++) {
if (!mach->links)
break;
link = mach->links;
for (i = 0; i < acp_data->info.count && link->num_adr; link++, i++) {
if (!snd_soc_acpi_sdw_link_slaves_found(dev, link,
acp_data->sdw->peripherals))
break;
}
if (i == acp_data->info.count || !link->num_adr)
if (!mach->machine_check || mach->machine_check(acp_data->sdw))
break;
}
if (mach && mach->link_mask) {
mach->mach_params.links = mach->links;
mach->mach_params.link_mask = mach->link_mask;
mach->mach_params.subsystem_rev = acp_data->acp_rev;
mach->mach_params.subsystem_vendor = acp_data->subsystem_vendor;
mach->mach_params.subsystem_device = acp_data->subsystem_device;
mach->mach_params.subsystem_id_set = true;
dev_dbg(dev, "SSID %x%04x\n", mach->mach_params.subsystem_vendor,
mach->mach_params.subsystem_device);
return mach;
}
}
dev_dbg(dev, "No SoundWire machine driver found\n");
return NULL;
}
#else
static int acp_scan_sdw_devices(struct device *dev, u64 addr)
{
return 0;
}
static int amd_sdw_probe(struct device *dev)
{
return 0;
}
static int amd_sdw_exit(struct acp63_dev_data *acp_data)
{
return 0;
}
static struct snd_soc_acpi_mach *acp63_sdw_machine_select(struct device *dev)
{
return NULL;
}
#endif
static int acp63_machine_register(struct device *dev)
{
struct snd_soc_acpi_mach *mach;
struct acp63_dev_data *adata = dev_get_drvdata(dev);
int size;
if (adata->is_sdw_dev && adata->is_sdw_config) {
size = sizeof(*adata->machines);
mach = acp63_sdw_machine_select(dev);
if (mach) {
adata->mach_dev = platform_device_register_data(dev, mach->drv_name,
PLATFORM_DEVID_NONE, mach,
size);
if (IS_ERR(adata->mach_dev)) {
dev_err(dev,
"cannot register Machine device for SoundWire Interface\n");
return PTR_ERR(adata->mach_dev);
}
}
} else if (adata->is_pdm_dev && !adata->is_sdw_dev && adata->is_pdm_config) {
adata->mach_dev = platform_device_register_data(dev, "acp_ps_mach",
PLATFORM_DEVID_NONE, NULL, 0);
if (IS_ERR(adata->mach_dev)) {
dev_err(dev, "cannot register amd_ps_mach device\n");
return PTR_ERR(adata->mach_dev);
}
}
return 0;
}
static int get_acp63_device_config(struct pci_dev *pci, struct acp63_dev_data *acp_data)
{
struct acpi_device *pdm_dev;
const union acpi_object *obj;
acpi_handle handle;
acpi_integer dmic_status;
bool is_dmic_dev = false;
bool is_sdw_dev = false;
bool wov_en, dmic_en;
int ret;
/* IF WOV entry not found, enable dmic based on acp-audio-device-type entry*/
wov_en = true;
dmic_en = false;
acp_hw_get_config(pci, acp_data);
if (acp_data->is_pdm_config) {
pdm_dev = acpi_find_child_device(ACPI_COMPANION(&pci->dev), ACP63_DMIC_ADDR, 0);
if (pdm_dev) {
/* is_dmic_dev flag will be set when ACP PDM controller device exists */
if (!acpi_dev_get_property(pdm_dev, "acp-audio-device-type",
ACPI_TYPE_INTEGER, &obj) &&
obj->integer.value == ACP_DMIC_DEV)
dmic_en = true;
}
handle = ACPI_HANDLE(&pci->dev);
ret = acpi_evaluate_integer(handle, "_WOV", NULL, &dmic_status);
if (!ACPI_FAILURE(ret))
wov_en = dmic_status;
}
if (dmic_en && wov_en)
is_dmic_dev = true;
if (acp_data->is_sdw_config) {
ret = acp_scan_sdw_devices(&pci->dev, ACP63_SDW_ADDR);
if (!ret && acp_data->info.link_mask)
is_sdw_dev = true;
}
acp_data->is_pdm_dev = is_dmic_dev;
acp_data->is_sdw_dev = is_sdw_dev;
if (!is_dmic_dev && !is_sdw_dev) {
dev_dbg(&pci->dev, "No PDM or SoundWire manager devices found\n");
return -ENODEV;
}
return 0;
}
static void acp63_fill_platform_dev_info(struct platform_device_info *pdevinfo,
struct device *parent,
struct fwnode_handle *fw_node,
char *name, unsigned int id,
const struct resource *res,
unsigned int num_res,
const void *data,
size_t size_data)
{
pdevinfo->name = name;
pdevinfo->id = id;
pdevinfo->parent = parent;
pdevinfo->num_res = num_res;
pdevinfo->res = res;
pdevinfo->data = data;
pdevinfo->size_data = size_data;
pdevinfo->fwnode = fw_node;
}
static int create_acp63_platform_devs(struct pci_dev *pci, struct acp63_dev_data *adata, u32 addr)
{
struct platform_device_info pdevinfo;
struct device *parent;
int ret;
parent = &pci->dev;
if (adata->is_sdw_dev || adata->is_pdm_dev) {
adata->res = devm_kzalloc(&pci->dev, sizeof(struct resource), GFP_KERNEL);
if (!adata->res) {
ret = -ENOMEM;
goto de_init;
}
adata->res->flags = IORESOURCE_MEM;
adata->res->start = addr;
adata->res->end = addr + (ACP63_REG_END - ACP63_REG_START);
memset(&pdevinfo, 0, sizeof(pdevinfo));
}
if (adata->is_pdm_dev && adata->is_pdm_config) {
acp63_fill_platform_dev_info(&pdevinfo, parent, NULL, "acp_ps_pdm_dma",
0, adata->res, 1, NULL, 0);
adata->pdm_dev = platform_device_register_full(&pdevinfo);
if (IS_ERR(adata->pdm_dev)) {
dev_err(&pci->dev,
"cannot register %s device\n", pdevinfo.name);
ret = PTR_ERR(adata->pdm_dev);
goto de_init;
}
memset(&pdevinfo, 0, sizeof(pdevinfo));
acp63_fill_platform_dev_info(&pdevinfo, parent, NULL, "dmic-codec",
0, NULL, 0, NULL, 0);
adata->dmic_codec_dev = platform_device_register_full(&pdevinfo);
if (IS_ERR(adata->dmic_codec_dev)) {
dev_err(&pci->dev,
"cannot register %s device\n", pdevinfo.name);
ret = PTR_ERR(adata->dmic_codec_dev);
goto unregister_pdm_dev;
}
}
if (adata->is_sdw_dev && adata->is_sdw_config) {
ret = amd_sdw_probe(&pci->dev);
if (ret) {
if (adata->is_pdm_dev)
goto unregister_dmic_codec_dev;
else
goto de_init;
}
memset(&pdevinfo, 0, sizeof(pdevinfo));
acp63_fill_platform_dev_info(&pdevinfo, parent, NULL, "amd_ps_sdw_dma",
0, adata->res, 1, NULL, 0);
adata->sdw_dma_dev = platform_device_register_full(&pdevinfo);
if (IS_ERR(adata->sdw_dma_dev)) {
dev_err(&pci->dev,
"cannot register %s device\n", pdevinfo.name);
ret = PTR_ERR(adata->sdw_dma_dev);
if (adata->is_pdm_dev)
goto unregister_dmic_codec_dev;
else
goto de_init;
}
}
return 0;
unregister_dmic_codec_dev:
platform_device_unregister(adata->dmic_codec_dev);
unregister_pdm_dev:
platform_device_unregister(adata->pdm_dev);
de_init:
if (acp_hw_deinit(adata, &pci->dev))
dev_err(&pci->dev, "ACP de-init failed\n");
return ret;
}
static int acp_hw_init_ops(struct acp63_dev_data *adata, struct pci_dev *pci)
{
adata->hw_ops = devm_kzalloc(&pci->dev, sizeof(struct acp_hw_ops),
GFP_KERNEL);
if (!adata->hw_ops)
return -ENOMEM;
switch (adata->acp_rev) {
case ACP63_PCI_REV:
acp63_hw_init_ops(adata->hw_ops);
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
acp70_hw_init_ops(adata->hw_ops);
break;
default:
dev_err(&pci->dev, "ACP device not found\n");
return -ENODEV;
}
return 0;
}
static int snd_acp63_probe(struct pci_dev *pci,
const struct pci_device_id *pci_id)
{
struct acp63_dev_data *adata;
u32 addr;
u32 irqflags, flag;
int ret;
irqflags = IRQF_SHARED;
/* Return if acp config flag is defined */
flag = snd_amd_acp_find_config(pci);
if (flag)
return -ENODEV;
/* ACP PCI revision id check for ACP6.3, ACP7.0 & ACP7.1 platforms */
switch (pci->revision) {
case ACP63_PCI_REV:
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
break;
default:
dev_dbg(&pci->dev, "acp63/acp70/acp71 pci device not found\n");
return -ENODEV;
}
if (pci_enable_device(pci)) {
dev_err(&pci->dev, "pci_enable_device failed\n");
return -ENODEV;
}
ret = pci_request_regions(pci, "AMD ACP6.3 audio");
if (ret < 0) {
dev_err(&pci->dev, "pci_request_regions failed\n");
goto disable_pci;
}
adata = devm_kzalloc(&pci->dev, sizeof(struct acp63_dev_data),
GFP_KERNEL);
if (!adata) {
ret = -ENOMEM;
goto release_regions;
}
addr = pci_resource_start(pci, 0);
adata->acp63_base = devm_ioremap(&pci->dev, addr,
pci_resource_len(pci, 0));
if (!adata->acp63_base) {
ret = -ENOMEM;
goto release_regions;
}
adata->addr = addr;
adata->reg_range = ACP63_REG_END - ACP63_REG_START;
adata->acp_rev = pci->revision;
adata->subsystem_vendor = pci->subsystem_vendor;
adata->subsystem_device = pci->subsystem_device;
pci_set_master(pci);
pci_set_drvdata(pci, adata);
mutex_init(&adata->acp_lock);
ret = acp_hw_init_ops(adata, pci);
if (ret) {
dev_err(&pci->dev, "ACP hw ops init failed\n");
goto release_regions;
}
ret = acp_hw_init(adata, &pci->dev);
if (ret)
goto release_regions;
ret = devm_request_threaded_irq(&pci->dev, pci->irq, acp63_irq_handler,
acp63_irq_thread, irqflags, "ACP_PCI_IRQ", adata);
if (ret) {
dev_err(&pci->dev, "ACP PCI IRQ request failed\n");
goto de_init;
}
ret = get_acp63_device_config(pci, adata);
/* ACP PCI driver probe should be continued even PDM or SoundWire Devices are not found */
if (ret) {
dev_dbg(&pci->dev, "get acp device config failed:%d\n", ret);
goto skip_pdev_creation;
}
ret = create_acp63_platform_devs(pci, adata, addr);
if (ret < 0) {
dev_err(&pci->dev, "ACP platform devices creation failed\n");
goto de_init;
}
if (adata->acp_rev >= ACP70_PCI_REV)
adata->machines = snd_soc_acpi_amd_acp70_sdw_machines;
else
adata->machines = snd_soc_acpi_amd_acp63_sdw_machines;
ret = acp63_machine_register(&pci->dev);
if (ret) {
dev_err(&pci->dev, "ACP machine register failed\n");
goto de_init;
}
skip_pdev_creation:
device_set_wakeup_enable(&pci->dev, true);
pm_runtime_set_autosuspend_delay(&pci->dev, ACP_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pci->dev);
pm_runtime_put_noidle(&pci->dev);
pm_runtime_allow(&pci->dev);
return 0;
de_init:
if (acp_hw_deinit(adata, &pci->dev))
dev_err(&pci->dev, "ACP de-init failed\n");
release_regions:
pci_release_regions(pci);
disable_pci:
pci_disable_device(pci);
return ret;
}
static int snd_acp_suspend(struct device *dev)
{
return acp_hw_suspend(dev);
}
static int snd_acp_runtime_resume(struct device *dev)
{
return acp_hw_runtime_resume(dev);
}
static void acp_disable_msi_on_resume(struct pci_dev *pdev)
{
u16 control;
if (!pdev->msi_cap)
return;
pci_read_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, &control);
if (control & PCI_MSI_FLAGS_ENABLE) {
dev_warn(&pdev->dev,
"ACP: MSI unexpectedly enabled after resume (flags=0x%04x), disabling\n",
control);
control &= ~PCI_MSI_FLAGS_ENABLE;
pci_write_config_word(pdev, pdev->msi_cap + PCI_MSI_FLAGS, control);
}
}
static int snd_acp_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
/*
* BIOS/firmware may re-enable MSI in PCI config space during
* system resume even though this driver only uses legacy INTx
* interrupts. If MSI is left enabled with stale address/data
* registers, the device will write interrupts to a bogus address
* causing IOMMU IO_PAGE_FAULT and interrupt delivery failure.
* Explicitly clear the MSI Enable bit before reinitializing
* the ACP hardware.
*/
acp_disable_msi_on_resume(pdev);
return acp_hw_resume(dev);
}
static const struct dev_pm_ops acp63_pm_ops = {
RUNTIME_PM_OPS(snd_acp_suspend, snd_acp_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(snd_acp_suspend, snd_acp_resume)
};
static void snd_acp63_remove(struct pci_dev *pci)
{
struct acp63_dev_data *adata;
int ret;
adata = pci_get_drvdata(pci);
if (adata->sdw) {
amd_sdw_exit(adata);
platform_device_unregister(adata->sdw_dma_dev);
}
if (adata->is_pdm_dev) {
platform_device_unregister(adata->pdm_dev);
platform_device_unregister(adata->dmic_codec_dev);
}
if (adata->mach_dev)
platform_device_unregister(adata->mach_dev);
ret = acp_hw_deinit(adata, &pci->dev);
if (ret)
dev_err(&pci->dev, "ACP de-init failed\n");
pm_runtime_forbid(&pci->dev);
pm_runtime_get_noresume(&pci->dev);
pci_release_regions(pci);
pci_disable_device(pci);
}
static const struct pci_device_id snd_acp63_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, ACP_DEVICE_ID),
.class = PCI_CLASS_MULTIMEDIA_OTHER << 8,
.class_mask = 0xffffff },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, snd_acp63_ids);
static struct pci_driver ps_acp63_driver = {
.name = KBUILD_MODNAME,
.id_table = snd_acp63_ids,
.probe = snd_acp63_probe,
.remove = snd_acp63_remove,
.driver = {
.pm = pm_ptr(&acp63_pm_ops),
}
};
module_pci_driver(ps_acp63_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_AUTHOR("Syed.SabaKareem@amd.com");
MODULE_DESCRIPTION("AMD common ACP PCI driver for ACP6.3, ACP7.0 & ACP7.1 platforms");
MODULE_IMPORT_NS("SOUNDWIRE_AMD_INIT");
MODULE_IMPORT_NS("SND_AMD_SOUNDWIRE_ACPI");
MODULE_LICENSE("GPL v2");
+493
View File
@@ -0,0 +1,493 @@
// 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;
}
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Machine driver for AMD Pink Sardine platform using DMIC
*
* Copyright 2022 Advanced Micro Devices, Inc.
*/
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <linux/module.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <linux/io.h>
#include <linux/dmi.h>
#include "acp63.h"
#define DRV_NAME "acp_ps_mach"
SND_SOC_DAILINK_DEF(acp63_pdm,
DAILINK_COMP_ARRAY(COMP_CPU("acp_ps_pdm_dma.0")));
SND_SOC_DAILINK_DEF(dmic_codec,
DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec.0",
"dmic-hifi")));
SND_SOC_DAILINK_DEF(pdm_platform,
DAILINK_COMP_ARRAY(COMP_PLATFORM("acp_ps_pdm_dma.0")));
static struct snd_soc_dai_link acp63_dai_pdm[] = {
{
.name = "acp63-dmic-capture",
.stream_name = "DMIC capture",
.capture_only = 1,
SND_SOC_DAILINK_REG(acp63_pdm, dmic_codec, pdm_platform),
},
};
static struct snd_soc_card acp63_card = {
.name = "acp63",
.owner = THIS_MODULE,
.dai_link = acp63_dai_pdm,
.num_links = 1,
};
static int acp63_probe(struct platform_device *pdev)
{
struct snd_soc_card *card;
int ret;
platform_set_drvdata(pdev, &acp63_card);
card = platform_get_drvdata(pdev);
acp63_card.dev = &pdev->dev;
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
return dev_err_probe(&pdev->dev, ret,
"snd_soc_register_card(%s) failed\n",
card->name);
}
return 0;
}
static struct platform_driver acp63_mach_driver = {
.driver = {
.name = "acp_ps_mach",
.pm = &snd_soc_pm_ops,
},
.probe = acp63_probe,
};
module_platform_driver(acp63_mach_driver);
MODULE_AUTHOR("Syed.SabaKareem@amd.com");
MODULE_DESCRIPTION("AMD Pink Sardine support for DMIC");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
+464
View File
@@ -0,0 +1,464 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD ALSA SoC common PDM Driver for ACP6.3, ACP7.0 & ACP7.1 platforms.
*
* Copyright 2022, 2025 Advanced Micro Devices, Inc.
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/bitfield.h>
#include <linux/err.h>
#include <linux/io.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/pm_runtime.h>
#include "acp63.h"
#define DRV_NAME "acp_ps_pdm_dma"
static int pdm_gain = 3;
module_param(pdm_gain, int, 0644);
MODULE_PARM_DESC(pdm_gain, "Gain control (0-3)");
static const struct snd_pcm_hardware acp63_pdm_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
.periods_min = CAPTURE_MIN_NUM_PERIODS,
.periods_max = CAPTURE_MAX_NUM_PERIODS,
};
static void acp63_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size,
u32 watermark_size, void __iomem *acp_base)
{
writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR);
writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE);
writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);
writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL);
}
static void acp63_enable_pdm_clock(void __iomem *acp_base)
{
u32 pdm_clk_enable, pdm_ctrl;
pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK;
pdm_ctrl = 0x00;
writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL);
pdm_ctrl = readl(acp_base + ACP_WOV_MISC_CTRL);
pdm_ctrl &= ~ACP_WOV_GAIN_CONTROL;
pdm_ctrl |= FIELD_PREP(ACP_WOV_GAIN_CONTROL, clamp(pdm_gain, 0, 3));
writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL);
}
static void acp63_enable_pdm_interrupts(struct pdm_dev_data *adata)
{
u32 ext_int_ctrl;
mutex_lock(adata->acp_lock);
ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl |= PDM_DMA_INTR_MASK;
writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);
mutex_unlock(adata->acp_lock);
}
static void acp63_disable_pdm_interrupts(struct pdm_dev_data *adata)
{
u32 ext_int_ctrl;
mutex_lock(adata->acp_lock);
ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);
ext_int_ctrl &= ~PDM_DMA_INTR_MASK;
writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);
mutex_unlock(adata->acp_lock);
}
static bool acp63_check_pdm_dma_status(void __iomem *acp_base)
{
bool pdm_dma_status;
u32 pdm_enable, pdm_dma_enable;
pdm_dma_status = false;
pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS))
pdm_dma_status = true;
return pdm_dma_status;
}
static int acp63_start_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable;
u32 pdm_dma_enable;
int timeout;
pdm_enable = 0x01;
pdm_dma_enable = 0x01;
acp63_enable_pdm_clock(acp_base);
writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS)
return 0;
udelay(DELAY_US);
}
return -ETIMEDOUT;
}
static int acp63_stop_pdm_dma(void __iomem *acp_base)
{
u32 pdm_enable, pdm_dma_enable;
int timeout;
pdm_enable = 0x00;
pdm_dma_enable = 0x00;
pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE);
pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if (pdm_dma_enable & 0x01) {
pdm_dma_enable = 0x02;
writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);
timeout = 0;
while (++timeout < ACP_COUNTER) {
pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);
if ((pdm_dma_enable & 0x02) == 0x00)
break;
udelay(DELAY_US);
}
if (timeout == ACP_COUNTER)
return -ETIMEDOUT;
}
if (pdm_enable == ACP_PDM_ENABLE) {
pdm_enable = ACP_PDM_DISABLE;
writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);
}
writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH);
return 0;
}
static void acp63_config_dma(struct pdm_stream_instance *rtd, int direction)
{
u16 page_idx;
u32 low, high, val;
dma_addr_t addr;
addr = rtd->dma_addr;
val = PDM_PTE_OFFSET;
/* Group Enable */
writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp63_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
writel(PAGE_SIZE_4K_ENABLE, rtd->acp63_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
writel(low, rtd->acp63_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
writel(high, rtd->acp63_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
}
}
static int acp63_pdm_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct pdm_dev_data *adata;
struct pdm_stream_instance *pdm_data;
int ret;
runtime = substream->runtime;
adata = dev_get_drvdata(component->dev);
pdm_data = kzalloc_obj(*pdm_data);
if (!pdm_data)
return -EINVAL;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
runtime->hw = acp63_pdm_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(pdm_data);
return ret;
}
acp63_enable_pdm_interrupts(adata);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
adata->capture_stream = substream;
pdm_data->acp63_base = adata->acp63_base;
runtime->private_data = pdm_data;
return ret;
}
static int acp63_pdm_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct pdm_stream_instance *rtd;
size_t size, period_bytes;
rtd = substream->runtime->private_data;
if (!rtd)
return -EINVAL;
size = params_buffer_bytes(params);
period_bytes = params_period_bytes(params);
rtd->dma_addr = substream->runtime->dma_addr;
rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
acp63_config_dma(rtd, substream->stream);
acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size,
period_bytes, rtd->acp63_base);
return 0;
}
static u64 acp63_pdm_get_byte_count(struct pdm_stream_instance *rtd,
int direction)
{
u32 high, low;
u64 byte_count;
high = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH);
byte_count = high;
low = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW);
byte_count = (byte_count << 32) | low;
return byte_count;
}
static snd_pcm_uframes_t acp63_pdm_dma_pointer(struct snd_soc_component *comp,
struct snd_pcm_substream *stream)
{
struct pdm_stream_instance *rtd;
u32 pos, buffersize;
u64 bytescount;
rtd = stream->runtime->private_data;
buffersize = frames_to_bytes(stream->runtime,
stream->runtime->buffer_size);
bytescount = acp63_pdm_get_byte_count(rtd, stream->stream);
if (bytescount > rtd->bytescount)
bytescount -= rtd->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(stream->runtime, pos);
}
static int acp63_pdm_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, MIN_BUFFER, MAX_BUFFER);
return 0;
}
static int acp63_pdm_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct pdm_dev_data *adata = dev_get_drvdata(component->dev);
struct snd_pcm_runtime *runtime = substream->runtime;
acp63_disable_pdm_interrupts(adata);
adata->capture_stream = NULL;
kfree(runtime->private_data);
return 0;
}
static int acp63_pdm_dai_trigger(struct snd_pcm_substream *substream,
int cmd, struct snd_soc_dai *dai)
{
struct pdm_stream_instance *rtd;
int ret;
bool pdm_status;
unsigned int ch_mask;
rtd = substream->runtime->private_data;
ret = 0;
switch (substream->runtime->channels) {
case TWO_CH:
ch_mask = 0x00;
break;
default:
return -EINVAL;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
writel(ch_mask, rtd->acp63_base + ACP_WOV_PDM_NO_OF_CHANNELS);
writel(PDM_DECIMATION_FACTOR, rtd->acp63_base + ACP_WOV_PDM_DECIMATION_FACTOR);
rtd->bytescount = acp63_pdm_get_byte_count(rtd, substream->stream);
pdm_status = acp63_check_pdm_dma_status(rtd->acp63_base);
if (!pdm_status)
ret = acp63_start_pdm_dma(rtd->acp63_base);
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
pdm_status = acp63_check_pdm_dma_status(rtd->acp63_base);
if (pdm_status)
ret = acp63_stop_pdm_dma(rtd->acp63_base);
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static const struct snd_soc_dai_ops acp63_pdm_dai_ops = {
.trigger = acp63_pdm_dai_trigger,
};
static struct snd_soc_dai_driver acp63_pdm_dai_driver = {
.name = "acp_ps_pdm_dma.0",
.capture = {
.rates = SNDRV_PCM_RATE_48000,
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rate_min = 48000,
.rate_max = 48000,
},
.ops = &acp63_pdm_dai_ops,
};
static const struct snd_soc_component_driver acp63_pdm_component = {
.name = DRV_NAME,
.open = acp63_pdm_dma_open,
.close = acp63_pdm_dma_close,
.hw_params = acp63_pdm_dma_hw_params,
.pointer = acp63_pdm_dma_pointer,
.pcm_new = acp63_pdm_dma_new,
.use_dai_pcm_id = true,
};
static int acp63_pdm_audio_probe(struct platform_device *pdev)
{
struct resource *res;
struct pdm_dev_data *adata;
struct acp63_dev_data *acp_data;
struct device *parent;
int status;
parent = pdev->dev.parent;
acp_data = dev_get_drvdata(parent);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);
if (!adata)
return -ENOMEM;
adata->acp63_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!adata->acp63_base)
return -ENOMEM;
adata->capture_stream = NULL;
adata->acp_lock = &acp_data->acp_lock;
dev_set_drvdata(&pdev->dev, adata);
status = devm_snd_soc_register_component(&pdev->dev,
&acp63_pdm_component,
&acp63_pdm_dai_driver, 1);
if (status) {
dev_err(&pdev->dev, "Fail to register acp pdm dai\n");
return -ENODEV;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
static void acp63_pdm_audio_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static int acp63_pdm_resume(struct device *dev)
{
struct pdm_dev_data *adata;
struct snd_pcm_runtime *runtime;
struct pdm_stream_instance *rtd;
u32 period_bytes, buffer_len;
adata = dev_get_drvdata(dev);
if (adata->capture_stream && adata->capture_stream->runtime) {
runtime = adata->capture_stream->runtime;
rtd = runtime->private_data;
period_bytes = frames_to_bytes(runtime, runtime->period_size);
buffer_len = frames_to_bytes(runtime, runtime->buffer_size);
acp63_config_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);
acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_len,
period_bytes, adata->acp63_base);
}
acp63_enable_pdm_interrupts(adata);
return 0;
}
static int acp63_pdm_suspend(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
acp63_disable_pdm_interrupts(adata);
return 0;
}
static int acp63_pdm_runtime_resume(struct device *dev)
{
struct pdm_dev_data *adata;
adata = dev_get_drvdata(dev);
acp63_enable_pdm_interrupts(adata);
return 0;
}
static const struct dev_pm_ops acp63_pdm_pm_ops = {
RUNTIME_PM_OPS(acp63_pdm_suspend, acp63_pdm_runtime_resume, NULL)
SYSTEM_SLEEP_PM_OPS(acp63_pdm_suspend, acp63_pdm_resume)
};
static struct platform_driver acp63_pdm_dma_driver = {
.probe = acp63_pdm_audio_probe,
.remove = acp63_pdm_audio_remove,
.driver = {
.name = "acp_ps_pdm_dma",
.pm = pm_ptr(&acp63_pdm_pm_ops),
},
};
module_platform_driver(acp63_pdm_dma_driver);
MODULE_AUTHOR("Syed.SabaKareem@amd.com");
MODULE_DESCRIPTION("AMD common PDM Driver for ACP6.3, ACP7,0 & ACP7.1 platforms");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRV_NAME);
+804
View File
@@ -0,0 +1,804 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD ALSA SoC common SoundWire DMA Driver for ACP6.3, ACP7.0 and ACP7.1
* platforms.
*
* Copyright 2023, 2025 Advanced Micro Devices, Inc.
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
#include <linux/pm_runtime.h>
#include <linux/soundwire/sdw_amd.h>
#include "acp63.h"
#define DRV_NAME "amd_ps_sdw_dma"
static struct sdw_dma_ring_buf_reg acp63_sdw0_dma_reg[ACP63_SDW0_DMA_MAX_STREAMS] = {
{ACP_AUDIO0_TX_DMA_SIZE, ACP_AUDIO0_TX_FIFOADDR, ACP_AUDIO0_TX_FIFOSIZE,
ACP_AUDIO0_TX_RINGBUFSIZE, ACP_AUDIO0_TX_RINGBUFADDR, ACP_AUDIO0_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO0_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO1_TX_DMA_SIZE, ACP_AUDIO1_TX_FIFOADDR, ACP_AUDIO1_TX_FIFOSIZE,
ACP_AUDIO1_TX_RINGBUFSIZE, ACP_AUDIO1_TX_RINGBUFADDR, ACP_AUDIO1_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO2_TX_DMA_SIZE, ACP_AUDIO2_TX_FIFOADDR, ACP_AUDIO2_TX_FIFOSIZE,
ACP_AUDIO2_TX_RINGBUFSIZE, ACP_AUDIO2_TX_RINGBUFADDR, ACP_AUDIO2_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO2_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO0_RX_DMA_SIZE, ACP_AUDIO0_RX_FIFOADDR, ACP_AUDIO0_RX_FIFOSIZE,
ACP_AUDIO0_RX_RINGBUFSIZE, ACP_AUDIO0_RX_RINGBUFADDR, ACP_AUDIO0_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO0_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO1_RX_DMA_SIZE, ACP_AUDIO1_RX_FIFOADDR, ACP_AUDIO1_RX_FIFOSIZE,
ACP_AUDIO1_RX_RINGBUFSIZE, ACP_AUDIO1_RX_RINGBUFADDR, ACP_AUDIO1_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO2_RX_DMA_SIZE, ACP_AUDIO2_RX_FIFOADDR, ACP_AUDIO2_RX_FIFOSIZE,
ACP_AUDIO2_RX_RINGBUFSIZE, ACP_AUDIO2_RX_RINGBUFADDR, ACP_AUDIO2_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO2_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_RX_LINEARPOSITIONCNTR_HIGH}
};
/*
* SDW1 instance supports one TX stream and one RX stream.
* For TX/RX streams DMA registers programming for SDW1 instance, it uses ACP_P1_AUDIO1 register
* set as per hardware register documentation
*/
static struct sdw_dma_ring_buf_reg acp63_sdw1_dma_reg[ACP63_SDW1_DMA_MAX_STREAMS] = {
{ACP_P1_AUDIO1_TX_DMA_SIZE, ACP_P1_AUDIO1_TX_FIFOADDR, ACP_P1_AUDIO1_TX_FIFOSIZE,
ACP_P1_AUDIO1_TX_RINGBUFSIZE, ACP_P1_AUDIO1_TX_RINGBUFADDR,
ACP_P1_AUDIO1_TX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO1_RX_DMA_SIZE, ACP_P1_AUDIO1_RX_FIFOADDR, ACP_P1_AUDIO1_RX_FIFOSIZE,
ACP_P1_AUDIO1_RX_RINGBUFSIZE, ACP_P1_AUDIO1_RX_RINGBUFADDR,
ACP_P1_AUDIO1_RX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH},
};
static u32 acp63_sdw0_dma_enable_reg[ACP63_SDW0_DMA_MAX_STREAMS] = {
ACP_SW0_AUDIO0_TX_EN,
ACP_SW0_AUDIO1_TX_EN,
ACP_SW0_AUDIO2_TX_EN,
ACP_SW0_AUDIO0_RX_EN,
ACP_SW0_AUDIO1_RX_EN,
ACP_SW0_AUDIO2_RX_EN,
};
/*
* SDW1 instance supports one TX stream and one RX stream.
* For TX/RX streams DMA enable register programming for SDW1 instance,
* it uses ACP_SW1_AUDIO1_TX_EN and ACP_SW1_AUDIO1_RX_EN registers
* as per hardware register documentation.
*/
static u32 acp63_sdw1_dma_enable_reg[ACP63_SDW1_DMA_MAX_STREAMS] = {
ACP_SW1_AUDIO1_TX_EN,
ACP_SW1_AUDIO1_RX_EN,
};
static struct sdw_dma_ring_buf_reg acp70_sdw0_dma_reg[ACP70_SDW0_DMA_MAX_STREAMS] = {
{ACP_AUDIO0_TX_DMA_SIZE, ACP_AUDIO0_TX_FIFOADDR, ACP_AUDIO0_TX_FIFOSIZE,
ACP_AUDIO0_TX_RINGBUFSIZE, ACP_AUDIO0_TX_RINGBUFADDR, ACP_AUDIO0_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO0_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO1_TX_DMA_SIZE, ACP_AUDIO1_TX_FIFOADDR, ACP_AUDIO1_TX_FIFOSIZE,
ACP_AUDIO1_TX_RINGBUFSIZE, ACP_AUDIO1_TX_RINGBUFADDR, ACP_AUDIO1_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO2_TX_DMA_SIZE, ACP_AUDIO2_TX_FIFOADDR, ACP_AUDIO2_TX_FIFOSIZE,
ACP_AUDIO2_TX_RINGBUFSIZE, ACP_AUDIO2_TX_RINGBUFADDR, ACP_AUDIO2_TX_INTR_WATERMARK_SIZE,
ACP_AUDIO2_TX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO0_RX_DMA_SIZE, ACP_AUDIO0_RX_FIFOADDR, ACP_AUDIO0_RX_FIFOSIZE,
ACP_AUDIO0_RX_RINGBUFSIZE, ACP_AUDIO0_RX_RINGBUFADDR, ACP_AUDIO0_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO0_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO0_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO1_RX_DMA_SIZE, ACP_AUDIO1_RX_FIFOADDR, ACP_AUDIO1_RX_FIFOSIZE,
ACP_AUDIO1_RX_RINGBUFSIZE, ACP_AUDIO1_RX_RINGBUFADDR, ACP_AUDIO1_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_AUDIO2_RX_DMA_SIZE, ACP_AUDIO2_RX_FIFOADDR, ACP_AUDIO2_RX_FIFOSIZE,
ACP_AUDIO2_RX_RINGBUFSIZE, ACP_AUDIO2_RX_RINGBUFADDR, ACP_AUDIO2_RX_INTR_WATERMARK_SIZE,
ACP_AUDIO2_RX_LINEARPOSITIONCNTR_LOW, ACP_AUDIO2_RX_LINEARPOSITIONCNTR_HIGH}
};
static struct sdw_dma_ring_buf_reg acp70_sdw1_dma_reg[ACP70_SDW1_DMA_MAX_STREAMS] = {
{ACP_P1_AUDIO0_TX_DMA_SIZE, ACP_P1_AUDIO0_TX_FIFOADDR, ACP_P1_AUDIO0_TX_FIFOSIZE,
ACP_P1_AUDIO0_TX_RINGBUFSIZE, ACP_P1_AUDIO0_TX_RINGBUFADDR,
ACP_P1_AUDIO0_TX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO0_TX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO0_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO1_TX_DMA_SIZE, ACP_P1_AUDIO1_TX_FIFOADDR, ACP_P1_AUDIO1_TX_FIFOSIZE,
ACP_P1_AUDIO1_TX_RINGBUFSIZE, ACP_P1_AUDIO1_TX_RINGBUFADDR,
ACP_P1_AUDIO1_TX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO2_TX_DMA_SIZE, ACP_P1_AUDIO2_TX_FIFOADDR, ACP_P1_AUDIO2_TX_FIFOSIZE,
ACP_P1_AUDIO2_TX_RINGBUFSIZE, ACP_P1_AUDIO2_TX_RINGBUFADDR,
ACP_P1_AUDIO2_TX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO2_TX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO2_TX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO0_RX_DMA_SIZE, ACP_P1_AUDIO0_RX_FIFOADDR, ACP_P1_AUDIO0_RX_FIFOSIZE,
ACP_P1_AUDIO0_RX_RINGBUFSIZE, ACP_P1_AUDIO0_RX_RINGBUFADDR,
ACP_P1_AUDIO0_RX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO0_RX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO0_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO1_RX_DMA_SIZE, ACP_P1_AUDIO1_RX_FIFOADDR, ACP_P1_AUDIO1_RX_FIFOSIZE,
ACP_P1_AUDIO1_RX_RINGBUFSIZE, ACP_P1_AUDIO1_RX_RINGBUFADDR,
ACP_P1_AUDIO1_RX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO1_RX_LINEARPOSITIONCNTR_HIGH},
{ACP_P1_AUDIO2_RX_DMA_SIZE, ACP_P1_AUDIO2_RX_FIFOADDR, ACP_P1_AUDIO2_RX_FIFOSIZE,
ACP_P1_AUDIO2_RX_RINGBUFSIZE, ACP_P1_AUDIO2_RX_RINGBUFADDR,
ACP_P1_AUDIO2_RX_INTR_WATERMARK_SIZE,
ACP_P1_AUDIO2_RX_LINEARPOSITIONCNTR_LOW, ACP_P1_AUDIO2_RX_LINEARPOSITIONCNTR_HIGH}
};
static u32 acp70_sdw0_dma_enable_reg[ACP70_SDW0_DMA_MAX_STREAMS] = {
ACP70_SW0_AUDIO0_TX_EN,
ACP70_SW0_AUDIO1_TX_EN,
ACP70_SW0_AUDIO2_TX_EN,
ACP70_SW0_AUDIO0_RX_EN,
ACP70_SW0_AUDIO1_RX_EN,
ACP70_SW0_AUDIO2_RX_EN,
};
static u32 acp70_sdw1_dma_enable_reg[ACP70_SDW1_DMA_MAX_STREAMS] = {
ACP70_SW1_AUDIO0_TX_EN,
ACP70_SW1_AUDIO1_TX_EN,
ACP70_SW1_AUDIO2_TX_EN,
ACP70_SW1_AUDIO0_RX_EN,
ACP70_SW1_AUDIO1_RX_EN,
ACP70_SW1_AUDIO2_RX_EN,
};
static const struct snd_pcm_hardware acp63_sdw_hardware_playback = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.buffer_bytes_max = SDW_PLAYBACK_MAX_NUM_PERIODS * SDW_PLAYBACK_MAX_PERIOD_SIZE,
.period_bytes_min = SDW_PLAYBACK_MIN_PERIOD_SIZE,
.period_bytes_max = SDW_PLAYBACK_MAX_PERIOD_SIZE,
.periods_min = SDW_PLAYBACK_MIN_NUM_PERIODS,
.periods_max = SDW_PLAYBACK_MAX_NUM_PERIODS,
};
static const struct snd_pcm_hardware acp63_sdw_hardware_capture = {
.info = SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
.channels_min = 2,
.channels_max = 2,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.buffer_bytes_max = SDW_CAPTURE_MAX_NUM_PERIODS * SDW_CAPTURE_MAX_PERIOD_SIZE,
.period_bytes_min = SDW_CAPTURE_MIN_PERIOD_SIZE,
.period_bytes_max = SDW_CAPTURE_MAX_PERIOD_SIZE,
.periods_min = SDW_CAPTURE_MIN_NUM_PERIODS,
.periods_max = SDW_CAPTURE_MAX_NUM_PERIODS,
};
static void acp63_enable_disable_sdw_dma_interrupts(void __iomem *acp_base, u32 irq_mask,
u32 irq_mask1, bool enable)
{
u32 ext_intr_cntl, ext_intr_cntl1;
if (enable) {
ext_intr_cntl = readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_intr_cntl |= irq_mask;
writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_intr_cntl1 = readl(acp_base + ACP_EXTERNAL_INTR_CNTL1);
ext_intr_cntl1 |= irq_mask1;
writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1);
} else {
ext_intr_cntl = readl(acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_intr_cntl &= ~irq_mask;
writel(ext_intr_cntl, acp_base + ACP_EXTERNAL_INTR_CNTL);
ext_intr_cntl1 = readl(acp_base + ACP_EXTERNAL_INTR_CNTL1);
ext_intr_cntl1 &= ~irq_mask1;
writel(ext_intr_cntl1, acp_base + ACP_EXTERNAL_INTR_CNTL1);
}
}
static void acp63_config_dma(struct acp_sdw_dma_stream *stream, void __iomem *acp_base,
u32 stream_id)
{
u16 page_idx;
u32 low, high, val;
u32 sdw_dma_pte_offset;
dma_addr_t addr;
addr = stream->dma_addr;
sdw_dma_pte_offset = SDW_PTE_OFFSET(stream->instance);
val = sdw_dma_pte_offset + (stream_id * ACP_SDW_PTE_OFFSET);
/* Group Enable */
writel(ACP_SDW_SRAM_PTE_OFFSET | BIT(31), acp_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_2);
writel(PAGE_SIZE_4K_ENABLE, acp_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_2);
for (page_idx = 0; page_idx < stream->num_pages; page_idx++) {
/* Load the low address of page int ACP SRAM through SRBM */
low = lower_32_bits(addr);
high = upper_32_bits(addr);
writel(low, acp_base + ACP_SCRATCH_REG_0 + val);
high |= BIT(31);
writel(high, acp_base + ACP_SCRATCH_REG_0 + val + 4);
val += 8;
addr += PAGE_SIZE;
}
writel(0x1, acp_base + ACPAXI2AXI_ATU_CTRL);
}
static int acp63_configure_sdw_ringbuffer(void __iomem *acp_base, u32 stream_id, u32 size,
u32 manager_instance, u32 acp_rev)
{
u32 reg_dma_size;
u32 reg_fifo_addr;
u32 reg_fifo_size;
u32 reg_ring_buf_size;
u32 reg_ring_buf_addr;
u32 sdw_fifo_addr;
u32 sdw_fifo_offset;
u32 sdw_ring_buf_addr;
u32 sdw_ring_buf_size;
u32 sdw_mem_window_offset;
switch (acp_rev) {
case ACP63_PCI_REV:
switch (manager_instance) {
case ACP_SDW0:
reg_dma_size = acp63_sdw0_dma_reg[stream_id].reg_dma_size;
reg_fifo_addr = acp63_sdw0_dma_reg[stream_id].reg_fifo_addr;
reg_fifo_size = acp63_sdw0_dma_reg[stream_id].reg_fifo_size;
reg_ring_buf_size = acp63_sdw0_dma_reg[stream_id].reg_ring_buf_size;
reg_ring_buf_addr = acp63_sdw0_dma_reg[stream_id].reg_ring_buf_addr;
break;
case ACP_SDW1:
reg_dma_size = acp63_sdw1_dma_reg[stream_id].reg_dma_size;
reg_fifo_addr = acp63_sdw1_dma_reg[stream_id].reg_fifo_addr;
reg_fifo_size = acp63_sdw1_dma_reg[stream_id].reg_fifo_size;
reg_ring_buf_size = acp63_sdw1_dma_reg[stream_id].reg_ring_buf_size;
reg_ring_buf_addr = acp63_sdw1_dma_reg[stream_id].reg_ring_buf_addr;
break;
default:
return -EINVAL;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
switch (manager_instance) {
case ACP_SDW0:
reg_dma_size = acp70_sdw0_dma_reg[stream_id].reg_dma_size;
reg_fifo_addr = acp70_sdw0_dma_reg[stream_id].reg_fifo_addr;
reg_fifo_size = acp70_sdw0_dma_reg[stream_id].reg_fifo_size;
reg_ring_buf_size = acp70_sdw0_dma_reg[stream_id].reg_ring_buf_size;
reg_ring_buf_addr = acp70_sdw0_dma_reg[stream_id].reg_ring_buf_addr;
break;
case ACP_SDW1:
reg_dma_size = acp70_sdw1_dma_reg[stream_id].reg_dma_size;
reg_fifo_addr = acp70_sdw1_dma_reg[stream_id].reg_fifo_addr;
reg_fifo_size = acp70_sdw1_dma_reg[stream_id].reg_fifo_size;
reg_ring_buf_size = acp70_sdw1_dma_reg[stream_id].reg_ring_buf_size;
reg_ring_buf_addr = acp70_sdw1_dma_reg[stream_id].reg_ring_buf_addr;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
sdw_fifo_offset = ACP_SDW_FIFO_OFFSET(manager_instance);
sdw_mem_window_offset = SDW_MEM_WINDOW_START(manager_instance);
sdw_fifo_addr = sdw_fifo_offset + (stream_id * SDW_FIFO_OFFSET);
sdw_ring_buf_addr = sdw_mem_window_offset + (stream_id * ACP_SDW_RING_BUFF_ADDR_OFFSET);
sdw_ring_buf_size = size;
writel(sdw_ring_buf_size, acp_base + reg_ring_buf_size);
writel(sdw_ring_buf_addr, acp_base + reg_ring_buf_addr);
writel(sdw_fifo_addr, acp_base + reg_fifo_addr);
writel(SDW_DMA_SIZE, acp_base + reg_dma_size);
writel(SDW_FIFO_SIZE, acp_base + reg_fifo_size);
return 0;
}
static int acp63_sdw_dma_open(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime;
struct acp_sdw_dma_stream *stream;
struct snd_soc_dai *cpu_dai;
struct amd_sdw_manager *amd_manager;
struct snd_soc_pcm_runtime *prtd = snd_soc_substream_to_rtd(substream);
int ret;
runtime = substream->runtime;
cpu_dai = snd_soc_rtd_to_cpu(prtd, 0);
amd_manager = snd_soc_dai_get_drvdata(cpu_dai);
stream = kzalloc_obj(*stream);
if (!stream)
return -ENOMEM;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = acp63_sdw_hardware_playback;
else
runtime->hw = acp63_sdw_hardware_capture;
ret = snd_pcm_hw_constraint_integer(runtime,
SNDRV_PCM_HW_PARAM_PERIODS);
if (ret < 0) {
dev_err(component->dev, "set integer constraint failed\n");
kfree(stream);
return ret;
}
stream->stream_id = cpu_dai->id;
stream->instance = amd_manager->instance;
runtime->private_data = stream;
return ret;
}
static int acp63_sdw_dma_hw_params(struct snd_soc_component *component,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct acp_sdw_dma_stream *stream;
struct sdw_dma_dev_data *sdw_data;
u32 period_bytes;
u32 water_mark_size_reg;
u32 irq_mask, ext_intr_ctrl;
u64 size;
u32 stream_id;
u32 acp_ext_intr_cntl_reg;
int ret;
sdw_data = dev_get_drvdata(component->dev);
stream = substream->runtime->private_data;
if (!stream)
return -EINVAL;
stream_id = stream->stream_id;
switch (sdw_data->acp_rev) {
case ACP63_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_data->acp63_sdw0_dma_stream[stream_id] = substream;
water_mark_size_reg = acp63_sdw0_dma_reg[stream_id].water_mark_size_reg;
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
irq_mask = BIT(ACP63_SDW0_DMA_TX_IRQ_MASK(stream_id));
else
irq_mask = BIT(ACP63_SDW0_DMA_RX_IRQ_MASK(stream_id));
break;
case ACP_SDW1:
sdw_data->acp63_sdw1_dma_stream[stream_id] = substream;
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
water_mark_size_reg = acp63_sdw1_dma_reg[stream_id].water_mark_size_reg;
irq_mask = BIT(ACP63_SDW1_DMA_IRQ_MASK(stream_id));
break;
default:
return -EINVAL;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_data->acp70_sdw0_dma_stream[stream_id] = substream;
water_mark_size_reg = acp70_sdw0_dma_reg[stream_id].water_mark_size_reg;
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
irq_mask = BIT(ACP70_SDW0_DMA_TX_IRQ_MASK(stream_id));
else
irq_mask = BIT(ACP70_SDW0_DMA_RX_IRQ_MASK(stream_id));
break;
case ACP_SDW1:
sdw_data->acp70_sdw1_dma_stream[stream_id] = substream;
acp_ext_intr_cntl_reg = ACP_EXTERNAL_INTR_CNTL1;
water_mark_size_reg = acp70_sdw1_dma_reg[stream_id].water_mark_size_reg;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
irq_mask = BIT(ACP70_SDW1_DMA_TX_IRQ_MASK(stream_id));
else
irq_mask = BIT(ACP70_SDW1_DMA_RX_IRQ_MASK(stream_id));
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
size = params_buffer_bytes(params);
period_bytes = params_period_bytes(params);
stream->dma_addr = substream->runtime->dma_addr;
stream->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
acp63_config_dma(stream, sdw_data->acp_base, stream_id);
ret = acp63_configure_sdw_ringbuffer(sdw_data->acp_base, stream_id, size,
stream->instance, sdw_data->acp_rev);
if (ret) {
dev_err(component->dev, "Invalid DMA channel\n");
return -EINVAL;
}
ext_intr_ctrl = readl(sdw_data->acp_base + acp_ext_intr_cntl_reg);
ext_intr_ctrl |= irq_mask;
writel(ext_intr_ctrl, sdw_data->acp_base + acp_ext_intr_cntl_reg);
writel(period_bytes, sdw_data->acp_base + water_mark_size_reg);
return 0;
}
static u64 acp63_sdw_get_byte_count(struct acp_sdw_dma_stream *stream, void __iomem *acp_base,
u32 acp_rev)
{
union acp_sdw_dma_count byte_count;
u32 pos_low_reg, pos_high_reg;
byte_count.bytescount = 0;
switch (acp_rev) {
case ACP63_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
pos_low_reg = acp63_sdw0_dma_reg[stream->stream_id].pos_low_reg;
pos_high_reg = acp63_sdw0_dma_reg[stream->stream_id].pos_high_reg;
break;
case ACP_SDW1:
pos_low_reg = acp63_sdw1_dma_reg[stream->stream_id].pos_low_reg;
pos_high_reg = acp63_sdw1_dma_reg[stream->stream_id].pos_high_reg;
break;
default:
goto POINTER_RETURN_BYTES;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
pos_low_reg = acp70_sdw0_dma_reg[stream->stream_id].pos_low_reg;
pos_high_reg = acp70_sdw0_dma_reg[stream->stream_id].pos_high_reg;
break;
case ACP_SDW1:
pos_low_reg = acp70_sdw1_dma_reg[stream->stream_id].pos_low_reg;
pos_high_reg = acp70_sdw1_dma_reg[stream->stream_id].pos_high_reg;
break;
default:
goto POINTER_RETURN_BYTES;
}
break;
default:
goto POINTER_RETURN_BYTES;
}
if (pos_low_reg) {
byte_count.bcount.high = readl(acp_base + pos_high_reg);
byte_count.bcount.low = readl(acp_base + pos_low_reg);
}
POINTER_RETURN_BYTES:
return byte_count.bytescount;
}
static snd_pcm_uframes_t acp63_sdw_dma_pointer(struct snd_soc_component *comp,
struct snd_pcm_substream *substream)
{
struct sdw_dma_dev_data *sdw_data;
struct acp_sdw_dma_stream *stream;
u32 pos, buffersize;
u64 bytescount;
sdw_data = dev_get_drvdata(comp->dev);
stream = substream->runtime->private_data;
buffersize = frames_to_bytes(substream->runtime,
substream->runtime->buffer_size);
bytescount = acp63_sdw_get_byte_count(stream, sdw_data->acp_base, sdw_data->acp_rev);
if (bytescount > stream->bytescount)
bytescount -= stream->bytescount;
pos = do_div(bytescount, buffersize);
return bytes_to_frames(substream->runtime, pos);
}
static int acp63_sdw_dma_new(struct snd_soc_component *component,
struct snd_soc_pcm_runtime *rtd)
{
struct device *parent = component->dev->parent;
snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
parent, SDW_MIN_BUFFER, SDW_MAX_BUFFER);
return 0;
}
static int acp63_sdw_dma_close(struct snd_soc_component *component,
struct snd_pcm_substream *substream)
{
struct sdw_dma_dev_data *sdw_data;
struct acp_sdw_dma_stream *stream;
sdw_data = dev_get_drvdata(component->dev);
stream = substream->runtime->private_data;
if (!stream)
return -EINVAL;
switch (sdw_data->acp_rev) {
case ACP63_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_data->acp63_sdw0_dma_stream[stream->stream_id] = NULL;
break;
case ACP_SDW1:
sdw_data->acp63_sdw1_dma_stream[stream->stream_id] = NULL;
break;
default:
return -EINVAL;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_data->acp70_sdw0_dma_stream[stream->stream_id] = NULL;
break;
case ACP_SDW1:
sdw_data->acp70_sdw1_dma_stream[stream->stream_id] = NULL;
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
kfree(stream);
return 0;
}
static int acp63_sdw_dma_enable(struct snd_pcm_substream *substream,
void __iomem *acp_base, u32 acp_rev, bool sdw_dma_enable)
{
struct acp_sdw_dma_stream *stream;
u32 stream_id;
u32 sdw_dma_en_reg;
u32 sdw_dma_en_stat_reg;
u32 sdw_dma_stat;
u32 dma_enable;
stream = substream->runtime->private_data;
stream_id = stream->stream_id;
switch (acp_rev) {
case ACP63_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_dma_en_reg = acp63_sdw0_dma_enable_reg[stream_id];
break;
case ACP_SDW1:
sdw_dma_en_reg = acp63_sdw1_dma_enable_reg[stream_id];
break;
default:
return -EINVAL;
}
break;
case ACP70_PCI_REV:
case ACP71_PCI_REV:
case ACP72_PCI_REV:
switch (stream->instance) {
case ACP_SDW0:
sdw_dma_en_reg = acp70_sdw0_dma_enable_reg[stream_id];
break;
case ACP_SDW1:
sdw_dma_en_reg = acp70_sdw1_dma_enable_reg[stream_id];
break;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
sdw_dma_en_stat_reg = sdw_dma_en_reg + 4;
dma_enable = sdw_dma_enable;
writel(dma_enable, acp_base + sdw_dma_en_reg);
return readl_poll_timeout(acp_base + sdw_dma_en_stat_reg, sdw_dma_stat,
(sdw_dma_stat == dma_enable), ACP_DELAY_US, ACP_COUNTER);
}
static int acp63_sdw_dma_trigger(struct snd_soc_component *comp,
struct snd_pcm_substream *substream,
int cmd)
{
struct sdw_dma_dev_data *sdw_data;
int ret;
sdw_data = dev_get_drvdata(comp->dev);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
ret = acp63_sdw_dma_enable(substream, sdw_data->acp_base, sdw_data->acp_rev, true);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
ret = acp63_sdw_dma_enable(substream, sdw_data->acp_base, sdw_data->acp_rev, false);
break;
default:
ret = -EINVAL;
}
if (ret)
dev_err(comp->dev, "trigger %d failed: %d", cmd, ret);
return ret;
}
static const struct snd_soc_component_driver acp63_sdw_component = {
.name = DRV_NAME,
.open = acp63_sdw_dma_open,
.close = acp63_sdw_dma_close,
.hw_params = acp63_sdw_dma_hw_params,
.trigger = acp63_sdw_dma_trigger,
.pointer = acp63_sdw_dma_pointer,
.pcm_new = acp63_sdw_dma_new,
.use_dai_pcm_id = true,
};
static int acp63_sdw_platform_probe(struct platform_device *pdev)
{
struct resource *res;
struct sdw_dma_dev_data *sdw_data;
struct acp63_dev_data *acp_data;
struct device *parent;
int status;
parent = pdev->dev.parent;
acp_data = dev_get_drvdata(parent);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
return -ENODEV;
}
sdw_data = devm_kzalloc(&pdev->dev, sizeof(*sdw_data), GFP_KERNEL);
if (!sdw_data)
return -ENOMEM;
sdw_data->acp_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!sdw_data->acp_base)
return -ENOMEM;
sdw_data->acp_lock = &acp_data->acp_lock;
sdw_data->acp_rev = acp_data->acp_rev;
dev_set_drvdata(&pdev->dev, sdw_data);
status = devm_snd_soc_register_component(&pdev->dev,
&acp63_sdw_component,
NULL, 0);
if (status) {
dev_err(&pdev->dev, "Fail to register sdw dma component\n");
return status;
}
pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
return 0;
}
static void acp63_sdw_platform_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static int acp63_restore_sdw_dma_config(struct sdw_dma_dev_data *sdw_data)
{
struct acp_sdw_dma_stream *stream;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
u32 period_bytes, buf_size, water_mark_size_reg;
u32 stream_count, irq_mask, irq_mask1;
int index, instance, ret;
irq_mask = ACP63_SDW_DMA_IRQ_MASK;
irq_mask1 = ACP63_P1_SDW_DMA_IRQ_MASK;
for (instance = 0; instance < AMD_SDW_MAX_MANAGERS; instance++) {
if (instance == ACP_SDW0)
stream_count = ACP63_SDW0_DMA_MAX_STREAMS;
else
stream_count = ACP63_SDW1_DMA_MAX_STREAMS;
for (index = 0; index < stream_count; index++) {
if (instance == ACP_SDW0) {
substream = sdw_data->acp63_sdw0_dma_stream[index];
water_mark_size_reg = acp63_sdw0_dma_reg[index].water_mark_size_reg;
} else {
substream = sdw_data->acp63_sdw1_dma_stream[index];
water_mark_size_reg = acp63_sdw1_dma_reg[index].water_mark_size_reg;
}
if (substream && substream->runtime) {
runtime = substream->runtime;
stream = runtime->private_data;
period_bytes = frames_to_bytes(runtime, runtime->period_size);
buf_size = frames_to_bytes(runtime, runtime->buffer_size);
acp63_config_dma(stream, sdw_data->acp_base, index);
ret = acp63_configure_sdw_ringbuffer(sdw_data->acp_base, index,
buf_size, instance,
ACP63_PCI_REV);
if (ret)
return ret;
writel(period_bytes, sdw_data->acp_base + water_mark_size_reg);
}
}
}
acp63_enable_disable_sdw_dma_interrupts(sdw_data->acp_base, irq_mask, irq_mask1, true);
return 0;
}
static int acp70_restore_sdw_dma_config(struct sdw_dma_dev_data *sdw_data)
{
struct acp_sdw_dma_stream *stream;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
u32 period_bytes, buf_size, water_mark_size_reg;
u32 stream_count, irq_mask, irq_mask1;
int index, instance, ret;
irq_mask = ACP70_SDW_DMA_IRQ_MASK;
irq_mask1 = ACP70_P1_SDW_DMA_IRQ_MASK;
stream_count = ACP70_SDW0_DMA_MAX_STREAMS;
for (instance = 0; instance < AMD_SDW_MAX_MANAGERS; instance++) {
for (index = 0; index < stream_count; index++) {
if (instance == ACP_SDW0) {
substream = sdw_data->acp70_sdw0_dma_stream[index];
water_mark_size_reg = acp70_sdw0_dma_reg[index].water_mark_size_reg;
} else {
substream = sdw_data->acp70_sdw1_dma_stream[index];
water_mark_size_reg = acp70_sdw1_dma_reg[index].water_mark_size_reg;
}
if (substream && substream->runtime) {
runtime = substream->runtime;
stream = runtime->private_data;
period_bytes = frames_to_bytes(runtime, runtime->period_size);
buf_size = frames_to_bytes(runtime, runtime->buffer_size);
acp63_config_dma(stream, sdw_data->acp_base, index);
ret = acp63_configure_sdw_ringbuffer(sdw_data->acp_base, index,
buf_size, instance,
sdw_data->acp_rev);
if (ret)
return ret;
writel(period_bytes, sdw_data->acp_base + water_mark_size_reg);
}
}
}
acp63_enable_disable_sdw_dma_interrupts(sdw_data->acp_base, irq_mask, irq_mask1, true);
return 0;
}
static int acp63_sdw_pcm_resume(struct device *dev)
{
struct sdw_dma_dev_data *sdw_data;
sdw_data = dev_get_drvdata(dev);
if (sdw_data->acp_rev == ACP63_PCI_REV)
return acp63_restore_sdw_dma_config(sdw_data);
else
return acp70_restore_sdw_dma_config(sdw_data);
}
static const struct dev_pm_ops acp63_pm_ops = {
SYSTEM_SLEEP_PM_OPS(NULL, acp63_sdw_pcm_resume)
};
static struct platform_driver acp63_sdw_dma_driver = {
.probe = acp63_sdw_platform_probe,
.remove = acp63_sdw_platform_remove,
.driver = {
.name = "amd_ps_sdw_dma",
.pm = pm_ptr(&acp63_pm_ops),
},
};
module_platform_driver(acp63_sdw_dma_driver);
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD common SDW DMA Driver for ACP6.3, ACP7.0 & ACP7.1 platforms");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);