1 | // SPDX-License-Identifier: GPL-2.0-or-later
|
---|
2 | /*
|
---|
3 | * Driver for audio on multifunction CS5535 companion device
|
---|
4 | * Copyright (C) Jaya Kumar
|
---|
5 | *
|
---|
6 | * Based on Jaroslav Kysela and Takashi Iwai's examples.
|
---|
7 | * This work was sponsored by CIS(M) Sdn Bhd.
|
---|
8 | *
|
---|
9 | * todo: add be fmt support, spdif, pm
|
---|
10 | */
|
---|
11 |
|
---|
12 | #include <linux/init.h>
|
---|
13 | #include <linux/pci.h>
|
---|
14 | #include <sound/core.h>
|
---|
15 | #include <sound/control.h>
|
---|
16 | #include <sound/initval.h>
|
---|
17 | #include <sound/asoundef.h>
|
---|
18 | #include <sound/pcm.h>
|
---|
19 | #include <sound/pcm_params.h>
|
---|
20 | #include <sound/ac97_codec.h>
|
---|
21 | #include "cs5535audio.h"
|
---|
22 |
|
---|
23 | static const struct snd_pcm_hardware snd_cs5535audio_playback =
|
---|
24 | {
|
---|
25 | .info = (
|
---|
26 | SNDRV_PCM_INFO_MMAP |
|
---|
27 | SNDRV_PCM_INFO_INTERLEAVED |
|
---|
28 | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
---|
29 | SNDRV_PCM_INFO_MMAP_VALID |
|
---|
30 | SNDRV_PCM_INFO_PAUSE |
|
---|
31 | SNDRV_PCM_INFO_RESUME
|
---|
32 | ),
|
---|
33 | .formats = (
|
---|
34 | SNDRV_PCM_FMTBIT_S16_LE
|
---|
35 | ),
|
---|
36 | .rates = (
|
---|
37 | SNDRV_PCM_RATE_CONTINUOUS |
|
---|
38 | SNDRV_PCM_RATE_8000_48000
|
---|
39 | ),
|
---|
40 | .rate_min = 4000,
|
---|
41 | .rate_max = 48000,
|
---|
42 | .channels_min = 2,
|
---|
43 | .channels_max = 2,
|
---|
44 | .buffer_bytes_max = (128*1024),
|
---|
45 | .period_bytes_min = 64,
|
---|
46 | .period_bytes_max = (64*1024 - 16),
|
---|
47 | .periods_min = 1,
|
---|
48 | .periods_max = CS5535AUDIO_MAX_DESCRIPTORS,
|
---|
49 | .fifo_size = 0,
|
---|
50 | };
|
---|
51 |
|
---|
52 | static const struct snd_pcm_hardware snd_cs5535audio_capture =
|
---|
53 | {
|
---|
54 | .info = (
|
---|
55 | SNDRV_PCM_INFO_MMAP |
|
---|
56 | SNDRV_PCM_INFO_INTERLEAVED |
|
---|
57 | SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
---|
58 | SNDRV_PCM_INFO_MMAP_VALID
|
---|
59 | ),
|
---|
60 | .formats = (
|
---|
61 | SNDRV_PCM_FMTBIT_S16_LE
|
---|
62 | ),
|
---|
63 | .rates = (
|
---|
64 | SNDRV_PCM_RATE_CONTINUOUS |
|
---|
65 | SNDRV_PCM_RATE_8000_48000
|
---|
66 | ),
|
---|
67 | .rate_min = 4000,
|
---|
68 | .rate_max = 48000,
|
---|
69 | .channels_min = 2,
|
---|
70 | .channels_max = 2,
|
---|
71 | .buffer_bytes_max = (128*1024),
|
---|
72 | .period_bytes_min = 64,
|
---|
73 | .period_bytes_max = (64*1024 - 16),
|
---|
74 | .periods_min = 1,
|
---|
75 | .periods_max = CS5535AUDIO_MAX_DESCRIPTORS,
|
---|
76 | .fifo_size = 0,
|
---|
77 | };
|
---|
78 |
|
---|
79 | static int snd_cs5535audio_playback_open(struct snd_pcm_substream *substream)
|
---|
80 | {
|
---|
81 | int err;
|
---|
82 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
83 | struct snd_pcm_runtime *runtime = substream->runtime;
|
---|
84 |
|
---|
85 | runtime->hw = snd_cs5535audio_playback;
|
---|
86 | runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_FRONT_DAC];
|
---|
87 | snd_pcm_limit_hw_rates(runtime);
|
---|
88 | cs5535au->playback_substream = substream;
|
---|
89 | runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK]);
|
---|
90 | err = snd_pcm_hw_constraint_integer(runtime,
|
---|
91 | SNDRV_PCM_HW_PARAM_PERIODS);
|
---|
92 | if (err < 0)
|
---|
93 | return err;
|
---|
94 |
|
---|
95 | return 0;
|
---|
96 | }
|
---|
97 |
|
---|
98 | static int snd_cs5535audio_playback_close(struct snd_pcm_substream *substream)
|
---|
99 | {
|
---|
100 | return 0;
|
---|
101 | }
|
---|
102 |
|
---|
103 | #define CS5535AUDIO_DESC_LIST_SIZE \
|
---|
104 | PAGE_ALIGN(CS5535AUDIO_MAX_DESCRIPTORS * sizeof(struct cs5535audio_dma_desc))
|
---|
105 |
|
---|
106 | static int cs5535audio_build_dma_packets(struct cs5535audio *cs5535au,
|
---|
107 | struct cs5535audio_dma *dma,
|
---|
108 | struct snd_pcm_substream *substream,
|
---|
109 | unsigned int periods,
|
---|
110 | unsigned int period_bytes)
|
---|
111 | {
|
---|
112 | unsigned int i;
|
---|
113 | u32 addr, jmpprd_addr;
|
---|
114 | struct cs5535audio_dma_desc *lastdesc;
|
---|
115 |
|
---|
116 | if (periods > CS5535AUDIO_MAX_DESCRIPTORS)
|
---|
117 | return -ENOMEM;
|
---|
118 |
|
---|
119 | if (dma->desc_buf.area == NULL) {
|
---|
120 | if (snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
|
---|
121 | &cs5535au->pci->dev,
|
---|
122 | CS5535AUDIO_DESC_LIST_SIZE+1,
|
---|
123 | &dma->desc_buf) < 0)
|
---|
124 | return -ENOMEM;
|
---|
125 | dma->period_bytes = dma->periods = 0;
|
---|
126 | }
|
---|
127 |
|
---|
128 | if (dma->periods == periods && dma->period_bytes == period_bytes)
|
---|
129 | return 0;
|
---|
130 |
|
---|
131 | /* the u32 cast is okay because in snd*create we successfully told
|
---|
132 | pci alloc that we're only 32 bit capable so the upper will be 0 */
|
---|
133 | addr = (u32) substream->runtime->dma_addr;
|
---|
134 | for (i = 0; i < periods; i++) {
|
---|
135 | struct cs5535audio_dma_desc *desc =
|
---|
136 | &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[i];
|
---|
137 | desc->addr = cpu_to_le32(addr);
|
---|
138 | desc->size = cpu_to_le16(period_bytes);
|
---|
139 | desc->ctlreserved = cpu_to_le16(PRD_EOP);
|
---|
140 | addr += period_bytes;
|
---|
141 | }
|
---|
142 | /* we reserved one dummy descriptor at the end to do the PRD jump */
|
---|
143 | lastdesc = &((struct cs5535audio_dma_desc *) dma->desc_buf.area)[periods];
|
---|
144 | lastdesc->addr = cpu_to_le32((u32) dma->desc_buf.addr);
|
---|
145 | lastdesc->size = 0;
|
---|
146 | lastdesc->ctlreserved = cpu_to_le16(PRD_JMP);
|
---|
147 | jmpprd_addr = (u32)dma->desc_buf.addr +
|
---|
148 | sizeof(struct cs5535audio_dma_desc) * periods;
|
---|
149 |
|
---|
150 | dma->substream = substream;
|
---|
151 | dma->period_bytes = period_bytes;
|
---|
152 | dma->periods = periods;
|
---|
153 | spin_lock_irq(&cs5535au->reg_lock);
|
---|
154 | dma->ops->disable_dma(cs5535au);
|
---|
155 | dma->ops->setup_prd(cs5535au, jmpprd_addr);
|
---|
156 | spin_unlock_irq(&cs5535au->reg_lock);
|
---|
157 | return 0;
|
---|
158 | }
|
---|
159 |
|
---|
160 | static void cs5535audio_playback_enable_dma(struct cs5535audio *cs5535au)
|
---|
161 | {
|
---|
162 | cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_EN);
|
---|
163 | }
|
---|
164 |
|
---|
165 | static void cs5535audio_playback_disable_dma(struct cs5535audio *cs5535au)
|
---|
166 | {
|
---|
167 | cs_writeb(cs5535au, ACC_BM0_CMD, 0);
|
---|
168 | }
|
---|
169 |
|
---|
170 | static void cs5535audio_playback_pause_dma(struct cs5535audio *cs5535au)
|
---|
171 | {
|
---|
172 | cs_writeb(cs5535au, ACC_BM0_CMD, BM_CTL_PAUSE);
|
---|
173 | }
|
---|
174 |
|
---|
175 | static void cs5535audio_playback_setup_prd(struct cs5535audio *cs5535au,
|
---|
176 | u32 prd_addr)
|
---|
177 | {
|
---|
178 | cs_writel(cs5535au, ACC_BM0_PRD, prd_addr);
|
---|
179 | }
|
---|
180 |
|
---|
181 | static u32 cs5535audio_playback_read_prd(struct cs5535audio *cs5535au)
|
---|
182 | {
|
---|
183 | return cs_readl(cs5535au, ACC_BM0_PRD);
|
---|
184 | }
|
---|
185 |
|
---|
186 | static u32 cs5535audio_playback_read_dma_pntr(struct cs5535audio *cs5535au)
|
---|
187 | {
|
---|
188 | return cs_readl(cs5535au, ACC_BM0_PNTR);
|
---|
189 | }
|
---|
190 |
|
---|
191 | static void cs5535audio_capture_enable_dma(struct cs5535audio *cs5535au)
|
---|
192 | {
|
---|
193 | cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_EN);
|
---|
194 | }
|
---|
195 |
|
---|
196 | static void cs5535audio_capture_disable_dma(struct cs5535audio *cs5535au)
|
---|
197 | {
|
---|
198 | cs_writeb(cs5535au, ACC_BM1_CMD, 0);
|
---|
199 | }
|
---|
200 |
|
---|
201 | static void cs5535audio_capture_pause_dma(struct cs5535audio *cs5535au)
|
---|
202 | {
|
---|
203 | cs_writeb(cs5535au, ACC_BM1_CMD, BM_CTL_PAUSE);
|
---|
204 | }
|
---|
205 |
|
---|
206 | static void cs5535audio_capture_setup_prd(struct cs5535audio *cs5535au,
|
---|
207 | u32 prd_addr)
|
---|
208 | {
|
---|
209 | cs_writel(cs5535au, ACC_BM1_PRD, prd_addr);
|
---|
210 | }
|
---|
211 |
|
---|
212 | static u32 cs5535audio_capture_read_prd(struct cs5535audio *cs5535au)
|
---|
213 | {
|
---|
214 | return cs_readl(cs5535au, ACC_BM1_PRD);
|
---|
215 | }
|
---|
216 |
|
---|
217 | static u32 cs5535audio_capture_read_dma_pntr(struct cs5535audio *cs5535au)
|
---|
218 | {
|
---|
219 | return cs_readl(cs5535au, ACC_BM1_PNTR);
|
---|
220 | }
|
---|
221 |
|
---|
222 | static void cs5535audio_clear_dma_packets(struct cs5535audio *cs5535au,
|
---|
223 | struct cs5535audio_dma *dma,
|
---|
224 | struct snd_pcm_substream *substream)
|
---|
225 | {
|
---|
226 | snd_dma_free_pages(&dma->desc_buf);
|
---|
227 | dma->desc_buf.area = NULL;
|
---|
228 | dma->substream = NULL;
|
---|
229 | }
|
---|
230 |
|
---|
231 | static int snd_cs5535audio_hw_params(struct snd_pcm_substream *substream,
|
---|
232 | struct snd_pcm_hw_params *hw_params)
|
---|
233 | {
|
---|
234 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
235 | struct cs5535audio_dma *dma = substream->runtime->private_data;
|
---|
236 | int err;
|
---|
237 |
|
---|
238 | dma->buf_addr = substream->runtime->dma_addr;
|
---|
239 | dma->buf_bytes = params_buffer_bytes(hw_params);
|
---|
240 |
|
---|
241 | err = cs5535audio_build_dma_packets(cs5535au, dma, substream,
|
---|
242 | params_periods(hw_params),
|
---|
243 | params_period_bytes(hw_params));
|
---|
244 | if (!err)
|
---|
245 | dma->pcm_open_flag = 1;
|
---|
246 |
|
---|
247 | return err;
|
---|
248 | }
|
---|
249 |
|
---|
250 | static int snd_cs5535audio_hw_free(struct snd_pcm_substream *substream)
|
---|
251 | {
|
---|
252 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
253 | struct cs5535audio_dma *dma = substream->runtime->private_data;
|
---|
254 |
|
---|
255 | if (dma->pcm_open_flag) {
|
---|
256 | if (substream == cs5535au->playback_substream)
|
---|
257 | snd_ac97_update_power(cs5535au->ac97,
|
---|
258 | AC97_PCM_FRONT_DAC_RATE, 0);
|
---|
259 | else
|
---|
260 | snd_ac97_update_power(cs5535au->ac97,
|
---|
261 | AC97_PCM_LR_ADC_RATE, 0);
|
---|
262 | dma->pcm_open_flag = 0;
|
---|
263 | }
|
---|
264 | cs5535audio_clear_dma_packets(cs5535au, dma, substream);
|
---|
265 | return 0;
|
---|
266 | }
|
---|
267 |
|
---|
268 | static int snd_cs5535audio_playback_prepare(struct snd_pcm_substream *substream)
|
---|
269 | {
|
---|
270 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
271 | return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE,
|
---|
272 | substream->runtime->rate);
|
---|
273 | }
|
---|
274 |
|
---|
275 | static int snd_cs5535audio_trigger(struct snd_pcm_substream *substream, int cmd)
|
---|
276 | {
|
---|
277 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
278 | struct cs5535audio_dma *dma = substream->runtime->private_data;
|
---|
279 | int err = 0;
|
---|
280 |
|
---|
281 | spin_lock(&cs5535au->reg_lock);
|
---|
282 | switch (cmd) {
|
---|
283 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
---|
284 | dma->ops->pause_dma(cs5535au);
|
---|
285 | break;
|
---|
286 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
---|
287 | dma->ops->enable_dma(cs5535au);
|
---|
288 | break;
|
---|
289 | case SNDRV_PCM_TRIGGER_START:
|
---|
290 | dma->ops->enable_dma(cs5535au);
|
---|
291 | break;
|
---|
292 | case SNDRV_PCM_TRIGGER_RESUME:
|
---|
293 | dma->ops->enable_dma(cs5535au);
|
---|
294 | break;
|
---|
295 | case SNDRV_PCM_TRIGGER_STOP:
|
---|
296 | dma->ops->disable_dma(cs5535au);
|
---|
297 | break;
|
---|
298 | case SNDRV_PCM_TRIGGER_SUSPEND:
|
---|
299 | dma->ops->disable_dma(cs5535au);
|
---|
300 | break;
|
---|
301 | default:
|
---|
302 | dev_err(cs5535au->card->dev, "unhandled trigger\n");
|
---|
303 | err = -EINVAL;
|
---|
304 | break;
|
---|
305 | }
|
---|
306 | spin_unlock(&cs5535au->reg_lock);
|
---|
307 | return err;
|
---|
308 | }
|
---|
309 |
|
---|
310 | static snd_pcm_uframes_t snd_cs5535audio_pcm_pointer(struct snd_pcm_substream
|
---|
311 | *substream)
|
---|
312 | {
|
---|
313 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
314 | u32 curdma;
|
---|
315 | struct cs5535audio_dma *dma;
|
---|
316 |
|
---|
317 | dma = substream->runtime->private_data;
|
---|
318 | curdma = dma->ops->read_dma_pntr(cs5535au);
|
---|
319 | if (curdma < dma->buf_addr) {
|
---|
320 | dev_err(cs5535au->card->dev, "curdma=%x < %x bufaddr.\n",
|
---|
321 | curdma, dma->buf_addr);
|
---|
322 | return 0;
|
---|
323 | }
|
---|
324 | curdma -= dma->buf_addr;
|
---|
325 | if (curdma >= dma->buf_bytes) {
|
---|
326 | dev_err(cs5535au->card->dev, "diff=%x >= %x buf_bytes.\n",
|
---|
327 | curdma, dma->buf_bytes);
|
---|
328 | return 0;
|
---|
329 | }
|
---|
330 | return bytes_to_frames(substream->runtime, curdma);
|
---|
331 | }
|
---|
332 |
|
---|
333 | static int snd_cs5535audio_capture_open(struct snd_pcm_substream *substream)
|
---|
334 | {
|
---|
335 | int err;
|
---|
336 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
337 | struct snd_pcm_runtime *runtime = substream->runtime;
|
---|
338 |
|
---|
339 | runtime->hw = snd_cs5535audio_capture;
|
---|
340 | runtime->hw.rates = cs5535au->ac97->rates[AC97_RATES_ADC];
|
---|
341 | snd_pcm_limit_hw_rates(runtime);
|
---|
342 | cs5535au->capture_substream = substream;
|
---|
343 | runtime->private_data = &(cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE]);
|
---|
344 | err = snd_pcm_hw_constraint_integer(runtime,
|
---|
345 | SNDRV_PCM_HW_PARAM_PERIODS);
|
---|
346 | if (err < 0)
|
---|
347 | return err;
|
---|
348 | olpc_capture_open(cs5535au->ac97);
|
---|
349 | return 0;
|
---|
350 | }
|
---|
351 |
|
---|
352 | static int snd_cs5535audio_capture_close(struct snd_pcm_substream *substream)
|
---|
353 | {
|
---|
354 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
355 | olpc_capture_close(cs5535au->ac97);
|
---|
356 | return 0;
|
---|
357 | }
|
---|
358 |
|
---|
359 | static int snd_cs5535audio_capture_prepare(struct snd_pcm_substream *substream)
|
---|
360 | {
|
---|
361 | struct cs5535audio *cs5535au = snd_pcm_substream_chip(substream);
|
---|
362 | return snd_ac97_set_rate(cs5535au->ac97, AC97_PCM_LR_ADC_RATE,
|
---|
363 | substream->runtime->rate);
|
---|
364 | }
|
---|
365 |
|
---|
366 | static const struct snd_pcm_ops snd_cs5535audio_playback_ops = {
|
---|
367 | .open = snd_cs5535audio_playback_open,
|
---|
368 | .close = snd_cs5535audio_playback_close,
|
---|
369 | .hw_params = snd_cs5535audio_hw_params,
|
---|
370 | .hw_free = snd_cs5535audio_hw_free,
|
---|
371 | .prepare = snd_cs5535audio_playback_prepare,
|
---|
372 | .trigger = snd_cs5535audio_trigger,
|
---|
373 | .pointer = snd_cs5535audio_pcm_pointer,
|
---|
374 | };
|
---|
375 |
|
---|
376 | static const struct snd_pcm_ops snd_cs5535audio_capture_ops = {
|
---|
377 | .open = snd_cs5535audio_capture_open,
|
---|
378 | .close = snd_cs5535audio_capture_close,
|
---|
379 | .hw_params = snd_cs5535audio_hw_params,
|
---|
380 | .hw_free = snd_cs5535audio_hw_free,
|
---|
381 | .prepare = snd_cs5535audio_capture_prepare,
|
---|
382 | .trigger = snd_cs5535audio_trigger,
|
---|
383 | .pointer = snd_cs5535audio_pcm_pointer,
|
---|
384 | };
|
---|
385 |
|
---|
386 | static const struct cs5535audio_dma_ops snd_cs5535audio_playback_dma_ops = {
|
---|
387 | .type = CS5535AUDIO_DMA_PLAYBACK,
|
---|
388 | .enable_dma = cs5535audio_playback_enable_dma,
|
---|
389 | .disable_dma = cs5535audio_playback_disable_dma,
|
---|
390 | .setup_prd = cs5535audio_playback_setup_prd,
|
---|
391 | .read_prd = cs5535audio_playback_read_prd,
|
---|
392 | .pause_dma = cs5535audio_playback_pause_dma,
|
---|
393 | .read_dma_pntr = cs5535audio_playback_read_dma_pntr,
|
---|
394 | };
|
---|
395 |
|
---|
396 | static const struct cs5535audio_dma_ops snd_cs5535audio_capture_dma_ops = {
|
---|
397 | .type = CS5535AUDIO_DMA_CAPTURE,
|
---|
398 | .enable_dma = cs5535audio_capture_enable_dma,
|
---|
399 | .disable_dma = cs5535audio_capture_disable_dma,
|
---|
400 | .setup_prd = cs5535audio_capture_setup_prd,
|
---|
401 | .read_prd = cs5535audio_capture_read_prd,
|
---|
402 | .pause_dma = cs5535audio_capture_pause_dma,
|
---|
403 | .read_dma_pntr = cs5535audio_capture_read_dma_pntr,
|
---|
404 | };
|
---|
405 |
|
---|
406 | int snd_cs5535audio_pcm(struct cs5535audio *cs5535au)
|
---|
407 | {
|
---|
408 | struct snd_pcm *pcm;
|
---|
409 | int err;
|
---|
410 |
|
---|
411 | err = snd_pcm_new(cs5535au->card, "CS5535 Audio", 0, 1, 1, &pcm);
|
---|
412 | if (err < 0)
|
---|
413 | return err;
|
---|
414 |
|
---|
415 | cs5535au->dmas[CS5535AUDIO_DMA_PLAYBACK].ops =
|
---|
416 | &snd_cs5535audio_playback_dma_ops;
|
---|
417 | cs5535au->dmas[CS5535AUDIO_DMA_CAPTURE].ops =
|
---|
418 | &snd_cs5535audio_capture_dma_ops;
|
---|
419 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
|
---|
420 | &snd_cs5535audio_playback_ops);
|
---|
421 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
|
---|
422 | &snd_cs5535audio_capture_ops);
|
---|
423 |
|
---|
424 | pcm->private_data = cs5535au;
|
---|
425 | pcm->info_flags = 0;
|
---|
426 | strcpy(pcm->name, "CS5535 Audio");
|
---|
427 |
|
---|
428 | snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
|
---|
429 | &cs5535au->pci->dev,
|
---|
430 | 64*1024, 128*1024);
|
---|
431 | cs5535au->pcm = pcm;
|
---|
432 |
|
---|
433 | return 0;
|
---|
434 | }
|
---|
435 |
|
---|