diff options
author | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
---|---|---|
committer | Igor Pashev <pashev.igor@gmail.com> | 2013-05-03 21:08:42 +0400 |
commit | 1058def8e7827e56ce4a70afb4aeacb5dc44148f (patch) | |
tree | 4495d23e7b54ab5700e3839081e797c1eafe0db9 /kernel/drv/oss_cs461x/oss_cs461x.c | |
download | oss4-upstream.tar.gz |
Imported Upstream version 4.2-build2006upstream/4.2-build2006upstream
Diffstat (limited to 'kernel/drv/oss_cs461x/oss_cs461x.c')
-rw-r--r-- | kernel/drv/oss_cs461x/oss_cs461x.c | 1931 |
1 files changed, 1931 insertions, 0 deletions
diff --git a/kernel/drv/oss_cs461x/oss_cs461x.c b/kernel/drv/oss_cs461x/oss_cs461x.c new file mode 100644 index 0000000..e024fc6 --- /dev/null +++ b/kernel/drv/oss_cs461x/oss_cs461x.c @@ -0,0 +1,1931 @@ +/* + * Purpose: Driver for Crystal cs461x and cs461x PCI audio controllers + */ +/* + * + * 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_cs461x_cfg.h" +#include "midi_core.h" +#include "ac97.h" +#include "oss_pci.h" +#include "cs461x.h" + +extern int cs461x_clkrun_fix; + +#define CRYSTAL_VENDOR_ID 0x1013 +#define CRYSTAL_CS4610_ID 0x6001 +#define CRYSTAL_CS461x_ID 0x6003 +#define CRYSTAL_CS4615_ID 0x6004 + +#define USE_SG + +#define WRITEB(a,d) devc->bRegister0[a] = d +#define READB(a) devc->bRegister0[a] +#define WRITEW(a,d) devc->wRegister0[a>>1] = d +#define READW(a) devc->wRegister0[a>>1] + +#define READ0L(a) (devc->dwRegister0[a>>2]) +#define WRITE0L(a, d) (devc->dwRegister0[a>>2] = d) +#define READ1L(a) (devc->dwRegister1[a>>2]) +#define WRITE1L(a,d) (devc->dwRegister1[a>>2] = d) + +#define READ1L(a) (devc->dwRegister1[a>>2]) + +#ifdef OSS_BIG_ENDIAN +static unsigned int +ymf_swap (unsigned int x) +{ + return ((x & 0x000000ff) << 24) | + ((x & 0x0000ff00) << 8) | + ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24); +} + +#define LSWAP(x) ymf_swap(x) +#else +#define LSWAP(x) x +#endif + +#define MAX_PORTC 2 + +typedef struct cs461x_portc +{ + int speed, bits, channels; + int open_mode; + int audio_enabled; + int trigger_bits; + int audiodev; +} +cs461x_portc; + +typedef struct cs461x_devc +{ + oss_device_t *osdev; + char *chip_name; + unsigned short subsysid; + + oss_native_word bar0addr, bar1addr; + unsigned int *bar0virt, *bar1virt; + volatile unsigned int *dwRegister0, *dwRegister1; + volatile unsigned short *wRegister0, *wRegister1; + volatile unsigned char *bRegister0, *bRegister1; + int irq; + int dual_codec; + unsigned int *play_sgbuf; + unsigned int play_sgbuf_phys; + oss_dma_handle_t play_sgbuf_dma_handle; + + /* Mutex */ + oss_mutex_t mutex; + oss_mutex_t low_mutex; + + /* MIDI */ + int midi_opened; + int midi_dev; + oss_midi_inputbyte_t midi_input_intr; + + /* Mixer parameters */ + ac97_devc ac97devc, ac97devc2; + int mixer_dev, mixer2_dev; + + /* Audio parameters */ + cs461x_portc portc[MAX_PORTC]; + int open_mode; + oss_native_word PCTL; /*Play Control Register */ + oss_native_word CCTL; /*Record Control Register */ + int processor_started; +} +cs461x_devc; + +#define MAX_CS461x 6 + +static int ac97_write (void *devc, int addr, int data); +static int ac97_read (void *devc, int addr); + +static void +PokeBA0 (cs461x_devc * devc, unsigned int offset, unsigned int value) +{ + WRITE0L (offset, value); +} + +static unsigned int +PeekBA0 (cs461x_devc * devc, unsigned int offset) +{ + unsigned int value; + + value = READ0L (offset); + return (value); + +} + +static int +ac97_read (void *devc_, int offset) +{ + cs461x_devc *devc = devc_; + int count; + unsigned int status, value; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + /* + * Make sure that there is not data sitting around from a previous + * uncompleted access. ACSDA = Status Data Register = 47Ch + */ + status = PeekBA0 (devc, BA0_ACSDA); + /* Get the actual AC97 register from the offset */ + PokeBA0 (devc, BA0_ACCAD, offset - BA0_AC97_RESET); + PokeBA0 (devc, BA0_ACCDA, 0); + PokeBA0 (devc, BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | + ACCTL_ESYN | ACCTL_RSTN); + + /* Wait for the read to occur. */ + for (count = 0; count < 10; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (100); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + status = PeekBA0 (devc, BA0_ACCTL); + if (!(status & ACCTL_DCV)) + { + break; + } + } + + /* Make sure the read completed. */ + if (status & ACCTL_DCV) + { + cmn_err (CE_WARN, "AC97 Read Timedout\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + + /* Wait for the valid status bit to go active. */ + + for (count = 0; count < 10; count++) + { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + */ + status = PeekBA0 (devc, BA0_ACSTS); + /* + * See if we have valid status. + * VSTS - Valid Status + */ + if (status & ACSTS_VSTS) + break; + + /* + * Wait for a short while. + */ + oss_udelay (100); + } + /* Make sure we got valid status. */ + if (!(status & ACSTS_VSTS)) + { + cmn_err (CE_WARN, "AC97 Read Timedout(2)\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ + value = PeekBA0 (devc, BA0_ACSDA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (value); +} + +static int +ac97_write (void *devc_, int offset, int data) +{ + cs461x_devc *devc = devc_; + int count; + unsigned int status = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + PokeBA0 (devc, BA0_ACCAD, offset); + PokeBA0 (devc, BA0_ACCDA, data); + PokeBA0 (devc, BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + for (count = 0; count < 10; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (100); + /* Now, check to see if the write has completed. */ + /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */ + status = PeekBA0 (devc, BA0_ACCTL); + if (!(status & ACCTL_DCV)) + break; + } + + /* write didn't completed. */ + if (status & ACCTL_DCV) + { + cmn_err (CE_WARN, "AC97 Write timeout\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; +} + +static int +ac97_read2 (void *devc_, int offset) +{ + cs461x_devc *devc = devc_; + int count; + unsigned int status, value; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + /* + * Make sure that there is not data sitting around from a previous + * uncompleted access. ACSDA = Status Data Register = 47Ch + */ + status = PeekBA0 (devc, BA0_ACSDA2); + + /* Get the actual AC97 register from the offset */ + PokeBA0 (devc, BA0_ACCAD, offset); + PokeBA0 (devc, BA0_ACCDA, 0); + PokeBA0 (devc, BA0_ACCTL, + ACCTL_DCV | ACCTL_TC | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN | + ACCTL_RSTN); + + + /* Wait for the read to occur. */ + for (count = 0; count < 10; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (1000); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + status = PeekBA0 (devc, BA0_ACCTL); + if (!(status & ACCTL_DCV)) + break; + } + + /* Make sure the read completed. */ + if (PeekBA0 (devc, BA0_ACCTL) & ACCTL_DCV) + { + cmn_err (CE_WARN, "Secondary AC97 Read Timedout\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + + /* Wait for the valid status bit to go active. */ + + for (count = 0; count < 10; count++) + { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + */ + status = PeekBA0 (devc, BA0_ACSTS2); + /* + * See if we have valid status. + * VSTS - Valid Status + */ + if (status & ACSTS_VSTS) + break; + + /* + * Wait for a short while. + */ + oss_udelay (1000); + } + /* Make sure we got valid status */ + if (!(status & ACSTS_VSTS)) + { + cmn_err (CE_WARN, "Secondary AC97 Read Timedout(2)\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ + value = PeekBA0 (devc, BA0_ACSDA2); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (value); +} + +static int +ac97_write2 (void *devc_, int offset, int data) +{ + cs461x_devc *devc = devc_; + int count; + unsigned int status = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + PokeBA0 (devc, BA0_ACCAD, offset); + PokeBA0 (devc, BA0_ACCDA, data); + PokeBA0 (devc, BA0_ACCTL, + ACCTL_DCV | ACCTL_TC | ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + for (count = 0; count < 10; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (1000); + /* Now, check to see if the write has completed. */ + /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */ + status = PeekBA0 (devc, BA0_ACCTL); + if (!(status & ACCTL_DCV)) + break; + } + + /* write didn't completed. */ + if (status & ACCTL_DCV) + { + cmn_err (CE_WARN, "Secondary AC97 Write timeout\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; +} + +/* + * this is 3*1024 for parameter, 3.5*1024 for sample and 2*3.5*1024 + * for code since each instruction is 40 bits and takes two dwords + */ +#define INKY_BA1_DWORD_SIZE (13 * 1024 + 512) +/* this is parameter, sample, and code */ +#define INKY_MEMORY_COUNT 3 +struct cs461x_firmware_struct +{ + struct + { + unsigned int ulDestAddr; + unsigned int ulSourceSize; + } + MemoryStat[INKY_MEMORY_COUNT]; + unsigned int BA1Array[INKY_BA1_DWORD_SIZE]; +}; +#include "cs461x_dsp.h" + +static void +DoTransfer (cs461x_devc * devc, unsigned int *fpulSrc, + unsigned int ulByteDestOffset, unsigned int ulByteLength) +{ + int dwByteCounter; + + if (ulByteDestOffset & 0x3) + { + cmn_err (CE_WARN, "invalid DMA address\n"); + return; + } + + for (dwByteCounter = 0; dwByteCounter < ulByteLength; dwByteCounter += 4) + { + WRITE1L ((ulByteDestOffset + dwByteCounter), + fpulSrc[dwByteCounter / 4]); + } +} + +static void +install_ucode (cs461x_devc * devc) +{ + unsigned int i, count; + + count = 0; + for (i = 0; i < INKY_MEMORY_COUNT; i++) + { + + DoTransfer (devc, (unsigned int *) (cs461x_firmware.BA1Array + count), + cs461x_firmware.MemoryStat[i].ulDestAddr, + cs461x_firmware.MemoryStat[i].ulSourceSize); + count += cs461x_firmware.MemoryStat[i].ulSourceSize / 4; + } +} + +void +clear_serial_fifo (cs461x_devc * devc) +{ + unsigned int ulIdx, ulLoop; + unsigned int ulStatus = 0; + unsigned int ulCLKCR1; + + + ulCLKCR1 = PeekBA0 (devc, BA0_CLKCR1); + + if (!(ulCLKCR1 & CLKCR1_SWCE)) + { + PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1 | CLKCR1_SWCE); + } + PokeBA0 (devc, BA0_SERBWP, 0); + + for (ulIdx = 0; ulIdx < 256; ulIdx++) + { + /* + * Make sure the previous FIFO write operation has completed. + */ + for (ulLoop = 0; ulLoop < 5; ulLoop++) + { + oss_udelay (100); + ulStatus = PeekBA0 (devc, BA0_SERBST); + if (!(ulStatus & SERBST_WBSY)) + { + break; + } + } + + if (ulStatus & SERBST_WBSY) + { + if (!(ulCLKCR1 & CLKCR1_SWCE)) + { + PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1); + } + } + + /* Write the serial port FIFO index. */ + + PokeBA0 (devc, BA0_SERBAD, ulIdx); + + /* + * Tell the serial port to load the new value into the FIFO location. + */ + PokeBA0 (devc, BA0_SERBCM, SERBCM_WRC); + } + + /* + * Now, if we powered up the devices, then power them back down again. + * This is kinda ugly, but should never happen. + */ + if (!(ulCLKCR1 & CLKCR1_SWCE)) + { + PokeBA0 (devc, BA0_CLKCR1, ulCLKCR1); + } + +} + +static int +cs461x_reset_processor (cs461x_devc * devc) +{ + unsigned int ulIdx; + + /* Write the reset bit of the SP control register. */ + WRITE1L (BA1_SPCR, SPCR_RSTSP); + WRITE1L (BA1_SPCR, SPCR_DRQEN); + + /* Clear the trap registers. */ + for (ulIdx = 0; ulIdx < 8; ulIdx++) + { + WRITE1L (BA1_DREG, DREG_REGID_TRAP_SELECT + ulIdx); + WRITE1L (BA1_TWPR, 0xFFFF); + } + + WRITE1L (BA1_DREG, 0); + WRITE1L (BA1_FRMT, 0xadf); + return (0); +} + +static int +cs461x_start_processor (cs461x_devc * devc) +{ + + unsigned int ulCount; + int ulTemp = 0; + + /* reset the processor first */ + cs461x_reset_processor (devc); + + /* reload the ucode */ + install_ucode (devc); + + /* Set the frame timer to reflect the number of cycles per frame. */ + WRITE1L (BA1_FRMT, 0xADF); + + /* + * Turn on the run, run at frame, and DMA enable bits in the local copy of + * the SP control register. + */ + WRITE1L (BA1_SPCR, SPCR_RUN | SPCR_RUNFR | SPCR_DRQEN); + + /* + * Wait until the run at frame bit resets itself in the SP control + * register. + */ + for (ulCount = 0; ulCount < 25; ulCount++) + { + /* Wait a little bit, so we don't issue PCI reads too frequently. */ + oss_udelay (100); + + /* Fetch the current value of the SP status register. */ + ulTemp = READ1L (BA1_SPCR); + + /* If the run at frame bit has reset, then stop waiting. */ + if (!(ulTemp & SPCR_RUNFR)) + break; + } + /* If the run at frame bit never reset, then return an error. */ + if (ulTemp & SPCR_RUNFR) + { + cmn_err (CE_WARN, "Start(): SPCR_RUNFR never reset.\n"); + } + devc->processor_started = 1; + return 0; +} + +static int +cs461xintr (oss_device_t * osdev) +{ + cs461x_devc *devc = (cs461x_devc *) osdev->devc; + cs461x_portc *portc; + unsigned int status; + int i; + unsigned int uart_stat; + int serviced = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + +/* Read the Interrupt Status Register */ + status = PeekBA0 (devc, BA0_HISR); + +/* + * This is the MIDI read interrupt service. First check to see + * if the MIDI interrupt flag is set in the HISR register. Next + * read the MIDI status register. See if Receive Buffer Empty + * is empty (0=FIFO Not empty, 1=FIFO is empty + */ + if ((devc->midi_opened & OPEN_READ) && (status & HISR_MIDI)) + { + uart_stat = PeekBA0 (devc, BA0_MIDSR); +/* + * read one byte of MIDI data and hand it off the the sequencer module + * to decode this. Keep checking to see if the data is available. Stop + * when no more data is there in the FIFO. + */ + while (!(uart_stat & MIDSR_RBE)) + { + unsigned char d; + d = PeekBA0 (devc, BA0_MIDRP); + + if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, d); + uart_stat = PeekBA0 (devc, BA0_MIDSR); + } + } + +/* Audio interrupt handling */ + if (status & 0x3) + { + for (i = 0; i < MAX_PORTC; i++) + { + portc = &devc->portc[i]; + if (status & 0x1) + if (portc->trigger_bits & PCM_ENABLE_OUTPUT) + { + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; + int ptr, n; + + ptr = READ1L (BA1_PBA) - dmap->dmabuf_phys; + ptr = ptr / dmap->fragment_size; + if (ptr < 0 || ptr >= dmap->nfrags) + ptr = 0; + n = 0; + while (dmap_get_qhead (dmap) != ptr && n++ < dmap->nfrags) + oss_audio_outputintr (portc->audiodev, 0); + } + + if (status & 0x2) + if (portc->trigger_bits & PCM_ENABLE_INPUT) + { + + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in; + int ptr, n; + + ptr = READ1L (BA1_CBA) - dmap->dmabuf_phys; + ptr = ptr / (dmap->fragment_size); + + if (ptr < 0 || ptr >= dmap->nfrags) + ptr = 0; + n = 0; + while (dmap_get_qtail (dmap) != ptr && n++ < dmap->nfrags) + oss_audio_inputintr (portc->audiodev, 0); + } + } + } + + serviced = 1; + PokeBA0 (devc, BA0_HICR, 0x03); + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return serviced; +} + +static int +cs461x_audio_set_rate (int dev, int arg) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->speed; + + if (audio_engines[dev]->flags & ADEV_FIXEDRATE) + { + audio_engines[dev]->fixed_rate = 48000; + arg = 48000; + } + else + audio_engines[dev]->fixed_rate = 0; + + if (arg > 48000) + arg = 48000; + if (arg < 5000) + arg = 5000; + portc->speed = arg; + return portc->speed; +} + +static short +cs461x_audio_set_channels (int dev, short arg) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + + if (audio_engines[dev]->flags & ADEV_STEREOONLY) + arg = 2; + + if ((arg != 1) && (arg != 2)) + return portc->channels; + portc->channels = arg; + + return portc->channels; +} + +static unsigned int +cs461x_audio_set_format (int dev, unsigned int arg) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->bits; + + if (audio_engines[dev]->flags & ADEV_16BITONLY) + arg = 16; + + if (!(arg & (AFMT_U8 | AFMT_S16_LE))) + return portc->bits; + portc->bits = arg; + + return portc->bits; +} + +/*ARGSUSED*/ +static int +cs461x_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void cs461x_audio_trigger (int dev, int state); + +static void +cs461x_audio_reset (int dev) +{ + cs461x_audio_trigger (dev, 0); +} + +static void +cs461x_audio_reset_input (int dev) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + cs461x_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +cs461x_audio_reset_output (int dev) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + cs461x_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +cs461x_audio_open (int dev, int mode, int open_flags) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + cs461x_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + if (devc->open_mode & mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + devc->open_mode |= mode; + + portc->open_mode = mode; + portc->audio_enabled &= ~mode; +#if 0 + if (mode & OPEN_READ) + { + dmapin->buffsize = 4096; + audio_engines[dev]->fixed_rate = 48000; + audio_engines[dev]->min_rate = 48000; + audio_engines[dev]->max_rate = 48000; + audio_engines[dev]->flags |= + ADEV_FIXEDRATE | ADEV_16BITONLY | ADEV_STEREOONLY; + } + if (mode & OPEN_WRITE) + { + audio_engines[dev]->min_rate = 5000; + audio_engines[dev]->max_rate = 48000; + audio_engines[dev]->flags &= + ~(ADEV_FIXEDRATE | ADEV_16BITONLY | ADEV_STEREOONLY); + audio_engines[dev]->fixed_rate = 0; + } +#endif + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +cs461x_audio_close (int dev, int mode) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + cs461x_devc *devc = audio_engines[dev]->devc; + + cs461x_audio_reset (dev); + portc->audio_enabled &= ~mode; + devc->open_mode &= ~mode; + portc->open_mode = 0; +} + +/*ARGSUSED*/ +static void +cs461x_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; +} + +/*ARGSUSED*/ +static void +cs461x_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cs461x_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +static void +cs461x_audio_trigger (int dev, int state) +{ + oss_native_word flags, tmp; + cs461x_devc *devc = audio_engines[dev]->devc; + cs461x_portc *portc = audio_engines[dev]->portc; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->open_mode & OPEN_WRITE) + { + if (state & PCM_ENABLE_OUTPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && + !(portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + tmp = READ1L (BA1_PCTL); + tmp &= 0xFFFF; + tmp |= devc->PCTL; + WRITE1L (BA1_PCTL, tmp); + portc->trigger_bits |= PCM_ENABLE_OUTPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + tmp = READ1L (BA1_PCTL); + tmp &= 0xFFFF; + WRITE1L (BA1_PCTL, tmp); + } + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + tmp = READ1L (BA1_CCTL); + tmp &= 0xFFFF0000; + tmp |= devc->CCTL; + WRITE1L (BA1_CCTL, tmp); + portc->trigger_bits |= PCM_ENABLE_INPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + tmp = READ1L (BA1_CCTL); + tmp &= 0xFFFF0000; + WRITE1L (BA1_CCTL, tmp); + } + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static int +cs461x_play_rate (cs461x_devc * devc, unsigned int ulInRate) +{ +/*define GOF_PER_SEC 200*/ + unsigned int ulTemp1, ulTemp2; + unsigned int ulPhiIncr; + unsigned int ulCorrectionPerGOF, ulCorrectionPerSec; + unsigned int ulOutRate = 48000; + + ulTemp1 = ulInRate << 16; + ulPhiIncr = ulTemp1 / ulOutRate; + ulTemp1 -= ulPhiIncr * ulOutRate; + ulTemp1 <<= 10; + ulPhiIncr <<= 10; + ulTemp2 = ulTemp1 / ulOutRate; + ulPhiIncr += ulTemp2; + ulTemp1 -= ulTemp2 * ulOutRate; + ulCorrectionPerGOF = ulTemp1 / 200; + ulTemp1 -= ulCorrectionPerGOF * 200; + ulCorrectionPerSec = ulTemp1; + + /* Fill in the SampleRateConverter control block. */ + WRITE1L (BA1_PSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | + (ulCorrectionPerGOF & 0xFFFF)); + WRITE1L (BA1_PPI, ulPhiIncr); + return 0; +} + +static int +cs461x_record_rate (cs461x_devc * devc, int ulOutRate) +{ + unsigned int ulPhiIncr, ulCoeffIncr, ulTemp1, ulTemp2; + unsigned int ulCorrectionPerGOF, ulCorrectionPerSec, ulInitialDelay; + unsigned int ulInRate = 48000; + + /* + * We can only decimate by up to a factor of 1/9th the hardware rate. + * Return an error if an attempt is made to stray outside that limit. + */ + if ((ulOutRate * 9) < ulInRate) + { + cmn_err (CE_WARN, + "SetCaptureSampleRate(): Requested output rate is below 1/9th of the input rate.\n"); + return (-1); + } + + /* + * We can not capture at at rate greater than the Input Rate (48000). + * Return an error if an attempt is made to stray outside that limit. + */ + if (ulOutRate > ulInRate) + { + cmn_err (CE_WARN, + "SetCaptureSampleRate(): Requested output rate is greater than the input rate."); + return (-1); + } + ulTemp1 = ulOutRate << 16; + ulCoeffIncr = ulTemp1 / ulInRate; + ulTemp1 -= ulCoeffIncr * ulInRate; + ulTemp1 <<= 7; + ulCoeffIncr <<= 7; + ulCoeffIncr += ulTemp1 / ulInRate; + ulCoeffIncr ^= 0xFFFFFFFF; + ulCoeffIncr++; + ulTemp1 = ulInRate << 16; + ulPhiIncr = ulTemp1 / ulOutRate; + ulTemp1 -= ulPhiIncr * ulOutRate; + ulTemp1 <<= 10; + ulPhiIncr <<= 10; + ulTemp2 = ulTemp1 / ulOutRate; + ulPhiIncr += ulTemp2; + ulTemp1 -= ulTemp2 * ulOutRate; + ulCorrectionPerGOF = ulTemp1 / 200; + ulTemp1 -= ulCorrectionPerGOF * 200; + ulCorrectionPerSec = ulTemp1; + ulInitialDelay = ((ulInRate * 24) + ulOutRate - 1) / ulOutRate; + /* Fill in the VariDecimate control block. */ + WRITE1L (BA1_CSRC, ((ulCorrectionPerSec << 16) & 0xFFFF0000) | + (ulCorrectionPerGOF & 0xFFFF)); + WRITE1L (BA1_CCI, ulCoeffIncr); + WRITE1L (BA1_CD, + (((BA1_VARIDEC_BUF_1 + (ulInitialDelay << 2)) << 16) & 0xFFFF0000) + | 0x80); + WRITE1L (BA1_CPI, ulPhiIncr); +#if 0 + { + unsigned int frameGroupLength, cnt; + int rate = ulOutRate; + + frameGroupLength = 1; + for (cnt = 2; cnt <= 64; cnt *= 2) + { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 2; + } + if (((rate / 3) * 3) != rate) + { + frameGroupLength *= 3; + } + for (cnt = 5; cnt <= 125; cnt *= 5) + { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 5; + } + + WRITE1L (BA1_CFG1, frameGroupLength); + WRITE1L (BA1_CFG2, (0x00800000 | frameGroupLength)); + WRITE1L (BA1_CCST, 0x0000FFFF); + WRITE1L (BA1_CSPB, ((65536 * rate) / 24000)); + WRITE1L ((BA1_CSPB + 4), 0x0000FFFF); + } +#endif + return (0); +} + +struct InitStruct +{ + unsigned long off; + unsigned long val; +} +InitArray[] = +{ + { + 0x00000040, 0x3fc0000f} + , + { + 0x0000004c, 0x04800000} + , + { + 0x000000b3, 0x00000780} + , + { + 0x000000b7, 0x00000000} + , + { + 0x000000bc, 0x07800000} + , + { + 0x000000cd, 0x00800000} +,}; + + +/* + * "SetCaptureSPValues()" -- Initialize record task values before each + * capture startup. + */ +void +SetCaptureSPValues (cs461x_devc * devc) +{ + unsigned i, offset; + for (i = 0; i < sizeof (InitArray) / sizeof (struct InitStruct); i++) + { + offset = InitArray[i].off * 4; /* 8bit to 32bit offset value */ + WRITE1L (offset, InitArray[i].val); + } +} + +/*ARGSUSED*/ +static int +cs461x_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + cs461x_devc *devc = audio_engines[dev]->devc; + cs461x_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_in; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + /* Start the processor */ + if (!devc->processor_started) + cs461x_start_processor (devc); + + /* Set Capture S/P values */ + SetCaptureSPValues (devc); + /* set the record rate */ + cs461x_record_rate (devc, portc->speed); + + /* write the buffer address */ + WRITE1L (BA1_CBA, dmap->dmabuf_phys); + + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +cs461x_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + cs461x_devc *devc = audio_engines[dev]->devc; + cs461x_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_out; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + /* Start the processor */ + if (!devc->processor_started) + cs461x_start_processor (devc); + +#ifdef USE_SG + { + unsigned int sg_temp[9], sg_npages = 0; + int i; + oss_native_word Count, playFormat, tmp, tmp2; + + sg_npages = (dmap->bytes_in_use) / 4096; + + devc->play_sgbuf[0] = dmap->dmabuf_phys; + devc->play_sgbuf[1] = 0x00000008; + + for (i = 0; i < sg_npages; i++) + { + devc->play_sgbuf[2 * i] = dmap->dmabuf_phys + 4096 * i; + if (i == sg_npages - 1) + tmp2 = 0xbfff0000; + else + tmp2 = 0x80000000 + 8 * (i + 1); + + devc->play_sgbuf[2 * i + 1] = tmp2; + } + sg_temp[0] = 0x82c0200d; + sg_temp[1] = 0xffff0000; + sg_temp[2] = devc->play_sgbuf[0]; + sg_temp[3] = 0x00010600; + sg_temp[4] = devc->play_sgbuf[2]; + sg_temp[5] = 0x80000010; + sg_temp[6] = devc->play_sgbuf[0]; + sg_temp[7] = devc->play_sgbuf[2]; + sg_temp[8] = (devc->play_sgbuf_phys & 0xffff000) | 0x10; + + for (i = 0; i < sizeof (sg_temp) / 4; i++) + WRITE1L ((BA1_PDTC + i * 4), sg_temp[i]); + + WRITE1L (BA1_PBA, dmap->dmabuf_phys); + + cs461x_play_rate (devc, (unsigned int) portc->speed); + + Count = 4; + playFormat = READ1L (BA1_PFIE); + playFormat &= ~0x0000f03f; + + if ((portc->channels == 2)) + { + playFormat &= ~DMA_RQ_C2_AC_MONO_TO_STEREO; + Count *= 2; + } + else + playFormat |= DMA_RQ_C2_AC_MONO_TO_STEREO; + + if ((portc->bits == 16)) + { + playFormat &= + ~(DMA_RQ_C2_AC_8_TO_16_BIT | DMA_RQ_C2_AC_SIGNED_CONVERT); + Count *= 2; + } + else + playFormat |= (DMA_RQ_C2_AC_8_TO_16_BIT | DMA_RQ_C2_AC_SIGNED_CONVERT); + + WRITE1L (BA1_PFIE, playFormat); + + tmp = READ1L (BA1_PDTC); + tmp &= 0xfffffe00; + WRITE1L (BA1_PDTC, tmp | --Count); + } +#else + { + unsigned int pdtc_value, pfie_value; + /* Set the sample rate converter */ + cs461x_play_rate (devc, (unsigned int) portc->speed); + + pfie_value = READ1L (BA1_PFIE); + pfie_value &= ~0x0000f03f; + + pdtc_value = READ1L (BA1_PDTC); + pdtc_value &= ~0x000003ff; + + /* Now set the sample size/stereo/mono */ + if ((portc->bits == 8) && (portc->channels == 1)) + { + pdtc_value |= 0x03; + pfie_value |= 0xB000; /*8bit mono */ + } + if ((portc->bits == 8) && (portc->channels == 2)) + { + pdtc_value |= 0x07; + pfie_value |= 0xA000; /*8bit stereo */ + } + if ((portc->bits == 16) && (portc->channels == 1)) + { + pdtc_value |= 0x07; + pfie_value |= 0x2000; /*16bit mono */ + } + if ((portc->bits == 16) && (portc->channels == 2)) + { + pdtc_value |= 0x0F; + pfie_value |= 0x0000; /*16bit stereo */ + } + + WRITE1L (BA1_PDTC, pdtc_value); + WRITE1L (BA1_PFIE, pfie_value); + WRITE1L (BA1_PBA, dmap->dmabuf_phys); + } +#endif + + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static int +cs461x_alloc_buffer (int dev, dmap_t * dmap, int direction) +{ + +#ifdef USE_SG + int err; +#else + oss_native_word phaddr; + cs461x_devc *devc = audio_engines[dev]->devc; +#endif + + if (dmap->dmabuf != NULL) + return 0; + +#ifdef USE_SG + if ((err = oss_alloc_dmabuf (dev, dmap, direction)) < 0) + { + cmn_err (CE_WARN, "Failed to allocate a DMA buffer\n"); + return err; + } +#else + dmap->dmabuf = + (void *) CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, &phaddr, dmap->dmabuf_dma_handle); + dmap->dmabuf_phys = phaddr; +# ifdef linux + oss_reserve_pages (dmap->dmabuf, dmap->dmabuf + 4096 - 1); +# endif + dmap->buffsize = 4096; +#endif + return 0; +} + +/*ARGSUSED*/ +static int +cs461x_free_buffer (int dev, dmap_t * dmap, int direction) +{ +#ifndef USE_SG + cs461x_devc *devc = audio_engines[dev]->devc; +#endif +#ifdef USE_SG + oss_free_dmabuf (dev, dmap); + dmap->dmabuf = 0; +#else + if (dmap->dmabuf == NULL) + return 0; + CONTIG_FREE (devc->osdev, dmap->dmabuf, 4096, dmap->dmabuf_dma_handle); +# ifdef linux + oss_unreserve_pages (dmap->dmabuf, dmap->dmabuf + 4096 - 1); +# endif + dmap->dmabuf = 0; +#endif + return 0; + +} + +static int +cs461x_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + cs461x_devc *devc = audio_engines[dev]->devc; + unsigned int ptr = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + if (direction == PCM_ENABLE_OUTPUT) + { + ptr = READ1L (BA1_PBA) - dmap->dmabuf_phys; + } + if (direction == PCM_ENABLE_INPUT) + { + ptr = READ1L (BA1_CBA) - dmap->dmabuf_phys; + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return ptr; +} + +static audiodrv_t cs461x_audio_driver = { + cs461x_audio_open, + cs461x_audio_close, + cs461x_audio_output_block, + cs461x_audio_start_input, + cs461x_audio_ioctl, + cs461x_audio_prepare_for_input, + cs461x_audio_prepare_for_output, + cs461x_audio_reset, + NULL, + NULL, + cs461x_audio_reset_input, + cs461x_audio_reset_output, + cs461x_audio_trigger, + cs461x_audio_set_rate, + cs461x_audio_set_format, + cs461x_audio_set_channels, + NULL, + NULL, + NULL, + NULL, + cs461x_alloc_buffer, + cs461x_free_buffer, + NULL, + NULL, + cs461x_get_buffer_pointer +}; + +/***********************MIDI PORT ROUTINES ****************/ + +/*ARGSUSED*/ +static int +cs461x_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, + oss_midi_outputintr_t outputintr) +{ + cs461x_devc *devc = (cs461x_devc *) midi_devs[dev]->devc; + oss_native_word flags; + + if (devc->midi_opened) + { + return OSS_EBUSY; + } + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + devc->midi_input_intr = inputbyte; + devc->midi_opened = mode; + + /* first reset the MIDI port */ + PokeBA0 (devc, BA0_MIDCR, 0x10); + PokeBA0 (devc, BA0_MIDCR, 0x00); + + /* Now check if we're in Read or Write mode */ + if (mode & OPEN_READ) + { + /* enable MIDI Input intr and receive enable */ + PokeBA0 (devc, BA0_MIDCR, MIDCR_RXE | MIDCR_RIE); + } + + if (mode & OPEN_WRITE) + { + /* enable MIDI transmit enable without interrupt mode */ + PokeBA0 (devc, BA0_MIDCR, MIDCR_TXE); + } + PokeBA0 (devc, BA0_HICR, HICR_IEV | HICR_CHGM); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static void +cs461x_midi_close (int dev, int mode) +{ + cs461x_devc *devc = (cs461x_devc *) midi_devs[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); +/* Reset the device*/ + PokeBA0 (devc, BA0_MIDCR, 0x10); + PokeBA0 (devc, BA0_MIDCR, 0x00); + devc->midi_opened = 0; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static int +cs461x_midi_out (int dev, unsigned char midi_byte) +{ + cs461x_devc *devc = (cs461x_devc *) midi_devs[dev]->devc; + unsigned char uart_stat; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + uart_stat = PeekBA0 (devc, BA0_MIDSR); + +/* Check if Transmit buffer full flag is set - if so return */ + if ((uart_stat & MIDSR_TBF)) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; + } +/* Now put the MIDI databyte in the write port */ + PokeBA0 (devc, BA0_MIDWP, midi_byte); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 1; +} + +/*ARGSUSED*/ +static int +cs461x_midi_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t cs461x_midi_driver = { + cs461x_midi_open, + cs461x_midi_close, + cs461x_midi_ioctl, + cs461x_midi_out +}; + +static int +init_cs461x (cs461x_devc * devc) +{ + + int my_mixer, my_mixer2, i; + unsigned int ulCount = 0; + unsigned int ulStatus = 0; + unsigned int tmp; + int first_dev = 0; + int adev; + oss_native_word phaddr; + +/****************BEGIN HARDWARE INIT*****************/ + /* + * First, blast the clock control register to zero so that the PLL starts + * out in a known state, and blast the master serial port control register + * to zero so that the serial ports also start out in a known state. + */ + PokeBA0 (devc, BA0_CLKCR1, 0x0); + PokeBA0 (devc, BA0_SERMC1, 0x0); + oss_udelay (1000); + /* + * If we are in AC97 mode, then we must set the part to a host controlled + * AC-link. Otherwise, we won't be able to bring up the link. + */ + if (devc->dual_codec) + PokeBA0 (devc, BA0_SERACC, + SERACC_HSP | SERACC_TWO_CODECS | SERACC_CODEC_TYPE_2_0); + else + PokeBA0 (devc, BA0_SERACC, SERACC_HSP | SERACC_CODEC_TYPE_1_03); /* AC97 1.03 */ + + /* + * Drive the ARST# pin low for a minimum of 1uS (as defined in the AC97 + * spec) and then drive it high. This is done for non AC97 modes since + * there might be logic external to the CS461x that uses the ARST# line + * for a reset. + */ + PokeBA0 (devc, BA0_ACCTL, 1); + oss_udelay (10000); + PokeBA0 (devc, BA0_ACCTL, 0); + oss_udelay (10000); + PokeBA0 (devc, BA0_ACCTL, ACCTL_RSTN); + /* + * The first thing we do here is to enable sync generation. As soon + * as we start receiving bit clock, we'll start producing the SYNC + * signal. + */ + PokeBA0 (devc, BA0_ACCTL, ACCTL_ESYN | ACCTL_RSTN); + + /* + * Now wait for a short while to allow the AC97 part to start + * generating bit clock (so we don't try to start the PLL without an + * input clock). + */ + oss_udelay (500000); + /* + * Set the serial port timing configuration, so that + * the clock control circuit gets its clock from the correct place. + */ + PokeBA0 (devc, BA0_SERMC1, SERMC1_PTC_AC97); + oss_udelay (500000); + /* + * Write the selected clock control setup to the hardware. Do not turn on + * SWCE yet (if requested), so that the devices clocked by the output of + * PLL are not clocked until the PLL is stable. + */ + PokeBA0 (devc, BA0_PLLCC, PLLCC_LPF_1050_2780_KHZ | PLLCC_CDR_73_104_MHZ); + PokeBA0 (devc, BA0_PLLM, 0x3a); + PokeBA0 (devc, BA0_CLKCR2, CLKCR2_PDIVS_8); + + /* + * Power up the PLL. + */ + PokeBA0 (devc, BA0_CLKCR1, CLKCR1_PLLP); + + /* + * Wait until the PLL has stabilized. + */ + oss_udelay (100000); + /* + * Turn on clocking of the core so that we can setup the serial ports. + */ + tmp = PeekBA0 (devc, BA0_CLKCR1) | CLKCR1_SWCE; + PokeBA0 (devc, BA0_CLKCR1, tmp); + + /* Clear the FIFOs */ + clear_serial_fifo (devc); + /* PokeBA0 (devc, BA0_SERBSP, 0); */ + + /* + * Write the serial port configuration to the part. The master + * enable bit is not set until all other values have been written. + */ + PokeBA0 (devc, BA0_SERC1, SERC1_SO1F_AC97 | SERC1_SO1EN); + PokeBA0 (devc, BA0_SERC2, SERC2_SI1F_AC97 | SERC1_SO1EN); + PokeBA0 (devc, BA0_SERMC1, SERMC1_PTC_AC97 | SERMC1_MSPE); + + PokeBA0 (devc, BA0_SERC7, SERC7_ASDI2EN); + PokeBA0 (devc, BA0_SERC3, 0); + PokeBA0 (devc, BA0_SERC4, 0); + PokeBA0 (devc, BA0_SERC5, 0); + PokeBA0 (devc, BA0_SERC6, 0); + + oss_udelay (100000); + + + /* Wait for the codec ready signal from the AC97 codec. */ + for (ulCount = 0; ulCount < 1000; ulCount++) + { + /* + * First, lets wait a short while to let things settle out a bit, + * and to prevent retrying the read too quickly. + */ + oss_udelay (10000); + /* + * Read the AC97 status register to see if we've seen a CODEC READY + * signal from the AC97 codec. + */ + ulStatus = PeekBA0 (devc, BA0_ACSTS); + if (ulStatus & ACSTS_CRDY) + break; + } + + if (!(ulStatus & ACSTS_CRDY)) + { + cmn_err (CE_WARN, "Initialize() Never read Codec Ready from AC97.\n"); + return 0; + } + + if (devc->dual_codec) + { + for (ulCount = 0; ulCount < 1000; ulCount++) + { + /* + * + * First, lets wait a short while to let things settle out a bit, + * and to prevent retrying the read too quickly. + * + */ + oss_udelay (10000); + /* + * Read the AC97 status register to see if we've seen a CODEC READY + * signal from the AC97 codec. + */ + ulStatus = PeekBA0 (devc, BA0_ACSTS2); + if (ulStatus & ACSTS_CRDY) + break; + } + + if (!(ulStatus & ACSTS_CRDY)) + { + cmn_err (CE_WARN, + "Initialize() Never read Codec Ready from second AC97.\n"); + } + } + /* + * Assert the vaid frame signal so that we can start sending commands + * to the AC97 codec. + */ + PokeBA0 (devc, BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN | ACCTL_RSTN); + /* + * Wait until we've sampled input slots 3 and 4 as valid, meaning that + * the codec is pumping ADC data across the AC-link. + */ + for (ulCount = 0; ulCount < 1000; ulCount++) + { + /* + * First, lets wait a short while to let things settle out a bit, + * and to prevent retrying the read too quickly. + */ + oss_udelay (10000); + /* + * Read the input slot valid register and see if input slots 3 and + * 4 are valid yet. + */ + ulStatus = PeekBA0 (devc, BA0_ACISV); + if ((ulStatus & (ACISV_ISV3 | ACISV_ISV4)) == (ACISV_ISV3 | ACISV_ISV4)) + break; + } + /* + * Make sure we sampled valid input slots 3 and 4. If not, then return + * an error. + */ + if ((ulStatus & (ACISV_ISV3 | ACISV_ISV4)) != (ACISV_ISV3 | ACISV_ISV4)) + cmn_err (CE_WARN, "Initialize(%x) Never sampled ISV3 & ISV4 from AC97.\n", + (int) ulStatus); + /* + * Now, assert valid frame and the slot 3 and 4 valid bits. This will + * commense the transfer of digital audio data to the AC97 codec. + */ + PokeBA0 (devc, BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); + + /* Reset the Processor */ + cs461x_reset_processor (devc); + /* Now download the image */ + install_ucode (devc); + + devc->processor_started = 0; + + /* Save the play and capture parameters and write 0 to stop DMA */ + devc->PCTL = READ1L (BA1_PCTL) & 0xFFFF0000; + WRITE1L (BA1_PCTL, devc->PCTL & 0x0000FFFF); + devc->CCTL = READ1L (BA1_CCTL) & 0x0000FFFF; + WRITE1L (BA1_CCTL, devc->CCTL & 0xFFFF0000); + + /* + * Enable interrupts on the part. + */ + PokeBA0 (devc, BA0_HICR, HICR_IEV | HICR_CHGM); + + ulStatus = READ1L (BA1_PFIE); + ulStatus &= ~0x0000f03f; + WRITE1L (BA1_PFIE, ulStatus); /* playback interrupt enable */ + + ulStatus = READ1L (BA1_CIE); + ulStatus &= ~0x0000003f; + ulStatus |= 0x00000001; + WRITE1L (BA1_CIE, ulStatus); /* capture interrupt enable */ + +/****** END OF HARDWARE INIT *****/ + + my_mixer = + ac97_install (&devc->ac97devc, "CS461x AC97 Mixer", ac97_read, ac97_write, + devc, devc->osdev); + + if (my_mixer >= 0) + { + devc->mixer_dev = my_mixer; + if (devc->subsysid == 0x5053) /* SantaCruz has dual codec */ + { + my_mixer2 = + ac97_install (&devc->ac97devc2, "CS4630 AC97 Secondary", + ac97_read2, ac97_write2, devc, devc->osdev); + if (my_mixer2 >= 0) + { + devc->mixer2_dev = my_mixer2; + ac97_write2 (devc, BA0_AC97_6CH_VOL_C_LFE, 0x7FFF); + ac97_write2 (devc, BA0_AC97_6CH_VOL_SURROUND, 0x7FFF); + } + } + } + else + return 0; + + /* Turn on amp on the CS4297 on TB/Videologic */ + if (devc->subsysid == 0x5053) + { + unsigned short u16PinConfig, u16LogicType, u16ValidSlots, u16Idx; + unsigned short u16Status = 0; + int i; + + + ac97_write (devc, 0x26, ac97_read (devc, 0x26) | 0x8000); +#if 0 + /* Set SPDIF */ + PokeBA0 (devc, BA0_ASER_MASTER, 1); /*ASER_MASTER_ME */ + WRITE1L (0x8049, 0x00000001); +#endif + /* + * Set GPIO pin's 7 and 8 so that they are configured for output. + */ + u16PinConfig = ac97_read2 (devc, BA0_AC97_GPIO_PIN_CONFIG); + u16PinConfig &= 0x27F; + ac97_write2 (devc, BA0_AC97_GPIO_PIN_CONFIG, u16PinConfig); + + /* + * Set GPIO pin's 7 and 8 so that they are compatible with CMOS logic. + */ + u16LogicType = ac97_read2 (devc, BA0_AC97_GPIO_PIN_TYPE); + u16LogicType &= 0x27F; + ac97_write2 (devc, BA0_AC97_GPIO_PIN_TYPE, u16LogicType); + + u16ValidSlots = PeekBA0 (devc, BA0_ACOSV); + u16ValidSlots |= 0x200; + PokeBA0 (devc, BA0_ACOSV, u16ValidSlots); + + /* + * Fill slots 12 with the correct value for the GPIO pins. + */ + for (u16Idx = 0x90; u16Idx <= 0x9F; u16Idx++) + { + + /* + * Initialize the fifo so that bits 7 and 8 are on. + * + * Remember that the GPIO pins in bonzo are shifted by 4 bits to + * the left. 0x1800 corresponds to bits 7 and 8. + */ + PokeBA0 (devc, BA0_SERBWP, 0x1800); + + /* + * Make sure the previous FIFO write operation has completed. + */ + for (i = 0; i < 5; i++) + { + u16Status = PeekBA0 (devc, BA0_SERBST); + if (!(u16Status & SERBST_WBSY)) + { + break; + } + oss_udelay (10000); + } + + /* + * Write the serial port FIFO index. + */ + PokeBA0 (devc, BA0_SERBAD, u16Idx); + + /* + * Tell the serial port to load the new value into the FIFO location. + */ + PokeBA0 (devc, BA0_SERBCM, SERBCM_WRC); + } + } + /* Hercules Game Theater Amp */ + if (devc->subsysid == 0x1681) + { + PokeBA0 (devc, BA0_EGPIODR, EGPIODR_GPOE2); /* enable EGPIO2 output */ + PokeBA0 (devc, BA0_EGPIOPTR, EGPIOPTR_GPPT2); /* open-drain on output */ + + } + +#ifdef USE_SG + devc->play_sgbuf = + (unsigned int *) CONTIG_MALLOC (devc->osdev, 4096, MEMLIMIT_32BITS, + &phaddr, devc->play_sgbuf_dma_handle); + if (devc->play_sgbuf == NULL) + { + cmn_err (CE_WARN, + "cs461x: Failed to allocate play scatter/gather buffer.\n"); + return 0; + } + devc->play_sgbuf_phys = phaddr; +#endif + + for (i = 0; i < MAX_PORTC; i++) + { + char tmp_name[100]; + cs461x_portc *portc = &devc->portc[i]; + int caps = ADEV_AUTOMODE; + + if (i == 0) + { + strcpy (tmp_name, devc->chip_name); + caps |= ADEV_DUPLEX; + } + else + { + strcpy (tmp_name, devc->chip_name); + caps |= ADEV_DUPLEX | ADEV_SHADOW; + } + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &cs461x_audio_driver, + sizeof (audiodrv_t), + caps, + AFMT_S16_LE | AFMT_U8, devc, -1)) < 0) + { + adev = -1; + return 0; + } + else + { + if (i == 0) + first_dev = adev; + audio_engines[adev]->portc = portc; + audio_engines[adev]->mixer_dev = my_mixer; + audio_engines[adev]->rate_source = first_dev; + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = 48000; + audio_engines[adev]->min_block = 4096; + audio_engines[adev]->max_block = 4096; + audio_engines[adev]->caps |= PCM_CAP_FREERATE; + portc->open_mode = 0; + portc->audiodev = adev; + portc->audio_enabled = 0; + portc->trigger_bits = 0; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + } + + } + +#ifndef sparc + devc->midi_dev = + oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CS461x", + "CS461x MIDI Port", &cs461x_midi_driver, + sizeof (midi_driver_t), + 0, devc, devc->osdev); +#endif + devc->midi_opened = 0; + return 1; +} + +int +oss_cs461x_attach (oss_device_t * osdev) +{ + unsigned char pci_irq_line, pci_revision, pci_irq_inta; + unsigned short pci_command, vendor, device; + unsigned int ioaddr; + int err; + cs461x_devc *devc; + + DDB (cmn_err (CE_WARN, "Entered CS461x probe routine\n")); +#if 0 + if (cs461x_clkrun_fix) + while ((osdev = (pci_find_class (0x680 << 8, osdev)))) + + { + unsigned char pp; + unsigned int port, control; + unsigned short vendor, device; + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != 0x8086 || device != 0x7113) + continue; + + pci_read_config_byte (osdev, 0x41, &pp); + port = pp << 8; + control = INW (devc->osdev, port + 0x10); + OUTW (devc->osdev, control | 0x2000, port + 0x10); + oss_udelay (100); + OUTW (devc->osdev, control & ~0x2000, port + 0x10); + } +#endif + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != CRYSTAL_VENDOR_ID || device != CRYSTAL_CS461x_ID) + return 0; + + if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) + { + cmn_err (CE_WARN, "Out of memory\n"); + return 0; + } + + devc->osdev = osdev; + osdev->devc = devc; + devc->open_mode = 0; + + oss_pci_byteswap (osdev, 1); + + pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision); + pci_read_config_word (osdev, PCI_COMMAND, &pci_command); + pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line); + pci_read_config_byte (osdev, PCI_INTERRUPT_LINE + 1, &pci_irq_inta); + pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &ioaddr); + devc->bar0addr = ioaddr; + pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_1, &ioaddr); + devc->bar1addr = ioaddr; + pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &devc->subsysid); + + switch (device) + { + case CRYSTAL_CS461x_ID: + devc->chip_name = "Crystal CS461x"; + break; + + case CRYSTAL_CS4610_ID: + devc->chip_name = "Crystal CS4610"; + break; + + case CRYSTAL_CS4615_ID: + devc->chip_name = "Crystal CS4615"; + break; + + default: + devc->chip_name = "CrystalPCI"; + } + + if (devc->subsysid == 0x5053) + { + devc->chip_name = "Crystal CS4630"; + devc->dual_codec = 1; + } + + /* activate the device enable bus master/memory space */ + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + if ((devc->bar0addr == 0) || (devc->bar1addr == 0)) + { + cmn_err (CE_WARN, "Undefined MEMORY I/O address.\n"); + return 0; + } + + if (pci_irq_line == 0) + { + cmn_err (CE_WARN, "IRQ not assigned by BIOS.\n"); + return 0; + } + + /* Map the shared memory area */ + devc->bar0virt = + (unsigned int *) MAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, 0x2000); + devc->bar1virt = + (unsigned int *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, 0x40000); + devc->dwRegister0 = (unsigned int *) devc->bar0virt; + devc->wRegister0 = (unsigned short *) devc->bar0virt; + devc->bRegister0 = (unsigned char *) devc->bar0virt; + devc->dwRegister1 = (unsigned int *) devc->bar1virt; + devc->wRegister1 = (unsigned short *) devc->bar1virt; + devc->bRegister1 = (unsigned char *) devc->bar1virt; + + devc->irq = pci_irq_line; + + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1); + + oss_register_device (osdev, devc->chip_name); + + if ((err = oss_register_interrupts (devc->osdev, 0, cs461xintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); + return 0; + } + + return init_cs461x (devc); /*Detected */ +} + + +int +oss_cs461x_detach (oss_device_t * osdev) +{ + cs461x_devc *devc = (cs461x_devc *) osdev->devc; + + + if (oss_disable_device (osdev) < 0) + return 0; + + PokeBA0 (devc, BA0_HICR, 0x02); /*enable intena */ + cs461x_reset_processor (devc); +#ifdef USE_SG + CONTIG_FREE (devc->osdev, devc->play_sgbuf, 4096, devc->play_sgbuf_dma_handle); +#endif + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + + UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt, 0x2000); + UNMAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, devc->bar1virt, 0x40000); + + devc->bar0addr = 0; + devc->bar1addr = 0; + + oss_unregister_device (devc->osdev); + return 1; +} |