blob: 06c1985c4d56dbb6c51c67c2c4638746b0a5eb24 [file] [log] [blame]
Vijendar Mukundaac289c72018-11-12 11:04:55 +05301/*
2 * AMD ALSA SoC PCM Driver
3 *
4 * Copyright 2016 Advanced Micro Devices, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 */
15
16#include <linux/platform_device.h>
17#include <linux/module.h>
18#include <linux/err.h>
19#include <linux/io.h>
20#include <sound/pcm.h>
21#include <sound/pcm_params.h>
22#include <sound/soc.h>
23#include <sound/soc-dai.h>
24
25#include "acp3x.h"
26
27#define DRV_NAME "acp3x-i2s-audio"
28
29struct i2s_dev_data {
Vijendar Mukunda32feac92018-11-12 11:04:56 +053030 unsigned int i2s_irq;
Vijendar Mukundaac289c72018-11-12 11:04:55 +053031 void __iomem *acp3x_base;
32 struct snd_pcm_substream *play_stream;
33 struct snd_pcm_substream *capture_stream;
34};
35
Vijendar Mukunda0b87d6b2018-11-12 11:04:57 +053036struct i2s_stream_instance {
37 u16 num_pages;
38 u16 channels;
39 u32 xfer_resolution;
40 struct page *pg;
41 void __iomem *acp3x_base;
42};
43
44static const struct snd_pcm_hardware acp3x_pcm_hardware_playback = {
45 .info = SNDRV_PCM_INFO_INTERLEAVED |
46 SNDRV_PCM_INFO_BLOCK_TRANSFER |
47 SNDRV_PCM_INFO_BATCH |
48 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
49 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
50 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
51 SNDRV_PCM_FMTBIT_S32_LE,
52 .channels_min = 2,
53 .channels_max = 8,
54 .rates = SNDRV_PCM_RATE_8000_96000,
55 .rate_min = 8000,
56 .rate_max = 96000,
57 .buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
58 .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
59 .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
60 .periods_min = PLAYBACK_MIN_NUM_PERIODS,
61 .periods_max = PLAYBACK_MAX_NUM_PERIODS,
62};
63
64static const struct snd_pcm_hardware acp3x_pcm_hardware_capture = {
65 .info = SNDRV_PCM_INFO_INTERLEAVED |
66 SNDRV_PCM_INFO_BLOCK_TRANSFER |
67 SNDRV_PCM_INFO_BATCH |
68 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
69 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
70 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S24_LE |
71 SNDRV_PCM_FMTBIT_S32_LE,
72 .channels_min = 2,
73 .channels_max = 2,
74 .rates = SNDRV_PCM_RATE_8000_48000,
75 .rate_min = 8000,
76 .rate_max = 48000,
77 .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
78 .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
79 .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
80 .periods_min = CAPTURE_MIN_NUM_PERIODS,
81 .periods_max = CAPTURE_MAX_NUM_PERIODS,
82};
83
Vijendar Mukundaac289c72018-11-12 11:04:55 +053084static int acp3x_power_on(void __iomem *acp3x_base, bool on)
85{
86 u16 val, mask;
87 u32 timeout;
88
89 if (on == true) {
90 val = 1;
91 mask = ACP3x_POWER_ON;
92 } else {
93 val = 0;
94 mask = ACP3x_POWER_OFF;
95 }
96
97 rv_writel(val, acp3x_base + mmACP_PGFSM_CONTROL);
98 timeout = 0;
99 while (true) {
100 val = rv_readl(acp3x_base + mmACP_PGFSM_STATUS);
101 if ((val & ACP3x_POWER_OFF_IN_PROGRESS) == mask)
102 break;
103 if (timeout > 100) {
104 pr_err("ACP3x power state change failure\n");
105 return -ENODEV;
106 }
107 timeout++;
108 cpu_relax();
109 }
110 return 0;
111}
112
113static int acp3x_reset(void __iomem *acp3x_base)
114{
115 u32 val, timeout;
116
117 rv_writel(1, acp3x_base + mmACP_SOFT_RESET);
118 timeout = 0;
119 while (true) {
120 val = rv_readl(acp3x_base + mmACP_SOFT_RESET);
121 if ((val & ACP3x_SOFT_RESET__SoftResetAudDone_MASK) ||
122 timeout > 100) {
123 if (val & ACP3x_SOFT_RESET__SoftResetAudDone_MASK)
124 break;
125 return -ENODEV;
126 }
127 timeout++;
128 cpu_relax();
129 }
130
131 rv_writel(0, acp3x_base + mmACP_SOFT_RESET);
132 timeout = 0;
133 while (true) {
134 val = rv_readl(acp3x_base + mmACP_SOFT_RESET);
135 if (!val || timeout > 100) {
136 if (!val)
137 break;
138 return -ENODEV;
139 }
140 timeout++;
141 cpu_relax();
142 }
143 return 0;
144}
145
146static int acp3x_init(void __iomem *acp3x_base)
147{
148 int ret;
149
150 /* power on */
151 ret = acp3x_power_on(acp3x_base, true);
152 if (ret) {
153 pr_err("ACP3x power on failed\n");
154 return ret;
155 }
156 /* Reset */
157 ret = acp3x_reset(acp3x_base);
158 if (ret) {
159 pr_err("ACP3x reset failed\n");
160 return ret;
161 }
162 return 0;
163}
164
165static int acp3x_deinit(void __iomem *acp3x_base)
166{
167 int ret;
168
169 /* Reset */
170 ret = acp3x_reset(acp3x_base);
171 if (ret) {
172 pr_err("ACP3x reset failed\n");
173 return ret;
174 }
175 /* power off */
176 ret = acp3x_power_on(acp3x_base, false);
177 if (ret) {
178 pr_err("ACP3x power off failed\n");
179 return ret;
180 }
181 return 0;
182}
183
Vijendar Mukunda32feac92018-11-12 11:04:56 +0530184static irqreturn_t i2s_irq_handler(int irq, void *dev_id)
185{
186 u16 play_flag, cap_flag;
187 u32 val;
188 struct i2s_dev_data *rv_i2s_data = dev_id;
189
190 if (!rv_i2s_data)
191 return IRQ_NONE;
192
193 play_flag = 0;
194 cap_flag = 0;
195 val = rv_readl(rv_i2s_data->acp3x_base + mmACP_EXTERNAL_INTR_STAT);
196 if ((val & BIT(BT_TX_THRESHOLD)) && rv_i2s_data->play_stream) {
197 rv_writel(BIT(BT_TX_THRESHOLD), rv_i2s_data->acp3x_base +
198 mmACP_EXTERNAL_INTR_STAT);
199 snd_pcm_period_elapsed(rv_i2s_data->play_stream);
200 play_flag = 1;
201 }
202
203 if ((val & BIT(BT_RX_THRESHOLD)) && rv_i2s_data->capture_stream) {
204 rv_writel(BIT(BT_RX_THRESHOLD), rv_i2s_data->acp3x_base +
205 mmACP_EXTERNAL_INTR_STAT);
206 snd_pcm_period_elapsed(rv_i2s_data->capture_stream);
207 cap_flag = 1;
208 }
209
210 if (play_flag | cap_flag)
211 return IRQ_HANDLED;
212 else
213 return IRQ_NONE;
214}
215
Vijendar Mukunda0b87d6b2018-11-12 11:04:57 +0530216static void config_acp3x_dma(struct i2s_stream_instance *rtd, int direction)
217{
218 u16 page_idx;
219 u64 addr;
220 u32 low, high, val, acp_fifo_addr;
221 struct page *pg = rtd->pg;
222
223 /* 8 scratch registers used to map one 64 bit address */
224 if (direction == SNDRV_PCM_STREAM_PLAYBACK)
225 val = 0;
226 else
227 val = rtd->num_pages * 8;
228
229 /* Group Enable */
230 rv_writel(ACP_SRAM_PTE_OFFSET | BIT(31), rtd->acp3x_base +
231 mmACPAXI2AXI_ATU_BASE_ADDR_GRP_1);
232 rv_writel(PAGE_SIZE_4K_ENABLE, rtd->acp3x_base +
233 mmACPAXI2AXI_ATU_PAGE_SIZE_GRP_1);
234
235 for (page_idx = 0; page_idx < rtd->num_pages; page_idx++) {
236 /* Load the low address of page int ACP SRAM through SRBM */
237 addr = page_to_phys(pg);
238 low = lower_32_bits(addr);
239 high = upper_32_bits(addr);
240
241 rv_writel(low, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val);
242 high |= BIT(31);
243 rv_writel(high, rtd->acp3x_base + mmACP_SCRATCH_REG_0 + val
244 + 4);
245 /* Move to next physically contiguos page */
246 val += 8;
247 pg++;
248 }
249
250 if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
251 /* Config ringbuffer */
252 rv_writel(MEM_WINDOW_START, rtd->acp3x_base +
253 mmACP_BT_TX_RINGBUFADDR);
254 rv_writel(MAX_BUFFER, rtd->acp3x_base +
255 mmACP_BT_TX_RINGBUFSIZE);
256 rv_writel(DMA_SIZE, rtd->acp3x_base + mmACP_BT_TX_DMA_SIZE);
257
258 /* Config audio fifo */
259 acp_fifo_addr = ACP_SRAM_PTE_OFFSET + (rtd->num_pages * 8)
260 + PLAYBACK_FIFO_ADDR_OFFSET;
261 rv_writel(acp_fifo_addr, rtd->acp3x_base +
262 mmACP_BT_TX_FIFOADDR);
263 rv_writel(FIFO_SIZE, rtd->acp3x_base + mmACP_BT_TX_FIFOSIZE);
264 } else {
265 /* Config ringbuffer */
266 rv_writel(MEM_WINDOW_START + MAX_BUFFER, rtd->acp3x_base +
267 mmACP_BT_RX_RINGBUFADDR);
268 rv_writel(MAX_BUFFER, rtd->acp3x_base +
269 mmACP_BT_RX_RINGBUFSIZE);
270 rv_writel(DMA_SIZE, rtd->acp3x_base + mmACP_BT_RX_DMA_SIZE);
271
272 /* Config audio fifo */
273 acp_fifo_addr = ACP_SRAM_PTE_OFFSET +
274 (rtd->num_pages * 8) + CAPTURE_FIFO_ADDR_OFFSET;
275 rv_writel(acp_fifo_addr, rtd->acp3x_base +
276 mmACP_BT_RX_FIFOADDR);
277 rv_writel(FIFO_SIZE, rtd->acp3x_base + mmACP_BT_RX_FIFOSIZE);
278 }
279
280 /* Enable watermark/period interrupt to host */
281 rv_writel(BIT(BT_TX_THRESHOLD) | BIT(BT_RX_THRESHOLD),
282 rtd->acp3x_base + mmACP_EXTERNAL_INTR_CNTL);
283}
284
285static int acp3x_dma_open(struct snd_pcm_substream *substream)
286{
287 int ret = 0;
288
289 struct snd_pcm_runtime *runtime = substream->runtime;
290 struct snd_soc_pcm_runtime *prtd = substream->private_data;
291 struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd,
292 DRV_NAME);
293 struct i2s_dev_data *adata = dev_get_drvdata(component->dev);
294
295 struct i2s_stream_instance *i2s_data = kzalloc(sizeof(struct i2s_stream_instance),
296 GFP_KERNEL);
297 if (!i2s_data)
298 return -EINVAL;
299
300 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
301 runtime->hw = acp3x_pcm_hardware_playback;
302 else
303 runtime->hw = acp3x_pcm_hardware_capture;
304
305 ret = snd_pcm_hw_constraint_integer(runtime,
306 SNDRV_PCM_HW_PARAM_PERIODS);
307 if (ret < 0) {
308 dev_err(component->dev, "set integer constraint failed\n");
309 return ret;
310 }
311
312 if (!adata->play_stream && !adata->capture_stream)
313 rv_writel(1, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
314
315 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
316 adata->play_stream = substream;
317 else
318 adata->capture_stream = substream;
319
320 i2s_data->acp3x_base = adata->acp3x_base;
321 runtime->private_data = i2s_data;
322 return 0;
323}
324
325static int acp3x_dma_hw_params(struct snd_pcm_substream *substream,
326 struct snd_pcm_hw_params *params)
327{
328 int status;
329 u64 size;
330 struct snd_dma_buffer *dma_buffer;
331 struct page *pg;
332 struct snd_pcm_runtime *runtime = substream->runtime;
333 struct i2s_stream_instance *rtd = runtime->private_data;
334
335 if (!rtd)
336 return -EINVAL;
337
338 dma_buffer = &substream->dma_buffer;
339 size = params_buffer_bytes(params);
340 status = snd_pcm_lib_malloc_pages(substream, size);
341 if (status < 0)
342 return status;
343
344 memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
345 pg = virt_to_page(substream->dma_buffer.area);
346 if (pg) {
347 rtd->pg = pg;
348 rtd->num_pages = (PAGE_ALIGN(size) >> PAGE_SHIFT);
349 config_acp3x_dma(rtd, substream->stream);
350 status = 0;
351 } else {
352 status = -ENOMEM;
353 }
354 return status;
355}
356
357static snd_pcm_uframes_t acp3x_dma_pointer(struct snd_pcm_substream *substream)
358{
359 u32 pos = 0;
360 struct i2s_stream_instance *rtd = substream->runtime->private_data;
361
362 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
363 pos = rv_readl(rtd->acp3x_base +
364 mmACP_BT_TX_LINKPOSITIONCNTR);
365 else
366 pos = rv_readl(rtd->acp3x_base +
367 mmACP_BT_RX_LINKPOSITIONCNTR);
368
369 if (pos >= MAX_BUFFER)
370 pos = 0;
371
372 return bytes_to_frames(substream->runtime, pos);
373}
374
375static int acp3x_dma_new(struct snd_soc_pcm_runtime *rtd)
376{
377 return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
378 SNDRV_DMA_TYPE_DEV,
379 NULL, MIN_BUFFER,
380 MAX_BUFFER);
381}
382
383static int acp3x_dma_hw_free(struct snd_pcm_substream *substream)
384{
385 return snd_pcm_lib_free_pages(substream);
386}
387
388static int acp3x_dma_mmap(struct snd_pcm_substream *substream,
389 struct vm_area_struct *vma)
390{
391 return snd_pcm_lib_default_mmap(substream, vma);
392}
393
394static int acp3x_dma_close(struct snd_pcm_substream *substream)
395{
396 struct snd_soc_pcm_runtime *prtd = substream->private_data;
397 struct i2s_stream_instance *rtd = substream->runtime->private_data;
398 struct snd_soc_component *component = snd_soc_rtdcom_lookup(prtd,
399 DRV_NAME);
400 struct i2s_dev_data *adata = dev_get_drvdata(component->dev);
401
402 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
403 adata->play_stream = NULL;
404 else
405 adata->capture_stream = NULL;
406
407 /* Disable ACP irq, when the current stream is being closed and
408 * another stream is also not active.
409 */
410 if (!adata->play_stream && !adata->capture_stream)
411 rv_writel(0, adata->acp3x_base + mmACP_EXTERNAL_INTR_ENB);
412 kfree(rtd);
413 return 0;
414}
415
Vijendar Mukundaac289c72018-11-12 11:04:55 +0530416static struct snd_pcm_ops acp3x_dma_ops = {
Vijendar Mukunda0b87d6b2018-11-12 11:04:57 +0530417 .open = acp3x_dma_open,
418 .close = acp3x_dma_close,
419 .ioctl = snd_pcm_lib_ioctl,
420 .hw_params = acp3x_dma_hw_params,
421 .hw_free = acp3x_dma_hw_free,
422 .pointer = acp3x_dma_pointer,
423 .mmap = acp3x_dma_mmap,
Vijendar Mukundaac289c72018-11-12 11:04:55 +0530424};
425
426struct snd_soc_dai_ops acp3x_dai_i2s_ops = {
427 .hw_params = NULL,
428 .trigger = NULL,
429 .set_fmt = NULL,
430};
431
432static struct snd_soc_dai_driver acp3x_i2s_dai_driver = {
433 .playback = {
434 .rates = SNDRV_PCM_RATE_8000_96000,
435 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
436 SNDRV_PCM_FMTBIT_U8 |
437 SNDRV_PCM_FMTBIT_S24_LE |
438 SNDRV_PCM_FMTBIT_S32_LE,
439 .channels_min = 2,
440 .channels_max = 8,
441
442 .rate_min = 8000,
443 .rate_max = 96000,
444 },
445 .capture = {
446 .rates = SNDRV_PCM_RATE_8000_48000,
447 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
448 SNDRV_PCM_FMTBIT_U8 |
449 SNDRV_PCM_FMTBIT_S24_LE |
450 SNDRV_PCM_FMTBIT_S32_LE,
451 .channels_min = 2,
452 .channels_max = 2,
453 .rate_min = 8000,
454 .rate_max = 48000,
455 },
456 .ops = &acp3x_dai_i2s_ops,
457};
458
459static const struct snd_soc_component_driver acp3x_i2s_component = {
460 .name = DRV_NAME,
461 .ops = &acp3x_dma_ops,
462 .pcm_new = acp3x_dma_new,
463};
464
465static int acp3x_audio_probe(struct platform_device *pdev)
466{
467 int status;
468 struct resource *res;
469 struct i2s_dev_data *adata;
470 unsigned int irqflags;
471
472 if (!pdev->dev.platform_data) {
473 dev_err(&pdev->dev, "platform_data not retrieved\n");
474 return -ENODEV;
475 }
476 irqflags = *((unsigned int *)(pdev->dev.platform_data));
477
478 adata = devm_kzalloc(&pdev->dev, sizeof(struct i2s_dev_data),
479 GFP_KERNEL);
480 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
481 if (!res) {
482 dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
483 return -ENODEV;
484 }
485
486 adata->acp3x_base = devm_ioremap(&pdev->dev, res->start,
487 resource_size(res));
488
Vijendar Mukunda32feac92018-11-12 11:04:56 +0530489 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
490 if (!res) {
491 dev_err(&pdev->dev, "IORESOURCE_IRQ FAILED\n");
492 return -ENODEV;
493 }
494
495 adata->i2s_irq = res->start;
Vijendar Mukundaac289c72018-11-12 11:04:55 +0530496 adata->play_stream = NULL;
497 adata->capture_stream = NULL;
498
499 dev_set_drvdata(&pdev->dev, adata);
500 /* Initialize ACP */
501 status = acp3x_init(adata->acp3x_base);
502 if (status)
503 return -ENODEV;
504 status = devm_snd_soc_register_component(&pdev->dev,
505 &acp3x_i2s_component,
506 &acp3x_i2s_dai_driver, 1);
507 if (status) {
508 dev_err(&pdev->dev, "Fail to register acp i2s dai\n");
509 goto dev_err;
510 }
Vijendar Mukunda32feac92018-11-12 11:04:56 +0530511 status = devm_request_irq(&pdev->dev, adata->i2s_irq, i2s_irq_handler,
512 irqflags, "ACP3x_I2S_IRQ", adata);
513 if (status) {
514 dev_err(&pdev->dev, "ACP3x I2S IRQ request failed\n");
515 goto dev_err;
516 }
Vijendar Mukundaac289c72018-11-12 11:04:55 +0530517
518 return 0;
519dev_err:
520 status = acp3x_deinit(adata->acp3x_base);
521 if (status)
522 dev_err(&pdev->dev, "ACP de-init failed\n");
523 else
524 dev_info(&pdev->dev, "ACP de-initialized\n");
525 /*ignore device status and return driver probe error*/
526 return -ENODEV;
527}
528
529static int acp3x_audio_remove(struct platform_device *pdev)
530{
531 int ret;
532 struct i2s_dev_data *adata = dev_get_drvdata(&pdev->dev);
533
534 ret = acp3x_deinit(adata->acp3x_base);
535 if (ret)
536 dev_err(&pdev->dev, "ACP de-init failed\n");
537 else
538 dev_info(&pdev->dev, "ACP de-initialized\n");
539
540 return 0;
541}
542
543static struct platform_driver acp3x_dma_driver = {
544 .probe = acp3x_audio_probe,
545 .remove = acp3x_audio_remove,
546 .driver = {
547 .name = "acp3x_rv_i2s",
548 },
549};
550
551module_platform_driver(acp3x_dma_driver);
552
553MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com");
554MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
555MODULE_DESCRIPTION("AMD ACP 3.x PCM Driver");
556MODULE_LICENSE("GPL v2");
557MODULE_ALIAS("platform:" DRV_NAME);