/* * Purpose: Driver for RME MADI and AES32 audio interfaces * */ /* * * 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; }