ASoC: amd: acp: add pm ops support for rembrandt platform
authorSyed Saba Kareem <Syed.SabaKareem@amd.com>
Mon, 26 Jun 2023 13:55:14 +0000 (19:25 +0530)
committerMark Brown <broonie@kernel.org>
Sun, 9 Jul 2023 21:47:41 +0000 (22:47 +0100)
Add pm ops for rembrandt platform.

Signed-off-by: Syed Saba Kareem <Syed.SabaKareem@amd.com>
Link: https://lore.kernel.org/r/20230626135515.1252063-11-Syed.SabaKareem@amd.com
Signed-off-by: Mark Brown <broonie@kernel.org>
sound/soc/amd/acp/acp-legacy-common.c
sound/soc/amd/acp/acp-rembrandt.c
sound/soc/amd/acp/amd.h

index 45a45d0..ba58165 100644 (file)
@@ -37,6 +37,214 @@ void acp_disable_interrupts(struct acp_dev_data *adata)
 }
 EXPORT_SYMBOL_NS_GPL(acp_disable_interrupts, SND_SOC_ACP_COMMON);
 
+static void set_acp_pdm_ring_buffer(struct snd_pcm_substream *substream,
+                                   struct snd_soc_dai *dai)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       struct acp_stream *stream = runtime->private_data;
+       struct device *dev = dai->component->dev;
+       struct acp_dev_data *adata = dev_get_drvdata(dev);
+
+       u32 physical_addr, pdm_size, period_bytes;
+
+       period_bytes = frames_to_bytes(runtime, runtime->period_size);
+       pdm_size = frames_to_bytes(runtime, runtime->buffer_size);
+       physical_addr = stream->reg_offset + MEM_WINDOW_START;
+
+       /* Init ACP PDM Ring buffer */
+       writel(physical_addr, adata->acp_base + ACP_WOV_RX_RINGBUFADDR);
+       writel(pdm_size, adata->acp_base + ACP_WOV_RX_RINGBUFSIZE);
+       writel(period_bytes, adata->acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);
+       writel(0x01, adata->acp_base + ACPAXI2AXI_ATU_CTRL);
+}
+
+static void set_acp_pdm_clk(struct snd_pcm_substream *substream,
+                           struct snd_soc_dai *dai)
+{
+       struct device *dev = dai->component->dev;
+       struct acp_dev_data *adata = dev_get_drvdata(dev);
+       unsigned int pdm_ctrl;
+
+       /* Enable default ACP PDM clk */
+       writel(PDM_CLK_FREQ_MASK, adata->acp_base + ACP_WOV_CLK_CTRL);
+       pdm_ctrl = readl(adata->acp_base + ACP_WOV_MISC_CTRL);
+       pdm_ctrl |= PDM_MISC_CTRL_MASK;
+       writel(pdm_ctrl, adata->acp_base + ACP_WOV_MISC_CTRL);
+       set_acp_pdm_ring_buffer(substream, dai);
+}
+
+void restore_acp_pdm_params(struct snd_pcm_substream *substream,
+                           struct acp_dev_data *adata)
+{
+       struct snd_soc_dai *dai;
+       struct snd_soc_pcm_runtime *soc_runtime;
+       u32 ext_int_ctrl;
+
+       soc_runtime = asoc_substream_to_rtd(substream);
+       dai = asoc_rtd_to_cpu(soc_runtime, 0);
+       /* Programming channel mask and sampling rate */
+       writel(adata->ch_mask, adata->acp_base + ACP_WOV_PDM_NO_OF_CHANNELS);
+       writel(PDM_DEC_64, adata->acp_base + ACP_WOV_PDM_DECIMATION_FACTOR);
+
+       /* Enabling ACP Pdm interuppts */
+       ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, 0));
+       ext_int_ctrl |= PDM_DMA_INTR_MASK;
+       writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, 0));
+       set_acp_pdm_clk(substream, dai);
+}
+EXPORT_SYMBOL_NS_GPL(restore_acp_pdm_params, SND_SOC_ACP_COMMON);
+
+static int set_acp_i2s_dma_fifo(struct snd_pcm_substream *substream,
+                               struct snd_soc_dai *dai)
+{
+       struct device *dev = dai->component->dev;
+       struct acp_dev_data *adata = dev_get_drvdata(dev);
+       struct acp_resource *rsrc = adata->rsrc;
+       struct acp_stream *stream = substream->runtime->private_data;
+       u32 reg_dma_size, reg_fifo_size, reg_fifo_addr;
+       u32 phy_addr, acp_fifo_addr, ext_int_ctrl;
+       unsigned int dir = substream->stream;
+
+       switch (dai->driver->id) {
+       case I2S_SP_INSTANCE:
+               if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+                       reg_dma_size = ACP_I2S_TX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       SP_PB_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_I2S_TX_FIFOADDR;
+                       reg_fifo_size = ACP_I2S_TX_FIFOSIZE;
+                       phy_addr = I2S_SP_TX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_I2S_TX_RINGBUFADDR);
+               } else {
+                       reg_dma_size = ACP_I2S_RX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       SP_CAPT_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_I2S_RX_FIFOADDR;
+                       reg_fifo_size = ACP_I2S_RX_FIFOSIZE;
+                       phy_addr = I2S_SP_RX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_I2S_RX_RINGBUFADDR);
+               }
+               break;
+       case I2S_BT_INSTANCE:
+               if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+                       reg_dma_size = ACP_BT_TX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       BT_PB_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_BT_TX_FIFOADDR;
+                       reg_fifo_size = ACP_BT_TX_FIFOSIZE;
+                       phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_BT_TX_RINGBUFADDR);
+               } else {
+                       reg_dma_size = ACP_BT_RX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       BT_CAPT_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_BT_RX_FIFOADDR;
+                       reg_fifo_size = ACP_BT_RX_FIFOSIZE;
+                       phy_addr = I2S_BT_TX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_BT_RX_RINGBUFADDR);
+               }
+               break;
+       case I2S_HS_INSTANCE:
+               if (dir == SNDRV_PCM_STREAM_PLAYBACK) {
+                       reg_dma_size = ACP_HS_TX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       HS_PB_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_HS_TX_FIFOADDR;
+                       reg_fifo_size = ACP_HS_TX_FIFOSIZE;
+                       phy_addr = I2S_HS_TX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_HS_TX_RINGBUFADDR);
+               } else {
+                       reg_dma_size = ACP_HS_RX_DMA_SIZE;
+                       acp_fifo_addr = rsrc->sram_pte_offset +
+                                       HS_CAPT_FIFO_ADDR_OFFSET;
+                       reg_fifo_addr = ACP_HS_RX_FIFOADDR;
+                       reg_fifo_size = ACP_HS_RX_FIFOSIZE;
+                       phy_addr = I2S_HS_RX_MEM_WINDOW_START + stream->reg_offset;
+                       writel(phy_addr, adata->acp_base + ACP_HS_RX_RINGBUFADDR);
+               }
+               break;
+       default:
+               dev_err(dev, "Invalid dai id %x\n", dai->driver->id);
+               return -EINVAL;
+       }
+
+       writel(DMA_SIZE, adata->acp_base + reg_dma_size);
+       writel(acp_fifo_addr, adata->acp_base + reg_fifo_addr);
+       writel(FIFO_SIZE, adata->acp_base + reg_fifo_size);
+
+       ext_int_ctrl = readl(ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used));
+       ext_int_ctrl |= BIT(I2S_RX_THRESHOLD(rsrc->offset)) |
+                       BIT(BT_RX_THRESHOLD(rsrc->offset)) |
+                       BIT(I2S_TX_THRESHOLD(rsrc->offset)) |
+                       BIT(BT_TX_THRESHOLD(rsrc->offset)) |
+                       BIT(HS_RX_THRESHOLD(rsrc->offset)) |
+                       BIT(HS_TX_THRESHOLD(rsrc->offset));
+
+       writel(ext_int_ctrl, ACP_EXTERNAL_INTR_CNTL(adata, rsrc->irqp_used));
+       return 0;
+}
+
+int restore_acp_i2s_params(struct snd_pcm_substream *substream,
+                          struct acp_dev_data *adata,
+                          struct acp_stream *stream)
+{
+       struct snd_soc_dai *dai;
+       struct snd_soc_pcm_runtime *soc_runtime;
+       u32 tdm_fmt, reg_val, fmt_reg, val;
+
+       soc_runtime = asoc_substream_to_rtd(substream);
+       dai = asoc_rtd_to_cpu(soc_runtime, 0);
+       if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+               tdm_fmt = adata->tdm_tx_fmt[stream->dai_id - 1];
+               switch (stream->dai_id) {
+               case I2S_BT_INSTANCE:
+                       reg_val = ACP_BTTDM_ITER;
+                       fmt_reg = ACP_BTTDM_TXFRMT;
+                       break;
+               case I2S_SP_INSTANCE:
+                       reg_val = ACP_I2STDM_ITER;
+                       fmt_reg = ACP_I2STDM_TXFRMT;
+                       break;
+               case I2S_HS_INSTANCE:
+                       reg_val = ACP_HSTDM_ITER;
+                       fmt_reg = ACP_HSTDM_TXFRMT;
+                       break;
+               default:
+                       pr_err("Invalid dai id %x\n", stream->dai_id);
+                       return -EINVAL;
+               }
+               val = adata->xfer_tx_resolution[stream->dai_id - 1] << 3;
+       } else {
+               tdm_fmt = adata->tdm_rx_fmt[stream->dai_id - 1];
+               switch (stream->dai_id) {
+               case I2S_BT_INSTANCE:
+                       reg_val = ACP_BTTDM_IRER;
+                       fmt_reg = ACP_BTTDM_RXFRMT;
+                       break;
+               case I2S_SP_INSTANCE:
+                       reg_val = ACP_I2STDM_IRER;
+                       fmt_reg = ACP_I2STDM_RXFRMT;
+                       break;
+               case I2S_HS_INSTANCE:
+                       reg_val = ACP_HSTDM_IRER;
+                       fmt_reg = ACP_HSTDM_RXFRMT;
+                       break;
+               default:
+                       pr_err("Invalid dai id %x\n", stream->dai_id);
+                       return -EINVAL;
+               }
+               val = adata->xfer_rx_resolution[stream->dai_id - 1] << 3;
+       }
+       writel(val, adata->acp_base + reg_val);
+       if (adata->tdm_mode == TDM_ENABLE) {
+               writel(tdm_fmt, adata->acp_base + fmt_reg);
+               val = readl(adata->acp_base + reg_val);
+               writel(val | 0x2, adata->acp_base + reg_val);
+       }
+       return set_acp_i2s_dma_fifo(substream, dai);
+}
+EXPORT_SYMBOL_NS_GPL(restore_acp_i2s_params, SND_SOC_ACP_COMMON);
+
 static int acp_power_on(struct acp_chip_info *chip)
 {
        u32 val, acp_pgfsm_stat_reg, acp_pgfsm_ctrl_reg;
index ea3d4aa..89314d9 100644 (file)
@@ -20,6 +20,7 @@
 #include <sound/soc-dai.h>
 #include <linux/dma-mapping.h>
 #include <linux/pci.h>
+#include <linux/pm_runtime.h>
 
 #include "amd.h"
 
@@ -236,7 +237,11 @@ static int rembrandt_audio_probe(struct platform_device *pdev)
        acp6x_master_clock_generate(dev);
        acp_enable_interrupts(adata);
        acp_platform_register(dev);
-
+       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;
 }
 
