464 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * AMD ALSA SoC Pink Sardine PDM Driver4 *5 * Copyright 2022 Advanced Micro Devices, Inc.6 */7 8#include <linux/platform_device.h>9#include <linux/module.h>10#include <linux/bitfield.h>11#include <linux/err.h>12#include <linux/io.h>13#include <sound/pcm_params.h>14#include <sound/soc.h>15#include <sound/soc-dai.h>16#include <linux/pm_runtime.h>17 18#include "acp63.h"19 20#define DRV_NAME "acp_ps_pdm_dma"21 22static int pdm_gain = 3;23module_param(pdm_gain, int, 0644);24MODULE_PARM_DESC(pdm_gain, "Gain control (0-3)");25 26static const struct snd_pcm_hardware acp63_pdm_hardware_capture = {27 .info = SNDRV_PCM_INFO_INTERLEAVED |28 SNDRV_PCM_INFO_BLOCK_TRANSFER |29 SNDRV_PCM_INFO_MMAP |30 SNDRV_PCM_INFO_MMAP_VALID |31 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,32 .formats = SNDRV_PCM_FMTBIT_S32_LE,33 .channels_min = 2,34 .channels_max = 2,35 .rates = SNDRV_PCM_RATE_48000,36 .rate_min = 48000,37 .rate_max = 48000,38 .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,39 .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,40 .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,41 .periods_min = CAPTURE_MIN_NUM_PERIODS,42 .periods_max = CAPTURE_MAX_NUM_PERIODS,43};44 45static void acp63_init_pdm_ring_buffer(u32 physical_addr, u32 buffer_size,46 u32 watermark_size, void __iomem *acp_base)47{48 writel(physical_addr, acp_base + ACP_WOV_RX_RINGBUFADDR);49 writel(buffer_size, acp_base + ACP_WOV_RX_RINGBUFSIZE);50 writel(watermark_size, acp_base + ACP_WOV_RX_INTR_WATERMARK_SIZE);51 writel(0x01, acp_base + ACPAXI2AXI_ATU_CTRL);52}53 54static void acp63_enable_pdm_clock(void __iomem *acp_base)55{56 u32 pdm_clk_enable, pdm_ctrl;57 58 pdm_clk_enable = ACP_PDM_CLK_FREQ_MASK;59 pdm_ctrl = 0x00;60 61 writel(pdm_clk_enable, acp_base + ACP_WOV_CLK_CTRL);62 pdm_ctrl = readl(acp_base + ACP_WOV_MISC_CTRL);63 pdm_ctrl &= ~ACP_WOV_GAIN_CONTROL;64 pdm_ctrl |= FIELD_PREP(ACP_WOV_GAIN_CONTROL, clamp(pdm_gain, 0, 3));65 writel(pdm_ctrl, acp_base + ACP_WOV_MISC_CTRL);66}67 68static void acp63_enable_pdm_interrupts(struct pdm_dev_data *adata)69{70 u32 ext_int_ctrl;71 72 mutex_lock(adata->acp_lock);73 ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);74 ext_int_ctrl |= PDM_DMA_INTR_MASK;75 writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);76 mutex_unlock(adata->acp_lock);77}78 79static void acp63_disable_pdm_interrupts(struct pdm_dev_data *adata)80{81 u32 ext_int_ctrl;82 83 mutex_lock(adata->acp_lock);84 ext_int_ctrl = readl(adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);85 ext_int_ctrl &= ~PDM_DMA_INTR_MASK;86 writel(ext_int_ctrl, adata->acp63_base + ACP_EXTERNAL_INTR_CNTL);87 mutex_unlock(adata->acp_lock);88}89 90static bool acp63_check_pdm_dma_status(void __iomem *acp_base)91{92 bool pdm_dma_status;93 u32 pdm_enable, pdm_dma_enable;94 95 pdm_dma_status = false;96 pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE);97 pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);98 if ((pdm_enable & ACP_PDM_ENABLE) && (pdm_dma_enable & ACP_PDM_DMA_EN_STATUS))99 pdm_dma_status = true;100 101 return pdm_dma_status;102}103 104static int acp63_start_pdm_dma(void __iomem *acp_base)105{106 u32 pdm_enable;107 u32 pdm_dma_enable;108 int timeout;109 110 pdm_enable = 0x01;111 pdm_dma_enable = 0x01;112 113 acp63_enable_pdm_clock(acp_base);114 writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);115 writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);116 timeout = 0;117 while (++timeout < ACP_COUNTER) {118 pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);119 if ((pdm_dma_enable & 0x02) == ACP_PDM_DMA_EN_STATUS)120 return 0;121 udelay(DELAY_US);122 }123 return -ETIMEDOUT;124}125 126static int acp63_stop_pdm_dma(void __iomem *acp_base)127{128 u32 pdm_enable, pdm_dma_enable;129 int timeout;130 131 pdm_enable = 0x00;132 pdm_dma_enable = 0x00;133 134 pdm_enable = readl(acp_base + ACP_WOV_PDM_ENABLE);135 pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);136 if (pdm_dma_enable & 0x01) {137 pdm_dma_enable = 0x02;138 writel(pdm_dma_enable, acp_base + ACP_WOV_PDM_DMA_ENABLE);139 timeout = 0;140 while (++timeout < ACP_COUNTER) {141 pdm_dma_enable = readl(acp_base + ACP_WOV_PDM_DMA_ENABLE);142 if ((pdm_dma_enable & 0x02) == 0x00)143 break;144 udelay(DELAY_US);145 }146 if (timeout == ACP_COUNTER)147 return -ETIMEDOUT;148 }149 if (pdm_enable == ACP_PDM_ENABLE) {150 pdm_enable = ACP_PDM_DISABLE;151 writel(pdm_enable, acp_base + ACP_WOV_PDM_ENABLE);152 }153 writel(0x01, acp_base + ACP_WOV_PDM_FIFO_FLUSH);154 return 0;155}156 157static void acp63_config_dma(struct pdm_stream_instance *rtd, int direction)158{159 u16 page_idx;160 u32 low, high, val;161 dma_addr_t addr;162 163 addr = rtd->dma_addr;164 val = PDM_PTE_OFFSET;165 166 /* Group Enable */167 writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp63_base + ACPAXI2AXI_ATU_BASE_ADDR_GRP_1);168 writel(PAGE_SIZE_4K_ENABLE, rtd->acp63_base + ACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);169 for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {170 /* Load the low address of page int ACP SRAM through SRBM */171 low = lower_32_bits(addr);172 high = upper_32_bits(addr);173 174 writel(low, rtd->acp63_base + ACP_SCRATCH_REG_0 + val);175 high |= BIT(31);176 writel(high, rtd->acp63_base + ACP_SCRATCH_REG_0 + val + 4);177 val += 8;178 addr += PAGE_SIZE;179 }180}181 182static int acp63_pdm_dma_open(struct snd_soc_component *component,183 struct snd_pcm_substream *substream)184{185 struct snd_pcm_runtime *runtime;186 struct pdm_dev_data *adata;187 struct pdm_stream_instance *pdm_data;188 int ret;189 190 runtime = substream->runtime;191 adata = dev_get_drvdata(component->dev);192 pdm_data = kzalloc(sizeof(*pdm_data), GFP_KERNEL);193 if (!pdm_data)194 return -EINVAL;195 196 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)197 runtime->hw = acp63_pdm_hardware_capture;198 199 ret = snd_pcm_hw_constraint_integer(runtime,200 SNDRV_PCM_HW_PARAM_PERIODS);201 if (ret < 0) {202 dev_err(component->dev, "set integer constraint failed\n");203 kfree(pdm_data);204 return ret;205 }206 207 acp63_enable_pdm_interrupts(adata);208 209 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)210 adata->capture_stream = substream;211 212 pdm_data->acp63_base = adata->acp63_base;213 runtime->private_data = pdm_data;214 return ret;215}216 217static int acp63_pdm_dma_hw_params(struct snd_soc_component *component,218 struct snd_pcm_substream *substream,219 struct snd_pcm_hw_params *params)220{221 struct pdm_stream_instance *rtd;222 size_t size, period_bytes;223 224 rtd = substream->runtime->private_data;225 if (!rtd)226 return -EINVAL;227 size = params_buffer_bytes(params);228 period_bytes = params_period_bytes(params);229 rtd->dma_addr = substream->runtime->dma_addr;230 rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);231 acp63_config_dma(rtd, substream->stream);232 acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, size,233 period_bytes, rtd->acp63_base);234 return 0;235}236 237static u64 acp63_pdm_get_byte_count(struct pdm_stream_instance *rtd,238 int direction)239{240 u32 high, low;241 u64 byte_count;242 243 high = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_HIGH);244 byte_count = high;245 low = readl(rtd->acp63_base + ACP_WOV_RX_LINEARPOSITIONCNTR_LOW);246 byte_count = (byte_count << 32) | low;247 return byte_count;248}249 250static snd_pcm_uframes_t acp63_pdm_dma_pointer(struct snd_soc_component *comp,251 struct snd_pcm_substream *stream)252{253 struct pdm_stream_instance *rtd;254 u32 pos, buffersize;255 u64 bytescount;256 257 rtd = stream->runtime->private_data;258 buffersize = frames_to_bytes(stream->runtime,259 stream->runtime->buffer_size);260 bytescount = acp63_pdm_get_byte_count(rtd, stream->stream);261 if (bytescount > rtd->bytescount)262 bytescount -= rtd->bytescount;263 pos = do_div(bytescount, buffersize);264 return bytes_to_frames(stream->runtime, pos);265}266 267static int acp63_pdm_dma_new(struct snd_soc_component *component,268 struct snd_soc_pcm_runtime *rtd)269{270 struct device *parent = component->dev->parent;271 272 snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,273 parent, MIN_BUFFER, MAX_BUFFER);274 return 0;275}276 277static int acp63_pdm_dma_close(struct snd_soc_component *component,278 struct snd_pcm_substream *substream)279{280 struct pdm_dev_data *adata = dev_get_drvdata(component->dev);281 struct snd_pcm_runtime *runtime = substream->runtime;282 283 acp63_disable_pdm_interrupts(adata);284 adata->capture_stream = NULL;285 kfree(runtime->private_data);286 return 0;287}288 289static int acp63_pdm_dai_trigger(struct snd_pcm_substream *substream,290 int cmd, struct snd_soc_dai *dai)291{292 struct pdm_stream_instance *rtd;293 int ret;294 bool pdm_status;295 unsigned int ch_mask;296 297 rtd = substream->runtime->private_data;298 ret = 0;299 switch (substream->runtime->channels) {300 case TWO_CH:301 ch_mask = 0x00;302 break;303 default:304 return -EINVAL;305 }306 switch (cmd) {307 case SNDRV_PCM_TRIGGER_START:308 case SNDRV_PCM_TRIGGER_RESUME:309 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:310 writel(ch_mask, rtd->acp63_base + ACP_WOV_PDM_NO_OF_CHANNELS);311 writel(PDM_DECIMATION_FACTOR, rtd->acp63_base + ACP_WOV_PDM_DECIMATION_FACTOR);312 rtd->bytescount = acp63_pdm_get_byte_count(rtd, substream->stream);313 pdm_status = acp63_check_pdm_dma_status(rtd->acp63_base);314 if (!pdm_status)315 ret = acp63_start_pdm_dma(rtd->acp63_base);316 break;317 case SNDRV_PCM_TRIGGER_STOP:318 case SNDRV_PCM_TRIGGER_SUSPEND:319 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:320 pdm_status = acp63_check_pdm_dma_status(rtd->acp63_base);321 if (pdm_status)322 ret = acp63_stop_pdm_dma(rtd->acp63_base);323 break;324 default:325 ret = -EINVAL;326 break;327 }328 return ret;329}330 331static const struct snd_soc_dai_ops acp63_pdm_dai_ops = {332 .trigger = acp63_pdm_dai_trigger,333};334 335static struct snd_soc_dai_driver acp63_pdm_dai_driver = {336 .name = "acp_ps_pdm_dma.0",337 .capture = {338 .rates = SNDRV_PCM_RATE_48000,339 .formats = SNDRV_PCM_FMTBIT_S32_LE,340 .channels_min = 2,341 .channels_max = 2,342 .rate_min = 48000,343 .rate_max = 48000,344 },345 .ops = &acp63_pdm_dai_ops,346};347 348static const struct snd_soc_component_driver acp63_pdm_component = {349 .name = DRV_NAME,350 .open = acp63_pdm_dma_open,351 .close = acp63_pdm_dma_close,352 .hw_params = acp63_pdm_dma_hw_params,353 .pointer = acp63_pdm_dma_pointer,354 .pcm_construct = acp63_pdm_dma_new,355};356 357static int acp63_pdm_audio_probe(struct platform_device *pdev)358{359 struct resource *res;360 struct pdm_dev_data *adata;361 struct acp63_dev_data *acp_data;362 struct device *parent;363 int status;364 365 parent = pdev->dev.parent;366 acp_data = dev_get_drvdata(parent);367 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);368 if (!res) {369 dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");370 return -ENODEV;371 }372 373 adata = devm_kzalloc(&pdev->dev, sizeof(*adata), GFP_KERNEL);374 if (!adata)375 return -ENOMEM;376 377 adata->acp63_base = devm_ioremap(&pdev->dev, res->start, resource_size(res));378 if (!adata->acp63_base)379 return -ENOMEM;380 381 adata->capture_stream = NULL;382 adata->acp_lock = &acp_data->acp_lock;383 dev_set_drvdata(&pdev->dev, adata);384 status = devm_snd_soc_register_component(&pdev->dev,385 &acp63_pdm_component,386 &acp63_pdm_dai_driver, 1);387 if (status) {388 dev_err(&pdev->dev, "Fail to register acp pdm dai\n");389 390 return -ENODEV;391 }392 pm_runtime_set_autosuspend_delay(&pdev->dev, ACP_SUSPEND_DELAY_MS);393 pm_runtime_use_autosuspend(&pdev->dev);394 pm_runtime_mark_last_busy(&pdev->dev);395 pm_runtime_set_active(&pdev->dev);396 pm_runtime_enable(&pdev->dev);397 return 0;398}399 400static void acp63_pdm_audio_remove(struct platform_device *pdev)401{402 pm_runtime_disable(&pdev->dev);403}404 405static int __maybe_unused acp63_pdm_resume(struct device *dev)406{407 struct pdm_dev_data *adata;408 struct snd_pcm_runtime *runtime;409 struct pdm_stream_instance *rtd;410 u32 period_bytes, buffer_len;411 412 adata = dev_get_drvdata(dev);413 if (adata->capture_stream && adata->capture_stream->runtime) {414 runtime = adata->capture_stream->runtime;415 rtd = runtime->private_data;416 period_bytes = frames_to_bytes(runtime, runtime->period_size);417 buffer_len = frames_to_bytes(runtime, runtime->buffer_size);418 acp63_config_dma(rtd, SNDRV_PCM_STREAM_CAPTURE);419 acp63_init_pdm_ring_buffer(PDM_MEM_WINDOW_START, buffer_len,420 period_bytes, adata->acp63_base);421 }422 acp63_enable_pdm_interrupts(adata);423 return 0;424}425 426static int __maybe_unused acp63_pdm_suspend(struct device *dev)427{428 struct pdm_dev_data *adata;429 430 adata = dev_get_drvdata(dev);431 acp63_disable_pdm_interrupts(adata);432 return 0;433}434 435static int __maybe_unused acp63_pdm_runtime_resume(struct device *dev)436{437 struct pdm_dev_data *adata;438 439 adata = dev_get_drvdata(dev);440 acp63_enable_pdm_interrupts(adata);441 return 0;442}443 444static const struct dev_pm_ops acp63_pdm_pm_ops = {445 SET_RUNTIME_PM_OPS(acp63_pdm_suspend, acp63_pdm_runtime_resume, NULL)446 SET_SYSTEM_SLEEP_PM_OPS(acp63_pdm_suspend, acp63_pdm_resume)447};448 449static struct platform_driver acp63_pdm_dma_driver = {450 .probe = acp63_pdm_audio_probe,451 .remove = acp63_pdm_audio_remove,452 .driver = {453 .name = "acp_ps_pdm_dma",454 .pm = &acp63_pdm_pm_ops,455 },456};457 458module_platform_driver(acp63_pdm_dma_driver);459 460MODULE_AUTHOR("Syed.SabaKareem@amd.com");461MODULE_DESCRIPTION("AMD PINK SARDINE PDM Driver");462MODULE_LICENSE("GPL v2");463MODULE_ALIAS("platform:" DRV_NAME);464