diff options
Diffstat (limited to 'sound/isa/opti9xx')
-rw-r--r-- | sound/isa/opti9xx/Makefile | 15 | ||||
-rw-r--r-- | sound/isa/opti9xx/miro.c | 1444 | ||||
-rw-r--r-- | sound/isa/opti9xx/miro.h | 73 | ||||
-rw-r--r-- | sound/isa/opti9xx/opti92x-ad1848.c | 1035 | ||||
-rw-r--r-- | sound/isa/opti9xx/opti92x-cs4231.c | 2 | ||||
-rw-r--r-- | sound/isa/opti9xx/opti93x.c | 3 |
6 files changed, 2572 insertions, 0 deletions
diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile new file mode 100644 index 0000000..b4d894d --- /dev/null +++ b/sound/isa/opti9xx/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@perex.cz> +# + +snd-opti92x-ad1848-objs := opti92x-ad1848.o +snd-opti92x-cs4231-objs := opti92x-cs4231.o +snd-opti93x-objs := opti93x.o +snd-miro-objs := miro.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o +obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o +obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o +obj-$(CONFIG_SND_MIRO) += snd-miro.o diff --git a/sound/isa/opti9xx/miro.c b/sound/isa/opti9xx/miro.c new file mode 100644 index 0000000..440755c --- /dev/null +++ b/sound/isa/opti9xx/miro.c @@ -0,0 +1,1444 @@ +/* + * ALSA soundcard driver for Miro miroSOUND PCM1 pro + * miroSOUND PCM12 + * miroSOUND PCM20 Radio + * + * Copyright (C) 2004-2005 Martin Langer <martin-langer@gmx.de> + * + * Based on OSS ACI and ALSA OPTi9xx drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/moduleparam.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/wss.h> +#include <sound/mpu401.h> +#include <sound/opl4.h> +#include <sound/control.h> +#include <sound/info.h> +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> +#include "miro.h" + +MODULE_AUTHOR("Martin Langer <martin-langer@gmx.de>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Miro miroSOUND PCM1 pro, PCM12, PCM20 Radio"); +MODULE_SUPPORTED_DEVICE("{{Miro,miroSOUND PCM1 pro}, " + "{Miro,miroSOUND PCM12}, " + "{Miro,miroSOUND PCM20 Radio}}"); + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +static int wss; +static int ide; + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for miro soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for miro soundcard."); +module_param(port, long, 0444); +MODULE_PARM_DESC(port, "WSS port # for miro driver."); +module_param(mpu_port, long, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for miro driver."); +module_param(fm_port, long, 0444); +MODULE_PARM_DESC(fm_port, "FM Port # for miro driver."); +module_param(irq, int, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for miro driver."); +module_param(mpu_irq, int, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for miro driver."); +module_param(dma1, int, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for miro driver."); +module_param(dma2, int, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for miro driver."); +module_param(wss, int, 0444); +MODULE_PARM_DESC(wss, "wss mode"); +module_param(ide, int, 0444); +MODULE_PARM_DESC(ide, "enable ide port"); + +#define OPTi9XX_HW_DETECT 0 +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + + +struct snd_miro { + unsigned short hardware; + unsigned char password; + char name[7]; + + struct resource *res_mc_base; + struct resource *res_aci_port; + + unsigned long mc_base; + unsigned long mc_base_size; + unsigned long pwd_reg; + + spinlock_t lock; + struct snd_card *card; + struct snd_pcm *pcm; + + long wss_base; + int irq; + int dma1; + int dma2; + + long fm_port; + + long mpu_port; + int mpu_irq; + + unsigned long aci_port; + int aci_vendor; + int aci_product; + int aci_version; + int aci_amp; + int aci_preamp; + int aci_solomode; + + struct mutex aci_mutex; +}; + +static void snd_miro_proc_init(struct snd_miro * miro); + +static char * snd_opti9xx_names[] = { + "unkown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + +/* + * ACI control + */ + +static int aci_busy_wait(struct snd_miro * miro) +{ + long timeout; + unsigned char byte; + + for (timeout = 1; timeout <= ACI_MINTIME+30; timeout++) { + if (((byte=inb(miro->aci_port + ACI_REG_BUSY)) & 1) == 0) { + if (timeout >= ACI_MINTIME) + snd_printd("aci ready in round %ld.\n", + timeout-ACI_MINTIME); + return byte; + } + if (timeout >= ACI_MINTIME) { + long out=10*HZ; + switch (timeout-ACI_MINTIME) { + case 0 ... 9: + out /= 10; + case 10 ... 19: + out /= 10; + case 20 ... 30: + out /= 10; + default: + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(out); + break; + } + } + } + snd_printk(KERN_ERR "aci_busy_wait() time out\n"); + return -EBUSY; +} + +static inline int aci_write(struct snd_miro * miro, unsigned char byte) +{ + if (aci_busy_wait(miro) >= 0) { + outb(byte, miro->aci_port + ACI_REG_COMMAND); + return 0; + } else { + snd_printk(KERN_ERR "aci busy, aci_write(0x%x) stopped.\n", byte); + return -EBUSY; + } +} + +static inline int aci_read(struct snd_miro * miro) +{ + unsigned char byte; + + if (aci_busy_wait(miro) >= 0) { + byte=inb(miro->aci_port + ACI_REG_STATUS); + return byte; + } else { + snd_printk(KERN_ERR "aci busy, aci_read() stopped.\n"); + return -EBUSY; + } +} + +static int aci_cmd(struct snd_miro * miro, int write1, int write2, int write3) +{ + int write[] = {write1, write2, write3}; + int value, i; + + if (mutex_lock_interruptible(&miro->aci_mutex)) + return -EINTR; + + for (i=0; i<3; i++) { + if (write[i]< 0 || write[i] > 255) + break; + else { + value = aci_write(miro, write[i]); + if (value < 0) + goto out; + } + } + + value = aci_read(miro); + +out: mutex_unlock(&miro->aci_mutex); + return value; +} + +static int aci_getvalue(struct snd_miro * miro, unsigned char index) +{ + return aci_cmd(miro, ACI_STATUS, index, -1); +} + +static int aci_setvalue(struct snd_miro * miro, unsigned char index, int value) +{ + return aci_cmd(miro, index, value, -1); +} + +/* + * MIXER part + */ + +#define snd_miro_info_capture snd_ctl_boolean_mono_info + +static int snd_miro_get_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + if ((value = aci_getvalue(miro, ACI_S_GENERAL)) < 0) { + snd_printk(KERN_ERR "snd_miro_get_capture() failed: %d\n", value); + return value; + } + + ucontrol->value.integer.value[0] = value & 0x20; + + return 0; +} + +static int snd_miro_put_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int change, value, error; + + value = !(ucontrol->value.integer.value[0]); + + if ((error = aci_setvalue(miro, ACI_SET_SOLOMODE, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_capture() failed: %d\n", error); + return error; + } + + change = (value != miro->aci_solomode); + miro->aci_solomode = value; + + return change; +} + +static int snd_miro_info_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + + return 0; +} + +static int snd_miro_get_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int value; + + if (miro->aci_version <= 176) { + + /* + OSS says it's not readable with versions < 176. + But it doesn't work on my card, + which is a PCM12 with aci_version = 176. + */ + + ucontrol->value.integer.value[0] = miro->aci_preamp; + return 0; + } + + if ((value = aci_getvalue(miro, ACI_GET_PREAMP)) < 0) { + snd_printk(KERN_ERR "snd_miro_get_preamp() failed: %d\n", value); + return value; + } + + ucontrol->value.integer.value[0] = value; + + return 0; +} + +static int snd_miro_put_preamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + if ((error = aci_setvalue(miro, ACI_SET_PREAMP, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_preamp() failed: %d\n", error); + return error; + } + + change = (value != miro->aci_preamp); + miro->aci_preamp = value; + + return change; +} + +#define snd_miro_info_amp snd_ctl_boolean_mono_info + +static int snd_miro_get_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = miro->aci_amp; + + return 0; +} + +static int snd_miro_put_amp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int error, value, change; + + value = ucontrol->value.integer.value[0]; + + if ((error = aci_setvalue(miro, ACI_SET_POWERAMP, value)) < 0) { + snd_printk(KERN_ERR "snd_miro_put_amp() to %d failed: %d\n", value, error); + return error; + } + + change = (value != miro->aci_amp); + miro->aci_amp = value; + + return change; +} + +#define MIRO_DOUBLE(ctl_name, ctl_index, get_right_reg, set_right_reg) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = ctl_name, \ + .index = ctl_index, \ + .info = snd_miro_info_double, \ + .get = snd_miro_get_double, \ + .put = snd_miro_put_double, \ + .private_value = get_right_reg | (set_right_reg << 8) \ +} + +static int snd_miro_info_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int reg = kcontrol->private_value & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + + if ((reg >= ACI_GET_EQ1) && (reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + uinfo->value.integer.min = - 0x7f; + uinfo->value.integer.max = 0x7f; + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 0x20; + } + + return 0; +} + +static int snd_miro_get_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uinfo) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int left_val, right_val; + + int right_reg = kcontrol->private_value & 0xff; + int left_reg = right_reg + 1; + + if ((right_val = aci_getvalue(miro, right_reg)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", right_reg, right_val); + return right_val; + } + + if ((left_val = aci_getvalue(miro, left_reg)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", left_reg, left_val); + return left_val; + } + + if ((right_reg >= ACI_GET_EQ1) && (right_reg <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left_val < 0x80) { + uinfo->value.integer.value[0] = left_val; + } else { + uinfo->value.integer.value[0] = 0x80 - left_val; + } + + if (right_val < 0x80) { + uinfo->value.integer.value[1] = right_val; + } else { + uinfo->value.integer.value[1] = 0x80 - right_val; + } + + } else { + + /* non-equalizer elements */ + + uinfo->value.integer.value[0] = 0x20 - left_val; + uinfo->value.integer.value[1] = 0x20 - right_val; + } + + return 0; +} + +static int snd_miro_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_miro *miro = snd_kcontrol_chip(kcontrol); + int left, right, left_old, right_old; + int setreg_left, setreg_right, getreg_left, getreg_right; + int change, error; + + left = ucontrol->value.integer.value[0]; + right = ucontrol->value.integer.value[1]; + + setreg_right = (kcontrol->private_value >> 8) & 0xff; + if (setreg_right == ACI_SET_MASTER) { + setreg_left = setreg_right + 1; + } else { + setreg_left = setreg_right + 8; + } + + getreg_right = kcontrol->private_value & 0xff; + getreg_left = getreg_right + 1; + + if ((left_old = aci_getvalue(miro, getreg_left)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_left, left_old); + return left_old; + } + + if ((right_old = aci_getvalue(miro, getreg_right)) < 0) { + snd_printk(KERN_ERR "aci_getvalue(%d) failed: %d\n", getreg_right, right_old); + return right_old; + } + + if ((getreg_right >= ACI_GET_EQ1) && (getreg_right <= ACI_GET_EQ7)) { + + /* equalizer elements */ + + if (left < -0x7f || left > 0x7f || + right < -0x7f || right > 0x7f) + return -EINVAL; + + if (left_old > 0x80) + left_old = 0x80 - left_old; + if (right_old > 0x80) + right_old = 0x80 - right_old; + + if (left >= 0) { + if ((error = aci_setvalue(miro, setreg_left, left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + left, error); + return error; + } + } else { + if ((error = aci_setvalue(miro, setreg_left, 0x80 - left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - left, error); + return error; + } + } + + if (right >= 0) { + if ((error = aci_setvalue(miro, setreg_right, right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + right, error); + return error; + } + } else { + if ((error = aci_setvalue(miro, setreg_right, 0x80 - right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x80 - right, error); + return error; + } + } + + } else { + + /* non-equalizer elements */ + + if (left < 0 || left > 0x20 || + right < 0 || right > 0x20) + return -EINVAL; + + left_old = 0x20 - left_old; + right_old = 0x20 - right_old; + + if ((error = aci_setvalue(miro, setreg_left, 0x20 - left)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - left, error); + return error; + } + if ((error = aci_setvalue(miro, setreg_right, 0x20 - right)) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + 0x20 - right, error); + return error; + } + } + + change = (left != left_old) || (right != right_old); + + return change; +} + +static struct snd_kcontrol_new snd_miro_controls[] __devinitdata = { +MIRO_DOUBLE("Master Playback Volume", 0, ACI_GET_MASTER, ACI_SET_MASTER), +MIRO_DOUBLE("Mic Playback Volume", 1, ACI_GET_MIC, ACI_SET_MIC), +MIRO_DOUBLE("Line Playback Volume", 1, ACI_GET_LINE, ACI_SET_LINE), +MIRO_DOUBLE("CD Playback Volume", 0, ACI_GET_CD, ACI_SET_CD), +MIRO_DOUBLE("Synth Playback Volume", 0, ACI_GET_SYNTH, ACI_SET_SYNTH), +MIRO_DOUBLE("PCM Playback Volume", 1, ACI_GET_PCM, ACI_SET_PCM), +MIRO_DOUBLE("Aux Playback Volume", 2, ACI_GET_LINE2, ACI_SET_LINE2), +}; + +/* Equalizer with seven bands (only PCM20) + from -12dB up to +12dB on each band */ +static struct snd_kcontrol_new snd_miro_eq_controls[] __devinitdata = { +MIRO_DOUBLE("Tone Control - 28 Hz", 0, ACI_GET_EQ1, ACI_SET_EQ1), +MIRO_DOUBLE("Tone Control - 160 Hz", 0, ACI_GET_EQ2, ACI_SET_EQ2), +MIRO_DOUBLE("Tone Control - 400 Hz", 0, ACI_GET_EQ3, ACI_SET_EQ3), +MIRO_DOUBLE("Tone Control - 1 kHz", 0, ACI_GET_EQ4, ACI_SET_EQ4), +MIRO_DOUBLE("Tone Control - 2.5 kHz", 0, ACI_GET_EQ5, ACI_SET_EQ5), +MIRO_DOUBLE("Tone Control - 6.3 kHz", 0, ACI_GET_EQ6, ACI_SET_EQ6), +MIRO_DOUBLE("Tone Control - 16 kHz", 0, ACI_GET_EQ7, ACI_SET_EQ7), +}; + +static struct snd_kcontrol_new snd_miro_radio_control[] __devinitdata = { +MIRO_DOUBLE("Radio Playback Volume", 0, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_line_control[] __devinitdata = { +MIRO_DOUBLE("Line Playback Volume", 2, ACI_GET_LINE1, ACI_SET_LINE1), +}; + +static struct snd_kcontrol_new snd_miro_preamp_control[] __devinitdata = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Boost", + .index = 1, + .info = snd_miro_info_preamp, + .get = snd_miro_get_preamp, + .put = snd_miro_put_preamp, +}}; + +static struct snd_kcontrol_new snd_miro_amp_control[] __devinitdata = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Boost", + .index = 0, + .info = snd_miro_info_amp, + .get = snd_miro_get_amp, + .put = snd_miro_put_amp, +}}; + +static struct snd_kcontrol_new snd_miro_capture_control[] __devinitdata = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Capture Switch", + .index = 0, + .info = snd_miro_info_capture, + .get = snd_miro_get_capture, + .put = snd_miro_put_capture, +}}; + +static unsigned char aci_init_values[][2] __devinitdata = { + { ACI_SET_MUTE, 0x00 }, + { ACI_SET_POWERAMP, 0x00 }, + { ACI_SET_PREAMP, 0x00 }, + { ACI_SET_SOLOMODE, 0x00 }, + { ACI_SET_MIC + 0, 0x20 }, + { ACI_SET_MIC + 8, 0x20 }, + { ACI_SET_LINE + 0, 0x20 }, + { ACI_SET_LINE + 8, 0x20 }, + { ACI_SET_CD + 0, 0x20 }, + { ACI_SET_CD + 8, 0x20 }, + { ACI_SET_PCM + 0, 0x20 }, + { ACI_SET_PCM + 8, 0x20 }, + { ACI_SET_LINE1 + 0, 0x20 }, + { ACI_SET_LINE1 + 8, 0x20 }, + { ACI_SET_LINE2 + 0, 0x20 }, + { ACI_SET_LINE2 + 8, 0x20 }, + { ACI_SET_SYNTH + 0, 0x20 }, + { ACI_SET_SYNTH + 8, 0x20 }, + { ACI_SET_MASTER + 0, 0x20 }, + { ACI_SET_MASTER + 1, 0x20 }, +}; + +static int __devinit snd_set_aci_init_values(struct snd_miro *miro) +{ + int idx, error; + + /* enable WSS on PCM1 */ + + if ((miro->aci_product == 'A') && wss) { + if ((error = aci_setvalue(miro, ACI_SET_WSS, wss)) < 0) { + snd_printk(KERN_ERR "enabling WSS mode failed\n"); + return error; + } + } + + /* enable IDE port */ + + if (ide) { + if ((error = aci_setvalue(miro, ACI_SET_IDE, ide)) < 0) { + snd_printk(KERN_ERR "enabling IDE port failed\n"); + return error; + } + } + + /* set common aci values */ + + for (idx = 0; idx < ARRAY_SIZE(aci_init_values); idx++) + if ((error = aci_setvalue(miro, aci_init_values[idx][0], + aci_init_values[idx][1])) < 0) { + snd_printk(KERN_ERR "aci_setvalue(%d) failed: %d\n", + aci_init_values[idx][0], error); + return error; + } + + miro->aci_amp = 0; + miro->aci_preamp = 0; + miro->aci_solomode = 1; + + return 0; +} + +static int __devinit snd_miro_mixer(struct snd_miro *miro) +{ + struct snd_card *card; + unsigned int idx; + int err; + + if (snd_BUG_ON(!miro || !miro->card)) + return -EINVAL; + + card = miro->card; + + switch (miro->hardware) { + case OPTi9XX_HW_82C924: + strcpy(card->mixername, "ACI & OPTi924"); + break; + case OPTi9XX_HW_82C929: + strcpy(card->mixername, "ACI & OPTi929"); + break; + default: + snd_BUG(); + break; + } + + for (idx = 0; idx < ARRAY_SIZE(snd_miro_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_controls[idx], miro))) < 0) + return err; + } + + if ((miro->aci_product == 'A') || (miro->aci_product == 'B')) { + /* PCM1/PCM12 with power-amp and Line 2 */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_line_control[0], miro))) < 0) + return err; + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_amp_control[0], miro))) < 0) + return err; + } + + if ((miro->aci_product == 'B') || (miro->aci_product == 'C')) { + /* PCM12/PCM20 with mic-preamp */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_preamp_control[0], miro))) < 0) + return err; + if (miro->aci_version >= 176) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_capture_control[0], miro))) < 0) + return err; + } + + if (miro->aci_product == 'C') { + /* PCM20 with radio and 7 band equalizer */ + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_radio_control[0], miro))) < 0) + return err; + for (idx = 0; idx < ARRAY_SIZE(snd_miro_eq_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_miro_eq_controls[idx], miro))) < 0) + return err; + } + } + + return 0; +} + +static long snd_legacy_find_free_ioport(long *port_table, long size) +{ + while (*port_table != -1) { + struct resource *res; + if ((res = request_region(*port_table, size, + "ALSA test")) != NULL) { + release_and_free_resource(res); + return *port_table; + } + port_table++; + } + return -1; +} + +static int __devinit snd_miro_init(struct snd_miro *chip, + unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + chip->mc_base_size = opti9xx_mc_size[hardware]; + + spin_lock_init(&chip->lock); + + chip->wss_base = -1; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->fm_port = -1; + chip->mpu_port = -1; + chip->mpu_irq = -1; + + switch (hardware) { + case OPTi9XX_HW_82C929: + chip->mc_base = 0xf8c; + chip->password = 0xe3; + chip->pwd_reg = 3; + break; + + case OPTi9XX_HW_82C924: + chip->mc_base = 0xf8c; + chip->password = 0xe5; + chip->pwd_reg = 3; + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", hardware); + return -ENODEV; + } + + return 0; +} + +static unsigned char snd_miro_read(struct snd_miro *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_miro_write(struct snd_miro *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; + + default: + snd_printk(KERN_ERR "sorry, no support for %d\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +#define snd_miro_write_mask(chip, reg, value, mask) \ + snd_miro_write(chip, reg, \ + (snd_miro_read(chip, reg) & ~(mask)) | ((value) & (mask))) + +/* + * Proc Interface + */ + +static void snd_miro_proc_read(struct snd_info_entry * entry, + struct snd_info_buffer *buffer) +{ + struct snd_miro *miro = (struct snd_miro *) entry->private_data; + char* model = "unknown"; + + /* miroSOUND PCM1 pro, early PCM12 */ + + if ((miro->hardware == OPTi9XX_HW_82C929) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'A')) { + switch(miro->aci_version) { + case 3: + model = "miroSOUND PCM1 pro"; + break; + default: + model = "miroSOUND PCM1 pro / (early) PCM12"; + break; + } + } + + /* miroSOUND PCM12, PCM12 (Rev. E), PCM12 pnp */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'B')) { + switch(miro->aci_version) { + case 4: + model = "miroSOUND PCM12"; + break; + case 176: + model = "miroSOUND PCM12 (Rev. E)"; + break; + default: + model = "miroSOUND PCM12 / PCM12 pnp"; + break; + } + } + + /* miroSOUND PCM20 radio */ + + if ((miro->hardware == OPTi9XX_HW_82C924) && + (miro->aci_vendor == 'm') && + (miro->aci_product == 'C')) { + switch(miro->aci_version) { + case 7: + model = "miroSOUND PCM20 radio (Rev. E)"; + break; + default: + model = "miroSOUND PCM20 radio"; + break; + } + } + + snd_iprintf(buffer, "\nGeneral information:\n"); + snd_iprintf(buffer, " model : %s\n", model); + snd_iprintf(buffer, " opti : %s\n", miro->name); + snd_iprintf(buffer, " codec : %s\n", miro->pcm->name); + snd_iprintf(buffer, " port : 0x%lx\n", miro->wss_base); + snd_iprintf(buffer, " irq : %d\n", miro->irq); + snd_iprintf(buffer, " dma : %d,%d\n\n", miro->dma1, miro->dma2); + + snd_iprintf(buffer, "MPU-401:\n"); + snd_iprintf(buffer, " port : 0x%lx\n", miro->mpu_port); + snd_iprintf(buffer, " irq : %d\n\n", miro->mpu_irq); + + snd_iprintf(buffer, "ACI information:\n"); + snd_iprintf(buffer, " vendor : "); + switch(miro->aci_vendor) { + case 'm': + snd_iprintf(buffer, "Miro\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", miro->aci_vendor); + break; + } + + snd_iprintf(buffer, " product : "); + switch(miro->aci_product) { + case 'A': + snd_iprintf(buffer, "miroSOUND PCM1 pro / (early) PCM12\n"); + break; + case 'B': + snd_iprintf(buffer, "miroSOUND PCM12\n"); + break; + case 'C': + snd_iprintf(buffer, "miroSOUND PCM20 radio\n"); + break; + default: + snd_iprintf(buffer, "unknown (0x%x)\n", miro->aci_product); + break; + } + + snd_iprintf(buffer, " firmware: %d (0x%x)\n", + miro->aci_version, miro->aci_version); + snd_iprintf(buffer, " port : 0x%lx-0x%lx\n", + miro->aci_port, miro->aci_port+2); + snd_iprintf(buffer, " wss : 0x%x\n", wss); + snd_iprintf(buffer, " ide : 0x%x\n", ide); + snd_iprintf(buffer, " solomode: 0x%x\n", miro->aci_solomode); + snd_iprintf(buffer, " amp : 0x%x\n", miro->aci_amp); + snd_iprintf(buffer, " preamp : 0x%x\n", miro->aci_preamp); +} + +static void __devinit snd_miro_proc_init(struct snd_miro * miro) +{ + struct snd_info_entry *entry; + + if (! snd_card_proc_new(miro->card, "miro", &entry)) + snd_info_set_text_ops(entry, miro, snd_miro_proc_read); +} + +/* + * Init + */ + +static int __devinit snd_miro_configure(struct snd_miro *chip) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + unsigned long flags; + + switch (chip->hardware) { + case OPTi9XX_HW_82C924: + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); + break; + case OPTi9XX_HW_82C929: + /* untested init commands for OPTi929 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(2), 0x20, 0x20); /* OPL4 */ + snd_miro_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); + snd_miro_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); + break; + default: + snd_printk(KERN_ERR "chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + switch (chip->wss_base) { + case 0x530: + wss_base_bits = 0x00; + break; + case 0x604: + wss_base_bits = 0x03; + break; + case 0xe80: + wss_base_bits = 0x01; + break; + case 0xf40: + wss_base_bits = 0x02; + break; + default: + snd_printk(KERN_ERR "WSS port 0x%lx not valid\n", chip->wss_base); + goto __skip_base; + } + snd_miro_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (chip->irq) { + case 5: + irq_bits = 0x05; + break; + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk(KERN_ERR "WSS irq # %d not valid\n", chip->irq); + goto __skip_resources; + } + + switch (chip->dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk(KERN_ERR "WSS dma1 # %d not valid\n", chip->dma1); + goto __skip_resources; + } + + if (chip->dma1 == chip->dma2) { + snd_printk(KERN_ERR "don't want to share dmas\n"); + return -EBUSY; + } + + switch (chip->dma2) { + case 0: + case 1: + break; + default: + snd_printk(KERN_ERR "WSS dma2 # %d not valid\n", chip->dma2); + goto __skip_resources; + } + dma_bits |= 0x04; + + spin_lock_irqsave(&chip->lock, flags); + outb(irq_bits << 3 | dma_bits, chip->wss_base); + spin_unlock_irqrestore(&chip->lock, flags); + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (chip->mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk(KERN_ERR "MPU-401 port 0x%lx not valid\n", + chip->mpu_port); + goto __skip_mpu; + } + + switch (chip->mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk(KERN_ERR "MPU-401 irq # %d not valid\n", + chip->mpu_irq); + goto __skip_mpu; + } + + snd_miro_write_mask(chip, OPTi9XX_MC_REG(6), + (chip->mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +static int __devinit snd_card_miro_detect(struct snd_card *card, + struct snd_miro *chip) +{ + int i, err; + unsigned char value; + + for (i = OPTi9XX_HW_82C929; i <= OPTi9XX_HW_82C924; i++) { + + if ((err = snd_miro_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + value = snd_miro_read(chip, OPTi9XX_MC_REG(1)); + if ((value != 0xff) && (value != inb(chip->mc_base + 1))) + if (value == snd_miro_read(chip, OPTi9XX_MC_REG(1))) + return 1; + + release_and_free_resource(chip->res_mc_base); + chip->res_mc_base = NULL; + + } + + return -ENODEV; +} + +static int __devinit snd_card_miro_aci_detect(struct snd_card *card, + struct snd_miro * miro) +{ + unsigned char regval; + int i; + + mutex_init(&miro->aci_mutex); + + /* get ACI port from OPTi9xx MC 4 */ + + miro->mc_base = 0xf8c; + regval=inb(miro->mc_base + 4); + miro->aci_port = (regval & 0x10) ? 0x344: 0x354; + + if ((miro->res_aci_port = request_region(miro->aci_port, 3, "miro aci")) == NULL) { + snd_printk(KERN_ERR "aci i/o area 0x%lx-0x%lx already used.\n", + miro->aci_port, miro->aci_port+2); + return -ENOMEM; + } + + /* force ACI into a known state */ + for (i = 0; i < 3; i++) + if (aci_cmd(miro, ACI_ERROR_OP, -1, -1) < 0) { + snd_printk(KERN_ERR "can't force aci into known state.\n"); + return -ENXIO; + } + + if ((miro->aci_vendor=aci_cmd(miro, ACI_READ_IDCODE, -1, -1)) < 0 || + (miro->aci_product=aci_cmd(miro, ACI_READ_IDCODE, -1, -1)) < 0) { + snd_printk(KERN_ERR "can't read aci id on 0x%lx.\n", miro->aci_port); + return -ENXIO; + } + + if ((miro->aci_version=aci_cmd(miro, ACI_READ_VERSION, -1, -1)) < 0) { + snd_printk(KERN_ERR "can't read aci version on 0x%lx.\n", + miro->aci_port); + return -ENXIO; + } + + if (aci_cmd(miro, ACI_INIT, -1, -1) < 0 || + aci_cmd(miro, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0 || + aci_cmd(miro, ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP) < 0) { + snd_printk(KERN_ERR "can't initialize aci.\n"); + return -ENXIO; + } + + return 0; +} + +static void snd_card_miro_free(struct snd_card *card) +{ + struct snd_miro *miro = card->private_data; + + release_and_free_resource(miro->res_aci_port); + release_and_free_resource(miro->res_mc_base); +} + +static int __devinit snd_miro_match(struct device *devptr, unsigned int n) +{ + return 1; +} + +static int __devinit snd_miro_probe(struct device *devptr, unsigned int n) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + static long possible_mpu_ports[] = {0x330, 0x300, 0x310, 0x320, -1}; + static int possible_irqs[] = {11, 9, 10, 7, -1}; + static int possible_mpu_irqs[] = {10, 5, 9, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; + static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}}; + + int error; + struct snd_miro *miro; + struct snd_wss *codec; + struct snd_timer *timer; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + + if (!(card = snd_card_new(index, id, THIS_MODULE, + sizeof(struct snd_miro)))) + return -ENOMEM; + + card->private_free = snd_card_miro_free; + miro = card->private_data; + miro->card = card; + + if ((error = snd_card_miro_aci_detect(card, miro)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to detect aci chip\n"); + return -ENODEV; + } + + /* init proc interface */ + snd_miro_proc_init(miro); + + if ((error = snd_card_miro_detect(card, miro)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to detect OPTi9xx chip\n"); + return -ENODEV; + } + + if (! miro->res_mc_base && + (miro->res_mc_base = request_region(miro->mc_base, miro->mc_base_size, + "miro (OPTi9xx MC)")) == NULL) { + snd_card_free(card); + snd_printk(KERN_ERR "request for OPTI9xx MC failed\n"); + return -ENOMEM; + } + + miro->wss_base = port; + miro->fm_port = fm_port; + miro->mpu_port = mpu_port; + miro->irq = irq; + miro->mpu_irq = mpu_irq; + miro->dma1 = dma1; + miro->dma2 = dma2; + + if (miro->wss_base == SNDRV_AUTO_PORT) { + if ((miro->wss_base = snd_legacy_find_free_ioport(possible_ports, 4)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free WSS port\n"); + return -EBUSY; + } + } + + if (miro->mpu_port == SNDRV_AUTO_PORT) { + if ((miro->mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + if (miro->irq == SNDRV_AUTO_IRQ) { + if ((miro->irq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (miro->mpu_irq == SNDRV_AUTO_IRQ) { + if ((miro->mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (miro->dma1 == SNDRV_AUTO_DMA) { + if ((miro->dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } + if (miro->dma2 == SNDRV_AUTO_DMA) { + if ((miro->dma2 = snd_legacy_find_free_dma(possible_dma2s[miro->dma1 % 4])) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR "unable to find a free DMA2\n"); + return -EBUSY; + } + } + + error = snd_miro_configure(miro); + if (error) { + snd_card_free(card); + return error; + } + + error = snd_wss_create(card, miro->wss_base + 4, -1, + miro->irq, miro->dma1, miro->dma2, + WSS_HW_AD1845, 0, &codec); + if (error < 0) { + snd_card_free(card); + return error; + } + + error = snd_wss_pcm(codec, 0, &pcm); + if (error < 0) { + snd_card_free(card); + return error; + } + error = snd_wss_mixer(codec); + if (error < 0) { + snd_card_free(card); + return error; + } + error = snd_wss_timer(codec, 0, &timer); + if (error < 0) { + snd_card_free(card); + return error; + } + + miro->pcm = pcm; + + if ((error = snd_miro_mixer(miro)) < 0) { + snd_card_free(card); + return error; + } + + if (miro->aci_vendor == 'm') { + /* It looks like a miro sound card. */ + switch (miro->aci_product) { + case 'A': + sprintf(card->shortname, + "miroSOUND PCM1 pro / PCM12"); + break; + case 'B': + sprintf(card->shortname, + "miroSOUND PCM12"); + break; + case 'C': + sprintf(card->shortname, + "miroSOUND PCM20 radio"); + break; + default: + sprintf(card->shortname, + "unknown miro"); + snd_printk(KERN_INFO "unknown miro aci id\n"); + break; + } + } else { + snd_printk(KERN_INFO "found unsupported aci card\n"); + sprintf(card->shortname, "unknown Cardinal Technologies"); + } + + strcpy(card->driver, "miro"); + sprintf(card->longname, "%s: OPTi%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, miro->name, pcm->name, miro->wss_base + 4, + miro->irq, miro->dma1, miro->dma2); + + if (miro->mpu_port <= 0 || miro->mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else + if ((error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + miro->mpu_port, 0, miro->mpu_irq, IRQF_DISABLED, + &rmidi))) + snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n", miro->mpu_port); + + if (miro->fm_port > 0 && miro->fm_port != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3 = NULL; + struct snd_opl4 *opl4; + if (snd_opl4_create(card, miro->fm_port, miro->fm_port - 8, + 2, &opl3, &opl4) < 0) + snd_printk(KERN_WARNING "no OPL4 device at 0x%lx\n", miro->fm_port); + } + + if ((error = snd_set_aci_init_values(miro)) < 0) { + snd_card_free(card); + return error; + } + + snd_card_set_dev(card, devptr); + + if ((error = snd_card_register(card))) { + snd_card_free(card); + return error; + } + + dev_set_drvdata(devptr, card); + return 0; +} + +static int __devexit snd_miro_remove(struct device *devptr, unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + dev_set_drvdata(devptr, NULL); + return 0; +} + +#define DEV_NAME "miro" + +static struct isa_driver snd_miro_driver = { + .match = snd_miro_match, + .probe = snd_miro_probe, + .remove = __devexit_p(snd_miro_remove), + /* FIXME: suspend/resume */ + .driver = { + .name = DEV_NAME + }, +}; + +static int __init alsa_card_miro_init(void) +{ + return isa_register_driver(&snd_miro_driver, 1); +} + +static void __exit alsa_card_miro_exit(void) +{ + isa_unregister_driver(&snd_miro_driver); +} + +module_init(alsa_card_miro_init) +module_exit(alsa_card_miro_exit) diff --git a/sound/isa/opti9xx/miro.h b/sound/isa/opti9xx/miro.h new file mode 100644 index 0000000..6e1385b --- /dev/null +++ b/sound/isa/opti9xx/miro.h @@ -0,0 +1,73 @@ +#ifndef _MIRO_H_ +#define _MIRO_H_ + +#define ACI_REG_COMMAND 0 /* write register offset */ +#define ACI_REG_STATUS 1 /* read register offset */ +#define ACI_REG_BUSY 2 /* busy register offset */ +#define ACI_REG_RDS 2 /* PCM20: RDS register offset */ +#define ACI_MINTIME 500 /* ACI time out limit */ + +#define ACI_SET_MUTE 0x0d +#define ACI_SET_POWERAMP 0x0f +#define ACI_SET_TUNERMUTE 0xa3 +#define ACI_SET_TUNERMONO 0xa4 +#define ACI_SET_IDE 0xd0 +#define ACI_SET_WSS 0xd1 +#define ACI_SET_SOLOMODE 0xd2 +#define ACI_SET_PREAMP 0x03 +#define ACI_GET_PREAMP 0x21 +#define ACI_WRITE_TUNE 0xa7 +#define ACI_READ_TUNERSTEREO 0xa8 +#define ACI_READ_TUNERSTATION 0xa9 +#define ACI_READ_VERSION 0xf1 +#define ACI_READ_IDCODE 0xf2 +#define ACI_INIT 0xff +#define ACI_STATUS 0xf0 +#define ACI_S_GENERAL 0x00 +#define ACI_ERROR_OP 0xdf + +/* ACI Mixer */ + +/* These are the values for the right channel GET registers. + Add an offset of 0x01 for the left channel register. + (left=right+0x01) */ + +#define ACI_GET_MASTER 0x03 +#define ACI_GET_MIC 0x05 +#define ACI_GET_LINE 0x07 +#define ACI_GET_CD 0x09 +#define ACI_GET_SYNTH 0x0b +#define ACI_GET_PCM 0x0d +#define ACI_GET_LINE1 0x10 /* Radio on PCM20 */ +#define ACI_GET_LINE2 0x12 + +#define ACI_GET_EQ1 0x22 /* from Bass ... */ +#define ACI_GET_EQ2 0x24 +#define ACI_GET_EQ3 0x26 +#define ACI_GET_EQ4 0x28 +#define ACI_GET_EQ5 0x2a +#define ACI_GET_EQ6 0x2c +#define ACI_GET_EQ7 0x2e /* ... to Treble */ + +/* And these are the values for the right channel SET registers. + For left channel access you have to add an offset of 0x08. + MASTER is an exception, which needs an offset of 0x01 */ + +#define ACI_SET_MASTER 0x00 +#define ACI_SET_MIC 0x30 +#define ACI_SET_LINE 0x31 +#define ACI_SET_CD 0x34 +#define ACI_SET_SYNTH 0x33 +#define ACI_SET_PCM 0x32 +#define ACI_SET_LINE1 0x35 /* Radio on PCM20 */ +#define ACI_SET_LINE2 0x36 + +#define ACI_SET_EQ1 0x40 /* from Bass ... */ +#define ACI_SET_EQ2 0x41 +#define ACI_SET_EQ3 0x42 +#define ACI_SET_EQ4 0x43 +#define ACI_SET_EQ5 0x44 +#define ACI_SET_EQ6 0x45 +#define ACI_SET_EQ7 0x46 /* ... to Treble */ + +#endif /* _MIRO_H_ */ diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c new file mode 100644 index 0000000..19706b0 --- /dev/null +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -0,0 +1,1035 @@ +/* + card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards. + Copyright (C) 1998-2000 by Massimo Piccioni <dafastidio@libero.it> + + Part of this code was developed at the Italian Ministry of Air Defence, + Sixth Division (oh, che pace ...), Rome. + + Thanks to Maria Grazia Pollarini, Salvatore Vassallo. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/isa.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/wss.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#ifndef OPTi93X +#include <sound/opl4.h> +#endif +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_LICENSE("GPL"); +#ifdef OPTi93X +MODULE_DESCRIPTION("OPTi93X"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C931/3}}"); +#else /* OPTi93X */ +#ifdef CS4231 +MODULE_DESCRIPTION("OPTi92X - CS4231"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (CS4231)}," + "{OPTi,82C925 (CS4231)}}"); +#else /* CS4231 */ +MODULE_DESCRIPTION("OPTi92X - AD1848"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (AD1848)}," + "{OPTi,82C925 (AD1848)}," + "{OAK,Mozart}}"); +#endif /* CS4231 */ +#endif /* OPTi93X */ + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +//static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ +#ifdef CONFIG_PNP +static int isapnp = 1; /* Enable ISA PnP detection */ +#endif +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#if defined(CS4231) || defined(OPTi93X) +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#endif /* CS4231 || OPTi93X */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard."); +//module_param(enable, bool, 0444); +//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard."); +#ifdef CONFIG_PNP +module_param(isapnp, bool, 0444); +MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard."); +#endif +module_param(port, long, 0444); +MODULE_PARM_DESC(port, "WSS port # for opti9xx driver."); +module_param(mpu_port, long, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver."); +module_param(fm_port, long, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver."); +module_param(irq, int, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver."); +module_param(mpu_irq, int, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver."); +module_param(dma1, int, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver."); +#if defined(CS4231) || defined(OPTi93X) +module_param(dma2, int, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver."); +#endif /* CS4231 || OPTi93X */ + +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + +#ifdef OPTi93X + +#define OPTi93X_STATUS 0x02 +#define OPTi93X_PORT(chip, r) ((chip)->port + OPTi93X_##r) + +#define OPTi93X_IRQ_PLAYBACK 0x04 +#define OPTi93X_IRQ_CAPTURE 0x08 + +#endif /* OPTi93X */ + +struct snd_opti9xx { + unsigned short hardware; + unsigned char password; + char name[7]; + + unsigned long mc_base; + struct resource *res_mc_base; + unsigned long mc_base_size; +#ifdef OPTi93X + unsigned long mc_indir_index; + struct snd_wss *codec; +#endif /* OPTi93X */ + unsigned long pwd_reg; + + spinlock_t lock; + + long wss_base; + int irq; + int dma1; + int dma2; + + long fm_port; + + long mpu_port; + int mpu_irq; + +#ifdef CONFIG_PNP + struct pnp_dev *dev; + struct pnp_dev *devmpu; +#endif /* CONFIG_PNP */ +}; + +static int snd_opti9xx_pnp_is_probed; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_opti9xx_pnpids[] = { +#ifndef OPTi93X + /* OPTi 82C924 */ + { .id = "OPT0924", .devs = { { "OPT0000" }, { "OPT0002" } }, .driver_data = 0x0924 }, + /* OPTi 82C925 */ + { .id = "OPT0925", .devs = { { "OPT9250" }, { "OPT0002" } }, .driver_data = 0x0925 }, +#else + /* OPTi 82C931/3 */ + { .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } }, .driver_data = 0x0931 }, +#endif /* OPTi93X */ + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids); + +#endif /* CONFIG_PNP */ + +#ifdef OPTi93X +#define DEV_NAME "opti93x" +#else +#define DEV_NAME "opti92x" +#endif + +static char * snd_opti9xx_names[] = { + "unkown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + + +static long __devinit snd_legacy_find_free_ioport(long *port_table, long size) +{ + while (*port_table != -1) { + if (request_region(*port_table, size, "ALSA test")) { + release_region(*port_table, size); + return *port_table; + } + port_table++; + } + return -1; +} + +static int __devinit snd_opti9xx_init(struct snd_opti9xx *chip, + unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + chip->mc_base_size = opti9xx_mc_size[hardware]; + + spin_lock_init(&chip->lock); + + chip->wss_base = -1; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->fm_port = -1; + chip->mpu_port = -1; + chip->mpu_irq = -1; + + switch (hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + chip->mc_base = 0xf8c; + chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3; + chip->pwd_reg = 3; + break; + + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + chip->mc_base = 0xf8c; + chip->password = 0xe5; + chip->pwd_reg = 3; + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d; + chip->mc_indir_index = 0xe0e; + chip->password = 0xe4; + chip->pwd_reg = 0; + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", hardware); + return -ENODEV; + } + return 0; +} + +static unsigned char snd_opti9xx_read(struct snd_opti9xx *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_opti9xx_write(struct snd_opti9xx *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +#define snd_opti9xx_write_mask(chip, reg, value, mask) \ + snd_opti9xx_write(chip, reg, \ + (snd_opti9xx_read(chip, reg) & ~(mask)) | ((value) & (mask))) + + +static int __devinit snd_opti9xx_configure(struct snd_opti9xx *chip) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + + case OPTi9XX_HW_82C925: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); +#ifdef CS4231 + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + /* + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae); + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); +#ifdef CS4231 + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + +#else /* OPTi93X */ + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + /* + * The BTC 1817DW has QS1000 wavetable which is connected + * to the serial digital input of the OPTI931. + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(21), 0x82, 0xff); + /* + * This bit sets OPTI931 to automaticaly select FM + * or digital input signal. + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(26), 0x01, 0x01); + case OPTi9XX_HW_82C930: /* FALL THROUGH */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 | + (chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04), + 0x34); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + switch (chip->wss_base) { + case 0x530: + wss_base_bits = 0x00; + break; + case 0x604: + wss_base_bits = 0x03; + break; + case 0xe80: + wss_base_bits = 0x01; + break; + case 0xf40: + wss_base_bits = 0x02; + break; + default: + snd_printk("WSS port 0x%lx not valid\n", chip->wss_base); + goto __skip_base; + } + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (chip->irq) { +//#ifdef OPTi93X + case 5: + irq_bits = 0x05; + break; +//#endif /* OPTi93X */ + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk("WSS irq # %d not valid\n", chip->irq); + goto __skip_resources; + } + + switch (chip->dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk("WSS dma1 # %d not valid\n", chip->dma1); + goto __skip_resources; + } + +#if defined(CS4231) || defined(OPTi93X) + if (chip->dma1 == chip->dma2) { + snd_printk("don't want to share dmas\n"); + return -EBUSY; + } + + switch (chip->dma2) { + case 0: + case 1: + break; + default: + snd_printk("WSS dma2 # %d not valid\n", chip->dma2); + goto __skip_resources; + } + dma_bits |= 0x04; +#endif /* CS4231 || OPTi93X */ + +#ifndef OPTi93X + outb(irq_bits << 3 | dma_bits, chip->wss_base); +#else /* OPTi93X */ + snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits)); +#endif /* OPTi93X */ + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (chip->mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk("MPU-401 port 0x%lx not valid\n", + chip->mpu_port); + goto __skip_mpu; + } + + switch (chip->mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk("MPU-401 irq # %d not valid\n", + chip->mpu_irq); + goto __skip_mpu; + } + + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), + (chip->mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +#ifdef OPTi93X + +static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id) +{ + struct snd_wss *codec = dev_id; + struct snd_opti9xx *chip = codec->card->private_data; + unsigned char status; + + status = snd_opti9xx_read(chip, OPTi9XX_MC_REG(11)); + if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream) + snd_pcm_period_elapsed(codec->playback_substream); + if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) { + snd_wss_overrange(codec); + snd_pcm_period_elapsed(codec->capture_substream); + } + outb(0x00, OPTi93X_PORT(codec, STATUS)); + return IRQ_HANDLED; +} + +#endif /* OPTi93X */ + +static int __devinit snd_card_opti9xx_detect(struct snd_card *card, + struct snd_opti9xx *chip) +{ + int i, err; + +#ifndef OPTi93X + for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) { + unsigned char value; + + if ((err = snd_opti9xx_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)); + if ((value != 0xff) && (value != inb(chip->mc_base + 1))) + if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1))) + return 1; + + release_and_free_resource(chip->res_mc_base); + chip->res_mc_base = NULL; + + } +#else /* OPTi93X */ + for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) { + unsigned long flags; + unsigned char value; + + if ((err = snd_opti9xx_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(((chip->mc_indir_index & (1 << 8)) >> 4) | + ((chip->mc_indir_index & 0xf0) >> 4), chip->mc_base); + spin_unlock_irqrestore(&chip->lock, flags); + + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)); + snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value); + if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value) + return 1; + + release_and_free_resource(chip->res_mc_base); + chip->res_mc_base = NULL; + } +#endif /* OPTi93X */ + + return -ENODEV; +} + +#ifdef CONFIG_PNP +static int __devinit snd_card_opti9xx_pnp(struct snd_opti9xx *chip, + struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *pdev; + int err; + + chip->dev = pnp_request_card_device(card, pid->devs[0].id, NULL); + if (chip->dev == NULL) + return -EBUSY; + + chip->devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL); + + pdev = chip->dev; + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err); + return err; + } + +#ifdef OPTi93X + port = pnp_port_start(pdev, 0) - 4; + fm_port = pnp_port_start(pdev, 1) + 8; +#else + if (pid->driver_data != 0x0924) + port = pnp_port_start(pdev, 1); + fm_port = pnp_port_start(pdev, 2) + 8; +#endif /* OPTi93X */ + irq = pnp_irq(pdev, 0); + dma1 = pnp_dma(pdev, 0); +#if defined(CS4231) || defined(OPTi93X) + dma2 = pnp_dma(pdev, 1); +#endif /* CS4231 || OPTi93X */ + + pdev = chip->devmpu; + if (pdev && mpu_port > 0) { + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure\n"); + mpu_port = -1; + chip->devmpu = NULL; + } else { + mpu_port = pnp_port_start(pdev, 0); + mpu_irq = pnp_irq(pdev, 0); + } + } + return pid->driver_data; +} +#endif /* CONFIG_PNP */ + +static void snd_card_opti9xx_free(struct snd_card *card) +{ + struct snd_opti9xx *chip = card->private_data; + + if (chip) { +#ifdef OPTi93X + struct snd_wss *codec = chip->codec; + if (codec && codec->irq > 0) { + disable_irq(codec->irq); + free_irq(codec->irq, codec); + } +#endif + release_and_free_resource(chip->res_mc_base); + } +} + +static int __devinit snd_opti9xx_probe(struct snd_card *card) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + int error; + struct snd_opti9xx *chip = card->private_data; + struct snd_wss *codec; +#ifdef CS4231 + struct snd_timer *timer; +#endif + struct snd_pcm *pcm; + struct snd_rawmidi *rmidi; + struct snd_hwdep *synth; + + if (! chip->res_mc_base && + (chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, + "OPTi9xx MC")) == NULL) + return -ENOMEM; + + chip->wss_base = port; + chip->fm_port = fm_port; + chip->mpu_port = mpu_port; + chip->irq = irq; + chip->mpu_irq = mpu_irq; + chip->dma1 = dma1; +#if defined(CS4231) || defined(OPTi93X) + chip->dma2 = dma2; +#else + chip->dma2 = -1; +#endif + + if (chip->wss_base == SNDRV_AUTO_PORT) { + chip->wss_base = snd_legacy_find_free_ioport(possible_ports, 4); + if (chip->wss_base < 0) { + snd_printk("unable to find a free WSS port\n"); + return -EBUSY; + } + } + error = snd_opti9xx_configure(chip); + if (error) + return error; + + error = snd_wss_create(card, chip->wss_base + 4, -1, + chip->irq, chip->dma1, chip->dma2, +#ifdef OPTi93X + WSS_HW_OPTI93X, WSS_HWSHARE_IRQ, +#else + WSS_HW_DETECT, 0, +#endif + &codec); + if (error < 0) + return error; +#ifdef OPTi93X + chip->codec = codec; +#endif + error = snd_wss_pcm(codec, 0, &pcm); + if (error < 0) + return error; + error = snd_wss_mixer(codec); + if (error < 0) + return error; +#ifdef CS4231 + error = snd_wss_timer(codec, 0, &timer); + if (error < 0) + return error; +#endif +#ifdef OPTi93X + error = request_irq(chip->irq, snd_opti93x_interrupt, + IRQF_DISABLED, DEV_NAME" - WSS", codec); + if (error < 0) { + snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", chip->irq); + return error; + } +#endif + strcpy(card->driver, chip->name); + sprintf(card->shortname, "OPTi %s", card->driver); +#if defined(CS4231) || defined(OPTi93X) + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, pcm->name, chip->wss_base + 4, + chip->irq, chip->dma1, chip->dma2); +#else + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d", + card->shortname, pcm->name, chip->wss_base + 4, + chip->irq, chip->dma1); +#endif /* CS4231 || OPTi93X */ + + if (chip->mpu_port <= 0 || chip->mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else + if ((error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + chip->mpu_port, 0, chip->mpu_irq, IRQF_DISABLED, + &rmidi))) + snd_printk(KERN_WARNING "no MPU-401 device at 0x%lx?\n", + chip->mpu_port); + + if (chip->fm_port > 0 && chip->fm_port != SNDRV_AUTO_PORT) { + struct snd_opl3 *opl3 = NULL; +#ifndef OPTi93X + if (chip->hardware == OPTi9XX_HW_82C928 || + chip->hardware == OPTi9XX_HW_82C929 || + chip->hardware == OPTi9XX_HW_82C924) { + struct snd_opl4 *opl4; + /* assume we have an OPL4 */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x20, 0x20); + if (snd_opl4_create(card, + chip->fm_port, + chip->fm_port - 8, + 2, &opl3, &opl4) < 0) { + /* no luck, use OPL3 instead */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x00, 0x20); + } + } +#endif /* !OPTi93X */ + if (!opl3 && snd_opl3_create(card, + chip->fm_port, + chip->fm_port + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_WARNING "no OPL device at 0x%lx-0x%lx\n", + chip->fm_port, chip->fm_port + 4 - 1); + } + if (opl3) { +#ifdef CS4231 + const int t1dev = 1; +#else + const int t1dev = 0; +#endif + if ((error = snd_opl3_timer_new(opl3, t1dev, t1dev+1)) < 0) + return error; + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, &synth)) < 0) + return error; + } + } + + return snd_card_register(card); +} + +static struct snd_card *snd_opti9xx_card_new(void) +{ + struct snd_card *card; + + card = snd_card_new(index, id, THIS_MODULE, sizeof(struct snd_opti9xx)); + if (! card) + return NULL; + card->private_free = snd_card_opti9xx_free; + return card; +} + +static int __devinit snd_opti9xx_isa_match(struct device *devptr, + unsigned int dev) +{ +#ifdef CONFIG_PNP + if (snd_opti9xx_pnp_is_probed) + return 0; + if (isapnp) + return 0; +#endif + return 1; +} + +static int __devinit snd_opti9xx_isa_probe(struct device *devptr, + unsigned int dev) +{ + struct snd_card *card; + int error; + static long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1}; +#ifdef OPTi93X + static int possible_irqs[] = {5, 9, 10, 11, 7, -1}; +#else + static int possible_irqs[] = {9, 10, 11, 7, -1}; +#endif /* OPTi93X */ + static int possible_mpu_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; +#if defined(CS4231) || defined(OPTi93X) + static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}}; +#endif /* CS4231 || OPTi93X */ + + if (mpu_port == SNDRV_AUTO_PORT) { + if ((mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) { + snd_printk(KERN_ERR "unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + if (irq == SNDRV_AUTO_IRQ) { + if ((irq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_printk(KERN_ERR "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (mpu_irq == SNDRV_AUTO_IRQ) { + if ((mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) { + snd_printk(KERN_ERR "unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (dma1 == SNDRV_AUTO_DMA) { + if ((dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) { + snd_printk(KERN_ERR "unable to find a free DMA1\n"); + return -EBUSY; + } + } +#if defined(CS4231) || defined(OPTi93X) + if (dma2 == SNDRV_AUTO_DMA) { + if ((dma2 = snd_legacy_find_free_dma(possible_dma2s[dma1 % 4])) < 0) { + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } +#endif + + card = snd_opti9xx_card_new(); + if (! card) + return -ENOMEM; + + if ((error = snd_card_opti9xx_detect(card, card->private_data)) < 0) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, devptr); + if ((error = snd_opti9xx_probe(card)) < 0) { + snd_card_free(card); + return error; + } + dev_set_drvdata(devptr, card); + return 0; +} + +static int __devexit snd_opti9xx_isa_remove(struct device *devptr, + unsigned int dev) +{ + snd_card_free(dev_get_drvdata(devptr)); + dev_set_drvdata(devptr, NULL); + return 0; +} + +static struct isa_driver snd_opti9xx_driver = { + .match = snd_opti9xx_isa_match, + .probe = snd_opti9xx_isa_probe, + .remove = __devexit_p(snd_opti9xx_isa_remove), + /* FIXME: suspend/resume */ + .driver = { + .name = DEV_NAME + }, +}; + +#ifdef CONFIG_PNP +static int __devinit snd_opti9xx_pnp_probe(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct snd_card *card; + int error, hw; + struct snd_opti9xx *chip; + + if (snd_opti9xx_pnp_is_probed) + return -EBUSY; + if (! isapnp) + return -ENODEV; + card = snd_opti9xx_card_new(); + if (! card) + return -ENOMEM; + chip = card->private_data; + + hw = snd_card_opti9xx_pnp(chip, pcard, pid); + switch (hw) { + case 0x0924: + hw = OPTi9XX_HW_82C924; + break; + case 0x0925: + hw = OPTi9XX_HW_82C925; + break; + case 0x0931: + hw = OPTi9XX_HW_82C931; + break; + default: + snd_card_free(card); + return -ENODEV; + } + + if ((error = snd_opti9xx_init(chip, hw))) { + snd_card_free(card); + return error; + } + if (hw <= OPTi9XX_HW_82C930) + chip->mc_base -= 0x80; + snd_card_set_dev(card, &pcard->card->dev); + if ((error = snd_opti9xx_probe(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + snd_opti9xx_pnp_is_probed = 1; + return 0; +} + +static void __devexit snd_opti9xx_pnp_remove(struct pnp_card_link * pcard) +{ + snd_card_free(pnp_get_card_drvdata(pcard)); + pnp_set_card_drvdata(pcard, NULL); + snd_opti9xx_pnp_is_probed = 0; +} + +static struct pnp_card_driver opti9xx_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "opti9xx", + .id_table = snd_opti9xx_pnpids, + .probe = snd_opti9xx_pnp_probe, + .remove = __devexit_p(snd_opti9xx_pnp_remove), +}; +#endif + +#ifdef OPTi93X +#define CHIP_NAME "82C93x" +#else +#define CHIP_NAME "82C92x" +#endif + +static int __init alsa_card_opti9xx_init(void) +{ +#ifdef CONFIG_PNP + pnp_register_card_driver(&opti9xx_pnpc_driver); + if (snd_opti9xx_pnp_is_probed) + return 0; + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif + return isa_register_driver(&snd_opti9xx_driver, 1); +} + +static void __exit alsa_card_opti9xx_exit(void) +{ + if (!snd_opti9xx_pnp_is_probed) { + isa_unregister_driver(&snd_opti9xx_driver); + return; + } +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif +} + +module_init(alsa_card_opti9xx_init) +module_exit(alsa_card_opti9xx_exit) diff --git a/sound/isa/opti9xx/opti92x-cs4231.c b/sound/isa/opti9xx/opti92x-cs4231.c new file mode 100644 index 0000000..b17ab19 --- /dev/null +++ b/sound/isa/opti9xx/opti92x-cs4231.c @@ -0,0 +1,2 @@ +#define CS4231 +#include "opti92x-ad1848.c" diff --git a/sound/isa/opti9xx/opti93x.c b/sound/isa/opti9xx/opti93x.c new file mode 100644 index 0000000..bad9da5 --- /dev/null +++ b/sound/isa/opti9xx/opti93x.c @@ -0,0 +1,3 @@ +#define OPTi93X +#include "opti92x-ad1848.c" + |