diff options
Diffstat (limited to 'kernel/drv/oss_audiopci/oss_audiopci.c')
-rw-r--r-- | kernel/drv/oss_audiopci/oss_audiopci.c | 1536 |
1 files changed, 1536 insertions, 0 deletions
diff --git a/kernel/drv/oss_audiopci/oss_audiopci.c b/kernel/drv/oss_audiopci/oss_audiopci.c new file mode 100644 index 0000000..f608cf8 --- /dev/null +++ b/kernel/drv/oss_audiopci/oss_audiopci.c @@ -0,0 +1,1536 @@ +/* + * Purpose: Creative/Ensoniq AudioPCI driver (ES1370 "CONCERT" ASIC and AKM4531 codec/mixer) + */ +/* + * + * 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_audiopci_cfg.h" +#include "audiopci.h" +#include "midi_core.h" +#include "oss_pci.h" + +#define ENSONIQ_VENDOR_ID 0x1274 +#define ENSONIQ_VENDOR_ID2 0x1275 +#define ENSONIQ_AUDIOPCI 0x5000 + +#define MAX_PORTC 2 + +typedef struct apci_portc +{ + + /* Audio parameters */ + int audiodev; + int open_mode; + int trigger_bits; + int audio_enabled; + int speed, bits, channels; + int atype; /* 0=DAC/ADC, 1=Synth */ + int speedsel; +} apci_portc; + +typedef struct apci_devc +{ + oss_device_t *osdev; + oss_mutex_t mutex, low_mutex; + oss_native_word base; + int irq; + char *chip_name; + + /* Mixer parameters */ + int *levels; + unsigned char ak_regs[0x20]; /* Current mixer register values */ + int recdevs; + int micbias, micboost; + unsigned char outsw1, outsw2; + + /* Audio parameters */ + int irq_allocated; + apci_portc portc[MAX_PORTC]; + +/* + * MIDI + */ + int midi_opened; + int midi_dev; + oss_midi_inputbyte_t midi_input_intr; +} apci_devc; + + +/* + * Initial values to be written into the mixer registers of AK4531 codec. + */ +static const unsigned char ak_reg_init[0x20] = { + /* Mute all inputs/outputs initially */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, /* 00 to 07 */ + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, /* 08 to 0f */ + 0x7f, 0x3d, 0x55, 0x26, 0xf7, 0xef, 0x03, 0x00, /* 10 to 17 */ + 0x00, 0x01 /* 18 to 19 */ +}; + +static void +ak_write (apci_devc * devc, int reg, int value) +{ + int i; + + if (reg < 0 || reg > 0x19) + return; + + value &= 0xff; + devc->ak_regs[reg] = (unsigned char) value; + + /* Wait until the codec is ready */ + for (i = 0; i < 0x40000; i++) + if (!(INB (devc->osdev, devc->base + CONC_bCODECSTAT_OFF) & 0x01)) + break; + oss_udelay (10); + OUTW (devc->osdev, (reg << 8) | value, devc->base + CONC_wCODECCTL_OFF); + oss_udelay (10); +} + +static void +apci_writemem (apci_devc * devc, int page, int offs, int data) +{ + int tmp; + + tmp = INL (devc->osdev, devc->base + 0xc); + OUTL (devc->osdev, page, devc->base + 0xc); /* Select memory page */ + OUTL (devc->osdev, data, devc->base + offs); + OUTL (devc->osdev, tmp, devc->base + 0xc); /* Select the original memory page */ +} + +static unsigned int +apci_readmem (apci_devc * devc, int page, int offs) +{ + unsigned int val; + + OUTL (devc->osdev, page, devc->base + 0xc); /* Select memory page */ + val = INL (devc->osdev, devc->base + offs); + return val; +} + +#define bmast_off(x) +#define bmast_on(x) + +static int +apci_set_recmask (apci_devc * devc, int mask) +{ + unsigned char tmp; + + mask &= REC_DEVS; + +/* + * Set lch input mixer SW 1 register + */ + tmp = 0; + if (mask & SOUND_MASK_ALTPCM) + tmp |= 0x40; + if (mask & SOUND_MASK_LINE) + tmp |= 0x10; + if (mask & SOUND_MASK_CD) + tmp |= 0x04; + if (mask & SOUND_MASK_MIC) + tmp |= 0x01; + ak_write (devc, 0x12, tmp); + +/* + * Set rch input mixer SW 1 register + */ + tmp = 0; + if (mask & SOUND_MASK_ALTPCM) + tmp |= 0x20; + if (mask & SOUND_MASK_LINE) + tmp |= 0x08; + if (mask & SOUND_MASK_CD) + tmp |= 0x02; + if (mask & SOUND_MASK_MIC) + tmp |= 0x01; + ak_write (devc, 0x13, tmp); + +/* + * Set lch input mixer SW 2 register + */ + tmp = 0; + if (mask & SOUND_MASK_LINE2) + tmp |= 0x40; + if (mask & SOUND_MASK_LINE3) + tmp |= 0x20; + if (mask & SOUND_MASK_LINE1) + tmp |= 0x10; + if (mask & SOUND_MASK_MIC) + tmp |= 0x80; + ak_write (devc, 0x14, tmp); + +/* + * Set rch input mixer SW 2 register + */ + tmp = 0; + if (mask & SOUND_MASK_LINE2) + tmp |= 0x40; + if (mask & SOUND_MASK_LINE3) + tmp |= 0x20; + if (mask & SOUND_MASK_LINE1) + tmp |= 0x08; + if (mask & SOUND_MASK_MIC) + tmp |= 0x80; + ak_write (devc, 0x15, tmp); + + return devc->recdevs = mask; +} + +/*ARGSUSED*/ +static void +change_bits (apci_devc * devc, unsigned char *regval, int dev, int chn, + int newval) +{ + unsigned char mask; + int shift; + int mute; + int mutemask; + int set_mute_bit; + + set_mute_bit = (newval == 0); + + if (ak_mix_devices[dev][chn].polarity == 1) /* Reverse */ + newval = 100 - newval; + + mask = (1 << ak_mix_devices[dev][chn].nbits) - 1; + shift = ak_mix_devices[dev][chn].bitpos; + +#if 0 + newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ + *regval &= ~(mask << shift); /* Clear bits */ + *regval |= (newval & mask) << shift; /* Set new value */ +#else + if (ak_mix_devices[dev][chn].mutepos == 8) + { /* if there is no mute bit */ + mute = 0; /* No mute bit; do nothing special */ + mutemask = ~0; /* No mute bit; do nothing special */ + } + else + { + mute = (set_mute_bit << ak_mix_devices[dev][chn].mutepos); + mutemask = ~(1 << ak_mix_devices[dev][chn].mutepos); + } + + newval = (int) ((newval * mask) + 50) / 100; /* Scale it */ + *regval &= (~(mask << shift)) & (mutemask); /* Clear bits */ + *regval |= ((newval & mask) << shift) | mute; /* Set new value */ +#endif +} + +static int +apci_mixer_get (apci_devc * devc, int dev) +{ + if (!((1 << dev) & MIXER_DEVS)) + return OSS_EINVAL; + + return devc->levels[dev]; +} + +static int +apci_mixer_set (apci_devc * devc, int dev, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + int retvol; + + int regoffs; + unsigned char val; + + if (dev > 31) + return OSS_EINVAL; + + if (!(MIXER_DEVS & (1 << dev))) + return OSS_EINVAL; + + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + if (ak_mix_devices[dev][RIGHT_CHN].regno == 0xff) /* Mono control */ + right = left; + + retvol = left | (right << 8); + +#if 1 + /* Scale volumes */ + left = mix_cvt[left]; + right = mix_cvt[right]; + + /* Scale it again */ + left = mix_cvt[left]; + right = mix_cvt[right]; +#endif + + if (ak_mix_devices[dev][LEFT_CHN].regno == 0xff) + return OSS_EINVAL; + + devc->levels[dev] = retvol; + + /* + * Set the left channel + */ + + regoffs = ak_mix_devices[dev][LEFT_CHN].regno; + val = 0; + change_bits (devc, &val, dev, LEFT_CHN, left); + ak_write (devc, regoffs, val); + + /* + * Set the right channel + */ + + if (ak_mix_devices[dev][RIGHT_CHN].regno == 0xff) + return retvol; /* Was just a mono channel */ + + regoffs = ak_mix_devices[dev][RIGHT_CHN].regno; + val = 0; + change_bits (devc, &val, dev, RIGHT_CHN, right); + ak_write (devc, regoffs, val); + + return retvol; +} + +static void +apci_mixer_reset (apci_devc * devc) +{ + int i; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (MIXER_DEVS & (1 << i)) + apci_mixer_set (devc, i, devc->levels[i]); + apci_set_recmask (devc, SOUND_MASK_MIC); + devc->outsw1 = ak_reg_init[0x10]; + devc->outsw2 = ak_reg_init[0x11]; +} + +/*ARGSUSED*/ +static int +apci_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + apci_devc *devc = mixer_devs[dev]->devc; + + if (((cmd >> 8) & 0xff) == 'M') + { + int val; + + if (IOC_IS_OUTPUT (cmd)) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + val = *arg; + return *arg = apci_set_recmask (devc, val); + break; + + default: + val = *arg; + return *arg = apci_mixer_set (devc, cmd & 0xff, val); + } + else + switch (cmd & 0xff) /* + * Return parameters + */ + { + + case SOUND_MIXER_RECSRC: + return *arg = devc->recdevs; + break; + + case SOUND_MIXER_DEVMASK: + return *arg = MIXER_DEVS; + break; + + case SOUND_MIXER_STEREODEVS: + return *arg = STEREO_DEVS; + break; + + case SOUND_MIXER_RECMASK: + return *arg = REC_DEVS; + break; + + case SOUND_MIXER_CAPS: + return *arg = SOUND_CAP_EXCL_INPUT; + break; + + default: + return *arg = apci_mixer_get (devc, cmd & 0xff); + } + } + else + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static int +getmute (apci_devc * devc, int offs, unsigned char bits, int value) +{ + unsigned char tmp; + + tmp = (offs == 0x10) ? devc->outsw1 : devc->outsw2; + + tmp &= bits; + return (tmp == 0); /* Note! inverted polarity */ +} + +static int +setmute (apci_devc * devc, int offs, unsigned char bits, int value) +{ + unsigned char tmp; + + value = !value; /* Inverted polarity (now 0=mute) */ + + tmp = (offs == 0x10) ? devc->outsw1 : devc->outsw2; + + tmp &= ~bits; /* Mask old bits */ + if (value) + tmp |= bits; + + ak_write (devc, offs, tmp); + if (offs == 0x10) + devc->outsw1 = tmp; + else + devc->outsw2 = tmp; + + return !value; +} + +static int +apci_outsw (int dev, int ctrl, unsigned int cmd, int value) +{ +/* + * Access function for AudioPCI mixer extension bits + */ + apci_devc *devc = mixer_devs[dev]->devc; + + if (cmd == SNDCTL_MIX_READ) + { + value = 0; + switch (ctrl) + { + case 1: /* 20 dB microphone boost */ + value = devc->micboost; + break; + + case 2: /* Microphone phantom power */ + value = devc->micbias; + break; + + case 10: /* Pcm mute */ + value = getmute (devc, 0x11, 0x0c, value); + break; + + case 11: /* Pcm2 mute */ + value = getmute (devc, 0x10, 0x60, value); + break; + + case 12: /* Mic mute */ + value = getmute (devc, 0x10, 0x01, value); + break; + + case 13: /* CD mute */ + value = getmute (devc, 0x10, 0x06, value); + break; + + case 14: /* Line mute */ + value = getmute (devc, 0x10, 0x18, value); + break; + + case 15: /* Line1 mute */ + value = getmute (devc, 0x11, 0x30, value); + break; + + case 16: /* Line2 mute */ + value = getmute (devc, 0x11, 0x01, value); + break; + + case 17: /* Line3 mute */ + value = getmute (devc, 0x11, 0x02, value); + break; + + case 18: /*Separate output enable for the synth device (XCTL0) */ + value = + ((INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & 0x01) != 0); + break; + + default: + return OSS_EINVAL; + } + + return value; + } + + if (cmd == SNDCTL_MIX_WRITE) + { + int tmp; + + if (value) + value = 1; + + switch (ctrl) + { + case 1: /* 20 dB microphone boost */ + devc->micboost = value; + ak_write (devc, 0x19, value); + break; + + case 2: /* Microphone phantom power */ + devc->micbias = value; + /* Delay the actual change until next recording */ + break; + + case 10: /* Pcm mute */ + value = setmute (devc, 0x11, 0x0c, value); + break; + + case 11: /* Pcm2 mute */ + value = setmute (devc, 0x10, 0x60, value); + break; + + case 12: /* Mic mute */ + value = setmute (devc, 0x10, 0x01, value); + break; + + case 13: /* CD mute */ + value = setmute (devc, 0x10, 0x06, value); + break; + + case 14: /* Line mute */ + value = setmute (devc, 0x10, 0x18, value); + break; + + case 15: /* Line1 mute */ + value = setmute (devc, 0x11, 0x30, value); + break; + + case 16: /* Line2 mute */ + value = setmute (devc, 0x11, 0x01, value); + break; + + case 17: /* Line3 mute */ + value = setmute (devc, 0x11, 0x02, value); + break; + + case 18: /*Separate output enable for the synth device (XCTL0) */ + tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF); + if (value) + { + OUTB (devc->osdev, tmp | 0x01, devc->base + CONC_bMISCCTL_OFF); + } + else + { + OUTB (devc->osdev, tmp & ~0x01, devc->base + CONC_bMISCCTL_OFF); + } + break; + + default: + return OSS_EINVAL; + } + + return value; + } + + return OSS_EINVAL; +} + +static int +apci_mix_init (int dev) +{ + int group, err; + + if ((group = mixer_ext_create_group (dev, 0, "APCI_EXTMIC")) < 0) + return group; + + if ((err = mixer_ext_create_control (dev, group, 1, apci_outsw, MIXT_ONOFF, + "APCI_MICBOOST", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 2, apci_outsw, MIXT_ONOFF, + "APCI_MICBIAS", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((group = mixer_ext_create_group (dev, 0, "APCI_MUTE")) < 0) + return group; + + if ((err = mixer_ext_create_control (dev, group, 10, apci_outsw, + MIXT_ONOFF, + "APCI_PCMMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 11, apci_outsw, + MIXT_ONOFF, + "APCI_PCM2MUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 12, apci_outsw, + MIXT_ONOFF, + "APCI_MICMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 13, apci_outsw, + MIXT_ONOFF, + "APCI_CDMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 14, apci_outsw, + MIXT_ONOFF, + "APCI_LINEMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 15, apci_outsw, + MIXT_ONOFF, + "APCI_LINE1MUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 16, apci_outsw, + MIXT_ONOFF, + "APCI_LINE2MUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, 17, apci_outsw, + MIXT_ONOFF, + "APCI_LINE3MUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((group = mixer_ext_create_group (dev, 0, "APCI_4CHAN")) < 0) + return group; + + if ((err = mixer_ext_create_control (dev, group, 18, apci_outsw, + MIXT_ONOFF, + "APCI4CH_ENABLE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + return 0; +} + +static mixer_driver_t apci_mixer_driver = { + apci_mixer_ioctl +}; + +static int +apciintr (oss_device_t * osdev) +{ + int stats, i; + unsigned char ackbits = 0, tmp; + unsigned char uart_stat; + apci_devc *devc = (apci_devc *) osdev->devc; + apci_portc *portc; + int serviced = 0; + + stats = INL (devc->osdev, devc->base + 0x04); + + if (!(stats & 0x80000000)) /* No interrupt pending */ + return 0; + + serviced = 1; + for (i = 0; i < MAX_PORTC; i++) + { + portc = &devc->portc[i]; + + if (stats & 0x00000010) /* CCB interrupt */ + { + cmn_err (CE_WARN, "CCB interrupt\n"); + } + + if ((stats & 0x00000004) && (portc->atype)) /* DAC1 (synth) interrupt */ + { + ackbits |= CONC_SERCTL_SYNIE; + if (portc->trigger_bits & PCM_ENABLE_OUTPUT) + oss_audio_outputintr (portc->audiodev, 0); + + } + + if ((stats & 0x00000002) && (!portc->atype)) /* DAC2 interrupt */ + { + ackbits |= CONC_SERCTL_DACIE; + if (portc->trigger_bits & PCM_ENABLE_OUTPUT) + oss_audio_outputintr (portc->audiodev, 0); + } + + if ((stats & 0x00000001) && (!portc->atype)) /* ADC interrupt */ + { + ackbits |= CONC_SERCTL_ADCIE; + if (portc->trigger_bits & PCM_ENABLE_INPUT) + oss_audio_inputintr (portc->audiodev, 0); + } + + if (stats & 0x00000008) /* UART interrupt */ + { + uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF); + + while (uart_stat & CONC_UART_RXRDY) + { + unsigned char d; + + d = INB (devc->osdev, devc->base + CONC_bUARTDATA_OFF); + + if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, d); + uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF); + } + } + + tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF); + OUTB (devc->osdev, (tmp & ~ackbits), devc->base + CONC_bSERCTL_OFF); /* Clear bits */ + OUTB (devc->osdev, tmp | ackbits, devc->base + CONC_bSERCTL_OFF); /* Return them back on */ + + } + return serviced; +} + +/* + * Audio routines + */ + +static unsigned short +compute_dac2_rate (int samPerSec) +{ + + unsigned short usTemp; + + /* samPerSec /= 2; */ + + usTemp = (unsigned short) ((DAC_CLOCK_DIVIDE / 8) / samPerSec); + + if (usTemp & 0x00000001) + { + usTemp >>= 1; + usTemp -= 1; + } + else + { + usTemp >>= 1; + usTemp -= 2; + } + + return usTemp; + +} + +static int +apci_audio_set_rate (int dev, int arg) +{ + apci_portc *portc = audio_engines[dev]->portc; + + int speeds[] = { 5512, 11025, 22050, 44100 }; + int i, n = 0, best = 1000000; + + + if (arg == 0) + return portc->speed; + + if (portc->atype) + { + if (arg > 44100) + arg = 44100; + if (arg < 5512) + arg = 5512; + + for (i = 0; i < 4; i++) + { + int diff = arg - speeds[i]; + + if (diff < 0) + diff *= -1; + + if (diff < best) + { + n = i; + best = diff; + } + } + portc->speed = speeds[n]; + portc->speedsel = n; + } + else + { + if (arg > 48000) + arg = 48000; + if (arg < 5000) + arg = 5000; + portc->speed = arg; + } + return portc->speed; +} + +static short +apci_audio_set_channels (int dev, short arg) +{ + apci_portc *portc = audio_engines[dev]->portc; + + if ((arg != 1) && (arg != 2)) + return portc->channels; + portc->channels = arg; + + return portc->channels; +} + +static unsigned int +apci_audio_set_format (int dev, unsigned int arg) +{ + apci_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 +apci_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void apci_audio_trigger (int dev, int state); + +static void +apci_audio_reset (int dev) +{ + apci_audio_trigger (dev, 0); +} + +static void +apci_audio_reset_input (int dev) +{ + apci_portc *portc = audio_engines[dev]->portc; + apci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +apci_audio_reset_output (int dev) +{ + apci_portc *portc = audio_engines[dev]->portc; + apci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +apci_audio_open (int dev, int mode, int open_flags) +{ + apci_portc *portc = audio_engines[dev]->portc; + apci_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; + } + + portc->open_mode = mode; + portc->audio_enabled = ~mode; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +static void +apci_audio_close (int dev, int mode) +{ + apci_portc *portc = audio_engines[dev]->portc; + + apci_audio_reset (dev); + portc->open_mode = 0; + portc->audio_enabled &= ~mode; +} + +/*ARGSUSED*/ +static void +apci_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + apci_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + +} + +/*ARGSUSED*/ +static void +apci_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + apci_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + +} + +static void +apci_audio_trigger (int dev, int state) +{ + apci_devc *devc = audio_engines[dev]->devc; + apci_portc *portc = audio_engines[dev]->portc; + int tmp; + 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)) + { + if (portc->atype) + { + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp |= CONC_DEVCTL_DAC1_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + } + else + { + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp |= CONC_DEVCTL_DAC2_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + } + portc->trigger_bits |= PCM_ENABLE_OUTPUT; + oss_udelay (50); + } + } + 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; + if (portc->atype) + { + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp &= ~CONC_DEVCTL_DAC1_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + + tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF); + tmp &= ~CONC_SERCTL_SYNIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + } + else + { + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp &= ~CONC_DEVCTL_DAC2_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + + tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF); + tmp &= ~CONC_SERCTL_DACIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + } + } + } + } + + if ((portc->open_mode & OPEN_READ) + && !(audio_engines[dev]->flags & ADEV_NOINPUT)) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp |= CONC_DEVCTL_ADC_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + portc->trigger_bits |= PCM_ENABLE_INPUT; + oss_udelay (50); + } + } + 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; + + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF); + tmp &= ~CONC_DEVCTL_ADC_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + + tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF); + tmp &= ~CONC_SERCTL_ADCIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + } + } + } + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +apci_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + dmap_t *dmap = audio_engines[dev]->dmap_in; + apci_devc *devc = audio_engines[dev]->devc; + apci_portc *portc = audio_engines[dev]->portc; + unsigned short tmp = 0x00; + oss_native_word flags; + + /* Set physical address of the DMA buffer */ + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + apci_writemem (devc, CONC_ADCCTL_PAGE, CONC_dADCPADDR_OFF, + dmap->dmabuf_phys); + + /* Set DAC (ADC) rate */ + OUTW (devc->osdev, compute_dac2_rate (portc->speed), + devc->base + CONC_wDACRATE_OFF); + + /* Set format */ + tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF); + tmp &= ~(CONC_PCM_ADC_STEREO | CONC_PCM_ADC_16BIT); + if (portc->channels == 2) + tmp |= CONC_PCM_ADC_STEREO; + if (portc->bits == 16) + { + tmp |= CONC_PCM_ADC_16BIT; + OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF); /* Skip count register */ + } + else + { + OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF); /* Skip count register */ + } + OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF); + + /* Set the frame count */ + apci_writemem (devc, CONC_ADCCTL_PAGE, CONC_wADCFC_OFF, + (dmap->bytes_in_use / 4) - 1); + + /* Set # of samples between interrupts */ + OUTW (devc->osdev, + (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1, + devc->base + CONC_wADCIC_OFF); + + /* Enable the wave interrupt */ + tmp = INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_ADCIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + tmp |= CONC_SERCTL_ADCIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + + /* Enable microphone phantom power */ + tmp = INW (devc->osdev, devc->base + 2) & ~CONC_DEVCTL_MICBIAS; + if (devc->micbias) + tmp |= CONC_DEVCTL_MICBIAS; + OUTW (devc->osdev, tmp, devc->base + 2); + + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +apci_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + dmap_t *dmap = audio_engines[dev]->dmap_out; + unsigned char tmp = 0x00; + apci_devc *devc = audio_engines[dev]->devc; + apci_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->atype) + { + /* Set physical address of the DMA buffer */ + apci_writemem (devc, CONC_SYNCTL_PAGE, CONC_dSYNPADDR_OFF, + dmap->dmabuf_phys); + + /* Set DAC1 rate */ + tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & ~0x30; + tmp |= portc->speedsel << 4; + OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF); + + /* Set format */ + tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF); + tmp &= ~(CONC_PCM_DAC1_STEREO | CONC_PCM_DAC1_16BIT); + if (portc->channels == 2) + tmp |= CONC_PCM_DAC1_STEREO; + if (portc->bits == 16) + { + tmp |= CONC_PCM_DAC1_16BIT; + } + OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF); + + /* Set the frame count */ + apci_writemem (devc, CONC_SYNCTL_PAGE, CONC_wSYNFC_OFF, + (dmap->bytes_in_use / 4) - 1); + + /* Set # of samples between interrupts */ + OUTW (devc->osdev, + (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1, + devc->base + CONC_wSYNIC_OFF); + + /* Enable the wave interrupt */ + tmp = + INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_SYNIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + tmp |= CONC_SERCTL_SYNIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + } + else + { + /* Set physical address of the DMA buffer */ + apci_writemem (devc, CONC_DACCTL_PAGE, CONC_dDACPADDR_OFF, + dmap->dmabuf_phys); + + /* Set DAC rate */ + OUTW (devc->osdev, compute_dac2_rate (portc->speed), + devc->base + CONC_wDACRATE_OFF); + + /* Set format */ + tmp = INB (devc->osdev, devc->base + CONC_bSERFMT_OFF); + tmp &= ~(CONC_PCM_DAC2_STEREO | CONC_PCM_DAC2_16BIT); + if (portc->channels == 2) + tmp |= CONC_PCM_DAC2_STEREO; + if (portc->bits == 16) + { + tmp |= CONC_PCM_DAC2_16BIT; + OUTB (devc->osdev, 0x10, devc->base + CONC_bSKIPC_OFF); /* Skip count register */ + } + else + { + OUTB (devc->osdev, 0x08, devc->base + CONC_bSKIPC_OFF); /* Skip count register */ + } + OUTB (devc->osdev, tmp, devc->base + CONC_bSERFMT_OFF); + + /* Set the frame count */ + apci_writemem (devc, CONC_DACCTL_PAGE, CONC_wDACFC_OFF, + (dmap->bytes_in_use / 4) - 1); + apci_writemem (devc, CONC_DACCTL_PAGE, CONC_wDACFC_OFF, + (dmap->bytes_in_use / 4) - 1); + + /* Set # of samples between interrupts */ + OUTW (devc->osdev, + (dmap->fragment_size / ((portc->channels * portc->bits) / 8)) - 1, + devc->base + CONC_wDACIC_OFF); + + /* Enable the wave interrupt */ + tmp = + INB (devc->osdev, devc->base + CONC_bSERCTL_OFF) & ~CONC_SERCTL_DACIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + tmp |= CONC_SERCTL_DACIE; + OUTB (devc->osdev, tmp, devc->base + CONC_bSERCTL_OFF); + } + + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +apci_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + apci_devc *devc = audio_engines[dev]->devc; + apci_portc *portc = audio_engines[dev]->portc; + int ptr = 0, port = 0, page = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + if (direction == PCM_ENABLE_OUTPUT) + { + if (portc->atype) + { + port = CONC_wSYNFC_OFF; + page = CONC_SYNCTL_PAGE; + } + else + { + port = CONC_wDACFC_OFF; + page = CONC_DACCTL_PAGE; + } + } + + if (direction == PCM_ENABLE_INPUT) + { + port = CONC_wADCFC_OFF; + page = CONC_ADCCTL_PAGE; + } + + ptr = apci_readmem (devc, page, port); + ptr >>= 16; + ptr <<= 2; /* count is in dwords */ + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return ptr; +} + + +static const audiodrv_t apci_audio_driver = { + apci_audio_open, + apci_audio_close, + apci_audio_output_block, + apci_audio_start_input, + apci_audio_ioctl, + apci_audio_prepare_for_input, + apci_audio_prepare_for_output, + apci_audio_reset, + NULL, + NULL, + apci_audio_reset_input, + apci_audio_reset_output, + apci_audio_trigger, + apci_audio_set_rate, + apci_audio_set_format, + apci_audio_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, /* apci_alloc_buffer */ + NULL, /* apci_free_buffer */ + NULL, + NULL, + apci_get_buffer_pointer +}; + +/*ARGSUSED*/ +static int +apci_midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, + oss_midi_outputintr_t outputintr) +{ + apci_devc *devc = (apci_devc *) midi_devs[dev]->devc; + + if (devc->midi_opened) + { + return OSS_EBUSY; + } + + devc->midi_input_intr = inputbyte; + devc->midi_opened = mode; + + if (mode & OPEN_READ) + { + OUTB (devc->osdev, CONC_UART_RXINTEN, devc->base + CONC_bUARTCSTAT_OFF); + } + + return 0; +} + +/*ARGSUSED*/ +static void +apci_midi_close (int dev, int mode) +{ + apci_devc *devc = (apci_devc *) midi_devs[dev]->devc; + + OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF); + devc->midi_opened = 0; +} + +static int +apci_midi_out (int dev, unsigned char midi_byte) +{ + apci_devc *devc = (apci_devc *) midi_devs[dev]->devc; + int i; + + unsigned char uart_stat = + INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF); + + bmast_off (devc); + for (i = 0; i < 30000; i++) + { + uart_stat = INB (devc->osdev, devc->base + CONC_bUARTCSTAT_OFF); + if (uart_stat & CONC_UART_TXRDY) + break; + } + + if (!(uart_stat & CONC_UART_TXRDY)) + { + bmast_on (devc); + return 0; + } + + + OUTB (devc->osdev, midi_byte, devc->base + CONC_bUARTDATA_OFF); + bmast_on (devc); + + return 1; +} + +/*ARGSUSED*/ +static int +apci_midi_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t apci_midi_driver = { + apci_midi_open, + apci_midi_close, + apci_midi_ioctl, + apci_midi_out, +}; + +static int +init_apci (apci_devc * devc) +{ + int i, my_mixer, tmp; + + devc->micbias = 1; + devc->micboost = 1; + + OUTW (devc->osdev, compute_dac2_rate (8000), + devc->base + CONC_wDACRATE_OFF); + + tmp = + INB (devc->osdev, + devc->base + CONC_bMISCCTL_OFF) & ~CONC_MISCTL_CCB_INTRM; + tmp |= CONC_MISCTL_MUTE | CONC_MISCTL_DAC1FREQ_2205; + OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF); + + /* Turn on UART, CODEC and joystick. Disable SERR. */ + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF) & ~CONC_DEVCTL_SERR_DISABLE; /* Yes */ + tmp |= CONC_DEVCTL_UART_EN | CONC_DEVCTL_CODEC_EN | CONC_DEVCTL_JSTICK_EN; + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + + /* Disable NMI */ + OUTB (devc->osdev, 0x00, devc->base + CONC_bNMIENA_OFF); + OUTW (devc->osdev, 0x0000, devc->base + CONC_wNMISTAT_OFF); + + /* Init serial interface */ + OUTB (devc->osdev, 0x00, devc->base + CONC_bSERCTL_OFF); + OUTB (devc->osdev, CONC_PCM_DAC1_STEREO | CONC_PCM_DAC1_16BIT, + devc->base + CONC_bSERFMT_OFF); + + /* Unmute the codec */ + tmp = INB (devc->osdev, devc->base + CONC_bMISCCTL_OFF) & ~CONC_MISCTL_MUTE; + OUTB (devc->osdev, tmp, devc->base + CONC_bMISCCTL_OFF); + + /* Reset the UART */ + OUTB (devc->osdev, 0x03, devc->base + CONC_bUARTCSTAT_OFF); + OUTB (devc->osdev, 0x00, devc->base + CONC_bUARTCSTAT_OFF); + + /* + ******** Mixer initialization ********* + */ + oss_udelay (30); + ak_write (devc, 0x16, 0x03); /* Release reset */ + oss_udelay (50); + ak_write (devc, 0x18, 0x00); /* Select ADC from input mixer */ + + for (i = 0; i <= 0x19; i++) + ak_write (devc, i, ak_reg_init[i]); + + /* Enable microphone phantom power */ + tmp = INW (devc->osdev, devc->base + 2) & ~CONC_DEVCTL_MICBIAS; + if (devc->micbias) + tmp |= CONC_DEVCTL_MICBIAS; + OUTW (devc->osdev, tmp, devc->base + 2); + + if ((my_mixer = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "Creative AudioPCI", + &apci_mixer_driver, + sizeof (mixer_driver_t), devc)) >= 0) + { + char mxname[20]; + + sprintf (mxname, "AudioPCI"); + devc->recdevs = 0; + apci_set_recmask (devc, SOUND_MASK_MIC); + devc->levels = load_mixer_volumes (mxname, default_mixer_levels, 1); + mixer_ext_set_init_fn (my_mixer, apci_mix_init, 30); + apci_mixer_reset (devc); + } + + for (i = 0; i < MAX_PORTC; i++) + { + + int adev; + char tmp_name[100]; + apci_portc *portc = &devc->portc[i]; + int caps = ADEV_AUTOMODE; + + if (i == 0) + { + strcpy (tmp_name, devc->chip_name); + caps |= ADEV_DUPLEX; + } + else + { + sprintf (tmp_name, "%s (playback only)", devc->chip_name); + caps |= ADEV_NOINPUT; + } + + if ((adev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &apci_audio_driver, + sizeof (audiodrv_t), + caps, + AFMT_U8 | AFMT_S16_LE, devc, -1)) < 0) + { + adev = -1; + return 0; + } + else + { + audio_engines[adev]->portc = portc; + if (i == 0) + { + audio_engines[adev]->min_rate = 5000; + audio_engines[adev]->max_rate = 48000; + audio_engines[adev]->caps |= PCM_CAP_FREERATE; + } + else + { + audio_engines[adev]->min_rate = 5012; + audio_engines[adev]->max_rate = 44100; + } +/* audio_engines[adev]->min_block = 1024; */ + portc->open_mode = 0; + portc->audiodev = adev; + portc->atype = i; + audio_engines[adev]->mixer_dev = my_mixer; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, adev, -1, 0); +#endif + } + } + + if ((devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "AUDIOPCI", "AudioPCI UART", &apci_midi_driver, sizeof (midi_driver_t), + 0, devc, devc->osdev)) < 0) + { + cmn_err (CE_WARN, "Couldn't install MIDI device\n"); + return 0; + } + + devc->midi_opened = 0; + return 1; +} + +int +oss_audiopci_attach (oss_device_t * osdev) +{ + unsigned char pci_irq_line, pci_revision; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr; + int err; + apci_devc *devc; + + DDB (cmn_err (CE_WARN, "Entered AudioPCI probe routine\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if ((vendor != ENSONIQ_VENDOR_ID && vendor != ENSONIQ_VENDOR_ID2) || + device != ENSONIQ_AUDIOPCI) + + return 0; + + 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_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr); + + if (pci_irq_line == 0) + { + cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d). Can't continue\n", + pci_irq_line); + return 0; + } + + if (pci_ioaddr == 0) + { + cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n"); + 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->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr); + /* Remove I/O space marker in bit 0. */ + devc->base &= ~3; + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + devc->chip_name = "Creative AudioPCI (ES1370)"; + + oss_register_device (osdev, devc->chip_name); + + + if ((err = oss_register_interrupts (osdev, 0, apciintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't allocate IRQ%d, err=%d\n", pci_irq_line, err); + return 0; + } + + MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV); + MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1); + + return init_apci (devc); /* Detected */ +} + + +int +oss_audiopci_detach (oss_device_t * osdev) +{ + apci_devc *devc = (apci_devc *) osdev->devc; + int tmp; + + if (oss_disable_device (osdev) < 0) + return 0; + + tmp = INB (devc->osdev, devc->base + CONC_bDEVCTL_OFF) & + ~(CONC_DEVCTL_DAC2_EN | CONC_DEVCTL_ADC_EN | CONC_DEVCTL_DAC1_EN); + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + OUTB (devc->osdev, tmp, devc->base + CONC_bDEVCTL_OFF); + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + + oss_unregister_device (devc->osdev); + return 1; +} |