1 | // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) |
2 | // |
3 | // This file is provided under a dual BSD/GPLv2 license. When using or |
4 | // redistributing this file, you may do so under either license. |
5 | // |
6 | // Copyright(c) 2021 Advanced Micro Devices, Inc. |
7 | // |
8 | // Authors: Ajit Kumar Pandey <AjitKumar.Pandey@amd.com> |
9 | |
10 | /* |
11 | * Generic interface for ACP audio blck PCM component |
12 | */ |
13 | |
14 | #include <linux/platform_device.h> |
15 | #include <linux/module.h> |
16 | #include <linux/err.h> |
17 | #include <linux/io.h> |
18 | #include <sound/pcm_params.h> |
19 | #include <sound/soc.h> |
20 | #include <sound/soc-dai.h> |
21 | #include <linux/dma-mapping.h> |
22 | |
23 | #include "amd.h" |
24 | #include "../mach-config.h" |
25 | #include "acp-mach.h" |
26 | |
27 | #define DRV_NAME "acp_i2s_dma" |
28 | |
29 | static const struct snd_pcm_hardware acp_pcm_hardware_playback = { |
30 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
31 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
32 | SNDRV_PCM_INFO_BATCH | |
33 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | |
34 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
35 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
36 | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
37 | SNDRV_PCM_FMTBIT_S32_LE, |
38 | .channels_min = 2, |
39 | .channels_max = 8, |
40 | .rates = SNDRV_PCM_RATE_8000_96000, |
41 | .rate_min = 8000, |
42 | .rate_max = 96000, |
43 | .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE, |
44 | .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, |
45 | .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, |
46 | .periods_min = PLAYBACK_MIN_NUM_PERIODS, |
47 | .periods_max = PLAYBACK_MAX_NUM_PERIODS, |
48 | }; |
49 | |
50 | static const struct snd_pcm_hardware acp_pcm_hardware_capture = { |
51 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
52 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
53 | SNDRV_PCM_INFO_BATCH | |
54 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | |
55 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME, |
56 | .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 | |
57 | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE | |
58 | SNDRV_PCM_FMTBIT_S32_LE, |
59 | .channels_min = 2, |
60 | .channels_max = 2, |
61 | .rates = SNDRV_PCM_RATE_8000_48000, |
62 | .rate_min = 8000, |
63 | .rate_max = 48000, |
64 | .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE, |
65 | .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, |
66 | .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, |
67 | .periods_min = CAPTURE_MIN_NUM_PERIODS, |
68 | .periods_max = CAPTURE_MAX_NUM_PERIODS, |
69 | }; |
70 | |
71 | int acp_machine_select(struct acp_dev_data *adata) |
72 | { |
73 | struct snd_soc_acpi_mach *mach; |
74 | int size, platform; |
75 | |
76 | if (adata->flag == FLAG_AMD_LEGACY_ONLY_DMIC) { |
77 | platform = adata->platform; |
78 | adata->mach_dev = platform_device_register_data(parent: adata->dev, name: "acp-pdm-mach" , |
79 | PLATFORM_DEVID_NONE, data: &platform, |
80 | size: sizeof(platform)); |
81 | } else { |
82 | size = sizeof(*adata->machines); |
83 | mach = snd_soc_acpi_find_machine(machines: adata->machines); |
84 | if (!mach) { |
85 | dev_err(adata->dev, "warning: No matching ASoC machine driver found\n" ); |
86 | return -EINVAL; |
87 | } |
88 | adata->mach_dev = platform_device_register_data(parent: adata->dev, name: mach->drv_name, |
89 | PLATFORM_DEVID_NONE, data: mach, size); |
90 | } |
91 | if (IS_ERR(ptr: adata->mach_dev)) |
92 | dev_warn(adata->dev, "Unable to register Machine device\n" ); |
93 | return 0; |
94 | } |
95 | EXPORT_SYMBOL_NS_GPL(acp_machine_select, SND_SOC_ACP_COMMON); |
96 | |
97 | static irqreturn_t i2s_irq_handler(int irq, void *data) |
98 | { |
99 | struct acp_dev_data *adata = data; |
100 | struct acp_resource *rsrc = adata->rsrc; |
101 | struct acp_stream *stream; |
102 | u16 i2s_flag = 0; |
103 | u32 ext_intr_stat, ext_intr_stat1; |
104 | |
105 | if (!adata) |
106 | return IRQ_NONE; |
107 | |
108 | if (adata->rsrc->no_of_ctrls == 2) |
109 | ext_intr_stat1 = readl(ACP_EXTERNAL_INTR_STAT(adata, (rsrc->irqp_used - 1))); |
110 | |
111 | ext_intr_stat = readl(ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); |
112 | |
113 | spin_lock(lock: &adata->acp_lock); |
114 | list_for_each_entry(stream, &adata->stream_list, list) { |
115 | if (ext_intr_stat & stream->irq_bit) { |
116 | writel(val: stream->irq_bit, |
117 | ACP_EXTERNAL_INTR_STAT(adata, rsrc->irqp_used)); |
118 | snd_pcm_period_elapsed(substream: stream->substream); |
119 | i2s_flag = 1; |
120 | } |
121 | if (adata->rsrc->no_of_ctrls == 2) { |
122 | if (ext_intr_stat1 & stream->irq_bit) { |
123 | writel(val: stream->irq_bit, ACP_EXTERNAL_INTR_STAT(adata, |
124 | (rsrc->irqp_used - 1))); |
125 | snd_pcm_period_elapsed(substream: stream->substream); |
126 | i2s_flag = 1; |
127 | } |
128 | } |
129 | } |
130 | spin_unlock(lock: &adata->acp_lock); |
131 | if (i2s_flag) |
132 | return IRQ_HANDLED; |
133 | |
134 | return IRQ_NONE; |
135 | } |
136 | |
137 | void config_pte_for_stream(struct acp_dev_data *adata, struct acp_stream *stream) |
138 | { |
139 | struct acp_resource *rsrc = adata->rsrc; |
140 | u32 pte_reg, pte_size, reg_val; |
141 | |
142 | /* Use ATU base Group5 */ |
143 | pte_reg = ACPAXI2AXI_ATU_BASE_ADDR_GRP_5; |
144 | pte_size = ACPAXI2AXI_ATU_PAGE_SIZE_GRP_5; |
145 | stream->reg_offset = 0x02000000; |
146 | |
147 | /* Group Enable */ |
148 | reg_val = rsrc->sram_pte_offset; |
149 | writel(val: reg_val | BIT(31), addr: adata->acp_base + pte_reg); |
150 | writel(PAGE_SIZE_4K_ENABLE, addr: adata->acp_base + pte_size); |
151 | writel(val: 0x01, addr: adata->acp_base + ACPAXI2AXI_ATU_CTRL); |
152 | } |
153 | EXPORT_SYMBOL_NS_GPL(config_pte_for_stream, SND_SOC_ACP_COMMON); |
154 | |
155 | void config_acp_dma(struct acp_dev_data *adata, struct acp_stream *stream, int size) |
156 | { |
157 | struct snd_pcm_substream *substream = stream->substream; |
158 | struct acp_resource *rsrc = adata->rsrc; |
159 | dma_addr_t addr = substream->dma_buffer.addr; |
160 | int num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT); |
161 | u32 low, high, val; |
162 | u16 page_idx; |
163 | |
164 | val = stream->pte_offset; |
165 | |
166 | for (page_idx = 0; page_idx < num_pages; page_idx++) { |
167 | /* Load the low address of page int ACP SRAM through SRBM */ |
168 | low = lower_32_bits(addr); |
169 | high = upper_32_bits(addr); |
170 | writel(val: low, addr: adata->acp_base + rsrc->scratch_reg_offset + val); |
171 | high |= BIT(31); |
172 | writel(val: high, addr: adata->acp_base + rsrc->scratch_reg_offset + val + 4); |
173 | |
174 | /* Move to next physically contiguous page */ |
175 | val += 8; |
176 | addr += PAGE_SIZE; |
177 | } |
178 | } |
179 | EXPORT_SYMBOL_NS_GPL(config_acp_dma, SND_SOC_ACP_COMMON); |
180 | |
181 | static int acp_dma_open(struct snd_soc_component *component, struct snd_pcm_substream *substream) |
182 | { |
183 | struct snd_pcm_runtime *runtime = substream->runtime; |
184 | struct device *dev = component->dev; |
185 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
186 | struct acp_stream *stream; |
187 | int ret; |
188 | |
189 | stream = kzalloc(size: sizeof(*stream), GFP_KERNEL); |
190 | if (!stream) |
191 | return -ENOMEM; |
192 | |
193 | stream->substream = substream; |
194 | |
195 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
196 | runtime->hw = acp_pcm_hardware_playback; |
197 | else |
198 | runtime->hw = acp_pcm_hardware_capture; |
199 | |
200 | ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
201 | if (ret < 0) { |
202 | dev_err(component->dev, "set integer constraint failed\n" ); |
203 | kfree(objp: stream); |
204 | return ret; |
205 | } |
206 | runtime->private_data = stream; |
207 | |
208 | writel(val: 1, ACP_EXTERNAL_INTR_ENB(adata)); |
209 | |
210 | spin_lock_irq(lock: &adata->acp_lock); |
211 | list_add_tail(new: &stream->list, head: &adata->stream_list); |
212 | spin_unlock_irq(lock: &adata->acp_lock); |
213 | |
214 | return ret; |
215 | } |
216 | |
217 | static int acp_dma_hw_params(struct snd_soc_component *component, |
218 | struct snd_pcm_substream *substream, |
219 | struct snd_pcm_hw_params *params) |
220 | { |
221 | struct acp_dev_data *adata = snd_soc_component_get_drvdata(c: component); |
222 | struct acp_stream *stream = substream->runtime->private_data; |
223 | u64 size = params_buffer_bytes(p: params); |
224 | |
225 | /* Configure ACP DMA block with params */ |
226 | config_pte_for_stream(adata, stream); |
227 | config_acp_dma(adata, stream, size); |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static snd_pcm_uframes_t acp_dma_pointer(struct snd_soc_component *component, |
233 | struct snd_pcm_substream *substream) |
234 | { |
235 | struct device *dev = component->dev; |
236 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
237 | struct acp_stream *stream = substream->runtime->private_data; |
238 | u32 pos, buffersize; |
239 | u64 bytescount; |
240 | |
241 | buffersize = frames_to_bytes(runtime: substream->runtime, |
242 | size: substream->runtime->buffer_size); |
243 | |
244 | bytescount = acp_get_byte_count(adata, dai_id: stream->dai_id, direction: substream->stream); |
245 | |
246 | if (bytescount > stream->bytescount) |
247 | bytescount -= stream->bytescount; |
248 | |
249 | pos = do_div(bytescount, buffersize); |
250 | |
251 | return bytes_to_frames(runtime: substream->runtime, size: pos); |
252 | } |
253 | |
254 | static int acp_dma_new(struct snd_soc_component *component, |
255 | struct snd_soc_pcm_runtime *rtd) |
256 | { |
257 | struct device *parent = component->dev->parent; |
258 | |
259 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, SNDRV_DMA_TYPE_DEV, |
260 | data: parent, MIN_BUFFER, MAX_BUFFER); |
261 | return 0; |
262 | } |
263 | |
264 | static int acp_dma_close(struct snd_soc_component *component, |
265 | struct snd_pcm_substream *substream) |
266 | { |
267 | struct device *dev = component->dev; |
268 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
269 | struct acp_stream *stream = substream->runtime->private_data; |
270 | |
271 | /* Remove entry from list */ |
272 | spin_lock_irq(lock: &adata->acp_lock); |
273 | list_del(entry: &stream->list); |
274 | spin_unlock_irq(lock: &adata->acp_lock); |
275 | kfree(objp: stream); |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static const struct snd_soc_component_driver acp_pcm_component = { |
281 | .name = DRV_NAME, |
282 | .open = acp_dma_open, |
283 | .close = acp_dma_close, |
284 | .hw_params = acp_dma_hw_params, |
285 | .pointer = acp_dma_pointer, |
286 | .pcm_construct = acp_dma_new, |
287 | .legacy_dai_naming = 1, |
288 | }; |
289 | |
290 | int acp_platform_register(struct device *dev) |
291 | { |
292 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
293 | struct snd_soc_dai_driver; |
294 | unsigned int status; |
295 | |
296 | status = devm_request_irq(dev, irq: adata->i2s_irq, handler: i2s_irq_handler, |
297 | IRQF_SHARED, devname: "ACP_I2S_IRQ" , dev_id: adata); |
298 | if (status) { |
299 | dev_err(dev, "ACP I2S IRQ request failed\n" ); |
300 | return status; |
301 | } |
302 | |
303 | status = devm_snd_soc_register_component(dev, component_driver: &acp_pcm_component, |
304 | dai_drv: adata->dai_driver, |
305 | num_dai: adata->num_dai); |
306 | if (status) { |
307 | dev_err(dev, "Fail to register acp i2s component\n" ); |
308 | return status; |
309 | } |
310 | |
311 | INIT_LIST_HEAD(list: &adata->stream_list); |
312 | spin_lock_init(&adata->acp_lock); |
313 | |
314 | return 0; |
315 | } |
316 | EXPORT_SYMBOL_NS_GPL(acp_platform_register, SND_SOC_ACP_COMMON); |
317 | |
318 | int acp_platform_unregister(struct device *dev) |
319 | { |
320 | struct acp_dev_data *adata = dev_get_drvdata(dev); |
321 | |
322 | if (adata->mach_dev) |
323 | platform_device_unregister(adata->mach_dev); |
324 | return 0; |
325 | } |
326 | EXPORT_SYMBOL_NS_GPL(acp_platform_unregister, SND_SOC_ACP_COMMON); |
327 | |
328 | MODULE_DESCRIPTION("AMD ACP PCM Driver" ); |
329 | MODULE_LICENSE("Dual BSD/GPL" ); |
330 | MODULE_ALIAS(DRV_NAME); |
331 | |