| /* |
| * pas2_pcm.c Audio routines for PAS16 |
| * |
| * |
| * Copyright (C) by Hannu Savolainen 1993-1997 |
| * |
| * OSS/Free for Linux is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) |
| * Version 2 (June 1991). See the "COPYING" file distributed with this software |
| * for more info. |
| * |
| * |
| * Thomas Sailer : ioctl code reworked (vmalloc/vfree removed) |
| * Alan Cox : Swatted a double allocation of device bug. Made a few |
| * more things module options. |
| * Bartlomiej Zolnierkiewicz : Added __init to pas_pcm_init() |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/spinlock.h> |
| #include <linux/timex.h> |
| #include "sound_config.h" |
| |
| #include "pas2.h" |
| |
| #ifndef DEB |
| #define DEB(WHAT) |
| #endif |
| |
| #define PAS_PCM_INTRBITS (0x08) |
| /* |
| * Sample buffer timer interrupt enable |
| */ |
| |
| #define PCM_NON 0 |
| #define PCM_DAC 1 |
| #define PCM_ADC 2 |
| |
| static unsigned long pcm_speed; /* sampling rate */ |
| static unsigned char pcm_channels = 1; /* channels (1 or 2) */ |
| static unsigned char pcm_bits = 8; /* bits/sample (8 or 16) */ |
| static unsigned char pcm_filter; /* filter FLAG */ |
| static unsigned char pcm_mode = PCM_NON; |
| static unsigned long pcm_count; |
| static unsigned short pcm_bitsok = 8; /* mask of OK bits */ |
| static int pcm_busy; |
| int pas_audiodev = -1; |
| static int open_mode; |
| |
| extern spinlock_t pas_lock; |
| |
| static int pcm_set_speed(int arg) |
| { |
| int foo, tmp; |
| unsigned long flags; |
| |
| if (arg == 0) |
| return pcm_speed; |
| |
| if (arg > 44100) |
| arg = 44100; |
| if (arg < 5000) |
| arg = 5000; |
| |
| if (pcm_channels & 2) |
| { |
| foo = ((CLOCK_TICK_RATE / 2) + (arg / 2)) / arg; |
| arg = ((CLOCK_TICK_RATE / 2) + (foo / 2)) / foo; |
| } |
| else |
| { |
| foo = (CLOCK_TICK_RATE + (arg / 2)) / arg; |
| arg = (CLOCK_TICK_RATE + (foo / 2)) / foo; |
| } |
| |
| pcm_speed = arg; |
| |
| tmp = pas_read(0x0B8A); |
| |
| /* |
| * Set anti-aliasing filters according to sample rate. You really *NEED* |
| * to enable this feature for all normal recording unless you want to |
| * experiment with aliasing effects. |
| * These filters apply to the selected "recording" source. |
| * I (pfw) don't know the encoding of these 5 bits. The values shown |
| * come from the SDK found on ftp.uwp.edu:/pub/msdos/proaudio/. |
| * |
| * I cleared bit 5 of these values, since that bit controls the master |
| * mute flag. (Olav Wölfelschneider) |
| * |
| */ |
| #if !defined NO_AUTO_FILTER_SET |
| tmp &= 0xe0; |
| if (pcm_speed >= 2 * 17897) |
| tmp |= 0x01; |
| else if (pcm_speed >= 2 * 15909) |
| tmp |= 0x02; |
| else if (pcm_speed >= 2 * 11931) |
| tmp |= 0x09; |
| else if (pcm_speed >= 2 * 8948) |
| tmp |= 0x11; |
| else if (pcm_speed >= 2 * 5965) |
| tmp |= 0x19; |
| else if (pcm_speed >= 2 * 2982) |
| tmp |= 0x04; |
| pcm_filter = tmp; |
| #endif |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| |
| pas_write(tmp & ~(0x40 | 0x80), 0x0B8A); |
| pas_write(0x00 | 0x30 | 0x04, 0x138B); |
| pas_write(foo & 0xff, 0x1388); |
| pas_write((foo >> 8) & 0xff, 0x1388); |
| pas_write(tmp, 0x0B8A); |
| |
| spin_unlock_irqrestore(&pas_lock, flags); |
| |
| return pcm_speed; |
| } |
| |
| static int pcm_set_channels(int arg) |
| { |
| |
| if ((arg != 1) && (arg != 2)) |
| return pcm_channels; |
| |
| if (arg != pcm_channels) |
| { |
| pas_write(pas_read(0xF8A) ^ 0x20, 0xF8A); |
| |
| pcm_channels = arg; |
| pcm_set_speed(pcm_speed); /* The speed must be reinitialized */ |
| } |
| return pcm_channels; |
| } |
| |
| static int pcm_set_bits(int arg) |
| { |
| if (arg == 0) |
| return pcm_bits; |
| |
| if ((arg & pcm_bitsok) != arg) |
| return pcm_bits; |
| |
| if (arg != pcm_bits) |
| { |
| pas_write(pas_read(0x8389) ^ 0x04, 0x8389); |
| |
| pcm_bits = arg; |
| } |
| return pcm_bits; |
| } |
| |
| static int pas_audio_ioctl(int dev, unsigned int cmd, void __user *arg) |
| { |
| int val, ret; |
| int __user *p = arg; |
| |
| DEB(printk("pas2_pcm.c: static int pas_audio_ioctl(unsigned int cmd = %X, unsigned int arg = %X)\n", cmd, arg)); |
| |
| switch (cmd) |
| { |
| case SOUND_PCM_WRITE_RATE: |
| if (get_user(val, p)) |
| return -EFAULT; |
| ret = pcm_set_speed(val); |
| break; |
| |
| case SOUND_PCM_READ_RATE: |
| ret = pcm_speed; |
| break; |
| |
| case SNDCTL_DSP_STEREO: |
| if (get_user(val, p)) |
| return -EFAULT; |
| ret = pcm_set_channels(val + 1) - 1; |
| break; |
| |
| case SOUND_PCM_WRITE_CHANNELS: |
| if (get_user(val, p)) |
| return -EFAULT; |
| ret = pcm_set_channels(val); |
| break; |
| |
| case SOUND_PCM_READ_CHANNELS: |
| ret = pcm_channels; |
| break; |
| |
| case SNDCTL_DSP_SETFMT: |
| if (get_user(val, p)) |
| return -EFAULT; |
| ret = pcm_set_bits(val); |
| break; |
| |
| case SOUND_PCM_READ_BITS: |
| ret = pcm_bits; |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| return put_user(ret, p); |
| } |
| |
| static void pas_audio_reset(int dev) |
| { |
| DEB(printk("pas2_pcm.c: static void pas_audio_reset(void)\n")); |
| |
| pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); /* Disable PCM */ |
| } |
| |
| static int pas_audio_open(int dev, int mode) |
| { |
| int err; |
| unsigned long flags; |
| |
| DEB(printk("pas2_pcm.c: static int pas_audio_open(int mode = %X)\n", mode)); |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| if (pcm_busy) |
| { |
| spin_unlock_irqrestore(&pas_lock, flags); |
| return -EBUSY; |
| } |
| pcm_busy = 1; |
| spin_unlock_irqrestore(&pas_lock, flags); |
| |
| if ((err = pas_set_intr(PAS_PCM_INTRBITS)) < 0) |
| return err; |
| |
| |
| pcm_count = 0; |
| open_mode = mode; |
| |
| return 0; |
| } |
| |
| static void pas_audio_close(int dev) |
| { |
| unsigned long flags; |
| |
| DEB(printk("pas2_pcm.c: static void pas_audio_close(void)\n")); |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| |
| pas_audio_reset(dev); |
| pas_remove_intr(PAS_PCM_INTRBITS); |
| pcm_mode = PCM_NON; |
| |
| pcm_busy = 0; |
| spin_unlock_irqrestore(&pas_lock, flags); |
| } |
| |
| static void pas_audio_output_block(int dev, unsigned long buf, int count, |
| int intrflag) |
| { |
| unsigned long flags, cnt; |
| |
| DEB(printk("pas2_pcm.c: static void pas_audio_output_block(char *buf = %P, int count = %X)\n", buf, count)); |
| |
| cnt = count; |
| if (audio_devs[dev]->dmap_out->dma > 3) |
| cnt >>= 1; |
| |
| if (audio_devs[dev]->flags & DMA_AUTOMODE && |
| intrflag && |
| cnt == pcm_count) |
| return; |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| |
| pas_write(pas_read(0xF8A) & ~0x40, |
| 0xF8A); |
| |
| /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_WRITE); */ |
| |
| if (audio_devs[dev]->dmap_out->dma > 3) |
| count >>= 1; |
| |
| if (count != pcm_count) |
| { |
| pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A); |
| pas_write(0x40 | 0x30 | 0x04, 0x138B); |
| pas_write(count & 0xff, 0x1389); |
| pas_write((count >> 8) & 0xff, 0x1389); |
| pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A); |
| |
| pcm_count = count; |
| } |
| pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A); |
| #ifdef NO_TRIGGER |
| pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A); |
| #endif |
| |
| pcm_mode = PCM_DAC; |
| |
| spin_unlock_irqrestore(&pas_lock, flags); |
| } |
| |
| static void pas_audio_start_input(int dev, unsigned long buf, int count, |
| int intrflag) |
| { |
| unsigned long flags; |
| int cnt; |
| |
| DEB(printk("pas2_pcm.c: static void pas_audio_start_input(char *buf = %P, int count = %X)\n", buf, count)); |
| |
| cnt = count; |
| if (audio_devs[dev]->dmap_out->dma > 3) |
| cnt >>= 1; |
| |
| if (audio_devs[pas_audiodev]->flags & DMA_AUTOMODE && |
| intrflag && |
| cnt == pcm_count) |
| return; |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| |
| /* DMAbuf_start_dma (dev, buf, count, DMA_MODE_READ); */ |
| |
| if (audio_devs[dev]->dmap_out->dma > 3) |
| count >>= 1; |
| |
| if (count != pcm_count) |
| { |
| pas_write(pas_read(0x0B8A) & ~0x80, 0x0B8A); |
| pas_write(0x40 | 0x30 | 0x04, 0x138B); |
| pas_write(count & 0xff, 0x1389); |
| pas_write((count >> 8) & 0xff, 0x1389); |
| pas_write(pas_read(0x0B8A) | 0x80, 0x0B8A); |
| |
| pcm_count = count; |
| } |
| pas_write(pas_read(0x0B8A) | 0x80 | 0x40, 0x0B8A); |
| #ifdef NO_TRIGGER |
| pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A); |
| #endif |
| |
| pcm_mode = PCM_ADC; |
| |
| spin_unlock_irqrestore(&pas_lock, flags); |
| } |
| |
| #ifndef NO_TRIGGER |
| static void pas_audio_trigger(int dev, int state) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&pas_lock, flags); |
| state &= open_mode; |
| |
| if (state & PCM_ENABLE_OUTPUT) |
| pas_write(pas_read(0xF8A) | 0x40 | 0x10, 0xF8A); |
| else if (state & PCM_ENABLE_INPUT) |
| pas_write((pas_read(0xF8A) | 0x40) & ~0x10, 0xF8A); |
| else |
| pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); |
| |
| spin_unlock_irqrestore(&pas_lock, flags); |
| } |
| #endif |
| |
| static int pas_audio_prepare_for_input(int dev, int bsize, int bcount) |
| { |
| pas_audio_reset(dev); |
| return 0; |
| } |
| |
| static int pas_audio_prepare_for_output(int dev, int bsize, int bcount) |
| { |
| pas_audio_reset(dev); |
| return 0; |
| } |
| |
| static struct audio_driver pas_audio_driver = |
| { |
| .owner = THIS_MODULE, |
| .open = pas_audio_open, |
| .close = pas_audio_close, |
| .output_block = pas_audio_output_block, |
| .start_input = pas_audio_start_input, |
| .ioctl = pas_audio_ioctl, |
| .prepare_for_input = pas_audio_prepare_for_input, |
| .prepare_for_output = pas_audio_prepare_for_output, |
| .halt_io = pas_audio_reset, |
| .trigger = pas_audio_trigger |
| }; |
| |
| void __init pas_pcm_init(struct address_info *hw_config) |
| { |
| DEB(printk("pas2_pcm.c: long pas_pcm_init()\n")); |
| |
| pcm_bitsok = 8; |
| if (pas_read(0xEF8B) & 0x08) |
| pcm_bitsok |= 16; |
| |
| pcm_set_speed(DSP_DEFAULT_SPEED); |
| |
| if ((pas_audiodev = sound_install_audiodrv(AUDIO_DRIVER_VERSION, |
| "Pro Audio Spectrum", |
| &pas_audio_driver, |
| sizeof(struct audio_driver), |
| DMA_AUTOMODE, |
| AFMT_U8 | AFMT_S16_LE, |
| NULL, |
| hw_config->dma, |
| hw_config->dma)) < 0) |
| printk(KERN_WARNING "PAS16: Too many PCM devices available\n"); |
| } |
| |
| void pas_pcm_interrupt(unsigned char status, int cause) |
| { |
| if (cause == 1) |
| { |
| /* |
| * Halt the PCM first. Otherwise we don't have time to start a new |
| * block before the PCM chip proceeds to the next sample |
| */ |
| |
| if (!(audio_devs[pas_audiodev]->flags & DMA_AUTOMODE)) |
| pas_write(pas_read(0xF8A) & ~0x40, 0xF8A); |
| |
| switch (pcm_mode) |
| { |
| case PCM_DAC: |
| DMAbuf_outputintr(pas_audiodev, 1); |
| break; |
| |
| case PCM_ADC: |
| DMAbuf_inputintr(pas_audiodev); |
| break; |
| |
| default: |
| printk(KERN_WARNING "PAS: Unexpected PCM interrupt\n"); |
| } |
| } |
| } |