diff options
Diffstat (limited to 'kernel/drv/oss_cs4281/oss_cs4281.c')
-rw-r--r-- | kernel/drv/oss_cs4281/oss_cs4281.c | 1061 |
1 files changed, 1061 insertions, 0 deletions
diff --git a/kernel/drv/oss_cs4281/oss_cs4281.c b/kernel/drv/oss_cs4281/oss_cs4281.c new file mode 100644 index 0000000..45ceaa7 --- /dev/null +++ b/kernel/drv/oss_cs4281/oss_cs4281.c @@ -0,0 +1,1061 @@ +/* + * Purpose: Driver for Crystal PCI audio controller. + */ +/* + * + * 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_cs4281_cfg.h" +#include "midi_core.h" +#include "ac97.h" +#include "oss_pci.h" +#include "cs4281.h" + +#define CRYSTAL_VENDOR_ID 0x1013 +#define CRYSTAL_CS4281_ID 0x6005 + +#if 1 +#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 READL(a) (devc->dwRegister0[a>>2]) +#define WRITEL(a, d) devc->dwRegister0[a>>2] = d +#else +#define WRITEB(a,d) PCI_WRITEB(devc->osdev, d, devc->bRegister0[a]) +#define READB(a) PCI_READB(devc->osdev, devc->bRegister0[a]) +#define WRITEW(a,d) PCI_WRITEW(devc->osdev, d, devc->wRegister0[a>>1]) +#define READW(a) PCI_READW(devc->osdev, devc->wRegister0[a>>1]) +#define READL(a) PCI_READL(devc->osdev, devc->dwRegister0[a>>2]) +#define WRITEL(a, d) PCI_WRITEL(devc->osdev, d, devc->dwRegister0[a>>2]) +#endif + +#ifdef OSS_BIG_ENDIAN +static unsigned int +be_swap (unsigned int x) +{ + return ((x & 0x000000ff) << 24) | + ((x & 0x0000ff00) << 8) | + ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24); +} + +#define LSWAP(x) be_swap(x) +#else +#define LSWAP(x) x +#endif + +#define MAX_PORTC 2 + +typedef struct cs4281_portc +{ + int speed, bits, channels; + int open_mode; + int trigger_bits; + int audio_enabled; + int audiodev; +} +cs4281_portc; + +typedef struct cs4281_devc +{ + oss_device_t *osdev; + char *chip_name; + 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; + volatile unsigned char intr_mask; + 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; + int mixer_dev; + + /* Audio parameters */ + cs4281_portc portc[MAX_PORTC]; + int open_mode; + int fm_attached, mpu_attached; +} +cs4281_devc; + + + +static int +ac97_read (void *devc_, int reg) +{ + cs4281_devc *devc = devc_; + int count; + oss_native_word 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 = READL (BA0_ACSDA); + /* Get the actual AC97 register from the offset */ + WRITEL (BA0_ACCAD, reg); + WRITEL (BA0_ACCDA, 0); + WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_CRW | ACCTL_VFRM | ACCTL_ESYN); + + /* Wait for the read to occur. */ + for (count = 0; count < 500; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (10); + /* + * Now, check to see if the read has completed. + * ACCTL = 460h, DCV should be reset by now and 460h = 17h + */ + status = READL (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 < 500; count++) + { + /* + * Read the AC97 status register. + * ACSTS = Status Register = 464h + */ + status = READL (BA0_ACSTS); + /* + * See if we have valid status. + * VSTS - Valid Status + */ + if (status & ACSTS_VSTS) + break; + + /* + * Wait for a short while. + */ + oss_udelay (10); + } + /* Make sure we got valid status. */ + if (!(status & ACSTS_VSTS)) + { + cmn_err (CE_WARN, "AC97 Read Timedout\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (-1); + } + + /* + * Read the data returned from the AC97 register. + * ACSDA = Status Data Register = 474h + */ + value = READL (BA0_ACSDA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return (value); +} + +static int +ac97_write (void *devc_, int reg, int data) +{ + cs4281_devc *devc = devc_; + int count; + oss_native_word status, flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + WRITEL (BA0_ACCAD, reg); + WRITEL (BA0_ACCDA, data); + WRITEL (BA0_ACCTL, ACCTL_DCV | ACCTL_VFRM | ACCTL_ESYN); + for (count = 0; count < 500; count++) + { + /* First, we want to wait for a short time. */ + oss_udelay (10); + /* Now, check to see if the write has completed. */ + /* ACCTL = 460h, DCV should be reset by now and 460h = 07h */ + status = READL (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 +cs4281intr (oss_device_t * osdev) +{ + cs4281_devc *devc = (cs4281_devc *) osdev->devc; + cs4281_portc *portc; + oss_native_word status, uart_stat; + int i; + int serviced = 0; + +/* Read the Interrupt Status Register */ + status = READL (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)) + { + serviced = 1; + uart_stat = READL (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 = READL (BA0_MIDRP); + + if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, d); + uart_stat = READL (BA0_MIDSR); + } + } +/* Audio interrupt handling */ + if (status & (HISR_INTENA | HISR_DMAI)) + for (i = 0; i < MAX_PORTC; i++) + { + portc = &devc->portc[i]; + if ((status & HISR_DMA0) && (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + dmap_t *dmapout = audio_engines[portc->audiodev]->dmap_out; + unsigned int currdac; + int n; + + serviced = 1; + READL (BA0_HDSR0); /* ack the DMA interrupt */ + currdac = READL (BA0_DCA0) - dmapout->dmabuf_phys; + currdac /= dmapout->fragment_size; + n = 0; + while (dmap_get_qhead (dmapout) != currdac + && n++ < dmapout->nfrags) + oss_audio_outputintr (portc->audiodev, 1); + } + + if ((status & HISR_DMA1) && (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + dmap_t *dmapin = audio_engines[portc->audiodev]->dmap_in; + unsigned int curradc; + int n; + + serviced = 1; + READL (BA0_HDSR1); /* ack the DMA interrupt */ + curradc = READL (BA0_DCA1) - dmapin->dmabuf_phys; + curradc /= dmapin->fragment_size; + n = 0; + while (dmap_get_qtail (dmapin) != curradc && n++ < dmapin->nfrags) + oss_audio_inputintr (portc->audiodev, 0); + } + WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM); + } + return serviced; +} + +static int +cs4281_audio_set_rate (int dev, int arg) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + if (arg == 0) + return portc->speed; + if (arg > 48000) + arg = 48000; + if (arg < 6023) + arg = 6023; + portc->speed = arg; + return portc->speed; +} + +static short +cs4281_audio_set_channels (int dev, short arg) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + if ((arg != 1) && (arg != 2)) + return portc->channels; + portc->channels = arg; + return portc->channels; +} + +static unsigned int +cs4281_audio_set_format (int dev, unsigned int arg) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + if (arg == 0) + return portc->bits; + if (!(arg & (AFMT_U8 | AFMT_S16_LE))) + return portc->bits; + portc->bits = arg; + return portc->bits; +} + +/*ARGSUSED*/ +static int +cs4281_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void cs4281_audio_trigger (int dev, int state); +static void +cs4281_audio_reset (int dev) +{ + cs4281_audio_trigger (dev, 0); +} + +static void +cs4281_audio_reset_input (int dev) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +cs4281_audio_reset_output (int dev) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + cs4281_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +cs4281_audio_open (int dev, int mode, int open_flags) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + cs4281_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; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +cs4281_audio_close (int dev, int mode) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + cs4281_devc *devc = audio_engines[dev]->devc; + cs4281_audio_reset (dev); + portc->open_mode = 0; + devc->open_mode &= ~mode; + portc->audio_enabled &= ~mode; +} + +/*ARGSUSED*/ +static void +cs4281_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; +} + +/*ARGSUSED*/ +static void +cs4281_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cs4281_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +static void +cs4281_audio_trigger (int dev, int state) +{ + cs4281_devc *devc = audio_engines[dev]->devc; + cs4281_portc *portc = audio_engines[dev]->portc; + oss_native_word tmp1; + oss_native_word flags; + 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)) + { + /* Clear DMA0 channel Mask bit. Start Playing. */ + tmp1 = READL (BA0_DCR0) & ~DCRn_MSK; /*enable DMA */ + WRITEL (BA0_DCR0, tmp1); /* (154h) */ + WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM); /*enable intr */ + portc->trigger_bits |= PCM_ENABLE_OUTPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + tmp1 = READL (BA0_DCR0) & ~DCRn_MSK; + WRITEL (BA0_DCR0, tmp1 | DCRn_MSK); + } + } + } + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + /* Clear DMA1 channel Mask bit. Start recording. */ + tmp1 = READL (BA0_DCR1) & ~DCRn_MSK; + WRITEL (BA0_DCR1, tmp1); /* (15ch) */ + WRITEL (BA0_HICR, HICR_IEV | HICR_CHGM); /*Set INTENA=1. */ + portc->trigger_bits |= PCM_ENABLE_INPUT; + } + } + else + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + tmp1 = READL (BA0_DCR1) & ~DCRn_MSK; + WRITEL (BA0_DCR1, tmp1 | DCRn_MSK); + } + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static int +cs4281_rate (oss_native_word rate) +{ + int val = 0; + + switch (rate) + { + case 8000: + val = 5; + break; + case 11025: + val = 4; + break; + case 16000: + val = 3; + break; + case 22050: + val = 2; + break; + case 44100: + val = 1; + break; + case 48000: + val = 0; + break; + default: + val = 1536000 / rate; + break; + } + return val; +} + +/*ARGSUSED*/ +static int +cs4281_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + cs4281_devc *devc = audio_engines[dev]->devc; + cs4281_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_in; + int count = dmap->bytes_in_use; + oss_native_word recordFormat; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + /* set the record rate */ + WRITEL (BA0_ADCSR, cs4281_rate (portc->speed)); /* (748h) */ + /* Start with defaults for the record format */ + /* reg & modify them for the current case. */ + recordFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE; + if (portc->channels == 1) /* If mono, */ + recordFormat |= DMRn_MONO; /* Turn on mono bit. */ + if (portc->bits == 8) /* If 8-bit, */ + recordFormat |= (DMRn_SIZE8 | DMRn_USIGN); /* turn on 8bit/unsigned. */ + WRITEL (BA0_DMR1, recordFormat); + /* set input gain to 0db */ + /* ac97_write(devc, BA0_AC97_RECORD_GAIN, 0x0808); */ + if (portc->channels == 2) /* If stereo, */ + count /= 2; /* halve DMA count(stereo); */ + if (portc->bits == 16) /* If 16-bit, */ + count /= 2; + /* Set the physical play buffer address DMA1 Base & Current. */ + WRITEL (BA0_DBA1, dmap->dmabuf_phys); /* (118h) */ + /* Set the sample count(-1) in DMA Base Count register 1. */ + WRITEL (BA0_DBC1, count - 1); + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +cs4281_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + cs4281_devc *devc = audio_engines[dev]->devc; + cs4281_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_out; + int count = dmap->bytes_in_use; + oss_native_word playFormat; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + /* Set the sample rate converter */ + WRITEL (BA0_DACSR, cs4281_rate (portc->speed)); + playFormat = DMRn_DMA | DMRn_AUTO | DMRn_TR_READ | (1 << 6); + if (portc->channels == 1) /* If stereo, */ + playFormat |= DMRn_MONO; /* Turn on mono bit. */ + if (portc->bits == 8) /* If 16-bit, */ + playFormat |= (DMRn_SIZE8 | DMRn_USIGN); /* turn on 8-bit/unsigned. */ + WRITEL (BA0_DMR0, playFormat); /* (150h) */ + if (portc->channels == 2) /* If stereo, */ + count /= 2; /* halve DMA count(stereo); */ + if (portc->bits == 16) /* If 16-bit, */ + count /= 2; + /* Set the physical play buffer address DMA0 Base & Current. */ + WRITEL (BA0_DBA0, dmap->dmabuf_phys & ~0x3); /* (118h) */ + /* Set the sample count(-1) in DMA Base Count register 0. */ + WRITEL (BA0_DBC0, count - 1); + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static int +cs4281_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + cs4281_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 = READL (BA0_DCA0); + } + if (direction == PCM_ENABLE_INPUT) + { + ptr = READL (BA0_DCA1); + } + ptr -= dmap->dmabuf_phys; + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return ptr; +} + +static audiodrv_t cs4281_audio_driver = { + cs4281_audio_open, + cs4281_audio_close, + cs4281_audio_output_block, + cs4281_audio_start_input, + cs4281_audio_ioctl, + cs4281_audio_prepare_for_input, + cs4281_audio_prepare_for_output, + cs4281_audio_reset, + NULL, + NULL, + cs4281_audio_reset_input, + cs4281_audio_reset_output, + cs4281_audio_trigger, + cs4281_audio_set_rate, + cs4281_audio_set_format, + cs4281_audio_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, /* cs4281_alloc_buffer, */ + NULL, /* cs4281_free_buffer, */ + NULL, + NULL, + cs4281_get_buffer_pointer +}; + +/***********************MIDI PORT ROUTINES ****************/ +/*ARGSUSED*/ +static int +cs4281_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, + oss_midi_outputintr_t outputintr) +{ + cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc; + if (devc->midi_opened) + { + return OSS_EBUSY; + } + + devc->midi_input_intr = inputbyte; + devc->midi_opened = mode; + /* first reset the MIDI port */ + WRITEL (BA0_MIDCR, 0x10); + WRITEL (BA0_MIDCR, 0x00); + /* Now check if we're in Read or Write mode */ + if (mode & OPEN_READ) + { + /* enable MIDI Input intr and receive enable */ + WRITEL (BA0_MIDCR, MIDCR_RXE | MIDCR_RIE); + } + + if (mode & OPEN_WRITE) + { + /* enable MIDI transmit enable */ + WRITEL (BA0_MIDCR, MIDCR_TXE); + } + return 0; +} + +/*ARGSUSED*/ +static void +cs4281_midi_close (int dev, int mode) +{ + cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc; +/* Reset the device*/ + WRITEL (BA0_MIDCR, 0x10); + WRITEL (BA0_MIDCR, 0x00); + devc->midi_opened = 0; +} + +static int +cs4281_midi_out (int dev, unsigned char midi_byte) +{ + cs4281_devc *devc = (cs4281_devc *) midi_devs[dev]->devc; + unsigned char uart_stat = READL (BA0_MIDSR); +/* Check if Transmit buffer full flag is set - if so return */ + if ((uart_stat & MIDSR_TBF)) + return 0; +/* Now put the MIDI databyte in the write port */ + WRITEL (BA0_MIDWP, midi_byte); + return 1; +} + +/*ARGSUSED*/ +static int +cs4281_midi_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t cs4281_midi_driver = { + cs4281_midi_open, + cs4281_midi_close, + cs4281_midi_ioctl, + cs4281_midi_out +}; + +static int +init_cs4281 (cs4281_devc * devc) +{ + + int my_mixer, i; + oss_native_word dwAC97SlotID, tmp1; + int first_dev = 0; +/****************BEGIN HARDWARE INIT*****************/ + /* Setup CFLR */ + tmp1 = READL (BA0_CFLR); + if (tmp1 != 0x01) + { + WRITEL (BA0_CFLR, 0x01); /*set up AC97 mode */ + tmp1 = READL (BA0_CFLR); + if (tmp1 != 0x01) + { + tmp1 = READL (BA0_CWPR); + if (tmp1 != 0x4281) + WRITEL (BA0_CWPR, 0x4281); + tmp1 = READL (BA0_CWPR); + if (tmp1 != 0x4281) + { + cmn_err (CE_WARN, "Resetting AC97 failed\n"); + return OSS_EIO; + } + WRITEL (BA0_CFLR, 0x1); + tmp1 = READL (BA0_CFLR); + if (tmp1 != 0x1) + { + cmn_err (CE_WARN, "Resetting AC97 still fails\n"); + return OSS_EIO; + } + } + } + /* Setup the FM and Joystick trap address for Legacy emulation */ + WRITEL (BA0_IOTCR, 0x1); + WRITEL (BA0_IOTFM, 0xc0030388); + WRITEL (BA0_IOTGP, 0xc0070200); + /**************************************** */ + /* Set up the Sound System Configuration */ + /**************************************** */ + /* Set the 'Configuration Write Protect' register */ + /* to 4281h. Allows vendor-defined configuration */ + /* space between 0e4h and 0ffh to be written. */ + WRITEL (BA0_CWPR, 0x4281); /* (3e0h) */ + if ((tmp1 = READL (BA0_SERC1)) != (SERC1_SO1EN | SERC1_SO1F_AC97)) + { + cmn_err (CE_WARN, "SERC1: AC97 check failed\n"); + return OSS_EIO; + } + /* setup power management to full power */ + WRITEL (BA0_SSPM, 0x7E); + /* 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. */ + WRITEL (BA0_CLKCR1, 0); /* (400h) */ + WRITEL (BA0_SERMC, 0); /* (420h) */ + /* (1) 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. */ + WRITEL (BA0_ACCTL, 0); + oss_udelay (50); + WRITEL (BA0_SPMC, 0); /* (3ech) */ + oss_udelay (50); + WRITEL (BA0_SPMC, SPMC_RSTN); + oss_udelay (50000); + WRITEL (BA0_SERMC, SERMC_PTC_AC97 | SERMC_MSPE | 0x10000); + /* (3) Turn on the Sound System Clocks. */ + WRITEL (BA0_CLKCR1, CLKCR1_DLLP); /* (400h) */ + /* Wait for the PLL to stabilize. */ + oss_udelay (50000); + /* Turn on clocking of the core (CLKCR1(400h) = 0x00000030) */ + WRITEL (BA0_CLKCR1, CLKCR1_DLLP | CLKCR1_SWCE); + /* (5) Wait for clock stabilization. */ + for (tmp1 = 0; tmp1 < 100; tmp1++) + { + if (READL (BA0_CLKCR1) & CLKCR1_DLLRDY) + break; + oss_udelay (50000); + } + if (!(READL (BA0_CLKCR1) & CLKCR1_DLLRDY)) + { + cmn_err (CE_WARN, "DLLRDY Clock not ready\n"); + return OSS_EIO; + } + /* (6) Enable ASYNC generation. */ + WRITEL (BA0_ACCTL, ACCTL_ESYN); /* (460h) */ + /* 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.) */ + /* (7) Wait for the codec ready signal from the AC97 codec. */ + for (tmp1 = 0; tmp1 < 100; tmp1++) + { + /* Delay a mil to let things settle out and */ + /* to prevent retrying the read too quickly. */ + if (READL (BA0_ACSTS) & ACSTS_CRDY) /* If ready, (464h) */ + break; /* exit the 'for' loop. */ + oss_udelay (50000); + } + if (!(READL (BA0_ACSTS) & ACSTS_CRDY)) /* If never came ready, */ + { + cmn_err (CE_WARN, "AC97 not ready\n"); + return OSS_EIO; /* exit initialization. */ + } + /* (8) Assert the 'valid frame' signal so we can */ + /* begin sending commands to the AC97 codec. */ + WRITEL (BA0_ACCTL, ACCTL_VFRM | ACCTL_ESYN); /* (460h) */ + + /* (11) Wait until we've sampled input slots 3 & 4 as valid, meaning */ + /* that the codec is pumping ADC data across the AC link. */ + for (tmp1 = 0; tmp1 < 100; tmp1++) + { + /* Read the input slot valid register; See */ + /* if input slots 3 and 4 are valid yet. */ + if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) == /* (474h) */ + (ACISV_ISV3 | ACISV_ISV4)) + break; /* Exit the 'for' if slots are valid. */ + oss_udelay (50000); + } + /* If we never got valid data, exit initialization. */ + if ((READL (BA0_ACISV) & (ACISV_ISV3 | ACISV_ISV4)) != + (ACISV_ISV3 | ACISV_ISV4)) + { + cmn_err (CE_WARN, "AC97 Slot not valid\n"); + return OSS_EIO; /* If no valid data, exit initialization. */ + } + /* (12) Start digital data transfer of audio data to the codec. */ + WRITEL (BA0_ACOSV, ACOSV_SLV3 | ACOSV_SLV4); /* (468h) */ + + /* For playback, we map AC97 slot 3 and 4(Left */ + /* & Right PCM playback) to DMA Channel 0. */ + /* Set the fifo to be 31 bytes at offset zero. */ + dwAC97SlotID = 0x01001F00; /* FCR0.RS[4:0]=1(=>slot4, right PCM playback). */ + /* FCR0.LS[4:0]=0(=>slot3, left PCM playback). */ + /* FCR0.SZ[6-0]=15; FCR0.OF[6-0]=0. */ + WRITEL (BA0_FCR0, dwAC97SlotID); /* (180h) */ + WRITEL (BA0_FCR0, dwAC97SlotID | FCRn_FEN); /* Turn on FIFO Enable. */ + /* For capture, we map AC97 slot 10 and 11(Left */ + /* and Right PCM Record) to DMA Channel 1. */ + /* Set the fifo to be 31 bytes at offset 32. */ + dwAC97SlotID = 0x0B0A1F20; /* FCR1.RS[4:0]=11(=>slot11, right PCM record). */ + /* FCR1.LS[4:0]=10(=>slot10, left PCM record). */ + /* FCR1.SZ[6-0]=15; FCR1.OF[6-0]=16. */ + WRITEL (BA0_FCR1, dwAC97SlotID); /* (184h) */ + WRITEL (BA0_FCR1, dwAC97SlotID | FCRn_FEN); /* Turn on FIFO Enable. */ + /* Map the Playback SRC to the same AC97 slots(3 & 4-- */ + /* --Playback left & right)as DMA channel 0. */ + /* Map the record SRC to the same AC97 slots(10 & 11-- */ + /* -- Record left & right) as DMA channel 1. */ + dwAC97SlotID = 0x0b0a0100; /*SCRSA.PRSS[4:0]=1(=>slot4, right PCM playback). */ + /*SCRSA.PLSS[4:0]=0(=>slot3, left PCM playback). */ + /*SCRSA.CRSS[4:0]=11(=>slot11, right PCM record) */ + /*SCRSA.CLSS[4:0]=10(=>slot10, left PCM record). */ + WRITEL (BA0_SRCSA, dwAC97SlotID); /* (75ch) */ + /* Set 'Half Terminal Count Interrupt Enable' and 'Terminal */ + /* Count Interrupt Enable' in DMA Control Registers 0 & 1. */ + /* Set 'MSK' flag to 1 to keep the DMA engines paused. */ + tmp1 = (DCRn_HTCIE | DCRn_TCIE | DCRn_MSK); /* (00030001h) */ + WRITEL (BA0_DCR0, tmp1); /* (154h) */ + WRITEL (BA0_DCR1, tmp1); /* (15ch) */ + /* Set 'Auto-Initialize Control' to 'enabled'; For playback, */ + /* set 'Transfer Type Control'(TR[1:0]) to 'read transfer', */ + /* for record, set Transfer Type Control to 'write transfer'. */ + /* All other bits set to zero; Some will be changed @ transfer start. */ + tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_READ); /* (20000018h) */ + WRITEL (BA0_DMR0, tmp1); /* (150h) */ + tmp1 = (DMRn_DMA | DMRn_AUTO | DMRn_TR_WRITE); /* (20000014h) */ + WRITEL (BA0_DMR1, tmp1); /* (158h) */ + /* Enable DMA interrupts generally, and */ + /* DMA0 & DMA1 interrupts specifically. */ + tmp1 = READL (BA0_HIMR) & 0x7fbbfcff; + WRITEL (BA0_HIMR, tmp1); + /* set up some volume defaults */ + WRITEL (BA0_PPLVC, 0x0808); + WRITEL (BA0_PPRVC, 0x0808); + WRITEL (BA0_FMLVC, 0x0); + WRITEL (BA0_FMRVC, 0x0); +/****** END OF HARDWARE INIT *****/ + + + my_mixer = + ac97_install (&devc->ac97devc, "CS4281 AC97 Mixer", ac97_read, ac97_write, + devc, devc->osdev); + if (my_mixer < 0) + { + return 0; + } + + devc->mixer_dev = my_mixer; + + /* ac97_write(devc, 0x26, ac97_read(devc, 0x26) | 0x8000); */ + for (i = 0; i < MAX_PORTC; i++) + { + int adev; + int caps = 0; + cs4281_portc *portc = &devc->portc[i]; + char tmp_name[100]; + strcpy (tmp_name, devc->chip_name); + + if (i == 0) + { + caps = ADEV_AUTOMODE | ADEV_DUPLEX; + strcpy (tmp_name, devc->chip_name); + } + else + { + caps = ADEV_AUTOMODE | ADEV_DUPLEX | ADEV_SHADOW; + strcpy (tmp_name, devc->chip_name); + } + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &cs4281_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]->rate_source = first_dev; + audio_engines[adev]->mixer_dev = my_mixer; + audio_engines[adev]->min_rate = 6023; + audio_engines[adev]->max_rate = 48000; + audio_engines[adev]->caps |= PCM_CAP_FREERATE; + portc->open_mode = 0; + portc->audiodev = adev; + portc->audio_enabled = 0; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + } + } + + devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "CS4281", "CS4281 MIDI Port", &cs4281_midi_driver, sizeof (midi_driver_t), + 0, devc, devc->osdev); + devc->midi_opened = 0; + return 1; +} + +int +oss_cs4281_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; + cs4281_devc *devc; + + DDB (cmn_err (CE_WARN, "Entered CS4281 probe routine\n")); + + 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_CS4281_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; + + 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; + + /* 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, 4 * 1024); + devc->bar1virt = + (unsigned int *) MAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, + 1024 * 1024); + devc->dwRegister0 = devc->bar0virt; + devc->wRegister0 = (unsigned short *) devc->bar0virt; + devc->bRegister0 = (unsigned char *) devc->bar0virt; + devc->dwRegister1 = devc->bar1virt; + devc->wRegister1 = (unsigned short *) devc->bar1virt; + devc->bRegister1 = (unsigned char *) devc->bar1virt; + + devc->chip_name = "CS4281"; + devc->irq = pci_irq_line; + devc->open_mode = 0; + + 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, cs4281intr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); + return 0; + } + return init_cs4281 (devc); /*Detected */ +} + + +int +oss_cs4281_detach (oss_device_t * osdev) +{ + cs4281_devc *devc = (cs4281_devc *) osdev->devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + WRITEL (BA0_HICR, 0x02); /*enable intena */ + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + + UNMAP_PCI_MEM (devc->osdev, 0, devc->bar0addr, devc->bar0virt, 4 * 1024); + UNMAP_PCI_MEM (devc->osdev, 1, devc->bar1addr, devc->bar1virt, 1024 * 1024); + + devc->bar0addr = 0; + devc->bar1addr = 0; + + oss_unregister_device (devc->osdev); + return 1; +} |