@@ -247,13 +252,48 @@ static void rembrandt_audio_remove(struct platform_device *pdev)
 
        acp_disable_interrupts(adata);
        acp_platform_unregister(dev);
+       pm_runtime_disable(&pdev->dev);
+}
+
+static int __maybe_unused rmb_pcm_resume(struct device *dev)
+{
+       struct acp_dev_data *adata = dev_get_drvdata(dev);
+       struct acp_stream *stream;
+       struct snd_pcm_substream *substream;
+       snd_pcm_uframes_t buf_in_frames;
+       u64 buf_size;
+
+       acp6x_master_clock_generate(dev);
+       spin_lock(&adata->acp_lock);
+       list_for_each_entry(stream, &adata->stream_list, list) {
+               if (stream) {
+                       substream = stream->substream;
+                       if (substream && substream->runtime) {
+                               buf_in_frames = (substream->runtime->buffer_size);
+                               buf_size = frames_to_bytes(substream->runtime, buf_in_frames);
+                               config_pte_for_stream(adata, stream);
+                               config_acp_dma(adata, stream, buf_size);
+                               if (stream->dai_id)
+                                       restore_acp_i2s_params(substream, adata, stream);
+                               else
+                                       restore_acp_pdm_params(substream, adata);
+                       }
+               }
+       }
+               spin_unlock(&adata->acp_lock);
+               return 0;
 }
 
