diff options
Diffstat (limited to 'kernel/drv/oss_envy24ht/oss_envy24ht.c')
-rw-r--r-- | kernel/drv/oss_envy24ht/oss_envy24ht.c | 2411 |
1 files changed, 2411 insertions, 0 deletions
diff --git a/kernel/drv/oss_envy24ht/oss_envy24ht.c b/kernel/drv/oss_envy24ht/oss_envy24ht.c new file mode 100644 index 0000000..11fb75b --- /dev/null +++ b/kernel/drv/oss_envy24ht/oss_envy24ht.c @@ -0,0 +1,2411 @@ +/* + * Purpose: VIA ENVY24HT chipset driver. + */ +/* + * + * 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_envy24ht_cfg.h" +#include <oss_pci.h> + +#define SUPPORTED_FORMAT AFMT_S32_LE + +#include <spdif.h> +#include "envy24ht.h" + +/* extern envy24ht_auxdrv_t envy24ht_viaref_auxdrv; */ +extern envy24ht_auxdrv_t envy24ht_ac97_auxdrv; +extern envy24ht_auxdrv_t envy24ht_revo51_auxdrv; +extern envy24ht_auxdrv_t envy24ht_revo71_auxdrv; +extern envy24ht_auxdrv_t envy24ht_aureon_auxdrv; +extern envy24ht_auxdrv_t envy24ht_julia_auxdrv; +extern envy24ht_auxdrv_t envy24ht_ap192_auxdrv; + +#define OUTCH_NAMES "front c/l side rear" +static char channel_names[4][10] = { + "front", + "c/l", + "side", + "rear" +}; + +static card_spec models[] = { + {0x17241412, "Generic Envy24PT motherboard audio", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + {0xf641270f, "Chaintech ZNF3-150", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + {0x2723270f, "Chaintech 9CJS", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + {0x50361297, "Shuttle SN25P", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + {0x020010b0, "Gainward Hollywood Envy24HT-S", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + {0x36311412, "M Audio Revolution 5.1", 6, 2, + MF_SPDIFOUT | MF_192K, &envy24ht_revo51_auxdrv}, + {0x36301412, "M Audio Revolution 7.1", 8, 2, + MF_SPDIFOUT | MF_192K, &envy24ht_revo71_auxdrv}, + {SSID_AUREON_SPACE, "Terratec Aureon 7.1 Space", 8, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, + {SSID_AUREON_UNIVERSE, "Terratec Aureon 7.1 Universe", 8, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, + {SSID_AUREON_SKY, "Terratec Aureon 7.1 Sky", 8, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, + {SSID_PRODIGY71, "Audiotrak Prodigy 7.1", 8, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_aureon_auxdrv}, + {SSID_JULIA, "Ego Systems Juli@", 2, 2, + MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI, + &envy24ht_julia_auxdrv}, + {SSID_PHASE28, "Terratec PHASE 28", 8, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97 | MF_MIDI, &envy24ht_aureon_auxdrv}, + {SSID_AP192, "M-Audio Audiophile 192", 2, 2, + MF_SPDIFOUT | MF_SPDIFIN | MF_192K | MF_NOAC97 | MF_MIDI, + &envy24ht_ap192_auxdrv}, + {0x24031412, "VIA Vinyl Tremor Audio", 6, 2, + MF_SPDIFOUT | MF_ENVY24PT, &envy24ht_ac97_auxdrv}, + /* XXX Do a separate auxdrv, to adjust for Envy24HT-S and other differences. */ + {SSID_PRODIGYHD2, "Audiotrak Prodigy HD2", 2, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv}, + {SSID_PRODIGYHD2_ADE, "Audiotrak Prodigy HD2 Advance DE", 2, 2, + MF_SPDIFOUT | MF_192K | MF_NOAC97, &envy24ht_ap192_auxdrv}, + /* {0x17241412, "VIA Envy24HT reference design", 6, 2, */ + /* MF_SPDIFOUT | MF_MIDI, &envy24ht_viaref_auxdrv}, */ + {0} +}; + +static struct speed_sel speed_tab[] = { + { + 8000, 0x06} + , + { + 9600, 0x03} + , + { + 11025, 0x0a} + , + { + 12000, 0x02} + , + { + 16000, 0x05} + , + { + 22050, 0x09} + , + { + 24000, 0x01} + , + { + 32000, 0x04} + , + { + 44100, 0x08} + , + { + 48000, 0x00} + , + { + 64000, 0x0f} + , + { + 88200, 0x0b} + , + { + 96000, 0x07} + , + { + 176400, 0x0c} + , + { + 192000, 0x0e} + , + {-1, 0x10} + , +}; + +static const envy24ht_auxdrv_t dummy_auxdrv = { NULL }; + +static int +ac97_read (void *devc_, int wAddr) +{ + envy24ht_devc *devc = devc_; + oss_native_word flags; + int n = 0, dat; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30); + OUTB (devc->osdev, wAddr, devc->mt_base + 0x04); + OUTB (devc->osdev, 0x10, devc->mt_base + 0x05); /* Codec read */ + + n = 0; + while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x10); + dat = INW (devc->osdev, devc->mt_base + 0x06); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return dat; +} + +static int +ac97_write (void *devc_, int wAddr, int wData) +{ + envy24ht_devc *devc = devc_; + oss_native_word flags; + int n = 0; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + while (n++ < 10000 && INB (devc->osdev, devc->mt_base + 0x05) & 0x30); + + OUTB (devc->osdev, wAddr, devc->mt_base + 0x04); + OUTW (devc->osdev, wData, devc->mt_base + 0x06); + OUTB (devc->osdev, 0x20, devc->mt_base + 0x05); /* Codec write */ + + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return 0; +} + +static __inline__ int +input_avail (envy24ht_devc * devc) +{ + unsigned char status; + + status = INB (devc->osdev, devc->ccs_base + 0x0b); + return status & 0x1f; /* Number of bytes in RX queue */ +} + +static __inline__ int +output_ready (envy24ht_devc * devc) +{ + unsigned char status; + + status = INB (devc->osdev, devc->ccs_base + 0x0a); + return (31 - (status & 0x1f)) > 0; /* Number of free bytes in TX queue */ +} + +static __inline__ void +midi_cmd (envy24ht_devc * devc, unsigned char cmd) +{ + OUTB (devc->osdev, cmd, devc->ccs_base + 0x0d); +} + +static __inline__ int +midi_read (envy24ht_devc * devc) +{ + return INB (devc->osdev, devc->ccs_base + 0x0c); +} + +static __inline__ void +midi_write (envy24ht_devc * devc, unsigned char byte) +{ + OUTB (devc->osdev, byte, devc->ccs_base + 0x0c); +} + +static void reset_midi (envy24ht_devc * devc); +static void enter_uart_mode (envy24ht_devc * devc); + +static void +midi_input_loop (envy24ht_devc * devc) +{ + int n = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + while (input_avail (devc) && n++ < 33) + { + unsigned char c = midi_read (devc); + + devc->input_byte = c; + if (devc->midi_opened & OPEN_READ && devc->midi_input_intr) + devc->midi_input_intr (devc->midi_dev, c); + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static void +midiintr (envy24ht_devc * devc) +{ + int status; + + status = INB (devc->osdev, devc->ccs_base + 0x02); + if (status & 0x80) + midi_input_loop (devc); + if ((status & 0x20)) + { + OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0x20, + devc->ccs_base + 0x01); + +#if 0 + if (devc->midi_output_intr) + devc->midi_output_intr (devc->midi_dev); +#endif + } +} + +/*ARGSUSED*/ +static int +midi_open (int dev, int mode, oss_midi_inputbyte_t inputbyte, + oss_midi_inputbuf_t inputbuf, oss_midi_outputintr_t outputintr) +{ + envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; + int n = 0, tmp; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (devc->midi_opened & mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + devc->midi_opened |= mode; + + if (mode & OPEN_READ) + { + while (n++ < 33 && input_avail (devc)) + midi_read (devc); + + devc->midi_input_intr = inputbyte; + devc->midi_output_intr = outputintr; + } + enter_uart_mode (devc); + tmp = INB (devc->osdev, devc->ccs_base + 0x01); + if (mode & OPEN_READ) + tmp &= ~0x80; + OUTB (devc->osdev, tmp, devc->ccs_base + 0x01); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +static void +midi_close (int dev, int mode) +{ + envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + reset_midi (devc); + enter_uart_mode (devc); + reset_midi (devc); + OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) | 0xa0, + devc->ccs_base + 0x01); + devc->midi_opened &= ~mode; + devc->midi_input_intr = NULL; + devc->midi_output_intr = NULL; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +static int +midi_out (int dev, unsigned char midi_byte) +{ + envy24ht_devc *devc = (envy24ht_devc *) midi_devs[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (!output_ready (devc)) + { + OUTB (devc->osdev, INB (devc->osdev, devc->ccs_base + 0x01) & ~0x20, + devc->ccs_base + 0x01); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; + } + + midi_write (devc, midi_byte); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 1; +} + +/*ARGSUSED*/ +static int +midi_ioctl (int dev, unsigned cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static midi_driver_t envy24ht_midi_driver = { + midi_open, + midi_close, + midi_ioctl, + midi_out +}; + +static void +enter_uart_mode (envy24ht_devc * devc) +{ + devc->input_byte = 0; + midi_cmd (devc, 1); +} + +void +attach_midi (envy24ht_devc * devc) +{ + char name[128]; + enter_uart_mode (devc); + + sprintf (name, "%s input", devc->model_data->product); + devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t), + MFLAG_INPUT, devc, devc->osdev); + sprintf (name, "%s output", devc->model_data->product); + devc->midi_dev = oss_install_mididev (OSS_MIDI_DRIVER_VERSION, "ENVY24HT", name, &envy24ht_midi_driver, sizeof (midi_driver_t), + MFLAG_OUTPUT, devc, devc->osdev); + devc->midi_opened = 0; + devc->midi_attached = 1; +} + +static void +reset_midi (envy24ht_devc * devc) +{ + /* + * Send the RESET command. Try again if no success at the first time. + */ + + midi_cmd (devc, 0); +} + +void +unload_midi (envy24ht_devc * devc) +{ + if (devc->midi_attached) + reset_midi (devc); + devc->midi_attached = 0; +} + +static int +envy24htintr (oss_device_t * osdev) +{ + envy24ht_devc *devc = osdev->devc; + int port, status, mt_status, serviced = 0; + + status = INB (devc->osdev, devc->ccs_base + 0x02); + if (status != 0) + serviced = 1; + + if (status & 0xa0) + midiintr (devc); + +/* + * Handle audio interrupts + */ + mt_status = INB (devc->osdev, devc->mt_base + 0x00); + + if (mt_status & 0x08) /* FIFO underrun/overrun */ + { + OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x1A), + devc->mt_base + 0x1A); + serviced = 1; + } + + if ((status & 0x10) || mt_status != 0) + { + + for (port = 0; port < devc->nr_outdevs; port++) + { + envy24ht_portc *portc = &devc->play_portc[port]; + + if (mt_status & portc->mask) + { + oss_audio_outputintr (portc->dev, 1); + } + } + + for (port = 0; port < devc->nr_indevs; port++) + { + envy24ht_portc *portc = &devc->rec_portc[port]; + + if (mt_status & portc->mask) + { + oss_audio_inputintr (portc->dev, 0); + } + } + } + OUTB (devc->osdev, mt_status, devc->mt_base + 0x00); + OUTB (devc->osdev, status, devc->ccs_base + 0x02); + + return serviced; +} + +/*ARGSUSED*/ +static int +envy24ht_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static mixer_driver_t envy24ht_mixer_driver = { + envy24ht_mixer_ioctl +}; + +static int +envy24ht_set_route (int dev, int ctrl, unsigned int cmd, int value) +{ + envy24ht_devc *devc = mixer_devs[dev]->hw_devc; + unsigned int tmp; + oss_native_word flags; + + if (ctrl < 0 || ctrl > 8) + return OSS_EINVAL; + + if (cmd == SNDCTL_MIX_READ) + { + tmp = INL (devc->osdev, devc->mt_base + 0x2c); + + switch (ctrl) + { + case 0: + tmp >>= 8; + break; + case 2: + tmp >>= 11; + break; + case 4: + tmp >>= 14; + break; + case 6: + tmp >>= 17; + break; + } + + tmp = (tmp & 0x03) >> 1; + + if (tmp == 3) + return 2; + return tmp; + } + else if (cmd == SNDCTL_MIX_WRITE) + { + int left_shift = 0, right_shift = 0; + + static const unsigned char cnv_tab[3] = { 0x00, 0x02, 0x06 }; + if (value < 0 || value > 2) + return OSS_EINVAL; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + tmp = INL (devc->osdev, devc->mt_base + 0x2c); + + switch (ctrl) + { + case 0: + left_shift = 8; + right_shift = 20; + break; + case 2: + left_shift = 11; + right_shift = 23; + break; + case 4: + left_shift = 14; + right_shift = 26; + break; + case 6: + left_shift = 17; + right_shift = 29; + break; + case 8: + left_shift = 0; + right_shift = 3; + break; + } + + tmp &= ~(0x7 << left_shift); + tmp &= ~(0x7 << right_shift); + tmp |= cnv_tab[value] << left_shift; + if (ctrl != 8) + tmp |= (cnv_tab[value] + 1) << right_shift; + else + tmp |= cnv_tab[value] << right_shift; + + OUTL (devc->osdev, tmp, devc->mt_base + 0x2c); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + + return value; + } + return OSS_EINVAL; +} + +static int +read_peak (envy24ht_devc * devc, int ch) +{ + int tmp; + + if (ch >= 22) + return 0; + + OUTB (devc->osdev, ch, devc->mt_base + 0x3e); + tmp = INB (devc->osdev, devc->mt_base + 0x3f); + + return tmp; +} + +/*ARGSUSED*/ +static int +envy24ht_get_peak (int dev, int ctrl, unsigned int cmd, int value) +{ + static const unsigned char peak_cnv[256] = { + 0, 18, 29, 36, 42, 47, 51, 54, 57, 60, 62, 65, 67, 69, 71, 72, + 74, 75, 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 89, 90, + 91, 92, 93, 93, 94, 95, 95, 96, 97, 97, 98, 99, 99, 100, 100, 101, + 101, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 107, 107, 108, 108, + 108, + 109, 109, 110, 110, 110, 111, 111, 111, 112, 112, 113, 113, 113, 114, 114, + 114, + 115, 115, 115, 115, 116, 116, 116, 117, 117, 117, 118, 118, 118, 118, 119, + 119, + 119, 119, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, + 123, + 123, 123, 124, 124, 124, 124, 125, 125, 125, 125, 125, 126, 126, 126, 126, + 126, + 127, 127, 127, 127, 127, 128, 128, 128, 128, 128, 129, 129, 129, 129, 129, + 130, + 130, 130, 130, 130, 130, 131, 131, 131, 131, 131, 131, 132, 132, 132, 132, + 132, + 132, 133, 133, 133, 133, 133, 133, 134, 134, 134, 134, 134, 134, 134, 135, + 135, + 135, 135, 135, 135, 135, 136, 136, 136, 136, 136, 136, 136, 137, 137, 137, + 137, + 137, 137, 137, 138, 138, 138, 138, 138, 138, 138, 138, 139, 139, 139, 139, + 139, + 139, 139, 139, 140, 140, 140, 140, 140, 140, 140, 140, 141, 141, 141, 141, + 141, + 141, 141, 141, 141, 142, 142, 142, 142, 142, 142, 142, 142, 142, 143, 143, + 143, + 143, 143, 143, 143, 143, 143, 144, 144, 144, 144, 144, 144, 144, 144, 144, + 144, + }; + + envy24ht_devc *devc = mixer_devs[dev]->hw_devc; + + int i, orign, n = -1, left = 0, right = 0; + + for (i = 0; i < 12 && n == -1; i++) + if (ctrl & (1 << i)) + n = i; + + if (n == -1) + return OSS_EINVAL; + + orign = n; + if (ctrl & 0x80000000) + n += 10; /* Recording stream */ + + if (cmd == SNDCTL_MIX_READ) + { + left = read_peak (devc, n); + if (ctrl & (1 << (orign + 1))) /* Stereo mode? */ + right = read_peak (devc, n + 1); + else + right = left; + + left = peak_cnv[left]; + right = peak_cnv[right]; + return left | (right << 8); + } + + return OSS_EINVAL; +} + +/*ARGSUSED*/ +static int +create_peak_mixer (int dev, envy24ht_devc * devc, int root) +{ + int i, mask = devc->outportmask, group, err, num, skip; + int nc = devc->nr_play_channels; + char tmp[64]; + + if ((group = mixer_ext_create_group (dev, 0, "ENVY24_PEAK")) < 0) + return group; + + skip = 2; + + for (i = 0; i < nc; i += skip) + { + + num = 1 << i; + if (!(mask & num)) + continue; /* Not present */ + + { + if (i == 8) + strcpy (tmp, "ENVY24_SPDIFOUT"); + else + sprintf (tmp, devc->channel_names[i / 2], i + 1, i + 2); + + num |= 1 << (i + 1); + if ((err = mixer_ext_create_control (dev, group, + num, envy24ht_get_peak, + MIXT_STEREOPEAK, + tmp, 144, + MIXF_READABLE | MIXF_DECIBEL)) < + 0) + return err; + } + } + + mask = devc->inportmask; + for (i = 0; i < devc->nr_rec_channels; i += skip) + { + + num = 1 << i; + if (!(mask & num)) + continue; /* Not present */ + + num |= 0x80000000; /* Input flag */ + + { + if (i == 8) + strcpy (tmp, "ENVY24_SPDIFIN"); + else + strcpy (tmp, "ENVY24_IN"); + + num |= 1 << (i + 1); + if ((err = mixer_ext_create_control (dev, group, + num, envy24ht_get_peak, + MIXT_STEREOPEAK, + tmp, 144, + MIXF_READABLE | MIXF_DECIBEL)) < + 0) + return err; + } + } + + return 0; +} + +/*ARGSUSED*/ +static int +create_rout_mixer (int dev, envy24ht_devc * devc, int root) +{ + int i, mask = devc->outportmask, group, ret, num; + char *name; + + if ((group = + mixer_ext_create_group_flags (dev, 0, "ENVY24_ROUTE", MIXF_FLAT)) < 0) + return group; + + for (i = 0; i < 8; i += 2) + { + + num = 1 << i; + if (!(mask & num)) + continue; /* Not present */ + + name = devc->channel_names[i / 2]; + + if ((ret = mixer_ext_create_control (dev, group, + i, + envy24ht_set_route, + MIXT_ENUM, name, 3, + MIXF_READABLE | + MIXF_WRITEABLE)) < 0) + return ret; + mixer_ext_set_strings (dev, ret, "DMA ANALOGIN DIGITALIN", 0); + } + + if (devc->model_data == NULL) + { + cmn_err (CE_CONT, "Internal error: No model data\n"); + return 0; + } + + mask = devc->inportmask; + + if (devc->model_data->flags & MF_SPDIFOUT) + { + if ((ret = mixer_ext_create_control (dev, group, + 8, envy24ht_set_route, + MIXT_ENUM, + "ENVY24_SPDIFOUT", 3, + MIXF_READABLE | + MIXF_WRITEABLE)) < 0) + return ret; + } + + return 0; +} + +/* + * S/PDIF lowlevel driver + */ +/*ARGSUSED*/ +static int +default_reprogram_device (void *_devc, void *_portc, + oss_digital_control * ctl, unsigned int mask) +{ + unsigned char c; + unsigned short cbits = 0; + envy24ht_devc *devc = _devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + + cbits |= (ctl->rate_bits & 0x0f) << 12; /* Sample rate */ + + if (ctl->out_vbit == VBIT_ON) + cbits |= 0x8000; /* Turn on the V bit */ + + cbits |= ctl->cbitout[0] & 0x07; /* Consumer/pro, audio/data, copyright */ + cbits |= (!!(ctl->cbitout[1] & 0x80)) << 11; /* Generation level */ + cbits |= (ctl->cbitout[1] & 0x7f) << 4; /* Category code */ + cbits |= (ctl->emphasis_type & 1) << 3; /* Pre-emphasis */ + + if (cbits != INW (devc->osdev, devc->mt_base + 0x3c)) + { + c = INB (devc->osdev, devc->ccs_base + 0x07); + OUTB (devc->osdev, c & ~0x80, devc->ccs_base + 0x07); /* Disable S/PDIF transmitter */ + OUTW (devc->osdev, cbits, devc->mt_base + 0x3c); + OUTB (devc->osdev, c | 0x80, devc->ccs_base + 0x07); /* (Re)enable S/PDIF transmitter */ + } + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; +} + +spdif_driver_t default_spdif_driver = { +/* reprogram_device: */ default_reprogram_device, +}; + + +static void +setup_sample_rate (envy24ht_devc * devc) +{ + unsigned char bits; + int change = 0; + + devc->speedbits = bits = speed_tab[devc->pending_speed_sel].speedbits; + if (devc->speed != speed_tab[devc->pending_speed_sel].speed) + change = 1; + devc->speed = devc->pending_speed = + speed_tab[devc->pending_speed_sel].speed; + mixer_devs[devc->mixer_dev]->modify_counter++; + + if (change) + { + oss_spdif_setrate (&devc->spdc, devc->speed); + + if (devc->model_data->svid == SSID_JULIA) + goto JULIA; + + if (devc->syncsource != 0) /* External sync */ + bits |= 0x10; + + if (devc->speed > 120000) + { + OUTB (devc->osdev, 0x08, devc->mt_base + 0x02); /* 128x I2S setup */ + } + else + { + OUTB (devc->osdev, 0x00, devc->mt_base + 0x02); /* 256x I2S setup */ + } + + OUTB (devc->osdev, bits & 0x0f, devc->mt_base + 0x01); /* Sampling rate */ +JULIA: + if (devc->auxdrv->set_rate) + devc->auxdrv->set_rate (devc); + } +} + +static int +envy24ht_set_control (int dev, int ctrl, unsigned int cmd, int value) +{ + envy24ht_devc *devc = mixer_devs[dev]->hw_devc; + oss_native_word flags; + + if (cmd == SNDCTL_MIX_READ) + switch (ctrl) + { + case 1: + return devc->pending_speed_sel; + break; + + case 2: + return devc->syncsource; + break; + + case 3: + return devc->use_src; + break; + + case 5: + return devc->ratelock; + break; + + case 6: + return devc->speed; + break; + + case 7: + return 1; + + default: + return OSS_EIO; + } + + if (cmd == SNDCTL_MIX_WRITE) + switch (ctrl) + { + case 1: + if (value < 0 || value > devc->max_ratesel) + return OSS_EIO; + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (devc->configured_rate_sel != value) + { + devc->configured_rate_sel = value; + if (devc->open_count < 1) + { + devc->pending_speed_sel = devc->configured_rate_sel; + setup_sample_rate (devc); + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return devc->configured_rate_sel; + break; + + case 2: + if (value < 0 || value > 2) + return OSS_EIO; + devc->syncsource = value; + if (devc->model_data->svid == SSID_JULIA) + devc->auxdrv->private1 (devc, value); + return devc->syncsource; + break; + + case 3: + devc->use_src = !!value; + return devc->use_src; + break; + + case 5: + return devc->ratelock = !!value; + break; + + case 6: + return devc->speed; + break; + + case 7: + return 1; + break; + + default: + return OSS_EIO; + } + + return OSS_EINVAL; +} + +static int +envy24ht_mix_init (int dev) +{ + envy24ht_devc *devc = mixer_devs[dev]->hw_devc; + int group, err, n; + + if ((group = + mixer_ext_create_group_flags (dev, 0, "ENVY24", MIXF_FLAT)) < 0) + return group; + + if ((err = create_peak_mixer (dev, devc, group)) < 0) + return err; + + if ((err = create_rout_mixer (dev, devc, group)) < 0) + return err; + + n = devc->max_ratesel + 1; + if ((err = mixer_ext_create_control (dev, group, + 1, envy24ht_set_control, + MIXT_ENUM, + "ENVY24_RATE", n, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + mixer_ext_set_strings (dev, err, + "8000 9600 11025 12000 16000 22050 24000 32000 44100 48000 64000 88200 96000 176400 192000", 0); + + if ((err = mixer_ext_create_control (dev, group, + 2, envy24ht_set_control, + MIXT_ENUM, + "ENVY24_SYNC", 2, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, + 3, envy24ht_set_control, + MIXT_ONOFF, + "ENVY24_SRC", 2, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, + 5, envy24ht_set_control, + MIXT_ONOFF, + "ENVY24_RATELOCK", 2, + MIXF_READABLE | MIXF_WRITEABLE)) < 0) + return err; + + if ((err = mixer_ext_create_control (dev, group, + 6, envy24ht_set_control, + MIXT_VALUE, + "ENVY24_ACTRATE", 192000, + MIXF_READABLE)) < 0) + return err; + + if (devc->auxdrv->mixer_init) + if ((err = devc->auxdrv->mixer_init (devc, dev, 0)) < 0) + return err; + + if ((err = oss_spdif_mix_init (&devc->spdc)) < 0) + return err; + + return 0; +} + +static int +eeprom_read (envy24ht_devc * devc, int pos) +{ + int i, status; + + for (i = 0; i < 0x10000; i++) + { + status = INB (devc->osdev, devc->ccs_base + 0x13); + if (!(status & 1)) + break; + + } + + OUTB (devc->osdev, pos, devc->ccs_base + 0x11); /* Offset */ + OUTB (devc->osdev, 0xa0, devc->ccs_base + 0x10); /* EEPROM read */ + + for (i = 0; i < 0x10000; i++) + { + status = INB (devc->osdev, devc->ccs_base + 0x13); + if (!(status & 1)) + break; + + } + + oss_udelay (1); + return INB (devc->osdev, devc->ccs_base + 0x12); +} + +static void +envy24pt_init (envy24ht_devc * devc) +{ + int gpio; + + gpio = INW (devc->osdev, devc->ccs_base + 0x14); + gpio |= INB (devc->osdev, devc->ccs_base + 0x1e) << 16; + +#if 0 +#define GPBIT(nn) !!(1<<nn) + cmn_err (CE_CONT, "GPIO=%06x\n", gpio); + cmn_err (CE_CONT, "With SPDIF_IN 'optical' connector: %d\n", GPBIT (1)); + cmn_err (CE_CONT, "With SPDIF_IN 'coaxial' connector: %d\n", GPBIT (2)); + + cmn_err (CE_CONT, "AC97 with stereo DAC for 7.1: %d\n", !GPBIT (8)); + cmn_err (CE_CONT, "Extra ADC/DAC connected to S/PDIF pins: %d\n", + GPBIT (9)); + cmn_err (CE_CONT, "S/PDIF connected to RDMA1: %d\n", !GPBIT (10)); + cmn_err (CE_CONT, "Smart 5.1 function supported: %d\n", GPBIT (11)); + cmn_err (CE_CONT, "De-POP function supported: %d\n", !GPBIT (12)); + cmn_err (CE_CONT, "PDMA4 DAC: 0=cs4321 1=WM8728: %d\n", GPBIT (13)); +#endif + + OUTB (devc->osdev, 0x00, devc->ccs_base + 0x04); /* System configuration */ + OUTB (devc->osdev, 0x02, devc->ccs_base + 0x05); /* AC-link configuration */ + OUTB (devc->osdev, 0x00, devc->ccs_base + 0x06); /* I2S configuration */ + OUTB (devc->osdev, 0x83, devc->ccs_base + 0x07); /* S/PDIF configuration */ + + /* TODO: GPIO initialization */ +} + +static void +julia_eeprom_init (envy24ht_devc * devc) +{ + OUTB (devc->osdev, 0x39, devc->ccs_base + 0x04); /* System configuration */ + OUTB (devc->osdev, 0x80, devc->ccs_base + 0x05); /* AC-link configuration */ + OUTB (devc->osdev, 0x78, devc->ccs_base + 0x06); /* I2S configuration */ + OUTB (devc->osdev, 0xc3, devc->ccs_base + 0x07); /* S/PDIF configuration */ + + OUTW (devc->osdev, 0xffff, devc->ccs_base + 0x18); /* GPIO direction */ + OUTW (devc->osdev, 0x0000, devc->ccs_base + 0x16); /* GPIO write mask */ + OUTW (devc->osdev, 0x801A, devc->ccs_base + 0x14); /* Initital bit state */ +} + +static int +init_eeprom_v2 (envy24ht_devc * devc) +{ + unsigned char *eeprom = (unsigned char *) &devc->eeprom; + + OUTB (devc->osdev, eeprom[6], devc->ccs_base + 0x04); + OUTB (devc->osdev, eeprom[7], devc->ccs_base + 0x05); + OUTB (devc->osdev, eeprom[8], devc->ccs_base + 0x06); + OUTB (devc->osdev, eeprom[9], devc->ccs_base + 0x07); + + OUTB (devc->osdev, eeprom[10], devc->ccs_base + 0x18); + OUTB (devc->osdev, eeprom[11], devc->ccs_base + 0x19); + OUTB (devc->osdev, eeprom[12], devc->ccs_base + 0x1a); + + OUTB (devc->osdev, eeprom[13], devc->ccs_base + 0x16); + OUTB (devc->osdev, eeprom[14], devc->ccs_base + 0x17); + OUTB (devc->osdev, eeprom[15], devc->ccs_base + 0x1f); + + OUTB (devc->osdev, eeprom[16], devc->ccs_base + 0x14); + OUTB (devc->osdev, eeprom[17], devc->ccs_base + 0x15); + OUTB (devc->osdev, eeprom[18], devc->ccs_base + 0x1e); + return 1; +} + +static int +load_eeprom (envy24ht_devc * devc) +{ + int status, i, check; + unsigned char *eeprom = (unsigned char *) &devc->eeprom, c; + static const char resolutions[4] = { 16, 18, 20, 24 }; + + c = 0; + + status = INB (devc->osdev, devc->ccs_base + 0x13); + + if (!(status & 0x80)) + return 0; /* No EEPROM */ + + devc->eeprom.bSize = sizeof (devc->eeprom); /* Guess the size */ + + for (i = 0; i < devc->eeprom.bSize; i++) + { + eeprom[i] = eeprom_read (devc, i); + eeprom[i] = eeprom_read (devc, i); + + if (devc->eeprom.bSize > sizeof (devc->eeprom)) + devc->eeprom.bSize = sizeof (devc->eeprom); + } +#if 1 + DDB (cmn_err (CE_CONT, "EEPROM=")); + for (i = 0; i < devc->eeprom.bSize; i++) + DDB (cmn_err (CE_CONT, "0x%02x, ", eeprom[i])); + DDB (cmn_err (CE_CONT, "\n")); +#endif + + check = 0; + for (i = 0; i < 4; i++) + { + check <<= 8; + check |= eeprom[i]; + } + + if (check != devc->model_data->svid) + cmn_err (CE_CONT, + "Envy24 WARNING: Possible EEPROM read error %08x != %08x\n", + check, devc->model_data->svid); + + DDB (cmn_err (CE_CONT, "EEPROM version %d\n", devc->eeprom.bVersion)); + + if (devc->eeprom.bVersion == 2) + { + return init_eeprom_v2 (devc); + } + + /* Init the controller registers based on the EEPROM data */ + + OUTB (devc->osdev, devc->eeprom.bCodecConfig, devc->ccs_base + 0x04); + OUTB (devc->osdev, devc->eeprom.bACLinkConfig, devc->ccs_base + 0x05); + OUTB (devc->osdev, devc->eeprom.bI2SID, devc->ccs_base + 0x06); + OUTB (devc->osdev, devc->eeprom.bSpdifConfig, devc->ccs_base + 0x07); + + /* GPIO ports */ + + OUTB (devc->osdev, devc->eeprom.bGPIODirection2, devc->ccs_base + 0x18); + OUTB (devc->osdev, devc->eeprom.bGPIODirection1, devc->ccs_base + 0x19); + OUTB (devc->osdev, devc->eeprom.bGPIODirection0, devc->ccs_base + 0x1a); + + OUTB (devc->osdev, devc->eeprom.bGPIOInitMask2, devc->ccs_base + 0x14); + OUTB (devc->osdev, devc->eeprom.bGPIOInitMask1, devc->ccs_base + 0x15); + OUTB (devc->osdev, devc->eeprom.bGPIOInitMask0, devc->ccs_base + 0x1f); + + OUTB (devc->osdev, devc->eeprom.bGPIOInitState2, devc->ccs_base + 0x14); + OUTB (devc->osdev, devc->eeprom.bGPIOInitState1, devc->ccs_base + 0x15); + OUTB (devc->osdev, devc->eeprom.bGPIOInitState0, devc->ccs_base + 0x1e); + +#if 1 + DDB (cmn_err (CE_CONT, "GPIO=%02x%02x%02x (%02x%02x%02x, %02x%02x%02x)\n", + devc->eeprom.bGPIOInitState2, + devc->eeprom.bGPIOInitState1, + devc->eeprom.bGPIOInitState0, + devc->eeprom.bGPIODirection2, + devc->eeprom.bGPIODirection1, + devc->eeprom.bGPIODirection0, + devc->eeprom.bGPIOInitMask2, + devc->eeprom.bGPIOInitMask1, devc->eeprom.bGPIOInitMask0)); + + c = devc->eeprom.bCodecConfig; + switch ((c >> 6) % 0x03) + { + case 0: + DDB (cmn_err (CE_CONT, "24.576MHz crystal\n")); + break; + case 1: + DDB (cmn_err (CE_CONT, "49.152MHz crystal\n")); + break; + default: + DDB (cmn_err (CE_CONT, "Unknown crystal frequency\n")); + } + + if (c & 0x20) + { + DDB (cmn_err (CE_CONT, "Has MPU401 UART\n")); + } + else + { + DDB (cmn_err (CE_CONT, "No MPU401 UART\n")); + } + DDB (cmn_err + (CE_CONT, "%d stereo ADC pairs connected\n", ((c >> 2) & 0x03) + 1)); + DDB (cmn_err (CE_CONT, "%d stereo DAC pairs connected\n", (c & 0x03) + 1)); + + c = devc->eeprom.bACLinkConfig; + DDB (cmn_err + (CE_CONT, "Converter type: %s\n", (c & 0x80) ? "I2S" : "AC97")); + if (!(c & 0x80)) + { + if (!(devc->model_data->flags & MF_NOAC97)) + devc->codec_type = CODEC_AC97; + + DDB (cmn_err (CE_NOTE, + "AC link connection mode type: %s\n", + (c & 0x02) ? "packed" : "split")); + } + else + { + c = devc->eeprom.bI2SID; + + DDB (cmn_err (CE_CONT, "I2C codec has volume control/mute: %s\n", + (c % 0x80) ? "YES" : "NO")); + DDB (cmn_err (CE_CONT, "I2C codec has 96 KHz S/R support: %s\n", + (c % 0x40) ? "YES" : "NO")); + DDB (cmn_err (CE_CONT, "I2C codec has 192 KHz S/R support: %s\n", + (c % 0x08) ? "YES" : "NO")); + + DDB (cmn_err (CE_CONT, + "Converter resolution %d bits\n", + resolutions[(c >> 4) & 0x03])); + } + + c = INB (devc->osdev, devc->ccs_base + 0x07); + DDB (cmn_err (CE_CONT, + "Internal S/PDIF out implemented: %s\n", + (c & 0x40) ? "YES" : "NO")); + DDB (cmn_err + (CE_CONT, "Internal S/PDIF out enabled: %s\n", + (c & 0x80) ? "YES" : "NO")); + DDB (cmn_err + (CE_CONT, "External S/PDIF out implemented: %s\n", + (c & 0x01) ? "YES" : "NO")); + DDB (cmn_err + (CE_CONT, "S/PDIF input present: %s\n", (c & 0x02) ? "YES" : "NO")); + DDB (cmn_err (CE_CONT, "S/PDIF chip IDs %x\n", (c >> 2) & 0x0f)); +#endif + + return 1; +} + +/*ARGSUSED*/ +static void +dump_regs (envy24ht_devc * devc, char *lbl) +{ +#if 0 + int i; + + cmn_err (CE_CONT, "\nDump registers: %s\n", lbl); + + for (i = 0; i < 0x20; i += 4) + { + if (!(i % (8 * 4))) + cmn_err (CE_CONT, "\nCCS%02x: ", i); + cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->ccs_base + i)); + } + cmn_err (CE_CONT, "\n"); + + for (i = 0; i < 0x80; i += 4) + { + if (!(i % (8 * 4))) + cmn_err (CE_CONT, "\nMT%02x: ", i); + cmn_err (CE_CONT, "%08x ", INL (devc->osdev, devc->mt_base + i)); + } + cmn_err (CE_CONT, "\n"); +#endif +} + +static int +verify_rate (envy24ht_devc * devc, int arg) +{ + if (devc->codec_type == CODEC_AC97 && arg > 48000) + arg = 48000; + if (arg > 96000 && !(devc->model_data->flags & MF_192K)) + arg = 96000; + + return arg; +} + +static int +envy24ht_set_rate (int dev, int arg) +{ + envy24ht_devc *devc = audio_engines[dev]->devc; + envy24ht_portc *portc = audio_engines[dev]->portc; + int i, ix, diff, bestdiff; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (arg == 0) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return devc->pending_speed; + } + + arg = verify_rate (devc, arg); +/* + * Don't permit changing the sampling rate if we have multiple clients. + */ + if (devc->open_count != 1 || devc->ratelock) + { + DDB (cmn_err (CE_CONT, "Can't set speed: open_count %d, ratelock %d\n", + devc->open_count, devc->ratelock)); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + if (arg != devc->pending_speed) + { + audio_engines[dev]->fixed_rate = devc->speed; + audio_engines[dev]->min_rate = devc->speed; + audio_engines[dev]->max_rate = devc->speed; + audio_engines[dev]->flags |= ADEV_FIXEDRATE; + } + else + { + audio_engines[dev]->min_rate = 8000; + audio_engines[dev]->max_rate = 192000; + audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; + } + return devc->pending_speed; + } + + if (portc->dev_flags & DF_SPDIF) + { + /* Allow only supported S/PDIF rates */ + if (arg < 32000) + arg = 32000; + if (arg > 96000) + arg = 96000; + } + + ix = 9; + bestdiff = 1000000; + i = 0; + audio_engines[dev]->flags &= ~ADEV_FIXEDRATE; + + while (speed_tab[i].speed != -1) + { + diff = speed_tab[i].speed - arg; + if (diff < 0) + diff = -diff; + if (diff < bestdiff) + { + ix = i; + bestdiff = diff; + } + i++; + } + + devc->pending_speed = speed_tab[ix].speed; + devc->pending_speed_sel = ix; + /*cmn_err(CE_CONT, "Requested sampling rate %d, got %d\n", arg, devc->pending_speed); */ + + //setup_sample_rate (devc); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return devc->pending_speed; +} + +static short +envy24ht_set_channels (int dev, short arg) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + + if (arg == 0) + return portc->channels; + + if (portc->dev_flags & DF_MULTICH) + { + int n = 2, ch, i, mask; + + if (arg < 2) + arg = 2; + + arg = ((arg + 1) / 2) * 2; /* Round to even number of channels */ + + if (arg > devc->model_data->nr_outs) + arg = devc->model_data->nr_outs; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + devc->busy_play_channels &= ~portc->used_chmask; + + for (ch = 2; ch <= arg; ch += 2) + { + mask = 0; + + for (i = 0; i < ch; i++) + mask |= (1 << i); + + if (devc->busy_play_channels & mask) + break; + n = ch; + } + + portc->channels = n; + portc->used_chmask = 0; + for (i = 0; i < n; i++) + portc->used_chmask |= (1 << i); + + devc->busy_play_channels |= portc->used_chmask; + /* MT19: Channel allocation */ + OUTB (devc->osdev, 4 - n / 2, devc->mt_base + 0x19); + /* cmn_err(CE_CONT, "%d channels: MT19=%02x\n", n, INB(devc->osdev, devc->mt_base+0x19)); */ + + if (portc->channels == 6) + { + /* The fragment size must be a multiple of 6 */ + audio_engines[dev]->min_block = 4 * 288; + audio_engines[dev]->max_block = 4 * 288; + + } + else + { + audio_engines[dev]->min_block = 0; + audio_engines[dev]->max_block = 0; + } + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return portc->channels; + } + + return portc->channels = 2; +} + +/*ARGSUSED*/ +static int +ac3_write (adev_t * adev, + dmap_t * dmap, + void *frombuf, void *tobuf, int maxspace, int *fromlen, int *tolen) +{ +/* + * This routine takes AC3 input 16 bits at time and packs them to + * 32 bit words. + */ + int i, l; + unsigned short *ip; + unsigned int *op; + + l = *fromlen * 2; + if (l > maxspace) + { + l = maxspace; + } + + *tolen = l; + *fromlen = l / 2; + l /= 4; + + ip = frombuf; + op = tobuf; + + for (i = 0; i < l; i++) + { + *op++ = (*ip++) << 16; + } + + return 0; +} + +static unsigned int +envy24ht_set_format (int dev, unsigned int arg) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + + if (arg == 0) + return portc->fmt; + + if (arg == AFMT_AC3 && (portc->dev_flags & DF_AC3)) + { + audio_engines[dev]->dmap_out->device_write = ac3_write; + return portc->fmt = AFMT_AC3; + } + audio_engines[dev]->dmap_out->device_write = NULL; + return portc->fmt = SUPPORTED_FORMAT; +} + +static int +envy24ht_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc = audio_engines[dev]->devc; + + switch (cmd) + { + case SNDCTL_DSP_GET_CHNORDER: + *(oss_uint64_t *) arg = CHNORDER_NORMAL; + return 0; + } + + if (portc->dev_flags & DF_SPDIF) + { + int ret; + ret = oss_spdif_ioctl (&devc->spdc, portc->open_mode, cmd, arg); + if (ret != SPDIF_NOIOCTL) + return ret; + } + + if (devc->auxdrv->audio_ioctl) + return devc->auxdrv->audio_ioctl (devc, portc, cmd, arg); + return OSS_EINVAL; +} + +static void envy24ht_trigger (int dev, int state); + +static void +envy24ht_reset (int dev) +{ + envy24ht_trigger (dev, 0); +} + +/*ARGSUSED*/ +static int +envy24ht_open_input (int dev, int mode, int open_flags) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc = audio_engines[dev]->devc; + adev_p adev = audio_engines[dev]; + oss_native_word flags; + + if (mode & OPEN_WRITE) + { + cmn_err (CE_CONT, "Playback is not possible with %s\n", adev->devnode); + return OSS_ENOTSUP; + } + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode || (devc->busy_rec_channels & portc->chmask)) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + portc->open_mode = mode; + devc->open_count++; + if (devc->open_count == 1) + { + devc->pending_speed_sel = devc->configured_rate_sel; + } + + if (portc->dev_flags & DF_SPDIF) + oss_spdif_open (&devc->spdc, mode); + + portc->used_chmask = portc->chmask; + devc->busy_rec_channels |= portc->chmask; + + if (!devc->use_src) + adev->flags |= ADEV_NOSRC; + else + adev->flags &= ~ADEV_NOSRC; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +envy24ht_open_output (int dev, int mode, int open_flags) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + adev_p adev = audio_engines[dev]; + + if (mode & OPEN_READ) + { + cmn_err (CE_CONT, "Recording is not possible with %s\n", adev->devnode); + return OSS_ENOTSUP; + } + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + if (portc->open_mode || (devc->busy_play_channels & portc->chmask)) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + portc->open_mode = mode; + portc->used_chmask = portc->chmask; + devc->busy_play_channels |= portc->chmask; + audio_engines[dev]->dmap_out->device_write = NULL; + + devc->open_count++; + if (devc->open_count == 1) + { + devc->pending_speed_sel = devc->configured_rate_sel; + } + + if (portc->dev_flags & DF_SPDIF) + oss_spdif_open (&devc->spdc, mode); + + if (!devc->use_src) + adev->flags |= ADEV_NOSRC; + else + adev->flags &= ~ADEV_NOSRC; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static void +envy24ht_close (int dev, int mode) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + devc->open_count--; + + if (portc->open_mode & OPEN_READ) + devc->busy_rec_channels &= ~portc->used_chmask; + if (portc->open_mode & OPEN_WRITE) + devc->busy_play_channels &= ~portc->used_chmask; + portc->open_mode = 0; + + if (portc->dev_flags & DF_MULTICH) + { + OUTB (devc->osdev, 0x03, devc->mt_base + 0x19); /* Channel allocation */ + portc->chmask = 0x003; /* Just the front channels */ + } + + if (portc->dev_flags & DF_SPDIF) + oss_spdif_close (&devc->spdc, mode); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static void +envy24ht_output_block (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +/*ARGSUSED*/ +static void +envy24ht_start_input (int dev, oss_native_word buf, int count, int fragsize, + int intrflag) +{ +} + +static void +envy24ht_trigger (int dev, int state) +{ + envy24ht_devc *devc = audio_engines[dev]->devc; + envy24ht_portc *portc = audio_engines[dev]->portc; + unsigned char enable, intrmask; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + enable = INB (devc->osdev, devc->mt_base + 0x18); + intrmask = INB (devc->osdev, devc->mt_base + 0x03); + + if (portc->state_bits == state) /* No change */ + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return; + } + portc->state_bits = state; + + if (portc->open_mode & OPEN_WRITE) + { + if (state & PCM_ENABLE_OUTPUT) + { + enable |= portc->mask; + intrmask &= ~portc->mask; + } + else + { + enable &= ~portc->mask; + intrmask |= portc->mask; + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + enable |= portc->mask; + intrmask &= ~portc->mask; + } + else + { + enable &= ~portc->mask; + intrmask |= portc->mask; + } + } + OUTB (devc->osdev, enable, devc->mt_base + 0x18); + OUTB (devc->osdev, intrmask, devc->mt_base + 0x03); + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + if (state) + dump_regs (devc, "trigger"); +} + +/*ARGSUSED*/ +static int +envy24ht_prepare_for_input (int dev, int bsize, int bcount) +{ + envy24ht_devc *devc = audio_engines[dev]->devc; + envy24ht_portc *portc = audio_engines[dev]->portc; + dmap_p dmap = audio_engines[dev]->dmap_in; + int buffsize, fragsize; + oss_native_word flags; + + if (audio_engines[dev]->flags & ADEV_NOINPUT) + return OSS_EACCES; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + setup_sample_rate (devc); + buffsize = dmap->bytes_in_use / 4 - 1; + fragsize = dmap->fragment_size / 4 - 1; + + OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00); + OUTW (devc->osdev, buffsize, portc->base + 0x04); + OUTW (devc->osdev, fragsize, portc->base + 0x06); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +envy24ht_prepare_for_output (int dev, int bsize, int bcount) +{ + envy24ht_devc *devc = audio_engines[dev]->devc; + envy24ht_portc *portc = audio_engines[dev]->portc; + dmap_p dmap = audio_engines[dev]->dmap_out; + int buffsize, fragsize; + oss_native_word flags; + + if (audio_engines[dev]->flags & ADEV_NOOUTPUT) + return OSS_EACCES; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + setup_sample_rate (devc); + buffsize = dmap->bytes_in_use / 4 - 1; + fragsize = dmap->fragment_size / 4 - 1; + + if (portc->dev_flags & DF_MULTICH) + { + /* Multi ch device */ + OUTL (devc->osdev, dmap->dmabuf_phys, devc->mt_base + 0x10); + OUTL (devc->osdev, buffsize, devc->mt_base + 0x14); + OUTL (devc->osdev, fragsize, devc->mt_base + 0x1c); + } + else + { + OUTL (devc->osdev, dmap->dmabuf_phys, portc->base + 0x00); + OUTW (devc->osdev, buffsize, portc->base + 0x04); + OUTW (devc->osdev, fragsize, portc->base + 0x06); + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +/*ARGSUSED*/ +static int +envy24ht_get_buffer_pointer (int dev, dmap_t * dmap, int direction) +{ + envy24ht_portc *portc = audio_engines[dev]->portc; + envy24ht_devc *devc; + int pos; + + devc = audio_engines[dev]->devc; + pos = (INW (devc->osdev, portc->base + 0x04) + 1) * 4; + return dmap->bytes_in_use - pos; +} + +static int +envy24ht_sync_control(int dev, int event, int mode) +{ + envy24ht_devc *devc = audio_engines[dev]->devc; + envy24ht_portc *portc = audio_engines[dev]->portc; + unsigned char enable, intrmask; + oss_native_word flags; + MUTEX_ENTER_IRQDISABLE(devc->mutex, flags); + if(event == SYNC_PREPARE) + { + devc->syncstart_mask |= portc->mask; + portc->state_bits = mode; + } + else if(event == SYNC_TRIGGER) + { + if(devc->syncstart_mask) + { + enable = INB (devc->osdev, devc->mt_base + 0x18); + intrmask = INB (devc->osdev, devc->mt_base + 0x03); + enable |= devc->syncstart_mask; + intrmask &= ~devc->syncstart_mask; + OUTB (devc->osdev, enable, devc->mt_base + 0x18); + OUTB (devc->osdev, intrmask, devc->mt_base + 0x03); + devc->syncstart_mask = 0; + } + } + MUTEX_EXIT_IRQRESTORE(devc->mutex, flags); + return 0; +} + +#if 0 +static int +envy24ht_check_output (int dev) +{ + int pos; + envy24ht_devc *devc = audio_engines[dev]->devc; + + pos = envy24ht_get_buffer_pointer (dev, audio_engines[dev]->dmap_out, 0); + + cmn_err (CE_CONT, + "Envy24ht: Output timeout on device %d (%d, %02x, %02x)\n", dev, + pos, INB (devc->osdev, devc->ccs_base + 0x02), INB (devc->osdev, + devc-> + ccs_base + + 0x00)); + return OSS_EIO; +} +#endif + +static audiodrv_t envy24ht_output_driver = { + envy24ht_open_output, + envy24ht_close, + envy24ht_output_block, + envy24ht_start_input, + envy24ht_ioctl, + envy24ht_prepare_for_input, + envy24ht_prepare_for_output, + envy24ht_reset, + NULL, + NULL, + NULL, + NULL, + envy24ht_trigger, + envy24ht_set_rate, + envy24ht_set_format, + envy24ht_set_channels, + NULL, + NULL, + NULL, /* check input */ + NULL, /* envy24ht_check_output */ + NULL, /* envy24ht_alloc_buffer */ + NULL, /* envy24ht_free_buffer */ + NULL, + NULL, + envy24ht_get_buffer_pointer, + NULL, + envy24ht_sync_control +}; + + +static audiodrv_t envy24ht_input_driver = { + envy24ht_open_input, + envy24ht_close, + envy24ht_output_block, + envy24ht_start_input, + envy24ht_ioctl, + envy24ht_prepare_for_input, + envy24ht_prepare_for_output, + envy24ht_reset, + NULL, + NULL, + NULL, + NULL, + envy24ht_trigger, + envy24ht_set_rate, + envy24ht_set_format, + envy24ht_set_channels, + NULL, + NULL, + NULL, + NULL, + NULL, /* envy24ht_alloc_buffer */ + NULL, /* envy24ht_free_buffer */ + NULL, + NULL, + envy24ht_get_buffer_pointer, + NULL, + envy24ht_sync_control +}; + +static const int bindings[MAX_ODEV] = { + DSP_BIND_FRONT, + DSP_BIND_CENTER_LFE, + DSP_BIND_SURR, + DSP_BIND_REAR +}; + +static int +init_play_device (envy24ht_devc * devc, int chmask, int offset, + unsigned char mask, char *name, int dev_flags, + char *port_id, char *devfile_name) +{ + int opts, dev, formats; + char tmp[80]; + envy24ht_portc *portc = NULL; + int i; + adev_p adev; + + sprintf (tmp, "%s %s out", devc->model_data->product, name); + + if (devc->nr_outdevs >= MAX_ODEV) + { + cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n"); + return OSS_ENXIO; + } + + opts = ADEV_AUTOMODE | ADEV_NOINPUT; + + if (dev_flags & DF_SPDIF) + opts |= ADEV_SPECIAL; + + formats = SUPPORTED_FORMAT; + if (dev_flags & DF_AC3) + formats |= AFMT_AC3; + + if (dev_flags & DF_MULTICH) + opts |= ADEV_COLD; + else + opts |= ADEV_SPECIAL; + + if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp, + &envy24ht_output_driver, + sizeof (audiodrv_t), + opts, formats, devc, -1, + devfile_name)) < 0) + { + return dev; + } + + if (devc->first_dev == -1) + { + devc->first_dev = dev; + audio_engines[dev]->outch_names = OUTCH_NAMES; + } + adev = audio_engines[dev]; + + portc = &devc->play_portc[devc->nr_outdevs]; + for (i = 0; speed_tab[i].speed != -1; i++) + adev->rates[adev->nrates++] = speed_tab[i].speed; + + portc->name = port_id; + audio_engines[dev]->portc = portc; + audio_engines[dev]->mixer_dev = devc->mixer_dev; + audio_engines[dev]->rate_source = devc->first_dev; + audio_engines[dev]->min_rate = 8000; + audio_engines[dev]->max_rate = 192000; + audio_engines[dev]->binding = bindings[devc->nr_outdevs]; + if (dev_flags & DF_SPDIF) + audio_engines[dev]->binding = DSP_BIND_SPDIF; + + portc->dev = dev; + portc->open_mode = 0; + portc->fmt = SUPPORTED_FORMAT; + portc->base = devc->mt_base + offset; + portc->mask = mask; + portc->dev_flags = dev_flags; + portc->chmask = chmask; + portc->state_bits = 0; + portc->direction = PCM_ENABLE_OUTPUT; + + audio_engines[dev]->min_channels = 2; + audio_engines[dev]->max_channels = 2; + + if (dev_flags & DF_SPDIF) + audio_engines[dev]->caps |= PCM_CAP_DIGITALOUT | DSP_CH_STEREO; + else + { + if (dev_flags & DF_MULTICH) + { + audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT; + audio_engines[dev]->caps |= DSP_CH_STEREO; + audio_engines[dev]->min_channels = 2; + audio_engines[dev]->max_channels = devc->model_data->nr_outs; + } + else + audio_engines[dev]->caps |= PCM_CAP_ANALOGOUT | DSP_CH_STEREO; + } + devc->nr_outdevs++; + + return dev; +} + +static int +init_rec_device (envy24ht_devc * devc, int chmask, int offset, + unsigned char mask, char *name, int dev_flags, char *devfile_name) +{ + int opts, dev, formats; + adev_p adev; + int i; + envy24ht_portc *portc = NULL; + char tmp[80]; + sprintf (tmp, "%s %s in", devc->model_data->product, name); + + if (devc->nr_indevs >= MAX_IDEV) + { + cmn_err (CE_CONT, "Envy24ht: Too many audio devices\n"); + return OSS_ENXIO; + } + + opts = ADEV_AUTOMODE | ADEV_NOOUTPUT | ADEV_COLD; + + if (dev_flags & DF_SPDIF) + opts |= ADEV_SPECIAL; + + formats = SUPPORTED_FORMAT; + if (dev_flags & DF_AC3) + formats |= AFMT_AC3; + + if ((dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp, + &envy24ht_input_driver, + sizeof (audiodrv_t), + opts, formats, devc, -1, + devfile_name)) < 0) + { + return dev; + } + + if (devc->first_dev == -1) + devc->first_dev = dev; + portc = &devc->rec_portc[devc->nr_indevs]; + adev = audio_engines[dev]; + + for (i = 0; speed_tab[i].speed != -1; i++) + adev->rates[adev->nrates++] = speed_tab[i].speed; + + audio_engines[dev]->portc = portc; + audio_engines[dev]->mixer_dev = devc->mixer_dev; + audio_engines[dev]->rate_source = devc->first_dev; + audio_engines[dev]->min_rate = 8000; + audio_engines[dev]->max_rate = 192000; + + portc->dev = dev; + portc->name = "rec"; + portc->open_mode = 0; + portc->base = devc->mt_base + offset; + portc->mask = mask; + portc->state_bits = 0; + portc->fmt = SUPPORTED_FORMAT; + portc->dev_flags = dev_flags; + portc->chmask = chmask; + portc->direction = PCM_ENABLE_INPUT; + if (dev_flags & DF_SPDIF) + audio_engines[dev]->caps |= PCM_CAP_DIGITALIN | DSP_CH_STEREO; + else + audio_engines[dev]->caps |= PCM_CAP_ANALOGIN | DSP_CH_STEREO; + devc->nr_indevs++; + + return dev; +} + +static void +init_devices (envy24ht_devc * devc) +{ + int front_engine, rec_engine; + + OUTB (devc->osdev, 0x03, devc->mt_base + 0x19); /* Channel allocation */ + OUTB (devc->osdev, 0x00, devc->mt_base + 0x1b); /* Unpause ALL channels */ + + devc->first_dev = -1; + + front_engine=init_play_device (devc, 0x003, 0x10, 0x01, devc->channel_names[0], + DF_MULTICH, "front", ""); + + if (devc->model_data->nr_outs > 2) + init_play_device (devc, 0x00c, 0x70, 0x10, devc->channel_names[1], 0, + "C/LFE", ""); + + if (devc->model_data->nr_outs > 4) + init_play_device (devc, 0x030, 0x60, 0x20, devc->channel_names[2], 0, + "side", ""); + + if (devc->model_data->nr_outs > 6) + init_play_device (devc, 0x0c0, 0x50, 0x40, devc->channel_names[3], 0, + "rear", ""); + + if (devc->model_data->flags & MF_SPDIFOUT) + { + init_play_device (devc, 0x300, 0x40, 0x80, "digital", + DF_SPDIF | DF_AC3, "spdif", "spdout"); + } + + rec_engine = init_rec_device (devc, 0x003, 0x20, 0x02, "analog", 0, ""); + + if (devc->model_data->flags & MF_SPDIFIN) + { + init_rec_device (devc, 0x00c, 0x30, 0x04, "digital", DF_SPDIF, "spdin"); + } + +#ifdef CONFIG_OSS_VMIX + if (rec_engine < 0) + rec_engine = -1; /* Not available */ + + if (front_engine >= 0) + vmix_attach_audiodev(devc->osdev, front_engine, rec_engine, 0); +#endif +} + +static void +install_ac97_mixer (envy24ht_devc * devc) +{ + int tmp; + tmp = 0; + + DDB (cmn_err (CE_CONT, "Installing AC97 mixer\n")); + + devc->mixer_dev = + ac97_install (&devc->ac97devc, devc->model_data->product, ac97_read, + ac97_write, devc, devc->osdev); + if (devc->mixer_dev < 0) + { + cmn_err (CE_CONT, "Envy24ht: Mixer install failed\n"); + return; + } + ac97_init_ext (devc->mixer_dev, &devc->ac97devc, envy24ht_mix_init, 50); + +#if 1 + /* AD1616 specific stuff. Check this if there is some other AC97 chip */ + /* Maybe this should be moved to ac97.c in a way or another */ + + /* Turn surround dacs ON */ + tmp = ac97_read (devc, 0x2a); + tmp &= ~0x3800; + ac97_write (devc, 0x2a, tmp); + + tmp = ac97_read (devc, 0x5a); + tmp &= ~0x8000; + tmp |= 0x1800; + ac97_write (devc, 0x5a, tmp); +#endif + +#if 0 + for (tmp = 0; tmp < 0x3f; tmp += 2) + cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); + for (tmp = 0x5a; tmp < 0x5d; tmp += 2) + cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); + for (tmp = 0x7a; tmp < 0x7f; tmp += 2) + cmn_err (CE_CONT, "%02x: %04x\n", tmp, ac97_read (devc, tmp)); +#endif +} + +int +oss_envy24ht_attach (oss_device_t * osdev) +{ + envy24ht_devc *devc; + extern int envy24ht_model; + unsigned char pci_irq_line; + unsigned short pci_command, vendor, device; + unsigned int subvendor; + unsigned int pci_ioaddr, pci_ioaddr1; + int i, err; + + char *name = "Generic ENVY24HT"; + + DDB (cmn_err (CE_CONT, "Entered Envy24HT probe routine\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if (vendor != ICENSEMBLE_VENDOR_ID || device != ICENSEMBLE_ENVY24HT_ID) + return 0; + + if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL) + { + cmn_err (CE_WARN, "Out of memory\n"); + return 0; + } + + devc->osdev = osdev; + osdev->devc = devc; + MUTEX_INIT (osdev, devc->mutex, MH_DRV); + MUTEX_INIT (osdev, devc->low_mutex, MH_DRV + 1); + + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_0, &pci_ioaddr); + pci_read_config_dword (osdev, PCI_BASE_ADDRESS_1, &pci_ioaddr1); + 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, 0x2c, &subvendor); + + DDB (cmn_err (CE_CONT, + "Device found at I/O %x, %x\n", pci_ioaddr & ~3, + pci_ioaddr1 & ~3)); + + devc->subvendor = subvendor; + + devc->ccs_base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr) & ~0x3; + DDB (cmn_err (CE_CONT, "CCS base %x/%lx\n", pci_ioaddr, devc->ccs_base)); + + devc->mt_base = MAP_PCI_IOADDR (devc->osdev, 1, pci_ioaddr1) & ~0x3; + DDB (cmn_err (CE_CONT, "MT base %x/%lx\n", pci_ioaddr1, devc->mt_base)); + + /* Reset the chip */ + OUTB (devc->osdev, 0x81, devc->ccs_base + 0x00); + oss_udelay (1000); + + /* Release reset */ + OUTB (devc->osdev, 0x00, devc->ccs_base + 0x00); + oss_udelay (1000); + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + devc->nr_outdevs = devc->nr_indevs = 0; + i = 0; + + if ((envy24ht_model > -1) + && (envy24ht_model < (sizeof (models) / sizeof (card_spec)) - 1)) + i = envy24ht_model; + else + while (models[i].svid != 0) + { + if (models[i].svid == subvendor) + { + name = models[i].product; + devc->model_data = &models[i]; + DDB (cmn_err (CE_CONT, "Card id '%s'\n", name)); + + break; + } + + i++; + } + + if (models[i].svid == 0) + { + cmn_err (CE_CONT, "Unknown device ID (%08x).\n", subvendor); + cmn_err (CE_CONT, "This card may not be supported (yet).\n"); + i = 0; /* Assume AC97 based Envy23PT */ + } + + oss_register_device (osdev, name); + + if (devc->model_data == NULL) + { + cmn_err (CE_CONT, "Envy24ht: This card was not recognized: %08x\n", + subvendor); + return 0; + } + + /* Disable all interrupts */ + OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); + OUTB (devc->osdev, 0xff, devc->mt_base + 0x03); + + if (devc->model_data->flags & MF_ENVY24PT) + { + devc->codec_type = CODEC_AC97; + envy24pt_init (devc); + } + else if (devc->model_data->svid == SSID_JULIA) + { + julia_eeprom_init (devc); + } + else + load_eeprom (devc); + + devc->irq = pci_irq_line; + if ((err = + oss_register_interrupts (devc->osdev, 0, envy24htintr, NULL)) < 0) + { + cmn_err (CE_WARN, "Can't register interrupt handler, err=%d\n", err); + return 0; + } + + i = 0; + devc->max_ratesel = 0; + + while (speed_tab[i].speed != -1) + { + int rate = speed_tab[i].speed; + + if (verify_rate (devc, rate) == rate) + devc->max_ratesel = i; + + i++; + } + + OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01); /* Enable audio interrupts */ + + if (devc->model_data->flags & MF_MIDI) + { + attach_midi (devc); + } + i = 0; + devc->max_ratesel = 0; + + while (speed_tab[i].speed != -1) + { + int rate = speed_tab[i].speed; + + if (verify_rate (devc, rate) == rate) + devc->max_ratesel = i; + + i++; + } + + devc->syncstart_mask = 0; + devc->speedbits = 0; + devc->speed = 0; + devc->pending_speed = 0; + devc->prev_speed = 0; + devc->pending_speed_sel = 9; + devc->configured_rate_sel = devc->pending_speed_sel; + devc->open_count = 0; + memcpy (devc->channel_names, channel_names, sizeof (channel_names)); + + devc->nr_play_channels = 10; + devc->nr_rec_channels = 10; +#define setmask(m, b) m|=(1<<(b)) + + devc->inportmask = 0; + devc->outportmask = 0; + devc->busy_play_channels = 0; + devc->busy_rec_channels = 0; + + for (i = 0; i < devc->model_data->nr_outs; i++) + setmask (devc->outportmask, i); + if (devc->model_data->flags & MF_SPDIFOUT) + { + setmask (devc->outportmask, 8); /* SPDIF */ + setmask (devc->outportmask, 9); /* SPDIF */ + } + for (i = 0; i < devc->model_data->nr_ins; i++) + setmask (devc->inportmask, i); + if (devc->model_data->flags & MF_SPDIFIN) + { + setmask (devc->inportmask, 8); /* SPDIF */ + setmask (devc->inportmask, 9); /* SPDIF */ + } + + if (devc->model_data->auxdrv == NULL) + { + devc->auxdrv = &dummy_auxdrv; + } + else + { + devc->auxdrv = devc->model_data->auxdrv; + if (devc->auxdrv->card_init) + devc->auxdrv->card_init (devc); + } + + if (devc->codec_type == CODEC_AC97) + install_ac97_mixer (devc); + else + { + if ((devc->mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, + devc->osdev, + devc->osdev, + devc->model_data-> + product, + &envy24ht_mixer_driver, + sizeof (mixer_driver_t), + devc)) >= 0) + { + int n = 50; + + mixer_devs[devc->mixer_dev]->hw_devc = devc; + mixer_ext_set_init_fn (devc->mixer_dev, envy24ht_mix_init, n); + mixer_devs[devc->mixer_dev]->priority = 1; /* Possible default mixer candidate */ + } + } + + if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN)) + { + int err; + + if ((err = oss_spdif_install (&devc->spdc, devc->osdev, + &default_spdif_driver, + sizeof (spdif_driver_t), devc, NULL, + devc->mixer_dev, SPDF_OUT, + DIG_PASSTHROUGH | DIG_EXACT | + DIG_CBITOUT_LIMITED | DIG_VBITOUT | + DIG_PRO | DIG_CONSUMER)) != 0) + { + cmn_err (CE_CONT, + "S/PDIF driver install failed. error %d\n", err); + return 0; + } + } + OUTB (devc->osdev, ~0x10, devc->ccs_base + 0x01); /* Enable audio interrupts */ + init_devices (devc); + setup_sample_rate (devc); + + + return 1; +} + +int +oss_envy24ht_detach (oss_device_t * osdev) +{ + envy24ht_devc *devc = osdev->devc; + + if (oss_disable_device (osdev) < 0) + return 0; + + /* Disable all interrupts */ + OUTB (devc->osdev, 0xff, devc->ccs_base + 0x01); + /* disable DMA interrupt mask */ + OUTB (devc->osdev, 0xff, devc->mt_base + 0x00); + + /* Stop playback */ + OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, + devc->mt_base + 0x18); + oss_udelay (100); + OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x01, + devc->mt_base + 0x18); + + /* Stop recording */ + OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, + devc->mt_base + 0x18); + oss_udelay (100); + OUTB (devc->osdev, INB (devc->osdev, devc->mt_base + 0x18) & ~0x04, + devc->mt_base + 0x18); + + unload_midi (devc); + + if (devc->auxdrv->card_uninit) + devc->auxdrv->card_uninit(devc); + + oss_unregister_interrupts (devc->osdev); + + if (devc->model_data->flags & (MF_SPDIFOUT | MF_SPDIFIN)) + { + oss_spdif_uninstall (&devc->spdc); + } + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + UNMAP_PCI_IOADDR (devc->osdev, 1); + + oss_unregister_device (osdev); + return 1; +} |