1// SPDX-License-Identifier: GPL-2.0+
2//
3// AMD ALSA SoC PCM Driver
4//
5// Copyright (C) 2021 Advanced Micro Devices, Inc. All rights reserved.
6
7#include <linux/platform_device.h>
8#include <linux/module.h>
9#include <linux/err.h>
10#include <linux/io.h>
11#include <sound/pcm_params.h>
12#include <sound/soc.h>
13#include <sound/soc-dai.h>
14#include <linux/dma-mapping.h>
15
16#include "acp5x.h"
17
18#define DRV_NAME "acp5x_i2s_playcap"
19
20static int acp5x_i2s_set_fmt(struct snd_soc_dai *cpu_dai,
21 unsigned int fmt)
22{
23 struct i2s_dev_data *adata;
24 int mode;
25
26 adata = snd_soc_dai_get_drvdata(dai: cpu_dai);
27 mode = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
28 switch (mode) {
29 case SND_SOC_DAIFMT_I2S:
30 adata->tdm_mode = TDM_DISABLE;
31 break;
32 case SND_SOC_DAIFMT_DSP_A:
33 adata->tdm_mode = TDM_ENABLE;
34 break;
35 default:
36 return -EINVAL;
37 }
38 mode = fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK;
39 switch (mode) {
40 case SND_SOC_DAIFMT_BP_FP:
41 adata->master_mode = I2S_MASTER_MODE_ENABLE;
42 break;
43 case SND_SOC_DAIFMT_BC_FC:
44 adata->master_mode = I2S_MASTER_MODE_DISABLE;
45 break;
46 }
47 return 0;
48}
49
50static int acp5x_i2s_set_tdm_slot(struct snd_soc_dai *cpu_dai,
51 u32 tx_mask, u32 rx_mask,
52 int slots, int slot_width)
53{
54 struct i2s_dev_data *adata;
55 u32 frm_len;
56 u16 slot_len;
57
58 adata = snd_soc_dai_get_drvdata(dai: cpu_dai);
59
60 /* These values are as per Hardware Spec */
61 switch (slot_width) {
62 case SLOT_WIDTH_8:
63 slot_len = 8;
64 break;
65 case SLOT_WIDTH_16:
66 slot_len = 16;
67 break;
68 case SLOT_WIDTH_24:
69 slot_len = 24;
70 break;
71 case SLOT_WIDTH_32:
72 slot_len = 0;
73 break;
74 default:
75 return -EINVAL;
76 }
77 frm_len = FRM_LEN | (slots << 15) | (slot_len << 18);
78 adata->tdm_fmt = frm_len;
79 return 0;
80}
81
82static int acp5x_i2s_hwparams(struct snd_pcm_substream *substream,
83 struct snd_pcm_hw_params *params,
84 struct snd_soc_dai *dai)
85{
86 struct i2s_stream_instance *rtd;
87 struct snd_soc_pcm_runtime *prtd;
88 struct snd_soc_card *card;
89 struct acp5x_platform_info *pinfo;
90 struct i2s_dev_data *adata;
91
92 u32 val;
93 u32 reg_val, frmt_reg;
94 u32 lrclk_div_val, bclk_div_val;
95
96 lrclk_div_val = 0;
97 bclk_div_val = 0;
98 prtd = snd_soc_substream_to_rtd(substream);
99 rtd = substream->runtime->private_data;
100 card = prtd->card;
101 adata = snd_soc_dai_get_drvdata(dai);
102 pinfo = snd_soc_card_get_drvdata(card);
103 if (pinfo) {
104 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
105 rtd->i2s_instance = pinfo->play_i2s_instance;
106 else
107 rtd->i2s_instance = pinfo->cap_i2s_instance;
108 }
109
110 /* These values are as per Hardware Spec */
111 switch (params_format(p: params)) {
112 case SNDRV_PCM_FORMAT_U8:
113 case SNDRV_PCM_FORMAT_S8:
114 rtd->xfer_resolution = 0x0;
115 break;
116 case SNDRV_PCM_FORMAT_S16_LE:
117 rtd->xfer_resolution = 0x02;
118 break;
119 case SNDRV_PCM_FORMAT_S24_LE:
120 rtd->xfer_resolution = 0x04;
121 break;
122 case SNDRV_PCM_FORMAT_S32_LE:
123 rtd->xfer_resolution = 0x05;
124 break;
125 default:
126 return -EINVAL;
127 }
128 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
129 switch (rtd->i2s_instance) {
130 case I2S_HS_INSTANCE:
131 reg_val = ACP_HSTDM_ITER;
132 frmt_reg = ACP_HSTDM_TXFRMT;
133 break;
134 case I2S_SP_INSTANCE:
135 default:
136 reg_val = ACP_I2STDM_ITER;
137 frmt_reg = ACP_I2STDM_TXFRMT;
138 }
139 } else {
140 switch (rtd->i2s_instance) {
141 case I2S_HS_INSTANCE:
142 reg_val = ACP_HSTDM_IRER;
143 frmt_reg = ACP_HSTDM_RXFRMT;
144 break;
145 case I2S_SP_INSTANCE:
146 default:
147 reg_val = ACP_I2STDM_IRER;
148 frmt_reg = ACP_I2STDM_RXFRMT;
149 }
150 }
151 if (adata->tdm_mode) {
152 val = acp_readl(base_addr: rtd->acp5x_base + reg_val);
153 acp_writel(val: val | 0x2, base_addr: rtd->acp5x_base + reg_val);
154 acp_writel(val: adata->tdm_fmt, base_addr: rtd->acp5x_base + frmt_reg);
155 }
156 val = acp_readl(base_addr: rtd->acp5x_base + reg_val);
157 val &= ~ACP5x_ITER_IRER_SAMP_LEN_MASK;
158 val = val | (rtd->xfer_resolution << 3);
159 acp_writel(val, base_addr: rtd->acp5x_base + reg_val);
160
161 if (adata->master_mode) {
162 switch (params_format(p: params)) {
163 case SNDRV_PCM_FORMAT_S16_LE:
164 switch (params_rate(p: params)) {
165 case 8000:
166 bclk_div_val = 768;
167 break;
168 case 16000:
169 bclk_div_val = 384;
170 break;
171 case 24000:
172 bclk_div_val = 256;
173 break;
174 case 32000:
175 bclk_div_val = 192;
176 break;
177 case 44100:
178 case 48000:
179 bclk_div_val = 128;
180 break;
181 case 88200:
182 case 96000:
183 bclk_div_val = 64;
184 break;
185 case 192000:
186 bclk_div_val = 32;
187 break;
188 default:
189 return -EINVAL;
190 }
191 lrclk_div_val = 32;
192 break;
193 case SNDRV_PCM_FORMAT_S32_LE:
194 switch (params_rate(p: params)) {
195 case 8000:
196 bclk_div_val = 384;
197 break;
198 case 16000:
199 bclk_div_val = 192;
200 break;
201 case 24000:
202 bclk_div_val = 128;
203 break;
204 case 32000:
205 bclk_div_val = 96;
206 break;
207 case 44100:
208 case 48000:
209 bclk_div_val = 64;
210 break;
211 case 88200:
212 case 96000:
213 bclk_div_val = 32;
214 break;
215 case 192000:
216 bclk_div_val = 16;
217 break;
218 default:
219 return -EINVAL;
220 }
221 lrclk_div_val = 64;
222 break;
223 default:
224 return -EINVAL;
225 }
226 rtd->lrclk_div = lrclk_div_val;
227 rtd->bclk_div = bclk_div_val;
228 }
229 return 0;
230}
231
232static int acp5x_i2s_trigger(struct snd_pcm_substream *substream,
233 int cmd, struct snd_soc_dai *dai)
234{
235 struct i2s_stream_instance *rtd;
236 struct i2s_dev_data *adata;
237 u32 ret, val, period_bytes, reg_val, ier_val, water_val;
238 u32 buf_size, buf_reg;
239
240 adata = snd_soc_dai_get_drvdata(dai);
241 rtd = substream->runtime->private_data;
242 period_bytes = frames_to_bytes(runtime: substream->runtime,
243 size: substream->runtime->period_size);
244 buf_size = frames_to_bytes(runtime: substream->runtime,
245 size: substream->runtime->buffer_size);
246 switch (cmd) {
247 case SNDRV_PCM_TRIGGER_START:
248 case SNDRV_PCM_TRIGGER_RESUME:
249 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
250 rtd->bytescount = acp_get_byte_count(rtd,
251 direction: substream->stream);
252 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
253 switch (rtd->i2s_instance) {
254 case I2S_HS_INSTANCE:
255 water_val =
256 ACP_HS_TX_INTR_WATERMARK_SIZE;
257 reg_val = ACP_HSTDM_ITER;
258 ier_val = ACP_HSTDM_IER;
259 buf_reg = ACP_HS_TX_RINGBUFSIZE;
260 break;
261 case I2S_SP_INSTANCE:
262 default:
263 water_val =
264 ACP_I2S_TX_INTR_WATERMARK_SIZE;
265 reg_val = ACP_I2STDM_ITER;
266 ier_val = ACP_I2STDM_IER;
267 buf_reg = ACP_I2S_TX_RINGBUFSIZE;
268 }
269 } else {
270 switch (rtd->i2s_instance) {
271 case I2S_HS_INSTANCE:
272 water_val =
273 ACP_HS_RX_INTR_WATERMARK_SIZE;
274 reg_val = ACP_HSTDM_IRER;
275 ier_val = ACP_HSTDM_IER;
276 buf_reg = ACP_HS_RX_RINGBUFSIZE;
277 break;
278 case I2S_SP_INSTANCE:
279 default:
280 water_val =
281 ACP_I2S_RX_INTR_WATERMARK_SIZE;
282 reg_val = ACP_I2STDM_IRER;
283 ier_val = ACP_I2STDM_IER;
284 buf_reg = ACP_I2S_RX_RINGBUFSIZE;
285 }
286 }
287 acp_writel(val: period_bytes, base_addr: rtd->acp5x_base + water_val);
288 acp_writel(val: buf_size, base_addr: rtd->acp5x_base + buf_reg);
289 if (adata->master_mode)
290 acp5x_set_i2s_clk(adata, rtd);
291 val = acp_readl(base_addr: rtd->acp5x_base + reg_val);
292 val = val | BIT(0);
293 acp_writel(val, base_addr: rtd->acp5x_base + reg_val);
294 acp_writel(val: 1, base_addr: rtd->acp5x_base + ier_val);
295 ret = 0;
296 break;
297 case SNDRV_PCM_TRIGGER_STOP:
298 case SNDRV_PCM_TRIGGER_SUSPEND:
299 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
300 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
301 switch (rtd->i2s_instance) {
302 case I2S_HS_INSTANCE:
303 reg_val = ACP_HSTDM_ITER;
304 break;
305 case I2S_SP_INSTANCE:
306 default:
307 reg_val = ACP_I2STDM_ITER;
308 }
309
310 } else {
311 switch (rtd->i2s_instance) {
312 case I2S_HS_INSTANCE:
313 reg_val = ACP_HSTDM_IRER;
314 break;
315 case I2S_SP_INSTANCE:
316 default:
317 reg_val = ACP_I2STDM_IRER;
318 }
319 }
320 val = acp_readl(base_addr: rtd->acp5x_base + reg_val);
321 val = val & ~BIT(0);
322 acp_writel(val, base_addr: rtd->acp5x_base + reg_val);
323
324 if (!(acp_readl(base_addr: rtd->acp5x_base + ACP_HSTDM_ITER) & BIT(0)) &&
325 !(acp_readl(base_addr: rtd->acp5x_base + ACP_HSTDM_IRER) & BIT(0)))
326 acp_writel(val: 0, base_addr: rtd->acp5x_base + ACP_HSTDM_IER);
327 if (!(acp_readl(base_addr: rtd->acp5x_base + ACP_I2STDM_ITER) & BIT(0)) &&
328 !(acp_readl(base_addr: rtd->acp5x_base + ACP_I2STDM_IRER) & BIT(0)))
329 acp_writel(val: 0, base_addr: rtd->acp5x_base + ACP_I2STDM_IER);
330 ret = 0;
331 break;
332 default:
333 ret = -EINVAL;
334 break;
335 }
336 return ret;
337}
338
339static const struct snd_soc_dai_ops acp5x_i2s_dai_ops = {
340 .hw_params = acp5x_i2s_hwparams,
341 .trigger = acp5x_i2s_trigger,
342 .set_fmt = acp5x_i2s_set_fmt,
343 .set_tdm_slot = acp5x_i2s_set_tdm_slot,
344};
345
346static const struct snd_soc_component_driver acp5x_dai_component = {
347 .name = "acp5x-i2s",
348 .legacy_dai_naming = 1,
349};
350
351static struct snd_soc_dai_driver acp5x_i2s_dai = {
352 .playback = {
353 .rates = SNDRV_PCM_RATE_8000_96000,
354 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
355 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
356 .channels_min = 2,
357 .channels_max = 2,
358 .rate_min = 8000,
359 .rate_max = 96000,
360 },
361 .capture = {
362 .rates = SNDRV_PCM_RATE_8000_96000,
363 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 |
364 SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S32_LE,
365 .channels_min = 2,
366 .channels_max = 2,
367 .rate_min = 8000,
368 .rate_max = 96000,
369 },
370 .ops = &acp5x_i2s_dai_ops,
371};
372
373static int acp5x_dai_probe(struct platform_device *pdev)
374{
375 struct resource *res;
376 struct i2s_dev_data *adata;
377 int ret;
378
379 adata = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct i2s_dev_data),
380 GFP_KERNEL);
381 if (!adata)
382 return -ENOMEM;
383
384 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
385 if (!res) {
386 dev_err(&pdev->dev, "IORESOURCE_MEM FAILED\n");
387 return -ENOMEM;
388 }
389 adata->acp5x_base = devm_ioremap(dev: &pdev->dev, offset: res->start,
390 size: resource_size(res));
391 if (!adata->acp5x_base)
392 return -ENOMEM;
393
394 adata->master_mode = I2S_MASTER_MODE_ENABLE;
395 dev_set_drvdata(dev: &pdev->dev, data: adata);
396 ret = devm_snd_soc_register_component(dev: &pdev->dev,
397 component_driver: &acp5x_dai_component,
398 dai_drv: &acp5x_i2s_dai, num_dai: 1);
399 if (ret)
400 dev_err(&pdev->dev, "Fail to register acp i2s dai\n");
401 return ret;
402}
403
404static struct platform_driver acp5x_dai_driver = {
405 .probe = acp5x_dai_probe,
406 .driver = {
407 .name = "acp5x_i2s_playcap",
408 },
409};
410
411module_platform_driver(acp5x_dai_driver);
412
413MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
414MODULE_DESCRIPTION("AMD ACP5.x CPU DAI Driver");
415MODULE_ALIAS("platform:" DRV_NAME);
416MODULE_LICENSE("GPL v2");
417

source code of linux/sound/soc/amd/vangogh/acp5x-i2s.c