+static const struct dev_pm_ops rmb_dma_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(NULL, rmb_pcm_resume)
+};
+
 static struct platform_driver rembrandt_driver = {
        .probe = rembrandt_audio_probe,
        .remove_new = rembrandt_audio_remove,
        .driver = {
                .name = "acp_asoc_rembrandt",
+               .pm = &rmb_dma_pm_ops,
        },
 };
 
index 15f772c..2ebe209 100644 (file)
 
 #define ACP_TIMEOUT            500
 #define DELAY_US               5
+#define ACP_SUSPEND_DELAY_MS   2000
 
 #define PDM_DMA_STAT            0x10
 #define PDM_DMA_INTR_MASK       0x10000
@@ -213,6 +214,14 @@ void acp_disable_interrupts(struct acp_dev_data *adata);
 /* Machine configuration */
 int snd_amd_acp_find_config(struct pci_dev *pci);
 
+void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream);
+void config_acp_dma(struct acp_dev_data *adata, struct acp_stream *stream, int size);
+void restore_acp_pdm_params(struct snd_pcm_substream *substream,
+                           struct acp_dev_data *adata);
+
+int restore_acp_i2s_params(struct snd_pcm_substream *substream,
+                          struct acp_dev_data *adata, struct acp_stream *stream);
+
 static inline u64 acp_get_byte_count(struct acp_dev_data *adata, int dai_id, int direction)
 {
        u64 byte_count = 0, low = 0, high = 0;