diff options
Diffstat (limited to 'kernel/drv/oss_cmpci')
-rw-r--r-- | kernel/drv/oss_cmpci/.devices | 5 | ||||
-rw-r--r-- | kernel/drv/oss_cmpci/.name | 1 | ||||
-rw-r--r-- | kernel/drv/oss_cmpci/oss_cmpci.c | 2379 | ||||
-rw-r--r-- | kernel/drv/oss_cmpci/oss_cmpci.man | 56 |
4 files changed, 2441 insertions, 0 deletions
diff --git a/kernel/drv/oss_cmpci/.devices b/kernel/drv/oss_cmpci/.devices new file mode 100644 index 0000000..9933503 --- /dev/null +++ b/kernel/drv/oss_cmpci/.devices @@ -0,0 +1,5 @@ +oss_cmpci pci13f6,100 C-Media CM8338A +oss_cmpci pci13f6,100 MIDIMan DiO 2448 +oss_cmpci pci13f6,101 CMedia CM8338B +oss_cmpci pci13f6,111 CMedia CM8738/CM8768 +oss_cmpci pci14af,20 Guillemot Maxi Sound MUSE diff --git a/kernel/drv/oss_cmpci/.name b/kernel/drv/oss_cmpci/.name new file mode 100644 index 0000000..0d987dc --- /dev/null +++ b/kernel/drv/oss_cmpci/.name @@ -0,0 +1 @@ +C-Media CM833x audio chipset diff --git a/kernel/drv/oss_cmpci/oss_cmpci.c b/kernel/drv/oss_cmpci/oss_cmpci.c new file mode 100644 index 0000000..50ccf84 --- /dev/null +++ b/kernel/drv/oss_cmpci/oss_cmpci.c @@ -0,0 +1,2379 @@ +/* + * Purpose: Driver for CMEDIA CM8738 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_cmpci_cfg.h" +#include "oss_pci.h" +#include "uart401.h" + +#define CMEDIA_VENDOR_ID 0x13F6 +#define CMEDIA_CM8738 0x0111 +#define CMEDIA_CM8338A 0x0100 +#define CMEDIA_CM8338B 0x0101 +/* + * CM8338 registers definition + */ + +#define FUNCTRL0 (devc->base+0x00) +#define FUNCTRL1 (devc->base+0x04) +#define CHFORMAT (devc->base+0x08) +#define INT_HLDCLR (devc->base+0x0C) +#define INT_STATUS (devc->base+0x10) +#define LEGACY_CTRL (devc->base+0x14) +#define MISC_CTRL (devc->base+0x18) +#define TDMA_POS (devc->base+0x1C) +#define MIXER (devc->base+0x20) +#define MIXER_DATA (devc->base+0x22) +#define MIXER_ADDR (devc->base+0x23) +#define MIXER1 (devc->base+0x24) +#define MIXER2 (devc->base+0x25) +#define AUX_VOL (devc->base+0x26) +#define MIXER3 (devc->base+0x27) +#define AC97 (devc->base+0x28) + +#define CH0_FRAME1 (devc->base+0x80) +#define CH0_FRAME2 (devc->base+0x84) +#define CH1_FRAME1 (devc->base+0x88) +#define CH1_FRAME2 (devc->base+0x8C) + +#define SPDIF_STAT (devc->base+0x90) +#define MISC2_CTRL (devc->base+0x92) + +#define MPU_MIRROR (devc->base+0x40) +#define FM_MIRROR (devc->base+0x50) +#define JOY_MIRROR (devc->base+0x60) + +#define DSP_MIX_DATARESETIDX (0x00) +#define DSP_MIX_OUTMIXIDX (0x3c) +#define CM_CH0_ENABLE 0x01 +#define CM_CH1_ENABLE 0x02 +#define CM_CH0_RESET 0x04 +#define CM_CH1_RESET 0x08 +#define CM_CH0_RECORD 0x01 +#define CM_CH1_RECORD 0x02 +#define CM_CH0_PLAY ~0x01 +#define CM_CH1_PLAY ~0x02 +#define CM_CH0_INT 1 +#define CM_CH1_INT 2 +#define CM_EXTENT_CODEC 0x100 +#define CM_EXTENT_MIDI 0x2 +#define CM_EXTENT_SYNTH 0x4 +#define CM_CFMT_STEREO 0x01 +#define CM_CFMT_16BIT 0x02 +#define CM_CFMT_MASK 0x03 +#define CM_CFMT_DACSHIFT 0 +#define CM_CFMT_ADCSHIFT 2 + +#define MUTE_LINE 1 +#define MUTE_CD 2 +#define MUTE_MIC 3 +#define MUTE_AUX 4 +#define REAR2LINE 5 +#define CEN2LINE 6 +#define BASS2LINE 7 +#define CEN2MIC 8 +#define MODE_4SPK 9 +#define DUALDAC 10 +#define MICBOOST 11 +#define SPDIF_PLAY 12 +#define SPDIF_LOOP 13 +#define SPDIF_REC 14 +#define SPDIF_IMON 15 +#define SPDIF_POL 16 +#define SPDIF_COPY 17 +#define SPDIF_OPT 18 +#define SPDIF_AC3 19 +#define AUX_REC 20 +#define AUX_LEVEL 21 + +#define CHAN0 0x1 +#define CHAN1 0x2 + +static struct +{ + unsigned int rate; + unsigned int lower; + unsigned int upper; + unsigned char freq; +} +rate_lookup[] = +{ + { + 5512, (0 + 5512) / 2, (5512 + 8000) / 2, 0} + , + { + 8000, (5512 + 8000) / 2, (8000 + 11025) / 2, 4} + , + { + 11025, (8000 + 11025) / 2, (11025 + 16000) / 2, 1} + , + { + 16000, (11025 + 16000) / 2, (16000 + 22050) / 2, 5} + , + { + 22050, (16000 + 22050) / 2, (22050 + 32000) / 2, 2} + , + { + 32000, (22050 + 32000) / 2, (32000 + 44100) / 2, 6} + , + { + 44100, (32000 + 44100) / 2, (44100 + 48000) / 2, 3} + , + { + 48000, (48000 + 44100) / 2, (48000 + 96000) / 2, 7} +}; + +static unsigned char cmpci_recmasks_L[SOUND_MIXER_NRDEVICES] = { + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x40, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x10, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x04, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_LINE1 */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +static unsigned char cmpci_recmasks_R[SOUND_MIXER_NRDEVICES] = { + 0x00, /* SOUND_MIXER_VOLUME */ + 0x00, /* SOUND_MIXER_BASS */ + 0x00, /* SOUND_MIXER_TREBLE */ + 0x20, /* SOUND_MIXER_SYNTH */ + 0x00, /* SOUND_MIXER_PCM */ + 0x00, /* SOUND_MIXER_SPEAKER */ + 0x08, /* SOUND_MIXER_LINE */ + 0x01, /* SOUND_MIXER_MIC */ + 0x02, /* SOUND_MIXER_CD */ + 0x00, /* SOUND_MIXER_IMIX */ + 0x00, /* SOUND_MIXER_LINE1 */ + 0x00, /* SOUND_MIXER_RECLEV */ + 0x00, /* SOUND_MIXER_IGAIN */ + 0x00 /* SOUND_MIXER_OGAIN */ +}; + +#define CMEDIA_RECORDING_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_LINE | \ + SOUND_MASK_MIC | SOUND_MASK_CD | SOUND_MASK_LINE1) + +#define CMEDIA_MIXER_DEVICES (SOUND_MASK_SYNTH | SOUND_MASK_PCM | \ + SOUND_MASK_LINE | SOUND_MASK_MIC | \ + SOUND_MASK_IGAIN | SOUND_MASK_CD | \ + SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER |\ + SOUND_MASK_LINE1|SOUND_MASK_RECLEV) + +#define LEFT_CHN 0 +#define RIGHT_CHN 1 +/* + * Mixer registers of CMPCI + */ +#define CMPCI_IMASK_L 0x3d +#define CMPCI_IMASK_R 0x3e + + +int default_levels[32] = { + 0x5a5a, /* Master Volume */ + 0x4b4b, /* Bass */ + 0x4b4b, /* Treble */ + 0x4b4b, /* FM */ + 0x4b4b, /* PCM */ + 0x4b4b, /* PC Speaker */ + 0x4b4b, /* Ext Line */ + 0x2020, /* Mic */ + 0x4b4b, /* CD */ + 0x0000, /* Recording monitor */ + 0x4b4b, /* SB PCM */ + 0x4b4b, /* Recording level */ + 0x4b4b, /* Input gain */ + 0x4b4b, /* Output gain */ + 0x4040, /* Line1 */ + 0x4040, /* Line2 */ + 0x1515 /* Line3 */ +}; + +typedef struct cmpci_portc +{ + int speed, bits, channels; + int open_mode; + int trigger_bits; + int audio_enabled; + int audiodev; + int dacfmt, adcfmt; + int chan0_play, chan0_rec; + int chan1_play, chan1_rec; +} +cmpci_portc; + +#define MAX_PORTC 2 + +typedef struct cmpci_devc +{ + oss_device_t *osdev; + oss_native_word base; + int fm_attached; + int irq; + int max_channels; + volatile unsigned char intr_mask; + int model; +#define MDL_CM8738 1 +#define MDL_CM8338A 2 +#define MDL_CM8338B 3 +#define MDL_CM8768 4 + char *chip_name; + int chiprev; + int mode_4spk; + int dev_mode; +#define DEFAULT_MODE 1 +#define DUALDAC_MODE 2 +#define SPDIFIN_MODE 4 + unsigned char spdif_control_bits[24]; + /* Audio parameters */ + oss_mutex_t mutex; + oss_mutex_t low_mutex; + int open_mode; + int audio_initialized; + cmpci_portc portc[MAX_PORTC]; + + /* spdif/ac3 stuff */ + int spdif_enabled; + int can_ac3; + + /* Mixer parameters */ + int mixer_dev; + int *levels; + int recmask; + + /* uart401 */ + int uart401_attached; + uart401_devc uart401devc; +} +cmpci_devc; + + +static void +set_spdif_rate (cmpci_devc * devc, cmpci_portc * portc) +{ + if (portc->speed == 48000) + { + /* setup MISC control reg */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 24) | (1 << 15), + MISC_CTRL); + } + else + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 15), + MISC_CTRL); + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 24), + MISC_CTRL); + } +} + +static void +setup_ac3 (cmpci_devc * devc, cmpci_portc * portc, int value) +{ + if (value && (portc->speed == 48000 || portc->speed == 44100)) + { + if (devc->chiprev == 37) + { + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (1 << 20), + CHFORMAT); + } + else + { + /* Enable AC3 */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 18), + MISC_CTRL); + } + } + else + { + if (devc->chiprev == 37) + { + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 20), + CHFORMAT); + } + else + { + /* Disable AC3 */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 18), + MISC_CTRL); + } + } +} + + +static void +cmpci_setmixer (cmpci_devc * devc, unsigned int port, unsigned int value) +{ + oss_native_word flags; + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR); + oss_udelay (20); + OUTB (devc->osdev, (unsigned char) (value & 0xff), MIXER_DATA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + oss_udelay (20); +} + +static unsigned int +cmpci_getmixer (cmpci_devc * devc, unsigned int port) +{ + unsigned int val; + + oss_native_word flags; + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + OUTB (devc->osdev, (unsigned char) (port & 0xff), MIXER_ADDR); + + + oss_udelay (20); + val = INB (devc->osdev, MIXER_DATA); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + oss_udelay (20); + return val; +} + +struct mixer_def +{ + unsigned int regno:8; + unsigned int bitoffs:4; + unsigned int nbits:4; +}; + +typedef struct mixer_def mixer_tab[32][2]; +typedef struct mixer_def mixer_ent; + +#define MIX_ENT(name, reg_l, bit_l, len_l, reg_r, bit_r, len_r) \ + {{reg_l, bit_l, len_l}, {reg_r, bit_r, len_r}} + +static mixer_tab cmpci_mix = { + MIX_ENT (SOUND_MIXER_VOLUME, 0x30, 7, 5, 0x31, 7, 5), + MIX_ENT (SOUND_MIXER_BASS, 0x46, 7, 4, 0x47, 7, 4), + MIX_ENT (SOUND_MIXER_TREBLE, 0x44, 7, 4, 0x45, 7, 4), + MIX_ENT (SOUND_MIXER_SYNTH, 0x34, 7, 5, 0x35, 7, 5), + MIX_ENT (SOUND_MIXER_PCM, 0x32, 7, 5, 0x33, 7, 5), + MIX_ENT (SOUND_MIXER_SPEAKER, 0x3b, 7, 2, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_LINE, 0x38, 7, 5, 0x39, 7, 5), + MIX_ENT (SOUND_MIXER_MIC, 0x3a, 7, 5, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_CD, 0x36, 7, 5, 0x37, 7, 5), + MIX_ENT (SOUND_MIXER_IMIX, 0x3c, 0, 1, 0x00, 0, 0), + MIX_ENT (SOUND_MIXER_LINE1, 0, 0, 0, 0, 0, 0), + MIX_ENT (SOUND_MIXER_RECLEV, 0x3f, 7, 2, 0x40, 7, 2), + MIX_ENT (SOUND_MIXER_IGAIN, 0x3f, 7, 2, 0x40, 7, 2), + MIX_ENT (SOUND_MIXER_OGAIN, 0x41, 7, 2, 0x42, 7, 2) +}; + +/*ARGSUSED*/ +static void +change_bits (cmpci_devc * devc, unsigned char *regval, int dev, int chn, + int newval) +{ + unsigned char mask; + int shift; + + mask = (1 << cmpci_mix[dev][chn].nbits) - 1; + newval = (int) ((newval * mask) + 50) / 100; /* Scale */ + + shift = cmpci_mix[dev][chn].bitoffs - cmpci_mix[dev][LEFT_CHN].nbits + 1; + + *regval &= ~(mask << shift); /* Mask out previous value */ + *regval |= (newval & mask) << shift; /* Set the new value */ +} + +static int set_recmask (int dev, int mask); + +static int +cmpci_mixer_set (cmpci_devc * devc, int chan, int value) +{ + int left = value & 0x000000ff; + int right = (value & 0x0000ff00) >> 8; + + int regoffs; + unsigned char val; + + if (left > 100) + left = 100; + if (right > 100) + right = 100; + + if (chan > 31) + return OSS_EINVAL; + + if (!(CMEDIA_MIXER_DEVICES & (1 << chan))) /* + * Not supported + */ + return OSS_EINVAL; + + regoffs = cmpci_mix[chan][LEFT_CHN].regno; + + if (regoffs == 0) + return OSS_EINVAL; + + val = cmpci_getmixer (devc, regoffs); + change_bits (devc, &val, chan, LEFT_CHN, left); + + devc->levels[chan] = left | (left << 8); + + if (cmpci_mix[chan][RIGHT_CHN].regno != regoffs) /* + * Change register + */ + { + cmpci_setmixer (devc, regoffs, val); /* + * Save the old one + */ + regoffs = cmpci_mix[chan][RIGHT_CHN].regno; + + if (regoffs == 0) + return left | (left << 8); /* + * Just left channel present + */ + + val = cmpci_getmixer (devc, regoffs); /* + * Read the new one + */ + } + + change_bits (devc, &val, chan, RIGHT_CHN, right); + + cmpci_setmixer (devc, regoffs, val); + + devc->levels[chan] = left | (right << 8); + return left | (right << 8); +} + +static int cmpci_outsw (int dev, int ctrl, unsigned int cmd, int value); + +/*ARGSUSED*/ +static int +cmpci_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + cmpci_devc *devc = mixer_devs[dev]->devc; + int val; + + if (((cmd >> 8) & 0xff) == 'M') + { + if (IOC_IS_OUTPUT (cmd)) + switch (cmd & 0xff) + { + case SOUND_MIXER_RECSRC: + val = *arg; + if (val == SOUND_MASK_LINE1) + *arg = cmpci_outsw (dev, AUX_REC, SNDCTL_MIX_WRITE, 1); + else + *arg = cmpci_outsw (dev, AUX_REC, SNDCTL_MIX_WRITE, 0); + return *arg = set_recmask (dev, val); + break; + + case SOUND_MIXER_LINE1: + val = *arg; + return *arg = cmpci_outsw (dev, AUX_LEVEL, SNDCTL_MIX_WRITE, val); + break; + + default: + val = *arg; + return *arg = cmpci_mixer_set (devc, cmd & 0xff, val); + } + else + switch (cmd & 0xff) + { + + case SOUND_MIXER_RECSRC: + return *arg = devc->recmask; + break; + + case SOUND_MIXER_DEVMASK: + return *arg = CMEDIA_MIXER_DEVICES; + break; + + case SOUND_MIXER_STEREODEVS: + return *arg = CMEDIA_MIXER_DEVICES & + ~(SOUND_MASK_MIC | SOUND_MASK_SPEAKER | SOUND_MASK_IMIX); + break; + + case SOUND_MIXER_RECMASK: + return *arg = CMEDIA_RECORDING_DEVICES; + break; + + case SOUND_MIXER_LINE1: + val = cmpci_outsw (dev, AUX_LEVEL, SNDCTL_MIX_READ, 0); + return *arg = val; + break; + + case SOUND_MIXER_CAPS: + return *arg = SOUND_CAP_EXCL_INPUT; + break; + + default: + return *arg = devc->levels[cmd & 0x1f]; + } + } + else + return OSS_EINVAL; +} + +static int +set_recmask (int dev, int mask) +{ + cmpci_devc *devc = mixer_devs[dev]->devc; + int devmask = mask & CMEDIA_RECORDING_DEVICES; + int i; + unsigned char regimageL, regimageR; + + if (!devmask) + devmask = SOUND_MASK_MIC; + + regimageL = regimageR = 0; + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if ((1 << i) & devmask) + { + regimageL |= cmpci_recmasks_L[i]; + regimageR |= cmpci_recmasks_R[i]; + } + cmpci_setmixer (devc, CMPCI_IMASK_L, regimageL); + cmpci_setmixer (devc, CMPCI_IMASK_R, regimageR); + devc->recmask = devmask; + return devc->recmask; +} + +static int +cmpci_outsw (int dev, int ctrl, unsigned int cmd, int value) +{ +/* + * Access function for CMPCI mixer extension bits + */ + cmpci_devc *devc = mixer_devs[dev]->devc; + int left, right, tmp; + + if (cmd == SNDCTL_MIX_READ) + { + value = 0; + switch (ctrl) + { + case MUTE_LINE: /* Left line in to output connection */ + value = (cmpci_getmixer (devc, 0x3c) & 0x18) ? 0 : 1; + break; + + case MUTE_CD: /* Cd in to output connection */ + value = (cmpci_getmixer (devc, 0x3c) & 0x06) ? 0 : 1; + break; + + case MUTE_MIC: /* Mic in to output connection */ + value = (cmpci_getmixer (devc, 0x3c) & 0x01) ? 0 : 1; + break; + + case MODE_4SPK: /* 4Speaker out */ + value = INL (devc->osdev, MISC_CTRL) & (1 << 26) ? 1 : 0; + break; + + case DUALDAC: /* dual dac */ + value = devc->dev_mode & DUALDAC_MODE ? 1 : 0; + break; + + case REAR2LINE: /* rear to line in */ + value = INB (devc->osdev, MIXER1) & (1 << 5) ? 1 : 0; + break; + + case CEN2LINE: /* center to line in */ + value = INL (devc->osdev, LEGACY_CTRL) & (1 << 14) ? 1 : 0; + break; + + case BASS2LINE: /* basss to line in */ + value = INL (devc->osdev, LEGACY_CTRL) & (1 << 13) ? 1 : 0; + break; + + case SPDIF_PLAY: /* spdif out */ + value = INL (devc->osdev, LEGACY_CTRL) & (1 << 23) ? 1 : 0; + break; + + case SPDIF_LOOP: /* S/PDIF I/O Loop */ + value = (INL (devc->osdev, FUNCTRL1) & (1 << 7)) ? 1 : 0; + break; + + case SPDIF_REC: /* spdif record mode */ + value = devc->dev_mode & SPDIFIN_MODE ? 1 : 0; + break; + + case SPDIF_IMON: /* spdif input monitor */ + value = INB (devc->osdev, MIXER1) & 0x1 ? 1 : 0; + break; + + case SPDIF_POL: /* spdif input reverse */ + if (devc->chiprev < 39) + value = INB (devc->osdev, MIXER3) & 0x06 ? 1 : 0; + else + value = INL (devc->osdev, CHFORMAT) & 0x80 ? 1 : 0; + break; + + case SPDIF_AC3: /* ac3 */ + value = INL (devc->osdev, MISC_CTRL) & (1 << 18) ? 1 : 0; + break; + + case SPDIF_COPY: /* copy protect (indirect) */ + value = INL (devc->osdev, LEGACY_CTRL) & (1 << 22) ? 1 : 0; + break; + + case SPDIF_OPT: /* Coax/Optical Select */ + value = INL (devc->osdev, MISC_CTRL) & (1 << 25) ? 1 : 0; + break; + + case CEN2MIC: /* Center2MIC */ + if (devc->chiprev >= 39) + value = INB (devc->osdev, MIXER3) & 0x4 ? 1 : 0; + break; + + case MICBOOST: /* MIC Boost */ + value = INB (devc->osdev, MIXER2) & 0x1 ? 0 : 1; + break; + + case AUX_LEVEL: + value = devc->levels[SOUND_MIXER_LINE1]; + break; + + case AUX_REC: /* set LINE1 as rec source - handled by set_recmask */ + break; + + case MUTE_AUX: /* AUX mute */ + value = INB (devc->osdev, MIXER2) & 0x30 ? 0 : 1; + break; + + default: + return OSS_EINVAL; + } + + return value; + } + + if (cmd == SNDCTL_MIX_WRITE) + { + switch (ctrl) + { + case MUTE_LINE: /* L/R line in to output connection */ + if (!value) + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x18); + else + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x18); + break; + + case MUTE_CD: /* Cd in to output connection */ + if (!value) + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x06); + else + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x06); + break; + + case MUTE_MIC: /* Mic in to output connection */ + if (!value) + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) | 0x01); + else + cmpci_setmixer (devc, 0x3c, cmpci_getmixer (devc, 0x3c) & ~0x01); + break; + + case MODE_4SPK: /* 4Speaker out */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 26), + MISC_CTRL); + devc->mode_4spk = 1; + } + else + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + devc->mode_4spk = 0; + break; + + case DUALDAC: /* DUAL DAC mode */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 23), + MISC_CTRL); + /* Disable 4Speaker mode */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + devc->dev_mode = DUALDAC_MODE; + } + else + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 23), + MISC_CTRL); + + /* enable back the 4Speaker mode */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | 1 << 26, + MISC_CTRL); + + devc->dev_mode = DEFAULT_MODE; + } + break; + + case REAR2LINE: /* REAR TO LINEIN */ + if (value) + { + OUTB (devc->osdev, (INB (devc->osdev, MIXER1) | (1 << 5)), + MIXER1); + } + else + OUTB (devc->osdev, (INB (devc->osdev, MIXER1) & ~(1 << 5)), + MIXER1); + break; + + case CEN2LINE: /* CENTER TO LINEIN */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 14), + LEGACY_CTRL); + } + else + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 14), + LEGACY_CTRL); + break; + + case BASS2LINE: /* BASS TO LINEIN */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 13), + LEGACY_CTRL); + } + else + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 13), + LEGACY_CTRL); + break; + + case SPDIF_PLAY: /* SPDIF ENABLE */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 23), + LEGACY_CTRL); + + /* enable wave/fm/midi to spdif OUT DAC2SPDO on rev 33/37 */ + if (devc->chiprev < 39) + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 21), + LEGACY_CTRL); + devc->spdif_enabled = 1; + } + else + { + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 23), + LEGACY_CTRL); + /* Disable wave/fm/midi to spdif OUT (DAC2SPDO) */ + if (devc->chiprev < 39) + OUTL (devc->osdev, + INL (devc->osdev, LEGACY_CTRL) & ~(1 << 21), + LEGACY_CTRL); + devc->spdif_enabled = 0; + } + break; + + case SPDIF_LOOP: /* S/PDIF I/O Loop */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 7), + FUNCTRL1); + } + else + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 7), + FUNCTRL1); + break; + + case SPDIF_REC: /* SPDIF Record Mode */ + if (value) + { + devc->dev_mode = SPDIFIN_MODE; + } + else + devc->dev_mode = DEFAULT_MODE; + break; + + case SPDIF_IMON: /* spdif monitor */ + if (value) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER1) | 0x0D, MIXER1); + } + else + OUTB (devc->osdev, INB (devc->osdev, MIXER1) & ~0xD, MIXER1); + break; + + case SPDIF_POL: /* spdif reverse */ + if (value) + { + if (devc->chiprev < 39) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER3) | 0x06, + MIXER3); + } + else + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | 0x80, + CHFORMAT); + } + else + { + if (devc->chiprev < 39) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER3) & ~0x06, + MIXER3); + } + else + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~0x80, + CHFORMAT); + } + break; + + case SPDIF_AC3: /* AC3 enabled on S/PDIF */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 18), + MISC_CTRL); + } + else + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 18), + MISC_CTRL); + break; + + case SPDIF_COPY: /* Copy protect */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 22), + LEGACY_CTRL); + } + else + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 22), + LEGACY_CTRL); + break; + + case SPDIF_OPT: /* Coax/Optical */ + if (value) + { + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 25), + MISC_CTRL); + } + else + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 25), + MISC_CTRL); + break; + + case CEN2MIC: /* Center -> Mic OUT */ + if (value) + { + if (devc->chiprev >= 39) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER3) | 0x4, MIXER3); + } + } + else if (devc->chiprev >= 39) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER3) & ~0x4, MIXER3); + } + break; + + case MICBOOST: /* Mic Boost */ + if (!value) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0x1, MIXER2); + } + else + OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0x1, MIXER2); + break; + + case AUX_LEVEL: /* Aux levels */ + left = value & 0xff; + right = (value >> 8) & 0xff; + if (left > 100) + left = 100; + if (right > 100) + right = 100; + value = left | (right << 8); + left = mix_cvt[left]; + right = mix_cvt[right]; + + tmp = ((right * ((1 << 4) - 1) / 100) << 4) | + (left * ((1 << 4) - 1) / 100); + + OUTB (devc->osdev, tmp, AUX_VOL); + devc->levels[SOUND_MIXER_LINE1] = value; + break; + + case AUX_REC: /* line1 record select */ + if (value) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0xc0, MIXER2); + } + else + OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0xc0, MIXER2); + break; + + case MUTE_AUX: /* line1 mute control */ + if (!value) + { + OUTB (devc->osdev, INB (devc->osdev, MIXER2) | 0x30, MIXER2); + } + else + OUTB (devc->osdev, INB (devc->osdev, MIXER2) & ~0x30, MIXER2); + break; + + default: + return OSS_EINVAL; + } + + return (value); + } + return OSS_EINVAL; +} + +static int +cmpci_mix_init (int dev) +{ + int group, err; + + if ((group = mixer_ext_create_group (dev, 0, "CMPCI_MUTECTL")) < 0) + return group; + + if ((err = + mixer_ext_create_control (dev, group, MUTE_LINE, cmpci_outsw, + MIXT_ONOFF, "CMPCI_LINEMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, MUTE_CD, cmpci_outsw, MIXT_ONOFF, + "CMPCI_CDMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, MUTE_MIC, cmpci_outsw, + MIXT_ONOFF, "CMPCI_MICMUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, MUTE_AUX, cmpci_outsw, + MIXT_ONOFF, "CMPCI_LINE1MUTE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((group = mixer_ext_create_group (dev, 0, "CMPCI_JACKCTL")) < 0) + return group; + if ((err = + mixer_ext_create_control (dev, group, REAR2LINE, cmpci_outsw, + MIXT_ONOFF, "CMPCI_REAR2LINE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + if ((err = + mixer_ext_create_control (dev, group, CEN2LINE, cmpci_outsw, + MIXT_ONOFF, "CMPCI_CEN2LINE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + if ((err = + mixer_ext_create_control (dev, group, BASS2LINE, cmpci_outsw, + MIXT_ONOFF, "CMPCI_BASS2LINE", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, CEN2MIC, cmpci_outsw, MIXT_ONOFF, + "CMPCI_CEN2MIC", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + + if ((group = mixer_ext_create_group (dev, 0, "CMPCI_MIXEXT")) < 0) + return group; + + if ((err = + mixer_ext_create_control (dev, group, MODE_4SPK, cmpci_outsw, + MIXT_ENUM, "CMPCI_SPKMODE", 2, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, DUALDAC, cmpci_outsw, MIXT_ONOFF, + "CMPCI_DUALDAC", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, MICBOOST, cmpci_outsw, + MIXT_ONOFF, "CMPCI_MICBOOST", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + /* Create a new SPDIF group */ + if ((group = mixer_ext_create_group (dev, 0, "CMPCI_SPDIF")) < 0) + return group; + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_PLAY, cmpci_outsw, + MIXT_ONOFF, "CMPCI_Play", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_LOOP, cmpci_outsw, + MIXT_ONOFF, "CMPCI_LOOP", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + /* Having this in mixer doesn't make any sense */ + if ((err = + mixer_ext_create_control (dev, group, SPDIF_REC, cmpci_outsw, + MIXT_ONOFF, "CMPCI_RECORD", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_IMON, cmpci_outsw, + MIXT_ONOFF, "CMPCI_IMON", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_POL, cmpci_outsw, + MIXT_ONOFF, "CMPCI_POLREV", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + +#if 0 + /* Having this in mixer doesn't make any sense */ + if ((err = + mixer_ext_create_control (dev, group, SPDIF_AC3, cmpci_outsw, + MIXT_ONOFF, "CMPCI_AC3", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; +#endif + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_COPY, cmpci_outsw, + MIXT_ONOFF, "CMPCI_COPYPROT", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = + mixer_ext_create_control (dev, group, SPDIF_OPT, cmpci_outsw, + MIXT_ONOFF, "CMPCI_OPTICAL", 1, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + return 0; +} + +static void +cmpci_mixer_reset (int dev) +{ + int i; + cmpci_devc *devc = mixer_devs[dev]->devc; + + devc->levels = load_mixer_volumes ("CM8738_Mixer", default_levels, 1); + devc->recmask = 0; + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) + cmpci_mixer_set (devc, i, devc->levels[i]); + + set_recmask (dev, SOUND_MASK_MIC); +} + + +static mixer_driver_t cmpci_mixer_driver = { + cmpci_mixer_ioctl +}; + + +static int +cmpciintr (oss_device_t * osdev) +{ + cmpci_devc *devc = (cmpci_devc *) osdev->devc; + unsigned int intstat, intsrc; + int i; + int serviced = 0; + + /* see if this is our interrupt */ + intsrc = INL (devc->osdev, INT_STATUS); + if (intsrc & (CM_CH0_INT | CM_CH1_INT)) + { + /* Handle playback */ + serviced = 1; + + intstat = INB (devc->osdev, INT_HLDCLR + 2); + + for (i = 0; i < MAX_PORTC; i++) + { + cmpci_portc *portc = &devc->portc[i]; + + if (intstat & CM_CH1_INT) + { + /* do chan1 playback */ + if ((portc->chan1_play) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + OUTB (devc->osdev, intstat & ~CM_CH1_INT, INT_HLDCLR + 2); + OUTB (devc->osdev, intstat | CM_CH1_INT, INT_HLDCLR + 2); + oss_audio_outputintr (portc->audiodev, 0); + } + + /* do chan1 record */ + if ((portc->chan1_rec) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + OUTB (devc->osdev, intstat & ~CM_CH1_INT, INT_HLDCLR + 2); + OUTB (devc->osdev, intstat | CM_CH1_INT, INT_HLDCLR + 2); + oss_audio_inputintr (portc->audiodev, 0); + } + } + + if (intstat & CM_CH0_INT) + { + /* do chan0 playback */ + if ((portc->chan0_play) && + (portc->trigger_bits & PCM_ENABLE_OUTPUT)) + { + OUTB (devc->osdev, intstat & ~CM_CH0_INT, INT_HLDCLR + 2); + OUTB (devc->osdev, intstat | CM_CH0_INT, INT_HLDCLR + 2); + oss_audio_outputintr (portc->audiodev, 0); + } + + /* do chan0 record */ + if ((portc->chan0_rec) && + (portc->trigger_bits & PCM_ENABLE_INPUT)) + { + OUTB (devc->osdev, intstat & ~CM_CH0_INT, INT_HLDCLR + 2); + OUTB (devc->osdev, intstat | CM_CH0_INT, INT_HLDCLR + 2); + oss_audio_inputintr (portc->audiodev, 0); + } + } + } + } + + if (intsrc & 0x10000) + { + serviced = 1; + uart401_irq (&devc->uart401devc); + } + + return serviced; +} + +static int +cmpci_audio_set_rate (int dev, int arg) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->speed; + + if (arg > 48000) + arg = 48000; + if (arg < 5000) + arg = 5000; + portc->speed = arg; + return portc->speed; +} + +static short +cmpci_audio_set_channels (int dev, short arg) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + + if (devc->model == MDL_CM8768) + { + if (arg>8) + arg=8; + + if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6) && (arg != 8)) + return portc->channels; + } + else + { + if (arg>6) + arg=6; + + if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6)) + return portc->channels; + } + portc->channels = arg; + + return portc->channels; +} + +static unsigned int +cmpci_audio_set_format (int dev, unsigned int arg) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->bits; + + if (!(arg & (AFMT_U8 | AFMT_S16_LE | AFMT_AC3))) + return portc->bits; + portc->bits = arg; + + return portc->bits; +} + +/*ARGSUSED*/ +static int +cmpci_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void cmpci_audio_trigger (int dev, int state); + +static void +cmpci_audio_reset (int dev) +{ + cmpci_audio_trigger (dev, 0); +} + +static void +cmpci_audio_reset_input (int dev) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + cmpci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +cmpci_audio_reset_output (int dev) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + cmpci_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +cmpci_audio_open (int dev, int mode, int open_flags) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + cmpci_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->dev_mode & DUALDAC_MODE)) + { + 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 +cmpci_audio_close (int dev, int mode) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + cmpci_devc *devc = audio_engines[dev]->devc; + + cmpci_audio_reset (dev); + portc->open_mode = 0; + devc->open_mode &= ~mode; + portc->audio_enabled &= ~mode; + + if ((devc->spdif_enabled) || (devc->dev_mode & SPDIFIN_MODE)) + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 9), FUNCTRL1); + + if (portc->chan0_play) + portc->chan0_play = 0; + + if (portc->chan1_play) + portc->chan1_play = 0; + + if (portc->chan0_rec) + portc->chan0_rec = 0; + + if (portc->chan1_rec) + portc->chan1_rec = 0; +} + +/*ARGSUSED*/ +static void +cmpci_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; +} + +/*ARGSUSED*/ +static void +cmpci_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +static void +cmpci_audio_trigger (int dev, int state) +{ + cmpci_portc *portc = audio_engines[dev]->portc; + cmpci_devc *devc = audio_engines[dev]->devc; + 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->chan0_play) + { + /* enable the channel0 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_ENABLE, + FUNCTRL0 + 2); + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) | CM_CH0_INT, + INT_HLDCLR + 2); + } + + if (portc->chan1_play) + { + /* enable the channel1 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_ENABLE, + FUNCTRL0 + 2); + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) | CM_CH1_INT, + INT_HLDCLR + 2); + } + 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; + + if (portc->chan0_play) + { + /* disable interrupt */ + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH0_INT, + INT_HLDCLR + 2); + + /* disable channel0 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_ENABLE, + FUNCTRL0 + 2); + } + if (portc->chan1_play) + { + /* disable interrupt */ + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH1_INT, + INT_HLDCLR + 2); + + /* disable channel */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_ENABLE, + FUNCTRL0 + 2); + } + } + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + if (portc->chan1_rec) + { + /* enable the channel1 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_ENABLE, + FUNCTRL0 + 2); + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) | CM_CH1_INT, + INT_HLDCLR + 2); + } + if (portc->chan0_rec) + { + /* enable the channel0 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_ENABLE, + FUNCTRL0 + 2); + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) | CM_CH0_INT, + INT_HLDCLR + 2); + } + 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; + if (portc->chan1_rec) + { + /* disable interrupt */ + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH1_INT, + INT_HLDCLR + 2); + + /* disable channel 1 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_ENABLE, + FUNCTRL0 + 2); + } + if (portc->chan0_rec) + { + /* disable interrupt */ + OUTB (devc->osdev, + INB (devc->osdev, INT_HLDCLR + 2) & ~CM_CH0_INT, + INT_HLDCLR + 2); + + /* disable channel 0 */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_ENABLE, + FUNCTRL0 + 2); + + } + } + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static void +set_dac_rate (int dev, int chan_type) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + unsigned char freq = 4, val; + int i; + int rate = portc->speed; + + if (rate > 48000) + rate = 48000; + if (rate < 5512) + rate = 5512; + + for (i = 0; i < sizeof (rate_lookup) / sizeof (rate_lookup[0]); i++) + { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) + { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + if (chan_type == CHAN0) + { + val = INB (devc->osdev, FUNCTRL1 + 1) & ~0x1c; + OUTB (devc->osdev, val | freq << 2, FUNCTRL1 + 1); + } + else + { + val = INB (devc->osdev, FUNCTRL1 + 1) & ~0xe0; + OUTB (devc->osdev, val | freq << 5, FUNCTRL1 + 1); + } + if (devc->spdif_enabled) + set_spdif_rate (devc, portc); +} + +static void +set_dac_fmt (int dev, int chan_type) +{ + unsigned char val; + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + int channels = portc->channels; + int bits = portc->bits; + + if (chan_type == CHAN0) + { + /* Set the format on Channl 0 */ + val = INB (devc->osdev, CHFORMAT) & ~0x3; + + if ((channels == 1) && (bits == 8)) + { + OUTB (devc->osdev, 0x00 | val, CHFORMAT); + portc->dacfmt = 0; + } + + if ((channels == 2) && (bits == 8)) + { + OUTB (devc->osdev, 0x01 | val, CHFORMAT); + portc->dacfmt = 1; + } + + if ((channels == 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x02 | val, CHFORMAT); + portc->dacfmt = 1; + } + + if ((channels > 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x03 | val, CHFORMAT); + portc->dacfmt = 2; + } + } + else + { + /* Set the format on Channel 1 */ + val = INB (devc->osdev, CHFORMAT) & ~0xC; + + if ((channels == 1) && (bits == 8)) + { + OUTB (devc->osdev, 0x00 | val, CHFORMAT); + portc->dacfmt = 0; + } + + if ((channels == 2) && (bits == 8)) + { + OUTB (devc->osdev, 0x04 | val, CHFORMAT); + portc->dacfmt = 1; + } + + if ((channels == 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x08 | val, CHFORMAT); + portc->dacfmt = 1; + } + + if ((channels > 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x0C | val, CHFORMAT); + portc->dacfmt = 2; + } + } +} + + +static void +set_adc_rate (int dev, int chan_type) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + unsigned char freq = 4, val; + int i; + int rate = portc->speed; + + if (rate > 48000) + rate = 48000; + if (rate < 5512) + rate = 5512; + for (i = 0; i < sizeof (rate_lookup) / sizeof (rate_lookup[0]); i++) + { + if (rate > rate_lookup[i].lower && rate <= rate_lookup[i].upper) + { + rate = rate_lookup[i].rate; + freq = rate_lookup[i].freq; + break; + } + } + if (chan_type == CHAN1) + { + val = INB (devc->osdev, FUNCTRL1 + 1) & ~0xe0; + OUTB (devc->osdev, val | freq << 5, FUNCTRL1 + 1); + } + else + { + val = INB (devc->osdev, FUNCTRL1 + 1) & ~0x1c; + OUTB (devc->osdev, val | freq << 2, FUNCTRL1 + 1); + } + + if (devc->dev_mode & SPDIFIN_MODE) + set_spdif_rate (devc, portc); +} + +static void +set_adc_fmt (int dev, int chan_type) +{ + unsigned char val; + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + int channels = portc->channels; + int bits = portc->bits; + + if (chan_type == CHAN1) + { + /* Set the format on Channel 1 */ + val = INB (devc->osdev, CHFORMAT) & ~0xC; + + if ((channels == 1) && (bits == 8)) + { + OUTB (devc->osdev, 0x00 | val, CHFORMAT); + portc->adcfmt = 0; + } + + if ((channels == 2) && (bits == 8)) + { + OUTB (devc->osdev, 0x04 | val, CHFORMAT); + portc->adcfmt = 1; + } + + if ((channels == 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x08 | val, CHFORMAT); + portc->adcfmt = 1; + } + + if ((channels > 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x0C | val, CHFORMAT); + portc->adcfmt = 2; + } + } + else + { + /* Set the format on Channl 0 */ + val = INB (devc->osdev, CHFORMAT) & ~0x3; + + if ((channels == 1) && (bits == 8)) + { + OUTB (devc->osdev, 0x00 | val, CHFORMAT); + portc->adcfmt = 0; + } + + if ((channels == 2) && (bits == 8)) + { + OUTB (devc->osdev, 0x01 | val, CHFORMAT); + portc->adcfmt = 1; + } + + if ((channels == 1) && (bits == 16)) + { + OUTB (devc->osdev, 0x02 | val, CHFORMAT); + portc->adcfmt = 1; + } + + if ((channels == 2) && (bits == 16)) + { + OUTB (devc->osdev, 0x03 | val, CHFORMAT); + portc->adcfmt = 2; + } + } +} + +static void +setup_record (int dev, int chan_type) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_in; + + if (chan_type == CHAN1) /* SPDIF Record can only occur on CHAN1 */ + { + /* reset and disable channel */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_RESET, FUNCTRL0 + 2); + oss_udelay (10); + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_RESET, FUNCTRL0 + 2); + + cmpci_outsw (devc->mixer_dev, SPDIF_PLAY, SNDCTL_MIX_WRITE, 0); + + OUTL (devc->osdev, dmap->dmabuf_phys, CH1_FRAME1); + OUTW (devc->osdev, (dmap->bytes_in_use >> portc->adcfmt) - 1, + CH1_FRAME2); + OUTW (devc->osdev, (dmap->fragment_size >> portc->adcfmt), + CH1_FRAME2 + 2); + + /* set channel 1 to record mode */ + OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) | CM_CH1_RECORD, + FUNCTRL0); + portc->chan1_rec = 1; + + /* setup SPDIF in on CHAN A */ + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 9), FUNCTRL1); + } + else /* Normal PCM record on Channel 0 */ + { + /* reset and disable channel */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_RESET, FUNCTRL0 + 2); + oss_udelay (10); + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_RESET, FUNCTRL0 + 2); + + OUTL (devc->osdev, dmap->dmabuf_phys, CH0_FRAME1); + OUTW (devc->osdev, (dmap->bytes_in_use >> portc->adcfmt) - 1, + CH0_FRAME2); + OUTW (devc->osdev, (dmap->fragment_size >> portc->adcfmt), + CH0_FRAME2 + 2); + + /* set channel 0 to record mode */ + OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) | CM_CH0_RECORD, + FUNCTRL0); + portc->chan0_rec = 1; + } +} + +/*ARGSUSED*/ +static int +cmpci_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + switch (devc->dev_mode) + { + case DEFAULT_MODE: + set_adc_rate (dev, CHAN0); + set_adc_fmt (dev, CHAN0); + setup_record (dev, CHAN0); + break; + + case DUALDAC_MODE: + cmn_err (CE_WARN, "Cannot record because DUALDAC mode is ON.\n"); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EIO; + + case SPDIFIN_MODE: + if (portc->speed < 44100) + { + cmn_err (CE_WARN, + "Cannot record spdif at sampling rate less than 44.1Khz.\n"); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EIO; + } + set_adc_rate (dev, CHAN1); + set_adc_fmt (dev, CHAN1); + setup_record (dev, CHAN1); + break; + } + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +setup_play (int dev, int chan_type) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_out; + + if (chan_type == CHAN0) + { + /* reset channel */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH0_RESET, FUNCTRL0 + 2); + oss_udelay (10); + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH0_RESET, FUNCTRL0 + 2); + oss_udelay (10); + + /* Now set the buffer address/sizes */ + OUTL (devc->osdev, dmap->dmabuf_phys, CH0_FRAME1); + OUTW (devc->osdev, (dmap->bytes_in_use >> portc->dacfmt) - 1, + CH0_FRAME2); + OUTW (devc->osdev, (dmap->fragment_size >> portc->dacfmt), + CH0_FRAME2 + 2); + + /* set channel 0 to play mode */ + OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) & CM_CH0_PLAY, FUNCTRL0); + portc->chan0_play = 1; + + /* setup spdif output on CHAN A , disable CHAN B spdif */ + if (devc->spdif_enabled) + { + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 8), + FUNCTRL1); + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 9), + FUNCTRL1); + } + } + else + { + /* reset and disable channel */ + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) | CM_CH1_RESET, FUNCTRL0 + 2); + oss_udelay (10); + OUTB (devc->osdev, + INB (devc->osdev, FUNCTRL0 + 2) & ~CM_CH1_RESET, FUNCTRL0 + 2); + oss_udelay (10); + + /* Handle 4/5/6 channel mode */ + if (portc->channels < 4) + { + /* check if 4speaker mode is enabled from mixer or not */ + if (devc->mode_4spk) + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + + /* disable 4channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29), + CHFORMAT); + /* disable 5 channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000), + CHFORMAT); + /* disable 6channel mode out CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15), + LEGACY_CTRL); + /* disable 8 channel decode on CHAN B - only for CMI8768 */ + if (devc->model == MDL_CM8768) + OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20, + MISC2_CTRL); + /* Set NXCNG */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(0x80000000), + LEGACY_CTRL); + } + + if ((portc->channels == 4) && (devc->chiprev > 37)) + { + /* disable 4 speaker mode */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + /* enable 4channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (1 << 29), + CHFORMAT); + + /* disable 5 channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000), + CHFORMAT); + /* disable 6channel mode out CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15), + LEGACY_CTRL); + /* disable center/bass channel */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 7), + MISC_CTRL); + /* disable bass */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 12), + LEGACY_CTRL); + + /* disable 8 channel decode on CHAN B - only for CMI8768 */ + if (devc->model == MDL_CM8768) + OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20, + MISC2_CTRL); + /* Set NXCNG */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(0x80000000), + LEGACY_CTRL); + } + + if ((portc->channels == 6) && (devc->chiprev > 37)) + { + /* disable 4 speaker mode */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + /* disable 4channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29), + CHFORMAT); + + /* enable center channel */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7), + MISC_CTRL); + /* enable bass */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12), + LEGACY_CTRL); + /* enable 5 channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) | (0x80000000), + CHFORMAT); + /* enable 6 channel decode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 15), + LEGACY_CTRL); + + /* disable 8 channel decode on CHAN B - only for CMI8768 */ + if (devc->model == MDL_CM8768) + OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) & ~0x20, + MISC2_CTRL); + + /* Set NXCNG */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (0x80000000), + LEGACY_CTRL); + } + + if ((portc->channels == 8) && (devc->model == MDL_CM8768)) + { + /* disable 4 speaker mode */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) & ~(1 << 26), + MISC_CTRL); + /* disable 4channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(1 << 29), + CHFORMAT); + + /* enable center channel */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7), + MISC_CTRL); + /* enable bass channel */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12), + LEGACY_CTRL); + /* disable 5 channel mode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, CHFORMAT) & ~(0x80000000), + CHFORMAT); + /* disable 6 channel decode on CHAN B */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) & ~(1 << 15), + LEGACY_CTRL); + + /* enable 8 channel decode on CHAN B */ + OUTB (devc->osdev, INB (devc->osdev, MISC2_CTRL) | 0x20, + MISC2_CTRL); + /* Set NXCNG */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (0x80000000), + LEGACY_CTRL); + } + + /* Now set the buffer address/sizes */ + OUTL (devc->osdev, dmap->dmabuf_phys, CH1_FRAME1); + OUTW (devc->osdev, (dmap->bytes_in_use >> portc->dacfmt) - 1, + CH1_FRAME2); + OUTW (devc->osdev, (dmap->fragment_size >> portc->dacfmt), + CH1_FRAME2 + 2); + + + /* set channel 1 to play mode */ + OUTB (devc->osdev, INB (devc->osdev, FUNCTRL0) & CM_CH1_PLAY, FUNCTRL0); + portc->chan1_play = 1; + + /* setup spdif output on CHAN B , disable CHAN A spdif */ + if (devc->spdif_enabled) + { + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) | (1 << 9), + FUNCTRL1); + OUTL (devc->osdev, INL (devc->osdev, FUNCTRL1) & ~(1 << 8), + FUNCTRL1); + } + } +} + +/*ARGSUSED*/ +static int +cmpci_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if ((portc->bits == AFMT_AC3) && devc->can_ac3) + { + portc->bits = 16; + portc->channels = 2; + cmpci_outsw (devc->mixer_dev, SPDIF_PLAY, SNDCTL_MIX_WRITE, 1); + setup_ac3 (devc, portc, 1); + } + else + setup_ac3 (devc, portc, 0); + + switch (devc->dev_mode) + { + case DEFAULT_MODE: + /* set speed */ + set_dac_rate (dev, CHAN1); + /* set format */ + set_dac_fmt (dev, CHAN1); + /* set buffer address/size and other setups */ + setup_play (dev, CHAN1); + break; + + case DUALDAC_MODE: + if (dev == devc->portc[0].audiodev) + { + set_dac_rate (dev, CHAN1); + set_dac_fmt (dev, CHAN1); + setup_play (dev, CHAN1); + setup_ac3 (devc, portc, 0); + } + if (dev == devc->portc[1].audiodev) + { + set_dac_rate (dev, CHAN0); + set_dac_fmt (dev, CHAN0); + setup_play (dev, CHAN0); + setup_ac3 (devc, portc, 0); + } + break; + + case SPDIFIN_MODE: + set_dac_rate (dev, CHAN0); + set_dac_fmt (dev, CHAN0); + setup_play (dev, CHAN0); + setup_ac3 (devc, portc, 0); + break; + } + + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static int +cmpci_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + cmpci_devc *devc = audio_engines[dev]->devc; + cmpci_portc *portc = audio_engines[dev]->portc; + unsigned int ptr = 0; + oss_native_word flags; + + if (!(portc->open_mode & direction)) + return 0; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + if (direction == PCM_ENABLE_INPUT) + { + if (portc->chan0_rec) + ptr = INW (devc->osdev, CH0_FRAME1); + if (portc->chan1_rec) + ptr = INW (devc->osdev, CH1_FRAME1); + } + + if (direction == PCM_ENABLE_OUTPUT) + { + if (portc->chan0_play) + ptr = INW (devc->osdev, CH0_FRAME1); + if (portc->chan1_play) + ptr = INW (devc->osdev, CH1_FRAME1); + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return ptr % dmap->bytes_in_use; +} + +static audiodrv_t cmpci_audio_driver = { + cmpci_audio_open, + cmpci_audio_close, + cmpci_audio_output_block, + cmpci_audio_start_input, + cmpci_audio_ioctl, + cmpci_audio_prepare_for_input, + cmpci_audio_prepare_for_output, + cmpci_audio_reset, + NULL, + NULL, + cmpci_audio_reset_input, + cmpci_audio_reset_output, + cmpci_audio_trigger, + cmpci_audio_set_rate, + cmpci_audio_set_format, + cmpci_audio_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, /* cmpci_alloc_buffer, */ + NULL, /* cmpci_free_buffer, */ + NULL, + NULL, + cmpci_get_buffer_pointer +}; + +#ifdef OBSOLETED_STUFF +static void +attach_mpu (cmpci_devc * devc) +{ + unsigned int base; + + if (devc->chiprev == 33) + base = 0x330; /* Chiprev033 doen't have bas+0x40 */ + else + base = MPU_MIRROR; /* base+0x40 is MPU PCI mirror */ + + uart401_init (&devc->uart401devc, devc->osdev, base, "Cmedia MIDI UART"); + devc->uart401_attached = 1; +} +#endif + +static int +init_cmpci (cmpci_devc * devc) +{ + oss_native_word val; + int first_dev = 0; + int i; + + devc->fm_attached = 0; + +/* + * Enable BusMasterMode and IOSpace Access + */ + /* Check the model number of the chip */ + val = INL (devc->osdev, INT_HLDCLR) & 0xff000000; + + if (!val) + { + val = INL (devc->osdev, CHFORMAT) & 0x1f000000; + if (!val) + { + devc->chiprev = 33; + devc->can_ac3 = 0; + devc->max_channels = 6; + } + else + { + devc->chiprev = 37; + devc->can_ac3 = 1; + devc->max_channels = 6; + } + } + else + { + if (val & 0x04000000) + { + devc->chiprev = 39; + devc->can_ac3 = 1; + devc->max_channels = 6; + } + if (val & 0x08000000) + { + devc->chiprev = 55; + devc->can_ac3 = 1; + devc->max_channels = 6; + } + if (val & 0x28000000) + { + devc->chiprev = 68; + devc->can_ac3 = 1; + devc->model = MDL_CM8768; + devc->max_channels = 8; + devc->chip_name = "CMedia CM8768"; + } + } + + /* enable uart, joystick in Function Control Reg1 */ + OUTB (devc->osdev, INB (devc->osdev, FUNCTRL1) | 0x06, FUNCTRL1); + /* enable FM */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 19), MISC_CTRL); + OUTB (devc->osdev, 0, INT_HLDCLR + 2); /* disable ints */ + OUTB (devc->osdev, 0, FUNCTRL0 + 2); /* reset channels */ + +#ifdef OBSOLETED_STUFF + attach_mpu (devc); +#endif + + /* install the CMPCI mixer */ + if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + "CMedia CMPCI", + &cmpci_mixer_driver, + sizeof (mixer_driver_t), + devc)) < 0) + { + return 0; + } + + mixer_devs[devc->mixer_dev]->hw_devc = devc; + mixer_devs[devc->mixer_dev]->priority = 1; /* Possible default mixer candidate */ + + cmpci_mixer_reset (devc->mixer_dev); + mixer_ext_set_init_fn (devc->mixer_dev, cmpci_mix_init, 25); + OUTB (devc->osdev, 0xF, MIXER2); + + /* setup 4speaker output */ + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 26), MISC_CTRL); + /* enable subwoofer/center channel */ + OUTL (devc->osdev, INL (devc->osdev, LEGACY_CTRL) | (1 << 12), LEGACY_CTRL); + OUTL (devc->osdev, INL (devc->osdev, MISC_CTRL) | (1 << 7), MISC_CTRL); + + for (i = 0; i < MAX_PORTC; i++) + { + char tmp_name[100]; + cmpci_portc *portc = &devc->portc[i]; + int caps = ADEV_AUTOMODE; + + if (i == 0) + { + sprintf (tmp_name, "%s (rev %0d)", devc->chip_name, devc->chiprev); + caps |= ADEV_DUPLEX; + } + else + { + sprintf (tmp_name, "%s (playback only)", devc->chip_name); + caps |= ADEV_NOINPUT; + } + if ((portc->audiodev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &cmpci_audio_driver, + sizeof (audiodrv_t), + caps, + AFMT_U8 | AFMT_S16_LE | + AFMT_AC3, devc, -1)) < 0) + { + return 0; + } + else + { + if (i == 0) + first_dev = portc->audiodev; + audio_engines[portc->audiodev]->portc = portc; + audio_engines[portc->audiodev]->rate_source = first_dev; + audio_engines[portc->audiodev]->caps = + PCM_CAP_ANALOGOUT | PCM_CAP_ANALOGIN | PCM_CAP_DIGITALOUT | + PCM_CAP_DIGITALIN; + audio_engines[portc->audiodev]->min_rate = 5000; + audio_engines[portc->audiodev]->max_rate = 48000; + audio_engines[portc->audiodev]->caps |= PCM_CAP_FREERATE; + audio_engines[portc->audiodev]->min_channels = 2; + audio_engines[portc->audiodev]->max_channels = devc->max_channels; + audio_engines[portc->audiodev]->vmix_flags = VMIX_MULTIFRAG; + audio_engines[portc->audiodev]->dmabuf_alloc_flags |= + DMABUF_SIZE_16BITS; + portc->open_mode = 0; + portc->audio_enabled = 0; + audio_engines[portc->audiodev]->mixer_dev = devc->mixer_dev; + devc->dev_mode = DEFAULT_MODE; + devc->spdif_enabled = 0; +#ifdef CONFIG_OSS_VMIX + if (i == 0) + vmix_attach_audiodev(devc->osdev, portc->audiodev, -1, 0); +#endif + } + } + return 1; +} + +int +oss_cmpci_attach (oss_device_t * osdev) +{ + unsigned char pci_irq_line, pci_revision; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr; + int err; + cmpci_devc *devc; + + DDB (cmn_err (CE_CONT, "Entered CMEDIA CMPCI attach routine\n")); + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != CMEDIA_VENDOR_ID + || ((device != CMEDIA_CM8738) && (device != CMEDIA_CM8338A) + && (device != CMEDIA_CM8338B))) + 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); + + DDB (cmn_err (CE_WARN, "CMPCI I/O base %04x\n", pci_ioaddr)); + + if (pci_ioaddr == 0) + { + cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n"); + return 0; + } + + if (pci_irq_line == 0) + { + cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d).\n", pci_irq_line); + 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->irq = pci_irq_line; + + /* Map the IO Base address */ + devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr); + + /* Remove I/O space marker in bit 0. */ + devc->base &= ~3; + + /* set the PCI_COMMAND register to master mode */ + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + switch (device) + { + case CMEDIA_CM8738: + devc->model = MDL_CM8738; + devc->chip_name = "CMedia CM8738"; + devc->max_channels = 6; + break; + + case CMEDIA_CM8338A: + devc->model = MDL_CM8338A; + devc->chip_name = "CMedia CM8338A"; + devc->max_channels = 6; + break; + + case CMEDIA_CM8338B: + devc->model = MDL_CM8338B; + devc->chip_name = "CMedia CM8338B"; + devc->max_channels = 6; + break; + } + + 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, cmpciintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't allocate IRQ%d, err=%d\n", pci_irq_line, err); + return 0; + } + + return init_cmpci (devc); /* Detected */ +} + +int +oss_cmpci_detach (oss_device_t * osdev) +{ + cmpci_devc *devc = (cmpci_devc *) osdev->devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + /* disable Interrupts */ + OUTB (devc->osdev, 0, INT_HLDCLR + 2); + + /* disable channels */ + OUTB (devc->osdev, 0, FUNCTRL0 + 2); + + /* uninstall UART401 */ + if (devc->uart401_attached) + uart401_disable (&devc->uart401devc); + + oss_unregister_interrupts (devc->osdev); + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + + oss_unregister_device (osdev); + return 1; +} diff --git a/kernel/drv/oss_cmpci/oss_cmpci.man b/kernel/drv/oss_cmpci/oss_cmpci.man new file mode 100644 index 0000000..1d817b3 --- /dev/null +++ b/kernel/drv/oss_cmpci/oss_cmpci.man @@ -0,0 +1,56 @@ +NAME +oss_cmpci - CMedia CMI8738/8768 audio driver. + +DESCRIPTION +Open Sound System driver for CMedia Electronics CMI8738/8768 audio + +CMI87xx device characteristics: + o 8/16 bit playback/record + o mono/stereo/4ch/5.1ch playback + o 8KHz to 48Khz sample rate. + + MIXER PANEL +The CMedia chip provides some unique features that are set up +by the Mixer chip. Running ossxmix will display the CMI8738 mixer +panel. + +Most of the sliders and buttons are self evident. However there +are some options that need explaining: + +Dual Dac: Enabling this button sets the CMPCI device as two +separate output devices with /dev/dsp1 audio going to the front and +/dev/dsp0 going to the rear outputs. Separate audio streams can +be send to the device simultaneously. + +Speaker Mode: The audio can be sent just to the front speakers or +it can be sent simultaneously to all speakers in the "Spread" mode. + +AC3 passthrough only works on Models 037 and higher. This is because of +a hardware bug in the earlier models so check the model number +(ossinfo -a). + +SPDIF: + +o Enable will enable SPDIF output. + +o Rec will allow you to record from the SPDIF device. Note that when +you have SPDIF recording enabled, you cannot play 4/6 channel audio. + +o Polarity - certain models require you to flip the bit otherwise you + get distorted audio. + +o IMon - monitor input via SPDIF in. + +o Optical - sets the SPDIF to Optical (TOSLINK) or RCA Jacks interface. + + + +OPTIONS +None + +FILES +CONFIGFILEPATH/oss_cmpci.conf Device configuration file + +AUTHOR +4Front Technologies + |