162306a36Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
262306a36Sopenharmony_ci//
362306a36Sopenharmony_ci// This file is provided under a dual BSD/GPLv2 license. When using or
462306a36Sopenharmony_ci// redistributing this file, you may do so under either license.
562306a36Sopenharmony_ci//
662306a36Sopenharmony_ci// Copyright(c) 2021 Advanced Micro Devices, Inc.
762306a36Sopenharmony_ci//
862306a36Sopenharmony_ci// Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * Generic interface for ACP audio blck PCM component
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/err.h>
1762306a36Sopenharmony_ci#include <linux/io.h>
1862306a36Sopenharmony_ci#include <sound/pcm_params.h>
1962306a36Sopenharmony_ci#include <sound/soc.h>
2062306a36Sopenharmony_ci#include <sound/soc-dai.h>
2162306a36Sopenharmony_ci#include <linux/dma-mapping.h>
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#include "amd.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#define DRV_NAME "acp_i2s_dma"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic const struct snd_pcm_hardware acp_pcm_hardware_playback = {
2862306a36Sopenharmony_ci	.info = SNDRV_PCM_INFO_INTERLEAVED |
2962306a36Sopenharmony_ci		SNDRV_PCM_INFO_BLOCK_TRANSFER |
3062306a36Sopenharmony_ci		SNDRV_PCM_INFO_BATCH |
3162306a36Sopenharmony_ci		SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
3262306a36Sopenharmony_ci		SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
3362306a36Sopenharmony_ci	.formats = SNDRV_PCM_FMTBIT_S16_LE |  SNDRV_PCM_FMTBIT_S8 |
3462306a36Sopenharmony_ci		   SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
3562306a36Sopenharmony_ci		   SNDRV_PCM_FMTBIT_S32_LE,
3662306a36Sopenharmony_ci	.channels_min = 2,
3762306a36Sopenharmony_ci	.channels_max = 8,
3862306a36Sopenharmony_ci	.rates = SNDRV_PCM_RATE_8000_96000,
3962306a36Sopenharmony_ci	.rate_min = 8000,
4062306a36Sopenharmony_ci	.rate_max = 96000,
4162306a36Sopenharmony_ci	.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
4262306a36Sopenharmony_ci	.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
4362306a36Sopenharmony_ci	.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
4462306a36Sopenharmony_ci	.periods_min = PLAYBACK_MIN_NUM_PERIODS,
4562306a36Sopenharmony_ci	.periods_max = PLAYBACK_MAX_NUM_PERIODS,
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic const struct snd_pcm_hardware acp_pcm_hardware_capture = {
4962306a36Sopenharmony_ci	.info = SNDRV_PCM_INFO_INTERLEAVED |
5062306a36Sopenharmony_ci		SNDRV_PCM_INFO_BLOCK_TRANSFER |
5162306a36Sopenharmony_ci		SNDRV_PCM_INFO_BATCH |
5262306a36Sopenharmony_ci		SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
5362306a36Sopenharmony_ci		SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
5462306a36Sopenharmony_ci	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
5562306a36Sopenharmony_ci		   SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
5662306a36Sopenharmony_ci		   SNDRV_PCM_FMTBIT_S32_LE,
5762306a36Sopenharmony_ci	.channels_min = 2,
5862306a36Sopenharmony_ci	.channels_max = 2,
5962306a36Sopenharmony_ci	.rates = SNDRV_PCM_RATE_8000_48000,
6062306a36Sopenharmony_ci	.rate_min = 8000,
6162306a36Sopenharmony_ci	.rate_max = 48000,
6262306a36Sopenharmony_ci	.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
6362306a36Sopenharmony_ci	.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
6462306a36Sopenharmony_ci	.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
6562306a36Sopenharmony_ci	.periods_min = CAPTURE_MIN_NUM_PERIODS,
6662306a36Sopenharmony_ci	.periods_max = CAPTURE_MAX_NUM_PERIODS,
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ciint acp_machine_select(struct acp_dev_data *adata)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct snd_soc_acpi_mach *mach;
7262306a36Sopenharmony_ci	int size;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	size = sizeof(*adata->machines);
7562306a36Sopenharmony_ci	mach = snd_soc_acpi_find_machine(adata->machines);
7662306a36Sopenharmony_ci	if (!mach) {
7762306a36Sopenharmony_ci		dev_err(adata->dev, "warning: No matching ASoC machine driver found\n");
7862306a36Sopenharmony_ci		return -EINVAL;
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	adata->mach_dev = platform_device_register_data(adata->dev, mach->drv_name,
8262306a36Sopenharmony_ci							PLATFORM_DEVID_NONE, mach, size);
8362306a36Sopenharmony_ci	if (IS_ERR(adata->mach_dev))
8462306a36Sopenharmony_ci		dev_warn(adata->dev, "Unable to register Machine device\n");
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	return 0;
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic irqreturn_t i2s_irq_handler(int irq, void *data)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct acp_dev_data *adata = data;
9362306a36Sopenharmony_ci	struct acp_resource *rsrc = adata->rsrc;
9462306a36Sopenharmony_ci	struct acp_stream *stream;
9562306a36Sopenharmony_ci	u16 i2s_flag = 0;
9662306a36Sopenharmony_ci	u32 ext_intr_stat, ext_intr_stat1;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (!adata)
9962306a36Sopenharmony_ci		return IRQ_NONE;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (adata->rsrc->no_of_ctrls == 2)
10262306a36Sopenharmony_ci		ext_intr_stat1 = readl(ACP_EXTERNAL_INTR_STAT(adata, (rsrc->irqp_used - 1)));
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	ext_intr_stat = readl(ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used));
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	spin_lock(&adata->acp_lock);
10762306a36Sopenharmony_ci	list_for_each_entry(stream, &adata->stream_list, list) {
10862306a36Sopenharmony_ci		if (ext_intr_stat & stream->irq_bit) {
10962306a36Sopenharmony_ci			writel(stream->irq_bit,
11062306a36Sopenharmony_ci			       ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used));
11162306a36Sopenharmony_ci			snd_pcm_period_elapsed(stream->substream);
11262306a36Sopenharmony_ci			i2s_flag = 1;
11362306a36Sopenharmony_ci		}
11462306a36Sopenharmony_ci		if (adata->rsrc->no_of_ctrls == 2) {
11562306a36Sopenharmony_ci			if (ext_intr_stat1 & stream->irq_bit) {
11662306a36Sopenharmony_ci				writel(stream->irq_bit, ACP_EXTERNAL_INTR_STAT(adata,
11762306a36Sopenharmony_ci				       (rsrc->irqp_used - 1)));
11862306a36Sopenharmony_ci				snd_pcm_period_elapsed(stream->substream);
11962306a36Sopenharmony_ci				i2s_flag = 1;
12062306a36Sopenharmony_ci			}
12162306a36Sopenharmony_ci		}
12262306a36Sopenharmony_ci	}
12362306a36Sopenharmony_ci	spin_unlock(&adata->acp_lock);
12462306a36Sopenharmony_ci	if (i2s_flag)
12562306a36Sopenharmony_ci		return IRQ_HANDLED;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	return IRQ_NONE;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_civoid config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct acp_resource *rsrc = adata->rsrc;
13362306a36Sopenharmony_ci	u32 pte_reg, pte_size, reg_val;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/* Use ATU base Group5 */
13662306a36Sopenharmony_ci	pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5;
13762306a36Sopenharmony_ci	pte_size =  ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5;
13862306a36Sopenharmony_ci	stream->reg_offset = 0x02000000;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	/* Group Enable */
14162306a36Sopenharmony_ci	reg_val = rsrc->sram_pte_offset;
14262306a36Sopenharmony_ci	writel(reg_val | BIT(31), adata->acp_base + pte_reg);
14362306a36Sopenharmony_ci	writel(PAGE_SIZE_4K_ENABLE,  adata->acp_base + pte_size);
14462306a36Sopenharmony_ci	writel(0x01, adata->acp_base + ACPAXI2AXI_ATU_CTRL);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(config_pte_for_stream, SND_SOC_ACP_COMMON);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_civoid config_acp_dma(struct acp_dev_data *adata, struct acp_stream *stream, int size)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	struct snd_pcm_substream *substream = stream->substream;
15162306a36Sopenharmony_ci	struct acp_resource *rsrc = adata->rsrc;
15262306a36Sopenharmony_ci	dma_addr_t addr = substream->dma_buffer.addr;
15362306a36Sopenharmony_ci	int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
15462306a36Sopenharmony_ci	u32 low, high, val;
15562306a36Sopenharmony_ci	u16 page_idx;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	val = stream->pte_offset;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	for (page_idx = 0; page_idx < num_pages; page_idx++) {
16062306a36Sopenharmony_ci		/* Load the low address of page int ACP SRAM through SRBM */
16162306a36Sopenharmony_ci		low = lower_32_bits(addr);
16262306a36Sopenharmony_ci		high = upper_32_bits(addr);
16362306a36Sopenharmony_ci		writel(low, adata->acp_base + rsrc->scratch_reg_offset + val);
16462306a36Sopenharmony_ci		high |= BIT(31);
16562306a36Sopenharmony_ci		writel(high, adata->acp_base + rsrc->scratch_reg_offset + val + 4);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci		/* Move to next physically contiguous page */
16862306a36Sopenharmony_ci		val += 8;
16962306a36Sopenharmony_ci		addr += PAGE_SIZE;
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(config_acp_dma, SND_SOC_ACP_COMMON);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream)
17562306a36Sopenharmony_ci{
17662306a36Sopenharmony_ci	struct snd_pcm_runtime *runtime = substream->runtime;
17762306a36Sopenharmony_ci	struct device *dev = component->dev;
17862306a36Sopenharmony_ci	struct acp_dev_data *adata = dev_get_drvdata(dev);
17962306a36Sopenharmony_ci	struct acp_stream *stream;
18062306a36Sopenharmony_ci	int ret;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	stream = kzalloc(sizeof(*stream), GFP_KERNEL);
18362306a36Sopenharmony_ci	if (!stream)
18462306a36Sopenharmony_ci		return -ENOMEM;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	stream->substream = substream;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
18962306a36Sopenharmony_ci		runtime->hw = acp_pcm_hardware_playback;
19062306a36Sopenharmony_ci	else
19162306a36Sopenharmony_ci		runtime->hw = acp_pcm_hardware_capture;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
19462306a36Sopenharmony_ci	if (ret < 0) {
19562306a36Sopenharmony_ci		dev_err(component->dev, "set integer constraint failed\n");
19662306a36Sopenharmony_ci		kfree(stream);
19762306a36Sopenharmony_ci		return ret;
19862306a36Sopenharmony_ci	}
19962306a36Sopenharmony_ci	runtime->private_data = stream;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	writel(1, ACP_EXTERNAL_INTR_ENB(adata));
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	spin_lock_irq(&adata->acp_lock);
20462306a36Sopenharmony_ci	list_add_tail(&stream->list, &adata->stream_list);
20562306a36Sopenharmony_ci	spin_unlock_irq(&adata->acp_lock);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	return ret;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_cistatic int acp_dma_hw_params(struct snd_soc_component *component,
21162306a36Sopenharmony_ci			     struct snd_pcm_substream *substream,
21262306a36Sopenharmony_ci			     struct snd_pcm_hw_params *params)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct acp_dev_data *adata = snd_soc_component_get_drvdata(component);
21562306a36Sopenharmony_ci	struct acp_stream *stream = substream->runtime->private_data;
21662306a36Sopenharmony_ci	u64 size = params_buffer_bytes(params);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	/* Configure ACP DMA block with params */
21962306a36Sopenharmony_ci	config_pte_for_stream(adata, stream);
22062306a36Sopenharmony_ci	config_acp_dma(adata, stream, size);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	return 0;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component,
22662306a36Sopenharmony_ci					 struct snd_pcm_substream *substream)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	struct device *dev = component->dev;
22962306a36Sopenharmony_ci	struct acp_dev_data *adata = dev_get_drvdata(dev);
23062306a36Sopenharmony_ci	struct acp_stream *stream = substream->runtime->private_data;
23162306a36Sopenharmony_ci	u32 pos, buffersize;
23262306a36Sopenharmony_ci	u64 bytescount;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	buffersize = frames_to_bytes(substream->runtime,
23562306a36Sopenharmony_ci				     substream->runtime->buffer_size);
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	bytescount = acp_get_byte_count(adata, stream->dai_id, substream->stream);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (bytescount > stream->bytescount)
24062306a36Sopenharmony_ci		bytescount -= stream->bytescount;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	pos = do_div(bytescount, buffersize);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	return bytes_to_frames(substream->runtime, pos);
24562306a36Sopenharmony_ci}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_cistatic int acp_dma_new(struct snd_soc_component *component,
24862306a36Sopenharmony_ci		       struct snd_soc_pcm_runtime *rtd)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct device *parent = component->dev->parent;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
25362306a36Sopenharmony_ci				       parent, MIN_BUFFER, MAX_BUFFER);
25462306a36Sopenharmony_ci	return 0;
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_cistatic int acp_dma_close(struct snd_soc_component *component,
25862306a36Sopenharmony_ci			 struct snd_pcm_substream *substream)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct device *dev = component->dev;
26162306a36Sopenharmony_ci	struct acp_dev_data *adata = dev_get_drvdata(dev);
26262306a36Sopenharmony_ci	struct acp_stream *stream = substream->runtime->private_data;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	/* Remove entry from list */
26562306a36Sopenharmony_ci	spin_lock_irq(&adata->acp_lock);
26662306a36Sopenharmony_ci	list_del(&stream->list);
26762306a36Sopenharmony_ci	spin_unlock_irq(&adata->acp_lock);
26862306a36Sopenharmony_ci	kfree(stream);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	return 0;
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic const struct snd_soc_component_driver acp_pcm_component = {
27462306a36Sopenharmony_ci	.name			= DRV_NAME,
27562306a36Sopenharmony_ci	.open			= acp_dma_open,
27662306a36Sopenharmony_ci	.close			= acp_dma_close,
27762306a36Sopenharmony_ci	.hw_params		= acp_dma_hw_params,
27862306a36Sopenharmony_ci	.pointer		= acp_dma_pointer,
27962306a36Sopenharmony_ci	.pcm_construct		= acp_dma_new,
28062306a36Sopenharmony_ci	.legacy_dai_naming	= 1,
28162306a36Sopenharmony_ci};
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ciint acp_platform_register(struct device *dev)
28462306a36Sopenharmony_ci{
28562306a36Sopenharmony_ci	struct acp_dev_data *adata = dev_get_drvdata(dev);
28662306a36Sopenharmony_ci	struct snd_soc_dai_driver;
28762306a36Sopenharmony_ci	unsigned int status;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	status = devm_request_irq(dev, adata->i2s_irq, i2s_irq_handler,
29062306a36Sopenharmony_ci				  IRQF_SHARED, "ACP_I2S_IRQ", adata);
29162306a36Sopenharmony_ci	if (status) {
29262306a36Sopenharmony_ci		dev_err(dev, "ACP I2S IRQ request failed\n");
29362306a36Sopenharmony_ci		return status;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	status = devm_snd_soc_register_component(dev, &acp_pcm_component,
29762306a36Sopenharmony_ci						 adata->dai_driver,
29862306a36Sopenharmony_ci						 adata->num_dai);
29962306a36Sopenharmony_ci	if (status) {
30062306a36Sopenharmony_ci		dev_err(dev, "Fail to register acp i2s component\n");
30162306a36Sopenharmony_ci		return status;
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	INIT_LIST_HEAD(&adata->stream_list);
30562306a36Sopenharmony_ci	spin_lock_init(&adata->acp_lock);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	return 0;
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON);
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ciint acp_platform_unregister(struct device *dev)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct acp_dev_data *adata = dev_get_drvdata(dev);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (adata->mach_dev)
31662306a36Sopenharmony_ci		platform_device_unregister(adata->mach_dev);
31762306a36Sopenharmony_ci	return 0;
31862306a36Sopenharmony_ci}
31962306a36Sopenharmony_ciEXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON);
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ciMODULE_DESCRIPTION("AMD ACP PCM Driver");
32262306a36Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL");
32362306a36Sopenharmony_ciMODULE_ALIAS(DRV_NAME);
324