diff options
author | Linus Torvalds | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/isa/cs423x |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'sound/isa/cs423x')
-rw-r--r-- | sound/isa/cs423x/Makefile | 25 | ||||
-rw-r--r-- | sound/isa/cs423x/cs4231.c | 169 | ||||
-rw-r--r-- | sound/isa/cs423x/cs4231_lib.c | 1964 | ||||
-rw-r--r-- | sound/isa/cs423x/cs4232.c | 2 | ||||
-rw-r--r-- | sound/isa/cs423x/cs4236.c | 608 | ||||
-rw-r--r-- | sound/isa/cs423x/cs4236_lib.c | 970 |
6 files changed, 3738 insertions, 0 deletions
diff --git a/sound/isa/cs423x/Makefile b/sound/isa/cs423x/Makefile new file mode 100644 index 000000000000..d2afaea30cbc --- /dev/null +++ b/sound/isa/cs423x/Makefile @@ -0,0 +1,25 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-cs4231-lib-objs := cs4231_lib.o +snd-cs4236-lib-objs := cs4236_lib.o +snd-cs4231-objs := cs4231.o +snd-cs4232-objs := cs4232.o +snd-cs4236-objs := cs4236.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AZT2320) += snd-cs4231-lib.o +obj-$(CONFIG_SND_OPL3SA2) += snd-cs4231-lib.o +obj-$(CONFIG_SND_CS4231) += snd-cs4231.o snd-cs4231-lib.o +obj-$(CONFIG_SND_CS4232) += snd-cs4232.o snd-cs4231-lib.o +obj-$(CONFIG_SND_CS4236) += snd-cs4236.o snd-cs4236-lib.o snd-cs4231-lib.o +obj-$(CONFIG_SND_GUSMAX) += snd-cs4231-lib.o +obj-$(CONFIG_SND_INTERWAVE) += snd-cs4231-lib.o +obj-$(CONFIG_SND_INTERWAVE_STB) += snd-cs4231-lib.o +obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-cs4231-lib.o +obj-$(CONFIG_SND_WAVEFRONT) += snd-cs4231-lib.o +obj-$(CONFIG_SND_SSCAPE) += snd-cs4231-lib.o + +obj-m := $(sort $(obj-m)) diff --git a/sound/isa/cs423x/cs4231.c b/sound/isa/cs423x/cs4231.c new file mode 100644 index 000000000000..7640837659ea --- /dev/null +++ b/sound/isa/cs423x/cs4231.c @@ -0,0 +1,169 @@ +/* + * Generic driver for CS4231 chips + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Originally the CS4232/CS4232A driver, modified for use on CS4231 by + * Tugrul Galatali <galatalt@stuy.edu> + * + * 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 <sound/driver.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/cs4231.h> +#include <sound/mpu401.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Generic CS4231"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4231}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; /* Enable this card */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CS4231 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CS4231 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CS4231 soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for CS4231 driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for CS4231 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for CS4231 driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for CS4231 driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for CS4231 driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for CS4231 driver."); + +static snd_card_t *snd_cs4231_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_card_cs4231_probe(int dev) +{ + snd_card_t *card; + struct snd_card_cs4231 *acard; + snd_pcm_t *pcm = NULL; + cs4231_t *chip; + int err; + + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify port\n"); + return -EINVAL; + } + if (irq[dev] == SNDRV_AUTO_IRQ) { + snd_printk("specify irq\n"); + return -EINVAL; + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + snd_printk("specify dma1\n"); + return -EINVAL; + } + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_card_cs4231 *)card->private_data; + if ((err = snd_cs4231_create(card, port[dev], -1, + irq[dev], + dma1[dev], + dma2[dev], + CS4231_HW_DETECT, + 0, &chip)) < 0) { + snd_card_free(card); + return err; + } + + if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "CS4231"); + strcpy(card->shortname, pcm->name); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + pcm->name, chip->port, irq[dev], dma1[dev]); + if (dma2[dev] >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]); + + if ((err = snd_cs4231_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_timer(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232, + mpu_port[dev], 0, + mpu_irq[dev], + mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0, + NULL) < 0) + printk(KERN_ERR "cs4231: MPU401 not detected\n"); + } + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_cs4231_cards[dev] = card; + return 0; +} + +static int __init alsa_card_cs4231_init(void) +{ + int dev, cards; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) { + if (snd_card_cs4231_probe(dev) >= 0) + cards++; + } + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "CS4231 soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_cs4231_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_cs4231_cards[idx]); +} + +module_init(alsa_card_cs4231_init) +module_exit(alsa_card_cs4231_exit) diff --git a/sound/isa/cs423x/cs4231_lib.c b/sound/isa/cs423x/cs4231_lib.c new file mode 100644 index 000000000000..3e7a2a33a5ca --- /dev/null +++ b/sound/isa/cs423x/cs4231_lib.c @@ -0,0 +1,1964 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of CS4231(A)/CS4232/InterWave & compatible chips + * + * Bugs: + * - sometimes record brokes playback with WSS portion of + * Yamaha OPL3-SA3 chip + * - CS4231 (GUS MAX) - still trouble with occasional noises + * - broken initialization? + * + * 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 <sound/driver.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/cs4231.h> +#include <sound/pcm_params.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/irq.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for control of CS4231(A)/CS4232/InterWave & compatible chips"); +MODULE_LICENSE("GPL"); + +#if 0 +#define SNDRV_DEBUG_MCE +#endif + +/* + * Some variables + */ + +static unsigned char freq_bits[14] = { + /* 5510 */ 0x00 | CS4231_XTAL2, + /* 6620 */ 0x0E | CS4231_XTAL2, + /* 8000 */ 0x00 | CS4231_XTAL1, + /* 9600 */ 0x0E | CS4231_XTAL1, + /* 11025 */ 0x02 | CS4231_XTAL2, + /* 16000 */ 0x02 | CS4231_XTAL1, + /* 18900 */ 0x04 | CS4231_XTAL2, + /* 22050 */ 0x06 | CS4231_XTAL2, + /* 27042 */ 0x04 | CS4231_XTAL1, + /* 32000 */ 0x06 | CS4231_XTAL1, + /* 33075 */ 0x0C | CS4231_XTAL2, + /* 37800 */ 0x08 | CS4231_XTAL2, + /* 44100 */ 0x0A | CS4231_XTAL2, + /* 48000 */ 0x0C | CS4231_XTAL1 +}; + +static unsigned int rates[14] = { + 5510, 6620, 8000, 9600, 11025, 16000, 18900, 22050, + 27042, 32000, 33075, 37800, 44100, 48000 +}; + +static snd_pcm_hw_constraint_list_t hw_constraints_rates = { + .count = 14, + .list = rates, + .mask = 0, +}; + +static int snd_cs4231_xrate(snd_pcm_runtime_t *runtime) +{ + return snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); +} + +static unsigned char snd_cs4231_original_image[32] = +{ + 0x00, /* 00/00 - lic */ + 0x00, /* 01/01 - ric */ + 0x9f, /* 02/02 - la1ic */ + 0x9f, /* 03/03 - ra1ic */ + 0x9f, /* 04/04 - la2ic */ + 0x9f, /* 05/05 - ra2ic */ + 0xbf, /* 06/06 - loc */ + 0xbf, /* 07/07 - roc */ + 0x20, /* 08/08 - pdfr */ + CS4231_AUTOCALIB, /* 09/09 - ic */ + 0x00, /* 0a/10 - pc */ + 0x00, /* 0b/11 - ti */ + CS4231_MODE2, /* 0c/12 - mi */ + 0xfc, /* 0d/13 - lbc */ + 0x00, /* 0e/14 - pbru */ + 0x00, /* 0f/15 - pbrl */ + 0x80, /* 10/16 - afei */ + 0x01, /* 11/17 - afeii */ + 0x9f, /* 12/18 - llic */ + 0x9f, /* 13/19 - rlic */ + 0x00, /* 14/20 - tlb */ + 0x00, /* 15/21 - thb */ + 0x00, /* 16/22 - la3mic/reserved */ + 0x00, /* 17/23 - ra3mic/reserved */ + 0x00, /* 18/24 - afs */ + 0x00, /* 19/25 - lamoc/version */ + 0xcf, /* 1a/26 - mioc */ + 0x00, /* 1b/27 - ramoc/reserved */ + 0x20, /* 1c/28 - cdfr */ + 0x00, /* 1d/29 - res4 */ + 0x00, /* 1e/30 - cbru */ + 0x00, /* 1f/31 - cbrl */ +}; + +/* + * Basic I/O functions + */ + +#if !defined(EBUS_SUPPORT) && !defined(SBUS_SUPPORT) +#define __CS4231_INLINE__ inline +#else +#define __CS4231_INLINE__ /* nothing */ +#endif + +static __CS4231_INLINE__ void cs4231_outb(cs4231_t *chip, u8 offset, u8 val) +{ +#ifdef EBUS_SUPPORT + if (chip->ebus->flag) { + writeb(val, chip->port + (offset << 2)); + } else { +#endif +#ifdef SBUS_SUPPORT + sbus_writeb(val, chip->port + (offset << 2)); +#endif +#ifdef EBUS_SUPPORT + } +#endif +#ifdef LEGACY_SUPPORT + outb(val, chip->port + offset); +#endif +} + +static __CS4231_INLINE__ u8 cs4231_inb(cs4231_t *chip, u8 offset) +{ +#ifdef EBUS_SUPPORT + if (chip->ebus_flag) { + return readb(chip->port + (offset << 2)); + } else { +#endif +#ifdef SBUS_SUPPORT + return sbus_readb(chip->port + (offset << 2)); +#endif +#ifdef EBUS_SUPPORT + } +#endif +#ifdef LEGACY_SUPPORT + return inb(chip->port + offset); +#endif +} + +static void snd_cs4231_outm(cs4231_t *chip, unsigned char reg, + unsigned char mask, unsigned char value) +{ + int timeout; + unsigned char tmp; + + for (timeout = 250; + timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk("outm: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value); +#endif + if (chip->calibrate_mute) { + chip->image[reg] &= mask; + chip->image[reg] |= value; + } else { + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + mb(); + tmp = (chip->image[reg] & mask) | value; + cs4231_outb(chip, CS4231P(REG), tmp); + chip->image[reg] = tmp; + mb(); + } +} + +static void snd_cs4231_dout(cs4231_t *chip, unsigned char reg, unsigned char value) +{ + int timeout; + + for (timeout = 250; + timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(10); + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + cs4231_outb(chip, CS4231P(REG), value); + mb(); +} + +void snd_cs4231_out(cs4231_t *chip, unsigned char reg, unsigned char value) +{ + int timeout; + + for (timeout = 250; + timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk("out: auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value); +#endif + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + cs4231_outb(chip, CS4231P(REG), value); + chip->image[reg] = value; + mb(); +#if 0 + printk("codec out - reg 0x%x = 0x%x\n", chip->mce_bit | reg, value); +#endif +} + +unsigned char snd_cs4231_in(cs4231_t *chip, unsigned char reg) +{ + int timeout; + + for (timeout = 250; + timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk("in: auto calibration time out - reg = 0x%x\n", reg); +#endif + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | reg); + mb(); + return cs4231_inb(chip, CS4231P(REG)); +} + +void snd_cs4236_ext_out(cs4231_t *chip, unsigned char reg, unsigned char val) +{ + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17); + cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01)); + cs4231_outb(chip, CS4231P(REG), val); + chip->eimage[CS4236_REG(reg)] = val; +#if 0 + printk("ext out : reg = 0x%x, val = 0x%x\n", reg, val); +#endif +} + +unsigned char snd_cs4236_ext_in(cs4231_t *chip, unsigned char reg) +{ + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | 0x17); + cs4231_outb(chip, CS4231P(REG), reg | (chip->image[CS4236_EXT_REG] & 0x01)); +#if 1 + return cs4231_inb(chip, CS4231P(REG)); +#else + { + unsigned char res; + res = cs4231_inb(chip, CS4231P(REG)); + printk("ext in : reg = 0x%x, val = 0x%x\n", reg, res); + return res; + } +#endif +} + +#if 0 + +static void snd_cs4231_debug(cs4231_t *chip) +{ + printk("CS4231 REGS: INDEX = 0x%02x ", cs4231_inb(chip, CS4231P(REGSEL))); + printk(" STATUS = 0x%02x\n", cs4231_inb(chip, CS4231P(STATUS))); + printk(" 0x00: left input = 0x%02x ", snd_cs4231_in(chip, 0x00)); + printk(" 0x10: alt 1 (CFIG 2) = 0x%02x\n", snd_cs4231_in(chip, 0x10)); + printk(" 0x01: right input = 0x%02x ", snd_cs4231_in(chip, 0x01)); + printk(" 0x11: alt 2 (CFIG 3) = 0x%02x\n", snd_cs4231_in(chip, 0x11)); + printk(" 0x02: GF1 left input = 0x%02x ", snd_cs4231_in(chip, 0x02)); + printk(" 0x12: left line in = 0x%02x\n", snd_cs4231_in(chip, 0x12)); + printk(" 0x03: GF1 right input = 0x%02x ", snd_cs4231_in(chip, 0x03)); + printk(" 0x13: right line in = 0x%02x\n", snd_cs4231_in(chip, 0x13)); + printk(" 0x04: CD left input = 0x%02x ", snd_cs4231_in(chip, 0x04)); + printk(" 0x14: timer low = 0x%02x\n", snd_cs4231_in(chip, 0x14)); + printk(" 0x05: CD right input = 0x%02x ", snd_cs4231_in(chip, 0x05)); + printk(" 0x15: timer high = 0x%02x\n", snd_cs4231_in(chip, 0x15)); + printk(" 0x06: left output = 0x%02x ", snd_cs4231_in(chip, 0x06)); + printk(" 0x16: left MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x16)); + printk(" 0x07: right output = 0x%02x ", snd_cs4231_in(chip, 0x07)); + printk(" 0x17: right MIC (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x17)); + printk(" 0x08: playback format = 0x%02x ", snd_cs4231_in(chip, 0x08)); + printk(" 0x18: IRQ status = 0x%02x\n", snd_cs4231_in(chip, 0x18)); + printk(" 0x09: iface (CFIG 1) = 0x%02x ", snd_cs4231_in(chip, 0x09)); + printk(" 0x19: left line out = 0x%02x\n", snd_cs4231_in(chip, 0x19)); + printk(" 0x0a: pin control = 0x%02x ", snd_cs4231_in(chip, 0x0a)); + printk(" 0x1a: mono control = 0x%02x\n", snd_cs4231_in(chip, 0x1a)); + printk(" 0x0b: init & status = 0x%02x ", snd_cs4231_in(chip, 0x0b)); + printk(" 0x1b: right line out = 0x%02x\n", snd_cs4231_in(chip, 0x1b)); + printk(" 0x0c: revision & mode = 0x%02x ", snd_cs4231_in(chip, 0x0c)); + printk(" 0x1c: record format = 0x%02x\n", snd_cs4231_in(chip, 0x1c)); + printk(" 0x0d: loopback = 0x%02x ", snd_cs4231_in(chip, 0x0d)); + printk(" 0x1d: var freq (PnP) = 0x%02x\n", snd_cs4231_in(chip, 0x1d)); + printk(" 0x0e: ply upr count = 0x%02x ", snd_cs4231_in(chip, 0x0e)); + printk(" 0x1e: ply lwr count = 0x%02x\n", snd_cs4231_in(chip, 0x1e)); + printk(" 0x0f: rec upr count = 0x%02x ", snd_cs4231_in(chip, 0x0f)); + printk(" 0x1f: rec lwr count = 0x%02x\n", snd_cs4231_in(chip, 0x1f)); +} + +#endif + +/* + * CS4231 detection / MCE routines + */ + +static void snd_cs4231_busy_wait(cs4231_t *chip) +{ + int timeout; + + /* huh.. looks like this sequence is proper for CS4231A chip (GUS MAX) */ + for (timeout = 5; timeout > 0; timeout--) + cs4231_inb(chip, CS4231P(REGSEL)); + /* end of cleanup sequence */ + for (timeout = 250; + timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); + timeout--) + udelay(10); +} + +void snd_cs4231_mce_up(cs4231_t *chip) +{ + unsigned long flags; + int timeout; + + for (timeout = 250; timeout > 0 && (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT); timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk("mce_up - auto calibration time out (0)\n"); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit |= CS4231_MCE; + timeout = cs4231_inb(chip, CS4231P(REGSEL)); + if (timeout == 0x80) + snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port); + if (!(timeout & CS4231_MCE)) + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +void snd_cs4231_mce_down(cs4231_t *chip) +{ + unsigned long flags; + int timeout; + + snd_cs4231_busy_wait(chip); +#if 0 + printk("(1) timeout = %i\n", timeout); +#endif +#ifdef CONFIG_SND_DEBUG + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", (long)CS4231P(REGSEL)); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit &= ~CS4231_MCE; + timeout = cs4231_inb(chip, CS4231P(REGSEL)); + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (timeout == 0x80) + snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port); + if ((timeout & CS4231_MCE) == 0 || + !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) { + return; + } + snd_cs4231_busy_wait(chip); + + /* calibration process */ + + for (timeout = 500; timeout > 0 && (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0; timeout--) + udelay(10); + if ((snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) == 0) { + snd_printd("cs4231_mce_down - auto calibration time out (1)\n"); + return; + } +#if 0 + printk("(2) timeout = %i, jiffies = %li\n", timeout, jiffies); +#endif + /* in 10 ms increments, check condition, up to 250 ms */ + timeout = 25; + while (snd_cs4231_in(chip, CS4231_TEST_INIT) & CS4231_CALIB_IN_PROGRESS) { + if (--timeout < 0) { + snd_printk("mce_down - auto calibration time out (2)\n"); + return; + } + msleep(10); + } +#if 0 + printk("(3) jiffies = %li\n", jiffies); +#endif + /* in 10 ms increments, check condition, up to 100 ms */ + timeout = 10; + while (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) { + if (--timeout < 0) { + snd_printk(KERN_ERR "mce_down - auto calibration time out (3)\n"); + return; + } + msleep(10); + } +#if 0 + printk("(4) jiffies = %li\n", jiffies); + snd_printk("mce_down - exit = 0x%x\n", cs4231_inb(chip, CS4231P(REGSEL))); +#endif +} + +static unsigned int snd_cs4231_get_count(unsigned char format, unsigned int size) +{ + switch (format & 0xe0) { + case CS4231_LINEAR_16: + case CS4231_LINEAR_16_BIG: + size >>= 1; + break; + case CS4231_ADPCM_16: + return size >> 2; + } + if (format & CS4231_STEREO) + size >>= 1; + return size; +} + +static int snd_cs4231_trigger(snd_pcm_substream_t *substream, + int cmd) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + int result = 0; + unsigned int what; + struct list_head *pos; + snd_pcm_substream_t *s; + int do_start; + +#if 0 + printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, cs4231_inb(chip, CS4231P(STATUS))); +#endif + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + do_start = 1; break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + do_start = 0; break; + default: + return -EINVAL; + } + + what = 0; + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + if (s == chip->playback_substream) { + what |= CS4231_PLAYBACK_ENABLE; + snd_pcm_trigger_done(s, substream); + } else if (s == chip->capture_substream) { + what |= CS4231_RECORD_ENABLE; + snd_pcm_trigger_done(s, substream); + } + } + spin_lock(&chip->reg_lock); + if (do_start) { + chip->image[CS4231_IFACE_CTRL] |= what; + if (chip->trigger) + chip->trigger(chip, what, 1); + } else { + chip->image[CS4231_IFACE_CTRL] &= ~what; + if (chip->trigger) + chip->trigger(chip, what, 0); + } + snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock(&chip->reg_lock); +#if 0 + snd_cs4231_debug(chip); +#endif + return result; +} + +/* + * CODEC I/O + */ + +static unsigned char snd_cs4231_get_rate(unsigned int rate) +{ + int i; + + for (i = 0; i < 14; i++) + if (rate == rates[i]) + return freq_bits[i]; + // snd_BUG(); + return freq_bits[13]; +} + +static unsigned char snd_cs4231_get_format(cs4231_t *chip, + int format, + int channels) +{ + unsigned char rformat; + + rformat = CS4231_LINEAR_8; + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: rformat = CS4231_ULAW_8; break; + case SNDRV_PCM_FORMAT_A_LAW: rformat = CS4231_ALAW_8; break; + case SNDRV_PCM_FORMAT_S16_LE: rformat = CS4231_LINEAR_16; break; + case SNDRV_PCM_FORMAT_S16_BE: rformat = CS4231_LINEAR_16_BIG; break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: rformat = CS4231_ADPCM_16; break; + } + if (channels > 1) + rformat |= CS4231_STEREO; +#if 0 + snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode); +#endif + return rformat; +} + +static void snd_cs4231_calibrate_mute(cs4231_t *chip, int mute) +{ + unsigned long flags; + + mute = mute ? 1 : 0; + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->calibrate_mute == mute) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return; + } + if (!mute) { + snd_cs4231_dout(chip, CS4231_LEFT_INPUT, chip->image[CS4231_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_RIGHT_INPUT, chip->image[CS4231_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_LOOPBACK, chip->image[CS4231_LOOPBACK]); + } + snd_cs4231_dout(chip, CS4231_AUX1_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX1_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX1_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX2_LEFT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_LEFT_INPUT]); + snd_cs4231_dout(chip, CS4231_AUX2_RIGHT_INPUT, mute ? 0x80 : chip->image[CS4231_AUX2_RIGHT_INPUT]); + snd_cs4231_dout(chip, CS4231_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LEFT_OUTPUT]); + snd_cs4231_dout(chip, CS4231_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_OUTPUT]); + snd_cs4231_dout(chip, CS4231_LEFT_LINE_IN, mute ? 0x80 : chip->image[CS4231_LEFT_LINE_IN]); + snd_cs4231_dout(chip, CS4231_RIGHT_LINE_IN, mute ? 0x80 : chip->image[CS4231_RIGHT_LINE_IN]); + snd_cs4231_dout(chip, CS4231_MONO_CTRL, mute ? 0xc0 : chip->image[CS4231_MONO_CTRL]); + if (chip->hardware == CS4231_HW_INTERWAVE) { + snd_cs4231_dout(chip, CS4231_LEFT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_LEFT_MIC_INPUT]); + snd_cs4231_dout(chip, CS4231_RIGHT_MIC_INPUT, mute ? 0x80 : chip->image[CS4231_RIGHT_MIC_INPUT]); + snd_cs4231_dout(chip, CS4231_LINE_LEFT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_LEFT_OUTPUT]); + snd_cs4231_dout(chip, CS4231_LINE_RIGHT_OUTPUT, mute ? 0x80 : chip->image[CS4231_LINE_RIGHT_OUTPUT]); + } + chip->calibrate_mute = mute; + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs4231_playback_format(cs4231_t *chip, + snd_pcm_hw_params_t *params, + unsigned char pdfr) +{ + unsigned long flags; + int full_calib = 1; + + down(&chip->mce_mutex); + snd_cs4231_calibrate_mute(chip, 1); + if (chip->hardware == CS4231_HW_CS4231A || + (chip->hardware & CS4231_HW_CS4232_MASK)) { + spin_lock_irqsave(&chip->reg_lock, flags); + if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (pdfr & 0x0f)) { /* rate is same? */ + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x10); + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x10); + udelay(100); /* Fixes audible clicks at least on GUS MAX */ + full_calib = 0; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + if (full_calib) { + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->hardware != CS4231_HW_INTERWAVE && !chip->single_dma) { + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, + (chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE) ? + (pdfr & 0xf0) | (chip->image[CS4231_REC_FORMAT] & 0x0f) : + pdfr); + } else { + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT] = pdfr); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + } + snd_cs4231_calibrate_mute(chip, 0); + up(&chip->mce_mutex); +} + +static void snd_cs4231_capture_format(cs4231_t *chip, + snd_pcm_hw_params_t *params, + unsigned char cdfr) +{ + unsigned long flags; + int full_calib = 1; + + down(&chip->mce_mutex); + snd_cs4231_calibrate_mute(chip, 1); + if (chip->hardware == CS4231_HW_CS4231A || + (chip->hardware & CS4231_HW_CS4232_MASK)) { + spin_lock_irqsave(&chip->reg_lock, flags); + if ((chip->image[CS4231_PLAYBK_FORMAT] & 0x0f) == (cdfr & 0x0f) || /* rate is same? */ + (chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x20); + snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT] = cdfr); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~0x20); + full_calib = 0; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + if (full_calib) { + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->hardware != CS4231_HW_INTERWAVE) { + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) { + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, + ((chip->single_dma ? cdfr : chip->image[CS4231_PLAYBK_FORMAT]) & 0xf0) | + (cdfr & 0x0f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + } + } + snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + } + snd_cs4231_calibrate_mute(chip, 0); + up(&chip->mce_mutex); +} + +/* + * Timer interface + */ + +static unsigned long snd_cs4231_timer_resolution(snd_timer_t * timer) +{ + cs4231_t *chip = snd_timer_chip(timer); + if (chip->hardware & CS4231_HW_CS4236B_MASK) + return 14467; + else + return chip->image[CS4231_PLAYBK_FORMAT] & 1 ? 9969 : 9920; +} + +static int snd_cs4231_timer_start(snd_timer_t * timer) +{ + unsigned long flags; + unsigned int ticks; + cs4231_t *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->reg_lock, flags); + ticks = timer->sticks; + if ((chip->image[CS4231_ALT_FEATURE_1] & CS4231_TIMER_ENABLE) == 0 || + (unsigned char)(ticks >> 8) != chip->image[CS4231_TIMER_HIGH] || + (unsigned char)ticks != chip->image[CS4231_TIMER_LOW]) { + snd_cs4231_out(chip, CS4231_TIMER_HIGH, chip->image[CS4231_TIMER_HIGH] = (unsigned char) (ticks >> 8)); + snd_cs4231_out(chip, CS4231_TIMER_LOW, chip->image[CS4231_TIMER_LOW] = (unsigned char) ticks); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | CS4231_TIMER_ENABLE); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4231_timer_stop(snd_timer_t * timer) +{ + unsigned long flags; + cs4231_t *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] &= ~CS4231_TIMER_ENABLE); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static void snd_cs4231_init(cs4231_t *chip) +{ + unsigned long flags; + + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("init: (1)\n"); +#endif + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO | + CS4231_CALIB_MODE); + chip->image[CS4231_IFACE_CTRL] |= CS4231_AUTOCALIB; + snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("init: (2)\n"); +#endif + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("init: (3) - afei = 0x%x\n", chip->image[CS4231_ALT_FEATURE_1]); +#endif + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_2, chip->image[CS4231_ALT_FEATURE_2]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, chip->image[CS4231_PLAYBK_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("init: (4)\n"); +#endif + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_REC_FORMAT, chip->image[CS4231_REC_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("init: (5)\n"); +#endif +} + +static int snd_cs4231_open(cs4231_t *chip, unsigned int mode) +{ + unsigned long flags; + + down(&chip->open_mutex); + if ((chip->mode & mode) || + ((chip->mode & CS4231_MODE_OPEN) && chip->single_dma)) { + up(&chip->open_mutex); + return -EAGAIN; + } + if (chip->mode & CS4231_MODE_OPEN) { + chip->mode |= mode; + up(&chip->open_mutex); + return 0; + } + /* ok. now enable and ack CODEC IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + chip->image[CS4231_PIN_CTRL] |= CS4231_IRQ_ENABLE; + snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, CS4231_PLAYBACK_IRQ | + CS4231_RECORD_IRQ | + CS4231_TIMER_IRQ); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->mode = mode; + up(&chip->open_mutex); + return 0; +} + +static void snd_cs4231_close(cs4231_t *chip, unsigned int mode) +{ + unsigned long flags; + + down(&chip->open_mutex); + chip->mode &= ~mode; + if (chip->mode & CS4231_MODE_OPEN) { + up(&chip->open_mutex); + return; + } + snd_cs4231_calibrate_mute(chip, 1); + + /* disable IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + chip->image[CS4231_PIN_CTRL] &= ~CS4231_IRQ_ENABLE; + snd_cs4231_out(chip, CS4231_PIN_CTRL, chip->image[CS4231_PIN_CTRL]); + + /* now disable record & playback */ + + if (chip->image[CS4231_IFACE_CTRL] & (CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO)) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO | + CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); + snd_cs4231_out(chip, CS4231_IFACE_CTRL, chip->image[CS4231_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + } + + /* clear IRQ again */ + snd_cs4231_out(chip, CS4231_IRQ_STATUS, 0); + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + cs4231_outb(chip, CS4231P(STATUS), 0); /* clear IRQ */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + + snd_cs4231_calibrate_mute(chip, 0); + + chip->mode = 0; + up(&chip->open_mutex); +} + +/* + * timer open/close + */ + +static int snd_cs4231_timer_open(snd_timer_t * timer) +{ + cs4231_t *chip = snd_timer_chip(timer); + snd_cs4231_open(chip, CS4231_MODE_TIMER); + return 0; +} + +static int snd_cs4231_timer_close(snd_timer_t * timer) +{ + cs4231_t *chip = snd_timer_chip(timer); + snd_cs4231_close(chip, CS4231_MODE_TIMER); + return 0; +} + +static struct _snd_timer_hardware snd_cs4231_timer_table = +{ + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 9945, + .ticks = 65535, + .open = snd_cs4231_timer_open, + .close = snd_cs4231_timer_close, + .c_resolution = snd_cs4231_timer_resolution, + .start = snd_cs4231_timer_start, + .stop = snd_cs4231_timer_stop, +}; + +/* + * ok.. exported functions.. + */ + +static int snd_cs4231_playback_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + unsigned char new_pdfr; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + new_pdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) | + snd_cs4231_get_rate(params_rate(hw_params)); + chip->set_playback_format(chip, hw_params, new_pdfr); + return 0; +} + +static int snd_cs4231_playback_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +#ifdef LEGACY_SUPPORT +static int snd_cs4231_playback_prepare(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + spin_lock_irqsave(&chip->reg_lock, flags); + chip->p_dma_size = size; + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_PLAYBACK_ENABLE | CS4231_PLAYBACK_PIO); + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + count = snd_cs4231_get_count(chip->image[CS4231_PLAYBK_FORMAT], count) - 1; + snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count); + snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); +#if 0 + snd_cs4231_debug(chip); +#endif + return 0; +} +#endif /* LEGACY_SUPPORT */ + +static int snd_cs4231_capture_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + unsigned char new_cdfr; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + new_cdfr = snd_cs4231_get_format(chip, params_format(hw_params), params_channels(hw_params)) | + snd_cs4231_get_rate(params_rate(hw_params)); + chip->set_capture_format(chip, hw_params, new_cdfr); + return 0; +} + +static int snd_cs4231_capture_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +#ifdef LEGACY_SUPPORT +static int snd_cs4231_capture_prepare(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + spin_lock_irqsave(&chip->reg_lock, flags); + chip->c_dma_size = size; + chip->image[CS4231_IFACE_CTRL] &= ~(CS4231_RECORD_ENABLE | CS4231_RECORD_PIO); + snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + count = snd_cs4231_get_count(chip->image[CS4231_REC_FORMAT], count) - 1; + if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) { + snd_cs4231_out(chip, CS4231_PLY_LWR_CNT, (unsigned char) count); + snd_cs4231_out(chip, CS4231_PLY_UPR_CNT, (unsigned char) (count >> 8)); + } else { + snd_cs4231_out(chip, CS4231_REC_LWR_CNT, (unsigned char) count); + snd_cs4231_out(chip, CS4231_REC_UPR_CNT, (unsigned char) (count >> 8)); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} +#endif + +static void snd_cs4231_overrange(cs4231_t *chip) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&chip->reg_lock, flags); + res = snd_cs4231_in(chip, CS4231_TEST_INIT); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (res & (0x08 | 0x02)) /* detect overrange only above 0dB; may be user selectable? */ + chip->capture_substream->runtime->overrange++; +} + +irqreturn_t snd_cs4231_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + cs4231_t *chip = dev_id; + unsigned char status; + + status = snd_cs4231_in(chip, CS4231_IRQ_STATUS); + if (status & CS4231_TIMER_IRQ) { + if (chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + } + if (chip->single_dma && chip->hardware != CS4231_HW_INTERWAVE) { + if (status & CS4231_PLAYBACK_IRQ) { + if (chip->mode & CS4231_MODE_PLAY) { + if (chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + } + if (chip->mode & CS4231_MODE_RECORD) { + if (chip->capture_substream) { + snd_cs4231_overrange(chip); + snd_pcm_period_elapsed(chip->capture_substream); + } + } + } + } else { + if (status & CS4231_PLAYBACK_IRQ) { + if (chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + } + if (status & CS4231_RECORD_IRQ) { + if (chip->capture_substream) { + snd_cs4231_overrange(chip); + snd_pcm_period_elapsed(chip->capture_substream); + } + } + } + + spin_lock(&chip->reg_lock); + snd_cs4231_outm(chip, CS4231_IRQ_STATUS, ~CS4231_ALL_IRQS | ~status, 0); + spin_unlock(&chip->reg_lock); + return IRQ_HANDLED; +} + +#ifdef LEGACY_SUPPORT +static snd_pcm_uframes_t snd_cs4231_playback_pointer(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_PLAYBACK_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma1, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_cs4231_capture_pointer(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[CS4231_IFACE_CTRL] & CS4231_RECORD_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} +#endif /* LEGACY_SUPPORT */ + +/* + + */ + +static int snd_cs4231_probe(cs4231_t *chip) +{ + unsigned long flags; + int i, id, rev; + unsigned char *ptr; + unsigned int hw; + +#if 0 + snd_cs4231_debug(chip); +#endif + id = 0; + for (i = 0; i < 50; i++) { + mb(); + if (cs4231_inb(chip, CS4231P(REGSEL)) & CS4231_INIT) + udelay(2000); + else { + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_MISC_INFO, CS4231_MODE2); + id = snd_cs4231_in(chip, CS4231_MISC_INFO) & 0x0f; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (id == 0x0a) + break; /* this is valid value */ + } + } + snd_printdd("cs4231: port = 0x%lx, id = 0x%x\n", chip->port, id); + if (id != 0x0a) + return -ENODEV; /* no valid device found */ + + if (((hw = chip->hardware) & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) { + rev = snd_cs4231_in(chip, CS4231_VERSION) & 0xe7; + snd_printdd("CS4231: VERSION (I25) = 0x%x\n", rev); + if (rev == 0x80) { + unsigned char tmp = snd_cs4231_in(chip, 23); + snd_cs4231_out(chip, 23, ~tmp); + if (snd_cs4231_in(chip, 23) != tmp) + chip->hardware = CS4231_HW_AD1845; + else + chip->hardware = CS4231_HW_CS4231; + } else if (rev == 0xa0) { + chip->hardware = CS4231_HW_CS4231A; + } else if (rev == 0xa2) { + chip->hardware = CS4231_HW_CS4232; + } else if (rev == 0xb2) { + chip->hardware = CS4231_HW_CS4232A; + } else if (rev == 0x83) { + chip->hardware = CS4231_HW_CS4236; + } else if (rev == 0x03) { + chip->hardware = CS4231_HW_CS4236B; + } else { + snd_printk("unknown CS chip with version 0x%x\n", rev); + return -ENODEV; /* unknown CS4231 chip? */ + } + } + spin_lock_irqsave(&chip->reg_lock, flags); + cs4231_inb(chip, CS4231P(STATUS)); /* clear any pendings IRQ */ + cs4231_outb(chip, CS4231P(STATUS), 0); + mb(); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->image[CS4231_MISC_INFO] = CS4231_MODE2; + switch (chip->hardware) { + case CS4231_HW_INTERWAVE: + chip->image[CS4231_MISC_INFO] = CS4231_IW_MODE3; + break; + case CS4231_HW_CS4235: + case CS4231_HW_CS4236B: + case CS4231_HW_CS4237B: + case CS4231_HW_CS4238B: + case CS4231_HW_CS4239: + if (hw == CS4231_HW_DETECT3) + chip->image[CS4231_MISC_INFO] = CS4231_4236_MODE3; + else + chip->hardware = CS4231_HW_CS4236; + break; + } + + chip->image[CS4231_IFACE_CTRL] = + (chip->image[CS4231_IFACE_CTRL] & ~CS4231_SINGLE_DMA) | + (chip->single_dma ? CS4231_SINGLE_DMA : 0); + chip->image[CS4231_ALT_FEATURE_1] = 0x80; + chip->image[CS4231_ALT_FEATURE_2] = chip->hardware == CS4231_HW_INTERWAVE ? 0xc2 : 0x01; + ptr = (unsigned char *) &chip->image; + snd_cs4231_mce_down(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (i = 0; i < 32; i++) /* ok.. fill all CS4231 registers */ + snd_cs4231_out(chip, i, *ptr++); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_up(chip); + snd_cs4231_mce_down(chip); + + mdelay(2); + + /* ok.. try check hardware version for CS4236+ chips */ + if ((hw & CS4231_HW_TYPE_MASK) == CS4231_HW_DETECT) { + if (chip->hardware == CS4231_HW_CS4236B) { + rev = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_cs4236_ext_out(chip, CS4236_VERSION, 0xff); + id = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_cs4236_ext_out(chip, CS4236_VERSION, rev); + snd_printdd("CS4231: ext version; rev = 0x%x, id = 0x%x\n", rev, id); + if ((id & 0x1f) == 0x1d) { /* CS4235 */ + chip->hardware = CS4231_HW_CS4235; + switch (id >> 5) { + case 4: + case 5: + case 6: + break; + default: + snd_printk("unknown CS4235 chip (enhanced version = 0x%x)\n", id); + } + } else if ((id & 0x1f) == 0x0b) { /* CS4236/B */ + switch (id >> 5) { + case 4: + case 5: + case 6: + case 7: + chip->hardware = CS4231_HW_CS4236B; + break; + default: + snd_printk("unknown CS4236 chip (enhanced version = 0x%x)\n", id); + } + } else if ((id & 0x1f) == 0x08) { /* CS4237B */ + chip->hardware = CS4231_HW_CS4237B; + switch (id >> 5) { + case 4: + case 5: + case 6: + case 7: + break; + default: + snd_printk("unknown CS4237B chip (enhanced version = 0x%x)\n", id); + } + } else if ((id & 0x1f) == 0x09) { /* CS4238B */ + chip->hardware = CS4231_HW_CS4238B; + switch (id >> 5) { + case 5: + case 6: + case 7: + break; + default: + snd_printk("unknown CS4238B chip (enhanced version = 0x%x)\n", id); + } + } else if ((id & 0x1f) == 0x1e) { /* CS4239 */ + chip->hardware = CS4231_HW_CS4239; + switch (id >> 5) { + case 4: + case 5: + case 6: + break; + default: + snd_printk("unknown CS4239 chip (enhanced version = 0x%x)\n", id); + } + } else { + snd_printk("unknown CS4236/CS423xB chip (enhanced version = 0x%x)\n", id); + } + } + } + return 0; /* all things are ok.. */ +} + +/* + + */ + +static snd_pcm_hardware_t snd_cs4231_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_cs4231_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | SNDRV_PCM_FMTBIT_IMA_ADPCM | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_KNOT | SNDRV_PCM_RATE_8000_48000, + .rate_min = 5510, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + + */ + +static int snd_cs4231_playback_open(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + runtime->hw = snd_cs4231_playback; + + /* hardware bug in InterWave chipset */ + if (chip->hardware == CS4231_HW_INTERWAVE && chip->dma1 > 3) + runtime->hw.formats &= ~SNDRV_PCM_FMTBIT_MU_LAW; + + /* hardware limitation of cheap chips */ + if (chip->hardware == CS4231_HW_CS4235 || + chip->hardware == CS4231_HW_CS4239) + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE; + +#ifdef LEGACY_SUPPORT + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.period_bytes_max); + + if (chip->claim_dma) { + if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma1)) < 0) + return err; + } +#endif + + if ((err = snd_cs4231_open(chip, CS4231_MODE_PLAY)) < 0) { +#ifdef LEGACY_SUPPORT + if (chip->release_dma) + chip->release_dma(chip, chip->dma_private_data, chip->dma1); +#endif + snd_free_pages(runtime->dma_area, runtime->dma_bytes); + return err; + } + chip->playback_substream = substream; +#if defined(SBUS_SUPPORT) || defined(EBUS_SUPPORT) + chip->p_periods_sent = 0; +#endif + snd_pcm_set_sync(substream); + chip->rate_constraint(runtime); + return 0; +} + +static int snd_cs4231_capture_open(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + runtime->hw = snd_cs4231_capture; + + /* hardware limitation of cheap chips */ + if (chip->hardware == CS4231_HW_CS4235 || + chip->hardware == CS4231_HW_CS4239) + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE; + +#ifdef LEGACY_SUPPORT + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.period_bytes_max); + + if (chip->claim_dma) { + if ((err = chip->claim_dma(chip, chip->dma_private_data, chip->dma2)) < 0) + return err; + } +#endif + + if ((err = snd_cs4231_open(chip, CS4231_MODE_RECORD)) < 0) { +#ifdef LEGACY_SUPPORT + if (chip->release_dma) + chip->release_dma(chip, chip->dma_private_data, chip->dma2); +#endif + snd_free_pages(runtime->dma_area, runtime->dma_bytes); + return err; + } + chip->capture_substream = substream; +#if defined(SBUS_SUPPORT) || defined(EBUS_SUPPORT) + chip->c_periods_sent = 0; +#endif + snd_pcm_set_sync(substream); + chip->rate_constraint(runtime); + return 0; +} + +static int snd_cs4231_playback_close(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + snd_cs4231_close(chip, CS4231_MODE_PLAY); + return 0; +} + +static int snd_cs4231_capture_close(snd_pcm_substream_t * substream) +{ + cs4231_t *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + snd_cs4231_close(chip, CS4231_MODE_RECORD); + return 0; +} + +#ifdef CONFIG_PM + +/* lowlevel suspend callback for CS4231 */ +static void snd_cs4231_suspend(cs4231_t *chip) +{ + int reg; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) + chip->image[reg] = snd_cs4231_in(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +/* lowlevel resume callback for CS4231 */ +static void snd_cs4231_resume(cs4231_t *chip) +{ + int reg; + unsigned long flags; + int timeout; + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) { + switch (reg) { + case CS4231_VERSION: + break; + default: + snd_cs4231_out(chip, reg, chip->image[reg]); + break; + } + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +#if 0 + snd_cs4231_mce_down(chip); +#else + /* The following is a workaround to avoid freeze after resume on TP600E. + This is the first half of copy of snd_cs4231_mce_down(), but doesn't + include rescheduling. -- iwai + */ + snd_cs4231_busy_wait(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit &= ~CS4231_MCE; + timeout = cs4231_inb(chip, CS4231P(REGSEL)); + cs4231_outb(chip, CS4231P(REGSEL), chip->mce_bit | (timeout & 0x1f)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (timeout == 0x80) + snd_printk("down [0x%lx]: serious init problem - codec still busy\n", chip->port); + if ((timeout & CS4231_MCE) == 0 || + !(chip->hardware & (CS4231_HW_CS4231_MASK | CS4231_HW_CS4232_MASK))) { + return; + } + snd_cs4231_busy_wait(chip); +#endif +} + +static int snd_cs4231_pm_suspend(snd_card_t *card, pm_message_t state) +{ + cs4231_t *chip = card->pm_private_data; + if (chip->suspend) + chip->suspend(chip); + return 0; +} + +static int snd_cs4231_pm_resume(snd_card_t *card) +{ + cs4231_t *chip = card->pm_private_data; + if (chip->resume) + chip->resume(chip); + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef LEGACY_SUPPORT + +static int snd_cs4231_free(cs4231_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->res_cport) { + release_resource(chip->res_cport); + kfree_nocheck(chip->res_cport); + } + if (chip->irq >= 0) { + disable_irq(chip->irq); + if (!(chip->hwshare & CS4231_HWSHARE_IRQ)) + free_irq(chip->irq, (void *) chip); + } + if (!(chip->hwshare & CS4231_HWSHARE_DMA1) && chip->dma1 >= 0) { + snd_dma_disable(chip->dma1); + free_dma(chip->dma1); + } + if (!(chip->hwshare & CS4231_HWSHARE_DMA2) && chip->dma2 >= 0 && chip->dma2 != chip->dma1) { + snd_dma_disable(chip->dma2); + free_dma(chip->dma2); + } + if (chip->timer) + snd_device_free(chip->card, chip->timer); + kfree(chip); + return 0; +} + +static int snd_cs4231_dev_free(snd_device_t *device) +{ + cs4231_t *chip = device->device_data; + return snd_cs4231_free(chip); +} + +#endif /* LEGACY_SUPPORT */ + +const char *snd_cs4231_chip_id(cs4231_t *chip) +{ + switch (chip->hardware) { + case CS4231_HW_CS4231: return "CS4231"; + case CS4231_HW_CS4231A: return "CS4231A"; + case CS4231_HW_CS4232: return "CS4232"; + case CS4231_HW_CS4232A: return "CS4232A"; + case CS4231_HW_CS4235: return "CS4235"; + case CS4231_HW_CS4236: return "CS4236"; + case CS4231_HW_CS4236B: return "CS4236B"; + case CS4231_HW_CS4237B: return "CS4237B"; + case CS4231_HW_CS4238B: return "CS4238B"; + case CS4231_HW_CS4239: return "CS4239"; + case CS4231_HW_INTERWAVE: return "AMD InterWave"; + case CS4231_HW_OPL3SA2: return chip->card->shortname; + case CS4231_HW_AD1845: return "AD1845"; + default: return "???"; + } +} + +static int snd_cs4231_new(snd_card_t * card, + unsigned short hardware, + unsigned short hwshare, + cs4231_t ** rchip) +{ + cs4231_t *chip; + + *rchip = NULL; + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->hardware = hardware; + chip->hwshare = hwshare; + + spin_lock_init(&chip->reg_lock); + init_MUTEX(&chip->mce_mutex); + init_MUTEX(&chip->open_mutex); + chip->card = card; + chip->rate_constraint = snd_cs4231_xrate; + chip->set_playback_format = snd_cs4231_playback_format; + chip->set_capture_format = snd_cs4231_capture_format; + memcpy(&chip->image, &snd_cs4231_original_image, sizeof(snd_cs4231_original_image)); + + *rchip = chip; + return 0; +} + +#ifdef LEGACY_SUPPORT + +int snd_cs4231_create(snd_card_t * card, + unsigned long port, + unsigned long cport, + int irq, int dma1, int dma2, + unsigned short hardware, + unsigned short hwshare, + cs4231_t ** rchip) +{ + static snd_device_ops_t ops = { + .dev_free = snd_cs4231_dev_free, + }; + cs4231_t *chip; + int err; + + err = snd_cs4231_new(card, hardware, hwshare, &chip); + if (err < 0) + return err; + + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + + if ((chip->res_port = request_region(port, 4, "CS4231")) == NULL) { + snd_printk(KERN_ERR "cs4231: can't grab port 0x%lx\n", port); + snd_cs4231_free(chip); + return -EBUSY; + } + chip->port = port; + if ((long)cport >= 0 && (chip->res_cport = request_region(cport, 8, "CS4232 Control")) == NULL) { + snd_printk(KERN_ERR "cs4231: can't grab control port 0x%lx\n", cport); + snd_cs4231_free(chip); + return -ENODEV; + } + chip->cport = cport; + if (!(hwshare & CS4231_HWSHARE_IRQ) && request_irq(irq, snd_cs4231_interrupt, SA_INTERRUPT, "CS4231", (void *) chip)) { + snd_printk(KERN_ERR "cs4231: can't grab IRQ %d\n", irq); + snd_cs4231_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (!(hwshare & CS4231_HWSHARE_DMA1) && request_dma(dma1, "CS4231 - 1")) { + snd_printk(KERN_ERR "cs4231: can't grab DMA1 %d\n", dma1); + snd_cs4231_free(chip); + return -EBUSY; + } + chip->dma1 = dma1; + if (!(hwshare & CS4231_HWSHARE_DMA2) && dma1 != dma2 && dma2 >= 0 && request_dma(dma2, "CS4231 - 2")) { + snd_printk(KERN_ERR "cs4231: can't grab DMA2 %d\n", dma2); + snd_cs4231_free(chip); + return -EBUSY; + } + if (dma1 == dma2 || dma2 < 0) { + chip->single_dma = 1; + chip->dma2 = chip->dma1; + } else + chip->dma2 = dma2; + + /* global setup */ + if (snd_cs4231_probe(chip) < 0) { + snd_cs4231_free(chip); + return -ENODEV; + } + snd_cs4231_init(chip); + + if (chip->hardware & CS4231_HW_CS4232_MASK) { + if (chip->res_cport == NULL) + snd_printk("CS4232 control port features are not accessible\n"); + } + + /* Register device */ + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_cs4231_free(chip); + return err; + } + +#ifdef CONFIG_PM + /* Power Management */ + chip->suspend = snd_cs4231_suspend; + chip->resume = snd_cs4231_resume; + snd_card_set_isa_pm_callback(card, snd_cs4231_pm_suspend, snd_cs4231_pm_resume, chip); +#endif + + *rchip = chip; + return 0; +} + +#endif /* LEGACY_SUPPORT */ + +static snd_pcm_ops_t snd_cs4231_playback_ops = { + .open = snd_cs4231_playback_open, + .close = snd_cs4231_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4231_playback_hw_params, + .hw_free = snd_cs4231_playback_hw_free, + .prepare = snd_cs4231_playback_prepare, + .trigger = snd_cs4231_trigger, + .pointer = snd_cs4231_playback_pointer, +}; + +static snd_pcm_ops_t snd_cs4231_capture_ops = { + .open = snd_cs4231_capture_open, + .close = snd_cs4231_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_cs4231_capture_hw_params, + .hw_free = snd_cs4231_capture_hw_free, + .prepare = snd_cs4231_capture_prepare, + .trigger = snd_cs4231_trigger, + .pointer = snd_cs4231_capture_pointer, +}; + +static void snd_cs4231_pcm_free(snd_pcm_t *pcm) +{ + cs4231_t *chip = pcm->private_data; + chip->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_cs4231_pcm(cs4231_t *chip, int device, snd_pcm_t **rpcm) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_pcm_new(chip->card, "CS4231", device, 1, 1, &pcm)) < 0) + return err; + + spin_lock_init(&chip->reg_lock); + init_MUTEX(&chip->mce_mutex); + init_MUTEX(&chip->open_mutex); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_cs4231_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_cs4231_capture_ops); + + /* global setup */ + pcm->private_data = chip; + pcm->private_free = snd_cs4231_pcm_free; + pcm->info_flags = 0; + if (chip->single_dma) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + if (chip->hardware != CS4231_HW_INTERWAVE) + pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX; + strcpy(pcm->name, snd_cs4231_chip_id(chip)); + +#ifdef LEGACY_SUPPORT + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, chip->dma1 > 3 || chip->dma2 > 3 ? 128*1024 : 64*1024); +#else +# ifdef EBUS_SUPPORT + if (chip->ebus_flag) { + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + chip->dev_u.pdev, + 64*1024, 128*1024); + } else { +# endif +# ifdef SBUS_SUPPORT + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_SBUS, + chip->dev_u.sdev, + 64*1024, 128*1024); +# endif +# ifdef EBUS_SUPPORT + } +# endif +#endif + + chip->pcm = pcm; + if (rpcm) + *rpcm = pcm; + return 0; +} + +static void snd_cs4231_timer_free(snd_timer_t *timer) +{ + cs4231_t *chip = timer->private_data; + chip->timer = NULL; +} + +int snd_cs4231_timer(cs4231_t *chip, int device, snd_timer_t **rtimer) +{ + snd_timer_t *timer; + snd_timer_id_t tid; + int err; + + /* Timer initialization */ + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = chip->card->number; + tid.device = device; + tid.subdevice = 0; + if ((err = snd_timer_new(chip->card, "CS4231", &tid, &timer)) < 0) + return err; + strcpy(timer->name, snd_cs4231_chip_id(chip)); + timer->private_data = chip; + timer->private_free = snd_cs4231_timer_free; + timer->hw = snd_cs4231_timer_table; + chip->timer = timer; + if (rtimer) + *rtimer = timer; + return 0; +} + +/* + * MIXER part + */ + +static int snd_cs4231_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[4] = { + "Line", "Aux", "Mic", "Mix" + }; + static char *opl3sa_texts[4] = { + "Line", "CD", "Mic", "Mix" + }; + static char *gusmax_texts[4] = { + "Line", "Synth", "Mic", "Mix" + }; + char **ptexts = texts; + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + + snd_assert(chip->card != NULL, return -EINVAL); + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 2; + uinfo->value.enumerated.items = 4; + if (uinfo->value.enumerated.item > 3) + uinfo->value.enumerated.item = 3; + if (!strcmp(chip->card->driver, "GUS MAX")) + ptexts = gusmax_texts; + switch (chip->hardware) { + case CS4231_HW_INTERWAVE: ptexts = gusmax_texts; break; + case CS4231_HW_OPL3SA2: ptexts = opl3sa_texts; break; + } + strcpy(uinfo->value.enumerated.name, ptexts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_cs4231_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.enumerated.item[0] = (chip->image[CS4231_LEFT_INPUT] & CS4231_MIXS_ALL) >> 6; + ucontrol->value.enumerated.item[1] = (chip->image[CS4231_RIGHT_INPUT] & CS4231_MIXS_ALL) >> 6; + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4231_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short left, right; + int change; + + if (ucontrol->value.enumerated.item[0] > 3 || + ucontrol->value.enumerated.item[1] > 3) + return -EINVAL; + left = ucontrol->value.enumerated.item[0] << 6; + right = ucontrol->value.enumerated.item[1] << 6; + spin_lock_irqsave(&chip->reg_lock, flags); + left = (chip->image[CS4231_LEFT_INPUT] & ~CS4231_MIXS_ALL) | left; + right = (chip->image[CS4231_RIGHT_INPUT] & ~CS4231_MIXS_ALL) | right; + change = left != chip->image[CS4231_LEFT_INPUT] || + right != chip->image[CS4231_RIGHT_INPUT]; + snd_cs4231_out(chip, CS4231_LEFT_INPUT, left); + snd_cs4231_out(chip, CS4231_RIGHT_INPUT, right); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +int snd_cs4231_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +int snd_cs4231_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +int snd_cs4231_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->image[reg] & ~(mask << shift)) | val; + change = val != chip->image[reg]; + snd_cs4231_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +int snd_cs4231_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +int snd_cs4231_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->image[right_reg] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +int snd_cs4231_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->image[right_reg] & ~(mask << shift_right)) | val2; + change = val1 != chip->image[left_reg] || val2 != chip->image[right_reg]; + snd_cs4231_out(chip, left_reg, val1); + snd_cs4231_out(chip, right_reg, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_cs4231_controls[] = { +CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1), +CS4231_DOUBLE("Line Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +CS4231_DOUBLE("Line Playback Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1), +CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Playback Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Playback Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_SINGLE("Mono Playback Switch", 0, CS4231_MONO_CTRL, 7, 1, 1), +CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1), +CS4231_SINGLE("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, 6, 1, 1), +CS4231_SINGLE("Mono Output Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0), +CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_cs4231_info_mux, + .get = snd_cs4231_get_mux, + .put = snd_cs4231_put_mux, +}, +CS4231_DOUBLE("Mic Boost", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 5, 5, 1, 0), +CS4231_SINGLE("Loopback Capture Switch", 0, CS4231_LOOPBACK, 0, 1, 0), +CS4231_SINGLE("Loopback Capture Volume", 0, CS4231_LOOPBACK, 2, 63, 1) +}; + +int snd_cs4231_mixer(cs4231_t *chip) +{ + snd_card_t *card; + unsigned int idx; + int err; + + snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL); + + card = chip->card; + + strcpy(card->mixername, chip->pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_cs4231_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4231_controls[idx], chip))) < 0) + return err; + } + return 0; +} + +EXPORT_SYMBOL(snd_cs4231_out); +EXPORT_SYMBOL(snd_cs4231_in); +EXPORT_SYMBOL(snd_cs4236_ext_out); +EXPORT_SYMBOL(snd_cs4236_ext_in); +EXPORT_SYMBOL(snd_cs4231_mce_up); +EXPORT_SYMBOL(snd_cs4231_mce_down); +EXPORT_SYMBOL(snd_cs4231_interrupt); +EXPORT_SYMBOL(snd_cs4231_chip_id); +EXPORT_SYMBOL(snd_cs4231_create); +EXPORT_SYMBOL(snd_cs4231_pcm); +EXPORT_SYMBOL(snd_cs4231_mixer); +EXPORT_SYMBOL(snd_cs4231_timer); +EXPORT_SYMBOL(snd_cs4231_info_single); +EXPORT_SYMBOL(snd_cs4231_get_single); +EXPORT_SYMBOL(snd_cs4231_put_single); +EXPORT_SYMBOL(snd_cs4231_info_double); +EXPORT_SYMBOL(snd_cs4231_get_double); +EXPORT_SYMBOL(snd_cs4231_put_double); + +/* + * INIT part + */ + +static int __init alsa_cs4231_init(void) +{ + return 0; +} + +static void __exit alsa_cs4231_exit(void) +{ +} + +module_init(alsa_cs4231_init) +module_exit(alsa_cs4231_exit) diff --git a/sound/isa/cs423x/cs4232.c b/sound/isa/cs423x/cs4232.c new file mode 100644 index 000000000000..9fad2e6c0c2c --- /dev/null +++ b/sound/isa/cs423x/cs4232.c @@ -0,0 +1,2 @@ +#define CS4232 +#include "cs4236.c" diff --git a/sound/isa/cs423x/cs4236.c b/sound/isa/cs423x/cs4236.c new file mode 100644 index 000000000000..e745a54e00a1 --- /dev/null +++ b/sound/isa/cs423x/cs4236.c @@ -0,0 +1,608 @@ +/* + * Driver for generic CS4232/CS4235/CS4236/CS4236B/CS4237B/CS4238B/CS4239 chips + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * + * 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 <sound/driver.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/cs4231.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_LICENSE("GPL"); +#ifdef CS4232 +MODULE_DESCRIPTION("Cirrus Logic CS4232"); +MODULE_SUPPORTED_DEVICE("{{Turtle Beach,TBS-2000}," + "{Turtle Beach,Tropez Plus}," + "{SIC CrystalWave 32}," + "{Hewlett Packard,Omnibook 5500}," + "{TerraTec,Maestro 32/96}," + "{Philips,PCA70PS}}"); +#else +MODULE_DESCRIPTION("Cirrus Logic CS4235-9"); +MODULE_SUPPORTED_DEVICE("{{Crystal Semiconductors,CS4235}," + "{Crystal Semiconductors,CS4236}," + "{Crystal Semiconductors,CS4237}," + "{Crystal Semiconductors,CS4238}," + "{Crystal Semiconductors,CS4239}," + "{Acer,AW37}," + "{Acer,AW35/Pro}," + "{Crystal,3D}," + "{Crystal Computer,TidalWave128}," + "{Dell,Optiplex GX1}," + "{Dell,Workstation 400 sound}," + "{EliteGroup,P5TX-LA sound}," + "{Gallant,SC-70P}," + "{Gateway,E1000 Onboard CS4236B}," + "{Genius,Sound Maker 3DJ}," + "{Hewlett Packard,HP6330 sound}," + "{IBM,PC 300PL sound}," + "{IBM,Aptiva 2137 E24}," + "{IBM,IntelliStation M Pro}," + "{Intel,Marlin Spike Mobo CS4235}," + "{Intel PR440FX Onboard}," + "{Guillemot,MaxiSound 16 PnP}," + "{NewClear,3D}," + "{TerraTec,AudioSystem EWS64L/XL}," + "{Typhoon Soundsystem,CS4236B}," + "{Turtle Beach,Malibu}," + "{Unknown,Digital PC 5000 Onboard}}"); +#endif + +#ifdef CS4232 +#define IDENT "CS4232" +#else +#define IDENT "CS4236+" +#endif + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; /* Enable this card */ +#ifdef CONFIG_PNP +static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long cport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* PnP setup */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for " IDENT " soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for " IDENT " soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable " IDENT " soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for specified soundcard."); +#endif +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for " IDENT " driver."); +module_param_array(cport, long, NULL, 0444); +MODULE_PARM_DESC(cport, "Control port # for " IDENT " driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for " IDENT " driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for " IDENT " driver."); +module_param_array(sb_port, long, NULL, 0444); +MODULE_PARM_DESC(sb_port, "SB port # for " IDENT " driver (optional)."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for " IDENT " driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for " IDENT " driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for " IDENT " driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for " IDENT " driver."); + +struct snd_card_cs4236 { + struct resource *res_sb_port; +#ifdef CONFIG_PNP + struct pnp_dev *wss; + struct pnp_dev *ctrl; + struct pnp_dev *mpu; +#endif +}; + +static snd_card_t *snd_cs4236_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +#define ISAPNP_CS4232(_va, _vb, _vc, _device, _wss, _ctrl, _mpu401) \ + { \ + ISAPNP_CARD_ID(_va, _vb, _vc, _device), \ + .devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \ + ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl), \ + ISAPNP_DEVICE_ID(_va, _vb, _vc, _mpu401) } \ + } +#define ISAPNP_CS4232_1(_va, _vb, _vc, _device, _wss, _ctrl, _mpu401) \ + { \ + ISAPNP_CARD_ID(_va, _vb, _vc, _device), \ + .devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \ + ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl), \ + ISAPNP_DEVICE_ID('P', 'N', 'P', _mpu401) } \ + } +#define ISAPNP_CS4232_WOMPU(_va, _vb, _vc, _device, _wss, _ctrl) \ + { \ + ISAPNP_CARD_ID(_va, _vb, _vc, _device), \ + .devs = { ISAPNP_DEVICE_ID(_va, _vb, _vc, _wss), \ + ISAPNP_DEVICE_ID(_va, _vb, _vc, _ctrl) } \ + } + + +#ifdef CS4232 +static struct pnp_card_device_id snd_cs423x_pnpids[] = { + /* Philips PCA70PS */ + { .id = "CSC0d32", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } }, + /* TerraTec Maestro 32/96 (CS4232) */ + { .id = "CSC1a32", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* HP Omnibook 5500 onboard */ + { .id = "CSC4232", .devs = { { "CSC0000" }, { "CSC0002" }, { "CSC0003" } } }, + /* Unnamed CS4236 card (Made in Taiwan) */ + { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Turtle Beach TBS-2000 (CS4232) */ + { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSCb006" } } }, + /* Turtle Beach Tropez Plus (CS4232) */ + { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PNPb006" } } }, + /* SIC CrystalWave 32 (CS4232) */ + { .id = "CSCf032", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* --- */ + { .id = "" } /* end */ +}; +#else /* CS4236 */ +static struct pnp_card_device_id snd_cs423x_pnpids[] = { + /* Intel Marlin Spike Motherboard - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Intel Marlin Spike Motherboard (#2) - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Unknown Intel mainboard - CS4235 */ + { .id = "CSC0225", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Genius Sound Maker 3DJ - CS4237B */ + { .id = "CSC0437", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Digital PC 5000 Onboard - CS4236B */ + { .id = "CSC0735", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* some uknown CS4236B */ + { .id = "CSC0b35", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Intel PR440FX Onboard sound */ + { .id = "CSC0b36", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 on mainboard without MPU */ + { .id = "CSC1425", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Gateway E1000 Onboard CS4236B */ + { .id = "CSC1335", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* HP 6330 Onboard sound */ + { .id = "CSC1525", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Crystal Computer TidalWave128 */ + { .id = "CSC1e37", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* ACER AW37 - CS4235 */ + { .id = "CSC4236", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* build-in soundcard in EliteGroup P5TX-LA motherboard - CS4237B */ + { .id = "CSC4237", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Crystal 3D - CS4237B */ + { .id = "CSC4336", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Typhoon Soundsystem PnP - CS4236B */ + { .id = "CSC4536", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Crystal CX4235-XQ3 EP - CS4235 */ + { .id = "CSC4625", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* Crystal Semiconductors CS4237B */ + { .id = "CSC4637", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* NewClear 3D - CX4237B-XQ3 */ + { .id = "CSC4837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Dell Optiplex GX1 - CS4236B */ + { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Dell P410 motherboard - CS4236B */ + { .id = "CSC6835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Dell Workstation 400 Onboard - CS4236B */ + { .id = "CSC6836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Turtle Beach Malibu - CS4237B */ + { .id = "CSC7537", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 - onboard */ + { .id = "CSC8025", .devs = { { "CSC0100" }, { "CSC0110" }, { "CSC0103" } } }, + /* IBM Aptiva 2137 E24 Onboard - CS4237B */ + { .id = "CSC8037", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* IBM IntelliStation M Pro motherboard */ + { .id = "CSCc835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Guillemot MaxiSound 16 PnP - CS4236B */ + { .id = "CSC9836", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* Gallant SC-70P */ + { .id = "CSC9837", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* TerraTec AudioSystem EWS64XL - CS4236B */ + { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" }, { "CSCa803" } } }, + /* TerraTec AudioSystem EWS64XL - CS4236B */ + { .id = "CSCa836", .devs = { { "CSCa800" }, { "CSCa810" } } }, + /* ACER AW37/Pro - CS4235 */ + { .id = "CSCd925", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* ACER AW35/Pro - CS4237B */ + { .id = "CSCd937", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4235 without MPU401 */ + { .id = "CSCe825", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* Unknown SiS530 - CS4235 */ + { .id = "CSC4825", .devs = { { "CSC0100" }, { "CSC0110" } } }, + /* IBM IntelliStation M Pro 6898 11U - CS4236B */ + { .id = "CSCe835", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* IBM PC 300PL Onboard - CS4236B */ + { .id = "CSCe836", .devs = { { "CSC0000" }, { "CSC0010" } } }, + /* Some noname CS4236 based card */ + { .id = "CSCe936", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4236B */ + { .id = "CSCf235", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* CS4236B */ + { .id = "CSCf238", .devs = { { "CSC0000" }, { "CSC0010" }, { "CSC0003" } } }, + /* --- */ + { .id = "" } /* end */ +}; +#endif + +MODULE_DEVICE_TABLE(pnp_card, snd_cs423x_pnpids); + +static int __devinit snd_card_cs4236_pnp(int dev, struct snd_card_cs4236 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL); + int err; + + if (!cfg) + return -ENOMEM; + + acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->wss == NULL) { + kfree(cfg); + return -EBUSY; + } + acard->ctrl = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->ctrl == NULL) { + kfree(cfg); + return -EBUSY; + } + if (id->devs[2].id[0]) { + acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL); + if (acard->mpu == NULL) { + kfree(cfg); + return -EBUSY; + } + } + + /* WSS initialization */ + pdev = acard->wss; + pnp_init_resource_table(cfg); + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 4); + if (fm_port[dev] != SNDRV_AUTO_PORT && fm_port[dev] > 0) + pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4); + if (sb_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], sb_port[dev], 16); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if (dma1[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma1[dev], 1); + if (dma2[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[1], dma2[dev] < 0 ? 4 : dma2[dev], 1); + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR IDENT " WSS PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + kfree(cfg); + printk(KERN_ERR IDENT " WSS PnP configure failed for WSS (out of resources?)\n"); + return -EBUSY; + } + port[dev] = pnp_port_start(pdev, 0); + if (fm_port[dev] > 0) + fm_port[dev] = pnp_port_start(pdev, 1); + sb_port[dev] = pnp_port_start(pdev, 2); + irq[dev] = pnp_irq(pdev, 0); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1) == 4 ? -1 : (int)pnp_dma(pdev, 1); + snd_printdd("isapnp WSS: wss port=0x%lx, fm port=0x%lx, sb port=0x%lx\n", + port[dev], fm_port[dev], sb_port[dev]); + snd_printdd("isapnp WSS: irq=%i, dma1=%i, dma2=%i\n", + irq[dev], dma1[dev], dma2[dev]); + /* CTRL initialization */ + if (acard->ctrl && cport[dev] > 0) { + pdev = acard->ctrl; + pnp_init_resource_table(cfg); + if (cport[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], cport[dev], 8); + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR IDENT " CTRL PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + kfree(cfg); + printk(KERN_ERR IDENT " CTRL PnP configure failed for WSS (out of resources?)\n"); + return -EBUSY; + } + cport[dev] = pnp_port_start(pdev, 0); + snd_printdd("isapnp CTRL: control port=0x%lx\n", cport[dev]); + } + /* MPU initialization */ + if (acard->mpu && mpu_port[dev] > 0) { + pdev = acard->mpu; + pnp_init_resource_table(cfg); + if (mpu_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], mpu_port[dev], 2); + if (mpu_irq[dev] != SNDRV_AUTO_IRQ && mpu_irq[dev] >= 0 && + pnp_irq_valid(pdev, 0)) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1); + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR IDENT " MPU401 PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + printk(KERN_ERR IDENT " MPU401 PnP configure failed for WSS (out of resources?)\n"); + mpu_port[dev] = SNDRV_AUTO_PORT; + mpu_irq[dev] = SNDRV_AUTO_IRQ; + } else { + mpu_port[dev] = pnp_port_start(pdev, 0); + if (mpu_irq[dev] >= 0 && + pnp_irq_valid(pdev, 0) && pnp_irq(pdev, 0) >= 0) { + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + mpu_irq[dev] = -1; /* disable interrupt */ + } + } + snd_printdd("isapnp MPU: port=0x%lx, irq=%i\n", mpu_port[dev], mpu_irq[dev]); + } + kfree(cfg); + return 0; +} +#endif /* CONFIG_PNP */ + +static void snd_card_cs4236_free(snd_card_t *card) +{ + struct snd_card_cs4236 *acard = (struct snd_card_cs4236 *)card->private_data; + + if (acard) { + if (acard->res_sb_port) { + release_resource(acard->res_sb_port); + kfree_nocheck(acard->res_sb_port); + } + } +} + +static int __devinit snd_card_cs423x_probe(int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + snd_card_t *card; + struct snd_card_cs4236 *acard; + snd_pcm_t *pcm = NULL; + cs4231_t *chip; + opl3_t *opl3; + int err; + +#ifdef CONFIG_PNP + if (!isapnp[dev]) { +#endif + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify port\n"); + return -EINVAL; + } + if (cport[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify cport\n"); + return -EINVAL; + } +#ifdef CONFIG_PNP + } +#endif + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_cs4236)); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_card_cs4236 *)card->private_data; + card->private_free = snd_card_cs4236_free; +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if ((err = snd_card_cs4236_pnp(dev, acard, pcard, pid))<0) { + printk(KERN_ERR "isapnp detection failed and probing for " IDENT " is not supported\n"); + snd_card_free(card); + return -ENXIO; + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + if (sb_port[dev] > 0 && sb_port[dev] != SNDRV_AUTO_PORT) + if ((acard->res_sb_port = request_region(sb_port[dev], 16, IDENT " SB")) == NULL) { + printk(KERN_ERR IDENT ": unable to register SB port at 0x%lx\n", sb_port[dev]); + snd_card_free(card); + return -ENOMEM; + } + +#ifdef CS4232 + if ((err = snd_cs4231_create(card, + port[dev], + cport[dev], + irq[dev], + dma1[dev], + dma2[dev], + CS4231_HW_DETECT, + 0, + &chip)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + +#else /* CS4236 */ + if ((err = snd_cs4236_create(card, + port[dev], + cport[dev], + irq[dev], + dma1[dev], + dma2[dev], + CS4231_HW_DETECT, + 0, + &chip)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4236_pcm(chip, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4236_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } +#endif + strcpy(card->driver, pcm->name); + strcpy(card->shortname, pcm->name); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i", + pcm->name, + chip->port, + irq[dev], + dma1[dev]); + if (dma2[dev] >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]); + + if ((err = snd_cs4231_timer(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_OPL3_CS, 0, &opl3) < 0) { + printk(KERN_ERR IDENT ": OPL3 not detected\n"); + } else { + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (mpu_irq[dev] == SNDRV_AUTO_IRQ) + mpu_irq[dev] = -1; + if (snd_mpu401_uart_new(card, 0, MPU401_HW_CS4232, + mpu_port[dev], 0, + mpu_irq[dev], + mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0, NULL) < 0) + printk(KERN_ERR IDENT ": MPU401 not detected\n"); + } + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_cs4236_legacy[dev] = card; + return 0; +} + +#ifdef CONFIG_PNP +static int __devinit snd_cs423x_pnp_detect(struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || !isapnp[dev]) + continue; + res = snd_card_cs423x_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_cs423x_pnp_remove(struct pnp_card_link * pcard) +{ + snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard); + + snd_card_disconnect(card); + snd_card_free_in_thread(card); +} + +static struct pnp_card_driver cs423x_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "cs423x", + .id_table = snd_cs423x_pnpids, + .probe = snd_cs423x_pnp_detect, + .remove = __devexit_p(snd_cs423x_pnp_remove), +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_cs423x_init(void) +{ + int dev, cards = 0; + + for (dev = 0; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + if (snd_card_cs423x_probe(dev, NULL, NULL) >= 0) + cards++; + } +#ifdef CONFIG_PNP + cards += pnp_register_card_driver(&cs423x_pnpc_driver); +#endif + if (!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&cs423x_pnpc_driver); +#endif +#ifdef MODULE + printk(KERN_ERR IDENT " soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_cs423x_exit(void) +{ + int idx; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&cs423x_pnpc_driver); +#endif + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_cs4236_legacy[idx]); +} + +module_init(alsa_card_cs423x_init) +module_exit(alsa_card_cs423x_exit) diff --git a/sound/isa/cs423x/cs4236_lib.c b/sound/isa/cs423x/cs4236_lib.c new file mode 100644 index 000000000000..2128d4bdef41 --- /dev/null +++ b/sound/isa/cs423x/cs4236_lib.c @@ -0,0 +1,970 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of CS4235/4236B/4237B/4238B/4239 chips + * + * Note: + * ----- + * + * Bugs: + * ----- + * + * 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 + * + */ + +/* + * Indirect control registers (CS4236B+) + * + * C0 + * D8: WSS reset (all chips) + * + * C1 (all chips except CS4236) + * D7-D5: version + * D4-D0: chip id + * 11101 - CS4235 + * 01011 - CS4236B + * 01000 - CS4237B + * 01001 - CS4238B + * 11110 - CS4239 + * + * C2 + * D7-D4: 3D Space (CS4235,CS4237B,CS4238B,CS4239) + * D3-D0: 3D Center (CS4237B); 3D Volume (CS4238B) + * + * C3 + * D7: 3D Enable (CS4237B) + * D6: 3D Mono Enable (CS4237B) + * D5: 3D Serial Output (CS4237B,CS4238B) + * D4: 3D Enable (CS4235,CS4238B,CS4239) + * + * C4 + * D7: consumer serial port enable (CS4237B,CS4238B) + * D6: channels status block reset (CS4237B,CS4238B) + * D5: user bit in sub-frame of digital audio data (CS4237B,CS4238B) + * D4: validity bit bit in sub-frame of digital audio data (CS4237B,CS4238B) + * + * C5 lower channel status (digital serial data description) (CS4237B,CS4238B) + * D7-D6: first two bits of category code + * D5: lock + * D4-D3: pre-emphasis (0 = none, 1 = 50/15us) + * D2: copy/copyright (0 = copy inhibited) + * D1: 0 = digital audio / 1 = non-digital audio + * + * C6 upper channel status (digital serial data description) (CS4237B,CS4238B) + * D7-D6: sample frequency (0 = 44.1kHz) + * D5: generation status (0 = no indication, 1 = original/commercially precaptureed data) + * D4-D0: category code (upper bits) + * + * C7 reserved (must write 0) + * + * C8 wavetable control + * D7: volume control interrupt enable (CS4235,CS4239) + * D6: hardware volume control format (CS4235,CS4239) + * D3: wavetable serial port enable (all chips) + * D2: DSP serial port switch (all chips) + * D1: disable MCLK (all chips) + * D0: force BRESET low (all chips) + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/cs4231.h> +#include <sound/asoundef.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for control of CS4235/4236B/4237B/4238B/4239 chips"); +MODULE_LICENSE("GPL"); + +/* + * + */ + +static unsigned char snd_cs4236_ext_map[18] = { + /* CS4236_LEFT_LINE */ 0xff, + /* CS4236_RIGHT_LINE */ 0xff, + /* CS4236_LEFT_MIC */ 0xdf, + /* CS4236_RIGHT_MIC */ 0xdf, + /* CS4236_LEFT_MIX_CTRL */ 0xe0 | 0x18, + /* CS4236_RIGHT_MIX_CTRL */ 0xe0, + /* CS4236_LEFT_FM */ 0xbf, + /* CS4236_RIGHT_FM */ 0xbf, + /* CS4236_LEFT_DSP */ 0xbf, + /* CS4236_RIGHT_DSP */ 0xbf, + /* CS4236_RIGHT_LOOPBACK */ 0xbf, + /* CS4236_DAC_MUTE */ 0xe0, + /* CS4236_ADC_RATE */ 0x01, /* 48kHz */ + /* CS4236_DAC_RATE */ 0x01, /* 48kHz */ + /* CS4236_LEFT_MASTER */ 0xbf, + /* CS4236_RIGHT_MASTER */ 0xbf, + /* CS4236_LEFT_WAVE */ 0xbf, + /* CS4236_RIGHT_WAVE */ 0xbf +}; + +/* + * + */ + +static void snd_cs4236_ctrl_out(cs4231_t *chip, unsigned char reg, unsigned char val) +{ + outb(reg, chip->cport + 3); + outb(chip->cimage[reg] = val, chip->cport + 4); +} + +static unsigned char snd_cs4236_ctrl_in(cs4231_t *chip, unsigned char reg) +{ + outb(reg, chip->cport + 3); + return inb(chip->cport + 4); +} + +/* + * PCM + */ + +#define CLOCKS 8 + +static ratnum_t clocks[CLOCKS] = { + { .num = 16934400, .den_min = 353, .den_max = 353, .den_step = 1 }, + { .num = 16934400, .den_min = 529, .den_max = 529, .den_step = 1 }, + { .num = 16934400, .den_min = 617, .den_max = 617, .den_step = 1 }, + { .num = 16934400, .den_min = 1058, .den_max = 1058, .den_step = 1 }, + { .num = 16934400, .den_min = 1764, .den_max = 1764, .den_step = 1 }, + { .num = 16934400, .den_min = 2117, .den_max = 2117, .den_step = 1 }, + { .num = 16934400, .den_min = 2558, .den_max = 2558, .den_step = 1 }, + { .num = 16934400/16, .den_min = 21, .den_max = 192, .den_step = 1 } +}; + +static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks = { + .nrats = CLOCKS, + .rats = clocks, +}; + +static int snd_cs4236_xrate(snd_pcm_runtime_t *runtime) +{ + return snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); +} + +static unsigned char divisor_to_rate_register(unsigned int divisor) +{ + switch (divisor) { + case 353: return 1; + case 529: return 2; + case 617: return 3; + case 1058: return 4; + case 1764: return 5; + case 2117: return 6; + case 2558: return 7; + default: + snd_runtime_check(divisor >= 21 && divisor <= 192, return 192); + return divisor; + } +} + +static void snd_cs4236_playback_format(cs4231_t *chip, snd_pcm_hw_params_t *params, unsigned char pdfr) +{ + unsigned long flags; + unsigned char rate = divisor_to_rate_register(params->rate_den); + + spin_lock_irqsave(&chip->reg_lock, flags); + /* set fast playback format change and clean playback FIFO */ + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x10); + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, pdfr & 0xf0); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] & ~0x10); + snd_cs4236_ext_out(chip, CS4236_DAC_RATE, rate); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs4236_capture_format(cs4231_t *chip, snd_pcm_hw_params_t *params, unsigned char cdfr) +{ + unsigned long flags; + unsigned char rate = divisor_to_rate_register(params->rate_den); + + spin_lock_irqsave(&chip->reg_lock, flags); + /* set fast capture format change and clean capture FIFO */ + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] | 0x20); + snd_cs4231_out(chip, CS4231_REC_FORMAT, cdfr & 0xf0); + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, chip->image[CS4231_ALT_FEATURE_1] & ~0x20); + snd_cs4236_ext_out(chip, CS4236_ADC_RATE, rate); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +#ifdef CONFIG_PM + +static void snd_cs4236_suspend(cs4231_t *chip) +{ + int reg; + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) + chip->image[reg] = snd_cs4231_in(chip, reg); + for (reg = 0; reg < 18; reg++) + chip->eimage[reg] = snd_cs4236_ext_in(chip, CS4236_I23VAL(reg)); + for (reg = 2; reg < 9; reg++) + chip->cimage[reg] = snd_cs4236_ctrl_in(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_cs4236_resume(cs4231_t *chip) +{ + int reg; + unsigned long flags; + + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (reg = 0; reg < 32; reg++) { + switch (reg) { + case CS4236_EXT_REG: + case CS4231_VERSION: + case 27: /* why? CS4235 - master left */ + case 29: /* why? CS4235 - master right */ + break; + default: + snd_cs4231_out(chip, reg, chip->image[reg]); + break; + } + } + for (reg = 0; reg < 18; reg++) + snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), chip->eimage[reg]); + for (reg = 2; reg < 9; reg++) { + switch (reg) { + case 7: + break; + default: + snd_cs4236_ctrl_out(chip, reg, chip->cimage[reg]); + } + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); +} + +#endif /* CONFIG_PM */ + +int snd_cs4236_create(snd_card_t * card, + unsigned long port, + unsigned long cport, + int irq, int dma1, int dma2, + unsigned short hardware, + unsigned short hwshare, + cs4231_t ** rchip) +{ + cs4231_t *chip; + unsigned char ver1, ver2; + unsigned int reg; + int err; + + *rchip = NULL; + if (hardware == CS4231_HW_DETECT) + hardware = CS4231_HW_DETECT3; + if (cport < 0x100) { + snd_printk("please, specify control port for CS4236+ chips\n"); + return -ENODEV; + } + if ((err = snd_cs4231_create(card, port, cport, irq, dma1, dma2, hardware, hwshare, &chip)) < 0) + return err; + + if (!(chip->hardware & CS4231_HW_CS4236B_MASK)) { + snd_printk("CS4236+: MODE3 and extended registers not available, hardware=0x%x\n",chip->hardware); + snd_device_free(card, chip); + return -ENODEV; + } +#if 0 + { + int idx; + for (idx = 0; idx < 8; idx++) + snd_printk("CD%i = 0x%x\n", idx, inb(chip->cport + idx)); + for (idx = 0; idx < 9; idx++) + snd_printk("C%i = 0x%x\n", idx, snd_cs4236_ctrl_in(chip, idx)); + } +#endif + ver1 = snd_cs4236_ctrl_in(chip, 1); + ver2 = snd_cs4236_ext_in(chip, CS4236_VERSION); + snd_printdd("CS4236: [0x%lx] C1 (version) = 0x%x, ext = 0x%x\n", cport, ver1, ver2); + if (ver1 != ver2) { + snd_printk("CS4236+ chip detected, but control port 0x%lx is not valid\n", cport); + snd_device_free(card, chip); + return -ENODEV; + } + snd_cs4236_ctrl_out(chip, 0, 0x00); + snd_cs4236_ctrl_out(chip, 2, 0xff); + snd_cs4236_ctrl_out(chip, 3, 0x00); + snd_cs4236_ctrl_out(chip, 4, 0x80); + snd_cs4236_ctrl_out(chip, 5, ((IEC958_AES1_CON_PCM_CODER & 3) << 6) | IEC958_AES0_CON_EMPHASIS_NONE); + snd_cs4236_ctrl_out(chip, 6, IEC958_AES1_CON_PCM_CODER >> 2); + snd_cs4236_ctrl_out(chip, 7, 0x00); + /* 0x8c for C8 is valid for Turtle Beach Malibu - the IEC-958 output */ + /* is working with this setup, other hardware should have */ + /* different signal paths and this value should be selectable */ + /* in the future */ + snd_cs4236_ctrl_out(chip, 8, 0x8c); + chip->rate_constraint = snd_cs4236_xrate; + chip->set_playback_format = snd_cs4236_playback_format; + chip->set_capture_format = snd_cs4236_capture_format; +#ifdef CONFIG_PM + chip->suspend = snd_cs4236_suspend; + chip->resume = snd_cs4236_resume; +#endif + + /* initialize extended registers */ + for (reg = 0; reg < sizeof(snd_cs4236_ext_map); reg++) + snd_cs4236_ext_out(chip, CS4236_I23VAL(reg), snd_cs4236_ext_map[reg]); + + /* initialize compatible but more featured registers */ + snd_cs4231_out(chip, CS4231_LEFT_INPUT, 0x40); + snd_cs4231_out(chip, CS4231_RIGHT_INPUT, 0x40); + snd_cs4231_out(chip, CS4231_AUX1_LEFT_INPUT, 0xff); + snd_cs4231_out(chip, CS4231_AUX1_RIGHT_INPUT, 0xff); + snd_cs4231_out(chip, CS4231_AUX2_LEFT_INPUT, 0xdf); + snd_cs4231_out(chip, CS4231_AUX2_RIGHT_INPUT, 0xdf); + snd_cs4231_out(chip, CS4231_RIGHT_LINE_IN, 0xff); + snd_cs4231_out(chip, CS4231_LEFT_LINE_IN, 0xff); + snd_cs4231_out(chip, CS4231_RIGHT_LINE_IN, 0xff); + switch (chip->hardware) { + case CS4231_HW_CS4235: + case CS4231_HW_CS4239: + snd_cs4231_out(chip, CS4235_LEFT_MASTER, 0xff); + snd_cs4231_out(chip, CS4235_RIGHT_MASTER, 0xff); + break; + } + + *rchip = chip; + return 0; +} + +int snd_cs4236_pcm(cs4231_t *chip, int device, snd_pcm_t **rpcm) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_cs4231_pcm(chip, device, &pcm)) < 0) + return err; + pcm->info_flags &= ~SNDRV_PCM_INFO_JOINT_DUPLEX; + if (rpcm) + *rpcm = pcm; + return 0; +} + +/* + * MIXER + */ + +#define CS4236_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_single, .put = snd_cs4236_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_cs4236_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + int mask = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_cs4236_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(reg)] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_cs4236_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->eimage[CS4236_REG(reg)] & ~(mask << shift)) | val; + change = val != chip->eimage[CS4236_REG(reg)]; + snd_cs4236_ext_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_SINGLEC(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_singlec, .put = snd_cs4236_put_singlec, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_cs4236_get_singlec(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->cimage[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_cs4236_put_singlec(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 8) & 0xff; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->cimage[reg] & ~(mask << shift)) | val; + change = val != chip->cimage[reg]; + snd_cs4236_ctrl_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_DOUBLE(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double, .put = snd_cs4236_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_cs4236_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + int mask = (kcontrol->private_value >> 24) & 0xff; + + uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_cs4236_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->eimage[CS4236_REG(left_reg)] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_cs4236_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg != right_reg) { + val1 = (chip->eimage[CS4236_REG(left_reg)] & ~(mask << shift_left)) | val1; + val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2; + change = val1 != chip->eimage[CS4236_REG(left_reg)] || val2 != chip->eimage[CS4236_REG(right_reg)]; + snd_cs4236_ext_out(chip, left_reg, val1); + snd_cs4236_ext_out(chip, right_reg, val2); + } else { + val1 = (chip->eimage[CS4236_REG(left_reg)] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != chip->eimage[CS4236_REG(left_reg)]; + snd_cs4236_ext_out(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_DOUBLE1(xname, xindex, left_reg, right_reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_double1, .put = snd_cs4236_put_double1, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_cs4236_get_double1(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (chip->image[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->eimage[CS4236_REG(right_reg)] >> shift_right) & mask; + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (invert) { + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + ucontrol->value.integer.value[1] = mask - ucontrol->value.integer.value[1]; + } + return 0; +} + +static int snd_cs4236_put_double1(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int shift_left = (kcontrol->private_value >> 16) & 0x07; + int shift_right = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int invert = (kcontrol->private_value >> 22) & 1; + int change; + unsigned short val1, val2; + + val1 = ucontrol->value.integer.value[0] & mask; + val2 = ucontrol->value.integer.value[1] & mask; + if (invert) { + val1 = mask - val1; + val2 = mask - val2; + } + val1 <<= shift_left; + val2 <<= shift_right; + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->image[left_reg] & ~(mask << shift_left)) | val1; + val2 = (chip->eimage[CS4236_REG(right_reg)] & ~(mask << shift_right)) | val2; + change = val1 != chip->image[left_reg] || val2 != chip->eimage[CS4236_REG(right_reg)]; + snd_cs4231_out(chip, left_reg, val1); + snd_cs4236_ext_out(chip, right_reg, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4236_MASTER_DIGITAL(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4236_get_master_digital, .put = snd_cs4236_put_master_digital, \ + .private_value = 71 << 24 } + +static inline int snd_cs4236_mixer_master_digital_invert_volume(int vol) +{ + return (vol < 64) ? 63 - vol : 64 + (71 - vol); +} + +static int snd_cs4236_get_master_digital(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & 0x7f); + ucontrol->value.integer.value[1] = snd_cs4236_mixer_master_digital_invert_volume(chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & 0x7f); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4236_put_master_digital(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1, val2; + + val1 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[0] & 0x7f); + val2 = snd_cs4236_mixer_master_digital_invert_volume(ucontrol->value.integer.value[1] & 0x7f); + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] & ~0x7f) | val1; + val2 = (chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)] & ~0x7f) | val2; + change = val1 != chip->eimage[CS4236_REG(CS4236_LEFT_MASTER)] || val2 != chip->eimage[CS4236_REG(CS4236_RIGHT_MASTER)]; + snd_cs4236_ext_out(chip, CS4236_LEFT_MASTER, val1); + snd_cs4236_ext_out(chip, CS4236_RIGHT_MASTER, val1); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define CS4235_OUTPUT_ACCU(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_double, \ + .get = snd_cs4235_get_output_accu, .put = snd_cs4235_put_output_accu, \ + .private_value = 3 << 24 } + +static inline int snd_cs4235_mixer_output_accu_get_volume(int vol) +{ + switch ((vol >> 5) & 3) { + case 0: return 1; + case 1: return 3; + case 2: return 2; + case 3: return 0; + } + return 3; +} + +static inline int snd_cs4235_mixer_output_accu_set_volume(int vol) +{ + switch (vol & 3) { + case 0: return 3 << 5; + case 1: return 0 << 5; + case 2: return 2 << 5; + case 3: return 1 << 5; + } + return 1 << 5; +} + +static int snd_cs4235_get_output_accu(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_LEFT_MASTER]); + ucontrol->value.integer.value[1] = snd_cs4235_mixer_output_accu_get_volume(chip->image[CS4235_RIGHT_MASTER]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4235_put_output_accu(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1, val2; + + val1 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[0]); + val2 = snd_cs4235_mixer_output_accu_set_volume(ucontrol->value.integer.value[1]); + spin_lock_irqsave(&chip->reg_lock, flags); + val1 = (chip->image[CS4235_LEFT_MASTER] & ~(3 << 5)) | val1; + val2 = (chip->image[CS4235_RIGHT_MASTER] & ~(3 << 5)) | val2; + change = val1 != chip->image[CS4235_LEFT_MASTER] || val2 != chip->image[CS4235_RIGHT_MASTER]; + snd_cs4231_out(chip, CS4235_LEFT_MASTER, val1); + snd_cs4231_out(chip, CS4235_RIGHT_MASTER, val2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_cs4236_controls[] = { + +CS4236_DOUBLE("Master Digital Playback Switch", 0, CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1), +CS4236_DOUBLE("Master Digital Capture Switch", 0, CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1), +CS4236_MASTER_DIGITAL("Master Digital Volume", 0), + +CS4236_DOUBLE("Capture Boost Volume", 0, CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1), + +CS4231_DOUBLE("PCM Playback Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +CS4231_DOUBLE("PCM Playback Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1), + +CS4236_DOUBLE("DSP Playback Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1), +CS4236_DOUBLE("DSP Playback Volume", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 0, 0, 63, 1), + +CS4236_DOUBLE("FM Playback Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1), +CS4236_DOUBLE("FM Playback Volume", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 0, 0, 63, 1), + +CS4236_DOUBLE("Wavetable Playback Switch", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1), +CS4236_DOUBLE("Wavetable Playback Volume", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 0, 0, 63, 1), + +CS4231_DOUBLE("Synth Playback Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +CS4231_DOUBLE("Synth Volume", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1), +CS4231_DOUBLE("Synth Capture Switch", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1), +CS4231_DOUBLE("Synth Capture Bypass", 0, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 5, 5, 1, 1), + +CS4236_DOUBLE("Mic Playback Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1), +CS4236_DOUBLE("Mic Capture Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1), +CS4236_DOUBLE("Mic Volume", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 0, 0, 31, 1), +CS4236_DOUBLE("Mic Playback Boost", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 5, 5, 1, 0), + +CS4231_DOUBLE("Line Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Line Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_DOUBLE("Line Capture Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1), +CS4231_DOUBLE("Line Capture Bypass", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 5, 5, 1, 1), + +CS4231_DOUBLE("CD Playback Switch", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("CD Volume", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1), +CS4231_DOUBLE("CD Capture Switch", 0, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1), + +CS4236_DOUBLE1("Mono Output Playback Switch", 0, CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1), +CS4236_DOUBLE1("Mono Playback Switch", 0, CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1), +CS4231_SINGLE("Mono Playback Volume", 0, CS4231_MONO_CTRL, 0, 15, 1), +CS4231_SINGLE("Mono Playback Bypass", 0, CS4231_MONO_CTRL, 5, 1, 0), + +CS4231_DOUBLE("Capture Volume", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 0, 0, 15, 0), +CS4231_DOUBLE("Analog Loopback Capture Switch", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0), + +CS4231_SINGLE("Digital Loopback Playback Switch", 0, CS4231_LOOPBACK, 0, 1, 0), +CS4236_DOUBLE1("Digital Loopback Playback Volume", 0, CS4231_LOOPBACK, CS4236_RIGHT_LOOPBACK, 2, 0, 63, 1) +}; + +static snd_kcontrol_new_t snd_cs4235_controls[] = { + +CS4231_DOUBLE("Master Switch", 0, CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 7, 7, 1, 1), +CS4231_DOUBLE("Master Volume", 0, CS4235_LEFT_MASTER, CS4235_RIGHT_MASTER, 0, 0, 31, 1), + +CS4235_OUTPUT_ACCU("Playback Volume", 0), + +CS4236_DOUBLE("Master Digital Playback Switch", 0, CS4236_LEFT_MASTER, CS4236_RIGHT_MASTER, 7, 7, 1, 1), +CS4236_DOUBLE("Master Digital Capture Switch", 0, CS4236_DAC_MUTE, CS4236_DAC_MUTE, 7, 6, 1, 1), +CS4236_MASTER_DIGITAL("Master Digital Volume", 0), + +CS4231_DOUBLE("Master Digital Playback Switch", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 7, 7, 1, 1), +CS4231_DOUBLE("Master Digital Capture Switch", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 6, 6, 1, 1), +CS4231_DOUBLE("Master Digital Volume", 1, CS4231_LEFT_LINE_IN, CS4231_RIGHT_LINE_IN, 0, 0, 31, 1), + +CS4236_DOUBLE("Capture Volume", 0, CS4236_LEFT_MIX_CTRL, CS4236_RIGHT_MIX_CTRL, 5, 5, 3, 1), + +CS4231_DOUBLE("PCM Switch", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 7, 7, 1, 1), +CS4231_DOUBLE("PCM Volume", 0, CS4231_LEFT_OUTPUT, CS4231_RIGHT_OUTPUT, 0, 0, 63, 1), + +CS4236_DOUBLE("DSP Switch", 0, CS4236_LEFT_DSP, CS4236_RIGHT_DSP, 7, 7, 1, 1), + +CS4236_DOUBLE("FM Switch", 0, CS4236_LEFT_FM, CS4236_RIGHT_FM, 7, 7, 1, 1), + +CS4236_DOUBLE("Wavetable Switch", 0, CS4236_LEFT_WAVE, CS4236_RIGHT_WAVE, 7, 7, 1, 1), + +CS4236_DOUBLE("Mic Capture Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 7, 7, 1, 1), +CS4236_DOUBLE("Mic Playback Switch", 0, CS4236_LEFT_MIC, CS4236_RIGHT_MIC, 6, 6, 1, 1), +CS4236_SINGLE("Mic Volume", 0, CS4236_LEFT_MIC, 0, 31, 1), +CS4236_SINGLE("Mic Playback Boost", 0, CS4236_LEFT_MIC, 5, 1, 0), + +CS4231_DOUBLE("Aux Playback Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Capture Switch", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 6, 6, 1, 1), +CS4231_DOUBLE("Aux Volume", 0, CS4231_AUX1_LEFT_INPUT, CS4231_AUX1_RIGHT_INPUT, 0, 0, 31, 1), + +CS4231_DOUBLE("Aux Playback Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Aux Capture Switch", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 6, 6, 1, 1), +CS4231_DOUBLE("Aux Volume", 1, CS4231_AUX2_LEFT_INPUT, CS4231_AUX2_RIGHT_INPUT, 0, 0, 31, 1), + +CS4236_DOUBLE1("Master Mono Switch", 0, CS4231_MONO_CTRL, CS4236_RIGHT_MIX_CTRL, 6, 7, 1, 1), + +CS4236_DOUBLE1("Mono Switch", 0, CS4231_MONO_CTRL, CS4236_LEFT_MIX_CTRL, 7, 7, 1, 1), +CS4231_SINGLE("Mono Volume", 0, CS4231_MONO_CTRL, 0, 15, 1), + +CS4231_DOUBLE("Analog Loopback Switch", 0, CS4231_LEFT_INPUT, CS4231_RIGHT_INPUT, 7, 7, 1, 0), +}; + +#define CS4236_IEC958_ENABLE(xname, xindex) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_cs4236_info_single, \ + .get = snd_cs4236_get_iec958_switch, .put = snd_cs4236_put_iec958_switch, \ + .private_value = 1 << 16 } + +static int snd_cs4236_get_iec958_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = chip->image[CS4231_ALT_FEATURE_1] & 0x02 ? 1 : 0; +#if 0 + printk("get valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n", + snd_cs4231_in(chip, CS4231_ALT_FEATURE_1), + snd_cs4236_ctrl_in(chip, 3), + snd_cs4236_ctrl_in(chip, 4), + snd_cs4236_ctrl_in(chip, 5), + snd_cs4236_ctrl_in(chip, 6), + snd_cs4236_ctrl_in(chip, 8)); +#endif + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_cs4236_put_iec958_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + cs4231_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short enable, val; + + enable = ucontrol->value.integer.value[0] & 1; + + down(&chip->mce_mutex); + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + val = (chip->image[CS4231_ALT_FEATURE_1] & ~0x0e) | (0<<2) | (enable << 1); + change = val != chip->image[CS4231_ALT_FEATURE_1]; + snd_cs4231_out(chip, CS4231_ALT_FEATURE_1, val); + val = snd_cs4236_ctrl_in(chip, 4) | 0xc0; + snd_cs4236_ctrl_out(chip, 4, val); + udelay(100); + val &= ~0x40; + snd_cs4236_ctrl_out(chip, 4, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + up(&chip->mce_mutex); + +#if 0 + printk("set valid: ALT = 0x%x, C3 = 0x%x, C4 = 0x%x, C5 = 0x%x, C6 = 0x%x, C8 = 0x%x\n", + snd_cs4231_in(chip, CS4231_ALT_FEATURE_1), + snd_cs4236_ctrl_in(chip, 3), + snd_cs4236_ctrl_in(chip, 4), + snd_cs4236_ctrl_in(chip, 5), + snd_cs4236_ctrl_in(chip, 6), + snd_cs4236_ctrl_in(chip, 8)); +#endif + return change; +} + +static snd_kcontrol_new_t snd_cs4236_iec958_controls[] = { +CS4236_IEC958_ENABLE("IEC958 Output Enable", 0), +CS4236_SINGLEC("IEC958 Output Validity", 0, 4, 4, 1, 0), +CS4236_SINGLEC("IEC958 Output User", 0, 4, 5, 1, 0), +CS4236_SINGLEC("IEC958 Output CSBR", 0, 4, 6, 1, 0), +CS4236_SINGLEC("IEC958 Output Channel Status Low", 0, 5, 1, 127, 0), +CS4236_SINGLEC("IEC958 Output Channel Status High", 0, 6, 0, 255, 0) +}; + +static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4235[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1) +}; + +static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4237[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 7, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1), +CS4236_SINGLEC("3D Control - Center", 0, 2, 0, 15, 1), +CS4236_SINGLEC("3D Control - Mono", 0, 3, 6, 1, 0), +CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0) +}; + +static snd_kcontrol_new_t snd_cs4236_3d_controls_cs4238[] = { +CS4236_SINGLEC("3D Control - Switch", 0, 3, 4, 1, 0), +CS4236_SINGLEC("3D Control - Space", 0, 2, 4, 15, 1), +CS4236_SINGLEC("3D Control - Volume", 0, 2, 0, 15, 1), +CS4236_SINGLEC("3D Control - IEC958", 0, 3, 5, 1, 0) +}; + +int snd_cs4236_mixer(cs4231_t *chip) +{ + snd_card_t *card; + unsigned int idx, count; + int err; + snd_kcontrol_new_t *kcontrol; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + card = chip->card; + strcpy(card->mixername, snd_cs4231_chip_id(chip)); + + if (chip->hardware == CS4231_HW_CS4235 || + chip->hardware == CS4231_HW_CS4239) { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4235_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4235_controls[idx], chip))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_controls[idx], chip))) < 0) + return err; + } + } + switch (chip->hardware) { + case CS4231_HW_CS4235: + case CS4231_HW_CS4239: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4235); + kcontrol = snd_cs4236_3d_controls_cs4235; + break; + case CS4231_HW_CS4237B: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4237); + kcontrol = snd_cs4236_3d_controls_cs4237; + break; + case CS4231_HW_CS4238B: + count = ARRAY_SIZE(snd_cs4236_3d_controls_cs4238); + kcontrol = snd_cs4236_3d_controls_cs4238; + break; + default: + count = 0; + kcontrol = NULL; + } + for (idx = 0; idx < count; idx++, kcontrol++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(kcontrol, chip))) < 0) + return err; + } + if (chip->hardware == CS4231_HW_CS4237B || + chip->hardware == CS4231_HW_CS4238B) { + for (idx = 0; idx < ARRAY_SIZE(snd_cs4236_iec958_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_cs4236_iec958_controls[idx], chip))) < 0) + return err; + } + } + return 0; +} + +EXPORT_SYMBOL(snd_cs4236_create); +EXPORT_SYMBOL(snd_cs4236_pcm); +EXPORT_SYMBOL(snd_cs4236_mixer); + +/* + * INIT part + */ + +static int __init alsa_cs4236_init(void) +{ + return 0; +} + +static void __exit alsa_cs4236_exit(void) +{ +} + +module_init(alsa_cs4236_init) +module_exit(alsa_cs4236_exit) |