Open Sound System |
Do you have problems with sound/audio application development? Don't panic! Click here for help! |
This file is part of Open Sound System.
Copyright (C) 4Front Technologies 1996-2008.
This this source file is released under GPL v2 license (no other versions). See the COPYING file included in the main directory of this source distribution for the license terms and conditions.
#include "oss_madi_cfg.h" #include "oss_pci.h" #include "madi.h" #define RME_VENDOR_ID 0x10ee /* Xilinx actually */ #define RME_MADI 0x3fc6 #define HWINFO_SIZE 128 extern int madi_devsize; extern int madi_maxchannels; #define LSWAP(x) x static void set_output_enable (madi_devc_t * devc, unsigned int chn, int enabled) { madi_write (devc, MADI_outputEnableStart + 4 * chn, enabled); } static void set_input_enable (madi_devc_t * devc, unsigned int chn, int enabled) { madi_write (devc, MADI_inputEnableStart + 4 * chn, enabled); } static void start_audio (madi_devc_t * devc) { madi_control (devc, devc->cmd | (MADI_AudioInterruptEnable | MADI_Start)); } static void stop_audio (madi_devc_t * devc) { madi_control (devc, devc->cmd & ~(MADI_AudioInterruptEnable | MADI_Start)); } static void audio_interrupt (madi_devc_t * devc, unsigned int status) { int i; for (i = 0; i < devc->num_outputs; i++) { madi_portc_t *portc = devc->out_portc[i]; if (!(portc->trigger_bits & PCM_ENABLE_OUTPUT)) continue; oss_audio_outputintr (portc->audio_dev, 1); } for (i = 0; i < devc->num_inputs; i++) { madi_portc_t *portc = devc->in_portc[i]; if (!(portc->trigger_bits & PCM_ENABLE_INPUT)) continue; oss_audio_inputintr (portc->audio_dev, 1); } } static int madiintr (oss_device_t * osdev) { madi_devc_t *devc = osdev->devc; unsigned int status; status = madi_read (devc, MADI_status); if (status & MADI_audioIntPending) audio_interrupt (devc, status); madi_write (devc, MADI_interruptAck, 0); return !!(status & (MADI_audioIntPending | MADI_midi0IRQStatus | MADI_midi1IRQStatus)) != 0; } static void madi_change_hw_rate (madi_devc_t * devc) { unsigned int rate_bits = 0; unsigned long long v; devc->rate = devc->next_rate;
Compute and set the sample rate in control2 register.
rate_bits = MADI_Freq1; // TODO devc->cmd &= ~MADI_FreqMask; devc->cmd |= rate_bits; madi_control (devc, devc->cmd);
Compute and set the DDS value
v = 110100480000000ULL / (unsigned long long) devc->rate; madi_write (devc, MADI_freq, (unsigned int) v); } static void initialize_hardware (madi_devc_t * devc) { int chn, src;
Intialize the control register
devc->cmd = MADI_ClockModeMaster | MADI_LatencyMask | MADI_LineOut; if (devc->model == MDL_AES32) devc->cmd |= MADI_SyncSrc0 | MADI_ProBit; else devc->cmd |= MADI_InputCoax | MADI_SyncRef_MADI | MADI_TX_64ch_mode | MADI_AutoInput; madi_control (devc, devc->cmd);
Set all input and output engines to stoped
for (chn = 0; chn < MAX_CHANNELS; chn++) { set_output_enable (devc, chn, 0); set_input_enable (devc, chn, 0); }
Init control2 register
if (devc->model == MDL_MADI) #ifdef OSS_BIG_ENDIAN madi_control2 (devc, MADI_BIGENDIAN_MODE); #else madi_control2 (devc, 0x0); #endif madi_change_hw_rate (devc);
Write all HW mixer registers
for (chn = 0; chn < MAX_CHANNELS; chn++) for (src = 0; src < 2 * MAX_CHANNELS; src++) madi_write_gain (devc, chn, src, devc->mixer_values[chn][src]); } static int madi_set_rate (int dev, int arg) { madi_devc_t *devc = audio_engines[dev]->devc; return devc->rate; } static __inline__ void reserve_rec_channels(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) devc->busy_rec_channels |= (1ULL << ch); } static __inline__ void free_rec_channels(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) devc->busy_rec_channels &= ~(1ULL << ch); } static __inline__ int rec_channels_avail(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) if (devc->busy_rec_channels & (1ULL << ch)) return 0; return 1; } static __inline__ void reserve_play_channels(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) devc->busy_play_channels |= (1ULL << ch); } static __inline__ void free_play_channels(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) devc->busy_play_channels &= ~(1ULL << ch); } static __inline__ int play_channels_avail(madi_devc_t *devc, int c, int nc) { int ch; for (ch=c;ch < c+nc;ch++) if (devc->busy_play_channels & (1ULL << ch)) { return 0; } return 1; } static short madi_set_channels (int dev, short arg) { madi_portc_t *portc = audio_engines[dev]->portc; madi_devc_t *devc = audio_engines[dev]->devc; oss_native_word flags; int ok=1; if (arg < 1) return portc->channels; if (arg != 1 && arg != 2 && arg != 4 && arg != 8 && arg != 16 && arg != 32 && arg != 64) return portc->channels; if (arg > portc->max_channels) return portc->channels; if (arg == portc->channels) return portc->channels; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (arg > portc->channels) /* Needs more channels */ { if (portc->direction == DIR_OUT) { if (!play_channels_avail(devc, portc->channel+portc->channels, arg - portc->channels)) ok=0; } else { if (!rec_channels_avail(devc, portc->channel+portc->channels, arg - portc->channels)) ok=0; } } else { if (portc->direction == DIR_OUT) { free_play_channels(devc, portc->channel+arg, portc->channels-arg); } else { free_rec_channels(devc, portc->channel+arg, portc->channels-arg); } } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); if (!ok) return portc->channels; return portc->channels = arg; } /*ARGSUSED*/ static unsigned int madi_set_format (int dev, unsigned int arg) { return AFMT_S32_LE; } static int madi_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { return OSS_EINVAL; } static void madi_trigger (int dev, int state) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; int ch; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode & OPEN_WRITE) { if ((state & PCM_ENABLE_OUTPUT) && !(portc->trigger_bits & PCM_ENABLE_OUTPUT)) { for (ch = 0; ch < portc->channels; ch++) set_output_enable (devc, portc->channel + ch, 1); portc->trigger_bits |= PCM_ENABLE_OUTPUT; devc->active_outputs |= (1ULL << portc->channel); } else if (portc->trigger_bits & PCM_ENABLE_OUTPUT) { for (ch = 0; ch < portc->channels; ch++) set_output_enable (devc, portc->channel + ch, 0); portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; devc->active_outputs &= ~(1ULL << portc->channel); } } if ((portc->open_mode & OPEN_READ) && !(portc->trigger_bits & PCM_ENABLE_INPUT)) { if (state & PCM_ENABLE_INPUT) { for (ch = 0; ch < portc->channels; ch++) set_input_enable (devc, portc->channel + ch, 1); portc->trigger_bits |= PCM_ENABLE_INPUT; devc->active_inputs |= (1ULL << portc->channel); } else if (portc->trigger_bits & PCM_ENABLE_INPUT) { for (ch = 0; ch < portc->channels; ch++) set_input_enable (devc, portc->channel + ch, 0); portc->trigger_bits &= ~PCM_ENABLE_INPUT; devc->active_inputs &= ~(1ULL << portc->channel); } } if (devc->active_inputs || devc->active_outputs) start_audio (devc); else stop_audio (devc); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } static int madi_sync_control (int dev, int event, int mode) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; oss_native_word flags; int ch; if (event == SYNC_PREPARE) { MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if ((mode & PCM_ENABLE_OUTPUT) && portc->direction == DIR_OUT) { for (ch = 0; ch < portc->channels; ch++) set_output_enable (devc, portc->channel + ch, 1); portc->trigger_bits |= PCM_ENABLE_OUTPUT; devc->active_outputs |= (1ULL << portc->channel); } if ((mode & PCM_ENABLE_INPUT) && portc->direction == DIR_IN) { for (ch = 0; ch < portc->channels; ch++) set_input_enable (devc, portc->channel + ch, 1); portc->trigger_bits |= PCM_ENABLE_INPUT; devc->active_inputs |= (1ULL << portc->channel); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } if (event == SYNC_TRIGGER) { MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (devc->active_inputs || devc->active_outputs) start_audio (devc); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } return OSS_EIO; } static void madi_halt (int dev) { madi_trigger (dev, 0); } /*ARGSUSED*/ static int madi_open (int dev, int mode, int open_flags) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; oss_native_word flags; int ok=1; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode != 0) ok=0; else if (portc->direction == DIR_OUT) { if (!play_channels_avail(devc, portc->channel, 2)) { ok=0; } } else { if (!rec_channels_avail(devc, portc->channel, 2)) { ok=0; } } if (!ok) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } portc->open_mode = mode; devc->open_audiodevs++; portc->channels = 2; if (portc->direction == DIR_OUT) reserve_play_channels(devc, portc->channel, 2); else reserve_rec_channels(devc, portc->channel, 2); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } /*ARGSUSED*/ static void madi_close (int dev, int mode) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; oss_native_word flags; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); portc->open_mode = 0; devc->open_audiodevs--; if (portc->direction == DIR_OUT) { free_play_channels(devc, portc->channel, portc->channels); } else { free_rec_channels(devc, portc->channel, portc->channels); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } /*ARGSUSED*/ static void madi_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } /*ARGSUSED*/ static void madi_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { } /*ARGSUSED*/ static int madi_prepare_for_input (int dev, int bsize, int bcount) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_in; int i; int offs = portc->channel * CHBUF_PAGES; for (i = 0; i < CHBUF_PAGES * portc->channels; i++) madi_write (devc, MADI_RecPageTable + 4 * (offs + i), LSWAP(dmap->dmabuf_phys + i * 4096)); return 0; } /*ARGSUSED*/ static int madi_prepare_for_output (int dev, int bsize, int bcount) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; dmap_t *dmap = audio_engines[dev]->dmap_out; int i; int offs = portc->channel * CHBUF_PAGES; for (i = 0; i < CHBUF_PAGES * portc->channels; i++) madi_write (devc, MADI_PlayPageTable + 4 * (offs + i), LSWAP(dmap->dmabuf_phys + i * 4096)); return 0; } /*ARGSUSED*/ static int madi_alloc_buffer (int dev, dmap_t * dmap, int direction) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; if (direction == OPEN_READ) { dmap->dmabuf = devc->recbuf + portc->channel * CHBUF_SIZE; dmap->dmabuf_phys = devc->recbuf_phys + portc->channel * CHBUF_SIZE; dmap->buffsize = CHBUF_SIZE; } else { dmap->dmabuf = devc->playbuf + portc->channel * CHBUF_SIZE; dmap->dmabuf_phys = devc->playbuf_phys + portc->channel * CHBUF_SIZE; dmap->buffsize = CHBUF_SIZE; } return 0; } /*ARGSUSED*/ static int madi_free_buffer (int dev, dmap_t * dmap, int direction) { dmap->dmabuf = NULL; dmap->dmabuf_phys = 0; dmap->buffsize = 0; return 0; } /*ARGSUSED*/ static int madi_get_buffer_pointer (int dev, dmap_t * dmap, int direction) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; unsigned int status; #if 1
Use only full/half buffer resolution.
int bytes; bytes = ((devc->cmd & MADI_LatencyMask) >> 1) + 8; bytes = 1 << bytes; bytes *= portc->channels; status = madi_read (devc, MADI_status); return (status & MADI_BufferHalf) ? bytes : 0; #else
Use full resolution
status = madi_read (devc, MADI_status) & MADI_BufferPosMask; return status * portc->channels; #endif } /*ARGSUSED*/ static void madi_setup_fragments (int dev, dmap_t * dmap, int direction) { madi_devc_t *devc = audio_engines[dev]->devc; madi_portc_t *portc = audio_engines[dev]->portc; int bytes; bytes = ((devc->cmd & MADI_LatencyMask) >> 1) + 8; bytes = 1 << bytes; dmap->buffsize = portc->channels * CHBUF_SIZE; #if 1
Use two fragment (ping-pong) buffer that is also used by the hardware.
dmap->fragment_size = bytes * portc->channels; dmap->nfrags = 2; #else
Use 4 fragments instead of 2 to avoid clicking caused by accessing the DMA buffer too close to the active DMA position.
This means that the DMA pointer will jump by 2 fregments every time there is a half/full bufferinterrupt.
dmap->fragment_size = (bytes * portc->channels) / 2; dmap->nfrags = 4; #endif dmap->bytes_in_use = dmap->nfrags * dmap->fragment_size; } static audiodrv_t madi_driver = { madi_open, madi_close, madi_output_block, madi_start_input, madi_ioctl, madi_prepare_for_input, madi_prepare_for_output, madi_halt, NULL, // madi_local_qlen, NULL, NULL, // madi_halt_input, NULL, // madi_halt_output, madi_trigger, madi_set_rate, madi_set_format, madi_set_channels, NULL, NULL, NULL, NULL, madi_alloc_buffer, madi_free_buffer, NULL, NULL, madi_get_buffer_pointer, NULL, madi_sync_control, NULL, NULL, NULL, NULL, madi_setup_fragments }; static int create_output_device (madi_devc_t * devc, char *name, int chn) { madi_portc_t *portc; adev_t *adev; int n; if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL) { cmn_err (CE_WARN, "Cannot allocate portc structure\n"); return OSS_ENOMEM; } memset (portc, 0, sizeof (*portc)); n = devc->num_outputs++;; portc->channel = chn; portc->channels = 2; portc->max_channels = 64 - chn; devc->out_portc[n] = portc; if ((portc->audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, name, &madi_driver, sizeof (audiodrv_t), ADEV_NOINPUT | ADEV_NONINTERLEAVED | ADEV_NOMMAP, AFMT_S32_LE, devc, -1)) < 0) { return portc->audio_dev; } adev = audio_engines[portc->audio_dev]; if (devc->first_audiodev == -1) devc->first_audiodev = portc->audio_dev; adev->rate_source = devc->first_audiodev; adev->mixer_dev = devc->mixer_dev; adev->portc = portc; adev->min_rate = devc->rate; adev->max_rate = devc->rate; adev->min_channels = 1; adev->max_channels = portc->max_channels; adev->min_block = CHBUF_SIZE / 2; adev->max_block = CHBUF_SIZE / 2; portc->direction = DIR_OUT; return portc->audio_dev; } static int create_input_device (madi_devc_t * devc, char *name, int chn) { madi_portc_t *portc; adev_t *adev; int n; if ((portc = PMALLOC (devc->osdev, sizeof (*portc))) == NULL) { cmn_err (CE_WARN, "Cannot allocate portc structure\n"); return OSS_ENOMEM; } memset (portc, 0, sizeof (*portc)); n = devc->num_inputs++;; portc->channel = chn; portc->channels = 2; portc->max_channels = 64 - chn; devc->in_portc[n] = portc; if ((portc->audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, name, &madi_driver, sizeof (audiodrv_t), ADEV_NOOUTPUT | ADEV_NONINTERLEAVED | ADEV_NOMMAP, AFMT_S32_LE, devc, -1)) < 0) { return portc->audio_dev; } adev = audio_engines[portc->audio_dev]; if (devc->first_audiodev == -1) devc->first_audiodev = portc->audio_dev; adev->rate_source = devc->first_audiodev; adev->mixer_dev = devc->mixer_dev; adev->portc = portc; adev->min_rate = devc->rate; adev->max_rate = devc->rate; adev->min_channels = 1; adev->max_channels = portc->max_channels; adev->min_block = CHBUF_SIZE / 2; adev->max_block = CHBUF_SIZE / 2; portc->direction = DIR_IN; return portc->audio_dev; } int oss_madi_attach (oss_device_t * osdev) { madi_devc_t *devc; unsigned char pci_irq_line, pci_revision /*, pci_latency */ ; unsigned short pci_command, vendor, device; unsigned int pci_ioaddr; int i; int err; int my_mixer; int chn, src; DDB (cmn_err (CE_NOTE, "Entered RME MADI detect routine\n")); pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); pci_read_config_word (osdev, PCI_DEVICE_ID, &device); if (vendor != RME_VENDOR_ID || device != RME_MADI) return 0; pci_read_config_word (osdev, PCI_COMMAND, &pci_command); pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision); pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line); pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &pci_ioaddr); pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; pci_write_config_word (osdev, PCI_COMMAND, pci_command); if (pci_ioaddr == 0) { cmn_err (CE_WARN, "BAR0 not initialized by BIOS\n"); return 0; } if ((devc = PMALLOC (osp, sizeof (*devc))) == NULL) { cmn_err (CE_WARN, "Cannot allocate devc\n"); return 0; } memset (devc, 0, sizeof (*devc)); devc->osdev = osdev; osdev->devc = devc; devc->first_audiodev = -1; MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); DDB (cmn_err (CE_CONT, "RME HDSPM revision %d\n", pci_revision)); if (pci_revision < 230) { devc->name = "RME MADI"; devc->model = MDL_MADI; } else { devc->name = "RME AES32"; devc->model = MDL_AES32; } DDB (cmn_err (CE_CONT, "Card model is %s\n", devc->name)); devc->rate = devc->next_rate = 48000; // TODO: Also set the other rate control fields */ osdev->hw_info = PMALLOC (osdev, HWINFO_SIZE); /* Text buffer for additional device info */ sprintf (osdev->hw_info, "PCI revision %d", pci_revision); oss_register_device (osdev, devc->name); devc->physaddr = pci_ioaddr; devc->registers = MAP_PCI_MEM (devc->osdev, 0, devc->physaddr, 65536); if (devc->registers == NULL) { cmn_err (CE_WARN, "Can't map PCI registers (0x%08x)\n", devc->physaddr); return 0; } devc->playbuf = CONTIG_MALLOC (devc->osdev, DMABUF_SIZE, MEMLIMIT_32BITS, &devc->playbuf_phys, devc->play_dma_handle); if (devc->playbuf == NULL) { cmn_err (CE_WARN, "Cannot allocate play DMA buffers\n"); return 0; } devc->recbuf = CONTIG_MALLOC (devc->osdev, DMABUF_SIZE, MEMLIMIT_32BITS, &devc->recbuf_phys, devc->rec_dma_handle); if (devc->recbuf == NULL) { cmn_err (CE_WARN, "Cannot allocate rec DMA buffers\n"); return 0; } if ((err = oss_register_interrupts (devc->osdev, 0, madiintr, NULL)) < 0) { cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); return 0; } if (madi_maxchannels < 2) madi_maxchannels=2; if (madi_maxchannels > MAX_CHANNELS)madi_maxchannels=MAX_CHANNELS; if (madi_devsize < 1) madi_devsize = 1; if (madi_devsize != 1 && madi_devsize != 2 && madi_devsize != 4) madi_devsize = 2;
Set the hw mixer shadow values to sane levels so that initialize_hardware() can write them to the actual registers.
for (chn = 0; chn < MAX_CHANNELS; chn++) for (src = 0; src < 2 * MAX_CHANNELS; src++) devc->mixer_values[chn][src] = MUTE_GAIN; /* Set everything off */ #if 0 for (src = 0; src < MAX_CHANNELS; src += 2) { /* Setup playback monitoring to analog out */ devc->mixer_values[MONITOR_CH][SRC_PLAY | src] = UNITY_GAIN / 10; devc->mixer_values[MONITOR_CH + 1][SRC_PLAY | (src + 1)] = UNITY_GAIN / 10; } #endif for (chn = 0; chn < MAX_CHANNELS; chn++) devc->mixer_values[chn][SRC_PLAY | chn] = UNITY_GAIN; /* N->N outputs on */ initialize_hardware (devc); if ((my_mixer = madi_install_mixer (devc)) < 0) { devc->mixer_dev = -1; return 0; } else { devc->mixer_dev = my_mixer; } if (madi_devsize == 1) { for (i = 0; i < madi_maxchannels; i++) { char tmp[32]; sprintf (tmp, "%s out%d", devc->name, i+1); create_output_device (devc, tmp, i); } for (i = 0; i < madi_maxchannels; i++) { char tmp[32]; sprintf (tmp, "%s in%d", devc->name, i+1); create_input_device (devc, tmp, i); } } else { for (i = 0; i < madi_maxchannels; i += madi_devsize) { char tmp[32]; sprintf (tmp, "%s out%d-%d", devc->name, i+1, i + madi_devsize); create_output_device (devc, tmp, i); } for (i = 0; i < madi_maxchannels; i += madi_devsize) { char tmp[32]; sprintf (tmp, "%s in%d-%d", devc->name, i+1, i + madi_devsize); create_input_device (devc, tmp, i); } } madi_activate_mixer (devc); return 1; } int oss_madi_detach (oss_device_t * osdev) { madi_devc_t *devc = (madi_devc_t *) osdev->devc; if (oss_disable_device (osdev) < 0) return 0; stop_audio (devc);
Shut up the hardware
devc->cmd &= ~MADI_FreqMask; madi_control (devc, devc->cmd); oss_unregister_interrupts (devc->osdev); MUTEX_CLEANUP (devc->mutex); if (devc->registers != NULL) UNMAP_PCI_MEM (osdev, 0, devc->physaddr, devc->registers, 65536); CONTIG_FREE (devc->osdev, devc->playbuf, DMABUF_SIZE, devc->play_dma_handle); CONTIG_FREE (devc->osdev, devc->recbuf, DMABUF_SIZE, devc->rec_dma_handle); oss_unregister_device (devc->osdev); return 1; }