diff options
Diffstat (limited to 'sound/oss/pas2_pcm.c')
-rw-r--r-- | sound/oss/pas2_pcm.c | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/sound/oss/pas2_pcm.c b/sound/oss/pas2_pcm.c new file mode 100644 index 000000000000..4af6aafa3d86 --- /dev/null +++ b/sound/oss/pas2_pcm.c | |||
@@ -0,0 +1,437 @@ | |||
1 | /* | ||
2 | * pas2_pcm.c Audio routines for PAS16 | ||
3 | * | ||
4 | * | ||
5 | * Copyright (C) by Hannu Savolainen 1993-1997 | ||
6 | * | ||
7 | * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) | ||
8 | * Version 2 (June 1991). See the "COPYING" file distributed with this software | ||
9 | * for more info. | ||
10 | * | ||
11 | * | ||
12 | * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed) | ||
13 | * Alan Cox : Swatted a double allocation of device bug. Made a few | ||
14 | * more things module options. | ||
15 | * Bartlomiej Zolnierkiewicz : Added __init to pas_pcm_init() | ||
16 | */ | ||
17 | |||
18 | #include <linux/init.h> | ||
19 | #include <linux/spinlock.h> | ||
20 | #include <asm/timex.h> | ||
21 | #include "sound_config.h" | ||
22 | |||
23 | #include "pas2.h" | ||
24 | |||
25 | #ifndef DEB | ||
26 | #define DEB(WHAT) | ||
27 | #endif | ||
28 | |||
29 | #define PAS_PCM_INTRBITS (0x08) | ||
30 | /* | ||
31 | * Sample buffer timer interrupt enable | ||
32 | */ | ||
33 | |||
34 | #define PCM_NON 0 | ||
35 | #define PCM_DAC 1 | ||
36 | #define PCM_ADC 2 | ||
37 | |||
38 | static unsigned long pcm_speed; /* sampling rate */ | ||
39 | static unsigned char pcm_channels = 1; /* channels (1 or 2) */ | ||
40 | static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */ | ||
41 | static unsigned char pcm_filter; /* filter FLAG */ | ||
42 | static unsigned char pcm_mode = PCM_NON; | ||
43 | static unsigned long pcm_count; | ||
44 | static unsigned short pcm_bitsok = 8; /* mask of OK bits */ | ||
45 | static int pcm_busy; | ||
46 | int pas_audiodev = -1; | ||
47 | static int open_mode; | ||
48 | |||
49 | extern spinlock_t pas_lock; | ||
50 | |||
51 | static int pcm_set_speed(int arg) | ||
52 | { | ||
53 | int foo, tmp; | ||
54 | unsigned long flags; | ||
55 | |||
56 | if (arg == 0) | ||
57 | return pcm_speed; | ||
58 | |||
59 | if (arg > 44100) | ||
60 | arg = 44100; | ||
61 | if (arg < 5000) | ||
62 | arg = 5000; | ||
63 | |||
64 | if (pcm_channels & 2) | ||
65 | { | ||
66 | foo = ((CLOCK_TICK_RATE / 2) + (arg / 2)) / arg; | ||
67 | arg = ((CLOCK_TICK_RATE / 2) + (foo / 2)) / foo; | ||
68 | } | ||
69 | else | ||
70 | { | ||
71 | foo = (CLOCK_TICK_RATE + (arg / 2)) / arg; | ||
72 | arg = (CLOCK_TICK_RATE + (foo / 2)) / foo; | ||
73 | } | ||
74 | |||
75 | pcm_speed = arg; | ||
76 | |||
77 | tmp = pas_read(0x0B8A); | ||
78 | |||
79 | /* | ||
80 | * Set anti-aliasing filters according to sample rate. You really *NEED* | ||
81 | * to enable this feature for all normal recording unless you want to | ||
82 | * experiment with aliasing effects. | ||
83 | * These filters apply to the selected "recording" source. | ||
84 | * I (pfw) don't know the encoding of these 5 bits. The values shown | ||
85 | * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/. | ||
86 | * | ||
87 | * I cleared bit 5 of these values, since that bit controls the master | ||
88 | * mute flag. (Olav Wölfelschneider) | ||
89 | * | ||
90 | */ | ||
91 | #if !defined NO_AUTO_FILTER_SET | ||
92 | tmp &= 0xe0; | ||
93 | if (pcm_speed >= 2 * 17897) | ||
94 | tmp |= 0x01; | ||
95 | else if (pcm_speed >= 2 * 15909) | ||
96 | tmp |= 0x02; | ||
97 | else if (pcm_speed >= 2 * 11931) | ||
98 | tmp |= 0x09; | ||
99 | else if (pcm_speed >= 2 * 8948) | ||
100 | tmp |= 0x11; | ||
101 | else if (pcm_speed >= 2 * 5965) | ||
102 | tmp |= 0x19; | ||
103 | else if (pcm_speed >= 2 * 2982) | ||
104 | tmp |= 0x04; | ||
105 | pcm_filter = tmp; | ||
106 | #endif | ||
107 | |||
108 | spin_lock_irqsave(&pas_lock, flags); | ||
109 | |||
110 | pas_write(tmp & ~(0x40 | 0x80), 0x0B8A); | ||
111 | pas_write(0x00 | 0x30 | 0x04, 0x138B); | ||
112 | pas_write(foo & 0xff, 0x1388); | ||
113 | pas_write((foo >> 8) & 0xff, 0x1388); | ||
114 | pas_write(tmp, 0x0B8A); | ||
115 | |||
116 | spin_unlock_irqrestore(&pas_lock, flags); | ||
117 | |||
118 | return pcm_speed; | ||
119 | } | ||
120 | |||
121 | static int pcm_set_channels(int arg) | ||
122 | { | ||
123 | |||
124 | if ((arg != 1) && (arg != 2)) | ||
125 | return pcm_channels; | ||
126 | |||
127 | if (arg != pcm_channels) | ||
128 | { | ||
129 | pas_write(pas_read(0xF8A) ^ 0x20, 0xF8A); | ||
130 | |||
131 | pcm_channels = arg; | ||
132 | pcm_set_speed(pcm_speed); /* The speed must be reinitialized */ | ||
133 | } | ||
134 | return pcm_channels; | ||
135 | } | ||
136 | |||
137 | static int pcm_set_bits(int arg) | ||
138 | { | ||
139 | if (arg == 0) | ||
140 | return pcm_bits; | ||
141 | |||
142 | if ((arg & pcm_bitsok) != arg) | ||
143 | return pcm_bits; | ||
144 | |||
145 | if (arg != pcm_bits) | ||
146 | { | ||
147 | pas_write(pas_read(0x8389) ^ 0x04, 0x8389); | ||
148 | |||
149 | pcm_bits = arg; | ||
150 | } | ||
151 | return pcm_bits; | ||
152 | } | ||
153 | |||
154 | static int pas_audio_ioctl(int dev, unsigned int cmd, void __user *arg) | ||
155 | { | ||
156 | int val, ret; | ||
157 | int __user *p = arg; | ||
158 | |||
159 | DEB(printk("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); | ||
160 | |||
161 | switch (cmd) | ||
162 | { | ||
163 | case SOUND_PCM_WRITE_RATE: | ||
164 | if (get_user(val, p)) | ||
165 | return -EFAULT; | ||
166 | ret = pcm_set_speed(val); | ||
167 | break; | ||
168 | |||
169 | case SOUND_PCM_READ_RATE: | ||
170 | ret = pcm_speed; | ||
171 | break; | ||
172 | |||
173 | case SNDCTL_DSP_STEREO: | ||
174 | if (get_user(val, p)) | ||
175 | return -EFAULT; | ||
176 | ret = pcm_set_channels(val + 1) - 1; | ||
177 | break; | ||
178 | |||
179 | case SOUND_PCM_WRITE_CHANNELS: | ||
180 | if (get_user(val, p)) | ||
181 | return -EFAULT; | ||
182 | ret = pcm_set_channels(val); | ||
183 | break; | ||
184 | |||
185 | case SOUND_PCM_READ_CHANNELS: | ||
186 | ret = pcm_channels; | ||
187 | break; | ||
188 | |||
189 | case SNDCTL_DSP_SETFMT: | ||
190 | if (get_user(val, p)) | ||
191 | return -EFAULT; | ||
192 | ret = pcm_set_bits(val); | ||
193 | break; | ||
194 | |||
195 | case SOUND_PCM_READ_BITS: | ||
196 | ret = pcm_bits; | ||
197 | break; | ||
198 | |||
199 | default: | ||
200 | return -EINVAL; | ||
201 | } | ||
202 | return put_user(ret, p); | ||
203 | } | ||
204 | |||
205 | static void pas_audio_reset(int dev) | ||
206 | { | ||
207 | DEB(printk("pas2_pcm.c: static void pas_audio_reset(void)\n")); | ||
208 | |||
209 | pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); /* Disable PCM */ | ||
210 | } | ||
211 | |||
212 | static int pas_audio_open(int dev, int mode) | ||
213 | { | ||
214 | int err; | ||
215 | unsigned long flags; | ||
216 | |||
217 | DEB(printk("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode)); | ||
218 | |||
219 | spin_lock_irqsave(&pas_lock, flags); | ||
220 | if (pcm_busy) | ||
221 | { | ||
222 | spin_unlock_irqrestore(&pas_lock, flags); | ||
223 | return -EBUSY; | ||
224 | } | ||
225 | pcm_busy = 1; | ||
226 | spin_unlock_irqrestore(&pas_lock, flags); | ||
227 | |||
228 | if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0) | ||
229 | return err; | ||
230 | |||
231 | |||
232 | pcm_count = 0; | ||
233 | open_mode = mode; | ||
234 | |||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | static void pas_audio_close(int dev) | ||
239 | { | ||
240 | unsigned long flags; | ||
241 | |||
242 | DEB(printk("pas2_pcm.c: static void pas_audio_close(void)\n")); | ||
243 | |||
244 | spin_lock_irqsave(&pas_lock, flags); | ||
245 | |||
246 | pas_audio_reset(dev); | ||
247 | pas_remove_intr(PAS_PCM_INTRBITS); | ||
248 | pcm_mode = PCM_NON; | ||
249 | |||
250 | pcm_busy = 0; | ||
251 | spin_unlock_irqrestore(&pas_lock, flags); | ||
252 | } | ||
253 | |||
254 | static void pas_audio_output_block(int dev, unsigned long buf, int count, | ||
255 | int intrflag) | ||
256 | { | ||
257 | unsigned long flags, cnt; | ||
258 | |||
259 | DEB(printk("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count)); | ||
260 | |||
261 | cnt = count; | ||
262 | if (audio_devs[dev]->dmap_out->dma > 3) | ||
263 | cnt >>= 1; | ||
264 | |||
265 | if (audio_devs[dev]->flags & DMA_AUTOMODE && | ||
266 | intrflag && | ||
267 | cnt == pcm_count) | ||
268 | return; | ||
269 | |||
270 | spin_lock_irqsave(&pas_lock, flags); | ||
271 | |||
272 | pas_write(pas_read(0xF8A) & ~0x40, | ||
273 | 0xF8A); | ||
274 | |||
275 | /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */ | ||
276 | |||
277 | if (audio_devs[dev]->dmap_out->dma > 3) | ||
278 | count >>= 1; | ||
279 | |||
280 | if (count != pcm_count) | ||
281 | { | ||
282 | pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A); | ||
283 | pas_write(0x40 | 0x30 | 0x04, 0x138B); | ||
284 | pas_write(count & 0xff, 0x1389); | ||
285 | pas_write((count >> 8) & 0xff, 0x1389); | ||
286 | pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A); | ||
287 | |||
288 | pcm_count = count; | ||
289 | } | ||
290 | pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A); | ||
291 | #ifdef NO_TRIGGER | ||
292 | pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A); | ||
293 | #endif | ||
294 | |||
295 | pcm_mode = PCM_DAC; | ||
296 | |||
297 | spin_unlock_irqrestore(&pas_lock, flags); | ||
298 | } | ||
299 | |||
300 | static void pas_audio_start_input(int dev, unsigned long buf, int count, | ||
301 | int intrflag) | ||
302 | { | ||
303 | unsigned long flags; | ||
304 | int cnt; | ||
305 | |||
306 | DEB(printk("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count)); | ||
307 | |||
308 | cnt = count; | ||
309 | if (audio_devs[dev]->dmap_out->dma > 3) | ||
310 | cnt >>= 1; | ||
311 | |||
312 | if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE && | ||
313 | intrflag && | ||
314 | cnt == pcm_count) | ||
315 | return; | ||
316 | |||
317 | spin_lock_irqsave(&pas_lock, flags); | ||
318 | |||
319 | /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */ | ||
320 | |||
321 | if (audio_devs[dev]->dmap_out->dma > 3) | ||
322 | count >>= 1; | ||
323 | |||
324 | if (count != pcm_count) | ||
325 | { | ||
326 | pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A); | ||
327 | pas_write(0x40 | 0x30 | 0x04, 0x138B); | ||
328 | pas_write(count & 0xff, 0x1389); | ||
329 | pas_write((count >> 8) & 0xff, 0x1389); | ||
330 | pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A); | ||
331 | |||
332 | pcm_count = count; | ||
333 | } | ||
334 | pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A); | ||
335 | #ifdef NO_TRIGGER | ||
336 | pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A); | ||
337 | #endif | ||
338 | |||
339 | pcm_mode = PCM_ADC; | ||
340 | |||
341 | spin_unlock_irqrestore(&pas_lock, flags); | ||
342 | } | ||
343 | |||
344 | #ifndef NO_TRIGGER | ||
345 | static void pas_audio_trigger(int dev, int state) | ||
346 | { | ||
347 | unsigned long flags; | ||
348 | |||
349 | spin_lock_irqsave(&pas_lock, flags); | ||
350 | state &= open_mode; | ||
351 | |||
352 | if (state & PCM_ENABLE_OUTPUT) | ||
353 | pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A); | ||
354 | else if (state & PCM_ENABLE_INPUT) | ||
355 | pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A); | ||
356 | else | ||
357 | pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); | ||
358 | |||
359 | spin_unlock_irqrestore(&pas_lock, flags); | ||
360 | } | ||
361 | #endif | ||
362 | |||
363 | static int pas_audio_prepare_for_input(int dev, int bsize, int bcount) | ||
364 | { | ||
365 | pas_audio_reset(dev); | ||
366 | return 0; | ||
367 | } | ||
368 | |||
369 | static int pas_audio_prepare_for_output(int dev, int bsize, int bcount) | ||
370 | { | ||
371 | pas_audio_reset(dev); | ||
372 | return 0; | ||
373 | } | ||
374 | |||
375 | static struct audio_driver pas_audio_driver = | ||
376 | { | ||
377 | .owner = THIS_MODULE, | ||
378 | .open = pas_audio_open, | ||
379 | .close = pas_audio_close, | ||
380 | .output_block = pas_audio_output_block, | ||
381 | .start_input = pas_audio_start_input, | ||
382 | .ioctl = pas_audio_ioctl, | ||
383 | .prepare_for_input = pas_audio_prepare_for_input, | ||
384 | .prepare_for_output = pas_audio_prepare_for_output, | ||
385 | .halt_io = pas_audio_reset, | ||
386 | .trigger = pas_audio_trigger | ||
387 | }; | ||
388 | |||
389 | void __init pas_pcm_init(struct address_info *hw_config) | ||
390 | { | ||
391 | DEB(printk("pas2_pcm.c: long pas_pcm_init()\n")); | ||
392 | |||
393 | pcm_bitsok = 8; | ||
394 | if (pas_read(0xEF8B) & 0x08) | ||
395 | pcm_bitsok |= 16; | ||
396 | |||
397 | pcm_set_speed(DSP_DEFAULT_SPEED); | ||
398 | |||
399 | if ((pas_audiodev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, | ||
400 | "Pro Audio Spectrum", | ||
401 | &pas_audio_driver, | ||
402 | sizeof(struct audio_driver), | ||
403 | DMA_AUTOMODE, | ||
404 | AFMT_U8 | AFMT_S16_LE, | ||
405 | NULL, | ||
406 | hw_config->dma, | ||
407 | hw_config->dma)) < 0) | ||
408 | printk(KERN_WARNING "PAS16: Too many PCM devices available\n"); | ||
409 | } | ||
410 | |||
411 | void pas_pcm_interrupt(unsigned char status, int cause) | ||
412 | { | ||
413 | if (cause == 1) | ||
414 | { | ||
415 | /* | ||
416 | * Halt the PCM first. Otherwise we don't have time to start a new | ||
417 | * block before the PCM chip proceeds to the next sample | ||
418 | */ | ||
419 | |||
420 | if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE)) | ||
421 | pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); | ||
422 | |||
423 | switch (pcm_mode) | ||
424 | { | ||
425 | case PCM_DAC: | ||
426 | DMAbuf_outputintr(pas_audiodev, 1); | ||
427 | break; | ||
428 | |||
429 | case PCM_ADC: | ||
430 | DMAbuf_inputintr(pas_audiodev); | ||
431 | break; | ||
432 | |||
433 | default: | ||
434 | printk(KERN_WARNING "PAS: Unexpected PCM interrupt\n"); | ||
435 | } | ||
436 | } | ||
437 | } | ||