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 | dev: &cs5535au->pci->dev, |
122 | CS5535AUDIO_DESC_LIST_SIZE+1, |
123 | dmab: &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(lock: &cs5535au->reg_lock); |
154 | dma->ops->disable_dma(cs5535au); |
155 | dma->ops->setup_prd(cs5535au, jmpprd_addr); |
156 | spin_unlock_irq(lock: &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(dmab: &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(p: hw_params); |
240 | |
241 | err = cs5535audio_build_dma_packets(cs5535au, dma, substream, |
242 | periods: params_periods(p: hw_params), |
243 | period_bytes: params_period_bytes(p: 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(ac97: cs5535au->ac97, |
258 | AC97_PCM_FRONT_DAC_RATE, powerup: 0); |
259 | else |
260 | snd_ac97_update_power(ac97: cs5535au->ac97, |
261 | AC97_PCM_LR_ADC_RATE, powerup: 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(ac97: cs5535au->ac97, AC97_PCM_FRONT_DAC_RATE, |
272 | rate: 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(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(lock: &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(runtime: substream->runtime, size: 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(ac97: 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(ac97: 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(ac97: cs5535au->ac97, AC97_PCM_LR_ADC_RATE, |
363 | rate: 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(card: cs5535au->card, id: "CS5535 Audio" , device: 0, playback_count: 1, capture_count: 1, rpcm: &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, direction: SNDRV_PCM_STREAM_PLAYBACK, |
420 | ops: &snd_cs5535audio_playback_ops); |
421 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, |
422 | ops: &snd_cs5535audio_capture_ops); |
423 | |
424 | pcm->private_data = cs5535au; |
425 | pcm->info_flags = 0; |
426 | strcpy(p: pcm->name, q: "CS5535 Audio" ); |
427 | |
428 | snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV, |
429 | data: &cs5535au->pci->dev, |
430 | size: 64*1024, max: 128*1024); |
431 | cs5535au->pcm = pcm; |
432 | |
433 | return 0; |
434 | } |
435 | |
436 | |