diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /sound/isa |
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')
76 files changed, 38300 insertions, 0 deletions
diff --git a/sound/isa/Kconfig b/sound/isa/Kconfig new file mode 100644 index 00000000000..4a6be966bd9 --- /dev/null +++ b/sound/isa/Kconfig @@ -0,0 +1,377 @@ +# ALSA ISA drivers + +menu "ISA devices" + depends on SND!=n && ISA + +config SND_AD1848_LIB + tristate + select SND_PCM + select SND_GENERIC_PM + +config SND_CS4231_LIB + tristate + select SND_PCM + select SND_GENERIC_PM + +config SND_AD1816A + tristate "Analog Devices SoundPort AD1816A" + depends on SND && ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for Analog Devices SoundPort + AD1816A or compatible sound chips. + + To compile this driver as a module, choose M here: the module + will be called snd-ad1816a. + +config SND_AD1848 + tristate "Generic AD1848/CS4248 driver" + depends on SND + select SND_AD1848_LIB + help + Say Y here to include support for AD1848 (Analog Devices) or + CS4248 (Cirrus Logic - Crystal Semiconductors) chips. + + For newer chips from Cirrus Logic, use the CS4231, CS4232 or + CS4236+ drivers. + + To compile this driver as a module, choose M here: the module + will be called snd-ad1848. + +config SND_CS4231 + tristate "Generic Cirrus Logic CS4231 driver" + depends on SND + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for CS4231 chips from Cirrus + Logic - Crystal Semiconductors. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4231. + +config SND_CS4232 + tristate "Generic Cirrus Logic CS4232 driver" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for CS4232 chips from Cirrus + Logic - Crystal Semiconductors. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4232. + +config SND_CS4236 + tristate "Generic Cirrus Logic CS4236+ driver" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y to include support for CS4235,CS4236,CS4237B,CS4238B, + CS4239 chips from Cirrus Logic - Crystal Semiconductors. + + To compile this driver as a module, choose M here: the module + will be called snd-cs4236. + +config SND_ES968 + tristate "Generic ESS ES968 driver" + depends on SND && ISAPNP + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for ESS AudioDrive ES968 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es968. + +config SND_ES1688 + tristate "Generic ESS ES688/ES1688 driver" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for ESS AudioDrive ES688 or + ES1688 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es1688. + +config SND_ES18XX + tristate "Generic ESS ES18xx driver" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + select SND_GENERIC_PM + help + Say Y here to include support for ESS AudioDrive ES18xx chips. + + To compile this driver as a module, choose M here: the module + will be called snd-es18xx. + +config SND_GUS_SYNTH + tristate + +config SND_GUSCLASSIC + tristate "Gravis UltraSound Classic" + depends on SND + select SND_RAWMIDI + select SND_PCM + select SND_GUS_SYNTH + help + Say Y here to include support for Gravis UltraSound Classic + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusclassic. + +config SND_GUSEXTREME + tristate "Gravis UltraSound Extreme" + depends on SND + select SND_HWDEP + select SND_MPU401_UART + select SND_PCM + select SND_GUS_SYNTH + help + Say Y here to include support for Gravis UltraSound Extreme + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusextreme. + +config SND_GUSMAX + tristate "Gravis UltraSound MAX" + depends on SND + select SND_RAWMIDI + select SND_CS4231_LIB + select SND_GUS_SYNTH + help + Say Y here to include support for Gravis UltraSound MAX + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-gusmax. + +config SND_INTERWAVE + tristate "AMD InterWave, Gravis UltraSound PnP" + depends on SND + select SND_RAWMIDI + select SND_CS4231_LIB + select SND_GUS_SYNTH + help + Say Y here to include support for AMD InterWave based + soundcards (Gravis UltraSound Plug & Play, STB SoundRage32, + MED3210, Dynasonic Pro, Panasonic PCA761AW). + + To compile this driver as a module, choose M here: the module + will be called snd-interwave. + +config SND_INTERWAVE_STB + tristate "AMD InterWave + TEA6330T (UltraSound 32-Pro)" + depends on SND + select SND_RAWMIDI + select SND_CS4231_LIB + select SND_GUS_SYNTH + help + Say Y here to include support for AMD InterWave based + soundcards with a TEA6330T bass and treble regulator + (UltraSound 32-Pro). + + To compile this driver as a module, choose M here: the module + will be called snd-interwave-stb. + +config SND_OPTI92X_AD1848 + tristate "OPTi 82C92x - AD1848" + depends on SND + select SND_OPL3_LIB + select SND_OPL4_LIB + select SND_MPU401_UART + select SND_AD1848_LIB + help + Say Y here to include support for soundcards based on Opti + 82C92x or OTI-601 chips and using an AD1848 codec. + + To compile this driver as a module, choose M here: the module + will be called snd-opti92x-ad1848. + +config SND_OPTI92X_CS4231 + tristate "OPTi 82C92x - CS4231" + depends on SND + select SND_OPL3_LIB + select SND_OPL4_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for soundcards based on Opti + 82C92x chips and using a CS4231 codec. + + To compile this driver as a module, choose M here: the module + will be called snd-opti92x-cs4231. + +config SND_OPTI93X + tristate "OPTi 82C93x" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for soundcards based on Opti + 82C93x chips. + + To compile this driver as a module, choose M here: the module + will be called snd-opti93x. + +config SND_SB8 + tristate "Sound Blaster 1.0/2.0/Pro (8-bit)" + depends on SND + select SND_OPL3_LIB + select SND_RAWMIDI + select SND_PCM + help + Say Y here to include support for Creative Sound Blaster 1.0/ + 2.0/Pro (8-bit) or 100% compatible soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-sb8. + +config SND_SB16 + tristate "Sound Blaster 16 (PnP)" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for Sound Blaster 16 soundcards + (including the Plug and Play version). + + To compile this driver as a module, choose M here: the module + will be called snd-sb16. + +config SND_SBAWE + tristate "Sound Blaster AWE (32,64) (PnP)" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for Sound Blaster AWE soundcards + (including the Plug and Play version). + + To compile this driver as a module, choose M here: the module + will be called snd-sbawe. + +config SND_SB16_CSP + bool "Sound Blaster 16/AWE CSP support" + depends on (SND_SB16 || SND_SBAWE) && (BROKEN || !PPC) + help + Say Y here to include support for the CSP core. This special + coprocessor can do variable tasks like various compression and + decompression algorithms. + +config SND_WAVEFRONT + tristate "Turtle Beach Maui,Tropez,Tropez+ (Wavefront)" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for Turtle Beach Maui, Tropez + and Tropez+ soundcards based on the Wavefront chip. + + To compile this driver as a module, choose M here: the module + will be called snd-wavefront. + +config SND_ALS100 + tristate "Avance Logic ALS100/ALS120" + depends on SND && ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for soundcards based on Avance + Logic ALS100, ALS110, ALS120 and ALS200 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-als100. + +config SND_AZT2320 + tristate "Aztech Systems AZT2320" + depends on SND && ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for soundcards based on the + Aztech Systems AZT2320 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-azt2320. + +config SND_CMI8330 + tristate "C-Media CMI8330" + depends on SND + select SND_AD1848_LIB + help + Say Y here to include support for soundcards based on the + C-Media CMI8330 chip. + + To compile this driver as a module, choose M here: the module + will be called snd-cmi8330. + +config SND_DT019X + tristate "Diamond Technologies DT-019X, Avance Logic ALS-007" + depends on SND && ISAPNP + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_PCM + help + Say Y here to include support for soundcards based on the + Diamond Technologies DT-019X or Avance Logic ALS-007 chips. + + To compile this driver as a module, choose M here: the module + will be called snd-dt019x. + +config SND_OPL3SA2 + tristate "Yamaha OPL3-SA2/SA3" + depends on SND + select SND_OPL3_LIB + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for Yamaha OPL3-SA2 and OPL3-SA3 + chips. + + To compile this driver as a module, choose M here: the module + will be called snd-opl3sa2. + +config SND_SGALAXY + tristate "Aztech Sound Galaxy" + depends on SND + select SND_AD1848_LIB + help + Say Y here to include support for Aztech Sound Galaxy + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-sgalaxy. + +config SND_SSCAPE + tristate "Ensoniq SoundScape PnP driver" + depends on SND + select SND_HWDEP + select SND_MPU401_UART + select SND_CS4231_LIB + help + Say Y here to include support for Ensoniq SoundScape PnP + soundcards. + + To compile this driver as a module, choose M here: the module + will be called snd-sscape. + +endmenu diff --git a/sound/isa/Makefile b/sound/isa/Makefile new file mode 100644 index 00000000000..05724eb7bfe --- /dev/null +++ b/sound/isa/Makefile @@ -0,0 +1,26 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-als100-objs := als100.o +snd-azt2320-objs := azt2320.o +snd-cmi8330-objs := cmi8330.o +snd-dt019x-objs := dt019x.o +snd-es18xx-objs := es18xx.o +snd-opl3sa2-objs := opl3sa2.o +snd-sgalaxy-objs := sgalaxy.o +snd-sscape-objs := sscape.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ALS100) += snd-als100.o +obj-$(CONFIG_SND_AZT2320) += snd-azt2320.o +obj-$(CONFIG_SND_CMI8330) += snd-cmi8330.o +obj-$(CONFIG_SND_DT019X) += snd-dt019x.o +obj-$(CONFIG_SND_ES18XX) += snd-es18xx.o +obj-$(CONFIG_SND_OPL3SA2) += snd-opl3sa2.o +obj-$(CONFIG_SND_SGALAXY) += snd-sgalaxy.o +obj-$(CONFIG_SND_SSCAPE) += snd-sscape.o + +obj-$(CONFIG_SND) += ad1816a/ ad1848/ cs423x/ es1688/ gus/ opti9xx/ \ + sb/ wavefront/ diff --git a/sound/isa/ad1816a/Makefile b/sound/isa/ad1816a/Makefile new file mode 100644 index 00000000000..a42b29cf854 --- /dev/null +++ b/sound/isa/ad1816a/Makefile @@ -0,0 +1,10 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-ad1816a-lib-objs := ad1816a_lib.o +snd-ad1816a-objs := ad1816a.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_AD1816A) += snd-ad1816a.o snd-ad1816a-lib.o diff --git a/sound/isa/ad1816a/ad1816a.c b/sound/isa/ad1816a/ad1816a.c new file mode 100644 index 00000000000..9fa7a78da6c --- /dev/null +++ b/sound/isa/ad1816a/ad1816a.c @@ -0,0 +1,312 @@ + +/* + card-ad1816a.c - driver for ADI SoundPort AD1816A based soundcards. + Copyright (C) 2000 by Massimo Piccioni <dafastidio@libero.it> + + 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/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/ad1816a.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> + +#define PFX "ad1816a: " + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("AD1816A, AD1815"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Highscreen,Sound-Boostar 16 3D}," + "{Analog Devices,AD1815}," + "{Analog Devices,AD1816A}," + "{TerraTec,Base 64}," + "{TerraTec,AudioSystem EWS64S}," + "{Aztech/Newcom SC-16 3D}," + "{Shark Predator ISA}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 1-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 */ +static long port[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 int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ad1816a based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ad1816a based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ad1816a based soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for ad1816a driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ad1816a driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for ad1816a driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for ad1816a driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for ad1816a driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "1st DMA # for ad1816a driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "2nd DMA # for ad1816a driver."); + +struct snd_card_ad1816a { + struct pnp_dev *dev; + struct pnp_dev *devmpu; +}; + +static struct pnp_card_device_id snd_ad1816a_pnpids[] = { + /* Analog Devices AD1815 */ + { .id = "ADS7150", .devs = { { .id = "ADS7150" }, { .id = "ADS7151" } } }, + /* Analog Devices AD1816A - added by Kenneth Platz <kxp@atl.hp.com> */ + { .id = "ADS7181", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Aztech/Newcom SC-16 3D */ + { .id = "AZT1022", .devs = { { .id = "AZT1018" }, { .id = "AZT2002" } } }, + /* Highscreen Sound-Boostar 16 3D - added by Stefan Behnel */ + { .id = "LWC1061", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Highscreen Sound-Boostar 16 3D */ + { .id = "MDK1605", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Shark Predator ISA - added by Ken Arromdee */ + { .id = "SMM7180", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Terratec AudioSystem EWS64S */ + { .id = "TER1112", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* Analog Devices AD1816A - Terratec Base 64 */ + { .id = "TER1411", .devs = { { .id = "ADS7180" }, { .id = "ADS7181" } } }, + /* end */ + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_ad1816a_pnpids); + + +#define DRIVER_NAME "snd-card-ad1816a" + + +static int __devinit snd_card_ad1816a_pnp(int dev, struct snd_card_ad1816a *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + int err; + + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -EBUSY; + } + acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->devmpu == NULL) { + kfree(cfg); + return -EBUSY; + } + + pdev = acard->dev; + pnp_init_resource_table(cfg); + + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], port[dev], 16); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4); + 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], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + printk(KERN_ERR PFX "AUDIO PnP configure failure\n"); + kfree(cfg); + return -EBUSY; + } + + port[dev] = pnp_port_start(pdev, 2); + fm_port[dev] = pnp_port_start(pdev, 1); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + + pdev = acard->devmpu; + 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) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1); + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + printk(KERN_ERR PFX "MPU401 PnP configure failure\n"); + mpu_port[dev] = -1; + acard->devmpu = NULL; + } else { + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } + + kfree(cfg); + return 0; +} + +static int __devinit snd_card_ad1816a_probe(int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + snd_card_t *card; + struct snd_card_ad1816a *acard; + ad1816a_t *chip; + opl3_t *opl3; + + if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_ad1816a))) == NULL) + return -ENOMEM; + acard = (struct snd_card_ad1816a *)card->private_data; + + if ((error = snd_card_ad1816a_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, &pcard->card->dev); + + if ((error = snd_ad1816a_create(card, port[dev], + irq[dev], + dma1[dev], + dma2[dev], + &chip)) < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "AD1816A"); + strcpy(card->shortname, "ADI SoundPort AD1816A"); + sprintf(card->longname, "%s, SS at 0x%lx, irq %d, dma %d&%d", + card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]); + + if ((error = snd_ad1816a_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_ad1816a_mixer(chip)) < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + mpu_port[dev], 0, mpu_irq[dev], SA_INTERRUPT, + NULL) < 0) + printk(KERN_ERR PFX "no MPU-401 device at 0x%lx.\n", mpu_port[dev]); + } + + if (fm_port[dev] > 0) { + if (snd_opl3_create(card, + fm_port[dev], fm_port[dev] + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx.\n", fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static int __devinit snd_ad1816a_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]) + continue; + res = snd_card_ad1816a_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_ad1816a_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 ad1816a_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "ad1816a", + .id_table = snd_ad1816a_pnpids, + .probe = snd_ad1816a_pnp_detect, + .remove = __devexit_p(snd_ad1816a_pnp_remove), +}; + +static int __init alsa_card_ad1816a_init(void) +{ + int cards = 0; + + cards += pnp_register_card_driver(&ad1816a_pnpc_driver); +#ifdef MODULE + if (!cards) { + pnp_unregister_card_driver(&ad1816a_pnpc_driver); + printk(KERN_ERR "no AD1816A based soundcards found.\n"); + } +#endif /* MODULE */ + return cards ? 0 : -ENODEV; +} + +static void __exit alsa_card_ad1816a_exit(void) +{ + pnp_unregister_card_driver(&ad1816a_pnpc_driver); +} + +module_init(alsa_card_ad1816a_init) +module_exit(alsa_card_ad1816a_exit) diff --git a/sound/isa/ad1816a/ad1816a_lib.c b/sound/isa/ad1816a/ad1816a_lib.c new file mode 100644 index 00000000000..625b2eff14a --- /dev/null +++ b/sound/isa/ad1816a/ad1816a_lib.c @@ -0,0 +1,974 @@ + +/* + ad1816a.c - lowlevel code for Analog Devices AD1816A chip. + Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it> + + 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/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/ad1816a.h> + +#include <asm/io.h> +#include <asm/dma.h> + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("lowlevel code for Analog Devices AD1816A chip"); +MODULE_LICENSE("GPL"); + +static inline int snd_ad1816a_busy_wait(ad1816a_t *chip) +{ + int timeout; + + for (timeout = 1000; timeout-- > 0; udelay(10)) + if (inb(AD1816A_REG(AD1816A_CHIP_STATUS)) & AD1816A_READY) + return 0; + + snd_printk("chip busy.\n"); + return -EBUSY; +} + +static inline unsigned char snd_ad1816a_in(ad1816a_t *chip, unsigned char reg) +{ + snd_ad1816a_busy_wait(chip); + return inb(AD1816A_REG(reg)); +} + +static inline void snd_ad1816a_out(ad1816a_t *chip, unsigned char reg, + unsigned char value) +{ + snd_ad1816a_busy_wait(chip); + outb(value, AD1816A_REG(reg)); +} + +static inline void snd_ad1816a_out_mask(ad1816a_t *chip, unsigned char reg, + unsigned char mask, unsigned char value) +{ + snd_ad1816a_out(chip, reg, + (value & mask) | (snd_ad1816a_in(chip, reg) & ~mask)); +} + +static unsigned short snd_ad1816a_read(ad1816a_t *chip, unsigned char reg) +{ + snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f); + return snd_ad1816a_in(chip, AD1816A_INDIR_DATA_LOW) | + (snd_ad1816a_in(chip, AD1816A_INDIR_DATA_HIGH) << 8); +} + +static void snd_ad1816a_write(ad1816a_t *chip, unsigned char reg, + unsigned short value) +{ + snd_ad1816a_out(chip, AD1816A_INDIR_ADDR, reg & 0x3f); + snd_ad1816a_out(chip, AD1816A_INDIR_DATA_LOW, value & 0xff); + snd_ad1816a_out(chip, AD1816A_INDIR_DATA_HIGH, (value >> 8) & 0xff); +} + +static void snd_ad1816a_write_mask(ad1816a_t *chip, unsigned char reg, + unsigned short mask, unsigned short value) +{ + snd_ad1816a_write(chip, reg, + (value & mask) | (snd_ad1816a_read(chip, reg) & ~mask)); +} + + +static unsigned char snd_ad1816a_get_format(ad1816a_t *chip, + unsigned int format, int channels) +{ + unsigned char retval = AD1816A_FMT_LINEAR_8; + + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + retval = AD1816A_FMT_ULAW_8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + retval = AD1816A_FMT_ALAW_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + retval = AD1816A_FMT_LINEAR_16_LIT; + break; + case SNDRV_PCM_FORMAT_S16_BE: + retval = AD1816A_FMT_LINEAR_16_BIG; + } + return (channels > 1) ? (retval | AD1816A_FMT_STEREO) : retval; +} + +static int snd_ad1816a_open(ad1816a_t *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + if (chip->mode & mode) { + spin_unlock_irqrestore(&chip->lock, flags); + return -EAGAIN; + } + + switch ((mode &= AD1816A_MODE_OPEN)) { + case AD1816A_MODE_PLAYBACK: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_PLAYBACK_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_PLAYBACK_IRQ_ENABLE, 0xffff); + break; + case AD1816A_MODE_CAPTURE: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_CAPTURE_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_CAPTURE_IRQ_ENABLE, 0xffff); + break; + case AD1816A_MODE_TIMER: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_TIMER_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_IRQ_ENABLE, 0xffff); + } + chip->mode |= mode; + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static void snd_ad1816a_close(ad1816a_t *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + switch ((mode &= AD1816A_MODE_OPEN)) { + case AD1816A_MODE_PLAYBACK: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_PLAYBACK_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_PLAYBACK_IRQ_ENABLE, 0x0000); + break; + case AD1816A_MODE_CAPTURE: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_CAPTURE_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_CAPTURE_IRQ_ENABLE, 0x0000); + break; + case AD1816A_MODE_TIMER: + snd_ad1816a_out_mask(chip, AD1816A_INTERRUPT_STATUS, + AD1816A_TIMER_IRQ_PENDING, 0x00); + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_IRQ_ENABLE, 0x0000); + } + if (!((chip->mode &= ~mode) & AD1816A_MODE_OPEN)) + chip->mode = 0; + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +static int snd_ad1816a_trigger(ad1816a_t *chip, unsigned char what, + int channel, int cmd) +{ + int error = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + spin_lock(&chip->lock); + cmd = (cmd == SNDRV_PCM_TRIGGER_START) ? 0xff: 0x00; + if (what & AD1816A_PLAYBACK_ENABLE) + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE, cmd); + if (what & AD1816A_CAPTURE_ENABLE) + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE, cmd); + spin_unlock(&chip->lock); + break; + default: + snd_printk("invalid trigger mode 0x%x.\n", what); + error = -EINVAL; + } + + return error; +} + +static int snd_ad1816a_playback_trigger(snd_pcm_substream_t *substream, int cmd) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + return snd_ad1816a_trigger(chip, AD1816A_PLAYBACK_ENABLE, + SNDRV_PCM_STREAM_PLAYBACK, cmd); +} + +static int snd_ad1816a_capture_trigger(snd_pcm_substream_t *substream, int cmd) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + return snd_ad1816a_trigger(chip, AD1816A_CAPTURE_ENABLE, + SNDRV_PCM_STREAM_CAPTURE, cmd); +} + +static int snd_ad1816a_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_ad1816a_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_ad1816a_playback_prepare(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size; + + spin_lock_irqsave(&chip->lock, flags); + + chip->p_dma_size = size = snd_pcm_lib_buffer_bytes(substream); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00); + + snd_dma_program(chip->dma1, runtime->dma_addr, size, + DMA_MODE_WRITE | DMA_AUTOINIT); + + snd_ad1816a_write(chip, AD1816A_PLAYBACK_SAMPLE_RATE, runtime->rate); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_FMT_ALL | AD1816A_FMT_STEREO, + snd_ad1816a_get_format(chip, runtime->format, + runtime->channels)); + + snd_ad1816a_write(chip, AD1816A_PLAYBACK_BASE_COUNT, + snd_pcm_lib_period_bytes(substream) / 4 - 1); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_capture_prepare(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size; + + spin_lock_irqsave(&chip->lock, flags); + + chip->c_dma_size = size = snd_pcm_lib_buffer_bytes(substream); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00); + + snd_dma_program(chip->dma2, runtime->dma_addr, size, + DMA_MODE_READ | DMA_AUTOINIT); + + snd_ad1816a_write(chip, AD1816A_CAPTURE_SAMPLE_RATE, runtime->rate); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_FMT_ALL | AD1816A_FMT_STEREO, + snd_ad1816a_get_format(chip, runtime->format, + runtime->channels)); + + snd_ad1816a_write(chip, AD1816A_CAPTURE_BASE_COUNT, + snd_pcm_lib_period_bytes(substream) / 4 - 1); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + + +static snd_pcm_uframes_t snd_ad1816a_playback_pointer(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + if (!(chip->mode & AD1816A_MODE_PLAYBACK)) + 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_ad1816a_capture_pointer(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + if (!(chip->mode & AD1816A_MODE_CAPTURE)) + return 0; + ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + + +static irqreturn_t snd_ad1816a_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + ad1816a_t *chip = dev_id; + unsigned char status; + + spin_lock(&chip->lock); + status = snd_ad1816a_in(chip, AD1816A_INTERRUPT_STATUS); + spin_unlock(&chip->lock); + + if ((status & AD1816A_PLAYBACK_IRQ_PENDING) && chip->playback_substream) + snd_pcm_period_elapsed(chip->playback_substream); + + if ((status & AD1816A_CAPTURE_IRQ_PENDING) && chip->capture_substream) + snd_pcm_period_elapsed(chip->capture_substream); + + if ((status & AD1816A_TIMER_IRQ_PENDING) && chip->timer) + snd_timer_interrupt(chip->timer, chip->timer->sticks); + + spin_lock(&chip->lock); + snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00); + spin_unlock(&chip->lock); + return IRQ_HANDLED; +} + + +static snd_pcm_hardware_t snd_ad1816a_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 55200, + .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_ad1816a_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S16_BE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 55200, + .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, +}; + +#if 0 /* not used now */ +static int snd_ad1816a_timer_close(snd_timer_t *timer) +{ + ad1816a_t *chip = snd_timer_chip(timer); + snd_ad1816a_close(chip, AD1816A_MODE_TIMER); + return 0; +} + +static int snd_ad1816a_timer_open(snd_timer_t *timer) +{ + ad1816a_t *chip = snd_timer_chip(timer); + snd_ad1816a_open(chip, AD1816A_MODE_TIMER); + return 0; +} + +static unsigned long snd_ad1816a_timer_resolution(snd_timer_t *timer) +{ + snd_assert(timer != NULL, return 0); + + return 10000; +} + +static int snd_ad1816a_timer_start(snd_timer_t *timer) +{ + unsigned short bits; + unsigned long flags; + ad1816a_t *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->lock, flags); + bits = snd_ad1816a_read(chip, AD1816A_INTERRUPT_ENABLE); + + if (!(bits & AD1816A_TIMER_ENABLE)) { + snd_ad1816a_write(chip, AD1816A_TIMER_BASE_COUNT, + timer->sticks & 0xffff); + + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_ENABLE, 0xffff); + } + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_timer_stop(snd_timer_t *timer) +{ + unsigned long flags; + ad1816a_t *chip = snd_timer_chip(timer); + spin_lock_irqsave(&chip->lock, flags); + + snd_ad1816a_write_mask(chip, AD1816A_INTERRUPT_ENABLE, + AD1816A_TIMER_ENABLE, 0x0000); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static struct _snd_timer_hardware snd_ad1816a_timer_table = { + .flags = SNDRV_TIMER_HW_AUTO, + .resolution = 10000, + .ticks = 65535, + .open = snd_ad1816a_timer_open, + .close = snd_ad1816a_timer_close, + .c_resolution = snd_ad1816a_timer_resolution, + .start = snd_ad1816a_timer_start, + .stop = snd_ad1816a_timer_stop, +}; +#endif /* not used now */ + + +static int snd_ad1816a_playback_open(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int error; + + if ((error = snd_ad1816a_open(chip, AD1816A_MODE_PLAYBACK)) < 0) + return error; + snd_pcm_set_sync(substream); + runtime->hw = snd_ad1816a_playback; + 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); + chip->playback_substream = substream; + return 0; +} + +static int snd_ad1816a_capture_open(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int error; + + if ((error = snd_ad1816a_open(chip, AD1816A_MODE_CAPTURE)) < 0) + return error; + snd_pcm_set_sync(substream); + runtime->hw = snd_ad1816a_capture; + 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); + chip->capture_substream = substream; + return 0; +} + +static int snd_ad1816a_playback_close(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + snd_ad1816a_close(chip, AD1816A_MODE_PLAYBACK); + return 0; +} + +static int snd_ad1816a_capture_close(snd_pcm_substream_t *substream) +{ + ad1816a_t *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + snd_ad1816a_close(chip, AD1816A_MODE_CAPTURE); + return 0; +} + + +static void snd_ad1816a_init(ad1816a_t *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + snd_ad1816a_out(chip, AD1816A_INTERRUPT_STATUS, 0x00); + snd_ad1816a_out_mask(chip, AD1816A_PLAYBACK_CONFIG, + AD1816A_PLAYBACK_ENABLE | AD1816A_PLAYBACK_PIO, 0x00); + snd_ad1816a_out_mask(chip, AD1816A_CAPTURE_CONFIG, + AD1816A_CAPTURE_ENABLE | AD1816A_CAPTURE_PIO, 0x00); + snd_ad1816a_write(chip, AD1816A_INTERRUPT_ENABLE, 0x0000); + snd_ad1816a_write_mask(chip, AD1816A_CHIP_CONFIG, + AD1816A_CAPTURE_NOT_EQUAL | AD1816A_WSS_ENABLE, 0xffff); + snd_ad1816a_write(chip, AD1816A_DSP_CONFIG, 0x0000); + snd_ad1816a_write(chip, AD1816A_POWERDOWN_CTRL, 0x0000); + + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int snd_ad1816a_probe(ad1816a_t *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + switch (chip->version = snd_ad1816a_read(chip, AD1816A_VERSION_ID)) { + case 0: + chip->hardware = AD1816A_HW_AD1815; + break; + case 1: + chip->hardware = AD1816A_HW_AD18MAX10; + break; + case 3: + chip->hardware = AD1816A_HW_AD1816A; + break; + default: + chip->hardware = AD1816A_HW_AUTO; + } + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_ad1816a_free(ad1816a_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma1 >= 0) { + snd_dma_disable(chip->dma1); + free_dma(chip->dma1); + } + if (chip->dma2 >= 0) { + snd_dma_disable(chip->dma2); + free_dma(chip->dma2); + } + kfree(chip); + return 0; +} + +static int snd_ad1816a_dev_free(snd_device_t *device) +{ + ad1816a_t *chip = device->device_data; + return snd_ad1816a_free(chip); +} + +static const char *snd_ad1816a_chip_id(ad1816a_t *chip) +{ + switch (chip->hardware) { + case AD1816A_HW_AD1816A: return "AD1816A"; + case AD1816A_HW_AD1815: return "AD1815"; + case AD1816A_HW_AD18MAX10: return "AD18max10"; + default: + snd_printk("Unknown chip version %d:%d.\n", + chip->version, chip->hardware); + return "AD1816A - unknown"; + } +} + +int snd_ad1816a_create(snd_card_t *card, + unsigned long port, int irq, int dma1, int dma2, + ad1816a_t **rchip) +{ + static snd_device_ops_t ops = { + .dev_free = snd_ad1816a_dev_free, + }; + int error; + ad1816a_t *chip; + + *rchip = NULL; + + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + + if ((chip->res_port = request_region(port, 16, "AD1816A")) == NULL) { + snd_printk(KERN_ERR "ad1816a: can't grab port 0x%lx\n", port); + snd_ad1816a_free(chip); + return -EBUSY; + } + if (request_irq(irq, snd_ad1816a_interrupt, SA_INTERRUPT, "AD1816A", (void *) chip)) { + snd_printk(KERN_ERR "ad1816a: can't grab IRQ %d\n", irq); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (request_dma(dma1, "AD1816A - 1")) { + snd_printk(KERN_ERR "ad1816a: can't grab DMA1 %d\n", dma1); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->dma1 = dma1; + if (request_dma(dma2, "AD1816A - 2")) { + snd_printk(KERN_ERR "ad1816a: can't grab DMA2 %d\n", dma2); + snd_ad1816a_free(chip); + return -EBUSY; + } + chip->dma2 = dma2; + + chip->card = card; + chip->port = port; + spin_lock_init(&chip->lock); + + if ((error = snd_ad1816a_probe(chip))) { + snd_ad1816a_free(chip); + return error; + } + + snd_ad1816a_init(chip); + + /* Register device */ + if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_ad1816a_free(chip); + return error; + } + + *rchip = chip; + return 0; +} + +static snd_pcm_ops_t snd_ad1816a_playback_ops = { + .open = snd_ad1816a_playback_open, + .close = snd_ad1816a_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1816a_hw_params, + .hw_free = snd_ad1816a_hw_free, + .prepare = snd_ad1816a_playback_prepare, + .trigger = snd_ad1816a_playback_trigger, + .pointer = snd_ad1816a_playback_pointer, +}; + +static snd_pcm_ops_t snd_ad1816a_capture_ops = { + .open = snd_ad1816a_capture_open, + .close = snd_ad1816a_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ad1816a_hw_params, + .hw_free = snd_ad1816a_hw_free, + .prepare = snd_ad1816a_capture_prepare, + .trigger = snd_ad1816a_capture_trigger, + .pointer = snd_ad1816a_capture_pointer, +}; + +static void snd_ad1816a_pcm_free(snd_pcm_t *pcm) +{ + ad1816a_t *chip = pcm->private_data; + chip->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_ad1816a_pcm(ad1816a_t *chip, int device, snd_pcm_t **rpcm) +{ + int error; + snd_pcm_t *pcm; + + if ((error = snd_pcm_new(chip->card, "AD1816A", device, 1, 1, &pcm))) + return error; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1816a_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1816a_capture_ops); + + pcm->private_data = chip; + pcm->private_free = snd_ad1816a_pcm_free; + pcm->info_flags = (chip->dma1 == chip->dma2 ) ? SNDRV_PCM_INFO_JOINT_DUPLEX : 0; + + strcpy(pcm->name, snd_ad1816a_chip_id(chip)); + snd_ad1816a_init(chip); + + 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); + + chip->pcm = pcm; + if (rpcm) + *rpcm = pcm; + return 0; +} + +#if 0 /* not used now */ +static void snd_ad1816a_timer_free(snd_timer_t *timer) +{ + ad1816a_t *chip = timer->private_data; + chip->timer = NULL; +} + +int snd_ad1816a_timer(ad1816a_t *chip, int device, snd_timer_t **rtimer) +{ + snd_timer_t *timer; + snd_timer_id_t tid; + int error; + + 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 ((error = snd_timer_new(chip->card, "AD1816A", &tid, &timer)) < 0) + return error; + strcpy(timer->name, snd_ad1816a_chip_id(chip)); + timer->private_data = chip; + timer->private_free = snd_ad1816a_timer_free; + chip->timer = timer; + timer->hw = snd_ad1816a_timer_table; + if (rtimer) + *rtimer = timer; + return 0; +} +#endif /* not used now */ + +/* + * + */ + +static int snd_ad1816a_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[8] = { + "Line", "Mix", "CD", "Synth", "Video", + "Mic", "Phone", + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 2; + uinfo->value.enumerated.items = 7; + if (uinfo->value.enumerated.item > 6) + uinfo->value.enumerated.item = 6; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_ad1816a_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short val; + + spin_lock_irqsave(&chip->lock, flags); + val = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL); + spin_unlock_irqrestore(&chip->lock, flags); + ucontrol->value.enumerated.item[0] = (val >> 12) & 7; + ucontrol->value.enumerated.item[1] = (val >> 4) & 7; + return 0; +} + +static int snd_ad1816a_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned short val; + int change; + + if (ucontrol->value.enumerated.item[0] > 6 || + ucontrol->value.enumerated.item[1] > 6) + return -EINVAL; + val = (ucontrol->value.enumerated.item[0] << 12) | + (ucontrol->value.enumerated.item[1] << 4); + spin_lock_irqsave(&chip->lock, flags); + change = snd_ad1816a_read(chip, AD1816A_ADC_SOURCE_SEL) != val; + snd_ad1816a_write(chip, AD1816A_ADC_SOURCE_SEL, val); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#define AD1816A_SINGLE(xname, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_single, \ + .get = snd_ad1816a_get_single, .put = snd_ad1816a_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_ad1816a_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_ad1816a_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_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->lock, flags); + ucontrol->value.integer.value[0] = (snd_ad1816a_read(chip, reg) >> shift) & mask; + spin_unlock_irqrestore(&chip->lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_ad1816a_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_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 old_val, val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->lock, flags); + old_val = snd_ad1816a_read(chip, reg); + val = (old_val & ~(mask << shift)) | val; + change = val != old_val; + snd_ad1816a_write(chip, reg, val); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#define AD1816A_DOUBLE(xname, reg, shift_left, shift_right, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .info = snd_ad1816a_info_double, \ + .get = snd_ad1816a_get_double, .put = snd_ad1816a_put_double, \ + .private_value = reg | (shift_left << 8) | (shift_right << 12) | (mask << 16) | (invert << 24) } + +static int snd_ad1816a_info_double(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 = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_ad1816a_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift_left = (kcontrol->private_value >> 8) & 0x0f; + int shift_right = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + unsigned short val; + + spin_lock_irqsave(&chip->lock, flags); + val = snd_ad1816a_read(chip, reg); + ucontrol->value.integer.value[0] = (val >> shift_left) & mask; + ucontrol->value.integer.value[1] = (val >> shift_right) & mask; + spin_unlock_irqrestore(&chip->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_ad1816a_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1816a_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift_left = (kcontrol->private_value >> 8) & 0x0f; + int shift_right = (kcontrol->private_value >> 12) & 0x0f; + int mask = (kcontrol->private_value >> 16) & 0xff; + int invert = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned short old_val, 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->lock, flags); + old_val = snd_ad1816a_read(chip, reg); + val1 = (old_val & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != old_val; + snd_ad1816a_write(chip, reg, val1); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_ad1816a_controls[] = { +AD1816A_DOUBLE("Master Playback Switch", AD1816A_MASTER_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("Master Playback Volume", AD1816A_MASTER_ATT, 8, 0, 31, 1), +AD1816A_DOUBLE("PCM Playback Switch", AD1816A_VOICE_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("PCM Playback Volume", AD1816A_VOICE_ATT, 8, 0, 63, 1), +AD1816A_DOUBLE("Line Playback Switch", AD1816A_LINE_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("Line Playback Volume", AD1816A_LINE_GAIN_ATT, 8, 0, 31, 1), +AD1816A_DOUBLE("CD Playback Switch", AD1816A_CD_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("CD Playback Volume", AD1816A_CD_GAIN_ATT, 8, 0, 31, 1), +AD1816A_DOUBLE("Synth Playback Switch", AD1816A_SYNTH_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("Synth Playback Volume", AD1816A_SYNTH_GAIN_ATT, 8, 0, 31, 1), +AD1816A_DOUBLE("FM Playback Switch", AD1816A_FM_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("FM Playback Volume", AD1816A_FM_ATT, 8, 0, 63, 1), +AD1816A_SINGLE("Mic Playback Switch", AD1816A_MIC_GAIN_ATT, 15, 1, 1), +AD1816A_SINGLE("Mic Playback Volume", AD1816A_MIC_GAIN_ATT, 8, 31, 1), +AD1816A_SINGLE("Mic Boost", AD1816A_MIC_GAIN_ATT, 14, 1, 0), +AD1816A_DOUBLE("Video Playback Switch", AD1816A_VID_GAIN_ATT, 15, 7, 1, 1), +AD1816A_DOUBLE("Video Playback Volume", AD1816A_VID_GAIN_ATT, 8, 0, 31, 1), +AD1816A_SINGLE("Phone Capture Switch", AD1816A_PHONE_IN_GAIN_ATT, 15, 1, 1), +AD1816A_SINGLE("Phone Capture Volume", AD1816A_PHONE_IN_GAIN_ATT, 0, 15, 1), +AD1816A_SINGLE("Phone Playback Switch", AD1816A_PHONE_OUT_ATT, 7, 1, 1), +AD1816A_SINGLE("Phone Playback Volume", AD1816A_PHONE_OUT_ATT, 0, 31, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_ad1816a_info_mux, + .get = snd_ad1816a_get_mux, + .put = snd_ad1816a_put_mux, +}, +AD1816A_DOUBLE("Capture Switch", AD1816A_ADC_PGA, 15, 7, 1, 1), +AD1816A_DOUBLE("Capture Volume", AD1816A_ADC_PGA, 8, 0, 15, 0), +AD1816A_SINGLE("3D Control - Switch", AD1816A_3D_PHAT_CTRL, 15, 1, 1), +AD1816A_SINGLE("3D Control - Level", AD1816A_3D_PHAT_CTRL, 0, 15, 0), +}; + +int snd_ad1816a_mixer(ad1816a_t *chip) +{ + snd_card_t *card; + unsigned int idx; + int err; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + + card = chip->card; + + strcpy(card->mixername, snd_ad1816a_chip_id(chip)); + + for (idx = 0; idx < ARRAY_SIZE(snd_ad1816a_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ad1816a_controls[idx], chip))) < 0) + return err; + } + return 0; +} + +EXPORT_SYMBOL(snd_ad1816a_create); +EXPORT_SYMBOL(snd_ad1816a_pcm); +EXPORT_SYMBOL(snd_ad1816a_mixer); + +static int __init alsa_ad1816a_init(void) +{ + return 0; +} + +static void __exit alsa_ad1816a_exit(void) +{ +} + +module_init(alsa_ad1816a_init) +module_exit(alsa_ad1816a_exit) diff --git a/sound/isa/ad1848/Makefile b/sound/isa/ad1848/Makefile new file mode 100644 index 00000000000..45d59998aa6 --- /dev/null +++ b/sound/isa/ad1848/Makefile @@ -0,0 +1,15 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-ad1848-lib-objs := ad1848_lib.o +snd-ad1848-objs := ad1848.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_CMI8330) += snd-ad1848-lib.o +obj-$(CONFIG_SND_SGALAXY) += snd-ad1848-lib.o +obj-$(CONFIG_SND_AD1848) += snd-ad1848.o snd-ad1848-lib.o +obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-ad1848-lib.o + +obj-m := $(sort $(obj-m)) diff --git a/sound/isa/ad1848/ad1848.c b/sound/isa/ad1848/ad1848.c new file mode 100644 index 00000000000..8c399340cd7 --- /dev/null +++ b/sound/isa/ad1848/ad1848.c @@ -0,0 +1,151 @@ +/* + * Generic driver for AD1848/AD1847/CS4248 chips (0.1 Alpha) + * Copyright (c) by Tugrul Galatali <galatalt@stuy.edu>, + * Jaroslav Kysela <perex@suse.cz> + * Based on card-4232.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/time.h> +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/ad1848.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Tugrul Galatali <galatalt@stuy.edu>, Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("AD1848/AD1847/CS4248"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Analog Devices,AD1848}," + "{Analog Devices,AD1847}," + "{Crystal Semiconductors,CS4248}}"); + +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 int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3,5,6,7 */ +static int thinkpad[SNDRV_CARDS]; /* Thinkpad special case */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for AD1848 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for AD1848 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable AD1848 soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for AD1848 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for AD1848 driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for AD1848 driver."); +module_param_array(thinkpad, bool, NULL, 0444); +MODULE_PARM_DESC(thinkpad, "Enable only for the onboard CS4248 of IBM Thinkpad 360/750/755 series."); + +static snd_card_t *snd_ad1848_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_card_ad1848_probe(int dev) +{ + snd_card_t *card; + ad1848_t *chip; + snd_pcm_t *pcm; + int err; + + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk(KERN_ERR "ad1848: specify port\n"); + return -EINVAL; + } + if (irq[dev] == SNDRV_AUTO_IRQ) { + snd_printk(KERN_ERR "ad1848: specify irq\n"); + return -EINVAL; + } + if (dma1[dev] == SNDRV_AUTO_DMA) { + snd_printk(KERN_ERR "ad1848: specify dma1\n"); + return -EINVAL; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + if ((err = snd_ad1848_create(card, port[dev], + irq[dev], + dma1[dev], + thinkpad[dev] ? AD1848_HW_THINKPAD : AD1848_HW_DETECT, + &chip)) < 0) { + snd_card_free(card); + return err; + } + + if ((err = snd_ad1848_pcm(chip, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_ad1848_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + strcpy(card->driver, "AD1848"); + 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 (thinkpad[dev]) { + strcat(card->longname, " [Thinkpad]"); + } + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_ad1848_cards[dev] = card; + return 0; +} + +static int __init alsa_card_ad1848_init(void) +{ + int dev, cards; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) + if (snd_card_ad1848_probe(dev) >= 0) + cards++; + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "AD1848 soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_ad1848_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_ad1848_cards[idx]); +} + +module_init(alsa_card_ad1848_init) +module_exit(alsa_card_ad1848_exit) diff --git a/sound/isa/ad1848/ad1848_lib.c b/sound/isa/ad1848/ad1848_lib.c new file mode 100644 index 00000000000..8fb3db103e4 --- /dev/null +++ b/sound/isa/ad1848/ad1848_lib.c @@ -0,0 +1,1279 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of AD1848/AD1847/CS4248 + * + * + * 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 + * + */ + +#define SNDRV_MAIN_OBJECT_FILE +#include <sound/driver.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/ad1848.h> +#include <sound/control.h> +#include <sound/pcm_params.h> + +#include <asm/io.h> +#include <asm/dma.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for control of AD1848/AD1847/CS4248"); +MODULE_LICENSE("GPL"); + +#if 0 +#define SNDRV_DEBUG_MCE +#endif + +/* + * Some variables + */ + +static unsigned char freq_bits[14] = { + /* 5510 */ 0x00 | AD1848_XTAL2, + /* 6620 */ 0x0E | AD1848_XTAL2, + /* 8000 */ 0x00 | AD1848_XTAL1, + /* 9600 */ 0x0E | AD1848_XTAL1, + /* 11025 */ 0x02 | AD1848_XTAL2, + /* 16000 */ 0x02 | AD1848_XTAL1, + /* 18900 */ 0x04 | AD1848_XTAL2, + /* 22050 */ 0x06 | AD1848_XTAL2, + /* 27042 */ 0x04 | AD1848_XTAL1, + /* 32000 */ 0x06 | AD1848_XTAL1, + /* 33075 */ 0x0C | AD1848_XTAL2, + /* 37800 */ 0x08 | AD1848_XTAL2, + /* 44100 */ 0x0A | AD1848_XTAL2, + /* 48000 */ 0x0C | AD1848_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 unsigned char snd_ad1848_original_image[16] = +{ + 0x00, /* 00 - lic */ + 0x00, /* 01 - ric */ + 0x9f, /* 02 - la1ic */ + 0x9f, /* 03 - ra1ic */ + 0x9f, /* 04 - la2ic */ + 0x9f, /* 05 - ra2ic */ + 0xbf, /* 06 - loc */ + 0xbf, /* 07 - roc */ + 0x20, /* 08 - dfr */ + AD1848_AUTOCALIB, /* 09 - ic */ + 0x00, /* 0a - pc */ + 0x00, /* 0b - ti */ + 0x00, /* 0c - mi */ + 0x00, /* 0d - lbc */ + 0x00, /* 0e - dru */ + 0x00, /* 0f - drl */ +}; + +/* + * Basic I/O functions + */ + +void snd_ad1848_out(ad1848_t *chip, + unsigned char reg, + unsigned char value) +{ + int timeout; + + for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) + snd_printk("auto calibration time out - reg = 0x%x, value = 0x%x\n", reg, value); +#endif + outb(chip->mce_bit | reg, AD1848P(chip, REGSEL)); + outb(chip->image[reg] = value, AD1848P(chip, REG)); + mb(); +#if 0 + printk("codec out - reg 0x%x = 0x%x\n", chip->mce_bit | reg, value); +#endif +} + +static void snd_ad1848_dout(ad1848_t *chip, + unsigned char reg, unsigned char value) +{ + int timeout; + + for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--) + udelay(100); + outb(chip->mce_bit | reg, AD1848P(chip, REGSEL)); + outb(value, AD1848P(chip, REG)); + mb(); +} + +static unsigned char snd_ad1848_in(ad1848_t *chip, unsigned char reg) +{ + int timeout; + + for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) + snd_printk("auto calibration time out - reg = 0x%x\n", reg); +#endif + outb(chip->mce_bit | reg, AD1848P(chip, REGSEL)); + mb(); + return inb(AD1848P(chip, REG)); +} + +#if 0 + +static void snd_ad1848_debug(ad1848_t *chip) +{ + printk("AD1848 REGS: INDEX = 0x%02x ", inb(AD1848P(chip, REGSEL))); + printk(" STATUS = 0x%02x\n", inb(AD1848P(chip, STATUS))); + printk(" 0x00: left input = 0x%02x ", snd_ad1848_in(chip, 0x00)); + printk(" 0x08: playback format = 0x%02x\n", snd_ad1848_in(chip, 0x08)); + printk(" 0x01: right input = 0x%02x ", snd_ad1848_in(chip, 0x01)); + printk(" 0x09: iface (CFIG 1) = 0x%02x\n", snd_ad1848_in(chip, 0x09)); + printk(" 0x02: AUXA left = 0x%02x ", snd_ad1848_in(chip, 0x02)); + printk(" 0x0a: pin control = 0x%02x\n", snd_ad1848_in(chip, 0x0a)); + printk(" 0x03: AUXA right = 0x%02x ", snd_ad1848_in(chip, 0x03)); + printk(" 0x0b: init & status = 0x%02x\n", snd_ad1848_in(chip, 0x0b)); + printk(" 0x04: AUXB left = 0x%02x ", snd_ad1848_in(chip, 0x04)); + printk(" 0x0c: revision & mode = 0x%02x\n", snd_ad1848_in(chip, 0x0c)); + printk(" 0x05: AUXB right = 0x%02x ", snd_ad1848_in(chip, 0x05)); + printk(" 0x0d: loopback = 0x%02x\n", snd_ad1848_in(chip, 0x0d)); + printk(" 0x06: left output = 0x%02x ", snd_ad1848_in(chip, 0x06)); + printk(" 0x0e: data upr count = 0x%02x\n", snd_ad1848_in(chip, 0x0e)); + printk(" 0x07: right output = 0x%02x ", snd_ad1848_in(chip, 0x07)); + printk(" 0x0f: data lwr count = 0x%02x\n", snd_ad1848_in(chip, 0x0f)); +} + +#endif + +/* + * AD1848 detection / MCE routines + */ + +static void snd_ad1848_mce_up(ad1848_t *chip) +{ + unsigned long flags; + int timeout; + + for (timeout = 250; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--) + udelay(100); +#ifdef CONFIG_SND_DEBUG + if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) + snd_printk("mce_up - auto calibration time out (0)\n"); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + chip->mce_bit |= AD1848_MCE; + timeout = inb(AD1848P(chip, REGSEL)); + if (timeout == 0x80) + snd_printk("mce_up [0x%lx]: serious init problem - codec still busy\n", chip->port); + if (!(timeout & AD1848_MCE)) + outb(chip->mce_bit | (timeout & 0x1f), AD1848P(chip, REGSEL)); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_ad1848_mce_down(ad1848_t *chip) +{ + unsigned long flags; + int timeout; + signed long time; + + spin_lock_irqsave(&chip->reg_lock, flags); + for (timeout = 5; timeout > 0; timeout--) + inb(AD1848P(chip, REGSEL)); + /* end of cleanup sequence */ + for (timeout = 12000; timeout > 0 && (inb(AD1848P(chip, REGSEL)) & AD1848_INIT); timeout--) + udelay(100); +#if 0 + printk("(1) timeout = %i\n", timeout); +#endif +#ifdef CONFIG_SND_DEBUG + if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) + snd_printk("mce_down [0x%lx] - auto calibration time out (0)\n", AD1848P(chip, REGSEL)); +#endif + chip->mce_bit &= ~AD1848_MCE; + timeout = inb(AD1848P(chip, REGSEL)); + outb(chip->mce_bit | (timeout & 0x1f), AD1848P(chip, REGSEL)); + if (timeout == 0x80) + snd_printk("mce_down [0x%lx]: serious init problem - codec still busy\n", chip->port); + if ((timeout & AD1848_MCE) == 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return; + } + /* calibration process */ + + for (timeout = 500; timeout > 0 && (snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) == 0; timeout--); + if ((snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) == 0) { + snd_printd("mce_down - auto calibration time out (1)\n"); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return; + } +#if 0 + printk("(2) timeout = %i, jiffies = %li\n", timeout, jiffies); +#endif + time = HZ / 4; + while (snd_ad1848_in(chip, AD1848_TEST_INIT) & AD1848_CALIB_IN_PROGRESS) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (time <= 0) { + snd_printk("mce_down - auto calibration time out (2)\n"); + return; + } + set_current_state(TASK_INTERRUPTIBLE); + time = schedule_timeout(time); + spin_lock_irqsave(&chip->reg_lock, flags); + } +#if 0 + printk("(3) jiffies = %li\n", jiffies); +#endif + time = HZ / 10; + while (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (time <= 0) { + snd_printk("mce_down - auto calibration time out (3)\n"); + return; + } + set_current_state(TASK_INTERRUPTIBLE); + time = schedule_timeout(time); + spin_lock_irqsave(&chip->reg_lock, flags); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +#if 0 + printk("(4) jiffies = %li\n", jiffies); + snd_printk("mce_down - exit = 0x%x\n", inb(AD1848P(chip, REGSEL))); +#endif +} + +static unsigned int snd_ad1848_get_count(unsigned char format, + unsigned int size) +{ + switch (format & 0xe0) { + case AD1848_LINEAR_16: + size >>= 1; + break; + } + if (format & AD1848_STEREO) + size >>= 1; + return size; +} + +static int snd_ad1848_trigger(ad1848_t *chip, unsigned char what, + int channel, int cmd) +{ + int result = 0; + +#if 0 + printk("codec trigger!!! - what = %i, enable = %i, status = 0x%x\n", what, enable, inb(AD1848P(card, STATUS))); +#endif + spin_lock(&chip->reg_lock); + if (cmd == SNDRV_PCM_TRIGGER_START) { + if (chip->image[AD1848_IFACE_CTRL] & what) { + spin_unlock(&chip->reg_lock); + return 0; + } + snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL] |= what); + chip->mode |= AD1848_MODE_RUNNING; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + if (!(chip->image[AD1848_IFACE_CTRL] & what)) { + spin_unlock(&chip->reg_lock); + return 0; + } + snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL] &= ~what); + chip->mode &= ~AD1848_MODE_RUNNING; + } else { + result = -EINVAL; + } + spin_unlock(&chip->reg_lock); + return result; +} + +/* + * CODEC I/O + */ + +static unsigned char snd_ad1848_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 int snd_ad1848_ioctl(snd_pcm_substream_t * substream, + unsigned int cmd, void *arg) +{ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static unsigned char snd_ad1848_get_format(int format, int channels) +{ + unsigned char rformat; + + rformat = AD1848_LINEAR_8; + switch (format) { + case SNDRV_PCM_FORMAT_A_LAW: rformat = AD1848_ALAW_8; break; + case SNDRV_PCM_FORMAT_MU_LAW: rformat = AD1848_ULAW_8; break; + case SNDRV_PCM_FORMAT_S16_LE: rformat = AD1848_LINEAR_16; break; + } + if (channels > 1) + rformat |= AD1848_STEREO; +#if 0 + snd_printk("get_format: 0x%x (mode=0x%x)\n", format, mode); +#endif + return rformat; +} + +static void snd_ad1848_calibrate_mute(ad1848_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_ad1848_dout(chip, AD1848_LEFT_INPUT, chip->image[AD1848_LEFT_INPUT]); + snd_ad1848_dout(chip, AD1848_RIGHT_INPUT, chip->image[AD1848_RIGHT_INPUT]); + } + snd_ad1848_dout(chip, AD1848_AUX1_LEFT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX1_LEFT_INPUT]); + snd_ad1848_dout(chip, AD1848_AUX1_RIGHT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX1_RIGHT_INPUT]); + snd_ad1848_dout(chip, AD1848_AUX2_LEFT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX2_LEFT_INPUT]); + snd_ad1848_dout(chip, AD1848_AUX2_RIGHT_INPUT, mute ? 0x80 : chip->image[AD1848_AUX2_RIGHT_INPUT]); + snd_ad1848_dout(chip, AD1848_LEFT_OUTPUT, mute ? 0x80 : chip->image[AD1848_LEFT_OUTPUT]); + snd_ad1848_dout(chip, AD1848_RIGHT_OUTPUT, mute ? 0x80 : chip->image[AD1848_RIGHT_OUTPUT]); + chip->calibrate_mute = mute; + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static void snd_ad1848_set_data_format(ad1848_t *chip, snd_pcm_hw_params_t *hw_params) +{ + if (hw_params == NULL) { + chip->image[AD1848_DATA_FORMAT] = 0x20; + } else { + chip->image[AD1848_DATA_FORMAT] = + snd_ad1848_get_format(params_format(hw_params), params_channels(hw_params)) | + snd_ad1848_get_rate(params_rate(hw_params)); + } + // snd_printk(">>> pmode = 0x%x, dfr = 0x%x\n", pstr->mode, chip->image[AD1848_DATA_FORMAT]); +} + +static int snd_ad1848_open(ad1848_t *chip, unsigned int mode) +{ + unsigned long flags; + + down(&chip->open_mutex); + if (chip->mode & AD1848_MODE_OPEN) { + up(&chip->open_mutex); + return -EAGAIN; + } + snd_ad1848_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("open: (1)\n"); +#endif + snd_ad1848_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO | + AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO | + AD1848_CALIB_MODE); + chip->image[AD1848_IFACE_CTRL] |= AD1848_AUTOCALIB; + snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("open: (2)\n"); +#endif + + snd_ad1848_set_data_format(chip, NULL); + + snd_ad1848_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_down(chip); + +#ifdef SNDRV_DEBUG_MCE + snd_printk("open: (3)\n"); +#endif + + /* ok. now enable and ack CODEC IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + chip->image[AD1848_PIN_CTRL] |= AD1848_IRQ_ENABLE; + snd_ad1848_out(chip, AD1848_PIN_CTRL, chip->image[AD1848_PIN_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->mode = mode; + up(&chip->open_mutex); + + return 0; +} + +static void snd_ad1848_close(ad1848_t *chip) +{ + unsigned long flags; + + down(&chip->open_mutex); + if (!chip->mode) { + up(&chip->open_mutex); + return; + } + /* disable IRQ */ + spin_lock_irqsave(&chip->reg_lock, flags); + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + chip->image[AD1848_PIN_CTRL] &= ~AD1848_IRQ_ENABLE; + snd_ad1848_out(chip, AD1848_PIN_CTRL, chip->image[AD1848_PIN_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + /* now disable capture & playback */ + + snd_ad1848_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO | + AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO); + snd_ad1848_out(chip, AD1848_IFACE_CTRL, chip->image[AD1848_IFACE_CTRL]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_down(chip); + + /* clear IRQ again */ + spin_lock_irqsave(&chip->reg_lock, flags); + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + outb(0, AD1848P(chip, STATUS)); /* clear IRQ */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->mode = 0; + up(&chip->open_mutex); +} + +/* + * ok.. exported functions.. + */ + +static int snd_ad1848_playback_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + return snd_ad1848_trigger(chip, AD1848_PLAYBACK_ENABLE, SNDRV_PCM_STREAM_PLAYBACK, cmd); +} + +static int snd_ad1848_capture_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + return snd_ad1848_trigger(chip, AD1848_CAPTURE_ENABLE, SNDRV_PCM_STREAM_CAPTURE, cmd); +} + +static int snd_ad1848_playback_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + snd_ad1848_calibrate_mute(chip, 1); + snd_ad1848_set_data_format(chip, hw_params); + snd_ad1848_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_down(chip); + snd_ad1848_calibrate_mute(chip, 0); + return 0; +} + +static int snd_ad1848_playback_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_ad1848_playback_prepare(snd_pcm_substream_t * substream) +{ + ad1848_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); + + chip->dma_size = size; + chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_PLAYBACK_ENABLE | AD1848_PLAYBACK_PIO); + snd_dma_program(chip->dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + count = snd_ad1848_get_count(chip->image[AD1848_DATA_FORMAT], count) - 1; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_DATA_LWR_CNT, (unsigned char) count); + snd_ad1848_out(chip, AD1848_DATA_UPR_CNT, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_ad1848_capture_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + unsigned long flags; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + snd_ad1848_calibrate_mute(chip, 1); + snd_ad1848_set_data_format(chip, hw_params); + snd_ad1848_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_DATA_FORMAT, chip->image[AD1848_DATA_FORMAT]); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_down(chip); + snd_ad1848_calibrate_mute(chip, 0); + return 0; +} + +static int snd_ad1848_capture_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_ad1848_capture_prepare(snd_pcm_substream_t * substream) +{ + ad1848_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); + + chip->dma_size = size; + chip->image[AD1848_IFACE_CTRL] &= ~(AD1848_CAPTURE_ENABLE | AD1848_CAPTURE_PIO); + snd_dma_program(chip->dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + count = snd_ad1848_get_count(chip->image[AD1848_DATA_FORMAT], count) - 1; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_DATA_LWR_CNT, (unsigned char) count); + snd_ad1848_out(chip, AD1848_DATA_UPR_CNT, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static irqreturn_t snd_ad1848_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + ad1848_t *chip = dev_id; + + if ((chip->mode & AD1848_MODE_PLAY) && chip->playback_substream && + (chip->mode & AD1848_MODE_RUNNING)) + snd_pcm_period_elapsed(chip->playback_substream); + if ((chip->mode & AD1848_MODE_CAPTURE) && chip->capture_substream && + (chip->mode & AD1848_MODE_RUNNING)) + snd_pcm_period_elapsed(chip->capture_substream); + outb(0, AD1848P(chip, STATUS)); /* clear global interrupt bit */ + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_ad1848_playback_pointer(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[AD1848_IFACE_CTRL] & AD1848_PLAYBACK_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_ad1848_capture_pointer(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[AD1848_IFACE_CTRL] & AD1848_CAPTURE_ENABLE)) + return 0; + ptr = snd_dma_pointer(chip->dma, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static void snd_ad1848_thinkpad_twiddle(ad1848_t *chip, int on) { + + int tmp; + + if (!chip->thinkpad_flag) return; + + outb(0x1c, AD1848_THINKPAD_CTL_PORT1); + tmp = inb(AD1848_THINKPAD_CTL_PORT2); + + if (on) + /* turn it on */ + tmp |= AD1848_THINKPAD_CS4248_ENABLE_BIT; + else + /* turn it off */ + tmp &= ~AD1848_THINKPAD_CS4248_ENABLE_BIT; + + outb(tmp, AD1848_THINKPAD_CTL_PORT2); + +} + +#ifdef CONFIG_PM +static int snd_ad1848_suspend(snd_card_t *card, pm_message_t state) +{ + ad1848_t *chip = card->pm_private_data; + + snd_pcm_suspend_all(chip->pcm); + /* FIXME: save registers? */ + + if (chip->thinkpad_flag) + snd_ad1848_thinkpad_twiddle(chip, 0); + + return 0; +} + +static int snd_ad1848_resume(snd_card_t *card) +{ + ad1848_t *chip = card->pm_private_data; + + if (chip->thinkpad_flag) + snd_ad1848_thinkpad_twiddle(chip, 1); + + /* FIXME: restore registers? */ + + return 0; +} +#endif /* CONFIG_PM */ + +static int snd_ad1848_probe(ad1848_t * chip) +{ + unsigned long flags; + int i, id, rev, ad1847; + unsigned char *ptr; + +#if 0 + snd_ad1848_debug(chip); +#endif + id = ad1847 = 0; + for (i = 0; i < 1000; i++) { + mb(); + if (inb(AD1848P(chip, REGSEL)) & AD1848_INIT) + udelay(500); + else { + spin_lock_irqsave(&chip->reg_lock, flags); + snd_ad1848_out(chip, AD1848_MISC_INFO, 0x00); + snd_ad1848_out(chip, AD1848_LEFT_INPUT, 0xaa); + snd_ad1848_out(chip, AD1848_RIGHT_INPUT, 0x45); + rev = snd_ad1848_in(chip, AD1848_RIGHT_INPUT); + if (rev == 0x65) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + id = 1; + ad1847 = 1; + break; + } + if (snd_ad1848_in(chip, AD1848_LEFT_INPUT) == 0xaa && rev == 0x45) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + id = 1; + break; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + } + if (id != 1) + return -ENODEV; /* no valid device found */ + if (chip->hardware == AD1848_HW_DETECT) { + if (ad1847) { + chip->hardware = AD1848_HW_AD1847; + } else { + chip->hardware = AD1848_HW_AD1848; + rev = snd_ad1848_in(chip, AD1848_MISC_INFO); + if (rev & 0x80) { + chip->hardware = AD1848_HW_CS4248; + } else if ((rev & 0x0f) == 0x0a) { + snd_ad1848_out(chip, AD1848_MISC_INFO, 0x40); + for (i = 0; i < 16; ++i) { + if (snd_ad1848_in(chip, i) != snd_ad1848_in(chip, i + 16)) { + chip->hardware = AD1848_HW_CMI8330; + break; + } + } + snd_ad1848_out(chip, AD1848_MISC_INFO, 0x00); + } + } + } + spin_lock_irqsave(&chip->reg_lock, flags); + inb(AD1848P(chip, STATUS)); /* clear any pendings IRQ */ + outb(0, AD1848P(chip, STATUS)); + mb(); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + chip->image[AD1848_MISC_INFO] = 0x00; + chip->image[AD1848_IFACE_CTRL] = + (chip->image[AD1848_IFACE_CTRL] & ~AD1848_SINGLE_DMA) | AD1848_SINGLE_DMA; + ptr = (unsigned char *) &chip->image; + snd_ad1848_mce_down(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + for (i = 0; i < 16; i++) /* ok.. fill all AD1848 registers */ + snd_ad1848_out(chip, i, *ptr++); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_ad1848_mce_up(chip); + snd_ad1848_mce_down(chip); + return 0; /* all things are ok.. */ +} + +/* + + */ + +static snd_pcm_hardware_t snd_ad1848_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE), + .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_ad1848_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE), + .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_ad1848_playback_open(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + if ((err = snd_ad1848_open(chip, AD1848_MODE_PLAY)) < 0) + return err; + chip->playback_substream = substream; + runtime->hw = snd_ad1848_playback; + snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + return 0; +} + +static int snd_ad1848_capture_open(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + if ((err = snd_ad1848_open(chip, AD1848_MODE_CAPTURE)) < 0) + return err; + chip->capture_substream = substream; + runtime->hw = snd_ad1848_capture; + snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(chip->dma, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + return 0; +} + +static int snd_ad1848_playback_close(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + + chip->mode &= ~AD1848_MODE_PLAY; + chip->playback_substream = NULL; + snd_ad1848_close(chip); + return 0; +} + +static int snd_ad1848_capture_close(snd_pcm_substream_t * substream) +{ + ad1848_t *chip = snd_pcm_substream_chip(substream); + + chip->mode &= ~AD1848_MODE_CAPTURE; + chip->capture_substream = NULL; + snd_ad1848_close(chip); + return 0; +} + +static int snd_ad1848_free(ad1848_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma >= 0) { + snd_dma_disable(chip->dma); + free_dma(chip->dma); + } + kfree(chip); + return 0; +} + +static int snd_ad1848_dev_free(snd_device_t *device) +{ + ad1848_t *chip = device->device_data; + return snd_ad1848_free(chip); +} + +static const char *snd_ad1848_chip_id(ad1848_t *chip) +{ + switch (chip->hardware) { + case AD1848_HW_AD1847: return "AD1847"; + case AD1848_HW_AD1848: return "AD1848"; + case AD1848_HW_CS4248: return "CS4248"; + case AD1848_HW_CMI8330: return "CMI8330/C3D"; + default: return "???"; + } +} + +int snd_ad1848_create(snd_card_t * card, + unsigned long port, + int irq, int dma, + unsigned short hardware, + ad1848_t ** rchip) +{ + static snd_device_ops_t ops = { + .dev_free = snd_ad1848_dev_free, + }; + ad1848_t *chip; + int err; + + *rchip = NULL; + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->reg_lock); + init_MUTEX(&chip->open_mutex); + chip->card = card; + chip->port = port; + chip->irq = -1; + chip->dma = -1; + chip->hardware = hardware; + memcpy(&chip->image, &snd_ad1848_original_image, sizeof(snd_ad1848_original_image)); + + if ((chip->res_port = request_region(port, 4, "AD1848")) == NULL) { + snd_printk(KERN_ERR "ad1848: can't grab port 0x%lx\n", port); + snd_ad1848_free(chip); + return -EBUSY; + } + if (request_irq(irq, snd_ad1848_interrupt, SA_INTERRUPT, "AD1848", (void *) chip)) { + snd_printk(KERN_ERR "ad1848: can't grab IRQ %d\n", irq); + snd_ad1848_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (request_dma(dma, "AD1848")) { + snd_printk(KERN_ERR "ad1848: can't grab DMA %d\n", dma); + snd_ad1848_free(chip); + return -EBUSY; + } + chip->dma = dma; + + if (hardware == AD1848_HW_THINKPAD) { + chip->thinkpad_flag = 1; + chip->hardware = AD1848_HW_DETECT; /* reset */ + snd_ad1848_thinkpad_twiddle(chip, 1); + snd_card_set_isa_pm_callback(card, snd_ad1848_suspend, snd_ad1848_resume, chip); + } + + if (snd_ad1848_probe(chip) < 0) { + snd_ad1848_free(chip); + return -ENODEV; + } + + /* Register device */ + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_ad1848_free(chip); + return err; + } + + *rchip = chip; + return 0; +} + +static snd_pcm_ops_t snd_ad1848_playback_ops = { + .open = snd_ad1848_playback_open, + .close = snd_ad1848_playback_close, + .ioctl = snd_ad1848_ioctl, + .hw_params = snd_ad1848_playback_hw_params, + .hw_free = snd_ad1848_playback_hw_free, + .prepare = snd_ad1848_playback_prepare, + .trigger = snd_ad1848_playback_trigger, + .pointer = snd_ad1848_playback_pointer, +}; + +static snd_pcm_ops_t snd_ad1848_capture_ops = { + .open = snd_ad1848_capture_open, + .close = snd_ad1848_capture_close, + .ioctl = snd_ad1848_ioctl, + .hw_params = snd_ad1848_capture_hw_params, + .hw_free = snd_ad1848_capture_hw_free, + .prepare = snd_ad1848_capture_prepare, + .trigger = snd_ad1848_capture_trigger, + .pointer = snd_ad1848_capture_pointer, +}; + +static void snd_ad1848_pcm_free(snd_pcm_t *pcm) +{ + ad1848_t *chip = pcm->private_data; + chip->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_ad1848_pcm(ad1848_t *chip, int device, snd_pcm_t **rpcm) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_pcm_new(chip->card, "AD1848", device, 1, 1, &pcm)) < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_ad1848_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_ad1848_capture_ops); + + pcm->private_free = snd_ad1848_pcm_free; + pcm->private_data = chip; + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + strcpy(pcm->name, snd_ad1848_chip_id(chip)); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, chip->dma > 3 ? 128*1024 : 64*1024); + + chip->pcm = pcm; + if (rpcm) + *rpcm = pcm; + return 0; +} + +const snd_pcm_ops_t *snd_ad1848_get_pcm_ops(int direction) +{ + return direction == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_ad1848_playback_ops : &snd_ad1848_capture_ops; +} + +/* + * MIXER part + */ + +static int snd_ad1848_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[4] = { + "Line", "Aux", "Mic", "Mix" + }; + + 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; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_ad1848_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.enumerated.item[0] = (chip->image[AD1848_LEFT_INPUT] & AD1848_MIXS_ALL) >> 6; + ucontrol->value.enumerated.item[1] = (chip->image[AD1848_RIGHT_INPUT] & AD1848_MIXS_ALL) >> 6; + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_ad1848_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_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[AD1848_LEFT_INPUT] & ~AD1848_MIXS_ALL) | left; + right = (chip->image[AD1848_RIGHT_INPUT] & ~AD1848_MIXS_ALL) | right; + change = left != chip->image[AD1848_LEFT_INPUT] || + right != chip->image[AD1848_RIGHT_INPUT]; + snd_ad1848_out(chip, AD1848_LEFT_INPUT, left); + snd_ad1848_out(chip, AD1848_RIGHT_INPUT, right); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static int snd_ad1848_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_ad1848_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_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; +} + +static int snd_ad1848_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_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_ad1848_out(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static int snd_ad1848_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_ad1848_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_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; +} + +static int snd_ad1848_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + ad1848_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->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_ad1848_out(chip, left_reg, val1); + snd_ad1848_out(chip, right_reg, val2); + } else { + val1 = (chip->image[left_reg] & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != chip->image[left_reg]; + snd_ad1848_out(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +/* + */ +int snd_ad1848_add_ctl(ad1848_t *chip, const char *name, int index, int type, unsigned long value) +{ + static snd_kcontrol_new_t newctls[] = { + [AD1848_MIX_SINGLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_ad1848_info_single, + .get = snd_ad1848_get_single, + .put = snd_ad1848_put_single, + }, + [AD1848_MIX_DOUBLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_ad1848_info_double, + .get = snd_ad1848_get_double, + .put = snd_ad1848_put_double, + }, + [AD1848_MIX_CAPTURE] = { + .info = snd_ad1848_info_mux, + .get = snd_ad1848_get_mux, + .put = snd_ad1848_put_mux, + }, + }; + snd_kcontrol_t *ctl; + int err; + + ctl = snd_ctl_new1(&newctls[type], chip); + if (! ctl) + return -ENOMEM; + strlcpy(ctl->id.name, name, sizeof(ctl->id.name)); + ctl->id.index = index; + ctl->private_value = value; + if ((err = snd_ctl_add(chip->card, ctl)) < 0) { + snd_ctl_free_one(ctl); + return err; + } + return 0; +} + + +static struct ad1848_mix_elem snd_ad1848_controls[] = { +AD1848_DOUBLE("PCM Playback Switch", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 7, 7, 1, 1), +AD1848_DOUBLE("PCM Playback Volume", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 0, 0, 63, 1), +AD1848_DOUBLE("Aux Playback Switch", 0, AD1848_AUX1_LEFT_INPUT, AD1848_AUX1_RIGHT_INPUT, 7, 7, 1, 1), +AD1848_DOUBLE("Aux Playback Volume", 0, AD1848_AUX1_LEFT_INPUT, AD1848_AUX1_RIGHT_INPUT, 0, 0, 31, 1), +AD1848_DOUBLE("Aux Playback Switch", 1, AD1848_AUX2_LEFT_INPUT, AD1848_AUX2_RIGHT_INPUT, 7, 7, 1, 1), +AD1848_DOUBLE("Aux Playback Volume", 1, AD1848_AUX2_LEFT_INPUT, AD1848_AUX2_RIGHT_INPUT, 0, 0, 31, 1), +AD1848_DOUBLE("Capture Volume", 0, AD1848_LEFT_INPUT, AD1848_RIGHT_INPUT, 0, 0, 15, 0), +{ + .name = "Capture Source", + .type = AD1848_MIX_CAPTURE, +}, +AD1848_SINGLE("Loopback Capture Switch", 0, AD1848_LOOPBACK, 0, 1, 0), +AD1848_SINGLE("Loopback Capture Volume", 0, AD1848_LOOPBACK, 1, 63, 0) +}; + +int snd_ad1848_mixer(ad1848_t *chip) +{ + snd_card_t *card; + snd_pcm_t *pcm; + unsigned int idx; + int err; + + snd_assert(chip != NULL && chip->pcm != NULL, return -EINVAL); + + pcm = chip->pcm; + card = chip->card; + + strcpy(card->mixername, pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_ad1848_controls); idx++) + if ((err = snd_ad1848_add_ctl_elem(chip, &snd_ad1848_controls[idx])) < 0) + return err; + + return 0; +} + +EXPORT_SYMBOL(snd_ad1848_out); +EXPORT_SYMBOL(snd_ad1848_create); +EXPORT_SYMBOL(snd_ad1848_pcm); +EXPORT_SYMBOL(snd_ad1848_get_pcm_ops); +EXPORT_SYMBOL(snd_ad1848_mixer); +EXPORT_SYMBOL(snd_ad1848_add_ctl); + +/* + * INIT part + */ + +static int __init alsa_ad1848_init(void) +{ + return 0; +} + +static void __exit alsa_ad1848_exit(void) +{ +} + +module_init(alsa_ad1848_init) +module_exit(alsa_ad1848_exit) diff --git a/sound/isa/als100.c b/sound/isa/als100.c new file mode 100644 index 00000000000..ac8f1366498 --- /dev/null +++ b/sound/isa/als100.c @@ -0,0 +1,336 @@ + +/* + card-als100.c - driver for Avance Logic ALS100 based soundcards. + Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it> + + Thanks to Pierfrancesco 'qM2' Passerini. + + 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/wait.h> +#include <linux/time.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#include <sound/sb.h> + +#define PFX "als100: " + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("Avance Logic ALS1X0"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS100 - PRO16PNP}," + "{Avance Logic,ALS110}," + "{Avance Logic,ALS120}," + "{Avance Logic,ALS200}," + "{3D Melody,MF1000}," + "{Digimate,3D Sound}," + "{Avance Logic,ALS120}," + "{RTL,RTL3000}}"); + +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 */ +static long port[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 int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for als100 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for als100 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable als100 based soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for als100 driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for als100 driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for als100 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for als100 driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for als100 driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for als100 driver."); +module_param_array(dma16, int, NULL, 0444); +MODULE_PARM_DESC(dma16, "16-bit DMA # for als100 driver."); + +struct snd_card_als100 { + int dev_no; + struct pnp_dev *dev; + struct pnp_dev *devmpu; + struct pnp_dev *devopl; +}; + +static struct pnp_card_device_id snd_als100_pnpids[] = { + /* ALS100 - PRO16PNP */ + { .id = "ALS0001", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" } } }, + /* ALS110 - MF1000 - Digimate 3D Sound */ + { .id = "ALS0110", .devs = { { "@@@1001" }, { "@X@1001" }, { "@H@1001" } } }, + /* ALS120 */ + { .id = "ALS0120", .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } } }, + /* ALS200 */ + { .id = "ALS0200", .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0001" } } }, + /* ALS200 OEM */ + { .id = "ALS0200", .devs = { { "@@@0020" }, { "@X@0020" }, { "@H@0020" } } }, + /* RTL3000 */ + { .id = "RTL3000", .devs = { { "@@@2001" }, { "@X@2001" }, { "@H@2001" } } }, + { .id = "", } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_als100_pnpids); + +#define DRIVER_NAME "snd-card-als100" + +static int __devinit snd_card_als100_pnp(int dev, struct snd_card_als100 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + int err; + + if (!cfg) + return -ENOMEM; + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -ENODEV; + } + acard->devmpu = pnp_request_card_device(card, id->devs[1].id, acard->dev); + acard->devopl = pnp_request_card_device(card, id->devs[2].id, acard->dev); + + pdev = acard->dev; + + pnp_init_resource_table(cfg); + + /* override resources */ + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (dma8[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1); + if (dma16[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[1], dma16[dev], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + kfree(cfg); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + dma8[dev] = pnp_dma(pdev, 1); + dma16[dev] = pnp_dma(pdev, 0); + irq[dev] = pnp_irq(pdev, 0); + + pdev = acard->devmpu; + if (pdev != NULL) { + 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) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "MPU401 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) + goto __mpu_error; + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + __mpu_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n"); + } + acard->devmpu = NULL; + mpu_port[dev] = -1; + } + + pdev = acard->devopl; + if (pdev != NULL) { + pnp_init_resource_table(cfg); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], fm_port[dev], 4); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "OPL3 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) + goto __fm_error; + fm_port[dev] = pnp_port_start(pdev, 0); + } else { + __fm_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "OPL3 pnp configure failure, skipping\n"); + } + acard->devopl = NULL; + fm_port[dev] = -1; + } + + kfree(cfg); + return 0; +} + +static int __init snd_card_als100_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + sb_t *chip; + snd_card_t *card; + struct snd_card_als100 *acard; + opl3_t *opl3; + + if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_als100))) == NULL) + return -ENOMEM; + acard = (struct snd_card_als100 *)card->private_data; + + if ((error = snd_card_als100_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, &pcard->card->dev); + + if ((error = snd_sbdsp_create(card, port[dev], + irq[dev], + snd_sb16dsp_interrupt, + dma8[dev], + dma16[dev], + SB_HW_ALS100, &chip)) < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "ALS100"); + strcpy(card->shortname, "Avance Logic ALS100"); + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, chip->name, chip->port, + irq[dev], dma8[dev], dma16[dev]); + + if ((error = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_ALS100, + mpu_port[dev], 0, + mpu_irq[dev], SA_INTERRUPT, + NULL) < 0) + snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]); + } + + 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_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static int __devinit snd_als100_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]) + continue; + res = snd_card_als100_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_als100_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 als100_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "als100", + .id_table = snd_als100_pnpids, + .probe = snd_als100_pnp_detect, + .remove = __devexit_p(snd_als100_pnp_remove), +}; + +static int __init alsa_card_als100_init(void) +{ + int cards = 0; + + cards += pnp_register_card_driver(&als100_pnpc_driver); +#ifdef MODULE + if (!cards) { + pnp_unregister_card_driver(&als100_pnpc_driver); + snd_printk(KERN_ERR "no ALS100 based soundcards found\n"); + } +#endif + return cards ? 0 : -ENODEV; +} + +static void __exit alsa_card_als100_exit(void) +{ + pnp_unregister_card_driver(&als100_pnpc_driver); +} + +module_init(alsa_card_als100_init) +module_exit(alsa_card_als100_exit) diff --git a/sound/isa/azt2320.c b/sound/isa/azt2320.c new file mode 100644 index 00000000000..bb41c6ec2f4 --- /dev/null +++ b/sound/isa/azt2320.c @@ -0,0 +1,366 @@ +/* + card-azt2320.c - driver for Aztech Systems AZT2320 based soundcards. + Copyright (C) 1999-2000 by Massimo Piccioni <dafastidio@libero.it> + + 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 +*/ + +/* + This driver should provide support for most Aztech AZT2320 based cards. + Several AZT2316 chips are also supported/tested, but autoprobe doesn't + work: all module option have to be set. + + No docs available for us at Aztech headquarters !!! Unbelievable ... + No other help obtained. + + Thanks to Rainer Wiesner <rainer.wiesner@01019freenet.de> for the WSS + activation method (full-duplex audio!). +*/ + +#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 <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/cs4231.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> + +#define PFX "azt2320: " + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("Aztech Systems AZT2320"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Aztech Systems,PRO16V}," + "{Aztech Systems,AZT2320}," + "{Aztech Systems,AZT3300}," + "{Aztech Systems,AZT2320}," + "{Aztech Systems,AZT3000}}"); + +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 */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static long wss_port[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 int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for azt2320 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for azt2320 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable azt2320 based soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for azt2320 driver."); +module_param_array(wss_port, long, NULL, 0444); +MODULE_PARM_DESC(wss_port, "WSS Port # for azt2320 driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for azt2320 driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for azt2320 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for azt2320 driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for azt2320 driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "1st DMA # for azt2320 driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "2nd DMA # for azt2320 driver."); + +struct snd_card_azt2320 { + int dev_no; + struct pnp_dev *dev; + struct pnp_dev *devmpu; +}; + +static struct pnp_card_device_id snd_azt2320_pnpids[] = { + /* PRO16V */ + { .id = "AZT1008", .devs = { { "AZT1008" }, { "AZT2001" }, } }, + /* Aztech Sound Galaxy 16 */ + { .id = "AZT2320", .devs = { { "AZT0001" }, { "AZT0002" }, } }, + /* Packard Bell Sound III 336 AM/SP */ + { .id = "AZT3000", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + /* AT3300 */ + { .id = "AZT3002", .devs = { { "AZT1004" }, { "AZT2001" }, } }, + /* --- */ + { .id = "AZT3005", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + /* --- */ + { .id = "AZT3011", .devs = { { "AZT1003" }, { "AZT2001" }, } }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_azt2320_pnpids); + +#define DRIVER_NAME "snd-card-azt2320" + +static int __devinit snd_card_azt2320_pnp(int dev, struct snd_card_azt2320 *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->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -ENODEV; + } + + acard->devmpu = pnp_request_card_device(card, id->devs[1].id, NULL); + + pdev = acard->dev; + pnp_init_resource_table(cfg); + + /* override resources */ + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4); + if (wss_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], wss_port[dev], 4); + 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], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + kfree(cfg); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + wss_port[dev] = pnp_port_start(pdev, 2); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + + pdev = acard->devmpu; + if (pdev != NULL) { + 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) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "MPU401 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) + goto __mpu_error; + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + } else { + __mpu_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "MPU401 pnp configure failure, skipping\n"); + } + acard->devmpu = NULL; + mpu_port[dev] = -1; + } + + kfree (cfg); + return 0; +} + +/* same of snd_sbdsp_command by Jaroslav Kysela */ +static int __devinit snd_card_azt2320_command(unsigned long port, unsigned char val) +{ + int i; + unsigned long limit; + + limit = jiffies + HZ / 10; + for (i = 50000; i && time_after(limit, jiffies); i--) + if (!(inb(port + 0x0c) & 0x80)) { + outb(val, port + 0x0c); + return 0; + } + return -EBUSY; +} + +static int __devinit snd_card_azt2320_enable_wss(unsigned long port) +{ + int error; + + if ((error = snd_card_azt2320_command(port, 0x09))) + return error; + if ((error = snd_card_azt2320_command(port, 0x00))) + return error; + + mdelay(5); + return 0; +} + +static int __devinit snd_card_azt2320_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + snd_card_t *card; + struct snd_card_azt2320 *acard; + cs4231_t *chip; + opl3_t *opl3; + + if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_azt2320))) == NULL) + return -ENOMEM; + acard = (struct snd_card_azt2320 *)card->private_data; + + if ((error = snd_card_azt2320_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, &pcard->card->dev); + + if ((error = snd_card_azt2320_enable_wss(port[dev]))) { + snd_card_free(card); + return error; + } + + if ((error = snd_cs4231_create(card, wss_port[dev], -1, + irq[dev], + dma1[dev], + dma2[dev], + CS4231_HW_DETECT, 0, &chip)) < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "AZT2320"); + strcpy(card->shortname, "Aztech AZT2320"); + sprintf(card->longname, "%s, WSS at 0x%lx, irq %i, dma %i&%i", + card->shortname, chip->port, irq[dev], dma1[dev], dma2[dev]); + + if ((error = snd_cs4231_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_mixer(chip)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_timer(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + + if (mpu_port[dev] > 0 && mpu_port[dev] != SNDRV_AUTO_PORT) { + if (snd_mpu401_uart_new(card, 0, MPU401_HW_AZT2320, + mpu_port[dev], 0, + mpu_irq[dev], SA_INTERRUPT, + NULL) < 0) + snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx\n", mpu_port[dev]); + } + + 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_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 1, 2)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static int __devinit snd_azt2320_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]) + continue; + res = snd_card_azt2320_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_azt2320_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 azt2320_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "azt2320", + .id_table = snd_azt2320_pnpids, + .probe = snd_azt2320_pnp_detect, + .remove = __devexit_p(snd_azt2320_pnp_remove), +}; + +static int __init alsa_card_azt2320_init(void) +{ + int cards = 0; + + cards += pnp_register_card_driver(&azt2320_pnpc_driver); +#ifdef MODULE + if (!cards) { + pnp_unregister_card_driver(&azt2320_pnpc_driver); + snd_printk(KERN_ERR "no AZT2320 based soundcards found\n"); + } +#endif + return cards ? 0 : -ENODEV; +} + +static void __exit alsa_card_azt2320_exit(void) +{ + pnp_unregister_card_driver(&azt2320_pnpc_driver); +} + +module_init(alsa_card_azt2320_init) +module_exit(alsa_card_azt2320_exit) diff --git a/sound/isa/cmi8330.c b/sound/isa/cmi8330.c new file mode 100644 index 00000000000..46776cc0c15 --- /dev/null +++ b/sound/isa/cmi8330.c @@ -0,0 +1,633 @@ +/* + * Driver for C-Media's CMI8330 soundcards. + * Copyright (c) by George Talusan <gstalusan@uwaterloo.ca> + * http://www.undergrad.math.uwaterloo.ca/~gstalusa + * + * 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 + * + */ + +/* + * NOTES + * + * The extended registers contain mixer settings which are largely + * untapped for the time being. + * + * MPU401 and SPDIF are not supported yet. I don't have the hardware + * to aid in coding and testing, so I won't bother. + * + * To quickly load the module, + * + * modprobe -a snd-cmi8330 sbport=0x220 sbirq=5 sbdma8=1 + * sbdma16=5 wssport=0x530 wssirq=11 wssdma=0 + * + * This card has two mixers and two PCM devices. I've cheesed it such + * that recording and playback can be done through the same device. + * The driver "magically" routes the capturing to the AD1848 codec, + * and playback to the SB16 codec. This allows for full-duplex mode + * to some extent. + * The utilities in alsa-utils are aware of both devices, so passing + * the appropriate parameters to amixer and alsactl will give you + * full control over both mixers. + */ + +#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/ad1848.h> +#include <sound/sb.h> +#include <sound/initval.h> + +/* + */ +/* #define ENABLE_SB_MIXER */ +#define PLAYBACK_ON_SB + +/* + */ +MODULE_AUTHOR("George Talusan <gstalusan@uwaterloo.ca>"); +MODULE_DESCRIPTION("C-Media CMI8330"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{C-Media,CMI8330,isapnp:{CMI0001,@@@0001,@X@0001}}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_ISAPNP; +#ifdef CONFIG_PNP +static int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#endif +static long sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int sbirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int sbdma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int sbdma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int wssirq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; +static int wssdma[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for CMI8330 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for CMI8330 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable CMI8330 soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif + +module_param_array(sbport, long, NULL, 0444); +MODULE_PARM_DESC(sbport, "Port # for CMI8330 SB driver."); +module_param_array(sbirq, int, NULL, 0444); +MODULE_PARM_DESC(sbirq, "IRQ # for CMI8330 SB driver."); +module_param_array(sbdma8, int, NULL, 0444); +MODULE_PARM_DESC(sbdma8, "DMA8 for CMI8330 SB driver."); +module_param_array(sbdma16, int, NULL, 0444); +MODULE_PARM_DESC(sbdma16, "DMA16 for CMI8330 SB driver."); + +module_param_array(wssport, long, NULL, 0444); +MODULE_PARM_DESC(wssport, "Port # for CMI8330 WSS driver."); +module_param_array(wssirq, int, NULL, 0444); +MODULE_PARM_DESC(wssirq, "IRQ # for CMI8330 WSS driver."); +module_param_array(wssdma, int, NULL, 0444); +MODULE_PARM_DESC(wssdma, "DMA for CMI8330 WSS driver."); + +#define CMI8330_RMUX3D 16 +#define CMI8330_MUTEMUX 17 +#define CMI8330_OUTPUTVOL 18 +#define CMI8330_MASTVOL 19 +#define CMI8330_LINVOL 20 +#define CMI8330_CDINVOL 21 +#define CMI8330_WAVVOL 22 +#define CMI8330_RECMUX 23 +#define CMI8330_WAVGAIN 24 +#define CMI8330_LINGAIN 25 +#define CMI8330_CDINGAIN 26 + +static unsigned char snd_cmi8330_image[((CMI8330_CDINGAIN)-16) + 1] = +{ + 0x40, /* 16 - recording mux (SB-mixer-enabled) */ +#ifdef ENABLE_SB_MIXER + 0x40, /* 17 - mute mux (Mode2) */ +#else + 0x0, /* 17 - mute mux */ +#endif + 0x0, /* 18 - vol */ + 0x0, /* 19 - master volume */ + 0x0, /* 20 - line-in volume */ + 0x0, /* 21 - cd-in volume */ + 0x0, /* 22 - wave volume */ + 0x0, /* 23 - mute/rec mux */ + 0x0, /* 24 - wave rec gain */ + 0x0, /* 25 - line-in rec gain */ + 0x0 /* 26 - cd-in rec gain */ +}; + +typedef int (*snd_pcm_open_callback_t)(snd_pcm_substream_t *); + +struct snd_cmi8330 { +#ifdef CONFIG_PNP + struct pnp_dev *cap; + struct pnp_dev *play; +#endif + snd_card_t *card; + ad1848_t *wss; + sb_t *sb; + + snd_pcm_t *pcm; + struct snd_cmi8330_stream { + snd_pcm_ops_t ops; + snd_pcm_open_callback_t open; + void *private_data; /* sb or wss */ + } streams[2]; +}; + +static snd_card_t *snd_cmi8330_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_cmi8330_pnpids[] = { + { .id = "CMI0001", .devs = { { "@@@0001" }, { "@X@0001" } } }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_cmi8330_pnpids); + +#endif + + +static struct ad1848_mix_elem snd_cmi8330_controls[] __initdata = { +AD1848_DOUBLE("Master Playback Volume", 0, CMI8330_MASTVOL, CMI8330_MASTVOL, 4, 0, 15, 0), +AD1848_SINGLE("Loud Playback Switch", 0, CMI8330_MUTEMUX, 6, 1, 1), +AD1848_DOUBLE("PCM Playback Switch", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 7, 7, 1, 1), +AD1848_DOUBLE("PCM Playback Volume", 0, AD1848_LEFT_OUTPUT, AD1848_RIGHT_OUTPUT, 0, 0, 63, 1), +AD1848_DOUBLE("Line Playback Switch", 0, CMI8330_MUTEMUX, CMI8330_MUTEMUX, 4, 3, 1, 0), +AD1848_DOUBLE("Line Playback Volume", 0, CMI8330_LINVOL, CMI8330_LINVOL, 4, 0, 15, 0), +AD1848_DOUBLE("Line Capture Switch", 0, CMI8330_RMUX3D, CMI8330_RMUX3D, 2, 1, 1, 0), +AD1848_DOUBLE("Line Capture Volume", 0, CMI8330_LINGAIN, CMI8330_LINGAIN, 4, 0, 15, 0), +AD1848_DOUBLE("CD Playback Switch", 0, CMI8330_MUTEMUX, CMI8330_MUTEMUX, 2, 1, 1, 0), +AD1848_DOUBLE("CD Capture Switch", 0, CMI8330_RMUX3D, CMI8330_RMUX3D, 4, 3, 1, 0), +AD1848_DOUBLE("CD Playback Volume", 0, CMI8330_CDINVOL, CMI8330_CDINVOL, 4, 0, 15, 0), +AD1848_DOUBLE("CD Capture Volume", 0, CMI8330_CDINGAIN, CMI8330_CDINGAIN, 4, 0, 15, 0), +AD1848_SINGLE("Mic Playback Switch", 0, CMI8330_MUTEMUX, 0, 1, 0), +AD1848_SINGLE("Mic Playback Volume", 0, CMI8330_OUTPUTVOL, 0, 7, 0), +AD1848_SINGLE("Mic Capture Switch", 0, CMI8330_RMUX3D, 0, 1, 0), +AD1848_SINGLE("Mic Capture Volume", 0, CMI8330_OUTPUTVOL, 5, 7, 0), +AD1848_DOUBLE("Wavetable Playback Switch", 0, CMI8330_RECMUX, CMI8330_RECMUX, 1, 0, 1, 0), +AD1848_DOUBLE("Wavetable Playback Volume", 0, CMI8330_WAVVOL, CMI8330_WAVVOL, 4, 0, 15, 0), +AD1848_DOUBLE("Wavetable Capture Switch", 0, CMI8330_RECMUX, CMI8330_RECMUX, 5, 4, 1, 0), +AD1848_DOUBLE("Wavetable Capture Volume", 0, CMI8330_WAVGAIN, CMI8330_WAVGAIN, 4, 0, 15, 0), +AD1848_SINGLE("3D Control - Switch", 0, CMI8330_RMUX3D, 5, 1, 1), +AD1848_SINGLE("PC Speaker Playback Volume", 0, CMI8330_OUTPUTVOL, 3, 3, 0), +AD1848_SINGLE("FM Playback Switch", 0, CMI8330_RECMUX, 3, 1, 1), +AD1848_SINGLE("IEC958 Input Capture Switch", 0, CMI8330_RMUX3D, 7, 1, 1), +AD1848_SINGLE("IEC958 Input Playback Switch", 0, CMI8330_MUTEMUX, 7, 1, 1), +}; + +#ifdef ENABLE_SB_MIXER +static struct sbmix_elem cmi8330_sb_mixers[] __initdata = { +SB_DOUBLE("SB Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31), +SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15), +SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15), +SB_DOUBLE("SB PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1), +SB_DOUBLE("SB CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31), +SB_DOUBLE("SB Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1), +SB_DOUBLE("SB Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31), +SB_SINGLE("SB Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1), +SB_SINGLE("SB Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31), +SB_SINGLE("SB PC Speaker Volume", SB_DSP4_SPEAKER_DEV, 6, 3), +SB_DOUBLE("SB Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3), +SB_DOUBLE("SB Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3), +SB_SINGLE("SB Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1), +}; + +static unsigned char cmi8330_sb_init_values[][2] __initdata = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, +}; + + +static int __devinit cmi8330_add_sb_mixers(sb_t *chip) +{ + int idx, err; + unsigned long flags; + + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, 0x00, 0x00); /* mixer reset */ + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + /* mute and zero volume channels */ + for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_init_values); idx++) { + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, cmi8330_sb_init_values[idx][0], + cmi8330_sb_init_values[idx][1]); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + } + + for (idx = 0; idx < ARRAY_SIZE(cmi8330_sb_mixers); idx++) { + if ((err = snd_sbmixer_add_ctl_elem(chip, &cmi8330_sb_mixers[idx])) < 0) + return err; + } + return 0; +} +#endif + +static int __devinit snd_cmi8330_mixer(snd_card_t *card, struct snd_cmi8330 *acard) +{ + unsigned int idx; + int err; + + strcpy(card->mixername, "CMI8330/C3D"); + + for (idx = 0; idx < ARRAY_SIZE(snd_cmi8330_controls); idx++) { + if ((err = snd_ad1848_add_ctl_elem(acard->wss, &snd_cmi8330_controls[idx])) < 0) + return err; + } + +#ifdef ENABLE_SB_MIXER + if ((err = cmi8330_add_sb_mixers(acard->sb)) < 0) + return err; +#endif + return 0; +} + +#ifdef CONFIG_PNP +static int __devinit snd_cmi8330_pnp(int dev, struct snd_cmi8330 *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; + + acard->cap = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->cap == NULL) { + kfree(cfg); + return -EBUSY; + } + acard->play = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->play == NULL) { + kfree(cfg); + return -EBUSY; + } + + pdev = acard->cap; + pnp_init_resource_table(cfg); + /* allocate AD1848 resources */ + if (wssport[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], wssport[dev], 8); + if (wssdma[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], wssdma[dev], 1); + if (wssirq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], wssirq[dev], 1); + + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR "CMI8330/C3D (AD1848) PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "CMI8330/C3D (AD1848) PnP configure failure\n"); + kfree(cfg); + return -EBUSY; + } + wssport[dev] = pnp_port_start(pdev, 0); + wssdma[dev] = pnp_dma(pdev, 0); + wssirq[dev] = pnp_irq(pdev, 0); + + /* allocate SB16 resources */ + pdev = acard->play; + pnp_init_resource_table(cfg); + if (sbport[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], sbport[dev], 16); + if (sbdma8[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], sbdma8[dev], 1); + if (sbdma16[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[1], sbdma16[dev], 1); + if (sbirq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], sbirq[dev], 1); + + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR "CMI8330/C3D (SB16) PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "CMI8330/C3D (SB16) PnP configure failure\n"); + kfree(cfg); + return -EBUSY; + } + sbport[dev] = pnp_port_start(pdev, 0); + sbdma8[dev] = pnp_dma(pdev, 0); + sbdma16[dev] = pnp_dma(pdev, 1); + sbirq[dev] = pnp_irq(pdev, 0); + + kfree(cfg); + return 0; +} +#endif + +/* + * PCM interface + * + * since we call the different chip interfaces for playback and capture + * directions, we need a trick. + * + * - copy the ops for each direction into a local record. + * - replace the open callback with the new one, which replaces the + * substream->private_data with the corresponding chip instance + * and calls again the original open callback of the chip. + * + */ + +#ifdef PLAYBACK_ON_SB +#define CMI_SB_STREAM SNDRV_PCM_STREAM_PLAYBACK +#define CMI_AD_STREAM SNDRV_PCM_STREAM_CAPTURE +#else +#define CMI_SB_STREAM SNDRV_PCM_STREAM_CAPTURE +#define CMI_AD_STREAM SNDRV_PCM_STREAM_PLAYBACK +#endif + +static int snd_cmi8330_playback_open(snd_pcm_substream_t * substream) +{ + struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream); + + /* replace the private_data and call the original open callback */ + substream->private_data = chip->streams[SNDRV_PCM_STREAM_PLAYBACK].private_data; + return chip->streams[SNDRV_PCM_STREAM_PLAYBACK].open(substream); +} + +static int snd_cmi8330_capture_open(snd_pcm_substream_t * substream) +{ + struct snd_cmi8330 *chip = snd_pcm_substream_chip(substream); + + /* replace the private_data and call the original open callback */ + substream->private_data = chip->streams[SNDRV_PCM_STREAM_CAPTURE].private_data; + return chip->streams[SNDRV_PCM_STREAM_CAPTURE].open(substream); +} + +static void snd_cmi8330_pcm_free(snd_pcm_t *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int __devinit snd_cmi8330_pcm(snd_card_t *card, struct snd_cmi8330 *chip) +{ + snd_pcm_t *pcm; + const snd_pcm_ops_t *ops; + int err; + static snd_pcm_open_callback_t cmi_open_callbacks[2] = { + snd_cmi8330_playback_open, + snd_cmi8330_capture_open + }; + + if ((err = snd_pcm_new(card, "CMI8330", 0, 1, 1, &pcm)) < 0) + return err; + strcpy(pcm->name, "CMI8330"); + pcm->private_data = chip; + pcm->private_free = snd_cmi8330_pcm_free; + + /* SB16 */ + ops = snd_sb16dsp_get_pcm_ops(CMI_SB_STREAM); + chip->streams[CMI_SB_STREAM].ops = *ops; + chip->streams[CMI_SB_STREAM].open = ops->open; + chip->streams[CMI_SB_STREAM].ops.open = cmi_open_callbacks[CMI_SB_STREAM]; + chip->streams[CMI_SB_STREAM].private_data = chip->sb; + + /* AD1848 */ + ops = snd_ad1848_get_pcm_ops(CMI_AD_STREAM); + chip->streams[CMI_AD_STREAM].ops = *ops; + chip->streams[CMI_AD_STREAM].open = ops->open; + chip->streams[CMI_AD_STREAM].ops.open = cmi_open_callbacks[CMI_AD_STREAM]; + chip->streams[CMI_AD_STREAM].private_data = chip->wss; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &chip->streams[SNDRV_PCM_STREAM_PLAYBACK].ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &chip->streams[SNDRV_PCM_STREAM_CAPTURE].ops); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 128*1024); + chip->pcm = pcm; + + return 0; +} + + +/* + */ + +static int __devinit snd_cmi8330_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + snd_card_t *card; + struct snd_cmi8330 *acard; + unsigned long flags; + int i, err; + +#ifdef CONFIG_PNP + if (!isapnp[dev]) { +#endif + if (wssport[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify wssport\n"); + return -EINVAL; + } + if (sbport[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify sbport\n"); + return -EINVAL; + } +#ifdef CONFIG_PNP + } +#endif + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_cmi8330)); + if (card == NULL) { + snd_printk("could not get a new card\n"); + return -ENOMEM; + } + acard = (struct snd_cmi8330 *)card->private_data; + acard->card = card; + +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if ((err = snd_cmi8330_pnp(dev, acard, pcard, pid)) < 0) { + snd_printk("PnP detection failed\n"); + snd_card_free(card); + return err; + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + + if ((err = snd_ad1848_create(card, + wssport[dev] + 4, + wssirq[dev], + wssdma[dev], + AD1848_HW_DETECT, + &acard->wss)) < 0) { + snd_printk("(AD1848) device busy??\n"); + snd_card_free(card); + return err; + } + if (acard->wss->hardware != AD1848_HW_CMI8330) { + snd_printk("(AD1848) not found during probe\n"); + snd_card_free(card); + return -ENODEV; + } + + if ((err = snd_sbdsp_create(card, sbport[dev], + sbirq[dev], + snd_sb16dsp_interrupt, + sbdma8[dev], + sbdma16[dev], + SB_HW_AUTO, &acard->sb)) < 0) { + snd_printk("(SB16) device busy??\n"); + snd_card_free(card); + return err; + } + if (acard->sb->hardware != SB_HW_16) { + snd_printk("(SB16) not found during probe\n"); + snd_card_free(card); + return -ENODEV; + } + + spin_lock_irqsave(&acard->wss->reg_lock, flags); + snd_ad1848_out(acard->wss, AD1848_MISC_INFO, 0x40); /* switch on MODE2 */ + for (i = CMI8330_RMUX3D; i <= CMI8330_CDINGAIN; i++) + snd_ad1848_out(acard->wss, i, snd_cmi8330_image[i - CMI8330_RMUX3D]); + spin_unlock_irqrestore(&acard->wss->reg_lock, flags); + + if ((err = snd_cmi8330_mixer(card, acard)) < 0) { + snd_printk("failed to create mixers\n"); + snd_card_free(card); + return err; + } + + if ((err = snd_cmi8330_pcm(card, acard)) < 0) { + snd_printk("failed to create pcms\n"); + snd_card_free(card); + return err; + } + + strcpy(card->driver, "CMI8330/C3D"); + strcpy(card->shortname, "C-Media CMI8330/C3D"); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, + acard->wss->port, + wssirq[dev], + wssdma[dev]); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_cmi8330_legacy[dev] = card; + return 0; +} + +#ifdef CONFIG_PNP +static int __devinit snd_cmi8330_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_cmi8330_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_cmi8330_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 cmi8330_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "cmi8330", + .id_table = snd_cmi8330_pnpids, + .probe = snd_cmi8330_pnp_detect, + .remove = __devexit_p(snd_cmi8330_pnp_remove), +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_cmi8330_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_cmi8330_probe(dev, NULL, NULL) >= 0) + cards++; + } +#ifdef CONFIG_PNP + cards += pnp_register_card_driver(&cmi8330_pnpc_driver); +#endif + + if (!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&cmi8330_pnpc_driver); +#endif +#ifdef MODULE + snd_printk(KERN_ERR "CMI8330 not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_cmi8330_exit(void) +{ + int i; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&cmi8330_pnpc_driver); +#endif + for (i = 0; i < SNDRV_CARDS; i++) + snd_card_free(snd_cmi8330_legacy[i]); +} + +module_init(alsa_card_cmi8330_init) +module_exit(alsa_card_cmi8330_exit) diff --git a/sound/isa/cs423x/Makefile b/sound/isa/cs423x/Makefile new file mode 100644 index 00000000000..d2afaea30cb --- /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 00000000000..7640837659e --- /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 00000000000..3e7a2a33a5c --- /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 00000000000..9fad2e6c0c2 --- /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 00000000000..e745a54e00a --- /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 00000000000..2128d4bdef4 --- /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) diff --git a/sound/isa/dt019x.c b/sound/isa/dt019x.c new file mode 100644 index 00000000000..db7c3397b32 --- /dev/null +++ b/sound/isa/dt019x.c @@ -0,0 +1,327 @@ + +/* + dt019x.c - driver for Diamond Technologies DT-0197H based soundcards. + Copyright (C) 1999, 2002 by Massimo Piccioni <dafastidio@libero.it> + + Generalised for soundcards based on DT-0196 and ALS-007 chips + by Jonathan Woithe <jwoithe@physics.adelaide.edu.au>: June 2002. + + 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/sched.h> +#include <linux/wait.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#include <sound/sb.h> + +#define PFX "dt019x: " + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("Diamond Technologies DT-019X / Avance Logic ALS-007"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Diamond Technologies DT-019X}," + "{Avance Logic ALS-007}}"); + +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 long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* PnP setup */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for DT-019X based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for DT-019X based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable DT-019X based soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for dt019x driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for dt019x driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for dt019x driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for dt019x driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for dt019x driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for dt019x driver."); + +struct snd_card_dt019x { + struct pnp_dev *dev; + struct pnp_dev *devmpu; + struct pnp_dev *devopl; +}; + +static struct pnp_card_device_id snd_dt019x_pnpids[] = { + /* DT197A30 */ + { .id = "RWB1688", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" }, } }, + /* DT0196 / ALS-007 */ + { .id = "ALS0007", .devs = { { "@@@0001" }, { "@X@0001" }, { "@H@0001" }, } }, + { .id = "", } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_dt019x_pnpids); + + +#define DRIVER_NAME "snd-card-dt019x" + + +static int __devinit snd_card_dt019x_pnp(int dev, struct snd_card_dt019x *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *pdev; + struct pnp_resource_table * cfg = kmalloc(sizeof(struct pnp_resource_table), GFP_KERNEL); + int err; + + if (!cfg) + return -ENOMEM; + + acard->dev = pnp_request_card_device(card, pid->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree (cfg); + return -ENODEV; + } + acard->devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL); + acard->devopl = pnp_request_card_device(card, pid->devs[2].id, NULL); + + pdev = acard->dev; + pnp_init_resource_table(cfg); + + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (dma8[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "DT-019X AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "DT-019X AUDIO pnp configure failure\n"); + kfree(cfg); + return err; + } + + port[dev] = pnp_port_start(pdev, 0); + dma8[dev] = pnp_dma(pdev, 0); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("dt019x: found audio interface: port=0x%lx, irq=0x%x, dma=0x%x\n", + port[dev],irq[dev],dma8[dev]); + + pdev = acard->devmpu; + + if (pdev != NULL) { + 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) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq[dev], 1); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "DT-019X MPU401 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "DT-019X MPU401 pnp configure failure, skipping\n"); + goto __mpu_error; + } + mpu_port[dev] = pnp_port_start(pdev, 0); + mpu_irq[dev] = pnp_irq(pdev, 0); + snd_printdd("dt019x: found MPU-401: port=0x%lx, irq=0x%x\n", + mpu_port[dev],mpu_irq[dev]); + } else { + __mpu_error: + acard->devmpu = NULL; + mpu_port[dev] = -1; + } + + pdev = acard->devopl; + if (pdev != NULL) { + pnp_init_resource_table(cfg); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], fm_port[dev], 4); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "DT-019X OPL3 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "DT-019X OPL3 pnp configure failure, skipping\n"); + goto __fm_error; + } + fm_port[dev] = pnp_port_start(pdev, 0); + snd_printdd("dt019x: found OPL3 synth: port=0x%lx\n",fm_port[dev]); + } else { + __fm_error: + acard->devopl = NULL; + fm_port[dev] = -1; + } + + kfree(cfg); + return 0; +} + +static int __devinit snd_card_dt019x_probe(int dev, struct pnp_card_link *pcard, const struct pnp_card_device_id *pid) +{ + int error; + sb_t *chip; + snd_card_t *card; + struct snd_card_dt019x *acard; + opl3_t *opl3; + + if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_dt019x))) == NULL) + return -ENOMEM; + acard = (struct snd_card_dt019x *)card->private_data; + + snd_card_set_dev(card, &pcard->card->dev); + if ((error = snd_card_dt019x_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + + if ((error = snd_sbdsp_create(card, port[dev], + irq[dev], + snd_sb16dsp_interrupt, + dma8[dev], + -1, + SB_HW_DT019X, + &chip)) < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "DT-019X"); + strcpy(card->shortname, "Diamond Tech. DT-019X"); + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d", + card->shortname, chip->name, chip->port, + irq[dev], dma8[dev]); + + if ((error = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return error; + } + + 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_SB,*/ + MPU401_HW_MPU401, + mpu_port[dev], 0, + mpu_irq[dev], + mpu_irq[dev] >= 0 ? SA_INTERRUPT : 0, + NULL) < 0) + snd_printk(KERN_ERR PFX "no MPU-401 device at 0x%lx ?\n", mpu_port[dev]); + } + + 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_AUTO, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx ?\n", + fm_port[dev], fm_port[dev] + 2); + } else { + if ((error = snd_opl3_timer_new(opl3, 0, 1)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static int __devinit snd_dt019x_pnp_probe(struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; + res = snd_card_dt019x_probe(dev, card, pid); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_dt019x_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 dt019x_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "dt019x", + .id_table = snd_dt019x_pnpids, + .probe = snd_dt019x_pnp_probe, + .remove = __devexit_p(snd_dt019x_pnp_remove), +}; + +static int __init alsa_card_dt019x_init(void) +{ + int cards = 0; + + cards += pnp_register_card_driver(&dt019x_pnpc_driver); + +#ifdef MODULE + if (!cards) { + pnp_unregister_card_driver(&dt019x_pnpc_driver); + snd_printk(KERN_ERR "no DT-019X / ALS-007 based soundcards found\n"); + } +#endif + return cards ? 0 : -ENODEV; +} + +static void __exit alsa_card_dt019x_exit(void) +{ + pnp_unregister_card_driver(&dt019x_pnpc_driver); +} + +module_init(alsa_card_dt019x_init) +module_exit(alsa_card_dt019x_exit) diff --git a/sound/isa/es1688/Makefile b/sound/isa/es1688/Makefile new file mode 100644 index 00000000000..501c8bf903a --- /dev/null +++ b/sound/isa/es1688/Makefile @@ -0,0 +1,11 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-es1688-lib-objs := es1688_lib.o +snd-es1688-objs := es1688.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ES1688) += snd-es1688.o snd-es1688-lib.o +obj-$(CONFIG_SND_GUSEXTREME) += snd-es1688-lib.o diff --git a/sound/isa/es1688/es1688.c b/sound/isa/es1688/es1688.c new file mode 100644 index 00000000000..c5eaec087b4 --- /dev/null +++ b/sound/isa/es1688/es1688.c @@ -0,0 +1,204 @@ +/* + * Driver for generic ESS AudioDrive ESx688 soundcards + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/es1688.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("ESS ESx688 AudioDrive"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESS,ES688 PnP AudioDrive,pnp:ESS0100}," + "{ESS,ES1688 PnP AudioDrive,pnp:ESS0102}," + "{ESS,ES688 AudioDrive,pnp:ESS6881}," + "{ESS,ES1688 AudioDrive,pnp:ESS1681}}"); + +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; /* 0x220,0x240,0x260 */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ESx688 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ESx688 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ESx688 soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for ESx688 driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ESx688 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for ESx688 driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for ESx688 driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for ESx688 driver."); + +static snd_card_t *snd_audiodrive_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_audiodrive_probe(int dev) +{ + static int possible_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dmas[] = {1, 3, 0, -1}; + int xirq, xdma, xmpu_irq; + snd_card_t *card; + es1688_t *chip; + opl3_t *opl3; + snd_pcm_t *pcm; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xmpu_irq = mpu_irq[dev]; + xdma = dma8[dev]; + if (xdma == SNDRV_AUTO_DMA) { + if ((xdma = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA\n"); + return -EBUSY; + } + } + + if ((err = snd_es1688_create(card, port[dev], mpu_port[dev], + xirq, xmpu_irq, xdma, + ES1688_HW_AUTO, &chip)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_es1688_pcm(chip, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_es1688_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, "ES1688"); + strcpy(card->shortname, pcm->name); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma %i", pcm->name, chip->port, xirq, xdma); + + if ((snd_opl3_create(card, chip->port, chip->port + 2, OPL3_HW_OPL3, 0, &opl3)) < 0) { + printk(KERN_ERR "es1688: opl3 not detected at 0x%lx\n", chip->port); + } else { + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + + if (xmpu_irq >= 0 && xmpu_irq != SNDRV_AUTO_IRQ && chip->mpu_port > 0) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688, + chip->mpu_port, 0, + xmpu_irq, + SA_INTERRUPT, + NULL)) < 0) { + snd_card_free(card); + return err; + } + } + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_audiodrive_cards[dev] = card; + return 0; + +} + +static int __init snd_audiodrive_legacy_auto_probe(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; + port[dev] = xport; + res = snd_audiodrive_probe(dev); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +static int __init alsa_card_es1688_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1}; + int dev, cards = 0, i; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) { + if (port[dev] == SNDRV_AUTO_PORT) + continue; + if (snd_audiodrive_probe(dev) >= 0) + cards++; + } + i = snd_legacy_auto_probe(possible_ports, snd_audiodrive_legacy_auto_probe); + if (i > 0) + cards += i; + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "ESS AudioDrive ES1688 soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_es1688_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_audiodrive_cards[idx]); +} + +module_init(alsa_card_es1688_init) +module_exit(alsa_card_es1688_exit) diff --git a/sound/isa/es1688/es1688_lib.c b/sound/isa/es1688/es1688_lib.c new file mode 100644 index 00000000000..17f68d07d9b --- /dev/null +++ b/sound/isa/es1688/es1688_lib.c @@ -0,0 +1,1062 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of ESS ES1688/688/488 chip + * + * + * 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/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/es1688.h> +#include <sound/initval.h> + +#include <asm/io.h> +#include <asm/dma.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("ESS ESx688 lowlevel module"); +MODULE_LICENSE("GPL"); + +static int snd_es1688_dsp_command(es1688_t *chip, unsigned char val) +{ + int i; + + for (i = 10000; i; i--) + if ((inb(ES1688P(chip, STATUS)) & 0x80) == 0) { + outb(val, ES1688P(chip, COMMAND)); + return 1; + } +#ifdef CONFIG_SND_DEBUG + printk("snd_es1688_dsp_command: timeout (0x%x)\n", val); +#endif + return 0; +} + +static int snd_es1688_dsp_get_byte(es1688_t *chip) +{ + int i; + + for (i = 1000; i; i--) + if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) + return inb(ES1688P(chip, READ)); + snd_printd("es1688 get byte failed: 0x%lx = 0x%x!!!\n", ES1688P(chip, DATA_AVAIL), inb(ES1688P(chip, DATA_AVAIL))); + return -ENODEV; +} + +static int snd_es1688_write(es1688_t *chip, + unsigned char reg, unsigned char data) +{ + if (!snd_es1688_dsp_command(chip, reg)) + return 0; + return snd_es1688_dsp_command(chip, data); +} + +static int snd_es1688_read(es1688_t *chip, unsigned char reg) +{ + /* Read a byte from an extended mode register of ES1688 */ + if (!snd_es1688_dsp_command(chip, 0xc0)) + return -1; + if (!snd_es1688_dsp_command(chip, reg)) + return -1; + return snd_es1688_dsp_get_byte(chip); +} + +void snd_es1688_mixer_write(es1688_t *chip, + unsigned char reg, unsigned char data) +{ + outb(reg, ES1688P(chip, MIXER_ADDR)); + udelay(10); + outb(data, ES1688P(chip, MIXER_DATA)); + udelay(10); +} + +static unsigned char snd_es1688_mixer_read(es1688_t *chip, unsigned char reg) +{ + unsigned char result; + + outb(reg, ES1688P(chip, MIXER_ADDR)); + udelay(10); + result = inb(ES1688P(chip, MIXER_DATA)); + udelay(10); + return result; +} + +static int snd_es1688_reset(es1688_t *chip) +{ + int i; + + outb(3, ES1688P(chip, RESET)); /* valid only for ESS chips, SB -> 1 */ + udelay(10); + outb(0, ES1688P(chip, RESET)); + udelay(30); + for (i = 0; i < 1000 && !(inb(ES1688P(chip, DATA_AVAIL)) & 0x80); i++); + if (inb(ES1688P(chip, READ)) != 0xaa) { + snd_printd("ess_reset at 0x%lx: failed!!!\n", chip->port); + return -ENODEV; + } + snd_es1688_dsp_command(chip, 0xc6); /* enable extended mode */ + return 0; +} + +static int snd_es1688_probe(es1688_t *chip) +{ + unsigned long flags; + unsigned short major, minor, hw; + int i; + + /* + * initialization sequence + */ + + spin_lock_irqsave(&chip->reg_lock, flags); /* Some ESS1688 cards need this */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE2)); /* ENABLE2 */ + inb(ES1688P(chip, ENABLE1)); /* ENABLE1 */ + inb(ES1688P(chip, ENABLE0)); /* ENABLE0 */ + + if (snd_es1688_reset(chip) < 0) { + snd_printdd("ESS: [0x%lx] reset failed... 0x%x\n", chip->port, inb(ES1688P(chip, READ))); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + snd_es1688_dsp_command(chip, 0xe7); /* return identification */ + + for (i = 1000, major = minor = 0; i; i--) { + if (inb(ES1688P(chip, DATA_AVAIL)) & 0x80) { + if (major == 0) { + major = inb(ES1688P(chip, READ)); + } else { + minor = inb(ES1688P(chip, READ)); + } + } + } + + spin_unlock_irqrestore(&chip->reg_lock, flags); + + snd_printdd("ESS: [0x%lx] found.. major = 0x%x, minor = 0x%x\n", chip->port, major, minor); + + chip->version = (major << 8) | minor; + if (!chip->version) + return -ENODEV; /* probably SB */ + + hw = ES1688_HW_AUTO; + switch (chip->version & 0xfff0) { + case 0x4880: + snd_printk("[0x%lx] ESS: AudioDrive ES488 detected, but driver is in another place\n", chip->port); + return -ENODEV; + case 0x6880: + hw = (chip->version & 0x0f) >= 8 ? ES1688_HW_1688 : ES1688_HW_688; + break; + default: + snd_printk("[0x%lx] ESS: unknown AudioDrive chip with version 0x%x (Jazz16 soundcard?)\n", chip->port, chip->version); + return -ENODEV; + } + + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */ + snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + + /* enable joystick, but disable OPL3 */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_es1688_mixer_write(chip, 0x40, 0x01); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + return 0; +} + +static int snd_es1688_init(es1688_t * chip, int enable) +{ + static int irqs[16] = {-1, -1, 0, -1, -1, 1, -1, 2, -1, 0, 3, -1, -1, -1, -1, -1}; + unsigned long flags; + int cfg, irq_bits, dma, dma_bits, tmp, tmp1; + + /* ok.. setup MPU-401 port and joystick and OPL3 */ + cfg = 0x01; /* enable joystick, but disable OPL3 */ + if (enable && chip->mpu_port >= 0x300 && chip->mpu_irq > 0 && chip->hardware != ES1688_HW_688) { + tmp = (chip->mpu_port & 0x0f0) >> 4; + if (tmp <= 3) { + switch (chip->mpu_irq) { + case 9: + tmp1 = 4; + break; + case 5: + tmp1 = 5; + break; + case 7: + tmp1 = 6; + break; + case 10: + tmp1 = 7; + break; + default: + tmp1 = 0; + } + if (tmp1) { + cfg |= (tmp << 3) | (tmp1 << 5); + } + } + } +#if 0 + snd_printk("mpu cfg = 0x%x\n", cfg); +#endif + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_mixer_write(chip, 0x40, cfg); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_read(chip, 0xb1); + snd_es1688_read(chip, 0xb2); + spin_unlock_irqrestore(&chip->reg_lock, flags); + if (enable) { + cfg = 0xf0; /* enable only DMA counter interrupt */ + irq_bits = irqs[chip->irq & 0x0f]; + if (irq_bits < 0) { + snd_printk("[0x%lx] ESS: bad IRQ %d for ES1688 chip!!\n", chip->port, chip->irq); +#if 0 + irq_bits = 0; + cfg = 0x10; +#endif + return -EINVAL; + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, cfg | (irq_bits << 2)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + cfg = 0xf0; /* extended mode DMA enable */ + dma = chip->dma8; + if (dma > 3 || dma == 2) { + snd_printk("[0x%lx] ESS: bad DMA channel %d for ES1688 chip!!\n", chip->port, dma); +#if 0 + dma_bits = 0; + cfg = 0x00; /* disable all DMA */ +#endif + return -EINVAL; + } else { + dma_bits = dma; + if (dma != 3) + dma_bits++; + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb2, cfg | (dma_bits << 2)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + } else { + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xb1, 0x10); /* disable IRQ */ + snd_es1688_write(chip, 0xb2, 0x00); /* disable DMA */ + spin_unlock_irqrestore(&chip->reg_lock, flags); + } + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_read(chip, 0xb1); + snd_es1688_read(chip, 0xb2); + snd_es1688_reset(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +/* + + */ + +static ratnum_t clocks[2] = { + { + .num = 795444, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 397722, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks = { + .nrats = 2, + .rats = clocks, +}; + +static void snd_es1688_set_rate(es1688_t *chip, snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int bits, divider; + + if (runtime->rate_num == clocks[0].num) + bits = 256 - runtime->rate_den; + else + bits = 128 - runtime->rate_den; + /* set filter register */ + divider = 256 - 7160000*20/(8*82*runtime->rate); + /* write result to hardware */ + snd_es1688_write(chip, 0xa1, bits); + snd_es1688_write(chip, 0xa2, divider); +} + +static int snd_es1688_ioctl(snd_pcm_substream_t * substream, + unsigned int cmd, void *arg) +{ + return snd_pcm_lib_ioctl(substream, cmd, arg); +} + +static int snd_es1688_trigger(es1688_t *chip, int cmd, unsigned char value) +{ + int val; + + if (cmd == SNDRV_PCM_TRIGGER_STOP) { + value = 0x00; + } else if (cmd != SNDRV_PCM_TRIGGER_START) { + return -EINVAL; + } + spin_lock(&chip->reg_lock); + chip->trigger_value = value; + val = snd_es1688_read(chip, 0xb8); + if ((val < 0) || (val & 0x0f) == value) { + spin_unlock(&chip->reg_lock); + return -EINVAL; /* something is wrong */ + } +#if 0 + printk("trigger: val = 0x%x, value = 0x%x\n", val, value); + printk("trigger: pointer = 0x%x\n", snd_dma_pointer(chip->dma8, chip->dma_size)); +#endif + snd_es1688_write(chip, 0xb8, (val & 0xf0) | value); + spin_unlock(&chip->reg_lock); + return 0; +} + +static int snd_es1688_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_es1688_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_es1688_playback_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + es1688_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma_size = size; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_reset(chip); + snd_es1688_set_rate(chip, substream); + snd_es1688_write(chip, 0xb8, 4); /* auto init DMA mode */ + snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels)); + snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */ + if (runtime->channels == 1) { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit mono */ + snd_es1688_write(chip, 0xb6, 0x80); + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0xd0); + } else { + /* 16. bit mono */ + snd_es1688_write(chip, 0xb6, 0x00); + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xf4); + } + } else { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit stereo */ + snd_es1688_write(chip, 0xb6, 0x80); + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0x98); + } else { + /* 16. bit stereo */ + snd_es1688_write(chip, 0xb6, 0x00); + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xbc); + } + } + snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50); + snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50); + snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKON); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + count = -count; + snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xa4, (unsigned char) count); + snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_es1688_playback_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + return snd_es1688_trigger(chip, cmd, 0x05); +} + +static int snd_es1688_capture_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + es1688_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma_size = size; + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_reset(chip); + snd_es1688_set_rate(chip, substream); + snd_es1688_dsp_command(chip, ES1688_DSP_CMD_SPKOFF); + snd_es1688_write(chip, 0xb8, 0x0e); /* auto init DMA mode */ + snd_es1688_write(chip, 0xa8, (snd_es1688_read(chip, 0xa8) & ~0x03) | (3 - runtime->channels)); + snd_es1688_write(chip, 0xb9, 2); /* demand mode (4 bytes/request) */ + if (runtime->channels == 1) { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit mono */ + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0xd0); + } else { + /* 16. bit mono */ + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xf4); + } + } else { + if (snd_pcm_format_width(runtime->format) == 8) { + /* 8. bit stereo */ + snd_es1688_write(chip, 0xb7, 0x51); + snd_es1688_write(chip, 0xb7, 0x98); + } else { + /* 16. bit stereo */ + snd_es1688_write(chip, 0xb7, 0x71); + snd_es1688_write(chip, 0xb7, 0xbc); + } + } + snd_es1688_write(chip, 0xb1, (snd_es1688_read(chip, 0xb1) & 0x0f) | 0x50); + snd_es1688_write(chip, 0xb2, (snd_es1688_read(chip, 0xb2) & 0x0f) | 0x50); + spin_unlock_irqrestore(&chip->reg_lock, flags); + /* --- */ + count = -count; + snd_dma_program(chip->dma8, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_es1688_write(chip, 0xa4, (unsigned char) count); + snd_es1688_write(chip, 0xa5, (unsigned char) (count >> 8)); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_es1688_capture_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + return snd_es1688_trigger(chip, cmd, 0x0f); +} + +static irqreturn_t snd_es1688_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + es1688_t *chip = dev_id; + + if (chip->trigger_value == 0x05) /* ok.. playback is active */ + snd_pcm_period_elapsed(chip->playback_substream); + if (chip->trigger_value == 0x0f) /* ok.. capture is active */ + snd_pcm_period_elapsed(chip->capture_substream); + + inb(ES1688P(chip, DATA_AVAIL)); /* ack interrupt */ + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_es1688_playback_pointer(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->trigger_value != 0x05) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_es1688_capture_pointer(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->trigger_value != 0x0f) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static snd_pcm_hardware_t snd_es1688_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_es1688_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + + */ + +static int snd_es1688_playback_open(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + if (chip->capture_substream != NULL) + return -EAGAIN; + chip->playback_substream = substream; + runtime->hw = snd_es1688_playback; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_es1688_capture_open(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + if (chip->playback_substream != NULL) + return -EAGAIN; + chip->capture_substream = substream; + runtime->hw = snd_es1688_capture; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_es1688_playback_close(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + return 0; +} + +static int snd_es1688_capture_close(snd_pcm_substream_t * substream) +{ + es1688_t *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + return 0; +} + +static int snd_es1688_free(es1688_t *chip) +{ + if (chip->res_port) { + snd_es1688_init(chip, 0); + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma8 >= 0) { + disable_dma(chip->dma8); + free_dma(chip->dma8); + } + kfree(chip); + return 0; +} + +static int snd_es1688_dev_free(snd_device_t *device) +{ + es1688_t *chip = device->device_data; + return snd_es1688_free(chip); +} + +static const char *snd_es1688_chip_id(es1688_t *chip) +{ + static char tmp[16]; + sprintf(tmp, "ES%s688 rev %i", chip->hardware == ES1688_HW_688 ? "" : "1", chip->version & 0x0f); + return tmp; +} + +int snd_es1688_create(snd_card_t * card, + unsigned long port, + unsigned long mpu_port, + int irq, + int mpu_irq, + int dma8, + unsigned short hardware, + es1688_t **rchip) +{ + static snd_device_ops_t ops = { + .dev_free = snd_es1688_dev_free, + }; + + es1688_t *chip; + int err; + + *rchip = NULL; + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + chip->irq = -1; + chip->dma8 = -1; + + if ((chip->res_port = request_region(port + 4, 12, "ES1688")) == NULL) { + snd_printk(KERN_ERR "es1688: can't grab port 0x%lx\n", port + 4); + snd_es1688_free(chip); + return -EBUSY; + } + if (request_irq(irq, snd_es1688_interrupt, SA_INTERRUPT, "ES1688", (void *) chip)) { + snd_printk(KERN_ERR "es1688: can't grab IRQ %d\n", irq); + snd_es1688_free(chip); + return -EBUSY; + } + chip->irq = irq; + if (request_dma(dma8, "ES1688")) { + snd_printk(KERN_ERR "es1688: can't grab DMA8 %d\n", dma8); + snd_es1688_free(chip); + return -EBUSY; + } + chip->dma8 = dma8; + + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->mixer_lock); + chip->card = card; + chip->port = port; + mpu_port &= ~0x000f; + if (mpu_port < 0x300 || mpu_port > 0x330) + mpu_port = 0; + chip->mpu_port = mpu_port; + chip->mpu_irq = mpu_irq; + chip->hardware = hardware; + + if ((err = snd_es1688_probe(chip)) < 0) { + snd_es1688_free(chip); + return err; + } + if ((err = snd_es1688_init(chip, 1)) < 0) { + snd_es1688_free(chip); + return err; + } + + /* Register device */ + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_es1688_free(chip); + return err; + } + + *rchip = chip; + return 0; +} + +static snd_pcm_ops_t snd_es1688_playback_ops = { + .open = snd_es1688_playback_open, + .close = snd_es1688_playback_close, + .ioctl = snd_es1688_ioctl, + .hw_params = snd_es1688_hw_params, + .hw_free = snd_es1688_hw_free, + .prepare = snd_es1688_playback_prepare, + .trigger = snd_es1688_playback_trigger, + .pointer = snd_es1688_playback_pointer, +}; + +static snd_pcm_ops_t snd_es1688_capture_ops = { + .open = snd_es1688_capture_open, + .close = snd_es1688_capture_close, + .ioctl = snd_es1688_ioctl, + .hw_params = snd_es1688_hw_params, + .hw_free = snd_es1688_hw_free, + .prepare = snd_es1688_capture_prepare, + .trigger = snd_es1688_capture_trigger, + .pointer = snd_es1688_capture_pointer, +}; + +static void snd_es1688_pcm_free(snd_pcm_t *pcm) +{ + es1688_t *chip = pcm->private_data; + chip->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_es1688_pcm(es1688_t * chip, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_pcm_new(chip->card, "ESx688", device, 1, 1, &pcm)) < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es1688_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es1688_capture_ops); + + pcm->private_data = chip; + pcm->private_free = snd_es1688_pcm_free; + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + sprintf(pcm->name, snd_es1688_chip_id(chip)); + chip->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 64*1024); + + if (rpcm) + *rpcm = pcm; + return 0; +} + +/* + * MIXER part + */ + +static int snd_es1688_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[9] = { + "Mic", "Mic Master", "CD", "AOUT", + "Mic1", "Mix", "Line", "Master" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 8; + if (uinfo->value.enumerated.item > 7) + uinfo->value.enumerated.item = 7; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_es1688_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_t *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = snd_es1688_mixer_read(chip, ES1688_REC_DEV) & 7; + return 0; +} + +static int snd_es1688_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval, nval; + int change; + + if (ucontrol->value.enumerated.item[0] > 8) + return -EINVAL; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_es1688_mixer_read(chip, ES1688_REC_DEV); + nval = (ucontrol->value.enumerated.item[0] & 7) | (oval & ~15); + change = nval != oval; + if (change) + snd_es1688_mixer_write(chip, ES1688_REC_DEV, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define ES1688_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es1688_info_single, \ + .get = snd_es1688_get_single, .put = snd_es1688_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_es1688_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_es1688_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_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] = (snd_es1688_mixer_read(chip, 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_es1688_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_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 char oval, nval; + + nval = (ucontrol->value.integer.value[0] & mask); + if (invert) + nval = mask - nval; + nval <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_es1688_mixer_read(chip, reg); + nval = (oval & ~(mask << shift)) | nval; + change = nval != oval; + if (change) + snd_es1688_mixer_write(chip, reg, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define ES1688_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_es1688_info_double, \ + .get = snd_es1688_get_double, .put = snd_es1688_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_es1688_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_es1688_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_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; + unsigned char left, right; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (left_reg < 0xa0) + left = snd_es1688_mixer_read(chip, left_reg); + else + left = snd_es1688_read(chip, left_reg); + if (left_reg != right_reg) { + if (right_reg < 0xa0) + right = snd_es1688_mixer_read(chip, right_reg); + else + right = snd_es1688_read(chip, right_reg); + } else + right = left; + spin_unlock_irqrestore(&chip->reg_lock, flags); + ucontrol->value.integer.value[0] = (left >> shift_left) & mask; + ucontrol->value.integer.value[1] = (right >> shift_right) & mask; + 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_es1688_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es1688_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 char val1, val2, oval1, oval2; + + 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) { + if (left_reg < 0xa0) + oval1 = snd_es1688_mixer_read(chip, left_reg); + else + oval1 = snd_es1688_read(chip, left_reg); + if (right_reg < 0xa0) + oval2 = snd_es1688_mixer_read(chip, right_reg); + else + oval2 = snd_es1688_read(chip, right_reg); + val1 = (oval1 & ~(mask << shift_left)) | val1; + val2 = (oval2 & ~(mask << shift_right)) | val2; + change = val1 != oval1 || val2 != oval2; + if (change) { + if (left_reg < 0xa0) + snd_es1688_mixer_write(chip, left_reg, val1); + else + snd_es1688_write(chip, left_reg, val1); + if (right_reg < 0xa0) + snd_es1688_mixer_write(chip, right_reg, val1); + else + snd_es1688_write(chip, right_reg, val1); + } + } else { + if (left_reg < 0xa0) + oval1 = snd_es1688_mixer_read(chip, left_reg); + else + oval1 = snd_es1688_read(chip, left_reg); + val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != oval1; + if (change) { + if (left_reg < 0xa0) + snd_es1688_mixer_write(chip, left_reg, val1); + else + snd_es1688_write(chip, left_reg, val1); + } + + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_es1688_controls[] = { +ES1688_DOUBLE("Master Playback Volume", 0, ES1688_MASTER_DEV, ES1688_MASTER_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("PCM Playback Volume", 0, ES1688_PCM_DEV, ES1688_PCM_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Line Playback Volume", 0, ES1688_LINE_DEV, ES1688_LINE_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("CD Playback Volume", 0, ES1688_CD_DEV, ES1688_CD_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("FM Playback Volume", 0, ES1688_FM_DEV, ES1688_FM_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Mic Playback Volume", 0, ES1688_MIC_DEV, ES1688_MIC_DEV, 4, 0, 15, 0), +ES1688_DOUBLE("Aux Playback Volume", 0, ES1688_AUX_DEV, ES1688_AUX_DEV, 4, 0, 15, 0), +ES1688_SINGLE("PC Speaker Playback Volume", 0, ES1688_SPEAKER_DEV, 0, 7, 0), +ES1688_DOUBLE("Capture Volume", 0, ES1688_RECLEV_DEV, ES1688_RECLEV_DEV, 4, 0, 15, 0), +ES1688_SINGLE("Capture Switch", 0, ES1688_REC_DEV, 4, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_es1688_info_mux, + .get = snd_es1688_get_mux, + .put = snd_es1688_put_mux, +}, +}; + +#define ES1688_INIT_TABLE_SIZE (sizeof(snd_es1688_init_table)/2) + +static unsigned char snd_es1688_init_table[][2] = { + { ES1688_MASTER_DEV, 0 }, + { ES1688_PCM_DEV, 0 }, + { ES1688_LINE_DEV, 0 }, + { ES1688_CD_DEV, 0 }, + { ES1688_FM_DEV, 0 }, + { ES1688_MIC_DEV, 0 }, + { ES1688_AUX_DEV, 0 }, + { ES1688_SPEAKER_DEV, 0 }, + { ES1688_RECLEV_DEV, 0 }, + { ES1688_REC_DEV, 0x17 } +}; + +int snd_es1688_mixer(es1688_t *chip) +{ + snd_card_t *card; + unsigned int idx; + int err; + unsigned char reg, val; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + + card = chip->card; + + strcpy(card->mixername, snd_es1688_chip_id(chip)); + + for (idx = 0; idx < ARRAY_SIZE(snd_es1688_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es1688_controls[idx], chip))) < 0) + return err; + } + for (idx = 0; idx < ES1688_INIT_TABLE_SIZE; idx++) { + reg = snd_es1688_init_table[idx][0]; + val = snd_es1688_init_table[idx][1]; + if (reg < 0xa0) + snd_es1688_mixer_write(chip, reg, val); + else + snd_es1688_write(chip, reg, val); + } + return 0; +} + +EXPORT_SYMBOL(snd_es1688_mixer_write); +EXPORT_SYMBOL(snd_es1688_create); +EXPORT_SYMBOL(snd_es1688_pcm); +EXPORT_SYMBOL(snd_es1688_mixer); + +/* + * INIT part + */ + +static int __init alsa_es1688_init(void) +{ + return 0; +} + +static void __exit alsa_es1688_exit(void) +{ +} + +module_init(alsa_es1688_init) +module_exit(alsa_es1688_exit) diff --git a/sound/isa/es18xx.c b/sound/isa/es18xx.c new file mode 100644 index 00000000000..1d832b2adb7 --- /dev/null +++ b/sound/isa/es18xx.c @@ -0,0 +1,2224 @@ +/* + * Driver for generic ESS AudioDrive ES18xx soundcards + * Copyright (c) by Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de> + * Copyright (c) by Abramo Bagnara <abramo@alsa-project.org> + * + * + * 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 + * + */ +/* GENERAL NOTES: + * + * BUGS: + * - There are pops (we can't delay in trigger function, cause midlevel + * often need to trigger down and then up very quickly). + * Any ideas? + * - Support for 16 bit DMA seems to be broken. I've no hardware to tune it. + */ + +/* + * ES1868 NOTES: + * - The chip has one half duplex pcm (with very limited full duplex support). + * + * - Duplex stereophonic sound is impossible. + * - Record and playback must share the same frequency rate. + * + * - The driver use dma2 for playback and dma1 for capture. + */ + +/* + * ES1869 NOTES: + * + * - there are a first full duplex pcm and a second playback only pcm + * (incompatible with first pcm capture) + * + * - there is support for the capture volume and ESS Spatializer 3D effect. + * + * - contrarily to some pages in DS_1869.PDF the rates can be set + * independently. + * + * BUGS: + * + * - There is a major trouble I noted: + * + * using both channel for playback stereo 16 bit samples at 44100 Hz + * the second pcm (Audio1) DMA slows down irregularly and sound is garbled. + * + * The same happens using Audio1 for captureing. + * + * The Windows driver does not suffer of this (although it use Audio1 + * only for captureing). I'm unable to discover why. + * + */ + + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/init.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/isapnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +#define PFX "es18xx: " + +struct _snd_es18xx { + unsigned long port; /* port of ESS chip */ + unsigned long mpu_port; /* MPU-401 port of ESS chip */ + unsigned long fm_port; /* FM port */ + unsigned long ctrl_port; /* Control port of ESS chip */ + struct resource *res_port; + struct resource *res_mpu_port; + struct resource *res_ctrl_port; + int irq; /* IRQ number of ESS chip */ + int dma1; /* DMA1 */ + int dma2; /* DMA2 */ + unsigned short version; /* version of ESS chip */ + int caps; /* Chip capabilities */ + unsigned short audio2_vol; /* volume level of audio2 */ + + unsigned short active; /* active channel mask */ + unsigned int dma1_size; + unsigned int dma2_size; + unsigned int dma1_shift; + unsigned int dma2_shift; + + snd_card_t *card; + snd_pcm_t *pcm; + snd_pcm_substream_t *playback_a_substream; + snd_pcm_substream_t *capture_a_substream; + snd_pcm_substream_t *playback_b_substream; + + snd_rawmidi_t *rmidi; + + snd_kcontrol_t *hw_volume; + snd_kcontrol_t *hw_switch; + snd_kcontrol_t *master_volume; + snd_kcontrol_t *master_switch; + + spinlock_t reg_lock; + spinlock_t mixer_lock; + spinlock_t ctrl_lock; +#ifdef CONFIG_PM + unsigned char pm_reg; +#endif +}; + +#define AUDIO1_IRQ 0x01 +#define AUDIO2_IRQ 0x02 +#define HWV_IRQ 0x04 +#define MPU_IRQ 0x08 + +#define ES18XX_PCM2 0x0001 /* Has two useable PCM */ +#define ES18XX_SPATIALIZER 0x0002 /* Has 3D Spatializer */ +#define ES18XX_RECMIX 0x0004 /* Has record mixer */ +#define ES18XX_DUPLEX_MONO 0x0008 /* Has mono duplex only */ +#define ES18XX_DUPLEX_SAME 0x0010 /* Playback and record must share the same rate */ +#define ES18XX_NEW_RATE 0x0020 /* More precise rate setting */ +#define ES18XX_AUXB 0x0040 /* AuxB mixer control */ +#define ES18XX_HWV 0x0080 /* Has hardware volume */ +#define ES18XX_MONO 0x0100 /* Mono_in mixer control */ +#define ES18XX_I2S 0x0200 /* I2S mixer control */ +#define ES18XX_MUTEREC 0x0400 /* Record source can be muted */ +#define ES18XX_CONTROL 0x0800 /* Has control ports */ + +/* Power Management */ +#define ES18XX_PM 0x07 +#define ES18XX_PM_GPO0 0x01 +#define ES18XX_PM_GPO1 0x02 +#define ES18XX_PM_PDR 0x04 +#define ES18XX_PM_ANA 0x08 +#define ES18XX_PM_FM 0x020 +#define ES18XX_PM_SUS 0x080 + +typedef struct _snd_es18xx es18xx_t; + +/* Lowlevel */ + +#define DAC1 0x01 +#define ADC1 0x02 +#define DAC2 0x04 +#define MILLISECOND 10000 + +static int snd_es18xx_dsp_command(es18xx_t *chip, unsigned char val) +{ + int i; + + for(i = MILLISECOND; i; i--) + if ((inb(chip->port + 0x0C) & 0x80) == 0) { + outb(val, chip->port + 0x0C); + return 0; + } + snd_printk("dsp_command: timeout (0x%x)\n", val); + return -EINVAL; +} + +static int snd_es18xx_dsp_get_byte(es18xx_t *chip) +{ + int i; + + for(i = MILLISECOND/10; i; i--) + if (inb(chip->port + 0x0C) & 0x40) + return inb(chip->port + 0x0A); + snd_printk("dsp_get_byte failed: 0x%lx = 0x%x!!!\n", chip->port + 0x0A, inb(chip->port + 0x0A)); + return -ENODEV; +} + +#undef REG_DEBUG + +static int snd_es18xx_write(es18xx_t *chip, + unsigned char reg, unsigned char data) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, data); + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); +#ifdef REG_DEBUG + snd_printk("Reg %02x set to %02x\n", reg, data); +#endif + return ret; +} + +static int snd_es18xx_read(es18xx_t *chip, unsigned char reg) +{ + unsigned long flags; + int ret, data; + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, 0xC0); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + data = snd_es18xx_dsp_get_byte(chip); + ret = data; +#ifdef REG_DEBUG + snd_printk("Reg %02x now is %02x (%d)\n", reg, data, ret); +#endif + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return ret; +} + +/* Return old value */ +static int snd_es18xx_bits(es18xx_t *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + int ret; + unsigned char old, new, oval; + unsigned long flags; + spin_lock_irqsave(&chip->reg_lock, flags); + ret = snd_es18xx_dsp_command(chip, 0xC0); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + ret = snd_es18xx_dsp_get_byte(chip); + if (ret < 0) { + goto end; + } + old = ret; + oval = old & mask; + if (val != oval) { + ret = snd_es18xx_dsp_command(chip, reg); + if (ret < 0) + goto end; + new = (old & ~mask) | (val & mask); + ret = snd_es18xx_dsp_command(chip, new); + if (ret < 0) + goto end; +#ifdef REG_DEBUG + snd_printk("Reg %02x was %02x, set to %02x (%d)\n", reg, old, new, ret); +#endif + } + ret = oval; + end: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return ret; +} + +static inline void snd_es18xx_mixer_write(es18xx_t *chip, + unsigned char reg, unsigned char data) +{ + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + outb(data, chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk("Mixer reg %02x set to %02x\n", reg, data); +#endif +} + +static inline int snd_es18xx_mixer_read(es18xx_t *chip, unsigned char reg) +{ + unsigned long flags; + int data; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + data = inb(chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk("Mixer reg %02x now is %02x\n", reg, data); +#endif + return data; +} + +/* Return old value */ +static inline int snd_es18xx_mixer_bits(es18xx_t *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + unsigned char old, new, oval; + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + old = inb(chip->port + 0x05); + oval = old & mask; + if (val != oval) { + new = (old & ~mask) | (val & mask); + outb(new, chip->port + 0x05); +#ifdef REG_DEBUG + snd_printk("Mixer reg %02x was %02x, set to %02x\n", reg, old, new); +#endif + } + spin_unlock_irqrestore(&chip->mixer_lock, flags); + return oval; +} + +static inline int snd_es18xx_mixer_writable(es18xx_t *chip, unsigned char reg, + unsigned char mask) +{ + int old, expected, new; + unsigned long flags; + spin_lock_irqsave(&chip->mixer_lock, flags); + outb(reg, chip->port + 0x04); + old = inb(chip->port + 0x05); + expected = old ^ mask; + outb(expected, chip->port + 0x05); + new = inb(chip->port + 0x05); + spin_unlock_irqrestore(&chip->mixer_lock, flags); +#ifdef REG_DEBUG + snd_printk("Mixer reg %02x was %02x, set to %02x, now is %02x\n", reg, old, expected, new); +#endif + return expected == new; +} + + +static int snd_es18xx_reset(es18xx_t *chip) +{ + int i; + outb(0x03, chip->port + 0x06); + inb(chip->port + 0x06); + outb(0x00, chip->port + 0x06); + for(i = 0; i < MILLISECOND && !(inb(chip->port + 0x0E) & 0x80); i++); + if (inb(chip->port + 0x0A) != 0xAA) + return -1; + return 0; +} + +static int snd_es18xx_reset_fifo(es18xx_t *chip) +{ + outb(0x02, chip->port + 0x06); + inb(chip->port + 0x06); + outb(0x00, chip->port + 0x06); + return 0; +} + +static ratnum_t new_clocks[2] = { + { + .num = 793800, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 768000, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static snd_pcm_hw_constraint_ratnums_t new_hw_constraints_clocks = { + .nrats = 2, + .rats = new_clocks, +}; + +static ratnum_t old_clocks[2] = { + { + .num = 795444, + .den_min = 1, + .den_max = 128, + .den_step = 1, + }, + { + .num = 397722, + .den_min = 1, + .den_max = 128, + .den_step = 1, + } +}; + +static snd_pcm_hw_constraint_ratnums_t old_hw_constraints_clocks = { + .nrats = 2, + .rats = old_clocks, +}; + + +static void snd_es18xx_rate_set(es18xx_t *chip, + snd_pcm_substream_t *substream, + int mode) +{ + unsigned int bits, div0; + snd_pcm_runtime_t *runtime = substream->runtime; + if (chip->caps & ES18XX_NEW_RATE) { + if (runtime->rate_num == new_clocks[0].num) + bits = 128 - runtime->rate_den; + else + bits = 256 - runtime->rate_den; + } else { + if (runtime->rate_num == old_clocks[0].num) + bits = 256 - runtime->rate_den; + else + bits = 128 - runtime->rate_den; + } + + /* set filter register */ + div0 = 256 - 7160000*20/(8*82*runtime->rate); + + if ((chip->caps & ES18XX_PCM2) && mode == DAC2) { + snd_es18xx_mixer_write(chip, 0x70, bits); + /* + * Comment from kernel oss driver: + * FKS: fascinating: 0x72 doesn't seem to work. + */ + snd_es18xx_write(chip, 0xA2, div0); + snd_es18xx_mixer_write(chip, 0x72, div0); + } else { + snd_es18xx_write(chip, 0xA1, bits); + snd_es18xx_write(chip, 0xA2, div0); + } +} + +static int snd_es18xx_playback_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + int shift, err; + + shift = 0; + if (params_channels(hw_params) == 2) + shift++; + if (snd_pcm_format_width(params_format(hw_params)) == 16) + shift++; + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if ((chip->caps & ES18XX_DUPLEX_MONO) && + (chip->capture_a_substream) && + params_channels(hw_params) != 1) { + _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS); + return -EBUSY; + } + chip->dma2_shift = shift; + } else { + chip->dma1_shift = shift; + } + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + return 0; +} + +static int snd_es18xx_pcm_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_es18xx_playback1_prepare(es18xx_t *chip, + snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma2_size = size; + + snd_es18xx_rate_set(chip, substream, DAC2); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_mixer_write(chip, 0x74, count & 0xff); + snd_es18xx_mixer_write(chip, 0x76, count >> 8); + + /* Set format */ + snd_es18xx_mixer_bits(chip, 0x7A, 0x07, + ((runtime->channels == 1) ? 0x00 : 0x02) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x01 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x04)); + + /* Set DMA controller */ + snd_dma_program(chip->dma2, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_playback1_trigger(es18xx_t *chip, + snd_pcm_substream_t * substream, + int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & DAC2) + return 0; + chip->active |= DAC2; + /* Start DMA */ + if (chip->dma2 >= 4) + snd_es18xx_mixer_write(chip, 0x78, 0xb3); + else + snd_es18xx_mixer_write(chip, 0x78, 0x93); +#ifdef AVOID_POPS + /* Avoid pops */ + udelay(100000); + if (chip->caps & ES18XX_PCM2) + /* Restore Audio 2 volume */ + snd_es18xx_mixer_write(chip, 0x7C, chip->audio2_vol); + else + /* Enable PCM output */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & DAC2)) + return 0; + chip->active &= ~DAC2; + /* Stop DMA */ + snd_es18xx_mixer_write(chip, 0x78, 0x00); +#ifdef AVOID_POPS + udelay(25000); + if (chip->caps & ES18XX_PCM2) + /* Set Audio 2 volume to 0 */ + snd_es18xx_mixer_write(chip, 0x7C, 0); + else + /* Disable PCM output */ + snd_es18xx_dsp_command(chip, 0xD3); +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_capture_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + int shift, err; + + shift = 0; + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->playback_a_substream && + params_channels(hw_params) != 1) { + _snd_pcm_hw_param_setempty(hw_params, SNDRV_PCM_HW_PARAM_CHANNELS); + return -EBUSY; + } + if (params_channels(hw_params) == 2) + shift++; + if (snd_pcm_format_width(params_format(hw_params)) == 16) + shift++; + chip->dma1_shift = shift; + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + return 0; +} + +static int snd_es18xx_capture_prepare(snd_pcm_substream_t *substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma1_size = size; + + snd_es18xx_reset_fifo(chip); + + /* Set stereo/mono */ + snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01); + + snd_es18xx_rate_set(chip, substream, ADC1); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_write(chip, 0xA4, count & 0xff); + snd_es18xx_write(chip, 0xA5, count >> 8); + +#ifdef AVOID_POPS + udelay(100000); +#endif + + /* Set format */ + snd_es18xx_write(chip, 0xB7, + snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71); + snd_es18xx_write(chip, 0xB7, 0x90 | + ((runtime->channels == 1) ? 0x40 : 0x08) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20)); + + /* Set DMA controler */ + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_capture_trigger(snd_pcm_substream_t *substream, + int cmd) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & ADC1) + return 0; + chip->active |= ADC1; + /* Start DMA */ + snd_es18xx_write(chip, 0xB8, 0x0f); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & ADC1)) + return 0; + chip->active &= ~ADC1; + /* Stop DMA */ + snd_es18xx_write(chip, 0xB8, 0x00); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_playback2_prepare(es18xx_t *chip, + snd_pcm_substream_t *substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + unsigned int count = snd_pcm_lib_period_bytes(substream); + + chip->dma1_size = size; + + snd_es18xx_reset_fifo(chip); + + /* Set stereo/mono */ + snd_es18xx_bits(chip, 0xA8, 0x03, runtime->channels == 1 ? 0x02 : 0x01); + + snd_es18xx_rate_set(chip, substream, DAC1); + + /* Transfer Count Reload */ + count = 0x10000 - count; + snd_es18xx_write(chip, 0xA4, count & 0xff); + snd_es18xx_write(chip, 0xA5, count >> 8); + + /* Set format */ + snd_es18xx_write(chip, 0xB6, + snd_pcm_format_unsigned(runtime->format) ? 0x80 : 0x00); + snd_es18xx_write(chip, 0xB7, + snd_pcm_format_unsigned(runtime->format) ? 0x51 : 0x71); + snd_es18xx_write(chip, 0xB7, 0x90 | + (runtime->channels == 1 ? 0x40 : 0x08) | + (snd_pcm_format_width(runtime->format) == 16 ? 0x04 : 0x00) | + (snd_pcm_format_unsigned(runtime->format) ? 0x00 : 0x20)); + + /* Set DMA controler */ + snd_dma_program(chip->dma1, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + return 0; +} + +static int snd_es18xx_playback2_trigger(es18xx_t *chip, + snd_pcm_substream_t *substream, + int cmd) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + if (chip->active & DAC1) + return 0; + chip->active |= DAC1; + /* Start DMA */ + snd_es18xx_write(chip, 0xB8, 0x05); +#ifdef AVOID_POPS + /* Avoid pops */ + udelay(100000); + /* Enable Audio 1 */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (!(chip->active & DAC1)) + return 0; + chip->active &= ~DAC1; + /* Stop DMA */ + snd_es18xx_write(chip, 0xB8, 0x00); +#ifdef AVOID_POPS + /* Avoid pops */ + udelay(25000); + /* Disable Audio 1 */ + snd_es18xx_dsp_command(chip, 0xD3); +#endif + break; + default: + return -EINVAL; + } + + return 0; +} + +static int snd_es18xx_playback_prepare(snd_pcm_substream_t *substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + return snd_es18xx_playback1_prepare(chip, substream); + else + return snd_es18xx_playback2_prepare(chip, substream); +} + +static int snd_es18xx_playback_trigger(snd_pcm_substream_t *substream, + int cmd) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + return snd_es18xx_playback1_trigger(chip, substream, cmd); + else + return snd_es18xx_playback2_trigger(chip, substream, cmd); +} + +static irqreturn_t snd_es18xx_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + es18xx_t *chip = dev_id; + unsigned char status; + + if (chip->caps & ES18XX_CONTROL) { + /* Read Interrupt status */ + status = inb(chip->ctrl_port + 6); + } else { + /* Read Interrupt status */ + status = snd_es18xx_mixer_read(chip, 0x7f) >> 4; + } +#if 0 + else { + status = 0; + if (inb(chip->port + 0x0C) & 0x01) + status |= AUDIO1_IRQ; + if (snd_es18xx_mixer_read(chip, 0x7A) & 0x80) + status |= AUDIO2_IRQ; + if ((chip->caps & ES18XX_HWV) && + snd_es18xx_mixer_read(chip, 0x64) & 0x10) + status |= HWV_IRQ; + } +#endif + + /* Audio 1 & Audio 2 */ + if (status & AUDIO2_IRQ) { + if (chip->active & DAC2) + snd_pcm_period_elapsed(chip->playback_a_substream); + /* ack interrupt */ + snd_es18xx_mixer_bits(chip, 0x7A, 0x80, 0x00); + } + if (status & AUDIO1_IRQ) { + /* ok.. capture is active */ + if (chip->active & ADC1) + snd_pcm_period_elapsed(chip->capture_a_substream); + /* ok.. playback2 is active */ + else if (chip->active & DAC1) + snd_pcm_period_elapsed(chip->playback_b_substream); + /* ack interrupt */ + inb(chip->port + 0x0E); + } + + /* MPU */ + if ((status & MPU_IRQ) && chip->rmidi) + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs); + + /* Hardware volume */ + if (status & HWV_IRQ) { + int split = snd_es18xx_mixer_read(chip, 0x64) & 0x80; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_switch->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->hw_volume->id); + if (!split) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_switch->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_volume->id); + } + /* ack interrupt */ + snd_es18xx_mixer_write(chip, 0x66, 0x00); + } + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_es18xx_playback_pointer(snd_pcm_substream_t * substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + int pos; + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if (!(chip->active & DAC2)) + return 0; + pos = snd_dma_pointer(chip->dma2, chip->dma2_size); + return pos >> chip->dma2_shift; + } else { + if (!(chip->active & DAC1)) + return 0; + pos = snd_dma_pointer(chip->dma1, chip->dma1_size); + return pos >> chip->dma1_shift; + } +} + +static snd_pcm_uframes_t snd_es18xx_capture_pointer(snd_pcm_substream_t * substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + int pos; + + if (!(chip->active & ADC1)) + return 0; + pos = snd_dma_pointer(chip->dma1, chip->dma1_size); + return pos >> chip->dma1_shift; +} + +static snd_pcm_hardware_t snd_es18xx_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_es18xx_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static int snd_es18xx_playback_open(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + es18xx_t *chip = snd_pcm_substream_chip(substream); + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) { + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->capture_a_substream && + chip->capture_a_substream->runtime->channels != 1) + return -EAGAIN; + chip->playback_a_substream = substream; + } else if (substream->number <= 1) { + if (chip->capture_a_substream) + return -EAGAIN; + chip->playback_b_substream = substream; + } else { + snd_BUG(); + return -EINVAL; + } + substream->runtime->hw = snd_es18xx_playback; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks); + return 0; +} + +static int snd_es18xx_capture_open(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + es18xx_t *chip = snd_pcm_substream_chip(substream); + + if (chip->playback_b_substream) + return -EAGAIN; + if ((chip->caps & ES18XX_DUPLEX_MONO) && + chip->playback_a_substream && + chip->playback_a_substream->runtime->channels != 1) + return -EAGAIN; + chip->capture_a_substream = substream; + substream->runtime->hw = snd_es18xx_capture; + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + (chip->caps & ES18XX_NEW_RATE) ? &new_hw_constraints_clocks : &old_hw_constraints_clocks); + return 0; +} + +static int snd_es18xx_playback_close(snd_pcm_substream_t * substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + + if (substream->number == 0 && (chip->caps & ES18XX_PCM2)) + chip->playback_a_substream = NULL; + else + chip->playback_b_substream = NULL; + + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_es18xx_capture_close(snd_pcm_substream_t * substream) +{ + es18xx_t *chip = snd_pcm_substream_chip(substream); + + chip->capture_a_substream = NULL; + snd_pcm_lib_free_pages(substream); + return 0; +} + +/* + * MIXER part + */ + +static int snd_es18xx_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[8] = { + "Mic", "Mic Master", "CD", "AOUT", + "Mic1", "Mix", "Line", "Master" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 8; + if (uinfo->value.enumerated.item > 7) + uinfo->value.enumerated.item = 7; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_es18xx_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = snd_es18xx_mixer_read(chip, 0x1c) & 0x07; + return 0; +} + +static int snd_es18xx_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = ucontrol->value.enumerated.item[0]; + + if (val > 7) + return -EINVAL; + return snd_es18xx_mixer_bits(chip, 0x1c, 0x07, val) != val; +} + +static int snd_es18xx_info_spatializer_enable(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_es18xx_get_spatializer_enable(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + unsigned char val = snd_es18xx_mixer_read(chip, 0x50); + ucontrol->value.integer.value[0] = !!(val & 8); + return 0; +} + +static int snd_es18xx_put_spatializer_enable(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + unsigned char oval, nval; + int change; + nval = ucontrol->value.integer.value[0] ? 0x0c : 0x04; + oval = snd_es18xx_mixer_read(chip, 0x50) & 0x0c; + change = nval != oval; + if (change) { + snd_es18xx_mixer_write(chip, 0x50, nval & ~0x04); + snd_es18xx_mixer_write(chip, 0x50, nval); + } + return change; +} + +static int snd_es18xx_info_hw_volume(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 63; + return 0; +} + +static int snd_es18xx_get_hw_volume(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = snd_es18xx_mixer_read(chip, 0x61) & 0x3f; + ucontrol->value.integer.value[1] = snd_es18xx_mixer_read(chip, 0x63) & 0x3f; + return 0; +} + +static int snd_es18xx_info_hw_switch(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_es18xx_get_hw_switch(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = !(snd_es18xx_mixer_read(chip, 0x61) & 0x40); + ucontrol->value.integer.value[1] = !(snd_es18xx_mixer_read(chip, 0x63) & 0x40); + return 0; +} + +static void snd_es18xx_hwv_free(snd_kcontrol_t *kcontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + chip->master_volume = NULL; + chip->master_switch = NULL; + chip->hw_volume = NULL; + chip->hw_switch = NULL; +} + +static int snd_es18xx_reg_bits(es18xx_t *chip, unsigned char reg, + unsigned char mask, unsigned char val) +{ + if (reg < 0xa0) + return snd_es18xx_mixer_bits(chip, reg, mask, val); + else + return snd_es18xx_bits(chip, reg, mask, val); +} + +static int snd_es18xx_reg_read(es18xx_t *chip, unsigned char reg) +{ + if (reg < 0xa0) + return snd_es18xx_mixer_read(chip, reg); + else + return snd_es18xx_read(chip, reg); +} + +#define ES18XX_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_es18xx_info_single, \ + .get = snd_es18xx_get_single, .put = snd_es18xx_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_es18xx_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_es18xx_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + 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 val; + + val = snd_es18xx_reg_read(chip, reg); + ucontrol->value.integer.value[0] = (val >> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_es18xx_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + 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; + unsigned char val; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + mask <<= shift; + val <<= shift; + return snd_es18xx_reg_bits(chip, reg, mask, val) != val; +} + +#define ES18XX_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_es18xx_info_double, \ + .get = snd_es18xx_get_double, .put = snd_es18xx_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_es18xx_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_es18xx_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + 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; + unsigned char left, right; + + left = snd_es18xx_reg_read(chip, left_reg); + if (left_reg != right_reg) + right = snd_es18xx_reg_read(chip, right_reg); + else + right = left; + ucontrol->value.integer.value[0] = (left >> shift_left) & mask; + ucontrol->value.integer.value[1] = (right >> shift_right) & mask; + 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_es18xx_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + es18xx_t *chip = snd_kcontrol_chip(kcontrol); + 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 char val1, val2, mask1, mask2; + + 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; + mask1 = mask << shift_left; + mask2 = mask << shift_right; + if (left_reg != right_reg) { + change = 0; + if (snd_es18xx_reg_bits(chip, left_reg, mask1, val1) != val1) + change = 1; + if (snd_es18xx_reg_bits(chip, right_reg, mask2, val2) != val2) + change = 1; + } else { + change = (snd_es18xx_reg_bits(chip, left_reg, mask1 | mask2, + val1 | val2) != (val1 | val2)); + } + return change; +} + +static snd_kcontrol_new_t snd_es18xx_base_controls[] = { +ES18XX_DOUBLE("Master Playback Volume", 0, 0x60, 0x62, 0, 0, 63, 0), +ES18XX_DOUBLE("Master Playback Switch", 0, 0x60, 0x62, 6, 6, 1, 1), +ES18XX_DOUBLE("Line Playback Volume", 0, 0x3e, 0x3e, 4, 0, 15, 0), +ES18XX_DOUBLE("CD Playback Volume", 0, 0x38, 0x38, 4, 0, 15, 0), +ES18XX_DOUBLE("FM Playback Volume", 0, 0x36, 0x36, 4, 0, 15, 0), +ES18XX_DOUBLE("Mono Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0), +ES18XX_DOUBLE("Mic Playback Volume", 0, 0x1a, 0x1a, 4, 0, 15, 0), +ES18XX_DOUBLE("Aux Playback Volume", 0, 0x3a, 0x3a, 4, 0, 15, 0), +ES18XX_SINGLE("PC Speaker Playback Volume", 0, 0x3c, 0, 7, 0), +ES18XX_SINGLE("Record Monitor", 0, 0xa8, 3, 1, 0), +ES18XX_DOUBLE("Capture Volume", 0, 0xb4, 0xb4, 4, 0, 15, 0), +ES18XX_SINGLE("Capture Switch", 0, 0x1c, 4, 1, 1), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_es18xx_info_mux, + .get = snd_es18xx_get_mux, + .put = snd_es18xx_put_mux, +} +}; + +static snd_kcontrol_new_t snd_es18xx_mono_in_control = +ES18XX_DOUBLE("Mono Input Playback Volume", 0, 0x6d, 0x6d, 4, 0, 15, 0); + +static snd_kcontrol_new_t snd_es18xx_recmix_controls[] = { +ES18XX_DOUBLE("PCM Capture Volume", 0, 0x69, 0x69, 4, 0, 15, 0), +ES18XX_DOUBLE("Mic Capture Volume", 0, 0x68, 0x68, 4, 0, 15, 0), +ES18XX_DOUBLE("Line Capture Volume", 0, 0x6e, 0x6e, 4, 0, 15, 0), +ES18XX_DOUBLE("FM Capture Volume", 0, 0x6b, 0x6b, 4, 0, 15, 0), +ES18XX_DOUBLE("Mono Capture Volume", 0, 0x6f, 0x6f, 4, 0, 15, 0), +ES18XX_DOUBLE("CD Capture Volume", 0, 0x6a, 0x6a, 4, 0, 15, 0), +ES18XX_DOUBLE("Aux Capture Volume", 0, 0x6c, 0x6c, 4, 0, 15, 0) +}; + +static snd_kcontrol_new_t snd_es18xx_pcm1_controls[] = { +ES18XX_DOUBLE("PCM Playback Volume", 0, 0x14, 0x14, 4, 0, 15, 0), +}; + +static snd_kcontrol_new_t snd_es18xx_pcm2_controls[] = { +ES18XX_DOUBLE("PCM Playback Volume", 0, 0x7c, 0x7c, 4, 0, 15, 0), +ES18XX_DOUBLE("PCM Playback Volume", 1, 0x14, 0x14, 4, 0, 15, 0) +}; + +static snd_kcontrol_new_t snd_es18xx_spatializer_controls[] = { +ES18XX_SINGLE("3D Control - Level", 0, 0x52, 0, 63, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Switch", + .info = snd_es18xx_info_spatializer_enable, + .get = snd_es18xx_get_spatializer_enable, + .put = snd_es18xx_put_spatializer_enable, +} +}; + +static snd_kcontrol_new_t snd_es18xx_micpre1_control = +ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0xa9, 2, 1, 0); + +static snd_kcontrol_new_t snd_es18xx_micpre2_control = +ES18XX_SINGLE("Mic Boost (+26dB)", 0, 0x7d, 3, 1, 0); + +static snd_kcontrol_new_t snd_es18xx_hw_volume_controls[] = { +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Hardware Master Playback Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_es18xx_info_hw_volume, + .get = snd_es18xx_get_hw_volume, +}, +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Hardware Master Playback Switch", + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = snd_es18xx_info_hw_switch, + .get = snd_es18xx_get_hw_switch, +}, +ES18XX_SINGLE("Hardware Master Volume Split", 0, 0x64, 7, 1, 0), +}; + +#if 0 +static int __devinit snd_es18xx_config_read(es18xx_t *chip, unsigned char reg) +{ + int data; + unsigned long flags; + spin_lock_irqsave(&chip->ctrl_lock, flags); + outb(reg, chip->ctrl_port); + data = inb(chip->ctrl_port + 1); + spin_unlock_irqrestore(&chip->ctrl_lock, flags); + return data; +} +#endif + +static void __devinit snd_es18xx_config_write(es18xx_t *chip, + unsigned char reg, unsigned char data) +{ + /* No need for spinlocks, this function is used only in + otherwise protected init code */ + outb(reg, chip->ctrl_port); + outb(data, chip->ctrl_port + 1); +#ifdef REG_DEBUG + snd_printk("Config reg %02x set to %02x\n", reg, data); +#endif +} + +static int __devinit snd_es18xx_initialize(es18xx_t *chip) +{ + int mask = 0; + + /* enable extended mode */ + snd_es18xx_dsp_command(chip, 0xC6); + /* Reset mixer registers */ + snd_es18xx_mixer_write(chip, 0x00, 0x00); + + /* Audio 1 DMA demand mode (4 bytes/request) */ + snd_es18xx_write(chip, 0xB9, 2); + if (chip->caps & ES18XX_CONTROL) { + /* Hardware volume IRQ */ + snd_es18xx_config_write(chip, 0x27, chip->irq); + if (chip->fm_port > 0 && chip->fm_port != SNDRV_AUTO_PORT) { + /* FM I/O */ + snd_es18xx_config_write(chip, 0x62, chip->fm_port >> 8); + snd_es18xx_config_write(chip, 0x63, chip->fm_port & 0xff); + } + if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) { + /* MPU-401 I/O */ + snd_es18xx_config_write(chip, 0x64, chip->mpu_port >> 8); + snd_es18xx_config_write(chip, 0x65, chip->mpu_port & 0xff); + /* MPU-401 IRQ */ + snd_es18xx_config_write(chip, 0x28, chip->irq); + } + /* Audio1 IRQ */ + snd_es18xx_config_write(chip, 0x70, chip->irq); + /* Audio2 IRQ */ + snd_es18xx_config_write(chip, 0x72, chip->irq); + /* Audio1 DMA */ + snd_es18xx_config_write(chip, 0x74, chip->dma1); + /* Audio2 DMA */ + snd_es18xx_config_write(chip, 0x75, chip->dma2); + + /* Enable Audio 1 IRQ */ + snd_es18xx_write(chip, 0xB1, 0x50); + /* Enable Audio 2 IRQ */ + snd_es18xx_mixer_write(chip, 0x7A, 0x40); + /* Enable Audio 1 DMA */ + snd_es18xx_write(chip, 0xB2, 0x50); + /* Enable MPU and hardware volume interrupt */ + snd_es18xx_mixer_write(chip, 0x64, 0x42); + } + else { + int irqmask, dma1mask, dma2mask; + switch (chip->irq) { + case 2: + case 9: + irqmask = 0; + break; + case 5: + irqmask = 1; + break; + case 7: + irqmask = 2; + break; + case 10: + irqmask = 3; + break; + default: + snd_printk("invalid irq %d\n", chip->irq); + return -ENODEV; + } + switch (chip->dma1) { + case 0: + dma1mask = 1; + break; + case 1: + dma1mask = 2; + break; + case 3: + dma1mask = 3; + break; + default: + snd_printk("invalid dma1 %d\n", chip->dma1); + return -ENODEV; + } + switch (chip->dma2) { + case 0: + dma2mask = 0; + break; + case 1: + dma2mask = 1; + break; + case 3: + dma2mask = 2; + break; + case 5: + dma2mask = 3; + break; + default: + snd_printk("invalid dma2 %d\n", chip->dma2); + return -ENODEV; + } + + /* Enable and set Audio 1 IRQ */ + snd_es18xx_write(chip, 0xB1, 0x50 | (irqmask << 2)); + /* Enable and set Audio 1 DMA */ + snd_es18xx_write(chip, 0xB2, 0x50 | (dma1mask << 2)); + /* Set Audio 2 DMA */ + snd_es18xx_mixer_bits(chip, 0x7d, 0x07, 0x04 | dma2mask); + /* Enable Audio 2 IRQ and DMA + Set capture mixer input */ + snd_es18xx_mixer_write(chip, 0x7A, 0x68); + /* Enable and set hardware volume interrupt */ + snd_es18xx_mixer_write(chip, 0x64, 0x06); + if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) { + /* MPU401 share irq with audio + Joystick enabled + FM enabled */ + snd_es18xx_mixer_write(chip, 0x40, 0x43 | (chip->mpu_port & 0xf0) >> 1); + } + snd_es18xx_mixer_write(chip, 0x7f, ((irqmask + 1) << 1) | 0x01); + } + if (chip->caps & ES18XX_NEW_RATE) { + /* Change behaviour of register A1 + 4x oversampling + 2nd channel DAC asynchronous */ + snd_es18xx_mixer_write(chip, 0x71, 0x32); + } + if (!(chip->caps & ES18XX_PCM2)) { + /* Enable DMA FIFO */ + snd_es18xx_write(chip, 0xB7, 0x80); + } + if (chip->caps & ES18XX_SPATIALIZER) { + /* Set spatializer parameters to recommended values */ + snd_es18xx_mixer_write(chip, 0x54, 0x8f); + snd_es18xx_mixer_write(chip, 0x56, 0x95); + snd_es18xx_mixer_write(chip, 0x58, 0x94); + snd_es18xx_mixer_write(chip, 0x5a, 0x80); + } + /* Mute input source */ + if (chip->caps & ES18XX_MUTEREC) + mask = 0x10; + if (chip->caps & ES18XX_RECMIX) + snd_es18xx_mixer_write(chip, 0x1c, 0x05 | mask); + else { + snd_es18xx_mixer_write(chip, 0x1c, 0x00 | mask); + snd_es18xx_write(chip, 0xb4, 0x00); + } +#ifndef AVOID_POPS + /* Enable PCM output */ + snd_es18xx_dsp_command(chip, 0xD1); +#endif + + return 0; +} + +static int __devinit snd_es18xx_identify(es18xx_t *chip) +{ + int hi,lo; + + /* reset */ + if (snd_es18xx_reset(chip) < 0) { + snd_printk("reset at 0x%lx failed!!!\n", chip->port); + return -ENODEV; + } + + snd_es18xx_dsp_command(chip, 0xe7); + hi = snd_es18xx_dsp_get_byte(chip); + if (hi < 0) { + return hi; + } + lo = snd_es18xx_dsp_get_byte(chip); + if ((lo & 0xf0) != 0x80) { + return -ENODEV; + } + if (hi == 0x48) { + chip->version = 0x488; + return 0; + } + if (hi != 0x68) { + return -ENODEV; + } + if ((lo & 0x0f) < 8) { + chip->version = 0x688; + return 0; + } + + outb(0x40, chip->port + 0x04); + hi = inb(chip->port + 0x05); + lo = inb(chip->port + 0x05); + if (hi != lo) { + chip->version = hi << 8 | lo; + chip->ctrl_port = inb(chip->port + 0x05) << 8; + chip->ctrl_port += inb(chip->port + 0x05); + + if ((chip->res_ctrl_port = request_region(chip->ctrl_port, 8, "ES18xx - CTRL")) == NULL) { + snd_printk(KERN_ERR PFX "unable go grab port 0x%lx\n", chip->ctrl_port); + return -EBUSY; + } + + return 0; + } + + /* If has Hardware volume */ + if (snd_es18xx_mixer_writable(chip, 0x64, 0x04)) { + /* If has Audio2 */ + if (snd_es18xx_mixer_writable(chip, 0x70, 0x7f)) { + /* If has volume count */ + if (snd_es18xx_mixer_writable(chip, 0x64, 0x20)) { + chip->version = 0x1887; + } else { + chip->version = 0x1888; + } + } else { + chip->version = 0x1788; + } + } + else + chip->version = 0x1688; + return 0; +} + +static int __devinit snd_es18xx_probe(es18xx_t *chip) +{ + if (snd_es18xx_identify(chip) < 0) { + snd_printk(KERN_ERR PFX "[0x%lx] ESS chip not found\n", chip->port); + return -ENODEV; + } + + switch (chip->version) { + case 0x1868: + chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_CONTROL | ES18XX_HWV; + break; + case 0x1869: + chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_MONO | ES18XX_MUTEREC | ES18XX_CONTROL | ES18XX_HWV; + break; + case 0x1878: + chip->caps = ES18XX_DUPLEX_MONO | ES18XX_DUPLEX_SAME | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV; + break; + case 0x1879: + chip->caps = ES18XX_PCM2 | ES18XX_SPATIALIZER | ES18XX_RECMIX | ES18XX_NEW_RATE | ES18XX_AUXB | ES18XX_I2S | ES18XX_CONTROL | ES18XX_HWV; + break; + case 0x1887: + chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_HWV; + break; + case 0x1888: + chip->caps = ES18XX_PCM2 | ES18XX_RECMIX | ES18XX_AUXB | ES18XX_DUPLEX_SAME | ES18XX_HWV; + break; + default: + snd_printk("[0x%lx] unsupported chip ES%x\n", + chip->port, chip->version); + return -ENODEV; + } + + snd_printd("[0x%lx] ESS%x chip found\n", chip->port, chip->version); + + if (chip->dma1 == chip->dma2) + chip->caps &= ~(ES18XX_PCM2 | ES18XX_DUPLEX_SAME); + + return snd_es18xx_initialize(chip); +} + +static snd_pcm_ops_t snd_es18xx_playback_ops = { + .open = snd_es18xx_playback_open, + .close = snd_es18xx_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es18xx_playback_hw_params, + .hw_free = snd_es18xx_pcm_hw_free, + .prepare = snd_es18xx_playback_prepare, + .trigger = snd_es18xx_playback_trigger, + .pointer = snd_es18xx_playback_pointer, +}; + +static snd_pcm_ops_t snd_es18xx_capture_ops = { + .open = snd_es18xx_capture_open, + .close = snd_es18xx_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_es18xx_capture_hw_params, + .hw_free = snd_es18xx_pcm_hw_free, + .prepare = snd_es18xx_capture_prepare, + .trigger = snd_es18xx_capture_trigger, + .pointer = snd_es18xx_capture_pointer, +}; + +static void snd_es18xx_pcm_free(snd_pcm_t *pcm) +{ + es18xx_t *codec = pcm->private_data; + codec->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int __devinit snd_es18xx_pcm(es18xx_t *chip, int device, snd_pcm_t ** rpcm) +{ + snd_pcm_t *pcm; + char str[16]; + int err; + + if (rpcm) + *rpcm = NULL; + sprintf(str, "ES%x", chip->version); + if (chip->caps & ES18XX_PCM2) { + err = snd_pcm_new(chip->card, str, device, 2, 1, &pcm); + } else { + err = snd_pcm_new(chip->card, str, device, 1, 1, &pcm); + } + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_es18xx_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_es18xx_capture_ops); + + /* global setup */ + pcm->private_data = chip; + pcm->private_free = snd_es18xx_pcm_free; + pcm->info_flags = 0; + if (chip->caps & ES18XX_DUPLEX_SAME) + pcm->info_flags |= SNDRV_PCM_INFO_JOINT_DUPLEX; + if (! (chip->caps & ES18XX_PCM2)) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + sprintf(pcm->name, "ESS AudioDrive ES%x", chip->version); + chip->pcm = pcm; + + 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); + + if (rpcm) + *rpcm = pcm; + return 0; +} + +/* Power Management support functions */ +#ifdef CONFIG_PM +static int snd_es18xx_suspend(snd_card_t *card, pm_message_t state) +{ + es18xx_t *chip = card->pm_private_data; + + snd_pcm_suspend_all(chip->pcm); + + /* power down */ + chip->pm_reg = (unsigned char)snd_es18xx_read(chip, ES18XX_PM); + chip->pm_reg |= (ES18XX_PM_FM | ES18XX_PM_SUS); + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg); + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_SUS); + + return 0; +} + +static int snd_es18xx_resume(snd_card_t *card) +{ + es18xx_t *chip = card->pm_private_data; + + /* restore PM register, we won't wake till (not 0x07) i/o activity though */ + snd_es18xx_write(chip, ES18XX_PM, chip->pm_reg ^= ES18XX_PM_FM); + + return 0; +} +#endif /* CONFIG_PM */ + +static int snd_es18xx_free(es18xx_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->res_ctrl_port) { + release_resource(chip->res_ctrl_port); + kfree_nocheck(chip->res_ctrl_port); + } + if (chip->res_mpu_port) { + release_resource(chip->res_mpu_port); + kfree_nocheck(chip->res_mpu_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); + if (chip->dma1 >= 0) { + disable_dma(chip->dma1); + free_dma(chip->dma1); + } + if (chip->dma2 >= 0 && chip->dma1 != chip->dma2) { + disable_dma(chip->dma2); + free_dma(chip->dma2); + } + kfree(chip); + return 0; +} + +static int snd_es18xx_dev_free(snd_device_t *device) +{ + es18xx_t *chip = device->device_data; + return snd_es18xx_free(chip); +} + +static int __devinit snd_es18xx_new_device(snd_card_t * card, + unsigned long port, + unsigned long mpu_port, + unsigned long fm_port, + int irq, int dma1, int dma2, + es18xx_t ** rchip) +{ + es18xx_t *chip; + static snd_device_ops_t ops = { + .dev_free = snd_es18xx_dev_free, + }; + int err; + + *rchip = NULL; + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->mixer_lock); + spin_lock_init(&chip->ctrl_lock); + chip->card = card; + chip->port = port; + chip->mpu_port = mpu_port; + chip->fm_port = fm_port; + chip->irq = -1; + chip->dma1 = -1; + chip->dma2 = -1; + chip->audio2_vol = 0x00; + chip->active = 0; + + if ((chip->res_port = request_region(port, 16, "ES18xx")) == NULL) { + snd_es18xx_free(chip); + snd_printk(KERN_ERR PFX "unable to grap ports 0x%lx-0x%lx\n", port, port + 16 - 1); + return -EBUSY; + } + + if (request_irq(irq, snd_es18xx_interrupt, SA_INTERRUPT, "ES18xx", (void *) chip)) { + snd_es18xx_free(chip); + snd_printk(KERN_ERR PFX "unable to grap IRQ %d\n", irq); + return -EBUSY; + } + chip->irq = irq; + + if (request_dma(dma1, "ES18xx DMA 1")) { + snd_es18xx_free(chip); + snd_printk(KERN_ERR PFX "unable to grap DMA1 %d\n", dma1); + return -EBUSY; + } + chip->dma1 = dma1; + + if (dma2 != dma1 && request_dma(dma2, "ES18xx DMA 2")) { + snd_es18xx_free(chip); + snd_printk(KERN_ERR PFX "unable to grap DMA2 %d\n", dma2); + return -EBUSY; + } + chip->dma2 = dma2; + + if (snd_es18xx_probe(chip) < 0) { + snd_es18xx_free(chip); + return -ENODEV; + } + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_es18xx_free(chip); + return err; + } + *rchip = chip; + return 0; +} + +static int __devinit snd_es18xx_mixer(es18xx_t *chip) +{ + snd_card_t *card; + int err; + unsigned int idx; + + card = chip->card; + + strcpy(card->mixername, chip->pcm->name); + + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_base_controls); idx++) { + snd_kcontrol_t *kctl; + kctl = snd_ctl_new1(&snd_es18xx_base_controls[idx], chip); + if (chip->caps & ES18XX_HWV) { + switch (idx) { + case 0: + chip->master_volume = kctl; + kctl->private_free = snd_es18xx_hwv_free; + break; + case 1: + chip->master_switch = kctl; + kctl->private_free = snd_es18xx_hwv_free; + break; + } + } + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + } + if (chip->caps & ES18XX_PCM2) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm2_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm2_controls[idx], chip))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_pcm1_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_pcm1_controls[idx], chip))) < 0) + return err; + } + } + + if (chip->caps & ES18XX_MONO) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_mono_in_control, chip))) < 0) + return err; + } + if (chip->caps & ES18XX_RECMIX) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_recmix_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_recmix_controls[idx], chip))) < 0) + return err; + } + } + switch (chip->version) { + default: + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre1_control, chip))) < 0) + return err; + break; + case 0x1869: + case 0x1879: + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_micpre2_control, chip))) < 0) + return err; + break; + } + if (chip->caps & ES18XX_SPATIALIZER) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_spatializer_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_es18xx_spatializer_controls[idx], chip))) < 0) + return err; + } + } + if (chip->caps & ES18XX_HWV) { + for (idx = 0; idx < ARRAY_SIZE(snd_es18xx_hw_volume_controls); idx++) { + snd_kcontrol_t *kctl; + kctl = snd_ctl_new1(&snd_es18xx_hw_volume_controls[idx], chip); + if (idx == 0) + chip->hw_volume = kctl; + else + chip->hw_switch = kctl; + kctl->private_free = snd_es18xx_hwv_free; + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + + } + } + return 0; +} + + +/* Card level */ + +MODULE_AUTHOR("Christian Fischbach <fishbach@pool.informatik.rwth-aachen.de>, Abramo Bagnara <abramo@alsa-project.org>"); +MODULE_DESCRIPTION("ESS ES18xx AudioDrive"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESS,ES1868 PnP AudioDrive}," + "{ESS,ES1869 PnP AudioDrive}," + "{ESS,ES1878 PnP AudioDrive}," + "{ESS,ES1879 PnP AudioDrive}," + "{ESS,ES1887 PnP AudioDrive}," + "{ESS,ES1888 PnP AudioDrive}," + "{ESS,ES1887 AudioDrive}," + "{ESS,ES1888 AudioDrive}}"); + +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; /* 0x220,0x240,0x260,0x280 */ +#ifndef CONFIG_PNP +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = -1}; +#else +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#endif +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ES18xx soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ES18xx soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable ES18xx soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for ES18xx driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for ES18xx driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for ES18xx driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for ES18xx driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA 1 # for ES18xx driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA 2 # for ES18xx driver."); + +struct snd_audiodrive { +#ifdef CONFIG_PNP + struct pnp_dev *dev; + struct pnp_dev *devc; +#endif +}; + +static snd_card_t *snd_audiodrive_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_audiodrive_pnpids[] = { + /* ESS 1868 (integrated on Compaq dual P-Pro motherboard and Genius 18PnP 3D) */ + { .id = "ESS1868", .devs = { { "ESS1868" }, { "ESS0000" } } }, + /* ESS 1868 (integrated on Maxisound Cards) */ + { .id = "ESS1868", .devs = { { "ESS8601" }, { "ESS8600" } } }, + /* ESS 1868 (integrated on Maxisound Cards) */ + { .id = "ESS1868", .devs = { { "ESS8611" }, { "ESS8610" } } }, + /* ESS ES1869 Plug and Play AudioDrive */ + { .id = "ESS0003", .devs = { { "ESS1869" }, { "ESS0006" } } }, + /* ESS 1869 */ + { .id = "ESS1869", .devs = { { "ESS1869" }, { "ESS0006" } } }, + /* ESS 1878 */ + { .id = "ESS1878", .devs = { { "ESS1878" }, { "ESS0004" } } }, + /* ESS 1879 */ + { .id = "ESS1879", .devs = { { "ESS1879" }, { "ESS0009" } } }, + /* --- */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_audiodrive_pnpids); + +static int __devinit snd_audiodrive_pnp(int dev, struct snd_audiodrive *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->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -EBUSY; + } + acard->devc = pnp_request_card_device(card, id->devs[1].id, NULL); + if (acard->devc == NULL) { + kfree(cfg); + return -EBUSY; + } + /* Control port initialization */ + err = pnp_activate_dev(acard->devc); + if (err < 0) { + snd_printk(KERN_ERR PFX "PnP control configure failure (out of resources?)\n"); + return -EAGAIN; + } + snd_printdd("pnp: port=0x%lx\n", pnp_port_start(acard->devc, 0)); + /* PnP initialization */ + pdev = acard->dev; + pnp_init_resource_table(cfg); + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4); + if (mpu_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], mpu_port[dev], 2); + 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], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR PFX "PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "PnP configure failure (out of resources?)\n"); + kfree(cfg); + return -EBUSY; + } + /* ok. hack using Vendor-Defined Card-Level registers */ + /* skip csn and logdev initialization - already done in isapnp_configure */ + if (pnp_device_is_isapnp(pdev)) { + isapnp_cfg_begin(isapnp_card_number(pdev), isapnp_csn_number(pdev)); + isapnp_write_byte(0x27, pnp_irq(pdev, 0)); /* Hardware Volume IRQ Number */ + if (mpu_port[dev] != SNDRV_AUTO_PORT) + isapnp_write_byte(0x28, pnp_irq(pdev, 0)); /* MPU-401 IRQ Number */ + isapnp_write_byte(0x72, pnp_irq(pdev, 0)); /* second IRQ */ + isapnp_cfg_end(); + } else { + snd_printk(KERN_ERR PFX "unable to install ISA PnP hack, expect malfunction\n"); + } + port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + mpu_port[dev] = pnp_port_start(pdev, 2); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("PnP ES18xx: port=0x%lx, fm port=0x%lx, mpu port=0x%lx\n", port[dev], fm_port[dev], mpu_port[dev]); + snd_printdd("PnP ES18xx: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]); + kfree(cfg); + return 0; +} +#endif /* CONFIG_PNP */ + +static int __devinit snd_audiodrive_probe(int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int possible_irqs[] = {5, 9, 10, 7, 11, 12, -1}; + static int possible_dmas[] = {1, 0, 3, 5, -1}; + int xirq, xdma1, xdma2; + snd_card_t *card; + struct snd_audiodrive *acard; + snd_rawmidi_t *rmidi = NULL; + es18xx_t *chip; + opl3_t *opl3; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_audiodrive)); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_audiodrive *)card->private_data; +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if ((err = snd_audiodrive_pnp(dev, acard, pcard, pid)) < 0) { + snd_card_free(card); + return err; + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA1\n"); + return -EBUSY; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if ((err = snd_es18xx_new_device(card, + port[dev], + mpu_port[dev], + fm_port[dev], + xirq, xdma1, xdma2, + &chip)) < 0) { + snd_card_free(card); + return err; + } + + sprintf(card->driver, "ES%x", chip->version); + sprintf(card->shortname, "ESS AudioDrive ES%x", chip->version); + if (xdma1 != xdma2) + sprintf(card->longname, "%s at 0x%lx, irq %d, dma1 %d, dma2 %d", + card->shortname, + chip->port, + xirq, xdma1, xdma2); + else + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, + chip->port, + xirq, xdma1); + + if ((err = snd_es18xx_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_es18xx_mixer(chip)) < 0) { + snd_card_free(card); + return err; + } + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + if (snd_opl3_create(card, chip->fm_port, chip->fm_port + 2, OPL3_HW_OPL3, 0, &opl3) < 0) { + snd_printk(KERN_ERR PFX "opl3 not detected at 0x%lx\n", chip->fm_port); + } 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 ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES18XX, + chip->mpu_port, 0, + xirq, 0, + &rmidi)) < 0) { + snd_card_free(card); + return err; + } + chip->rmidi = rmidi; + } + + /* Power Management */ + snd_card_set_isa_pm_callback(card, snd_es18xx_suspend, snd_es18xx_resume, chip); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_audiodrive_legacy[dev] = card; + return 0; +} + +static int __devinit snd_audiodrive_probe_legacy_port(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + port[dev] = xport; + res = snd_audiodrive_probe(dev, NULL, NULL); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + + +#ifdef CONFIG_PNP +static int __devinit snd_audiodrive_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_audiodrive_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + + return -ENODEV; +} + +static void __devexit snd_audiodrive_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 es18xx_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "es18xx", + .id_table = snd_audiodrive_pnpids, + .probe = snd_audiodrive_pnp_detect, + .remove = __devexit_p(snd_audiodrive_pnp_remove), +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_es18xx_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280, -1}; + int dev, cards = 0, i; + + /* legacy non-auto cards at first */ + for (dev = 0; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + if (snd_audiodrive_probe(dev, NULL, NULL) >= 0) + cards++; + } + /* legacy auto configured cards */ + i = snd_legacy_auto_probe(possible_ports, snd_audiodrive_probe_legacy_port); + if (i > 0) + cards += i; + +#ifdef CONFIG_PNP + /* ISA PnP cards at last */ + i = pnp_register_card_driver(&es18xx_pnpc_driver); + if (i > 0) + cards += i; + +#endif + if(!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&es18xx_pnpc_driver); +#endif +#ifdef MODULE + snd_printk(KERN_ERR "ESS AudioDrive ES18xx soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_es18xx_exit(void) +{ + int idx; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&es18xx_pnpc_driver); +#endif + for(idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_audiodrive_legacy[idx]); +} + +module_init(alsa_card_es18xx_init) +module_exit(alsa_card_es18xx_exit) diff --git a/sound/isa/gus/Makefile b/sound/isa/gus/Makefile new file mode 100644 index 00000000000..bae5dbd6c8e --- /dev/null +++ b/sound/isa/gus/Makefile @@ -0,0 +1,36 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-gus-lib-objs := gus_main.o \ + gus_io.o gus_irq.o gus_timer.o \ + gus_mem.o gus_mem_proc.o gus_dram.o gus_dma.o gus_volume.o \ + gus_pcm.o gus_mixer.o \ + gus_uart.o \ + gus_reset.o +snd-gus-synth-objs := gus_synth.o gus_sample.o gus_simple.o gus_instr.o + +snd-gusclassic-objs := gusclassic.o +snd-gusextreme-objs := gusextreme.o +snd-gusmax-objs := gusmax.o +snd-interwave-objs := interwave.o +snd-interwave-stb-objs := interwave-stb.o + +# +# this function returns: +# "m" - CONFIG_SND_SEQUENCER is m +# <empty string> - CONFIG_SND_SEQUENCER is undefined +# otherwise parameter #1 value +# +sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) + +# Toplevel Module Dependency +obj-$(CONFIG_SND_GUSCLASSIC) += snd-gusclassic.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSMAX) += snd-gusmax.o snd-gus-lib.o +obj-$(CONFIG_SND_GUSEXTREME) += snd-gusextreme.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE) += snd-interwave.o snd-gus-lib.o +obj-$(CONFIG_SND_INTERWAVE_STB) += snd-interwave-stb.o snd-gus-lib.o +obj-$(call sequencer,$(CONFIG_SND_GUS_SYNTH)) += snd-gus-synth.o + +obj-m := $(sort $(obj-m)) diff --git a/sound/isa/gus/gus_dma.c b/sound/isa/gus/gus_dma.c new file mode 100644 index 00000000000..de4b56d80b3 --- /dev/null +++ b/sound/isa/gus/gus_dma.c @@ -0,0 +1,244 @@ +/* + * Routines for GF1 DMA control + * 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 <asm/dma.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/gus.h> + +static void snd_gf1_dma_ack(snd_gus_card_t * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, 0x00); + snd_gf1_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_dma_program(snd_gus_card_t * gus, + unsigned int addr, + unsigned long buf_addr, + unsigned int count, + unsigned int cmd) +{ + unsigned long flags; + unsigned int address; + unsigned char dma_cmd; + unsigned int address_high; + + // snd_printk("dma_transfer: addr=0x%x, buf=0x%lx, count=0x%x\n", addr, (long) buf, count); + + if (gus->gf1.dma1 > 3) { + if (gus->gf1.enh_mode) { + address = addr >> 1; + } else { + if (addr & 0x1f) { + snd_printd("snd_gf1_dma_transfer: unaligned address (0x%x)?\n", addr); + return; + } + address = (addr & 0x000c0000) | ((addr & 0x0003ffff) >> 1); + } + } else { + address = addr; + } + + dma_cmd = SNDRV_GF1_DMA_ENABLE | (unsigned short) cmd; +#if 0 + dma_cmd |= 0x08; +#endif + if (dma_cmd & SNDRV_GF1_DMA_16BIT) { + count++; + count &= ~1; /* align */ + } + if (gus->gf1.dma1 > 3) { + dma_cmd |= SNDRV_GF1_DMA_WIDTH16; + count++; + count &= ~1; /* align */ + } + snd_gf1_dma_ack(gus); + snd_dma_program(gus->gf1.dma1, buf_addr, count, dma_cmd & SNDRV_GF1_DMA_READ ? DMA_MODE_READ : DMA_MODE_WRITE); +#if 0 + snd_printk("address = 0x%x, count = 0x%x, dma_cmd = 0x%x\n", address << 1, count, dma_cmd); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + if (gus->gf1.enh_mode) { + address_high = ((address >> 16) & 0x000000f0) | (address & 0x0000000f); + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH, (unsigned char) address_high); + } else + snd_gf1_write16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW, (unsigned short) (address >> 4)); + snd_gf1_write8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL, dma_cmd); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static snd_gf1_dma_block_t *snd_gf1_dma_next_block(snd_gus_card_t * gus) +{ + snd_gf1_dma_block_t *block; + + /* PCM block have bigger priority than synthesizer one */ + if (gus->gf1.dma_data_pcm) { + block = gus->gf1.dma_data_pcm; + if (gus->gf1.dma_data_pcm_last == block) { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = NULL; + } else { + gus->gf1.dma_data_pcm = block->next; + } + } else if (gus->gf1.dma_data_synth) { + block = gus->gf1.dma_data_synth; + if (gus->gf1.dma_data_synth_last == block) { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + } else { + gus->gf1.dma_data_synth = block->next; + } + } else { + block = NULL; + } + if (block) { + gus->gf1.dma_ack = block->ack; + gus->gf1.dma_private_data = block->private_data; + } + return block; +} + + +static void snd_gf1_dma_interrupt(snd_gus_card_t * gus) +{ + snd_gf1_dma_block_t *block; + + snd_gf1_dma_ack(gus); + if (gus->gf1.dma_ack) + gus->gf1.dma_ack(gus, gus->gf1.dma_private_data); + spin_lock(&gus->dma_lock); + if (gus->gf1.dma_data_pcm == NULL && + gus->gf1.dma_data_synth == NULL) { + gus->gf1.dma_ack = NULL; + gus->gf1.dma_flags &= ~SNDRV_GF1_DMA_TRIGGER; + spin_unlock(&gus->dma_lock); + return; + } + block = snd_gf1_dma_next_block(gus); + spin_unlock(&gus->dma_lock); + snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); + kfree(block); +#if 0 + printk("program dma (IRQ) - addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", addr, (long) buffer, count, cmd); +#endif +} + +int snd_gf1_dma_init(snd_gus_card_t * gus) +{ + down(&gus->dma_mutex); + gus->gf1.dma_shared++; + if (gus->gf1.dma_shared > 1) { + up(&gus->dma_mutex); + return 0; + } + gus->gf1.interrupt_handler_dma_write = snd_gf1_dma_interrupt; + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = NULL; + up(&gus->dma_mutex); + return 0; +} + +int snd_gf1_dma_done(snd_gus_card_t * gus) +{ + snd_gf1_dma_block_t *block; + + down(&gus->dma_mutex); + gus->gf1.dma_shared--; + if (!gus->gf1.dma_shared) { + snd_dma_disable(gus->gf1.dma1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_WRITE); + snd_gf1_dma_ack(gus); + while ((block = gus->gf1.dma_data_pcm)) { + gus->gf1.dma_data_pcm = block->next; + kfree(block); + } + while ((block = gus->gf1.dma_data_synth)) { + gus->gf1.dma_data_synth = block->next; + kfree(block); + } + gus->gf1.dma_data_pcm_last = + gus->gf1.dma_data_synth_last = NULL; + } + up(&gus->dma_mutex); + return 0; +} + +int snd_gf1_dma_transfer_block(snd_gus_card_t * gus, + snd_gf1_dma_block_t * __block, + int atomic, + int synth) +{ + unsigned long flags; + snd_gf1_dma_block_t *block; + + block = kmalloc(sizeof(*block), atomic ? GFP_ATOMIC : GFP_KERNEL); + if (block == NULL) { + snd_printk("gf1: DMA transfer failure; not enough memory\n"); + return -ENOMEM; + } + *block = *__block; + block->next = NULL; +#if 0 + printk("addr = 0x%x, buffer = 0x%lx, count = 0x%x, cmd = 0x%x\n", block->addr, (long) block->buffer, block->count, block->cmd); +#endif +#if 0 + printk("gus->gf1.dma_data_pcm_last = 0x%lx\n", (long)gus->gf1.dma_data_pcm_last); + printk("gus->gf1.dma_data_pcm = 0x%lx\n", (long)gus->gf1.dma_data_pcm); +#endif + spin_lock_irqsave(&gus->dma_lock, flags); + if (synth) { + if (gus->gf1.dma_data_synth_last) { + gus->gf1.dma_data_synth_last->next = block; + gus->gf1.dma_data_synth_last = block; + } else { + gus->gf1.dma_data_synth = + gus->gf1.dma_data_synth_last = block; + } + } else { + if (gus->gf1.dma_data_pcm_last) { + gus->gf1.dma_data_pcm_last->next = block; + gus->gf1.dma_data_pcm_last = block; + } else { + gus->gf1.dma_data_pcm = + gus->gf1.dma_data_pcm_last = block; + } + } + if (!(gus->gf1.dma_flags & SNDRV_GF1_DMA_TRIGGER)) { + gus->gf1.dma_flags |= SNDRV_GF1_DMA_TRIGGER; + block = snd_gf1_dma_next_block(gus); + spin_unlock_irqrestore(&gus->dma_lock, flags); + if (block == NULL) + return 0; + snd_gf1_dma_program(gus, block->addr, block->buf_addr, block->count, (unsigned short) block->cmd); + kfree(block); + return 0; + } + spin_unlock_irqrestore(&gus->dma_lock, flags); + return 0; +} diff --git a/sound/isa/gus/gus_dram.c b/sound/isa/gus/gus_dram.c new file mode 100644 index 00000000000..22120b868b5 --- /dev/null +++ b/sound/isa/gus/gus_dram.c @@ -0,0 +1,103 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * DRAM access routines + * + * + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + + +static int snd_gus_dram_poke(snd_gus_card_t *gus, char __user *_buffer, + unsigned int address, unsigned int size) +{ + unsigned long flags; + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (copy_from_user(buffer, _buffer, size1)) + return -EFAULT; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + snd_gf1_dram_addr(gus, address); + outsb(GUSP(gus, DRAM), buffer, size1); + spin_unlock_irqrestore(&gus->reg_lock, flags); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + snd_gf1_poke(gus, address++, *pbuffer++); + } + size -= size1; + _buffer += size1; + } + return 0; +} + + +int snd_gus_dram_write(snd_gus_card_t *gus, char __user *buffer, + unsigned int address, unsigned int size) +{ + return snd_gus_dram_poke(gus, buffer, address, size); +} + +static int snd_gus_dram_peek(snd_gus_card_t *gus, char __user *_buffer, + unsigned int address, unsigned int size, + int rom) +{ + unsigned long flags; + unsigned int size1, size2; + char buffer[256], *pbuffer; + + while (size > 0) { + size1 = size > sizeof(buffer) ? sizeof(buffer) : size; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, rom ? 0x03 : 0x01); + snd_gf1_dram_addr(gus, address); + insb(GUSP(gus, DRAM), buffer, size1); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + spin_unlock_irqrestore(&gus->reg_lock, flags); + address += size1; + } else { + pbuffer = buffer; + size2 = size1; + while (size2--) + *pbuffer++ = snd_gf1_peek(gus, address++); + } + if (copy_to_user(_buffer, buffer, size1)) + return -EFAULT; + size -= size1; + _buffer += size1; + } + return 0; +} + +int snd_gus_dram_read(snd_gus_card_t *gus, char __user *buffer, + unsigned int address, unsigned int size, + int rom) +{ + return snd_gus_dram_peek(gus, buffer, address, size, rom); +} diff --git a/sound/isa/gus/gus_instr.c b/sound/isa/gus/gus_instr.c new file mode 100644 index 00000000000..591a9a17feb --- /dev/null +++ b/sound/isa/gus/gus_instr.c @@ -0,0 +1,173 @@ +/* + * Routines for Gravis UltraSound soundcards - Synthesizer + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +/* + * + */ + +int snd_gus_iwffff_put_sample(void *private_data, iwffff_wave_t *wave, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + snd_gf1_mem_block_t *block; + int err; + + if (wave->format & IWFFFF_WAVE_ROM) + return 0; /* it's probably ok - verify the address? */ + if (wave->format & IWFFFF_WAVE_STEREO) + return -EINVAL; /* not supported */ + block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF, + NULL, wave->size, + wave->format & IWFFFF_WAVE_16BIT, 1, + wave->share_id); + if (block == NULL) + return -ENOMEM; + err = snd_gus_dram_write(gus, data, + block->ptr, wave->size); + if (err < 0) { + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0); + snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block); + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1); + return err; + } + wave->address.memory = block->ptr; + return 0; +} + +int snd_gus_iwffff_get_sample(void *private_data, iwffff_wave_t *wave, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + + return snd_gus_dram_read(gus, data, wave->address.memory, wave->size, + wave->format & IWFFFF_WAVE_ROM ? 1 : 0); +} + +int snd_gus_iwffff_remove_sample(void *private_data, iwffff_wave_t *wave, + int atomic) +{ + snd_gus_card_t *gus = private_data; + + if (wave->format & IWFFFF_WAVE_ROM) + return 0; /* it's probably ok - verify the address? */ + return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory); +} + +/* + * + */ + +int snd_gus_gf1_put_sample(void *private_data, gf1_wave_t *wave, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + snd_gf1_mem_block_t *block; + int err; + + if (wave->format & GF1_WAVE_STEREO) + return -EINVAL; /* not supported */ + block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_WAVE_GF1, + NULL, wave->size, + wave->format & GF1_WAVE_16BIT, 1, + wave->share_id); + if (block == NULL) + return -ENOMEM; + err = snd_gus_dram_write(gus, data, + block->ptr, wave->size); + if (err < 0) { + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0); + snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block); + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1); + return err; + } + wave->address.memory = block->ptr; + return 0; +} + +int snd_gus_gf1_get_sample(void *private_data, gf1_wave_t *wave, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + + return snd_gus_dram_read(gus, data, wave->address.memory, wave->size, 0); +} + +int snd_gus_gf1_remove_sample(void *private_data, gf1_wave_t *wave, + int atomic) +{ + snd_gus_card_t *gus = private_data; + + return snd_gf1_mem_free(&gus->gf1.mem_alloc, wave->address.memory); +} + +/* + * + */ + +int snd_gus_simple_put_sample(void *private_data, simple_instrument_t *instr, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + snd_gf1_mem_block_t *block; + int err; + + if (instr->format & SIMPLE_WAVE_STEREO) + return -EINVAL; /* not supported */ + block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE, + NULL, instr->size, + instr->format & SIMPLE_WAVE_16BIT, 1, + instr->share_id); + if (block == NULL) + return -ENOMEM; + err = snd_gus_dram_write(gus, data, block->ptr, instr->size); + if (err < 0) { + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 0); + snd_gf1_mem_xfree(&gus->gf1.mem_alloc, block); + snd_gf1_mem_lock(&gus->gf1.mem_alloc, 1); + return err; + } + instr->address.memory = block->ptr; + return 0; +} + +int snd_gus_simple_get_sample(void *private_data, simple_instrument_t *instr, + char __user *data, long len, int atomic) +{ + snd_gus_card_t *gus = private_data; + + return snd_gus_dram_read(gus, data, instr->address.memory, instr->size, 0); +} + +int snd_gus_simple_remove_sample(void *private_data, simple_instrument_t *instr, + int atomic) +{ + snd_gus_card_t *gus = private_data; + + return snd_gf1_mem_free(&gus->gf1.mem_alloc, instr->address.memory); +} diff --git a/sound/isa/gus/gus_io.c b/sound/isa/gus/gus_io.c new file mode 100644 index 00000000000..f0570f2bf75 --- /dev/null +++ b/sound/isa/gus/gus_io.c @@ -0,0 +1,531 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * I/O routines for GF1/InterWave synthesizer chips + * + * + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +void snd_gf1_delay(snd_gus_card_t * gus) +{ + int i; + + for (i = 0; i < 6; i++) { + mb(); + inb(GUSP(gus, DRAM)); + } +} + +/* + * ======================================================================= + */ + +/* + * ok.. stop of control registers (wave & ramp) need some special things.. + * big UltraClick (tm) elimination... + */ + +static inline void __snd_gf1_ctrl_stop(snd_gus_card_t * gus, unsigned char reg) +{ + unsigned char value; + + outb(reg | 0x80, gus->gf1.reg_regsel); + mb(); + value = inb(gus->gf1.reg_data8); + mb(); + outb(reg, gus->gf1.reg_regsel); + mb(); + outb((value | 0x03) & ~(0x80 | 0x20), gus->gf1.reg_data8); + mb(); +} + +static inline void __snd_gf1_write8(snd_gus_card_t * gus, + unsigned char reg, + unsigned char data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outb(data, gus->gf1.reg_data8); + mb(); +} + +static inline unsigned char __snd_gf1_look8(snd_gus_card_t * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inb(gus->gf1.reg_data8); +} + +static inline void __snd_gf1_write16(snd_gus_card_t * gus, + unsigned char reg, unsigned int data) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) data, gus->gf1.reg_data16); + mb(); +} + +static inline unsigned short __snd_gf1_look16(snd_gus_card_t * gus, + unsigned char reg) +{ + outb(reg, gus->gf1.reg_regsel); + mb(); + return inw(gus->gf1.reg_data16); +} + +static inline void __snd_gf1_adlib_write(snd_gus_card_t * gus, + unsigned char reg, unsigned char data) +{ + outb(reg, gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); + outb(data, gus->gf1.reg_timerdata); + inb(gus->gf1.reg_timerctrl); + inb(gus->gf1.reg_timerctrl); +} + +static inline void __snd_gf1_write_addr(snd_gus_card_t * gus, unsigned char reg, + unsigned int addr, int w_16bit) +{ + if (gus->gf1.enh_mode) { + if (w_16bit) + addr = ((addr >> 1) & ~0x0000000f) | (addr & 0x0000000f); + __snd_gf1_write8(gus, SNDRV_GF1_VB_UPPER_ADDRESS, (unsigned char) ((addr >> 26) & 0x03)); + } else if (w_16bit) + addr = (addr & 0x00c0000f) | ((addr & 0x003ffff0) >> 1); + __snd_gf1_write16(gus, reg, (unsigned short) (addr >> 11)); + __snd_gf1_write16(gus, reg + 1, (unsigned short) (addr << 5)); +} + +static inline unsigned int __snd_gf1_read_addr(snd_gus_card_t * gus, + unsigned char reg, short w_16bit) +{ + unsigned int res; + + res = ((unsigned int) __snd_gf1_look16(gus, reg | 0x80) << 11) & 0xfff800; + res |= ((unsigned int) __snd_gf1_look16(gus, (reg + 1) | 0x80) >> 5) & 0x0007ff; + if (gus->gf1.enh_mode) { + res |= (unsigned int) __snd_gf1_look8(gus, SNDRV_GF1_VB_UPPER_ADDRESS | 0x80) << 26; + if (w_16bit) + res = ((res << 1) & 0xffffffe0) | (res & 0x0000000f); + } else if (w_16bit) + res = ((res & 0x001ffff0) << 1) | (res & 0x00c0000f); + return res; +} + + +/* + * ======================================================================= + */ + +void snd_gf1_ctrl_stop(snd_gus_card_t * gus, unsigned char reg) +{ + __snd_gf1_ctrl_stop(gus, reg); +} + +void snd_gf1_write8(snd_gus_card_t * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_write8(gus, reg, data); +} + +unsigned char snd_gf1_look8(snd_gus_card_t * gus, unsigned char reg) +{ + return __snd_gf1_look8(gus, reg); +} + +void snd_gf1_write16(snd_gus_card_t * gus, + unsigned char reg, + unsigned int data) +{ + __snd_gf1_write16(gus, reg, data); +} + +unsigned short snd_gf1_look16(snd_gus_card_t * gus, unsigned char reg) +{ + return __snd_gf1_look16(gus, reg); +} + +void snd_gf1_adlib_write(snd_gus_card_t * gus, + unsigned char reg, + unsigned char data) +{ + __snd_gf1_adlib_write(gus, reg, data); +} + +void snd_gf1_write_addr(snd_gus_card_t * gus, unsigned char reg, + unsigned int addr, short w_16bit) +{ + __snd_gf1_write_addr(gus, reg, addr, w_16bit); +} + +unsigned int snd_gf1_read_addr(snd_gus_card_t * gus, + unsigned char reg, + short w_16bit) +{ + return __snd_gf1_read_addr(gus, reg, w_16bit); +} + +/* + + */ + +void snd_gf1_i_ctrl_stop(snd_gus_card_t * gus, unsigned char reg) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_ctrl_stop(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_i_write8(snd_gus_card_t * gus, + unsigned char reg, + unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write8(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned char snd_gf1_i_look8(snd_gus_card_t * gus, unsigned char reg) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_look8(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_i_write16(snd_gus_card_t * gus, + unsigned char reg, + unsigned int data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write16(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned short snd_gf1_i_look16(snd_gus_card_t * gus, unsigned char reg) +{ + unsigned long flags; + unsigned short res; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_look16(gus, reg); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_i_adlib_write(snd_gus_card_t * gus, + unsigned char reg, + unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_adlib_write(gus, reg, data); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_i_write_addr(snd_gus_card_t * gus, unsigned char reg, + unsigned int addr, short w_16bit) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + __snd_gf1_write_addr(gus, reg, addr, w_16bit); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned int snd_gf1_i_read_addr(snd_gus_card_t * gus, + unsigned char reg, short w_16bit) +{ + unsigned int res; + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + res = __snd_gf1_read_addr(gus, reg, w_16bit); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +/* + + */ + +void snd_gf1_dram_addr(snd_gus_card_t * gus, unsigned int addr) +{ + outb(0x43, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(0x44, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); +} + +void snd_gf1_poke(snd_gus_card_t * gus, unsigned int addr, unsigned char data) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(data, gus->gf1.reg_dram); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned char snd_gf1_peek(snd_gus_card_t * gus, unsigned int addr) +{ + unsigned long flags; + unsigned char res; + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + res = inb(gus->gf1.reg_dram); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_pokew(snd_gus_card_t * gus, unsigned int addr, unsigned short data) +{ + unsigned long flags; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk("snd_gf1_pokew - GF1!!!\n"); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + outw(data, gus->gf1.reg_data16); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +unsigned short snd_gf1_peekw(snd_gus_card_t * gus, unsigned int addr) +{ + unsigned long flags; + unsigned short res; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk("snd_gf1_peekw - GF1!!!\n"); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + mb(); + res = inw(gus->gf1.reg_data16); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return res; +} + +void snd_gf1_dram_setmem(snd_gus_card_t * gus, unsigned int addr, + unsigned short value, unsigned int count) +{ + unsigned long port; + unsigned long flags; + +#ifdef CONFIG_SND_DEBUG + if (!gus->interwave) + snd_printk("snd_gf1_dram_setmem - GF1!!!\n"); +#endif + addr &= ~1; + count >>= 1; + port = GUSP(gus, GF1DATALOW); + spin_lock_irqsave(&gus->reg_lock, flags); + outb(SNDRV_GF1_GW_DRAM_IO_LOW, gus->gf1.reg_regsel); + mb(); + outw((unsigned short) addr, gus->gf1.reg_data16); + mb(); + outb(SNDRV_GF1_GB_DRAM_IO_HIGH, gus->gf1.reg_regsel); + mb(); + outb((unsigned char) (addr >> 16), gus->gf1.reg_data8); + mb(); + outb(SNDRV_GF1_GW_DRAM_IO16, gus->gf1.reg_regsel); + while (count--) + outw(value, port); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +/* + + */ + +void snd_gf1_select_active_voices(snd_gus_card_t * gus) +{ + unsigned short voices; + + static unsigned short voices_tbl[32 - 14 + 1] = + { + 44100, 41160, 38587, 36317, 34300, 32494, 30870, 29400, 28063, 26843, + 25725, 24696, 23746, 22866, 22050, 21289, 20580, 19916, 19293 + }; + + voices = gus->gf1.active_voices; + if (voices > 32) + voices = 32; + if (voices < 14) + voices = 14; + if (gus->gf1.enh_mode) + voices = 32; + gus->gf1.active_voices = voices; + gus->gf1.playback_freq = + gus->gf1.enh_mode ? 44100 : voices_tbl[voices - 14]; + if (!gus->gf1.enh_mode) { + snd_gf1_i_write8(gus, SNDRV_GF1_GB_ACTIVE_VOICES, 0xc0 | (voices - 1)); + udelay(100); + } +} + +#ifdef CONFIG_SND_DEBUG + +void snd_gf1_print_voice_registers(snd_gus_card_t * gus) +{ + unsigned char mode; + int voice, ctrl; + + voice = gus->gf1.active_voice; + printk(" -%i- GF1 voice ctrl, ramp ctrl = 0x%x, 0x%x\n", voice, ctrl = snd_gf1_i_read8(gus, 0), snd_gf1_i_read8(gus, 0x0d)); + printk(" -%i- GF1 frequency = 0x%x\n", voice, snd_gf1_i_read16(gus, 1)); + printk(" -%i- GF1 loop start, end = 0x%x (0x%x), 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 2, ctrl & 4), snd_gf1_i_read_addr(gus, 2, (ctrl & 4) ^ 4), snd_gf1_i_read_addr(gus, 4, ctrl & 4), snd_gf1_i_read_addr(gus, 4, (ctrl & 4) ^ 4)); + printk(" -%i- GF1 ramp start, end, rate = 0x%x, 0x%x, 0x%x\n", voice, snd_gf1_i_read8(gus, 7), snd_gf1_i_read8(gus, 8), snd_gf1_i_read8(gus, 6)); + printk(" -%i- GF1 volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 9)); + printk(" -%i- GF1 position = 0x%x (0x%x)\n", voice, snd_gf1_i_read_addr(gus, 0x0a, ctrl & 4), snd_gf1_i_read_addr(gus, 0x0a, (ctrl & 4) ^ 4)); + if (gus->interwave && snd_gf1_i_read8(gus, 0x19) & 0x01) { /* enhanced mode */ + mode = snd_gf1_i_read8(gus, 0x15); + printk(" -%i- GFA1 mode = 0x%x\n", voice, mode); + if (mode & 0x01) { /* Effect processor */ + printk(" -%i- GFA1 effect address = 0x%x\n", voice, snd_gf1_i_read_addr(gus, 0x11, ctrl & 4)); + printk(" -%i- GFA1 effect volume = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x16)); + printk(" -%i- GFA1 effect volume final = 0x%x\n", voice, snd_gf1_i_read16(gus, 0x1d)); + printk(" -%i- GFA1 effect acumulator = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x14)); + } + if (mode & 0x20) { + printk(" -%i- GFA1 left offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x13), snd_gf1_i_read16(gus, 0x13) >> 4); + printk(" -%i- GFA1 left offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1c), snd_gf1_i_read16(gus, 0x1c) >> 4); + printk(" -%i- GFA1 right offset = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x0c), snd_gf1_i_read16(gus, 0x0c) >> 4); + printk(" -%i- GFA1 right offset final = 0x%x (%i)\n", voice, snd_gf1_i_read16(gus, 0x1b), snd_gf1_i_read16(gus, 0x1b) >> 4); + } else + printk(" -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c)); + } else + printk(" -%i- GF1 pan = 0x%x\n", voice, snd_gf1_i_read8(gus, 0x0c)); +} + +void snd_gf1_print_global_registers(snd_gus_card_t * gus) +{ + unsigned char global_mode = 0x00; + + printk(" -G- GF1 active voices = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ACTIVE_VOICES)); + if (gus->interwave) { + global_mode = snd_gf1_i_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE); + printk(" -G- GF1 global mode = 0x%x\n", global_mode); + } + if (global_mode & 0x02) /* LFO enabled? */ + printk(" -G- GF1 LFO base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_LFO_BASE)); + printk(" -G- GF1 voices IRQ read = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VOICES_IRQ_READ)); + printk(" -G- GF1 DRAM DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL)); + printk(" -G- GF1 DRAM DMA high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_DMA_LOW)); + printk(" -G- GF1 DRAM IO high/low = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_IO_HIGH), snd_gf1_i_read16(gus, SNDRV_GF1_GW_DRAM_IO_LOW)); + if (!gus->interwave) + printk(" -G- GF1 record DMA control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL)); + printk(" -G- GF1 DRAM IO 16 = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_DRAM_IO16)); + if (gus->gf1.enh_mode) { + printk(" -G- GFA1 memory config = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG)); + printk(" -G- GFA1 memory control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MEMORY_CONTROL)); + printk(" -G- GFA1 FIFO record base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR)); + printk(" -G- GFA1 FIFO playback base = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR)); + printk(" -G- GFA1 interleave control = 0x%x\n", snd_gf1_i_look16(gus, SNDRV_GF1_GW_INTERLEAVE)); + } +} + +void snd_gf1_print_setup_registers(snd_gus_card_t * gus) +{ + printk(" -S- mix control = 0x%x\n", inb(GUSP(gus, MIXCNTRLREG))); + printk(" -S- IRQ status = 0x%x\n", inb(GUSP(gus, IRQSTAT))); + printk(" -S- timer control = 0x%x\n", inb(GUSP(gus, TIMERCNTRL))); + printk(" -S- timer data = 0x%x\n", inb(GUSP(gus, TIMERDATA))); + printk(" -S- status read = 0x%x\n", inb(GUSP(gus, REGCNTRLS))); + printk(" -S- Sound Blaster control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL)); + printk(" -S- AdLib timer 1/2 = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1), snd_gf1_i_look8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2)); + printk(" -S- reset = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)); + if (gus->interwave) { + printk(" -S- compatibility = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_COMPATIBILITY)); + printk(" -S- decode control = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_DECODE_CONTROL)); + printk(" -S- version number = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER)); + printk(" -S- MPU-401 emul. control A/B = 0x%x/0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A), snd_gf1_i_look8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B)); + printk(" -S- emulation IRQ = 0x%x\n", snd_gf1_i_look8(gus, SNDRV_GF1_GB_EMULATION_IRQ)); + } +} + +void snd_gf1_peek_print_block(snd_gus_card_t * gus, unsigned int addr, int count, int w_16bit) +{ + if (!w_16bit) { + while (count-- > 0) + printk(count > 0 ? "%02x:" : "%02x", snd_gf1_peek(gus, addr++)); + } else { + while (count-- > 0) { + printk(count > 0 ? "%04x:" : "%04x", snd_gf1_peek(gus, addr) | (snd_gf1_peek(gus, addr + 1) << 8)); + addr += 2; + } + } +} + +#endif diff --git a/sound/isa/gus/gus_irq.c b/sound/isa/gus/gus_irq.c new file mode 100644 index 00000000000..1e2a15eb810 --- /dev/null +++ b/sound/isa/gus/gus_irq.c @@ -0,0 +1,142 @@ +/* + * Routine for IRQ handling from GF1/InterWave chip + * 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 <sound/core.h> +#include <sound/info.h> +#include <sound/gus.h> + +#ifdef CONFIG_SND_DEBUG +#define STAT_ADD(x) ((x)++) +#else +#define STAT_ADD(x) while (0) { ; } +#endif + +irqreturn_t snd_gus_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + snd_gus_card_t * gus = dev_id; + unsigned char status; + int loop = 100; + int handled = 0; + +__again: + status = inb(gus->gf1.reg_irqstat); + if (status == 0) + return IRQ_RETVAL(handled); + handled = 1; + // snd_printk("IRQ: status = 0x%x\n", status); + if (status & 0x02) { + STAT_ADD(gus->gf1.interrupt_stat_midi_in); + gus->gf1.interrupt_handler_midi_in(gus); + } + if (status & 0x01) { + STAT_ADD(gus->gf1.interrupt_stat_midi_out); + gus->gf1.interrupt_handler_midi_out(gus); + } + if (status & (0x20 | 0x40)) { + unsigned int already, _current_; + unsigned char voice_status, voice; + snd_gus_voice_t *pvoice; + + already = 0; + while (((voice_status = snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ)) & 0xc0) != 0xc0) { + voice = voice_status & 0x1f; + _current_ = 1 << voice; + if (already & _current_) + continue; /* multi request */ + already |= _current_; /* mark request */ +#if 0 + printk("voice = %i, voice_status = 0x%x, voice_verify = %i\n", voice, voice_status, inb(GUSP(gus, GF1PAGE))); +#endif + pvoice = &gus->gf1.voices[voice]; + if (pvoice->use) { + if (!(voice_status & 0x80)) { /* voice position IRQ */ + STAT_ADD(pvoice->interrupt_stat_wave); + pvoice->handler_wave(gus, pvoice); + } + if (!(voice_status & 0x40)) { /* volume ramp IRQ */ + STAT_ADD(pvoice->interrupt_stat_volume); + pvoice->handler_volume(gus, pvoice); + } + } else { + STAT_ADD(gus->gf1.interrupt_stat_voice_lost); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_i_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + } + } + } + if (status & 0x04) { + STAT_ADD(gus->gf1.interrupt_stat_timer1); + gus->gf1.interrupt_handler_timer1(gus); + } + if (status & 0x08) { + STAT_ADD(gus->gf1.interrupt_stat_timer2); + gus->gf1.interrupt_handler_timer2(gus); + } + if (status & 0x80) { + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_DRAM_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_write); + gus->gf1.interrupt_handler_dma_write(gus); + } + if (snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL) & 0x40) { + STAT_ADD(gus->gf1.interrupt_stat_dma_read); + gus->gf1.interrupt_handler_dma_read(gus); + } + } + if (--loop > 0) + goto __again; + return IRQ_NONE; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gus_irq_info_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_gus_card_t *gus; + snd_gus_voice_t *pvoice; + int idx; + + gus = entry->private_data; + snd_iprintf(buffer, "midi out = %u\n", gus->gf1.interrupt_stat_midi_out); + snd_iprintf(buffer, "midi in = %u\n", gus->gf1.interrupt_stat_midi_in); + snd_iprintf(buffer, "timer1 = %u\n", gus->gf1.interrupt_stat_timer1); + snd_iprintf(buffer, "timer2 = %u\n", gus->gf1.interrupt_stat_timer2); + snd_iprintf(buffer, "dma write = %u\n", gus->gf1.interrupt_stat_dma_write); + snd_iprintf(buffer, "dma read = %u\n", gus->gf1.interrupt_stat_dma_read); + snd_iprintf(buffer, "voice lost = %u\n", gus->gf1.interrupt_stat_voice_lost); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + snd_iprintf(buffer, "voice %i: wave = %u, volume = %u\n", + idx, + pvoice->interrupt_stat_wave, + pvoice->interrupt_stat_volume); + } +} + +void snd_gus_irq_profile_init(snd_gus_card_t *gus) +{ + snd_info_entry_t *entry; + + if (! snd_card_proc_new(gus->card, "gusirq", &entry)) + snd_info_set_text_ops(entry, gus, 1024, snd_gus_irq_info_read); +} + +#endif diff --git a/sound/isa/gus/gus_main.c b/sound/isa/gus/gus_main.c new file mode 100644 index 00000000000..73f81c14f76 --- /dev/null +++ b/sound/isa/gus/gus_main.c @@ -0,0 +1,514 @@ +/* + * Routines for Gravis UltraSound soundcards + * 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/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/control.h> + +#include <asm/dma.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards"); +MODULE_LICENSE("GPL"); + +static int snd_gus_init_dma_irq(snd_gus_card_t * gus, int latches); + +int snd_gus_use_inc(snd_gus_card_t * gus) +{ + if (!try_module_get(gus->card->module)) + return 0; + return 1; +} + +void snd_gus_use_dec(snd_gus_card_t * gus) +{ + module_put(gus->card->module); +} + +static int snd_gus_joystick_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 31; + return 0; +} + +static int snd_gus_joystick_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = gus->joystick_dac & 31; + return 0; +} + +static int snd_gus_joystick_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval; + + nval = ucontrol->value.integer.value[0] & 31; + spin_lock_irqsave(&gus->reg_lock, flags); + change = gus->joystick_dac != nval; + gus->joystick_dac = nval; + snd_gf1_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_gus_joystick_control = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Joystick Speed", + .info = snd_gus_joystick_info, + .get = snd_gus_joystick_get, + .put = snd_gus_joystick_put +}; + +static void snd_gus_init_control(snd_gus_card_t *gus) +{ + if (!gus->ace_flag) + snd_ctl_add(gus->card, snd_ctl_new1(&snd_gus_joystick_control, gus)); +} + +/* + * + */ + +static int snd_gus_free(snd_gus_card_t *gus) +{ + if (gus->gf1.res_port2 == NULL) + goto __hw_end; +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (gus->seq_dev) { + snd_device_free(gus->card, gus->seq_dev); + gus->seq_dev = NULL; + } +#endif + snd_gf1_stop(gus); + snd_gus_init_dma_irq(gus, 0); + __hw_end: + if (gus->gf1.res_port1) { + release_resource(gus->gf1.res_port1); + kfree_nocheck(gus->gf1.res_port1); + } + if (gus->gf1.res_port2) { + release_resource(gus->gf1.res_port2); + kfree_nocheck(gus->gf1.res_port2); + } + if (gus->gf1.irq >= 0) + free_irq(gus->gf1.irq, (void *) gus); + if (gus->gf1.dma1 >= 0) { + disable_dma(gus->gf1.dma1); + free_dma(gus->gf1.dma1); + } + if (!gus->equal_dma && gus->gf1.dma2 >= 0) { + disable_dma(gus->gf1.dma2); + free_dma(gus->gf1.dma2); + } + kfree(gus); + return 0; +} + +static int snd_gus_dev_free(snd_device_t *device) +{ + snd_gus_card_t *gus = device->device_data; + return snd_gus_free(gus); +} + +int snd_gus_create(snd_card_t * card, + unsigned long port, + int irq, int dma1, int dma2, + int timer_dev, + int voices, + int pcm_channels, + int effect, + snd_gus_card_t **rgus) +{ + snd_gus_card_t *gus; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_gus_dev_free, + }; + + *rgus = NULL; + gus = kcalloc(1, sizeof(*gus), GFP_KERNEL); + if (gus == NULL) + return -ENOMEM; + gus->gf1.irq = -1; + gus->gf1.dma1 = -1; + gus->gf1.dma2 = -1; + gus->card = card; + gus->gf1.port = port; + /* fill register variables for speedup */ + gus->gf1.reg_page = GUSP(gus, GF1PAGE); + gus->gf1.reg_regsel = GUSP(gus, GF1REGSEL); + gus->gf1.reg_data8 = GUSP(gus, GF1DATAHIGH); + gus->gf1.reg_data16 = GUSP(gus, GF1DATALOW); + gus->gf1.reg_irqstat = GUSP(gus, IRQSTAT); + gus->gf1.reg_dram = GUSP(gus, DRAM); + gus->gf1.reg_timerctrl = GUSP(gus, TIMERCNTRL); + gus->gf1.reg_timerdata = GUSP(gus, TIMERDATA); + /* allocate resources */ + if ((gus->gf1.res_port1 = request_region(port, 16, "GUS GF1 (Adlib/SB)")) == NULL) { + snd_printk(KERN_ERR "gus: can't grab SB port 0x%lx\n", port); + snd_gus_free(gus); + return -EBUSY; + } + if ((gus->gf1.res_port2 = request_region(port + 0x100, 12, "GUS GF1 (Synth)")) == NULL) { + snd_printk(KERN_ERR "gus: can't grab synth port 0x%lx\n", port + 0x100); + snd_gus_free(gus); + return -EBUSY; + } + if (irq >= 0 && request_irq(irq, snd_gus_interrupt, SA_INTERRUPT, "GUS GF1", (void *) gus)) { + snd_printk(KERN_ERR "gus: can't grab irq %d\n", irq); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.irq = irq; + if (request_dma(dma1, "GUS - 1")) { + snd_printk(KERN_ERR "gus: can't grab DMA1 %d\n", dma1); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.dma1 = dma1; + if (dma2 >= 0 && dma1 != dma2) { + if (request_dma(dma2, "GUS - 2")) { + snd_printk(KERN_ERR "gus: can't grab DMA2 %d\n", dma2); + snd_gus_free(gus); + return -EBUSY; + } + gus->gf1.dma2 = dma2; + } else { + gus->gf1.dma2 = gus->gf1.dma1; + gus->equal_dma = 1; + } + gus->timer_dev = timer_dev; + if (voices < 14) + voices = 14; + if (voices > 32) + voices = 32; + if (pcm_channels < 0) + pcm_channels = 0; + if (pcm_channels > 8) + pcm_channels = 8; + pcm_channels++; + pcm_channels &= ~1; + gus->gf1.effect = effect ? 1 : 0; + gus->gf1.active_voices = voices; + gus->gf1.pcm_channels = pcm_channels; + gus->gf1.volume_ramp = 25; + gus->gf1.smooth_pan = 1; + spin_lock_init(&gus->reg_lock); + spin_lock_init(&gus->voice_alloc); + spin_lock_init(&gus->active_voice_lock); + spin_lock_init(&gus->event_lock); + spin_lock_init(&gus->dma_lock); + spin_lock_init(&gus->pcm_volume_level_lock); + spin_lock_init(&gus->uart_cmd_lock); + init_MUTEX(&gus->dma_mutex); + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, gus, &ops)) < 0) { + snd_gus_free(gus); + return err; + } + *rgus = gus; + return 0; +} + +/* + * Memory detection routine for plain GF1 soundcards + */ + +static int snd_gus_detect_memory(snd_gus_card_t * gus) +{ + int l, idx, local; + unsigned char d; + + snd_gf1_poke(gus, 0L, 0xaa); + snd_gf1_poke(gus, 1L, 0x55); + if (snd_gf1_peek(gus, 0L) != 0xaa || snd_gf1_peek(gus, 1L) != 0x55) { + snd_printk("plain GF1 card at 0x%lx without onboard DRAM?\n", gus->gf1.port); + return -ENOMEM; + } + for (idx = 1, d = 0xab; idx < 4; idx++, d++) { + local = idx << 18; + snd_gf1_poke(gus, local, d); + snd_gf1_poke(gus, local + 1, d + 1); + if (snd_gf1_peek(gus, local) != d || + snd_gf1_peek(gus, local + 1) != d + 1 || + snd_gf1_peek(gus, 0L) != 0xaa) + break; + } +#if 1 + gus->gf1.memory = idx << 18; +#else + gus->gf1.memory = 256 * 1024; +#endif + for (l = 0, local = gus->gf1.memory; l < 4; l++, local -= 256 * 1024) { + gus->gf1.mem_alloc.banks_8[l].address = + gus->gf1.mem_alloc.banks_8[l].size = 0; + gus->gf1.mem_alloc.banks_16[l].address = l << 18; + gus->gf1.mem_alloc.banks_16[l].size = local > 0 ? 256 * 1024 : 0; + } + gus->gf1.mem_alloc.banks_8[0].size = gus->gf1.memory; + return 0; /* some memory were detected */ +} + +static int snd_gus_init_dma_irq(snd_gus_card_t * gus, int latches) +{ + snd_card_t *card; + unsigned long flags; + int irq, dma1, dma2; + static unsigned char irqs[16] = + {0, 0, 1, 3, 0, 2, 0, 4, 0, 1, 0, 5, 6, 0, 0, 7}; + static unsigned char dmas[8] = + {6, 1, 0, 2, 0, 3, 4, 5}; + + snd_assert(gus != NULL, return -EINVAL); + card = gus->card; + snd_assert(card != NULL, return -EINVAL); + + gus->mix_cntrl_reg &= 0xf8; + gus->mix_cntrl_reg |= 0x01; /* disable MIC, LINE IN, enable LINE OUT */ + if (gus->codec_flag || gus->ess_flag) { + gus->mix_cntrl_reg &= ~1; /* enable LINE IN */ + gus->mix_cntrl_reg |= 4; /* enable MIC */ + } + dma1 = gus->gf1.dma1; + dma1 = dma1 < 0 ? -dma1 : dma1; + dma1 = dmas[dma1 & 7]; + dma2 = gus->gf1.dma2; + dma2 = dma2 < 0 ? -dma2 : dma2; + dma2 = dmas[dma2 & 7]; +#if 0 + printk("dma1 = %i, dma2 = %i\n", gus->gf1.dma1, gus->gf1.dma2); +#endif + dma1 |= gus->equal_dma ? 0x40 : (dma2 << 3); + + if ((dma1 & 7) == 0 || (dma2 & 7) == 0) { + snd_printk("Error! DMA isn't defined.\n"); + return -EINVAL; + } + irq = gus->gf1.irq; + irq = irq < 0 ? -irq : irq; + irq = irqs[irq & 0x0f]; + if (irq == 0) { + snd_printk("Error! IRQ isn't defined.\n"); + return -EINVAL; + } + irq |= 0x40; +#if 0 + card->mixer.mix_ctrl_reg |= 0x10; +#endif + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(5, GUSP(gus, REGCNTRLS)); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(0x00, GUSP(gus, IRQDMACNTRLREG)); + outb(0, GUSP(gus, REGCNTRLS)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + udelay(100); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(dma1, GUSP(gus, IRQDMACNTRLREG)); + if (latches) { + outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(irq, GUSP(gus, IRQDMACNTRLREG)); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + + udelay(100); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x00 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(dma1, GUSP(gus, IRQDMACNTRLREG)); + if (latches) { + outb(0x40 | gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(irq, GUSP(gus, IRQDMACNTRLREG)); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + + snd_gf1_delay(gus); + + if (latches) + gus->mix_cntrl_reg |= 0x08; /* enable latches */ + else + gus->mix_cntrl_reg &= ~0x08; /* disable latches */ + spin_lock_irqsave(&gus->reg_lock, flags); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + outb(0, GUSP(gus, GF1PAGE)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + return 0; +} + +static int snd_gus_check_version(snd_gus_card_t * gus) +{ + unsigned long flags; + unsigned char val, rev; + snd_card_t *card; + + card = gus->card; + spin_lock_irqsave(&gus->reg_lock, flags); + outb(0x20, GUSP(gus, REGCNTRLS)); + val = inb(GUSP(gus, REGCNTRLS)); + rev = inb(GUSP(gus, BOARDVERSION)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_printdd("GF1 [0x%lx] init - val = 0x%x, rev = 0x%x\n", gus->gf1.port, val, rev); + strcpy(card->driver, "GUS"); + strcpy(card->longname, "Gravis UltraSound Classic (2.4)"); + if ((val != 255 && (val & 0x06)) || (rev >= 5 && rev != 255)) { + if (rev >= 5 && rev <= 9) { + gus->ics_flag = 1; + if (rev == 5) + gus->ics_flipped = 1; + card->longname[27] = '3'; + card->longname[29] = rev == 5 ? '5' : '7'; + } + if (rev >= 10 && rev != 255) { + if (rev >= 10 && rev <= 11) { + strcpy(card->driver, "GUS MAX"); + strcpy(card->longname, "Gravis UltraSound MAX"); + gus->max_flag = 1; + } else if (rev == 0x30) { + strcpy(card->driver, "GUS ACE"); + strcpy(card->longname, "Gravis UltraSound Ace"); + gus->ace_flag = 1; + } else if (rev == 0x50) { + strcpy(card->driver, "GUS Extreme"); + strcpy(card->longname, "Gravis UltraSound Extreme"); + gus->ess_flag = 1; + } else { + snd_printk("unknown GF1 revision number at 0x%lx - 0x%x (0x%x)\n", gus->gf1.port, rev, val); + snd_printk(" please - report to <perex@suse.cz>\n"); + } + } + } + strcpy(card->shortname, card->longname); + gus->uart_enable = 1; /* standard GUSes doesn't have midi uart trouble */ + snd_gus_init_control(gus); + return 0; +} + +static void snd_gus_seq_dev_free(snd_seq_device_t *seq_dev) +{ + snd_gus_card_t *gus = seq_dev->private_data; + gus->seq_dev = NULL; +} + +int snd_gus_initialize(snd_gus_card_t *gus) +{ + int err; + + if (!gus->interwave) { + if ((err = snd_gus_check_version(gus)) < 0) { + snd_printk("version check failed\n"); + return err; + } + if ((err = snd_gus_detect_memory(gus)) < 0) + return err; + } + if ((err = snd_gus_init_dma_irq(gus, 1)) < 0) + return err; +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (snd_seq_device_new(gus->card, 1, SNDRV_SEQ_DEV_ID_GUS, + sizeof(snd_gus_card_t*), &gus->seq_dev) >= 0) { + strcpy(gus->seq_dev->name, "GUS"); + *(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(gus->seq_dev) = gus; + gus->seq_dev->private_data = gus; + gus->seq_dev->private_free = snd_gus_seq_dev_free; + } +#endif + snd_gf1_start(gus); + gus->initialized = 1; + return 0; +} + + /* gus_io.c */ +EXPORT_SYMBOL(snd_gf1_delay); +EXPORT_SYMBOL(snd_gf1_write8); +EXPORT_SYMBOL(snd_gf1_look8); +EXPORT_SYMBOL(snd_gf1_write16); +EXPORT_SYMBOL(snd_gf1_look16); +EXPORT_SYMBOL(snd_gf1_i_write8); +EXPORT_SYMBOL(snd_gf1_i_look8); +EXPORT_SYMBOL(snd_gf1_i_write16); +EXPORT_SYMBOL(snd_gf1_i_look16); +EXPORT_SYMBOL(snd_gf1_dram_addr); +EXPORT_SYMBOL(snd_gf1_write_addr); +EXPORT_SYMBOL(snd_gf1_poke); +EXPORT_SYMBOL(snd_gf1_peek); + /* gus_reset.c */ +EXPORT_SYMBOL(snd_gf1_alloc_voice); +EXPORT_SYMBOL(snd_gf1_free_voice); +EXPORT_SYMBOL(snd_gf1_ctrl_stop); +EXPORT_SYMBOL(snd_gf1_stop_voice); +EXPORT_SYMBOL(snd_gf1_start); +EXPORT_SYMBOL(snd_gf1_stop); + /* gus_mixer.c */ +EXPORT_SYMBOL(snd_gf1_new_mixer); + /* gus_pcm.c */ +EXPORT_SYMBOL(snd_gf1_pcm_new); + /* gus.c */ +EXPORT_SYMBOL(snd_gus_use_inc); +EXPORT_SYMBOL(snd_gus_use_dec); +EXPORT_SYMBOL(snd_gus_create); +EXPORT_SYMBOL(snd_gus_initialize); + /* gus_irq.c */ +EXPORT_SYMBOL(snd_gus_interrupt); + /* gus_uart.c */ +EXPORT_SYMBOL(snd_gf1_rawmidi_new); + /* gus_dram.c */ +EXPORT_SYMBOL(snd_gus_dram_write); +EXPORT_SYMBOL(snd_gus_dram_read); + /* gus_volume.c */ +EXPORT_SYMBOL(snd_gf1_lvol_to_gvol_raw); +EXPORT_SYMBOL(snd_gf1_translate_freq); + /* gus_mem.c */ +EXPORT_SYMBOL(snd_gf1_mem_alloc); +EXPORT_SYMBOL(snd_gf1_mem_xfree); +EXPORT_SYMBOL(snd_gf1_mem_free); +EXPORT_SYMBOL(snd_gf1_mem_lock); + +/* + * INIT part + */ + +static int __init alsa_gus_init(void) +{ + return 0; +} + +static void __exit alsa_gus_exit(void) +{ +} + +module_init(alsa_gus_init) +module_exit(alsa_gus_exit) diff --git a/sound/isa/gus/gus_mem.c b/sound/isa/gus/gus_mem.c new file mode 100644 index 00000000000..bfc2b91001d --- /dev/null +++ b/sound/isa/gus/gus_mem.c @@ -0,0 +1,353 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * GUS's memory allocation routines / bottom layer + * + * + * 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/slab.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer); +#endif + +void snd_gf1_mem_lock(snd_gf1_mem_t * alloc, int xup) +{ + if (!xup) { + down(&alloc->memory_mutex); + } else { + up(&alloc->memory_mutex); + } +} + +snd_gf1_mem_block_t *snd_gf1_mem_xalloc(snd_gf1_mem_t * alloc, + snd_gf1_mem_block_t * block) +{ + snd_gf1_mem_block_t *pblock, *nblock; + + nblock = (snd_gf1_mem_block_t *) kmalloc(sizeof(snd_gf1_mem_block_t), GFP_KERNEL); + if (nblock == NULL) + return NULL; + *nblock = *block; + pblock = alloc->first; + while (pblock) { + if (pblock->ptr > nblock->ptr) { + nblock->prev = pblock->prev; + nblock->next = pblock; + pblock->prev = nblock; + if (pblock == alloc->first) + alloc->first = nblock; + else + nblock->prev->next = nblock; + up(&alloc->memory_mutex); + return NULL; + } + pblock = pblock->next; + } + nblock->next = NULL; + if (alloc->last == NULL) { + nblock->prev = NULL; + alloc->first = alloc->last = nblock; + } else { + nblock->prev = alloc->last; + alloc->last->next = nblock; + alloc->last = nblock; + } + return nblock; +} + +int snd_gf1_mem_xfree(snd_gf1_mem_t * alloc, snd_gf1_mem_block_t * block) +{ + if (block->share) { /* ok.. shared block */ + block->share--; + up(&alloc->memory_mutex); + return 0; + } + if (alloc->first == block) { + alloc->first = block->next; + if (block->next) + block->next->prev = NULL; + } else { + block->prev->next = block->next; + if (block->next) + block->next->prev = block->prev; + } + if (alloc->last == block) { + alloc->last = block->prev; + if (block->prev) + block->prev->next = NULL; + } else { + block->next->prev = block->prev; + if (block->prev) + block->prev->next = block->next; + } + kfree(block->name); + kfree(block); + return 0; +} + +snd_gf1_mem_block_t *snd_gf1_mem_look(snd_gf1_mem_t * alloc, + unsigned int address) +{ + snd_gf1_mem_block_t *block; + + for (block = alloc->first; block; block = block->next) { + if (block->ptr == address) { + return block; + } + } + return NULL; +} + +snd_gf1_mem_block_t *snd_gf1_mem_share(snd_gf1_mem_t * alloc, + unsigned int *share_id) +{ + snd_gf1_mem_block_t *block; + + if (!share_id[0] && !share_id[1] && + !share_id[2] && !share_id[3]) + return NULL; + for (block = alloc->first; block; block = block->next) + if (!memcmp(share_id, block->share_id, sizeof(share_id))) + return block; + return NULL; +} + +static int snd_gf1_mem_find(snd_gf1_mem_t * alloc, + snd_gf1_mem_block_t * block, + unsigned int size, int w_16, int align) +{ + snd_gf1_bank_info_t *info = w_16 ? alloc->banks_16 : alloc->banks_8; + unsigned int idx, boundary; + int size1; + snd_gf1_mem_block_t *pblock; + unsigned int ptr1, ptr2; + + align--; + if (w_16 && align < 1) + align = 1; + block->flags = w_16 ? SNDRV_GF1_MEM_BLOCK_16BIT : 0; + block->owner = SNDRV_GF1_MEM_OWNER_DRIVER; + block->share = 0; + block->share_id[0] = block->share_id[1] = + block->share_id[2] = block->share_id[3] = 0; + block->name = NULL; + block->prev = block->next = NULL; + for (pblock = alloc->first, idx = 0; pblock; pblock = pblock->next) { + while (pblock->ptr >= (boundary = info[idx].address + info[idx].size)) + idx++; + while (pblock->ptr + pblock->size >= (boundary = info[idx].address + info[idx].size)) + idx++; + ptr2 = boundary; + if (pblock->next) { + if (pblock->ptr + pblock->size == pblock->next->ptr) + continue; + if (pblock->next->ptr < boundary) + ptr2 = pblock->next->ptr; + } + ptr1 = (pblock->ptr + pblock->size + align) & ~align; + if (ptr1 >= ptr2) + continue; + size1 = ptr2 - ptr1; + if ((int)size <= size1) { + block->ptr = ptr1; + block->size = size; + return 0; + } + } + while (++idx < 4) { + if (size <= info[idx].size) { + /* I assume that bank address is already aligned.. */ + block->ptr = info[idx].address; + block->size = size; + return 0; + } + } + return -ENOMEM; +} + +snd_gf1_mem_block_t *snd_gf1_mem_alloc(snd_gf1_mem_t * alloc, int owner, + char *name, int size, int w_16, int align, + unsigned int *share_id) +{ + snd_gf1_mem_block_t block, *nblock; + + snd_gf1_mem_lock(alloc, 0); + if (share_id != NULL) { + nblock = snd_gf1_mem_share(alloc, share_id); + if (nblock != NULL) { + if (size != (int)nblock->size) { + /* TODO: remove in the future */ + snd_printk("snd_gf1_mem_alloc - share: sizes differ\n"); + goto __std; + } + nblock->share++; + snd_gf1_mem_lock(alloc, 1); + return NULL; + } + } + __std: + if (snd_gf1_mem_find(alloc, &block, size, w_16, align) < 0) { + snd_gf1_mem_lock(alloc, 1); + return NULL; + } + if (share_id != NULL) + memcpy(&block.share_id, share_id, sizeof(block.share_id)); + block.owner = owner; + block.name = snd_kmalloc_strdup(name, GFP_KERNEL); + nblock = snd_gf1_mem_xalloc(alloc, &block); + snd_gf1_mem_lock(alloc, 1); + return nblock; +} + +int snd_gf1_mem_free(snd_gf1_mem_t * alloc, unsigned int address) +{ + int result; + snd_gf1_mem_block_t *block; + + snd_gf1_mem_lock(alloc, 0); + if ((block = snd_gf1_mem_look(alloc, address)) != NULL) { + result = snd_gf1_mem_xfree(alloc, block); + snd_gf1_mem_lock(alloc, 1); + return result; + } + snd_gf1_mem_lock(alloc, 1); + return -EINVAL; +} + +int snd_gf1_mem_init(snd_gus_card_t * gus) +{ + snd_gf1_mem_t *alloc; + snd_gf1_mem_block_t block; +#ifdef CONFIG_SND_DEBUG + snd_info_entry_t *entry; +#endif + + alloc = &gus->gf1.mem_alloc; + init_MUTEX(&alloc->memory_mutex); + alloc->first = alloc->last = NULL; + if (!gus->gf1.memory) + return 0; + + memset(&block, 0, sizeof(block)); + block.owner = SNDRV_GF1_MEM_OWNER_DRIVER; + if (gus->gf1.enh_mode) { + block.ptr = 0; + block.size = 1024; + block.name = snd_kmalloc_strdup("InterWave LFOs", GFP_KERNEL); + if (snd_gf1_mem_xalloc(alloc, &block) == NULL) + return -ENOMEM; + } + block.ptr = gus->gf1.default_voice_address; + block.size = 4; + block.name = snd_kmalloc_strdup("Voice default (NULL's)", GFP_KERNEL); + if (snd_gf1_mem_xalloc(alloc, &block) == NULL) + return -ENOMEM; +#ifdef CONFIG_SND_DEBUG + if (! snd_card_proc_new(gus->card, "gusmem", &entry)) { + snd_info_set_text_ops(entry, gus, 1024, snd_gf1_mem_info_read); + entry->c.text.read_size = 256 * 1024; + } +#endif + return 0; +} + +int snd_gf1_mem_done(snd_gus_card_t * gus) +{ + snd_gf1_mem_t *alloc; + snd_gf1_mem_block_t *block, *nblock; + + alloc = &gus->gf1.mem_alloc; + block = alloc->first; + while (block) { + nblock = block->next; + snd_gf1_mem_xfree(alloc, block); + block = nblock; + } + return 0; +} + +#ifdef CONFIG_SND_DEBUG +static void snd_gf1_mem_info_read(snd_info_entry_t *entry, + snd_info_buffer_t * buffer) +{ + snd_gus_card_t *gus; + snd_gf1_mem_t *alloc; + snd_gf1_mem_block_t *block; + unsigned int total, used; + int i; + + gus = entry->private_data; + alloc = &gus->gf1.mem_alloc; + down(&alloc->memory_mutex); + snd_iprintf(buffer, "8-bit banks : \n "); + for (i = 0; i < 4; i++) + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_8[i].address, alloc->banks_8[i].size >> 10, i + 1 < 4 ? "," : ""); + snd_iprintf(buffer, "\n" + "16-bit banks : \n "); + for (i = total = 0; i < 4; i++) { + snd_iprintf(buffer, "0x%06x (%04ik)%s", alloc->banks_16[i].address, alloc->banks_16[i].size >> 10, i + 1 < 4 ? "," : ""); + total += alloc->banks_16[i].size; + } + snd_iprintf(buffer, "\n"); + used = 0; + for (block = alloc->first, i = 0; block; block = block->next, i++) { + used += block->size; + snd_iprintf(buffer, "Block %i at 0x%lx onboard 0x%x size %i (0x%x):\n", i, (long) block, block->ptr, block->size, block->size); + if (block->share || + block->share_id[0] || block->share_id[1] || + block->share_id[2] || block->share_id[3]) + snd_iprintf(buffer, " Share : %i [id0 0x%x] [id1 0x%x] [id2 0x%x] [id3 0x%x]\n", + block->share, + block->share_id[0], block->share_id[1], + block->share_id[2], block->share_id[3]); + snd_iprintf(buffer, " Flags :%s\n", + block->flags & SNDRV_GF1_MEM_BLOCK_16BIT ? " 16-bit" : ""); + snd_iprintf(buffer, " Owner : "); + switch (block->owner) { + case SNDRV_GF1_MEM_OWNER_DRIVER: + snd_iprintf(buffer, "driver - %s\n", block->name); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_SIMPLE: + snd_iprintf(buffer, "SIMPLE wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_GF1: + snd_iprintf(buffer, "GF1 wave\n"); + break; + case SNDRV_GF1_MEM_OWNER_WAVE_IWFFFF: + snd_iprintf(buffer, "IWFFFF wave\n"); + break; + default: + snd_iprintf(buffer, "unknown\n"); + } + } + snd_iprintf(buffer, " Total: memory = %i, used = %i, free = %i\n", + total, used, total - used); + up(&alloc->memory_mutex); +#if 0 + ultra_iprintf(buffer, " Verify: free = %i, max 8-bit block = %i, max 16-bit block = %i\n", + ultra_memory_free_size(card, &card->gf1.mem_alloc), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 0), + ultra_memory_free_block(card, &card->gf1.mem_alloc, 1)); +#endif +} +#endif diff --git a/sound/isa/gus/gus_mem_proc.c b/sound/isa/gus/gus_mem_proc.c new file mode 100644 index 00000000000..886763f1213 --- /dev/null +++ b/sound/isa/gus/gus_mem_proc.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * GUS's memory access via proc filesystem + * + * + * 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/slab.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/info.h> + +typedef struct gus_proc_private { + int rom; /* data are in ROM */ + unsigned int address; + unsigned int size; + snd_gus_card_t * gus; +} gus_proc_private_t; + +static long snd_gf1_mem_proc_dump(snd_info_entry_t *entry, void *file_private_data, + struct file *file, char __user *buf, + unsigned long count, unsigned long pos) +{ + long size; + gus_proc_private_t *priv = entry->private_data; + snd_gus_card_t *gus = priv->gus; + int err; + + size = count; + if (pos + size > priv->size) + size = (long)priv->size - pos; + if (size > 0) { + if ((err = snd_gus_dram_read(gus, buf, pos, size, priv->rom)) < 0) + return err; + return size; + } + return 0; +} + +static long long snd_gf1_mem_proc_llseek(snd_info_entry_t *entry, + void *private_file_data, + struct file *file, + long long offset, + int orig) +{ + gus_proc_private_t *priv = entry->private_data; + + switch (orig) { + case 0: /* SEEK_SET */ + file->f_pos = offset; + break; + case 1: /* SEEK_CUR */ + file->f_pos += offset; + break; + case 2: /* SEEK_END, offset is negative */ + file->f_pos = priv->size + offset; + break; + default: + return -EINVAL; + } + if (file->f_pos > priv->size) + file->f_pos = priv->size; + return file->f_pos; +} + +static void snd_gf1_mem_proc_free(snd_info_entry_t *entry) +{ + gus_proc_private_t *priv = entry->private_data; + kfree(priv); +} + +static struct snd_info_entry_ops snd_gf1_mem_proc_ops = { + .read = snd_gf1_mem_proc_dump, + .llseek = snd_gf1_mem_proc_llseek, +}; + +int snd_gf1_mem_proc_init(snd_gus_card_t * gus) +{ + int idx; + char name[16]; + gus_proc_private_t *priv; + snd_info_entry_t *entry; + + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.mem_alloc.banks_8[idx].size > 0) { + priv = kcalloc(1, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + priv->gus = gus; + sprintf(name, "gus-ram-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = gus->gf1.mem_alloc.banks_8[idx].address; + priv->size = entry->size = gus->gf1.mem_alloc.banks_8[idx].size; + } + } + } + for (idx = 0; idx < 4; idx++) { + if (gus->gf1.rom_present & (1 << idx)) { + priv = kcalloc(1, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + priv->rom = 1; + priv->gus = gus; + sprintf(name, "gus-rom-%i", idx); + if (! snd_card_proc_new(gus->card, name, &entry)) { + entry->content = SNDRV_INFO_CONTENT_DATA; + entry->private_data = priv; + entry->private_free = snd_gf1_mem_proc_free; + entry->c.ops = &snd_gf1_mem_proc_ops; + priv->address = idx * 4096 * 1024; + priv->size = entry->size = gus->gf1.rom_memory; + } + } + } + return 0; +} diff --git a/sound/isa/gus/gus_mixer.c b/sound/isa/gus/gus_mixer.c new file mode 100644 index 00000000000..a051094d510 --- /dev/null +++ b/sound/isa/gus/gus_mixer.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of ICS 2101 chip and "mixer" in GF1 chip + * + * + * 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/time.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/gus.h> + +/* + * + */ + +#define GF1_SINGLE(xname, xindex, shift, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_gf1_info_single, \ + .get = snd_gf1_get_single, .put = snd_gf1_put_single, \ + .private_value = shift | (invert << 8) } + +static int snd_gf1_info_single(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_gf1_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + + ucontrol->value.integer.value[0] = (gus->mix_cntrl_reg >> shift) & 1; + if (invert) + ucontrol->value.integer.value[0] ^= 1; + return 0; +} + +static int snd_gf1_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int shift = kcontrol->private_value & 0xff; + int invert = (kcontrol->private_value >> 8) & 1; + int change; + unsigned char oval, nval; + + nval = ucontrol->value.integer.value[0] & 1; + if (invert) + nval ^= 1; + nval <<= shift; + spin_lock_irqsave(&gus->reg_lock, flags); + oval = gus->mix_cntrl_reg; + nval = (oval & ~(1 << shift)) | nval; + change = nval != oval; + outb(gus->mix_cntrl_reg = nval, GUSP(gus, MIXCNTRLREG)); + outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +#define ICS_DOUBLE(xname, xindex, addr) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_ics_info_double, \ + .get = snd_ics_get_double, .put = snd_ics_put_double, \ + .private_value = addr } + +static int snd_ics_info_double(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_ics_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int addr = kcontrol->private_value & 0xff; + unsigned char left, right; + + spin_lock_irqsave(&gus->reg_lock, flags); + left = gus->gf1.ics_regs[addr][0]; + right = gus->gf1.ics_regs[addr][1]; + spin_unlock_irqrestore(&gus->reg_lock, flags); + ucontrol->value.integer.value[0] = left & 127; + ucontrol->value.integer.value[1] = right & 127; + return 0; +} + +static int snd_ics_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int addr = kcontrol->private_value & 0xff; + int change; + unsigned char val1, val2, oval1, oval2, tmp; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + spin_lock_irqsave(&gus->reg_lock, flags); + oval1 = gus->gf1.ics_regs[addr][0]; + oval2 = gus->gf1.ics_regs[addr][1]; + change = val1 != oval1 || val2 != oval2; + gus->gf1.ics_regs[addr][0] = val1; + gus->gf1.ics_regs[addr][1] = val2; + if (gus->ics_flag && gus->ics_flipped && + (addr == SNDRV_ICS_GF1_DEV || addr == SNDRV_ICS_MASTER_DEV)) { + tmp = val1; + val1 = val2; + val2 = tmp; + } + addr <<= 3; + outb(addr | 0, GUSP(gus, MIXCNTRLPORT)); + outb(1, GUSP(gus, MIXDATAPORT)); + outb(addr | 2, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val1, GUSP(gus, MIXDATAPORT)); + outb(addr | 1, GUSP(gus, MIXCNTRLPORT)); + outb(2, GUSP(gus, MIXDATAPORT)); + outb(addr | 3, GUSP(gus, MIXCNTRLPORT)); + outb((unsigned char) val2, GUSP(gus, MIXDATAPORT)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_gf1_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +GF1_SINGLE("Line Switch", 0, 0, 1), +GF1_SINGLE("Mic Switch", 0, 2, 0) +}; + +static snd_kcontrol_new_t snd_ics_controls[] = { +GF1_SINGLE("Master Playback Switch", 0, 1, 1), +ICS_DOUBLE("Master Playback Volume", 0, SNDRV_ICS_MASTER_DEV), +ICS_DOUBLE("Synth Playback Volume", 0, SNDRV_ICS_GF1_DEV), +GF1_SINGLE("Line Switch", 0, 0, 1), +ICS_DOUBLE("Line Playback Volume", 0, SNDRV_ICS_LINE_DEV), +GF1_SINGLE("Mic Switch", 0, 2, 0), +ICS_DOUBLE("Mic Playback Volume", 0, SNDRV_ICS_MIC_DEV), +ICS_DOUBLE("CD Playback Volume", 0, SNDRV_ICS_CD_DEV) +}; + +int snd_gf1_new_mixer(snd_gus_card_t * gus) +{ + snd_card_t *card; + unsigned int idx, max; + int err; + + snd_assert(gus != NULL, return -EINVAL); + card = gus->card; + snd_assert(card != NULL, return -EINVAL); + + if (gus->ics_flag) + snd_component_add(card, "ICS2101"); + if (card->mixername[0] == '\0') { + strcpy(card->mixername, gus->ics_flag ? "GF1,ICS2101" : "GF1"); + } else { + if (gus->ics_flag) + strcat(card->mixername, ",ICS2101"); + strcat(card->mixername, ",GF1"); + } + + if (!gus->ics_flag) { + max = gus->ess_flag ? 1 : ARRAY_SIZE(snd_gf1_controls); + for (idx = 0; idx < max; idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_gf1_controls[idx], gus))) < 0) + return err; + } + } else { + for (idx = 0; idx < ARRAY_SIZE(snd_ics_controls); idx++) { + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_ics_controls[idx], gus))) < 0) + return err; + } + } + return 0; +} diff --git a/sound/isa/gus/gus_pcm.c b/sound/isa/gus/gus_pcm.c new file mode 100644 index 00000000000..8995ad9c516 --- /dev/null +++ b/sound/isa/gus/gus_pcm.c @@ -0,0 +1,903 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of GF1 chip (PCM things) + * + * InterWave chips supports interleaved DMA, but this feature isn't used in + * this code. + * + * This code emulates autoinit DMA transfer for playback, recording by GF1 + * chip doesn't support autoinit DMA. + * + * + * 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 <asm/dma.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/gus.h> +#include <sound/pcm_params.h> +#include "gus_tables.h" + +/* maximum rate */ + +#define SNDRV_GF1_PCM_RATE 48000 + +#define SNDRV_GF1_PCM_PFLG_NONE 0 +#define SNDRV_GF1_PCM_PFLG_ACTIVE (1<<0) +#define SNDRV_GF1_PCM_PFLG_NEUTRAL (2<<0) + +typedef struct { + snd_gus_card_t * gus; + snd_pcm_substream_t * substream; + spinlock_t lock; + unsigned int voices; + snd_gus_voice_t *pvoices[2]; + unsigned int memory; + unsigned short flags; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int bpos; + unsigned int blocks; + unsigned int block_size; + unsigned int dma_size; + wait_queue_head_t sleep; + atomic_t dma_count; + int final_volume; +} gus_pcm_private_t; + +static int snd_gf1_pcm_use_dma = 1; + +static void snd_gf1_pcm_block_change_ack(snd_gus_card_t * gus, void *private_data) +{ + gus_pcm_private_t *pcmp = private_data; + + if (pcmp) { + atomic_dec(&pcmp->dma_count); + wake_up(&pcmp->sleep); + } +} + +static int snd_gf1_pcm_block_change(snd_pcm_substream_t * substream, + unsigned int offset, + unsigned int addr, + unsigned int count) +{ + snd_gf1_dma_block_t block; + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + + count += offset & 31; + offset &= ~31; + // snd_printk("block change - offset = 0x%x, count = 0x%x\n", offset, count); + memset(&block, 0, sizeof(block)); + block.cmd = SNDRV_GF1_DMA_IRQ; + if (snd_pcm_format_unsigned(runtime->format)) + block.cmd |= SNDRV_GF1_DMA_UNSIGNED; + if (snd_pcm_format_width(runtime->format) == 16) + block.cmd |= SNDRV_GF1_DMA_16BIT; + block.addr = addr & ~31; + block.buffer = runtime->dma_area + offset; + block.buf_addr = runtime->dma_addr + offset; + block.count = count; + block.private_data = pcmp; + block.ack = snd_gf1_pcm_block_change_ack; + if (!snd_gf1_dma_transfer_block(pcmp->gus, &block, 0, 0)) + atomic_inc(&pcmp->dma_count); + return 0; +} + +static void snd_gf1_pcm_trigger_up(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + snd_gus_card_t * gus = pcmp->gus; + unsigned long flags; + unsigned char voice_ctrl, ramp_ctrl; + unsigned short rate; + unsigned int curr, begin, end; + unsigned short vol; + unsigned char pan; + unsigned int voice; + + if (substream == NULL) + return; + spin_lock_irqsave(&pcmp->lock, flags); + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { + spin_unlock_irqrestore(&pcmp->lock, flags); + return; + } + pcmp->flags |= SNDRV_GF1_PCM_PFLG_ACTIVE; + pcmp->final_volume = 0; + spin_unlock_irqrestore(&pcmp->lock, flags); + rate = snd_gf1_translate_freq(gus, runtime->rate << 4); + /* enable WAVE IRQ */ + voice_ctrl = snd_pcm_format_width(runtime->format) == 16 ? 0x24 : 0x20; + /* enable RAMP IRQ + rollover */ + ramp_ctrl = 0x24; + if (pcmp->blocks == 1) { + voice_ctrl |= 0x08; /* loop enable */ + ramp_ctrl &= ~0x04; /* disable rollover */ + } + for (voice = 0; voice < pcmp->voices; voice++) { + begin = pcmp->memory + voice * (pcmp->dma_size / runtime->channels); + curr = begin + (pcmp->bpos * pcmp->block_size) / runtime->channels; + end = curr + (pcmp->block_size / runtime->channels); + end -= snd_pcm_format_width(runtime->format) == 16 ? 2 : 1; + // snd_printk("init: curr=0x%x, begin=0x%x, end=0x%x, ctrl=0x%x, ramp=0x%x, rate=0x%x\n", curr, begin, end, voice_ctrl, ramp_ctrl, rate); + pan = runtime->channels == 2 ? (!voice ? 1 : 14) : 8; + vol = !voice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, pan); + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, rate); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, begin << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, curr << 4, voice_ctrl & 4); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME << 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0x2f); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, vol >> 8); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + } + spin_lock_irqsave(&gus->reg_lock, flags); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, 0x00); /* deactivate voice */ + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; + } + voice_ctrl |= 0x20; + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + for (voice = 0; voice < pcmp->voices; voice++) { + snd_gf1_select_voice(gus, pcmp->pvoices[voice]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + voice_ctrl &= ~0x20; /* disable IRQ for next voice */ + } + } + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_pcm_interrupt_wave(snd_gus_card_t * gus, snd_gus_voice_t *pvoice) +{ + gus_pcm_private_t * pcmp; + snd_pcm_runtime_t * runtime; + unsigned char voice_ctrl, ramp_ctrl; + unsigned int idx; + unsigned int end, step; + + if (!pvoice->private_data) { + snd_printd("snd_gf1_pcm: unknown wave irq?\n"); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + pcmp = pvoice->private_data; + if (pcmp == NULL) { + snd_printd("snd_gf1_pcm: unknown wave irq?\n"); + snd_gf1_smart_stop_voice(gus, pvoice->number); + return; + } + gus = pcmp->gus; + runtime = pcmp->substream->runtime; + + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & ~0x8b; + ramp_ctrl = (snd_gf1_read8(gus, SNDRV_GF1_VB_VOLUME_CONTROL) & ~0xa4) | 0x03; +#if 0 + snd_gf1_select_voice(gus, pvoice->number); + printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pcmp->pvoices[1]->number); + printk("position = 0x%x\n", (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4)); + snd_gf1_select_voice(gus, pvoice->number); +#endif + pcmp->bpos++; + pcmp->bpos %= pcmp->blocks; + if (pcmp->bpos + 1 >= pcmp->blocks) { /* last block? */ + voice_ctrl |= 0x08; /* enable loop */ + } else { + ramp_ctrl |= 0x04; /* enable rollover */ + } + end = pcmp->memory + (((pcmp->bpos + 1) * pcmp->block_size) / runtime->channels); + end -= voice_ctrl & 4 ? 2 : 1; + step = pcmp->dma_size / runtime->channels; + voice_ctrl |= 0x20; + if (!pcmp->final_volume) { + ramp_ctrl |= 0x20; + ramp_ctrl &= ~0x03; + } + for (idx = 0; idx < pcmp->voices; idx++, end += step) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, end << 4, voice_ctrl & 4); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + voice_ctrl |= 0x20; + for (idx = 0; idx < pcmp->voices; idx++) { + snd_gf1_select_voice(gus, pcmp->pvoices[idx]->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice_ctrl); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, ramp_ctrl); + voice_ctrl &= ~0x20; + } + } + spin_unlock(&gus->reg_lock); + + snd_pcm_period_elapsed(pcmp->substream); +#if 0 + if ((runtime->flags & SNDRV_PCM_FLG_MMAP) && + *runtime->state == SNDRV_PCM_STATE_RUNNING) { + end = pcmp->bpos * pcmp->block_size; + if (runtime->channels > 1) { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + (end / 2), pcmp->block_size / 2); + snd_gf1_pcm_block_change(pcmp->substream, end + (pcmp->block_size / 2), pcmp->memory + (pcmp->dma_size / 2) + (end / 2), pcmp->block_size / 2); + } else { + snd_gf1_pcm_block_change(pcmp->substream, end, pcmp->memory + end, pcmp->block_size); + } + } +#endif +} + +static void snd_gf1_pcm_interrupt_volume(snd_gus_card_t * gus, snd_gus_voice_t * pvoice) +{ + unsigned short vol; + int cvoice; + gus_pcm_private_t *pcmp = pvoice->private_data; + + /* stop ramp, but leave rollover bit untouched */ + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + spin_unlock(&gus->reg_lock); + if (pcmp == NULL) + return; + /* are we active? */ + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + return; + /* load real volume - better precision */ + cvoice = pcmp->pvoices[0] == pvoice ? 0 : 1; + if (pcmp->substream == NULL) + return; + vol = !cvoice ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; + spin_unlock(&gus->reg_lock); +} + +static void snd_gf1_pcm_volume_change(snd_gus_card_t * gus) +{ +} + +static int snd_gf1_pcm_poke_block(snd_gus_card_t *gus, unsigned char *buf, + unsigned int pos, unsigned int count, + int w16, int invert) +{ + unsigned int len; + unsigned long flags; + + // printk("poke block; buf = 0x%x, pos = %i, count = %i, port = 0x%x\n", (int)buf, pos, count, gus->gf1.port); + while (count > 0) { + len = count; + if (len > 512) /* limit, to allow IRQ */ + len = 512; + count -= len; + if (gus->interwave) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01 | (invert ? 0x08 : 0x00)); + snd_gf1_dram_addr(gus, pos); + if (w16) { + outb(SNDRV_GF1_GW_DRAM_IO16, GUSP(gus, GF1REGSEL)); + outsw(GUSP(gus, GF1DATALOW), buf, len >> 1); + } else { + outsb(GUSP(gus, DRAM), buf, len); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + buf += 512; + pos += 512; + } else { + invert = invert ? 0x80 : 0x00; + if (w16) { + len >>= 1; + while (len--) { + snd_gf1_poke(gus, pos++, *buf++); + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } else { + while (len--) + snd_gf1_poke(gus, pos++, *buf++ ^ invert); + } + } + if (count > 0 && !in_interrupt()) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + return -EAGAIN; + } + } + return 0; +} + +static int snd_gf1_pcm_playback_copy(snd_pcm_substream_t *substream, + int voice, + snd_pcm_uframes_t pos, + void __user *src, + snd_pcm_uframes_t count) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + unsigned int bpos, len; + + bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2)); + len = samples_to_bytes(runtime, count); + snd_assert(bpos <= pcmp->dma_size, return -EIO); + snd_assert(bpos + len <= pcmp->dma_size, return -EIO); + if (copy_from_user(runtime->dma_area + bpos, src, len)) + return -EFAULT; + if (snd_gf1_pcm_use_dma && len > 32) { + return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len); + } else { + snd_gus_card_t *gus = pcmp->gus; + int err, w16, invert; + + w16 = (snd_pcm_format_width(runtime->format) == 16); + invert = snd_pcm_format_unsigned(runtime->format); + if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0) + return err; + } + return 0; +} + +static int snd_gf1_pcm_playback_silence(snd_pcm_substream_t *substream, + int voice, + snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + unsigned int bpos, len; + + bpos = samples_to_bytes(runtime, pos) + (voice * (pcmp->dma_size / 2)); + len = samples_to_bytes(runtime, count); + snd_assert(bpos <= pcmp->dma_size, return -EIO); + snd_assert(bpos + len <= pcmp->dma_size, return -EIO); + snd_pcm_format_set_silence(runtime->format, runtime->dma_area + bpos, count); + if (snd_gf1_pcm_use_dma && len > 32) { + return snd_gf1_pcm_block_change(substream, bpos, pcmp->memory + bpos, len); + } else { + snd_gus_card_t *gus = pcmp->gus; + int err, w16, invert; + + w16 = (snd_pcm_format_width(runtime->format) == 16); + invert = snd_pcm_format_unsigned(runtime->format); + if ((err = snd_gf1_pcm_poke_block(gus, runtime->dma_area + bpos, pcmp->memory + bpos, len, w16, invert)) < 0) + return err; + } + return 0; +} + +static int snd_gf1_pcm_playback_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + int err; + + if ((err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params))) < 0) + return err; + if (err > 0) { /* change */ + snd_gf1_mem_block_t *block; + if (pcmp->memory > 0) { + snd_gf1_mem_free(&gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + if ((block = snd_gf1_mem_alloc(&gus->gf1.mem_alloc, + SNDRV_GF1_MEM_OWNER_DRIVER, + "GF1 PCM", + runtime->dma_bytes, 1, 32, + NULL)) == NULL) + return -ENOMEM; + pcmp->memory = block->ptr; + } + pcmp->voices = params_channels(hw_params); + if (pcmp->pvoices[0] == NULL) { + if ((pcmp->pvoices[0] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) + return -ENOMEM; + pcmp->pvoices[0]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[0]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[0]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[0]->private_data = pcmp; + } + if (pcmp->voices > 1 && pcmp->pvoices[1] == NULL) { + if ((pcmp->pvoices[1] = snd_gf1_alloc_voice(pcmp->gus, SNDRV_GF1_VOICE_TYPE_PCM, 0, 0)) == NULL) + return -ENOMEM; + pcmp->pvoices[1]->handler_wave = snd_gf1_pcm_interrupt_wave; + pcmp->pvoices[1]->handler_volume = snd_gf1_pcm_interrupt_volume; + pcmp->pvoices[1]->volume_change = snd_gf1_pcm_volume_change; + pcmp->pvoices[1]->private_data = pcmp; + } else if (pcmp->voices == 1) { + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + } + return 0; +} + +static int snd_gf1_pcm_playback_hw_free(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + + snd_pcm_lib_free_pages(substream); + if (pcmp->pvoices[0]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[0]); + pcmp->pvoices[0] = NULL; + } + if (pcmp->pvoices[1]) { + snd_gf1_free_voice(pcmp->gus, pcmp->pvoices[1]); + pcmp->pvoices[1] = NULL; + } + if (pcmp->memory > 0) { + snd_gf1_mem_free(&pcmp->gus->gf1.mem_alloc, pcmp->memory); + pcmp->memory = 0; + } + return 0; +} + +static int snd_gf1_pcm_playback_prepare(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + + pcmp->bpos = 0; + pcmp->dma_size = snd_pcm_lib_buffer_bytes(substream); + pcmp->block_size = snd_pcm_lib_period_bytes(substream); + pcmp->blocks = pcmp->dma_size / pcmp->block_size; + return 0; +} + +static int snd_gf1_pcm_playback_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + int voice; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + snd_gf1_pcm_trigger_up(substream); + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + spin_lock(&pcmp->lock); + pcmp->flags &= ~SNDRV_GF1_PCM_PFLG_ACTIVE; + spin_unlock(&pcmp->lock); + voice = pcmp->pvoices[0]->number; + snd_gf1_stop_voices(gus, voice, voice); + if (pcmp->pvoices[1]) { + voice = pcmp->pvoices[1]->number; + snd_gf1_stop_voices(gus, voice, voice); + } + } else { + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_playback_pointer(snd_pcm_substream_t * substream) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + unsigned int pos; + unsigned char voice_ctrl; + + pos = 0; + spin_lock(&gus->reg_lock); + if (pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE) { + snd_gf1_select_voice(gus, pcmp->pvoices[0]->number); + voice_ctrl = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + pos = (snd_gf1_read_addr(gus, SNDRV_GF1_VA_CURRENT, voice_ctrl & 4) >> 4) - pcmp->memory; + if (substream->runtime->channels > 1) + pos <<= 1; + pos = bytes_to_frames(runtime, pos); + } + spin_unlock(&gus->reg_lock); + return pos; +} + +static ratnum_t clock = { + .num = 9878400/16, + .den_min = 2, + .den_max = 257, + .den_step = 1, +}; + +static snd_pcm_hw_constraint_ratnums_t hw_constraints_clocks = { + .nrats = 1, + .rats = &clock, +}; + +static int snd_gf1_pcm_capture_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + + gus->c_dma_size = params_buffer_bytes(hw_params); + gus->c_period_size = params_period_bytes(hw_params); + gus->c_pos = 0; + gus->gf1.pcm_rcntrl_reg = 0x21; /* IRQ at end, enable & start */ + if (params_channels(hw_params) > 1) + gus->gf1.pcm_rcntrl_reg |= 2; + if (gus->gf1.dma2 > 3) + gus->gf1.pcm_rcntrl_reg |= 4; + if (snd_pcm_format_unsigned(params_format(hw_params))) + gus->gf1.pcm_rcntrl_reg |= 0x80; + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_gf1_pcm_capture_hw_free(snd_pcm_substream_t * substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int snd_gf1_pcm_capture_prepare(snd_pcm_substream_t * substream) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RECORD_RATE, runtime->rate_den - 2); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + snd_dma_program(gus->gf1.dma2, runtime->dma_addr, gus->c_period_size, DMA_MODE_READ); + return 0; +} + +static int snd_gf1_pcm_capture_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + int val; + + if (cmd == SNDRV_PCM_TRIGGER_START) { + val = gus->gf1.pcm_rcntrl_reg; + } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { + val = 0; + } else { + return -EINVAL; + } + + spin_lock(&gus->reg_lock); + snd_gf1_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, val); + snd_gf1_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); + spin_unlock(&gus->reg_lock); + return 0; +} + +static snd_pcm_uframes_t snd_gf1_pcm_capture_pointer(snd_pcm_substream_t * substream) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + int pos = snd_dma_pointer(gus->gf1.dma2, gus->c_period_size); + pos = bytes_to_frames(substream->runtime, (gus->c_pos + pos) % gus->c_dma_size); + return pos; +} + +static void snd_gf1_pcm_interrupt_dma_read(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL, 0); /* disable sampling */ + snd_gf1_i_look8(gus, SNDRV_GF1_GB_REC_DMA_CONTROL); /* Sampling Control Register */ + if (gus->pcm_cap_substream != NULL) { + snd_gf1_pcm_capture_prepare(gus->pcm_cap_substream); + snd_gf1_pcm_capture_trigger(gus->pcm_cap_substream, SNDRV_PCM_TRIGGER_START); + gus->c_pos += gus->c_period_size; + snd_pcm_period_elapsed(gus->pcm_cap_substream); + } +} + +static snd_pcm_hardware_t snd_gf1_pcm_playback = +{ + .info = SNDRV_PCM_INFO_NONINTERLEAVED, + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE), + .rates = SNDRV_PCM_RATE_CONTINUOUS | 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_gf1_pcm_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 5510, + .rate_max = 44100, + .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 void snd_gf1_pcm_playback_free(snd_pcm_runtime_t *runtime) +{ + gus_pcm_private_t * pcmp = runtime->private_data; + kfree(pcmp); +} + +static int snd_gf1_pcm_playback_open(snd_pcm_substream_t *substream) +{ + gus_pcm_private_t *pcmp; + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + int err; + + pcmp = kcalloc(1, sizeof(*pcmp), GFP_KERNEL); + if (pcmp == NULL) + return -ENOMEM; + pcmp->gus = gus; + spin_lock_init(&pcmp->lock); + init_waitqueue_head(&pcmp->sleep); + atomic_set(&pcmp->dma_count, 0); + + runtime->private_data = pcmp; + runtime->private_free = snd_gf1_pcm_playback_free; + +#if 0 + printk("playback.buffer = 0x%lx, gf1.pcm_buffer = 0x%lx\n", (long) pcm->playback.buffer, (long) gus->gf1.pcm_buffer); +#endif + if ((err = snd_gf1_dma_init(gus)) < 0) + return err; + pcmp->flags = SNDRV_GF1_PCM_PFLG_NONE; + pcmp->substream = substream; + runtime->hw = snd_gf1_pcm_playback; + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma1, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 64); + return 0; +} + +static int snd_gf1_pcm_playback_close(snd_pcm_substream_t * substream) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + gus_pcm_private_t *pcmp = runtime->private_data; + + if (!wait_event_timeout(pcmp->sleep, (atomic_read(&pcmp->dma_count) <= 0), 2*HZ)) + snd_printk("gf1 pcm - serious DMA problem\n"); + + snd_gf1_dma_done(gus); + return 0; +} + +static int snd_gf1_pcm_capture_open(snd_pcm_substream_t * substream) +{ + snd_pcm_runtime_t *runtime = substream->runtime; + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + + gus->gf1.interrupt_handler_dma_read = snd_gf1_pcm_interrupt_dma_read; + gus->pcm_cap_substream = substream; + substream->runtime->hw = snd_gf1_pcm_capture; + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_limit_isa_dma_size(gus->gf1.dma2, &runtime->hw.period_bytes_max); + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clocks); + return 0; +} + +static int snd_gf1_pcm_capture_close(snd_pcm_substream_t * substream) +{ + snd_gus_card_t *gus = snd_pcm_substream_chip(substream); + + gus->pcm_cap_substream = NULL; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_DMA_READ); + return 0; +} + +static void snd_gf1_pcm_free(snd_pcm_t *pcm) +{ + snd_gus_card_t *gus = pcm->private_data; + gus->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int snd_gf1_pcm_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int snd_gf1_pcm_volume_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&gus->pcm_volume_level_lock, flags); + ucontrol->value.integer.value[0] = gus->gf1.pcm_volume_level_left1; + ucontrol->value.integer.value[1] = gus->gf1.pcm_volume_level_right1; + spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags); + return 0; +} + +static int snd_gf1_pcm_volume_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_gus_card_t *gus = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned int idx; + unsigned short val1, val2, vol; + gus_pcm_private_t *pcmp; + snd_gus_voice_t *pvoice; + + val1 = ucontrol->value.integer.value[0] & 127; + val2 = ucontrol->value.integer.value[1] & 127; + spin_lock_irqsave(&gus->pcm_volume_level_lock, flags); + change = val1 != gus->gf1.pcm_volume_level_left1 || + val2 != gus->gf1.pcm_volume_level_right1; + gus->gf1.pcm_volume_level_left1 = val1; + gus->gf1.pcm_volume_level_right1 = val2; + gus->gf1.pcm_volume_level_left = snd_gf1_lvol_to_gvol_raw(val1 << 9) << 4; + gus->gf1.pcm_volume_level_right = snd_gf1_lvol_to_gvol_raw(val2 << 9) << 4; + spin_unlock_irqrestore(&gus->pcm_volume_level_lock, flags); + /* are we active? */ + spin_lock_irqsave(&gus->voice_alloc, flags); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->pcm) + continue; + pcmp = pvoice->private_data; + if (!(pcmp->flags & SNDRV_GF1_PCM_PFLG_ACTIVE)) + continue; + /* load real volume - better precision */ + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, pvoice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + vol = pvoice == pcmp->pvoices[0] ? gus->gf1.pcm_volume_level_left : gus->gf1.pcm_volume_level_right; + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, vol); + pcmp->final_volume = 1; + spin_unlock_irqrestore(&gus->reg_lock, flags); + } + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return change; +} + +static snd_kcontrol_new_t snd_gf1_pcm_volume_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static snd_kcontrol_new_t snd_gf1_pcm_volume_control1 = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "GPCM Playback Volume", + .info = snd_gf1_pcm_volume_info, + .get = snd_gf1_pcm_volume_get, + .put = snd_gf1_pcm_volume_put +}; + +static snd_pcm_ops_t snd_gf1_pcm_playback_ops = { + .open = snd_gf1_pcm_playback_open, + .close = snd_gf1_pcm_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_gf1_pcm_playback_hw_params, + .hw_free = snd_gf1_pcm_playback_hw_free, + .prepare = snd_gf1_pcm_playback_prepare, + .trigger = snd_gf1_pcm_playback_trigger, + .pointer = snd_gf1_pcm_playback_pointer, + .copy = snd_gf1_pcm_playback_copy, + .silence = snd_gf1_pcm_playback_silence, +}; + +static snd_pcm_ops_t snd_gf1_pcm_capture_ops = { + .open = snd_gf1_pcm_capture_open, + .close = snd_gf1_pcm_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_gf1_pcm_capture_hw_params, + .hw_free = snd_gf1_pcm_capture_hw_free, + .prepare = snd_gf1_pcm_capture_prepare, + .trigger = snd_gf1_pcm_capture_trigger, + .pointer = snd_gf1_pcm_capture_pointer, +}; + +int snd_gf1_pcm_new(snd_gus_card_t * gus, int pcm_dev, int control_index, snd_pcm_t ** rpcm) +{ + snd_card_t *card; + snd_kcontrol_t *kctl; + snd_pcm_t *pcm; + snd_pcm_substream_t *substream; + int capture, err; + + if (rpcm) + *rpcm = NULL; + card = gus->card; + capture = !gus->interwave && !gus->ess_flag && !gus->ace_flag ? 1 : 0; + err = snd_pcm_new(card, + gus->interwave ? "AMD InterWave" : "GF1", + pcm_dev, + gus->gf1.pcm_channels / 2, + capture, + &pcm); + if (err < 0) + return err; + pcm->private_data = gus; + pcm->private_free = snd_gf1_pcm_free; + /* playback setup */ + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_gf1_pcm_playback_ops); + + for (substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; substream; substream = substream->next) + snd_pcm_lib_preallocate_pages(substream, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, gus->gf1.dma1 > 3 ? 128*1024 : 64*1024); + + pcm->info_flags = 0; + pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX; + if (capture) { + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_gf1_pcm_capture_ops); + if (gus->gf1.dma2 == gus->gf1.dma1) + pcm->info_flags |= SNDRV_PCM_INFO_HALF_DUPLEX; + snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(), + 64*1024, gus->gf1.dma2 > 3 ? 128*1024 : 64*1024); + } + strcpy(pcm->name, pcm->id); + if (gus->interwave) { + sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A'); + } + strcat(pcm->name, " (synth)"); + gus->pcm = pcm; + + if (gus->codec_flag) + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control1, gus); + else + kctl = snd_ctl_new1(&snd_gf1_pcm_volume_control, gus); + if ((err = snd_ctl_add(card, kctl)) < 0) + return err; + kctl->id.index = control_index; + + if (rpcm) + *rpcm = pcm; + return 0; +} + diff --git a/sound/isa/gus/gus_reset.c b/sound/isa/gus/gus_reset.c new file mode 100644 index 00000000000..b4e66f6a10a --- /dev/null +++ b/sound/isa/gus/gus_reset.c @@ -0,0 +1,413 @@ +/* + * 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/delay.h> +#include <linux/interrupt.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +extern void snd_gf1_timers_init(snd_gus_card_t * gus); +extern void snd_gf1_timers_done(snd_gus_card_t * gus); +extern int snd_gf1_synth_init(snd_gus_card_t * gus); +extern void snd_gf1_synth_done(snd_gus_card_t * gus); + +/* + * ok.. default interrupt handlers... + */ + +static void snd_gf1_default_interrupt_handler_midi_out(snd_gus_card_t * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x20); +} + +static void snd_gf1_default_interrupt_handler_midi_in(snd_gus_card_t * gus) +{ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd &= ~0x80); +} + +static void snd_gf1_default_interrupt_handler_timer1(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~4); +} + +static void snd_gf1_default_interrupt_handler_timer2(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, gus->gf1.timer_enabled &= ~8); +} + +static void snd_gf1_default_interrupt_handler_wave_and_volume(snd_gus_card_t * gus, snd_gus_voice_t * voice) +{ + snd_gf1_i_ctrl_stop(gus, 0x00); + snd_gf1_i_ctrl_stop(gus, 0x0d); +} + +static void snd_gf1_default_interrupt_handler_dma_write(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, 0x41, 0x00); +} + +static void snd_gf1_default_interrupt_handler_dma_read(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, 0x49, 0x00); +} + +void snd_gf1_set_default_handlers(snd_gus_card_t * gus, unsigned int what) +{ + if (what & SNDRV_GF1_HANDLER_MIDI_OUT) + gus->gf1.interrupt_handler_midi_out = snd_gf1_default_interrupt_handler_midi_out; + if (what & SNDRV_GF1_HANDLER_MIDI_IN) + gus->gf1.interrupt_handler_midi_in = snd_gf1_default_interrupt_handler_midi_in; + if (what & SNDRV_GF1_HANDLER_TIMER1) + gus->gf1.interrupt_handler_timer1 = snd_gf1_default_interrupt_handler_timer1; + if (what & SNDRV_GF1_HANDLER_TIMER2) + gus->gf1.interrupt_handler_timer2 = snd_gf1_default_interrupt_handler_timer2; + if (what & SNDRV_GF1_HANDLER_VOICE) { + snd_gus_voice_t *voice; + + voice = &gus->gf1.voices[what & 0xffff]; + voice->handler_wave = + voice->handler_volume = snd_gf1_default_interrupt_handler_wave_and_volume; + voice->handler_effect = NULL; + voice->volume_change = NULL; + } + if (what & SNDRV_GF1_HANDLER_DMA_WRITE) + gus->gf1.interrupt_handler_dma_write = snd_gf1_default_interrupt_handler_dma_write; + if (what & SNDRV_GF1_HANDLER_DMA_READ) + gus->gf1.interrupt_handler_dma_read = snd_gf1_default_interrupt_handler_dma_read; +} + +/* + + */ + +static void snd_gf1_clear_regs(snd_gus_card_t * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + inb(GUSP(gus, IRQSTAT)); + snd_gf1_write8(gus, 0x41, 0); /* DRAM DMA Control Register */ + snd_gf1_write8(gus, 0x45, 0); /* Timer Control */ + snd_gf1_write8(gus, 0x49, 0); /* Sampling Control Register */ + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void snd_gf1_look_regs(snd_gus_card_t * gus) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_look8(gus, 0x41); /* DRAM DMA Control Register */ + snd_gf1_look8(gus, 0x49); /* Sampling Control Register */ + inb(GUSP(gus, IRQSTAT)); + snd_gf1_read8(gus, 0x0f); /* IRQ Source Register */ + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +/* + * put selected GF1 voices to initial stage... + */ + +void snd_gf1_smart_stop_voice(snd_gus_card_t * gus, unsigned short voice) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice); +#if 0 + printk(" -%i- smart stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +void snd_gf1_stop_voice(snd_gus_card_t * gus, unsigned short voice) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice); +#if 0 + printk(" -%i- stop voice - volume = 0x%x\n", voice, snd_gf1_i_read16(gus, SNDRV_GF1_VW_VOLUME)); +#endif + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); + spin_unlock_irqrestore(&gus->reg_lock, flags); +#if 0 + snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_VIBRATO); + snd_gf1_lfo_shutdown(gus, voice, ULTRA_LFO_TREMOLO); +#endif +} + +void snd_gf1_clear_voices(snd_gus_card_t * gus, unsigned short v_min, unsigned short v_max) +{ + unsigned long flags; + unsigned int daddr; + unsigned short i, w_16; + + daddr = gus->gf1.default_voice_address << 4; + for (i = v_min; i <= v_max; i++) { +#if 0 + if (gus->gf1.syn_voices) + gus->gf1.syn_voices[i].flags = ~VFLG_DYNAMIC; +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, i); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); /* Voice Control Register = voice stop */ + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); /* Volume Ramp Control Register = ramp off */ + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, gus->gf1.memory ? 0x02 : 0x82); /* Deactivate voice */ + w_16 = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL) & 0x04; + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, 0x400); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, daddr, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, 0); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, daddr, w_16); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, 7); + if (gus->gf1.enh_mode) { + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, 0); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, 0); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); +#if 0 + snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_VIBRATO); + snd_gf1_lfo_shutdown(gus, i, ULTRA_LFO_TREMOLO); +#endif + } +} + +void snd_gf1_stop_voices(snd_gus_card_t * gus, unsigned short v_min, unsigned short v_max) +{ + unsigned long flags; + short i, ramp_ok; + unsigned short ramp_end; + + if (!in_interrupt()) { /* this can't be done in interrupt */ + for (i = v_min, ramp_ok = 0; i <= v_max; i++) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, i); + ramp_end = snd_gf1_read16(gus, 9) >> 8; + if (ramp_end > SNDRV_GF1_MIN_OFFSET) { + ramp_ok++; + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, 20); /* ramp rate */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, SNDRV_GF1_MIN_OFFSET); /* ramp start */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, ramp_end); /* ramp end */ + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); /* ramp down */ + if (gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, 0x40); + } + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + } + msleep_interruptible(50); + } + snd_gf1_clear_voices(gus, v_min, v_max); +} + +static void snd_gf1_alloc_voice_use(snd_gus_card_t * gus, + snd_gus_voice_t * pvoice, + int type, int client, int port) +{ + pvoice->use = 1; + switch (type) { + case SNDRV_GF1_VOICE_TYPE_PCM: + gus->gf1.pcm_alloc_voices++; + pvoice->pcm = 1; + break; + case SNDRV_GF1_VOICE_TYPE_SYNTH: + pvoice->synth = 1; + pvoice->client = client; + pvoice->port = port; + break; + case SNDRV_GF1_VOICE_TYPE_MIDI: + pvoice->midi = 1; + pvoice->client = client; + pvoice->port = port; + break; + } +} + +snd_gus_voice_t *snd_gf1_alloc_voice(snd_gus_card_t * gus, int type, int client, int port) +{ + snd_gus_voice_t *pvoice; + unsigned long flags; + int idx; + + spin_lock_irqsave(&gus->voice_alloc, flags); + if (type == SNDRV_GF1_VOICE_TYPE_PCM) { + if (gus->gf1.pcm_alloc_voices >= gus->gf1.pcm_channels) { + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return NULL; + } + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (!pvoice->use) { + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return pvoice; + } + } + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (pvoice->midi && !pvoice->client) { + snd_gf1_clear_voices(gus, pvoice->number, pvoice->number); + snd_gf1_alloc_voice_use(gus, pvoice, type, client, port); + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return pvoice; + } + } + spin_unlock_irqrestore(&gus->voice_alloc, flags); + return NULL; +} + +void snd_gf1_free_voice(snd_gus_card_t * gus, snd_gus_voice_t *voice) +{ + unsigned long flags; + void (*private_free)(snd_gus_voice_t *voice); + void *private_data; + + if (voice == NULL || !voice->use) + return; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | voice->number); + snd_gf1_clear_voices(gus, voice->number, voice->number); + spin_lock_irqsave(&gus->voice_alloc, flags); + private_free = voice->private_free; + private_data = voice->private_data; + voice->private_free = NULL; + voice->private_data = NULL; + if (voice->pcm) + gus->gf1.pcm_alloc_voices--; + voice->use = voice->pcm = 0; + voice->sample_ops = NULL; + spin_unlock_irqrestore(&gus->voice_alloc, flags); + if (private_free) + private_free(voice); +} + +/* + * call this function only by start of driver + */ + +int snd_gf1_start(snd_gus_card_t * gus) +{ + unsigned long flags; + unsigned int i; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_JOYSTICK_DAC_LEVEL, gus->joystick_dac); + + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_ALL); + for (i = 0; i < 32; i++) { + gus->gf1.voices[i].number = i; + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_VOICE | i); + } + + snd_gf1_uart_cmd(gus, 0x03); /* huh.. this cleanup took me some time... */ + + if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + } + snd_gf1_clear_regs(gus); + snd_gf1_select_active_voices(gus); + snd_gf1_delay(gus); + gus->gf1.default_voice_address = gus->gf1.memory > 0 ? 0 : 512 - 8; + /* initialize LFOs & clear LFOs memory */ + if (gus->gf1.enh_mode && gus->gf1.memory) { + gus->gf1.hw_lfo = 1; + gus->gf1.default_voice_address += 1024; + } else { + gus->gf1.sw_lfo = 1; + } +#if 0 + snd_gf1_lfo_init(gus); +#endif + if (gus->gf1.memory > 0) + for (i = 0; i < 4; i++) + snd_gf1_poke(gus, gus->gf1.default_voice_address + i, 0); + snd_gf1_clear_regs(gus); + snd_gf1_clear_voices(gus, 0, 31); + snd_gf1_look_regs(gus); + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 7); /* Reset Register = IRQ enable, DAC enable */ + if (gus->gf1.enh_mode) { /* enhanced mode !!!! */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_i_look8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); + } + while ((snd_gf1_i_read8(gus, SNDRV_GF1_GB_VOICES_IRQ) & 0xc0) != 0xc0); + + spin_lock_irqsave(&gus->reg_lock, flags); + outb(gus->gf1.active_voice = 0, GUSP(gus, GF1PAGE)); + outb(gus->mix_cntrl_reg, GUSP(gus, MIXCNTRLREG)); + spin_unlock_irqrestore(&gus->reg_lock, flags); + + snd_gf1_timers_init(gus); + snd_gf1_look_regs(gus); + snd_gf1_mem_init(gus); + snd_gf1_mem_proc_init(gus); +#ifdef CONFIG_SND_DEBUG + snd_gus_irq_profile_init(gus); +#endif + +#if 0 + if (gus->pnp_flag) { + if (gus->chip.playback_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_RECORD_BASE_ADDR, gus->chip.playback_fifo_block->ptr >> 8); + if (gus->chip.record_fifo_size > 0) + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_PLAY_BASE_ADDR, gus->chip.record_fifo_block->ptr >> 8); + snd_gf1_i_write16(gus, SNDRV_GF1_GW_FIFO_SIZE, gus->chip.interwave_fifo_reg); + } +#endif + + return 0; +} + +/* + * call this function only by shutdown of driver + */ + +int snd_gf1_stop(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0); /* stop all timers */ + snd_gf1_stop_voices(gus, 0, 31); /* stop all voices */ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* disable IRQ & DAC */ + snd_gf1_timers_done(gus); + snd_gf1_mem_done(gus); +#if 0 + snd_gf1_lfo_done(gus); +#endif + return 0; +} diff --git a/sound/isa/gus/gus_sample.c b/sound/isa/gus/gus_sample.c new file mode 100644 index 00000000000..4290e03acd5 --- /dev/null +++ b/sound/isa/gus/gus_sample.c @@ -0,0 +1,155 @@ +/* + * Routines for Gravis UltraSound soundcards - Sample support + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +/* + * + */ + +static void select_instrument(snd_gus_card_t * gus, snd_gus_voice_t * v) +{ + snd_seq_kinstr_t *instr; + +#if 0 + printk("select instrument: cluster = %li, std = 0x%x, bank = %i, prg = %i\n", + v->instr.cluster, + v->instr.std, + v->instr.bank, + v->instr.prg); +#endif + instr = snd_seq_instr_find(gus->gf1.ilist, &v->instr, 0, 1); + if (instr != NULL) { + if (instr->ops) { + if (!strcmp(instr->ops->instr_type, SNDRV_SEQ_INSTR_ID_SIMPLE)) + snd_gf1_simple_init(v); + } + snd_seq_instr_free_use(gus->gf1.ilist, instr); + } +} + +/* + * + */ + +static void event_sample(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->gus, v, SAMPLE_STOP_IMMEDIATELY); + v->instr.std = ev->data.sample.param.sample.std; + if (v->instr.std & 0xff000000) { /* private instrument */ + v->instr.std &= 0x00ffffff; + v->instr.std |= (unsigned int)ev->source.client << 24; + } + v->instr.bank = ev->data.sample.param.sample.bank; + v->instr.prg = ev->data.sample.param.sample.prg; + select_instrument(p->gus, v); +} + +static void event_cluster(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->gus, v, SAMPLE_STOP_IMMEDIATELY); + v->instr.cluster = ev->data.sample.param.cluster.cluster; + select_instrument(p->gus, v); +} + +static void event_start(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_start) + v->sample_ops->sample_start(p->gus, v, ev->data.sample.param.position); +} + +static void event_stop(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_stop) + v->sample_ops->sample_stop(p->gus, v, ev->data.sample.param.stop_mode); +} + +static void event_freq(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_freq) + v->sample_ops->sample_freq(p->gus, v, ev->data.sample.param.frequency); +} + +static void event_volume(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_volume) + v->sample_ops->sample_volume(p->gus, v, &ev->data.sample.param.volume); +} + +static void event_loop(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_loop) + v->sample_ops->sample_loop(p->gus, v, &ev->data.sample.param.loop); +} + +static void event_position(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_pos) + v->sample_ops->sample_pos(p->gus, v, ev->data.sample.param.position); +} + +static void event_private1(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v) +{ + if (v->sample_ops && v->sample_ops->sample_private1) + v->sample_ops->sample_private1(p->gus, v, (unsigned char *)&ev->data.sample.param.raw8); +} + +typedef void (gus_sample_event_handler_t)(snd_seq_event_t *ev, snd_gus_port_t *p, snd_gus_voice_t *v); + +static gus_sample_event_handler_t *gus_sample_event_handlers[9] = { + event_sample, + event_cluster, + event_start, + event_stop, + event_freq, + event_volume, + event_loop, + event_position, + event_private1 +}; + +void snd_gus_sample_event(snd_seq_event_t *ev, snd_gus_port_t *p) +{ + int idx, voice; + snd_gus_card_t *gus = p->gus; + snd_gus_voice_t *v; + unsigned long flags; + + idx = ev->type - SNDRV_SEQ_EVENT_SAMPLE; + if (idx < 0 || idx > 8) + return; + for (voice = 0; voice < 32; voice++) { + v = &gus->gf1.voices[voice]; + if (v->use && v->client == ev->source.client && + v->port == ev->source.port && + v->index == ev->data.sample.channel) { + spin_lock_irqsave(&gus->event_lock, flags); + gus_sample_event_handlers[idx](ev, p, v); + spin_unlock_irqrestore(&gus->event_lock, flags); + return; + } + } +} diff --git a/sound/isa/gus/gus_simple.c b/sound/isa/gus/gus_simple.c new file mode 100644 index 00000000000..c122e7be6ce --- /dev/null +++ b/sound/isa/gus/gus_simple.c @@ -0,0 +1,634 @@ +/* + * Routines for Gravis UltraSound soundcards - Simple instrument handlers + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> +#include "gus_tables.h" + +/* + * + */ + +static void interrupt_wave(snd_gus_card_t *gus, snd_gus_voice_t *voice); +static void interrupt_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice); +static void interrupt_effect(snd_gus_card_t *gus, snd_gus_voice_t *voice); + +static void sample_start(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position); +static void sample_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_stop_mode_t mode); +static void sample_freq(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_frequency_t freq); +static void sample_volume(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_ev_volume_t *volume); +static void sample_loop(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_ev_loop_t *loop); +static void sample_pos(snd_gus_card_t *card, snd_gus_voice_t *voice, snd_seq_position_t position); +static void sample_private1(snd_gus_card_t *card, snd_gus_voice_t *voice, unsigned char *data); + +static snd_gus_sample_ops_t sample_ops = { + sample_start, + sample_stop, + sample_freq, + sample_volume, + sample_loop, + sample_pos, + sample_private1 +}; + +#if 0 + +static void note_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, int wait); +static void note_wait(snd_gus_card_t *gus, snd_gus_voice_t *voice); +static void note_off(snd_gus_card_t *gus, snd_gus_voice_t *voice); +static void note_volume(snd_gus_card_t *card, snd_gus_voice_t *voice); +static void note_pitchbend(snd_gus_card_t *card, snd_gus_voice_t *voice); +static void note_vibrato(snd_gus_card_t *card, snd_gus_voice_t *voice); +static void note_tremolo(snd_gus_card_t *card, snd_gus_voice_t *voice); + +static struct snd_gus_note_handlers note_commands = { + note_stop, + note_wait, + note_off, + note_volume, + note_pitchbend, + note_vibrato, + note_tremolo +}; + +static void chn_trigger_down(snd_gus_card_t *card, ultra_channel_t *channel, ultra_instrument_t *instrument, unsigned char note, unsigned char velocity, unsigned char priority ); +static void chn_trigger_up( ultra_card_t *card, ultra_note_t *note ); +static void chn_control( ultra_card_t *card, ultra_channel_t *channel, unsigned short p1, unsigned short p2 ); + +static struct ULTRA_STRU_INSTRUMENT_CHANNEL_COMMANDS channel_commands = { + chn_trigger_down, + chn_trigger_up, + chn_control +}; + +#endif + +static void do_volume_envelope(snd_gus_card_t *card, snd_gus_voice_t *voice); +static void do_pan_envelope(snd_gus_card_t *card, snd_gus_voice_t *voice); + +/* + * + */ + +static void interrupt_wave(snd_gus_card_t *gus, snd_gus_voice_t *voice) +{ + spin_lock(&gus->event_lock); + snd_gf1_stop_voice(gus, voice->number); + spin_lock(&gus->reg_lock); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, 0); + spin_unlock(&gus->reg_lock); + voice->flags &= ~SNDRV_GF1_VFLG_RUNNING; + spin_unlock(&gus->event_lock); +} + +static void interrupt_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice) +{ + spin_lock(&gus->event_lock); + if (voice->flags & SNDRV_GF1_VFLG_RUNNING) + do_volume_envelope(gus, voice); + else + snd_gf1_stop_voice(gus, voice->number); + spin_unlock(&gus->event_lock); +} + +static void interrupt_effect(snd_gus_card_t *gus, snd_gus_voice_t *voice) +{ + spin_lock(&gus->event_lock); + if ((voice->flags & (SNDRV_GF1_VFLG_RUNNING|SNDRV_GF1_VFLG_EFFECT_TIMER1)) == + (SNDRV_GF1_VFLG_RUNNING|SNDRV_GF1_VFLG_EFFECT_TIMER1)) + do_pan_envelope(gus, voice); + spin_unlock(&gus->event_lock); +} + +/* + * + */ + +static void do_volume_envelope(snd_gus_card_t *gus, snd_gus_voice_t *voice) +{ + unsigned short next, rate, old_volume; + int program_next_ramp; + unsigned long flags; + + if (!gus->gf1.volume_ramp) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, voice->gf1_volume); + printk("gf1_volume = 0x%x\n", voice->gf1_volume); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return; + } + program_next_ramp = 0; + rate = next = 0; + while (1) { + program_next_ramp = 0; + rate = next = 0; + switch (voice->venv_state) { + case VENV_BEFORE: + voice->venv_state = VENV_ATTACK; + voice->venv_value_next = 0; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME); + spin_unlock_irqrestore(&gus->reg_lock, flags); + break; + case VENV_ATTACK: + voice->venv_state = VENV_SUSTAIN; + program_next_ramp++; + next = 255; + rate = gus->gf1.volume_ramp; + break; + case VENV_SUSTAIN: + voice->venv_state = VENV_RELEASE; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, ((int)voice->gf1_volume * (int)voice->venv_value_next) / 255); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return; + case VENV_RELEASE: + voice->venv_state = VENV_DONE; + program_next_ramp++; + next = 0; + rate = gus->gf1.volume_ramp; + break; + case VENV_DONE: + snd_gf1_stop_voice(gus, voice->number); + voice->flags &= ~SNDRV_GF1_VFLG_RUNNING; + return; + case VENV_VOLUME: + program_next_ramp++; + next = voice->venv_value_next; + rate = gus->gf1.volume_ramp; + voice->venv_state = voice->venv_state_prev; + break; + } + voice->venv_value_next = next; + if (!program_next_ramp) + continue; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_ctrl_stop(gus, SNDRV_GF1_VB_VOLUME_CONTROL); + old_volume = snd_gf1_read16(gus, SNDRV_GF1_VW_VOLUME) >> 8; + if (!rate) { + spin_unlock_irqrestore(&gus->reg_lock, flags); + continue; + } + next = (((int)voice->gf1_volume * (int)next) / 255) >> 8; + if (old_volume < SNDRV_GF1_MIN_OFFSET) + old_volume = SNDRV_GF1_MIN_OFFSET; + if (next < SNDRV_GF1_MIN_OFFSET) + next = SNDRV_GF1_MIN_OFFSET; + if (next > SNDRV_GF1_MAX_OFFSET) + next = SNDRV_GF1_MAX_OFFSET; + if (old_volume == next) { + spin_unlock_irqrestore(&gus->reg_lock, flags); + continue; + } + voice->volume_control &= ~0xc3; + voice->volume_control |= 0x20; + if (old_volume > next) { + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, next); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, old_volume); + voice->volume_control |= 0x40; + } else { + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_START, old_volume); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_END, next); + } + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_RATE, rate); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, voice->volume_control); + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_VOLUME_CONTROL, voice->volume_control); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + return; + } +} + +static void do_pan_envelope(snd_gus_card_t *gus, snd_gus_voice_t *voice) +{ + unsigned long flags; + unsigned char old_pan; + +#if 0 + snd_gf1_select_voice(gus, voice->number); + printk(" -%i- do_pan_envelope - flags = 0x%x (0x%x -> 0x%x)\n", + voice->number, + voice->flags, + voice->gf1_pan, + snd_gf1_i_read8(gus, SNDRV_GF1_VB_PAN) & 0x0f); +#endif + if (gus->gf1.enh_mode) { + voice->flags &= ~(SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN); + return; + } + if (!gus->gf1.smooth_pan) { + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, voice->gf1_pan); + spin_unlock_irqrestore(&gus->reg_lock, flags); + return; + } + if (!(voice->flags & SNDRV_GF1_VFLG_PAN)) /* before */ + voice->flags |= SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + old_pan = snd_gf1_read8(gus, SNDRV_GF1_VB_PAN) & 0x0f; + if (old_pan > voice->gf1_pan ) + old_pan--; + if (old_pan < voice->gf1_pan) + old_pan++; + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, old_pan); + spin_unlock_irqrestore(&gus->reg_lock, flags); + if (old_pan == voice->gf1_pan) /* the goal was reached */ + voice->flags &= ~(SNDRV_GF1_VFLG_EFFECT_TIMER1|SNDRV_GF1_VFLG_PAN); +#if 0 + snd_gf1_select_voice(gus, voice->number); + printk(" -%i- (1) do_pan_envelope - flags = 0x%x (0x%x -> 0x%x)\n", + voice->number, + voice->flags, + voice->gf1_pan, + snd_gf1_i_read8(gus, GF1_VB_PAN) & 0x0f); +#endif +} + +static void set_enhanced_pan(snd_gus_card_t *gus, snd_gus_voice_t *voice, unsigned short pan) +{ + unsigned long flags; + unsigned short vlo, vro; + + vlo = SNDRV_GF1_ATTEN((SNDRV_GF1_ATTEN_TABLE_SIZE-1) - pan); + vro = SNDRV_GF1_ATTEN(pan); + if (pan != SNDRV_GF1_ATTEN_TABLE_SIZE - 1 && pan != 0) { + vlo >>= 1; + vro >>= 1; + } + vlo <<= 4; + vro <<= 4; +#if 0 + printk("vlo = 0x%x (0x%x), vro = 0x%x (0x%x)\n", + vlo, snd_gf1_i_read16(gus, GF1_VW_OFFSET_LEFT), + vro, snd_gf1_i_read16(gus, GF1_VW_OFFSET_RIGHT)); +#endif + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT_FINAL, vlo); + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT_FINAL, vro); + spin_unlock_irqrestore(&gus->reg_lock, flags); + voice->vlo = vlo; + voice->vro = vro; +} + +/* + * + */ + +static void sample_start(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position) +{ + unsigned long flags; + unsigned int begin, addr, addr_end, addr_start; + int w_16; + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + + instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + begin = simple->address.memory << 4; + w_16 = simple->format & SIMPLE_WAVE_16BIT ? 0x04 : 0; + addr_start = simple->loop_start; + if (simple->format & SIMPLE_WAVE_LOOP) { + addr_end = simple->loop_end; + } else { + addr_end = (simple->size << 4) - (w_16 ? 40 : 24); + } + if (simple->format & SIMPLE_WAVE_BACKWARD) { + addr = simple->loop_end; + if (position < simple->loop_end) + addr -= position; + } else { + addr = position; + } + voice->control = 0x00; + voice->mode = 0x20; /* enable offset registers */ + if (simple->format & SIMPLE_WAVE_16BIT) + voice->control |= 0x04; + if (simple->format & SIMPLE_WAVE_BACKWARD) + voice->control |= 0x40; + if (simple->format & SIMPLE_WAVE_LOOP) { + voice->control |= 0x08; + } else { + voice->control |= 0x20; + } + if (simple->format & SIMPLE_WAVE_BIDIR) + voice->control |= 0x10; + if (simple->format & SIMPLE_WAVE_ULAW) + voice->mode |= 0x40; + if (w_16) { + addr = ((addr << 1) & ~0x1f) | (addr & 0x0f); + addr_start = ((addr_start << 1) & ~0x1f) | (addr_start & 0x0f); + addr_end = ((addr_end << 1) & ~0x1f) | (addr_end & 0x0f); + } + addr += begin; + addr_start += begin; + addr_end += begin; + snd_gf1_stop_voice(gus, voice->number); + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, voice->fc_register + voice->fc_lfo); + voice->venv_state = VENV_BEFORE; + voice->volume_control = 0x03; + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, addr_start, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, addr_end, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, addr, w_16); + if (!gus->gf1.enh_mode) { + snd_gf1_write8(gus, SNDRV_GF1_VB_PAN, voice->gf1_pan); + } else { + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT, voice->vlo); + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_LEFT_FINAL, voice->vlo); + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT, voice->vro); + snd_gf1_write16(gus, SNDRV_GF1_VW_OFFSET_RIGHT_FINAL, voice->vro); + snd_gf1_write8(gus, SNDRV_GF1_VB_ACCUMULATOR, voice->effect_accumulator); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME, voice->gf1_effect_volume); + snd_gf1_write16(gus, SNDRV_GF1_VW_EFFECT_VOLUME_FINAL, voice->gf1_effect_volume); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); + do_volume_envelope(gus, voice); + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + if (gus->gf1.enh_mode) + snd_gf1_write8(gus, SNDRV_GF1_VB_MODE, voice->mode); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice->control); + if (!gus->gf1.enh_mode) { + snd_gf1_delay(gus); + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, voice->control ); + } + spin_unlock_irqrestore(&gus->reg_lock, flags); +#if 0 + snd_gf1_print_voice_registers(gus); +#endif + voice->flags |= SNDRV_GF1_VFLG_RUNNING; + snd_seq_instr_free_use(gus->gf1.ilist, instr); +} + +static void sample_stop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_stop_mode_t mode) +{ + unsigned char control; + unsigned long flags; + + if (!(voice->flags & SNDRV_GF1_VFLG_RUNNING)) + return; + switch (mode) { + default: + if (gus->gf1.volume_ramp > 0) { + if (voice->venv_state < VENV_RELEASE) { + voice->venv_state = VENV_RELEASE; + do_volume_envelope(gus, voice); + } + } + if (mode != SAMPLE_STOP_VENVELOPE) { + snd_gf1_stop_voice(gus, voice->number); + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_VOLUME, SNDRV_GF1_MIN_VOLUME); + spin_unlock_irqrestore(&gus->reg_lock, flags); + voice->flags &= ~SNDRV_GF1_VFLG_RUNNING; + } + break; + case SAMPLE_STOP_LOOP: /* disable loop only */ + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + control = snd_gf1_read8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL); + control &= ~(0x83 | 0x04); + control |= 0x20; + snd_gf1_write8(gus, SNDRV_GF1_VB_ADDRESS_CONTROL, control); + spin_unlock_irqrestore(&gus->reg_lock, flags); + break; + } +} + +static void sample_freq(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_frequency_t freq) +{ + unsigned long flags; + + spin_lock_irqsave(&gus->reg_lock, flags); + voice->fc_register = snd_gf1_translate_freq(gus, freq); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write16(gus, SNDRV_GF1_VW_FREQUENCY, voice->fc_register + voice->fc_lfo); + spin_unlock_irqrestore(&gus->reg_lock, flags); +} + +static void sample_volume(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_ev_volume_t *volume) +{ + if (volume->volume >= 0) { + volume->volume &= 0x3fff; + voice->gf1_volume = snd_gf1_lvol_to_gvol_raw(volume->volume << 2) << 4; + voice->venv_state_prev = VENV_SUSTAIN; + voice->venv_state = VENV_VOLUME; + do_volume_envelope(gus, voice); + } + if (volume->lr >= 0) { + volume->lr &= 0x3fff; + if (!gus->gf1.enh_mode) { + voice->gf1_pan = (volume->lr >> 10) & 15; + if (!gus->gf1.full_range_pan) { + if (voice->gf1_pan == 0) + voice->gf1_pan++; + if (voice->gf1_pan == 15) + voice->gf1_pan--; + } + voice->flags &= ~SNDRV_GF1_VFLG_PAN; /* before */ + do_pan_envelope(gus, voice); + } else { + set_enhanced_pan(gus, voice, volume->lr >> 7); + } + } +} + +static void sample_loop(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_ev_loop_t *loop) +{ + unsigned long flags; + int w_16 = voice->control & 0x04; + unsigned int begin, addr_start, addr_end; + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + +#if 0 + printk("voice_loop: start = 0x%x, end = 0x%x\n", loop->start, loop->end); +#endif + instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + begin = simple->address.memory; + addr_start = loop->start; + addr_end = loop->end; + addr_start = (((addr_start << 1) & ~0x1f) | (addr_start & 0x0f)) + begin; + addr_end = (((addr_end << 1) & ~0x1f) | (addr_end & 0x0f)) + begin; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_START, addr_start, w_16); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_END, addr_end, w_16); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_seq_instr_free_use(gus->gf1.ilist, instr); +} + +static void sample_pos(snd_gus_card_t *gus, snd_gus_voice_t *voice, snd_seq_position_t position) +{ + unsigned long flags; + int w_16 = voice->control & 0x04; + unsigned int begin, addr; + simple_instrument_t *simple; + snd_seq_kinstr_t *instr; + +#if 0 + printk("voice_loop: start = 0x%x, end = 0x%x\n", loop->start, loop->end); +#endif + instr = snd_seq_instr_find(gus->gf1.ilist, &voice->instr, 0, 1); + if (instr == NULL) + return; + voice->instr = instr->instr; /* copy ID to speedup aliases */ + simple = KINSTR_DATA(instr); + begin = simple->address.memory; + addr = (((position << 1) & ~0x1f) | (position & 0x0f)) + begin; + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_select_voice(gus, voice->number); + snd_gf1_write_addr(gus, SNDRV_GF1_VA_CURRENT, addr, w_16); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_seq_instr_free_use(gus->gf1.ilist, instr); +} + +#if 0 + +static unsigned char get_effects_mask( ultra_card_t *card, int value ) +{ + if ( value > 7 ) return 0; + if ( card -> gf1.effects && card -> gf1.effects -> chip_type == ULTRA_EFFECT_CHIP_INTERWAVE ) + return card -> gf1.effects -> chip.interwave.voice_output[ value ]; + return 0; +} + +#endif + +static void sample_private1(snd_gus_card_t *card, snd_gus_voice_t *voice, unsigned char *data) +{ +#if 0 + unsigned long flags; + unsigned char uc; + + switch ( *data ) { + case ULTRA_PRIV1_IW_EFFECT: + uc = get_effects_mask( card, ultra_get_byte( data, 4 ) ); + uc |= get_effects_mask( card, ultra_get_byte( data, 4 ) >> 4 ); + uc |= get_effects_mask( card, ultra_get_byte( data, 5 ) ); + uc |= get_effects_mask( card, ultra_get_byte( data, 5 ) >> 4 ); + voice -> data.simple.effect_accumulator = uc; + voice -> data.simple.effect_volume = ultra_translate_voice_volume( card, ultra_get_word( data, 2 ) ) << 4; + if ( !card -> gf1.enh_mode ) return; + if ( voice -> flags & VFLG_WAIT_FOR_START ) return; + if ( voice -> flags & VFLG_RUNNING ) + { + CLI( &flags ); + gf1_select_voice( card, voice -> number ); + ultra_write8( card, GF1_VB_ACCUMULATOR, voice -> data.simple.effect_accumulator ); + ultra_write16( card, GF1_VW_EFFECT_VOLUME_FINAL, voice -> data.simple.effect_volume ); + STI( &flags ); + } + break; + case ULTRA_PRIV1_IW_LFO: + ultra_lfo_command( card, voice -> number, data ); + } +#endif +} + +#if 0 + +/* + * + */ + +static void note_stop( ultra_card_t *card, ultra_voice_t *voice, int wait ) +{ +} + +static void note_wait( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +static void note_off( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +static void note_volume( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +static void note_pitchbend( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +static void note_vibrato( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +static void note_tremolo( ultra_card_t *card, ultra_voice_t *voice ) +{ +} + +/* + * + */ + +static void chn_trigger_down( ultra_card_t *card, ultra_channel_t *channel, ultra_instrument_t *instrument, unsigned char note, unsigned char velocity, unsigned char priority ) +{ +} + +static void chn_trigger_up( ultra_card_t *card, ultra_note_t *note ) +{ +} + +static void chn_control( ultra_card_t *card, ultra_channel_t *channel, unsigned short p1, unsigned short p2 ) +{ +} + +/* + * + */ + +#endif + +void snd_gf1_simple_init(snd_gus_voice_t *voice) +{ + voice->handler_wave = interrupt_wave; + voice->handler_volume = interrupt_volume; + voice->handler_effect = interrupt_effect; + voice->volume_change = NULL; + voice->sample_ops = &sample_ops; +} diff --git a/sound/isa/gus/gus_synth.c b/sound/isa/gus/gus_synth.c new file mode 100644 index 00000000000..66552e6013a --- /dev/null +++ b/sound/isa/gus/gus_synth.c @@ -0,0 +1,329 @@ +/* + * Routines for Gravis UltraSound soundcards - Synthesizer + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/seq_device.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for Gravis UltraSound soundcards - Synthesizer"); +MODULE_LICENSE("GPL"); + +/* + * + */ + +static void snd_gus_synth_free_voices(snd_gus_card_t * gus, int client, int port) +{ + int idx; + snd_gus_voice_t * voice; + + for (idx = 0; idx < 32; idx++) { + voice = &gus->gf1.voices[idx]; + if (voice->use && voice->client == client && voice->port == port) + snd_gf1_free_voice(gus, voice); + } +} + +static int snd_gus_synth_use(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_gus_port_t * port = (snd_gus_port_t *)private_data; + snd_gus_card_t * gus = port->gus; + snd_gus_voice_t * voice; + unsigned int idx; + + if (info->voices > 32) + return -EINVAL; + down(&gus->register_mutex); + if (!snd_gus_use_inc(gus)) { + up(&gus->register_mutex); + return -EFAULT; + } + for (idx = 0; idx < info->voices; idx++) { + voice = snd_gf1_alloc_voice(gus, SNDRV_GF1_VOICE_TYPE_SYNTH, info->sender.client, info->sender.port); + if (voice == NULL) { + snd_gus_synth_free_voices(gus, info->sender.client, info->sender.port); + snd_gus_use_dec(gus); + up(&gus->register_mutex); + return -EBUSY; + } + voice->index = idx; + } + up(&gus->register_mutex); + return 0; +} + +static int snd_gus_synth_unuse(void *private_data, snd_seq_port_subscribe_t *info) +{ + snd_gus_port_t * port = (snd_gus_port_t *)private_data; + snd_gus_card_t * gus = port->gus; + + down(&gus->register_mutex); + snd_gus_synth_free_voices(gus, info->sender.client, info->sender.port); + snd_gus_use_dec(gus); + up(&gus->register_mutex); + return 0; +} + +/* + * + */ + +static void snd_gus_synth_free_private_instruments(snd_gus_port_t *p, int client) +{ + snd_seq_instr_header_t ifree; + + memset(&ifree, 0, sizeof(ifree)); + ifree.cmd = SNDRV_SEQ_INSTR_FREE_CMD_PRIVATE; + snd_seq_instr_list_free_cond(p->gus->gf1.ilist, &ifree, client, 0); +} + +int snd_gus_synth_event_input(snd_seq_event_t *ev, int direct, void *private_data, int atomic, int hop) +{ + snd_gus_port_t * p = (snd_gus_port_t *) private_data; + + snd_assert(p != NULL, return -EINVAL); + if (ev->type >= SNDRV_SEQ_EVENT_SAMPLE && + ev->type <= SNDRV_SEQ_EVENT_SAMPLE_PRIVATE1) { + snd_gus_sample_event(ev, p); + return 0; + } + if (ev->source.client == SNDRV_SEQ_CLIENT_SYSTEM && + ev->source.port == SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE) { + if (ev->type == SNDRV_SEQ_EVENT_CLIENT_EXIT) { + snd_gus_synth_free_private_instruments(p, ev->data.addr.client); + return 0; + } + } + if (direct) { + if (ev->type >= SNDRV_SEQ_EVENT_INSTR_BEGIN) { + snd_seq_instr_event(&p->gus->gf1.iwffff_ops.kops, + p->gus->gf1.ilist, + ev, + p->gus->gf1.seq_client, + atomic, hop); + return 0; + } + } + return 0; +} + +static void snd_gus_synth_instr_notify(void *private_data, + snd_seq_kinstr_t *instr, + int what) +{ + unsigned int idx; + snd_gus_card_t *gus = private_data; + snd_gus_voice_t *pvoice; + unsigned long flags; + + spin_lock_irqsave(&gus->event_lock, flags); + for (idx = 0; idx < 32; idx++) { + pvoice = &gus->gf1.voices[idx]; + if (pvoice->use && !memcmp(&pvoice->instr, &instr->instr, sizeof(pvoice->instr))) { + if (pvoice->sample_ops && pvoice->sample_ops->sample_stop) { + pvoice->sample_ops->sample_stop(gus, pvoice, SAMPLE_STOP_IMMEDIATELY); + } else { + snd_gf1_stop_voice(gus, pvoice->number); + pvoice->flags &= ~SNDRV_GF1_VFLG_RUNNING; + } + } + } + spin_unlock_irqrestore(&gus->event_lock, flags); +} + +/* + * + */ + +static void snd_gus_synth_free_port(void *private_data) +{ + snd_gus_port_t * p = (snd_gus_port_t *)private_data; + + if (p) + snd_midi_channel_free_set(p->chset); +} + +static int snd_gus_synth_create_port(snd_gus_card_t * gus, int idx) +{ + snd_gus_port_t * p; + snd_seq_port_callback_t callbacks; + char name[32]; + int result; + + p = &gus->gf1.seq_ports[idx]; + p->chset = snd_midi_channel_alloc_set(16); + if (p->chset == NULL) + return -ENOMEM; + p->chset->private_data = p; + p->gus = gus; + p->client = gus->gf1.seq_client; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.owner = THIS_MODULE; + callbacks.use = snd_gus_synth_use; + callbacks.unuse = snd_gus_synth_unuse; + callbacks.event_input = snd_gus_synth_event_input; + callbacks.private_free = snd_gus_synth_free_port; + callbacks.private_data = p; + + sprintf(name, "%s port %i", gus->interwave ? "AMD InterWave" : "GF1", idx); + p->chset->port = snd_seq_event_port_attach(gus->gf1.seq_client, + &callbacks, + SNDRV_SEQ_PORT_CAP_WRITE | SNDRV_SEQ_PORT_CAP_SUBS_WRITE, + SNDRV_SEQ_PORT_TYPE_DIRECT_SAMPLE | + SNDRV_SEQ_PORT_TYPE_SYNTH, + 16, 0, + name); + if (p->chset->port < 0) { + result = p->chset->port; + snd_gus_synth_free_port(p); + return result; + } + p->port = p->chset->port; + return 0; +} + +/* + * + */ + +static int snd_gus_synth_new_device(snd_seq_device_t *dev) +{ + snd_gus_card_t *gus; + int client, i; + snd_seq_client_callback_t callbacks; + snd_seq_client_info_t *cinfo; + snd_seq_port_subscribe_t sub; + snd_iwffff_ops_t *iwops; + snd_gf1_ops_t *gf1ops; + snd_simple_ops_t *simpleops; + + gus = *(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (gus == NULL) + return -EINVAL; + + init_MUTEX(&gus->register_mutex); + gus->gf1.seq_client = -1; + + cinfo = kmalloc(sizeof(*cinfo), GFP_KERNEL); + if (! cinfo) + return -ENOMEM; + + /* allocate new client */ + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.private_data = gus; + callbacks.allow_output = callbacks.allow_input = 1; + client = gus->gf1.seq_client = + snd_seq_create_kernel_client(gus->card, 1, &callbacks); + if (client < 0) { + kfree(cinfo); + return client; + } + + /* change name of client */ + memset(cinfo, 0, sizeof(*cinfo)); + cinfo->client = client; + cinfo->type = KERNEL_CLIENT; + sprintf(cinfo->name, gus->interwave ? "AMD InterWave" : "GF1"); + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SET_CLIENT_INFO, cinfo); + kfree(cinfo); + + for (i = 0; i < 4; i++) + snd_gus_synth_create_port(gus, i); + + gus->gf1.ilist = snd_seq_instr_list_new(); + if (gus->gf1.ilist == NULL) { + snd_seq_delete_kernel_client(client); + gus->gf1.seq_client = -1; + return -ENOMEM; + } + gus->gf1.ilist->flags = SNDRV_SEQ_INSTR_FLG_DIRECT; + + simpleops = &gus->gf1.simple_ops; + snd_seq_simple_init(simpleops, gus, NULL); + simpleops->put_sample = snd_gus_simple_put_sample; + simpleops->get_sample = snd_gus_simple_get_sample; + simpleops->remove_sample = snd_gus_simple_remove_sample; + simpleops->notify = snd_gus_synth_instr_notify; + + gf1ops = &gus->gf1.gf1_ops; + snd_seq_gf1_init(gf1ops, gus, &simpleops->kops); + gf1ops->put_sample = snd_gus_gf1_put_sample; + gf1ops->get_sample = snd_gus_gf1_get_sample; + gf1ops->remove_sample = snd_gus_gf1_remove_sample; + gf1ops->notify = snd_gus_synth_instr_notify; + + iwops = &gus->gf1.iwffff_ops; + snd_seq_iwffff_init(iwops, gus, &gf1ops->kops); + iwops->put_sample = snd_gus_iwffff_put_sample; + iwops->get_sample = snd_gus_iwffff_get_sample; + iwops->remove_sample = snd_gus_iwffff_remove_sample; + iwops->notify = snd_gus_synth_instr_notify; + + memset(&sub, 0, sizeof(sub)); + sub.sender.client = SNDRV_SEQ_CLIENT_SYSTEM; + sub.sender.port = SNDRV_SEQ_PORT_SYSTEM_ANNOUNCE; + sub.dest.client = client; + sub.dest.port = 0; + snd_seq_kernel_client_ctl(client, SNDRV_SEQ_IOCTL_SUBSCRIBE_PORT, &sub); + + return 0; +} + +static int snd_gus_synth_delete_device(snd_seq_device_t *dev) +{ + snd_gus_card_t *gus; + + gus = *(snd_gus_card_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (gus == NULL) + return -EINVAL; + + if (gus->gf1.seq_client >= 0) { + snd_seq_delete_kernel_client(gus->gf1.seq_client); + gus->gf1.seq_client = -1; + } + if (gus->gf1.ilist) + snd_seq_instr_list_free(&gus->gf1.ilist); + return 0; +} + +static int __init alsa_gus_synth_init(void) +{ + static snd_seq_dev_ops_t ops = { + snd_gus_synth_new_device, + snd_gus_synth_delete_device + }; + + return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_GUS, &ops, + sizeof(snd_gus_card_t*)); +} + +static void __exit alsa_gus_synth_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_GUS); +} + +module_init(alsa_gus_synth_init) +module_exit(alsa_gus_synth_exit) diff --git a/sound/isa/gus/gus_tables.h b/sound/isa/gus/gus_tables.h new file mode 100644 index 00000000000..ed8e9d85ad3 --- /dev/null +++ b/sound/isa/gus/gus_tables.h @@ -0,0 +1,86 @@ +/* + * 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 + * + */ + +#define SNDRV_GF1_SCALE_TABLE_SIZE 128 +#define SNDRV_GF1_ATTEN_TABLE_SIZE 128 + +#ifdef __GUS_TABLES_ALLOC__ + +unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE] = +{ + 8372, 8870, 9397, 9956, 10548, 11175, + 11840, 12544, 13290, 14080, 14917, 15804, + 16744, 17740, 18795, 19912, 21096, 22351, + 23680, 25088, 26580, 28160, 29834, 31609, + 33488, 35479, 37589, 39824, 42192, 44701, + 47359, 50175, 53159, 56320, 59669, 63217, + 66976, 70959, 75178, 79649, 84385, 89402, + 94719, 100351, 106318, 112640, 119338, 126434, + 133952, 141918, 150356, 159297, 168769, 178805, + 189437, 200702, 212636, 225280, 238676, 252868, + 267905, 283835, 300713, 318594, 337539, 357610, + 378874, 401403, 425272, 450560, 477352, 505737, + 535809, 567670, 601425, 637188, 675077, 715219, + 757749, 802807, 850544, 901120, 954703, 1011473, + 1071618, 1135340, 1202851, 1274376, 1350154, 1430439, + 1515497, 1605613, 1701088, 1802240, 1909407, 2022946, + 2143237, 2270680, 2405702, 2548752, 2700309, 2860878, + 3030994, 3211227, 3402176, 3604480, 3818814, 4045892, + 4286473, 4541360, 4811404, 5097505, 5400618, 5721755, + 6061989, 6422453, 6804352, 7208960, 7637627, 8091784, + 8572947, 9082720, 9622807, 10195009, 10801236, 11443511, + 12123977, 12844906 +}; + +unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE] = { + 4095 /* 0 */,1789 /* 1 */,1533 /* 2 */,1383 /* 3 */,1277 /* 4 */, + 1195 /* 5 */,1127 /* 6 */,1070 /* 7 */,1021 /* 8 */,978 /* 9 */, + 939 /* 10 */,903 /* 11 */,871 /* 12 */,842 /* 13 */,814 /* 14 */, + 789 /* 15 */,765 /* 16 */,743 /* 17 */,722 /* 18 */,702 /* 19 */, + 683 /* 20 */,665 /* 21 */,647 /* 22 */,631 /* 23 */,615 /* 24 */, + 600 /* 25 */,586 /* 26 */,572 /* 27 */,558 /* 28 */,545 /* 29 */, + 533 /* 30 */,521 /* 31 */,509 /* 32 */,498 /* 33 */,487 /* 34 */, + 476 /* 35 */,466 /* 36 */,455 /* 37 */,446 /* 38 */,436 /* 39 */, + 427 /* 40 */,418 /* 41 */,409 /* 42 */,400 /* 43 */,391 /* 44 */, + 383 /* 45 */,375 /* 46 */,367 /* 47 */,359 /* 48 */,352 /* 49 */, + 344 /* 50 */,337 /* 51 */,330 /* 52 */,323 /* 53 */,316 /* 54 */, + 309 /* 55 */,302 /* 56 */,296 /* 57 */,289 /* 58 */,283 /* 59 */, + 277 /* 60 */,271 /* 61 */,265 /* 62 */,259 /* 63 */,253 /* 64 */, + 247 /* 65 */,242 /* 66 */,236 /* 67 */,231 /* 68 */,225 /* 69 */, + 220 /* 70 */,215 /* 71 */,210 /* 72 */,205 /* 73 */,199 /* 74 */, + 195 /* 75 */,190 /* 76 */,185 /* 77 */,180 /* 78 */,175 /* 79 */, + 171 /* 80 */,166 /* 81 */,162 /* 82 */,157 /* 83 */,153 /* 84 */, + 148 /* 85 */,144 /* 86 */,140 /* 87 */,135 /* 88 */,131 /* 89 */, + 127 /* 90 */,123 /* 91 */,119 /* 92 */,115 /* 93 */,111 /* 94 */, + 107 /* 95 */,103 /* 96 */,100 /* 97 */,96 /* 98 */,92 /* 99 */, + 88 /* 100 */,85 /* 101 */,81 /* 102 */,77 /* 103 */,74 /* 104 */, + 70 /* 105 */,67 /* 106 */,63 /* 107 */,60 /* 108 */,56 /* 109 */, + 53 /* 110 */,50 /* 111 */,46 /* 112 */,43 /* 113 */,40 /* 114 */, + 37 /* 115 */,33 /* 116 */,30 /* 117 */,27 /* 118 */,24 /* 119 */, + 21 /* 120 */,18 /* 121 */,15 /* 122 */,12 /* 123 */,9 /* 124 */, + 6 /* 125 */,3 /* 126 */,0 /* 127 */, +}; + +#else + +extern unsigned int snd_gf1_scale_table[SNDRV_GF1_SCALE_TABLE_SIZE]; +extern unsigned short snd_gf1_atten_table[SNDRV_GF1_ATTEN_TABLE_SIZE]; + +#endif diff --git a/sound/isa/gus/gus_timer.c b/sound/isa/gus/gus_timer.c new file mode 100644 index 00000000000..9876603ff6c --- /dev/null +++ b/sound/isa/gus/gus_timer.c @@ -0,0 +1,204 @@ +/* + * Routines for Gravis UltraSound soundcards - Timers + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * + * GUS have similar timers as AdLib (OPL2/OPL3 chips). + * + * + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +/* + * Timer 1 - 80us + */ + +static int snd_gf1_timer1_start(snd_timer_t * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + snd_gus_card_t *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + ticks = timer->sticks; + tmp = (gus->gf1.timer_enabled |= 4); + snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_1, 256 - ticks); /* timer 1 count */ + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 1 IRQ */ + snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +static int snd_gf1_timer1_stop(snd_timer_t * timer) +{ + unsigned long flags; + unsigned char tmp; + snd_gus_card_t *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + tmp = (gus->gf1.timer_enabled &= ~4); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +/* + * Timer 2 - 320us + */ + +static int snd_gf1_timer2_start(snd_timer_t * timer) +{ + unsigned long flags; + unsigned char tmp; + unsigned int ticks; + snd_gus_card_t *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + ticks = timer->sticks; + tmp = (gus->gf1.timer_enabled |= 8); + snd_gf1_write8(gus, SNDRV_GF1_GB_ADLIB_TIMER_2, 256 - ticks); /* timer 2 count */ + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* enable timer 2 IRQ */ + snd_gf1_adlib_write(gus, 0x04, tmp >> 2); /* timer 2 start */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +static int snd_gf1_timer2_stop(snd_timer_t * timer) +{ + unsigned long flags; + unsigned char tmp; + snd_gus_card_t *gus; + + gus = snd_timer_chip(timer); + spin_lock_irqsave(&gus->reg_lock, flags); + tmp = (gus->gf1.timer_enabled &= ~8); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, tmp); /* disable timer #1 */ + spin_unlock_irqrestore(&gus->reg_lock, flags); + return 0; +} + +/* + + */ + +static void snd_gf1_interrupt_timer1(snd_gus_card_t * gus) +{ + snd_timer_t *timer = gus->gf1.timer1; + + if (timer == NULL) + return; + snd_timer_interrupt(timer, timer->sticks); +} + +static void snd_gf1_interrupt_timer2(snd_gus_card_t * gus) +{ + snd_timer_t *timer = gus->gf1.timer2; + + if (timer == NULL) + return; + snd_timer_interrupt(timer, timer->sticks); +} + +/* + + */ + +static struct _snd_timer_hardware snd_gf1_timer1 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 80000, + .ticks = 256, + .start = snd_gf1_timer1_start, + .stop = snd_gf1_timer1_stop, +}; + +static struct _snd_timer_hardware snd_gf1_timer2 = +{ + .flags = SNDRV_TIMER_HW_STOP, + .resolution = 320000, + .ticks = 256, + .start = snd_gf1_timer2_start, + .stop = snd_gf1_timer2_stop, +}; + +static void snd_gf1_timer1_free(snd_timer_t *timer) +{ + snd_gus_card_t *gus = timer->private_data; + gus->gf1.timer1 = NULL; +} + +static void snd_gf1_timer2_free(snd_timer_t *timer) +{ + snd_gus_card_t *gus = timer->private_data; + gus->gf1.timer2 = NULL; +} + +void snd_gf1_timers_init(snd_gus_card_t * gus) +{ + snd_timer_t *timer; + snd_timer_id_t tid; + + if (gus->gf1.timer1 != NULL || gus->gf1.timer2 != NULL) + return; + + gus->gf1.interrupt_handler_timer1 = snd_gf1_interrupt_timer1; + gus->gf1.interrupt_handler_timer2 = snd_gf1_interrupt_timer2; + + tid.dev_class = SNDRV_TIMER_CLASS_CARD; + tid.dev_sclass = SNDRV_TIMER_SCLASS_NONE; + tid.card = gus->card->number; + tid.device = gus->timer_dev; + tid.subdevice = 0; + + if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) { + strcpy(timer->name, "GF1 timer #1"); + timer->private_data = gus; + timer->private_free = snd_gf1_timer1_free; + timer->hw = snd_gf1_timer1; + } + gus->gf1.timer1 = timer; + + tid.device++; + + if (snd_timer_new(gus->card, "GF1 timer", &tid, &timer) >= 0) { + strcpy(timer->name, "GF1 timer #2"); + timer->private_data = gus; + timer->private_free = snd_gf1_timer2_free; + timer->hw = snd_gf1_timer2; + } + gus->gf1.timer2 = timer; +} + +void snd_gf1_timers_done(snd_gus_card_t * gus) +{ + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_TIMER1 | SNDRV_GF1_HANDLER_TIMER2); + if (gus->gf1.timer1) { + snd_device_free(gus->card, gus->gf1.timer1); + gus->gf1.timer1 = NULL; + } + if (gus->gf1.timer2) { + snd_device_free(gus->card, gus->gf1.timer2); + gus->gf1.timer2 = NULL; + } +} diff --git a/sound/isa/gus/gus_uart.c b/sound/isa/gus/gus_uart.c new file mode 100644 index 00000000000..1bc2da8784e --- /dev/null +++ b/sound/isa/gus/gus_uart.c @@ -0,0 +1,257 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for the GF1 MIDI interface - like UART 6850 + * + * + * 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/interrupt.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/gus.h> + +static void snd_gf1_interrupt_midi_in(snd_gus_card_t * gus) +{ + int count; + unsigned char stat, data, byte; + unsigned long flags; + + count = 10; + while (count) { + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + stat = snd_gf1_uart_stat(gus); + if (!(stat & 0x01)) { /* data in Rx FIFO? */ + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + count--; + continue; + } + count = 100; /* arm counter to new value */ + data = snd_gf1_uart_get(gus); + if (!(gus->gf1.uart_cmd & 0x80)) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + if (stat & 0x10) { /* framing error */ + gus->gf1.uart_framing++; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + continue; + } + byte = snd_gf1_uart_get(gus); + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + snd_rawmidi_receive(gus->midi_substream_input, &byte, 1); + if (stat & 0x20) { + gus->gf1.uart_overrun++; + } + } +} + +static void snd_gf1_interrupt_midi_out(snd_gus_card_t * gus) +{ + char byte; + unsigned long flags; + + /* try unlock output */ + if (snd_gf1_uart_stat(gus) & 0x01) + snd_gf1_interrupt_midi_in(gus); + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (snd_gf1_uart_stat(gus) & 0x02) { /* Tx FIFO free? */ + if (snd_rawmidi_transmit(gus->midi_substream_output, &byte, 1) != 1) { /* no other bytes or error */ + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); /* disable Tx interrupt */ + } else { + snd_gf1_uart_put(gus, byte); + } + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static void snd_gf1_uart_reset(snd_gus_card_t * gus, int close) +{ + snd_gf1_uart_cmd(gus, 0x03); /* reset */ + if (!close && gus->uart_enable) { + udelay(160); + snd_gf1_uart_cmd(gus, 0x00); /* normal operations */ + } +} + +static int snd_gf1_uart_output_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_gus_card_t *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (!(gus->gf1.uart_cmd & 0x80)) { /* input active? */ + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_out = snd_gf1_interrupt_midi_out; + gus->midi_substream_output = substream; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +#if 0 + snd_printk("write init - cmd = 0x%x, stat = 0x%x\n", gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); +#endif + return 0; +} + +static int snd_gf1_uart_input_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_gus_card_t *gus; + int i; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) { + snd_gf1_uart_reset(gus, 0); + } + gus->gf1.interrupt_handler_midi_in = snd_gf1_interrupt_midi_in; + gus->midi_substream_input = substream; + if (gus->uart_enable) { + for (i = 0; i < 1000 && (snd_gf1_uart_stat(gus) & 0x01); i++) + snd_gf1_uart_get(gus); /* clean Rx */ + if (i >= 1000) + snd_printk("gus midi uart init read - cleanup error\n"); + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +#if 0 + snd_printk("read init - enable = %i, cmd = 0x%x, stat = 0x%x\n", gus->uart_enable, gus->gf1.uart_cmd, snd_gf1_uart_stat(gus)); + snd_printk("[0x%x] reg (ctrl/status) = 0x%x, reg (data) = 0x%x (page = 0x%x)\n", gus->gf1.port + 0x100, inb(gus->gf1.port + 0x100), inb(gus->gf1.port + 0x101), inb(gus->gf1.port + 0x102)); +#endif + return 0; +} + +static int snd_gf1_uart_output_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_gus_card_t *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_in != snd_gf1_interrupt_midi_in) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_OUT); + gus->midi_substream_output = NULL; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return 0; +} + +static int snd_gf1_uart_input_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_gus_card_t *gus; + + gus = substream->rmidi->private_data; + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.interrupt_handler_midi_out != snd_gf1_interrupt_midi_out) + snd_gf1_uart_reset(gus, 1); + snd_gf1_set_default_handlers(gus, SNDRV_GF1_HANDLER_MIDI_IN); + gus->midi_substream_input = NULL; + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return 0; +} + +static void snd_gf1_uart_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + snd_gus_card_t *gus; + unsigned long flags; + + gus = substream->rmidi->private_data; + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (up) { + if ((gus->gf1.uart_cmd & 0x80) == 0) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x80); /* enable Rx interrupts */ + } else { + if (gus->gf1.uart_cmd & 0x80) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x80); /* disable Rx interrupts */ + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static void snd_gf1_uart_output_trigger(snd_rawmidi_substream_t * substream, int up) +{ + unsigned long flags; + snd_gus_card_t *gus; + char byte; + int timeout; + + gus = substream->rmidi->private_data; + + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (up) { + if ((gus->gf1.uart_cmd & 0x20) == 0) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + /* wait for empty Rx - Tx is probably unlocked */ + timeout = 10000; + while (timeout-- > 0 && snd_gf1_uart_stat(gus) & 0x01); + /* Tx FIFO free? */ + spin_lock_irqsave(&gus->uart_cmd_lock, flags); + if (gus->gf1.uart_cmd & 0x20) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + if (snd_gf1_uart_stat(gus) & 0x02) { + if (snd_rawmidi_transmit(substream, &byte, 1) != 1) { + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); + return; + } + snd_gf1_uart_put(gus, byte); + } + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd | 0x20); /* enable Tx interrupt */ + } + } else { + if (gus->gf1.uart_cmd & 0x20) + snd_gf1_uart_cmd(gus, gus->gf1.uart_cmd & ~0x20); + } + spin_unlock_irqrestore(&gus->uart_cmd_lock, flags); +} + +static snd_rawmidi_ops_t snd_gf1_uart_output = +{ + .open = snd_gf1_uart_output_open, + .close = snd_gf1_uart_output_close, + .trigger = snd_gf1_uart_output_trigger, +}; + +static snd_rawmidi_ops_t snd_gf1_uart_input = +{ + .open = snd_gf1_uart_input_open, + .close = snd_gf1_uart_input_close, + .trigger = snd_gf1_uart_input_trigger, +}; + +int snd_gf1_rawmidi_new(snd_gus_card_t * gus, int device, snd_rawmidi_t ** rrawmidi) +{ + snd_rawmidi_t *rmidi; + int err; + + if (rrawmidi) + *rrawmidi = NULL; + if ((err = snd_rawmidi_new(gus->card, "GF1", device, 1, 1, &rmidi)) < 0) + return err; + strcpy(rmidi->name, gus->interwave ? "AMD InterWave" : "GF1"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_gf1_uart_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_gf1_uart_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT | SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = gus; + gus->midi_uart = rmidi; + if (rrawmidi) + *rrawmidi = rmidi; + return err; +} diff --git a/sound/isa/gus/gus_volume.c b/sound/isa/gus/gus_volume.c new file mode 100644 index 00000000000..b72bcfb2861 --- /dev/null +++ b/sound/isa/gus/gus_volume.c @@ -0,0 +1,210 @@ +/* + * 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/time.h> +#include <sound/core.h> +#include <sound/gus.h> +#define __GUS_TABLES_ALLOC__ +#include "gus_tables.h" + +EXPORT_SYMBOL(snd_gf1_atten_table); /* for snd-gus-synth module */ + +unsigned short snd_gf1_lvol_to_gvol_raw(unsigned int vol) +{ + unsigned short e, m, tmp; + + if (vol > 65535) + vol = 65535; + tmp = vol; + e = 7; + if (tmp < 128) { + while (e > 0 && tmp < (1 << e)) + e--; + } else { + while (tmp > 255) { + tmp >>= 1; + e++; + } + } + m = vol - (1 << e); + if (m > 0) { + if (e > 8) + m >>= e - 8; + else if (e < 8) + m <<= 8 - e; + m &= 255; + } + return (e << 8) | m; +} + +unsigned int snd_gf1_gvol_to_lvol_raw(unsigned short gf1_vol) +{ + unsigned int rvol; + unsigned short e, m; + + if (!gf1_vol) + return 0; + e = gf1_vol >> 8; + m = (unsigned char) gf1_vol; + rvol = 1 << e; + if (e > 8) + return rvol | (m << (e - 8)); + return rvol | (m >> (8 - e)); +} + +unsigned int snd_gf1_calc_ramp_rate(snd_gus_card_t * gus, + unsigned short start, + unsigned short end, + unsigned int us) +{ + static unsigned char vol_rates[19] = + { + 23, 24, 26, 28, 29, 31, 32, 34, + 36, 37, 39, 40, 42, 44, 45, 47, + 49, 50, 52 + }; + unsigned short range, increment, value, i; + + start >>= 4; + end >>= 4; + if (start < end) + us /= end - start; + else + us /= start - end; + range = 4; + value = gus->gf1.enh_mode ? + vol_rates[0] : + vol_rates[gus->gf1.active_voices - 14]; + for (i = 0; i < 3; i++) { + if (us < value) { + range = i; + break; + } else + value <<= 3; + } + if (range == 4) { + range = 3; + increment = 1; + } else + increment = (value + (value >> 1)) / us; + return (range << 6) | (increment & 0x3f); +} + +unsigned short snd_gf1_translate_freq(snd_gus_card_t * gus, unsigned int freq16) +{ + freq16 >>= 3; + if (freq16 < 50) + freq16 = 50; + if (freq16 & 0xf8000000) { + freq16 = ~0xf8000000; + snd_printk("snd_gf1_translate_freq: overflow - freq = 0x%x\n", freq16); + } + return ((freq16 << 9) + (gus->gf1.playback_freq >> 1)) / gus->gf1.playback_freq; +} + +short snd_gf1_compute_vibrato(short cents, unsigned short fc_register) +{ + static short vibrato_table[] = + { + 0, 0, 32, 592, 61, 1175, 93, 1808, + 124, 2433, 152, 3007, 182, 3632, 213, 4290, + 241, 4834, 255, 5200 + }; + + long depth; + short *vi1, *vi2, pcents, v1; + + pcents = cents < 0 ? -cents : cents; + for (vi1 = vibrato_table, vi2 = vi1 + 2; pcents > *vi2; vi1 = vi2, vi2 += 2); + v1 = *(vi1 + 1); + /* The FC table above is a list of pairs. The first number in the pair */ + /* is the cents index from 0-255 cents, and the second number in the */ + /* pair is the FC adjustment needed to change the pitch by the indexed */ + /* number of cents. The table was created for an FC of 32768. */ + /* The following expression does a linear interpolation against the */ + /* approximated log curve in the table above, and then scales the number */ + /* by the FC before the LFO. This calculation also adjusts the output */ + /* value to produce the appropriate depth for the hardware. The depth */ + /* is 2 * desired FC + 1. */ + depth = (((int) (*(vi2 + 1) - *vi1) * (pcents - *vi1) / (*vi2 - *vi1)) + v1) * fc_register >> 14; + if (depth) + depth++; + if (depth > 255) + depth = 255; + return cents < 0 ? -(short) depth : (short) depth; +} + +unsigned short snd_gf1_compute_pitchbend(unsigned short pitchbend, unsigned short sens) +{ + static long log_table[] = {1024, 1085, 1149, 1218, 1290, 1367, 1448, 1534, 1625, 1722, 1825, 1933}; + int wheel, sensitivity; + unsigned int mantissa, f1, f2; + unsigned short semitones, f1_index, f2_index, f1_power, f2_power; + char bend_down = 0; + int bend; + + if (!sens) + return 1024; + wheel = (int) pitchbend - 8192; + sensitivity = ((int) sens * wheel) / 128; + if (sensitivity < 0) { + bend_down = 1; + sensitivity = -sensitivity; + } + semitones = (unsigned int) (sensitivity >> 13); + mantissa = sensitivity % 8192; + f1_index = semitones % 12; + f2_index = (semitones + 1) % 12; + f1_power = semitones / 12; + f2_power = (semitones + 1) / 12; + f1 = log_table[f1_index] << f1_power; + f2 = log_table[f2_index] << f2_power; + bend = (int) ((((f2 - f1) * mantissa) >> 13) + f1); + if (bend_down) + bend = 1048576L / bend; + return bend; +} + +unsigned short snd_gf1_compute_freq(unsigned int freq, + unsigned int rate, + unsigned short mix_rate) +{ + unsigned int fc; + int scale = 0; + + while (freq >= 4194304L) { + scale++; + freq >>= 1; + } + fc = (freq << 10) / rate; + if (fc > 97391L) { + fc = 97391; + snd_printk("patch: (1) fc frequency overflow - %u\n", fc); + } + fc = (fc * 44100UL) / mix_rate; + while (scale--) + fc <<= 1; + if (fc > 65535L) { + fc = 65535; + snd_printk("patch: (2) fc frequency overflow - %u\n", fc); + } + return (unsigned short) fc; +} diff --git a/sound/isa/gus/gusclassic.c b/sound/isa/gus/gusclassic.c new file mode 100644 index 00000000000..a99fa5040b4 --- /dev/null +++ b/sound/isa/gus/gusclassic.c @@ -0,0 +1,260 @@ +/* + * Driver for Gravis UltraSound Classic soundcard + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/gus.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Gravis UltraSound Classic"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Classic}}"); + +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; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for GUS Classic soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for GUS Classic soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable GUS Classic soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for GUS Classic driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for GUS Classic driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for GUS Classic driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for GUS Classic driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS Classic driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for GUS Classic driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS Classic driver."); + +static snd_card_t *snd_gusclassic_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_gusclassic_detect(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0) + return -ENODEV; +#endif + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1) + return -ENODEV; +#endif + + return 0; +} + +static void __init snd_gusclassic_init(int dev, snd_gus_card_t * gus) +{ + gus->equal_irq = 0; + gus->codec_flag = 0; + gus->max_flag = 0; + gus->joystick_dac = joystick_dac[dev]; +} + +static int __init snd_gusclassic_probe(int dev) +{ + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, 4, -1}; + static int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + int xirq, xdma1, xdma2; + snd_card_t *card; + struct snd_gusclassic *guscard; + snd_gus_card_t *gus = NULL; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + guscard = (struct snd_gusclassic *)card->private_data; + if (pcm_channels[dev] < 2) + pcm_channels[dev] = 2; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA1\n"); + return -EBUSY; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } + + + if ((err = snd_gus_create(card, + port[dev], + xirq, xdma1, xdma2, + 0, channels[dev], pcm_channels[dev], + 0, &gus)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_gusclassic_detect(gus)) < 0) { + snd_card_free(card); + return err; + } + snd_gusclassic_init(dev, gus); + if ((err = snd_gus_initialize(gus)) < 0) { + snd_card_free(card); + return err; + } + if (gus->max_flag || gus->ess_flag) { + snd_printdd("GUS Classic or ACE soundcard was not detected at 0x%lx\n", gus->gf1.port); + snd_card_free(card); + return -ENODEV; + } + if ((err = snd_gf1_new_mixer(gus)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_gf1_pcm_new(gus, 0, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if (!gus->ace_flag) { + if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %d, dma %d", gus->gf1.port, xirq, xdma1); + if (dma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_gusclassic_cards[dev] = card; + return 0; +} + +static int __init snd_gusclassic_legacy_auto_probe(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; + port[dev] = xport; + res = snd_gusclassic_probe(dev); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +static int __init alsa_card_gusclassic_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260, -1}; + int dev, cards, i; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) { + if (port[dev] == SNDRV_AUTO_PORT) + continue; + if (snd_gusclassic_probe(dev) >= 0) + cards++; + } + i = snd_legacy_auto_probe(possible_ports, snd_gusclassic_legacy_auto_probe); + if (i > 0) + cards += i; + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "GUS Classic soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_gusclassic_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_gusclassic_cards[idx]); +} + +module_init(alsa_card_gusclassic_init) +module_exit(alsa_card_gusclassic_exit) diff --git a/sound/isa/gus/gusextreme.c b/sound/isa/gus/gusextreme.c new file mode 100644 index 00000000000..bc6fecb18dc --- /dev/null +++ b/sound/isa/gus/gusextreme.c @@ -0,0 +1,374 @@ +/* + * Driver for Gravis UltraSound Extreme soundcards + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/es1688.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Gravis UltraSound Extreme"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Extreme}}"); + +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; /* 0x220,0x240,0x260 */ +static long gf1_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x210,0x220,0x230,0x240,0x250,0x260,0x270 */ +static long mpu_port[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS) - 1] = -1}; /* 0x300,0x310,0x320 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int gf1_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for GUS Extreme soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for GUS Extreme soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable GUS Extreme soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for GUS Extreme driver."); +module_param_array(gf1_port, long, NULL, 0444); +MODULE_PARM_DESC(gf1_port, "GF1 port # for GUS Extreme driver (optional)."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for GUS Extreme driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for GUS Extreme driver."); +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 IRQ # for GUS Extreme driver."); +module_param_array(gf1_irq, int, NULL, 0444); +MODULE_PARM_DESC(gf1_irq, "GF1 IRQ # for GUS Extreme driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for GUS Extreme driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "GF1 DMA # for GUS Extreme driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS Extreme driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "GF1 channels for GUS Extreme driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS Extreme driver."); + +static snd_card_t *snd_gusextreme_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_gusextreme_detect(int dev, + snd_card_t * card, + snd_gus_card_t * gus, + es1688_t *es1688) +{ + unsigned long flags; + + /* + * This is main stuff - enable access to GF1 chip... + * I'm not sure, if this will work for card which have + * ES1688 chip in another place than 0x220. + * + * I used reverse-engineering in DOSEMU. [--jk] + * + * ULTRINIT.EXE: + * 0x230 = 0,2,3 + * 0x240 = 2,0,1 + * 0x250 = 2,0,3 + * 0x260 = 2,2,1 + */ + + spin_lock_irqsave(&es1688->mixer_lock, flags); + snd_es1688_mixer_write(es1688, 0x40, 0x0b); /* don't change!!! */ + spin_unlock_irqrestore(&es1688->mixer_lock, flags); + spin_lock_irqsave(&es1688->reg_lock, flags); + outb(gf1_port[dev] & 0x040 ? 2 : 0, ES1688P(es1688, INIT1)); + outb(0, 0x201); + outb(gf1_port[dev] & 0x020 ? 2 : 0, ES1688P(es1688, INIT1)); + outb(0, 0x201); + outb(gf1_port[dev] & 0x010 ? 3 : 1, ES1688P(es1688, INIT1)); + spin_unlock_irqrestore(&es1688->reg_lock, flags); + + udelay(100); + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0) + return -EIO; +#endif + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -EIO; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1) + return -EIO; +#endif + + return 0; +} + +static void __init snd_gusextreme_init(int dev, snd_gus_card_t * gus) +{ + gus->joystick_dac = joystick_dac[dev]; +} + +static int __init snd_gusextreme_mixer(es1688_t *chip) +{ + snd_card_t *card = chip->card; + snd_ctl_elem_id_t id1, id2; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign Master Playback Switch to Synth Playback Switch */ + strcpy(id1.name, "Master Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + return 0; +} + +static int __init snd_gusextreme_probe(int dev) +{ + static int possible_ess_irqs[] = {5, 9, 10, 7, -1}; + static int possible_ess_dmas[] = {1, 3, 0, -1}; + static int possible_gf1_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static int possible_gf1_dmas[] = {5, 6, 7, 1, 3, -1}; + int xgf1_irq, xgf1_dma, xess_irq, xmpu_irq, xess_dma; + snd_card_t *card; + struct snd_gusextreme *acard; + snd_gus_card_t *gus; + es1688_t *es1688; + opl3_t *opl3; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_gusextreme *)card->private_data; + + xgf1_irq = gf1_irq[dev]; + if (xgf1_irq == SNDRV_AUTO_IRQ) { + if ((xgf1_irq = snd_legacy_find_free_irq(possible_gf1_irqs)) < 0) { + snd_printk("unable to find a free IRQ for GF1\n"); + err = -EBUSY; + goto out; + } + } + xess_irq = irq[dev]; + if (xess_irq == SNDRV_AUTO_IRQ) { + if ((xess_irq = snd_legacy_find_free_irq(possible_ess_irqs)) < 0) { + snd_printk("unable to find a free IRQ for ES1688\n"); + err = -EBUSY; + goto out; + } + } + if (mpu_port[dev] == SNDRV_AUTO_PORT) + mpu_port[dev] = 0; + xmpu_irq = mpu_irq[dev]; + if (xmpu_irq > 15) + xmpu_irq = -1; + xgf1_dma = dma1[dev]; + if (xgf1_dma == SNDRV_AUTO_DMA) { + if ((xgf1_dma = snd_legacy_find_free_dma(possible_gf1_dmas)) < 0) { + snd_printk("unable to find a free DMA for GF1\n"); + err = -EBUSY; + goto out; + } + } + xess_dma = dma8[dev]; + if (xess_dma == SNDRV_AUTO_DMA) { + if ((xess_dma = snd_legacy_find_free_dma(possible_ess_dmas)) < 0) { + snd_printk("unable to find a free DMA for ES1688\n"); + err = -EBUSY; + goto out; + } + } + + if ((err = snd_es1688_create(card, port[dev], mpu_port[dev], + xess_irq, xmpu_irq, xess_dma, + ES1688_HW_1688, &es1688)) < 0) + goto out; + if (gf1_port[dev] < 0) + gf1_port[dev] = port[dev] + 0x20; + if ((err = snd_gus_create(card, + gf1_port[dev], + xgf1_irq, + xgf1_dma, + -1, + 0, channels[dev], + pcm_channels[dev], 0, + &gus)) < 0) + goto out; + + if ((err = snd_gusextreme_detect(dev, card, gus, es1688)) < 0) + goto out; + + snd_gusextreme_init(dev, gus); + if ((err = snd_gus_initialize(gus)) < 0) + goto out; + + if (!gus->ess_flag) { + snd_printdd("GUS Extreme soundcard was not detected at 0x%lx\n", gus->gf1.port); + err = -ENODEV; + goto out; + } + if ((err = snd_es1688_pcm(es1688, 0, NULL)) < 0) + goto out; + + if ((err = snd_es1688_mixer(es1688)) < 0) + goto out; + + snd_component_add(card, "ES1688"); + if (pcm_channels[dev] > 0) { + if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0) + goto out; + } + if ((err = snd_gf1_new_mixer(gus)) < 0) + goto out; + + if ((err = snd_gusextreme_mixer(es1688)) < 0) + goto out; + + if (snd_opl3_create(card, es1688->port, es1688->port + 2, + OPL3_HW_OPL3, 0, &opl3) < 0) { + printk(KERN_ERR "gusextreme: opl3 not detected at 0x%lx\n", es1688->port); + } else { + if ((err = snd_opl3_hwdep_new(opl3, 0, 2, NULL)) < 0) + goto out; + } + + if (es1688->mpu_port >= 0x300 && + (err = snd_mpu401_uart_new(card, 0, MPU401_HW_ES1688, + es1688->mpu_port, 0, + xmpu_irq, + SA_INTERRUPT, + NULL)) < 0) + goto out; + + sprintf(card->longname, "Gravis UltraSound Extreme at 0x%lx, irq %i&%i, dma %i&%i", + es1688->port, xgf1_irq, xess_irq, xgf1_dma, xess_dma); + if ((err = snd_card_register(card)) < 0) + goto out; + + snd_gusextreme_cards[dev] = card; + return 0; + + out: + snd_card_free(card); + return err; +} + +static int __init snd_gusextreme_legacy_auto_probe(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; + port[dev] = xport; + res = snd_gusextreme_probe(dev); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +static int __init alsa_card_gusextreme_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1}; + int dev, cards, i; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev] > 0; dev++) { + if (port[dev] == SNDRV_AUTO_PORT) + continue; + if (snd_gusextreme_probe(dev) >= 0) + cards++; + } + i = snd_legacy_auto_probe(possible_ports, snd_gusextreme_legacy_auto_probe); + if (i > 0) + cards += i; + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "GUS Extreme soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_gusextreme_exit(void) +{ + int idx; + snd_card_t *card; + struct snd_gusextreme *acard; + + for (idx = 0; idx < SNDRV_CARDS; idx++) { + card = snd_gusextreme_cards[idx]; + if (card == NULL) + continue; + acard = (struct snd_gusextreme *)card->private_data; + snd_card_free(snd_gusextreme_cards[idx]); + } +} + +module_init(alsa_card_gusextreme_init) +module_exit(alsa_card_gusextreme_exit) diff --git a/sound/isa/gus/gusmax.c b/sound/isa/gus/gusmax.c new file mode 100644 index 00000000000..400ff34710f --- /dev/null +++ b/sound/isa/gus/gusmax.c @@ -0,0 +1,400 @@ +/* + * Driver for Gravis UltraSound MAX soundcard + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/cs4231.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Gravis UltraSound MAX"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound MAX}}"); + +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; /* 0x220,0x230,0x240,0x250,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 24}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for GUS MAX soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for GUS MAX soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable GUS MAX soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for GUS MAX driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for GUS MAX driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for GUS MAX driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for GUS MAX driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for GUS MAX driver."); +module_param_array(channels, int, NULL, 0444); +MODULE_PARM_DESC(channels, "Used GF1 channels for GUS MAX driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for GUS MAX driver."); + +struct snd_gusmax { + int irq; + snd_card_t *card; + snd_gus_card_t *gus; + cs4231_t *cs4231; + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +}; + +static snd_card_t *snd_gusmax_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + + +static int __init snd_gusmax_detect(snd_gus_card_t * gus) +{ + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0) + return -ENODEV; +#endif + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); +#ifdef CONFIG_SND_DEBUG_DETECT + { + unsigned char d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1) + return -ENODEV; +#endif + return 0; +} + +static irqreturn_t snd_gusmax_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct snd_gusmax *maxcard = (struct snd_gusmax *) dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(maxcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, maxcard->gus, regs); + loop++; + } + if (inb(maxcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_cs4231_interrupt(irq, maxcard->cs4231, regs); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void __init snd_gusmax_init(int dev, snd_card_t * card, snd_gus_card_t * gus) +{ + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->joystick_dac = joystick_dac[dev]; + /* init control register */ + gus->max_cntrl_val = (gus->gf1.port >> 4) & 0x0f; + if (gus->gf1.dma1 > 3) + gus->max_cntrl_val |= 0x10; + if (gus->gf1.dma2 > 3) + gus->max_cntrl_val |= 0x20; + gus->max_cntrl_val |= 0x40; + outb(gus->max_cntrl_val, GUSP(gus, MAXCNTRLPORT)); +} + +#define CS4231_PRIVATE( left, right, shift, mute ) \ + ((left << 24)|(right << 16)|(shift<<8)|mute) + +static int __init snd_gusmax_mixer(cs4231_t *chip) +{ + snd_card_t *card = chip->card; + snd_ctl_elem_id_t id1, id2; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUXA to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUXB to CD */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; +#if 0 + /* reassign Mono Input to MIC */ + if (snd_mixer_group_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + SNDRV_MIXER_IN_MONO, 0, SNDRV_MIXER_ETYPE_INPUT, + SNDRV_MIXER_IN_MIC, 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Volume", 0, SNDRV_MIXER_ETYPE_VOLUME1, + "Mic Capture Volume", 0) < 0) + goto __error; + if (snd_mixer_elem_rename(mixer, + "Mono Capture Switch", 0, SNDRV_MIXER_ETYPE_SWITCH1, + "Mic Capture Switch", 0) < 0) + goto __error; +#endif + return 0; +} + +static void snd_gusmax_free(snd_card_t *card) +{ + struct snd_gusmax *maxcard = (struct snd_gusmax *)card->private_data; + + if (maxcard == NULL) + return; + if (maxcard->irq >= 0) + free_irq(maxcard->irq, (void *)maxcard); +} + +static int __init snd_gusmax_probe(int dev) +{ + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static int possible_dmas[] = {5, 6, 7, 1, 3, -1}; + int xirq, xdma1, xdma2, err; + snd_card_t *card; + snd_gus_card_t *gus = NULL; + cs4231_t *cs4231; + struct snd_gusmax *maxcard; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_gusmax)); + if (card == NULL) + return -ENOMEM; + card->private_free = snd_gusmax_free; + maxcard = (struct snd_gusmax *)card->private_data; + maxcard->card = card; + maxcard->irq = -1; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA1\n"); + return -EBUSY; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if ((err = snd_gus_create(card, + port[dev], + -xirq, xdma1, xdma2, + 0, channels[dev], + pcm_channels[dev], + 0, &gus)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_gusmax_detect(gus)) < 0) { + snd_card_free(card); + return err; + } + maxcard->gus_status_reg = gus->gf1.reg_irqstat; + maxcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + snd_gusmax_init(dev, card, gus); + if ((err = snd_gus_initialize(gus)) < 0) { + snd_card_free(card); + return err; + } + if (!gus->max_flag) { + printk(KERN_ERR "GUS MAX soundcard was not detected at 0x%lx\n", gus->gf1.port); + snd_card_free(card); + return -ENODEV; + } + + if (request_irq(xirq, snd_gusmax_interrupt, SA_INTERRUPT, "GUS MAX", (void *)maxcard)) { + snd_card_free(card); + printk(KERN_ERR "gusmax: unable to grab IRQ %d\n", xirq); + return -EBUSY; + } + maxcard->irq = xirq; + + if ((err = snd_cs4231_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + CS4231_HW_DETECT, + CS4231_HWSHARE_IRQ | + CS4231_HWSHARE_DMA1 | + CS4231_HWSHARE_DMA2, + &cs4231)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_pcm(cs4231, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_mixer(cs4231)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_timer(cs4231, 2, NULL)) < 0) { + snd_card_free(card); + return err; + } + if (pcm_channels[dev] > 0) { + if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + if ((err = snd_gusmax_mixer(cs4231)) < 0) { + snd_card_free(card); + return err; + } + + if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + + sprintf(card->longname + strlen(card->longname), " at 0x%lx, irq %i, dma %i", gus->gf1.port, xirq, xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%i", xdma2); + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + + maxcard->gus = gus; + maxcard->cs4231 = cs4231; + snd_gusmax_cards[dev] = card; + return 0; +} + +static int __init snd_gusmax_legacy_auto_probe(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; + port[dev] = xport; + res = snd_gusmax_probe(dev); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +static int __init alsa_card_gusmax_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x230, 0x240, 0x250, 0x260, -1}; + int dev, cards, i; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev] > 0; dev++) { + if (port[dev] == SNDRV_AUTO_PORT) + continue; + if (snd_gusmax_probe(dev) >= 0) + cards++; + } + i = snd_legacy_auto_probe(possible_ports, snd_gusmax_legacy_auto_probe); + if (i > 0) + cards += i; + + if (!cards) { +#ifdef MODULE + printk(KERN_ERR "GUS MAX soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_gusmax_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_gusmax_cards[idx]); +} + +module_init(alsa_card_gusmax_init) +module_exit(alsa_card_gusmax_exit) diff --git a/sound/isa/gus/interwave-stb.c b/sound/isa/gus/interwave-stb.c new file mode 100644 index 00000000000..dbe4f48a984 --- /dev/null +++ b/sound/isa/gus/interwave-stb.c @@ -0,0 +1,2 @@ +#define SNDRV_STB +#include "interwave.c" diff --git a/sound/isa/gus/interwave.c b/sound/isa/gus/interwave.c new file mode 100644 index 00000000000..46e867daba6 --- /dev/null +++ b/sound/isa/gus/interwave.c @@ -0,0 +1,969 @@ +/* + * Driver for AMD InterWave soundcard + * 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 + * + * 1999/07/22 Erik Inge Bolso <knan@mo.himolde.no> + * * mixer group handlers + * + */ + +#include <sound/driver.h> +#include <asm/dma.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/gus.h> +#include <sound/cs4231.h> +#ifdef SNDRV_STB +#include <sound/tea6330t.h> +#endif +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_LICENSE("GPL"); +#ifndef SNDRV_STB +MODULE_DESCRIPTION("AMD InterWave"); +MODULE_SUPPORTED_DEVICE("{{Gravis,UltraSound Plug & Play}," + "{STB,SoundRage32}," + "{MED,MED3210}," + "{Dynasonix,Dynasonix Pro}," + "{Panasonic,PCA761AW}}"); +#else +MODULE_DESCRIPTION("AMD InterWave STB with TEA6330T"); +MODULE_SUPPORTED_DEVICE("{{AMD,InterWave STB with TEA6330T}}"); +#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; /* 0x210,0x220,0x230,0x240,0x250,0x260 */ +#ifdef SNDRV_STB +static long port_tc[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x350,0x360,0x370,0x380 */ +#endif +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,3,5,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 */ +static int joystick_dac[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 29}; + /* 0 to 31, (0.59V-4.52V or 0.389V-2.98V) */ +static int midi[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0}; +static int pcm_channels[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2}; +static int effect[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for InterWave soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for InterWave soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable InterWave 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 InterWave driver."); +#ifdef SNDRV_STB +module_param_array(port_tc, long, NULL, 0444); +MODULE_PARM_DESC(port_tc, "Tone control (TEA6330T - i2c bus) port # for InterWave driver."); +#endif +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for InterWave driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for InterWave driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for InterWave driver."); +module_param_array(joystick_dac, int, NULL, 0444); +MODULE_PARM_DESC(joystick_dac, "Joystick DAC level 0.59V-4.52V or 0.389V-2.98V for InterWave driver."); +module_param_array(midi, int, NULL, 0444); +MODULE_PARM_DESC(midi, "MIDI UART enable for InterWave driver."); +module_param_array(pcm_channels, int, NULL, 0444); +MODULE_PARM_DESC(pcm_channels, "Reserved PCM channels for InterWave driver."); +module_param_array(effect, int, NULL, 0444); +MODULE_PARM_DESC(effect, "Effects enable for InterWave driver."); + +struct snd_interwave { + int irq; + snd_card_t *card; + snd_gus_card_t *gus; + cs4231_t *cs4231; +#ifdef SNDRV_STB + struct resource *i2c_res; +#endif + unsigned short gus_status_reg; + unsigned short pcm_status_reg; +#ifdef CONFIG_PNP + struct pnp_dev *dev; +#ifdef SNDRV_STB + struct pnp_dev *devtc; +#endif +#endif +}; + +static snd_card_t *snd_interwave_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_interwave_pnpids[] = { +#ifndef SNDRV_STB + /* Gravis UltraSound Plug & Play */ + { .id = "GRV0001", .devs = { { .id = "GRV0000" } } }, + /* STB SoundRage32 */ + { .id = "STB011a", .devs = { { .id = "STB0010" } } }, + /* MED3210 */ + { .id = "DXP3201", .devs = { { .id = "DXP0010" } } }, + /* Dynasonic Pro */ + /* This device also have CDC1117:DynaSonix Pro Audio Effects Processor */ + { .id = "CDC1111", .devs = { { .id = "CDC1112" } } }, + /* Panasonic PCA761AW Audio Card */ + { .id = "ADV55ff", .devs = { { .id = "ADV0010" } } }, + /* InterWave STB without TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" } } }, +#else + /* InterWave STB with TEA6330T */ + { .id = "ADV550a", .devs = { { .id = "ADV0010" }, { .id = "ADV0015" } } }, +#endif + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_interwave_pnpids); + +#endif /* CONFIG_PNP */ + + +#ifdef SNDRV_STB +static void snd_interwave_i2c_setlines(snd_i2c_bus_t *bus, int ctrl, int data) +{ + unsigned long port = bus->private_value; + +#if 0 + printk("i2c_setlines - 0x%lx <- %i,%i\n", port, ctrl, data); +#endif + outb((data << 1) | ctrl, port); + udelay(10); +} + +static int snd_interwave_i2c_getclockline(snd_i2c_bus_t *bus) +{ + unsigned long port = bus->private_value; + unsigned char res; + + res = inb(port) & 1; +#if 0 + printk("i2c_getclockline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static int snd_interwave_i2c_getdataline(snd_i2c_bus_t *bus, int ack) +{ + unsigned long port = bus->private_value; + unsigned char res; + + if (ack) + udelay(10); + res = (inb(port) & 2) >> 1; +#if 0 + printk("i2c_getdataline - 0x%lx -> %i\n", port, res); +#endif + return res; +} + +static snd_i2c_bit_ops_t snd_interwave_i2c_bit_ops = { + .setlines = snd_interwave_i2c_setlines, + .getclock = snd_interwave_i2c_getclockline, + .getdata = snd_interwave_i2c_getdataline, +}; + +static int __devinit snd_interwave_detect_stb(struct snd_interwave *iwcard, + snd_gus_card_t * gus, int dev, + snd_i2c_bus_t **rbus) +{ + unsigned long port; + snd_i2c_bus_t *bus; + snd_card_t *card = iwcard->card; + char name[32]; + int err; + + *rbus = NULL; + port = port_tc[dev]; + if (port == SNDRV_AUTO_PORT) { + port = 0x350; + if (gus->gf1.port == 0x250) { + port = 0x360; + } + while (port <= 0x380) { + if ((iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)")) != NULL) + break; + port += 0x10; + } + } else { + iwcard->i2c_res = request_region(port, 1, "InterWave (I2C bus)"); + } + if (iwcard->i2c_res == NULL) { + snd_printk(KERN_ERR "interwave: can't grab i2c bus port\n"); + return -ENODEV; + } + + sprintf(name, "InterWave-%i", card->number); + if ((err = snd_i2c_bus_create(card, name, NULL, &bus)) < 0) + return err; + bus->private_value = port; + bus->hw_ops.bit = &snd_interwave_i2c_bit_ops; + if ((err = snd_tea6330t_detect(bus, 0)) < 0) + return err; + *rbus = bus; + return 0; +} +#endif + +static int __devinit snd_interwave_detect(struct snd_interwave *iwcard, + snd_gus_card_t * gus, + int dev +#ifdef SNDRV_STB + , snd_i2c_bus_t **rbus +#endif + ) +{ + unsigned long flags; + unsigned char rev1, rev2; + + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 0); /* reset GF1 */ +#ifdef CONFIG_SND_DEBUG_DETECT + { + int d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 0) { + snd_printk("[0x%lx] check 1 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 0) + return -ENODEV; +#endif + udelay(160); + snd_gf1_i_write8(gus, SNDRV_GF1_GB_RESET, 1); /* release reset */ + udelay(160); +#ifdef CONFIG_SND_DEBUG_DETECT + { + int d; + + if (((d = snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET)) & 0x07) != 1) { + snd_printk("[0x%lx] check 2 failed - 0x%x\n", gus->gf1.port, d); + return -ENODEV; + } + } +#else + if ((snd_gf1_i_look8(gus, SNDRV_GF1_GB_RESET) & 0x07) != 1) + return -ENODEV; +#endif + + spin_lock_irqsave(&gus->reg_lock, flags); + rev1 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, ~rev1); + rev2 = snd_gf1_look8(gus, SNDRV_GF1_GB_VERSION_NUMBER); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, rev1); + spin_unlock_irqrestore(&gus->reg_lock, flags); + snd_printdd("[0x%lx] InterWave check - rev1=0x%x, rev2=0x%x\n", gus->gf1.port, rev1, rev2); + if ((rev1 & 0xf0) == (rev2 & 0xf0) && + (rev1 & 0x0f) != (rev2 & 0x0f)) { + snd_printdd("[0x%lx] InterWave check - passed\n", gus->gf1.port); + gus->interwave = 1; + strcpy(gus->card->shortname, "AMD InterWave"); + gus->revision = rev1 >> 4; +#ifndef SNDRV_STB + return 0; /* ok.. We have an InterWave board */ +#else + return snd_interwave_detect_stb(iwcard, gus, dev, rbus); +#endif + } + snd_printdd("[0x%lx] InterWave check - failed\n", gus->gf1.port); + return -ENODEV; +} + +static irqreturn_t snd_interwave_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct snd_interwave *iwcard = (struct snd_interwave *) dev_id; + int loop, max = 5; + int handled = 0; + + do { + loop = 0; + if (inb(iwcard->gus_status_reg)) { + handled = 1; + snd_gus_interrupt(irq, iwcard->gus, regs); + loop++; + } + if (inb(iwcard->pcm_status_reg) & 0x01) { /* IRQ bit is set? */ + handled = 1; + snd_cs4231_interrupt(irq, iwcard->cs4231, regs); + loop++; + } + } while (loop && --max > 0); + return IRQ_RETVAL(handled); +} + +static void __devinit snd_interwave_reset(snd_gus_card_t * gus) +{ + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x00); + udelay(160); + snd_gf1_write8(gus, SNDRV_GF1_GB_RESET, 0x01); + udelay(160); +} + +static void __devinit snd_interwave_bank_sizes(snd_gus_card_t * gus, int *sizes) +{ + unsigned int idx; + unsigned int local; + unsigned char d; + + for (idx = 0; idx < 4; idx++) { + sizes[idx] = 0; + d = 0x55; + for (local = idx << 22; + local < (idx << 22) + 0x400000; + local += 0x40000, d++) { + snd_gf1_poke(gus, local, d); + snd_gf1_poke(gus, local + 1, d + 1); +#if 0 + printk("d = 0x%x, local = 0x%x, local + 1 = 0x%x, idx << 22 = 0x%x\n", + d, + snd_gf1_peek(gus, local), + snd_gf1_peek(gus, local + 1), + snd_gf1_peek(gus, idx << 22)); +#endif + if (snd_gf1_peek(gus, local) != d || + snd_gf1_peek(gus, local + 1) != d + 1 || + snd_gf1_peek(gus, idx << 22) != 0x55) + break; + sizes[idx]++; + } + } +#if 0 + printk("sizes: %i %i %i %i\n", sizes[0], sizes[1], sizes[2], sizes[3]); +#endif +} + +struct rom_hdr { + /* 000 */ unsigned char iwave[8]; + /* 008 */ unsigned char rom_hdr_revision; + /* 009 */ unsigned char series_number; + /* 010 */ unsigned char series_name[16]; + /* 026 */ unsigned char date[10]; + /* 036 */ unsigned short vendor_revision_major; + /* 038 */ unsigned short vendor_revision_minor; + /* 040 */ unsigned int rom_size; + /* 044 */ unsigned char copyright[128]; + /* 172 */ unsigned char vendor_name[64]; + /* 236 */ unsigned char rom_description[128]; + /* 364 */ unsigned char pad[147]; + /* 511 */ unsigned char csum; +}; + +static void __devinit snd_interwave_detect_memory(snd_gus_card_t * gus) +{ + static unsigned int lmc[13] = + { + 0x00000001, 0x00000101, 0x01010101, 0x00000401, + 0x04040401, 0x00040101, 0x04040101, 0x00000004, + 0x00000404, 0x04040404, 0x00000010, 0x00001010, + 0x10101010 + }; + + int bank_pos, pages; + unsigned int i, lmct; + int psizes[4]; + unsigned char iwave[8]; + unsigned char csum; + + snd_interwave_reset(gus); + snd_gf1_write8(gus, SNDRV_GF1_GB_GLOBAL_MODE, snd_gf1_read8(gus, SNDRV_GF1_GB_GLOBAL_MODE) | 0x01); /* enhanced mode */ + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x01); /* DRAM I/O cycles selected */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff10) | 0x004c); + /* ok.. simple test of memory size */ + pages = 0; + snd_gf1_poke(gus, 0, 0x55); + snd_gf1_poke(gus, 1, 0xaa); +#if 1 + if (snd_gf1_peek(gus, 0) == 0x55 && snd_gf1_peek(gus, 1) == 0xaa) +#else + if (0) /* ok.. for testing of 0k RAM */ +#endif + { + snd_interwave_bank_sizes(gus, psizes); + lmct = (psizes[3] << 24) | (psizes[2] << 16) | + (psizes[1] << 8) | psizes[0]; +#if 0 + printk("lmct = 0x%08x\n", lmct); +#endif + for (i = 0; i < ARRAY_SIZE(lmc); i++) + if (lmct == lmc[i]) { +#if 0 + printk("found !!! %i\n", i); +#endif + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | i); + snd_interwave_bank_sizes(gus, psizes); + break; + } + if (i >= ARRAY_SIZE(lmc) && !gus->gf1.enh_mode) + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xfff0) | 2); + for (i = 0; i < 4; i++) { + gus->gf1.mem_alloc.banks_8[i].address = + gus->gf1.mem_alloc.banks_16[i].address = i << 22; + gus->gf1.mem_alloc.banks_8[i].size = + gus->gf1.mem_alloc.banks_16[i].size = psizes[i] << 18; + pages += psizes[i]; + } + } + pages <<= 18; + gus->gf1.memory = pages; + + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x03); /* select ROM */ + snd_gf1_write16(gus, SNDRV_GF1_GW_MEMORY_CONFIG, (snd_gf1_look16(gus, SNDRV_GF1_GW_MEMORY_CONFIG) & 0xff1f) | (4 << 5)); + gus->gf1.rom_banks = 0; + gus->gf1.rom_memory = 0; + for (bank_pos = 0; bank_pos < 16L * 1024L * 1024L; bank_pos += 4L * 1024L * 1024L) { + for (i = 0; i < 8; ++i) + iwave[i] = snd_gf1_peek(gus, bank_pos + i); +#ifdef CONFIG_SND_DEBUG_ROM + printk("ROM at 0x%06x = %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", bank_pos, + iwave[0], iwave[1], iwave[2], iwave[3], + iwave[4], iwave[5], iwave[6], iwave[7]); +#endif + if (strncmp(iwave, "INTRWAVE", 8)) + continue; /* first check */ + csum = 0; + for (i = 0; i < sizeof(struct rom_hdr); i++) + csum += snd_gf1_peek(gus, bank_pos + i); +#ifdef CONFIG_SND_DEBUG_ROM + printk("ROM checksum = 0x%x (computed)\n", csum); +#endif + if (csum != 0) + continue; /* not valid rom */ + gus->gf1.rom_banks++; + gus->gf1.rom_present |= 1 << (bank_pos >> 22); + gus->gf1.rom_memory = snd_gf1_peek(gus, bank_pos + 40) | + (snd_gf1_peek(gus, bank_pos + 41) << 8) | + (snd_gf1_peek(gus, bank_pos + 42) << 16) | + (snd_gf1_peek(gus, bank_pos + 43) << 24); + } +#if 0 + if (gus->gf1.rom_memory > 0) { + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + gus->card->type = SNDRV_CARD_TYPE_IW_DYNASONIC; + } +#endif + snd_gf1_write8(gus, SNDRV_GF1_GB_MEMORY_CONTROL, 0x00); /* select RAM */ + + if (!gus->gf1.enh_mode) + snd_interwave_reset(gus); +} + +static void __devinit snd_interwave_init(int dev, snd_gus_card_t * gus) +{ + unsigned long flags; + + /* ok.. some InterWave specific initialization */ + spin_lock_irqsave(&gus->reg_lock, flags); + snd_gf1_write8(gus, SNDRV_GF1_GB_SOUND_BLASTER_CONTROL, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_COMPATIBILITY, 0x1f); + snd_gf1_write8(gus, SNDRV_GF1_GB_DECODE_CONTROL, 0x49); + snd_gf1_write8(gus, SNDRV_GF1_GB_VERSION_NUMBER, 0x11); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_A, 0x00); + snd_gf1_write8(gus, SNDRV_GF1_GB_MPU401_CONTROL_B, 0x30); + snd_gf1_write8(gus, SNDRV_GF1_GB_EMULATION_IRQ, 0x00); + spin_unlock_irqrestore(&gus->reg_lock, flags); + gus->equal_irq = 1; + gus->codec_flag = 1; + gus->interwave = 1; + gus->max_flag = 1; + gus->joystick_dac = joystick_dac[dev]; + +} + +static snd_kcontrol_new_t snd_interwave_controls[] = { +CS4231_DOUBLE("Master Playback Switch", 0, CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Master Playback Volume", 0, CS4231_LINE_LEFT_OUTPUT, CS4231_LINE_RIGHT_OUTPUT, 0, 0, 31, 1), +CS4231_DOUBLE("Mic Playback Switch", 0, CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 7, 7, 1, 1), +CS4231_DOUBLE("Mic Playback Volume", 0, CS4231_LEFT_MIC_INPUT, CS4231_RIGHT_MIC_INPUT, 0, 0, 31, 1) +}; + +static int __devinit snd_interwave_mixer(cs4231_t *chip) +{ + snd_card_t *card = chip->card; + snd_ctl_elem_id_t id1, id2; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; +#if 0 + /* remove mono microphone controls */ + strcpy(id1.name, "Mic Playback Switch"); + if ((err = snd_ctl_remove_id(card, &id1)) < 0) + return err; + strcpy(id1.name, "Mic Playback Volume"); + if ((err = snd_ctl_remove_id(card, &id1)) < 0) + return err; +#endif + /* add new master and mic controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_interwave_controls); idx++) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_interwave_controls[idx], chip))) < 0) + return err; + snd_cs4231_out(chip, CS4231_LINE_LEFT_OUTPUT, 0x9f); + snd_cs4231_out(chip, CS4231_LINE_RIGHT_OUTPUT, 0x9f); + snd_cs4231_out(chip, CS4231_LEFT_MIC_INPUT, 0x9f); + snd_cs4231_out(chip, CS4231_RIGHT_MIC_INPUT, 0x9f); + /* reassign AUXA to SYNTHESIZER */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "Synth Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Synth Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUXB to CD */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + return 0; +} + +#ifdef CONFIG_PNP + +static int __devinit snd_interwave_pnp(int dev, struct snd_interwave *iwcard, + 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; + + iwcard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (iwcard->dev == NULL) { + kfree(cfg); + return -EBUSY; + } +#ifdef SNDRV_STB + iwcard->devtc = pnp_request_card_device(card, id->devs[1].id, NULL); + if (iwcard->devtc == NULL) { + kfree(cfg); + return -EBUSY; + } +#endif + /* Synth & Codec initialization */ + pdev = iwcard->dev; + pnp_init_resource_table(cfg); + if (port[dev] != SNDRV_AUTO_PORT) { + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + pnp_resource_change(&cfg->port_resource[1], port[dev] + 0x100, 12); + pnp_resource_change(&cfg->port_resource[2], port[dev] + 0x10c, 4); + } + 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], 1); + if (dma2[dev] < 0) + pnp_resource_change(&cfg->dma_resource[1], 4, 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "InterWave - Synth - the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + kfree(cfg); + snd_printk(KERN_ERR "InterWave PnP configure failure (out of resources?)\n"); + return err; + } + if (pnp_port_start(pdev, 0) + 0x100 != pnp_port_start(pdev, 1) || + pnp_port_start(pdev, 0) + 0x10c != pnp_port_start(pdev, 2)) { + kfree(cfg); + snd_printk(KERN_ERR "PnP configure failure (wrong ports)\n"); + return -ENOENT; + } + port[dev] = pnp_port_start(pdev, 0); + dma1[dev] = pnp_dma(pdev, 0); + if (dma2[dev] >= 0) + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("isapnp IW: sb port=0x%lx, gf1 port=0x%lx, codec port=0x%lx\n", + pnp_port_start(pdev, 0), + pnp_port_start(pdev, 1), + pnp_port_start(pdev, 2)); + snd_printdd("isapnp IW: dma1=%i, dma2=%i, irq=%i\n", dma1[dev], dma2[dev], irq[dev]); +#ifdef SNDRV_STB + /* Tone Control initialization */ + pdev = iwcard->devtc; + pnp_init_resource_table(cfg); + if (port_tc[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port_tc[dev], 1); + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "InterWave - ToneControl - the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + kfree(cfg); + snd_printk(KERN_ERR "InterWave ToneControl PnP configure failure (out of resources?)\n"); + return err; + } + port_tc[dev] = pnp_port_start(pdev, 0); + snd_printdd("isapnp IW: tone control port=0x%lx\n", port_tc[dev]); +#endif + kfree(cfg); + return 0; +} +#endif /* CONFIG_PNP */ + +static void snd_interwave_free(snd_card_t *card) +{ + struct snd_interwave *iwcard = (struct snd_interwave *)card->private_data; + + if (iwcard == NULL) + return; +#ifdef SNDRV_STB + if (iwcard->i2c_res) { + release_resource(iwcard->i2c_res); + kfree_nocheck(iwcard->i2c_res); + } +#endif + if (iwcard->irq >= 0) + free_irq(iwcard->irq, (void *)iwcard); +} + +static int __devinit snd_interwave_probe(int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int possible_irqs[] = {5, 11, 12, 9, 7, 15, 3, -1}; + static int possible_dmas[] = {0, 1, 3, 5, 6, 7, -1}; + int xirq, xdma1, xdma2; + snd_card_t *card; + struct snd_interwave *iwcard; + cs4231_t *cs4231; + snd_gus_card_t *gus; +#ifdef SNDRV_STB + snd_i2c_bus_t *i2c_bus; +#endif + snd_pcm_t *pcm; + char *str; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_interwave)); + if (card == NULL) + return -ENOMEM; + iwcard = (struct snd_interwave *)card->private_data; + iwcard->card = card; + iwcard->irq = -1; + card->private_free = snd_interwave_free; +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if (snd_interwave_pnp(dev, iwcard, pcard, pid)) { + snd_card_free(card); + return -ENODEV; + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA1\n"); + return -EBUSY; + } + } + xdma2 = dma2[dev]; + if (xdma2 == SNDRV_AUTO_DMA) { + if ((xdma2 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } + + if ((err = snd_gus_create(card, + port[dev], + -xirq, xdma1, xdma2, + 0, 32, + pcm_channels[dev], effect[dev], &gus)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_interwave_detect(iwcard, gus, dev +#ifdef SNDRV_STB + , &i2c_bus +#endif + )) < 0) { + snd_card_free(card); + return err; + } + iwcard->gus_status_reg = gus->gf1.reg_irqstat; + iwcard->pcm_status_reg = gus->gf1.port + 0x10c + 2; + + snd_interwave_init(dev, gus); + snd_interwave_detect_memory(gus); + if ((err = snd_gus_initialize(gus)) < 0) { + snd_card_free(card); + return err; + } + + if (request_irq(xirq, snd_interwave_interrupt, SA_INTERRUPT, "InterWave", (void *)iwcard)) { + snd_card_free(card); + snd_printk("unable to grab IRQ %d\n", xirq); + return -EBUSY; + } + iwcard->irq = xirq; + + if ((err = snd_cs4231_create(card, + gus->gf1.port + 0x10c, -1, xirq, + xdma2 < 0 ? xdma1 : xdma2, xdma1, + CS4231_HW_INTERWAVE, + CS4231_HWSHARE_IRQ | + CS4231_HWSHARE_DMA1 | + CS4231_HWSHARE_DMA2, + &cs4231)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_pcm(cs4231, 0, &pcm)) < 0) { + snd_card_free(card); + return err; + } + sprintf(pcm->name + strlen(pcm->name), " rev %c", gus->revision + 'A'); + strcat(pcm->name, " (codec)"); + if ((err = snd_cs4231_timer(cs4231, 2, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_mixer(cs4231)) < 0) { + snd_card_free(card); + return err; + } + if (pcm_channels[dev] > 0) { + if ((err = snd_gf1_pcm_new(gus, 1, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + if ((err = snd_interwave_mixer(cs4231)) < 0) { + snd_card_free(card); + return err; + } +#ifdef SNDRV_STB + { + snd_ctl_elem_id_t id1, id2; + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + strcpy(id1.name, "Master Playback Switch"); + strcpy(id2.name, id1.name); + id2.index = 1; + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_card_free(card); + return err; + } + strcpy(id1.name, "Master Playback Volume"); + strcpy(id2.name, id1.name); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_tea6330t_update_mixer(card, i2c_bus, 0, 1)) < 0) { + snd_card_free(card); + return err; + } + } +#endif + + gus->uart_enable = midi[dev]; + if ((err = snd_gf1_rawmidi_new(gus, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + +#ifndef SNDRV_STB + str = "AMD InterWave"; + if (gus->gf1.rom_banks == 1 && gus->gf1.rom_present == 8) + str = "Dynasonic 3-D"; +#else + str = "InterWave STB"; +#endif + strcpy(card->driver, str); + strcpy(card->shortname, str); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma %d", + str, + gus->gf1.port, + xirq, + xdma1); + if (xdma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + + iwcard->cs4231 = cs4231; + iwcard->gus = gus; + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_interwave_legacy[dev++] = card; + return 0; +} + +static int __devinit snd_interwave_probe_legacy_port(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + port[dev] = xport; + res = snd_interwave_probe(dev, NULL, NULL); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +#ifdef CONFIG_PNP + +static int __devinit snd_interwave_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_interwave_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + + return -ENODEV; +} + +static void __devexit snd_interwave_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 interwave_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "interwave", + .id_table = snd_interwave_pnpids, + .probe = snd_interwave_pnp_detect, + .remove = __devexit_p(snd_interwave_pnp_remove), +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_interwave_init(void) +{ + int cards = 0, i; + static long possible_ports[] = {0x210, 0x220, 0x230, 0x240, 0x250, 0x260, -1}; + int dev; + + for (dev = 0; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + if (!snd_interwave_probe(dev, NULL, NULL)) { + cards++; + continue; + } +#ifdef MODULE + printk(KERN_ERR "InterWave soundcard #%i not found at 0x%lx or device busy\n", dev, port[dev]); +#endif + } + /* legacy auto configured cards */ + i = snd_legacy_auto_probe(possible_ports, snd_interwave_probe_legacy_port); + if (i > 0) + cards += i; +#ifdef CONFIG_PNP + /* ISA PnP cards */ + i = pnp_register_card_driver(&interwave_pnpc_driver); + if (i > 0) + cards += i; +#endif + + if (!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&interwave_pnpc_driver); +#endif +#ifdef MODULE + printk(KERN_ERR "InterWave soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_interwave_exit(void) +{ + int dev; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&interwave_pnpc_driver); +#endif + for (dev = 0; dev < SNDRV_CARDS; dev++) + snd_card_free(snd_interwave_legacy[dev]); +} + +module_init(alsa_card_interwave_init) +module_exit(alsa_card_interwave_exit) diff --git a/sound/isa/opl3sa2.c b/sound/isa/opl3sa2.c new file mode 100644 index 00000000000..95c7b3e5340 --- /dev/null +++ b/sound/isa/opl3sa2.c @@ -0,0 +1,860 @@ +/* + * Driver for Yamaha OPL3-SA[2,3] soundcards + * 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/interrupt.h> +#include <linux/pm.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> + +#include <asm/io.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Yamaha OPL3SA2+"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Yamaha,YMF719E-S}," + "{Genius,Sound Maker 3DX}," + "{Yamaha,OPL3SA3}," + "{Intel,AL440LX sound}," + "{NeoMagic,MagicWave 3DX}}"); + +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; /* 0xf86,0x370,0x100 */ +static long sb_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240,0x260 */ +static long wss_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x530,0xe80,0xf40,0x604 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x388 */ +static long midi_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT;/* 0x330,0x300 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 0,1,3,5,9,11,12,15 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int dma2[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3,5,6,7 */ +static int opl3sa3_ymode[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 0 }; /* 0,1,2,3 */ /*SL Added*/ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for OPL3-SA soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for OPL3-SA soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable OPL3-SA soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for OPL3-SA driver."); +module_param_array(sb_port, long, NULL, 0444); +MODULE_PARM_DESC(sb_port, "SB port # for OPL3-SA driver."); +module_param_array(wss_port, long, NULL, 0444); +MODULE_PARM_DESC(wss_port, "WSS port # for OPL3-SA driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for OPL3-SA driver."); +module_param_array(midi_port, long, NULL, 0444); +MODULE_PARM_DESC(midi_port, "MIDI port # for OPL3-SA driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for OPL3-SA driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for OPL3-SA driver."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for OPL3-SA driver."); +module_param_array(opl3sa3_ymode, int, NULL, 0444); +MODULE_PARM_DESC(opl3sa3_ymode, "Speaker size selection for 3D Enhancement mode: Desktop/Large Notebook/Small Notebook/HiFi."); + +/* control ports */ +#define OPL3SA2_PM_CTRL 0x01 +#define OPL3SA2_SYS_CTRL 0x02 +#define OPL3SA2_IRQ_CONFIG 0x03 +#define OPL3SA2_IRQ_STATUS 0x04 +#define OPL3SA2_DMA_CONFIG 0x06 +#define OPL3SA2_MASTER_LEFT 0x07 +#define OPL3SA2_MASTER_RIGHT 0x08 +#define OPL3SA2_MIC 0x09 +#define OPL3SA2_MISC 0x0A + +/* opl3sa3 only */ +#define OPL3SA3_DGTL_DOWN 0x12 +#define OPL3SA3_ANLG_DOWN 0x13 +#define OPL3SA3_WIDE 0x14 +#define OPL3SA3_BASS 0x15 +#define OPL3SA3_TREBLE 0x16 + +/* power management bits */ +#define OPL3SA2_PM_ADOWN 0x20 +#define OPL3SA2_PM_PSV 0x04 +#define OPL3SA2_PM_PDN 0x02 +#define OPL3SA2_PM_PDX 0x01 + +#define OPL3SA2_PM_D0 0x00 +#define OPL3SA2_PM_D3 (OPL3SA2_PM_ADOWN|OPL3SA2_PM_PSV|OPL3SA2_PM_PDN|OPL3SA2_PM_PDX) + +typedef struct snd_opl3sa2 opl3sa2_t; + +struct snd_opl3sa2 { + snd_card_t *card; + int version; /* 2 or 3 */ + unsigned long port; /* control port */ + struct resource *res_port; /* control port resource */ + int irq; + int single_dma; + spinlock_t reg_lock; + snd_hwdep_t *synth; + snd_rawmidi_t *rmidi; + cs4231_t *cs4231; +#ifdef CONFIG_PNP + struct pnp_dev *dev; +#endif + unsigned char ctlregs[0x20]; + int ymode; /* SL added */ + snd_kcontrol_t *master_switch; + snd_kcontrol_t *master_volume; +#ifdef CONFIG_PM + void (*cs4231_suspend)(cs4231_t *); + void (*cs4231_resume)(cs4231_t *); +#endif +}; + +static snd_card_t *snd_opl3sa2_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_opl3sa2_pnpids[] = { + /* Yamaha YMF719E-S (Genius Sound Maker 3DX) */ + { .id = "YMH0020", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA3 (integrated on Intel's Pentium II AL440LX motherboard) */ + { .id = "YMH0030", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA2 */ + { .id = "YMH0800", .devs = { { "YMH0021" } } }, + /* Yamaha OPL3-SA2 */ + { .id = "YMH0801", .devs = { { "YMH0021" } } }, + /* NeoMagic MagicWave 3DX */ + { .id = "NMX2200", .devs = { { "YMH2210" } } }, + /* --- */ + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_opl3sa2_pnpids); + +#endif /* CONFIG_PNP */ + + +/* read control port (w/o spinlock) */ +static unsigned char __snd_opl3sa2_read(opl3sa2_t *chip, unsigned char reg) +{ + unsigned char result; +#if 0 + outb(0x1d, port); /* password */ + printk("read [0x%lx] = 0x%x\n", port, inb(port)); +#endif + outb(reg, chip->port); /* register */ + result = inb(chip->port + 1); +#if 0 + printk("read [0x%lx] = 0x%x [0x%x]\n", port, result, inb(port)); +#endif + return result; +} + +/* read control port (with spinlock) */ +static unsigned char snd_opl3sa2_read(opl3sa2_t *chip, unsigned char reg) +{ + unsigned long flags; + unsigned char result; + + spin_lock_irqsave(&chip->reg_lock, flags); + result = __snd_opl3sa2_read(chip, reg); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return result; +} + +/* write control port (w/o spinlock) */ +static void __snd_opl3sa2_write(opl3sa2_t *chip, unsigned char reg, unsigned char value) +{ +#if 0 + outb(0x1d, port); /* password */ +#endif + outb(reg, chip->port); /* register */ + outb(value, chip->port + 1); + chip->ctlregs[reg] = value; +} + +/* write control port (with spinlock) */ +static void snd_opl3sa2_write(opl3sa2_t *chip, unsigned char reg, unsigned char value) +{ + unsigned long flags; + spin_lock_irqsave(&chip->reg_lock, flags); + __snd_opl3sa2_write(chip, reg, value); + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static int __init snd_opl3sa2_detect(opl3sa2_t *chip) +{ + snd_card_t *card; + unsigned long port; + unsigned char tmp, tmp1; + char str[2]; + + card = chip->card; + port = chip->port; + if ((chip->res_port = request_region(port, 2, "OPL3-SA control")) == NULL) { + snd_printk(KERN_ERR "opl3sa2: can't grab port 0x%lx\n", port); + return -EBUSY; + } + // snd_printk("REG 0A = 0x%x\n", snd_opl3sa2_read(chip, 0x0a)); + chip->version = 0; + tmp = snd_opl3sa2_read(chip, OPL3SA2_MISC); + if (tmp == 0xff) { + snd_printd("OPL3-SA [0x%lx] detect = 0x%x\n", port, tmp); + return -ENODEV; + } + switch (tmp & 0x07) { + case 0x01: + chip->version = 2; /* YMF711 */ + break; + default: + chip->version = 3; + /* 0x02 - standard */ + /* 0x03 - YM715B */ + /* 0x04 - YM719 - OPL-SA4? */ + /* 0x05 - OPL3-SA3 - Libretto 100 */ + break; + } + str[0] = chip->version + '0'; + str[1] = 0; + strcat(card->shortname, str); + snd_opl3sa2_write(chip, OPL3SA2_MISC, tmp ^ 7); + if ((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MISC)) != tmp) { + snd_printd("OPL3-SA [0x%lx] detect (1) = 0x%x (0x%x)\n", port, tmp, tmp1); + return -ENODEV; + } + /* try if the MIC register is accesible */ + tmp = snd_opl3sa2_read(chip, OPL3SA2_MIC); + snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x8a); + if (((tmp1 = snd_opl3sa2_read(chip, OPL3SA2_MIC)) & 0x9f) != 0x8a) { + snd_printd("OPL3-SA [0x%lx] detect (2) = 0x%x (0x%x)\n", port, tmp, tmp1); + return -ENODEV; + } + snd_opl3sa2_write(chip, OPL3SA2_MIC, 0x9f); + /* initialization */ + /* Power Management - full on */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0); + if (chip->version > 2) { + /* ymode is bits 4&5 (of 0 to 7) on all but opl3sa2 versions */ + snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, (chip->ymode << 4)); + } else { + /* default for opl3sa2 versions */ + snd_opl3sa2_write(chip, OPL3SA2_SYS_CTRL, 0x00); + } + snd_opl3sa2_write(chip, OPL3SA2_IRQ_CONFIG, 0x0d); /* Interrupt Channel Configuration - IRQ A = OPL3 + MPU + WSS */ + if (chip->single_dma) { + snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x03); /* DMA Configuration - DMA A = WSS-R + WSS-P */ + } else { + snd_opl3sa2_write(chip, OPL3SA2_DMA_CONFIG, 0x21); /* DMA Configuration - DMA B = WSS-R, DMA A = WSS-P */ + } + snd_opl3sa2_write(chip, OPL3SA2_MISC, 0x80 | (tmp & 7)); /* Miscellaneous - default */ + if (chip->version > 2) { + snd_opl3sa2_write(chip, OPL3SA3_DGTL_DOWN, 0x00); /* Digital Block Partial Power Down - default */ + snd_opl3sa2_write(chip, OPL3SA3_ANLG_DOWN, 0x00); /* Analog Block Partial Power Down - default */ + } + return 0; +} + +static irqreturn_t snd_opl3sa2_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned short status; + opl3sa2_t *chip = dev_id; + int handled = 0; + + if (chip == NULL || chip->card == NULL) + return IRQ_NONE; + + status = snd_opl3sa2_read(chip, OPL3SA2_IRQ_STATUS); + + if (status & 0x20) { + handled = 1; + snd_opl3_interrupt(chip->synth); + } + + if ((status & 0x10) && chip->rmidi != NULL) { + handled = 1; + snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data, regs); + } + + if (status & 0x07) { /* TI,CI,PI */ + handled = 1; + snd_cs4231_interrupt(irq, chip->cs4231, regs); + } + + if (status & 0x40) { /* hardware volume change */ + handled = 1; + /* reading from Master Lch register at 0x07 clears this bit */ + snd_opl3sa2_read(chip, OPL3SA2_MASTER_RIGHT); + snd_opl3sa2_read(chip, OPL3SA2_MASTER_LEFT); + if (chip->master_switch && chip->master_volume) { + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_switch->id); + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE, &chip->master_volume->id); + } + } + return IRQ_RETVAL(handled); +} + +#define OPL3SA2_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_opl3sa2_info_single, \ + .get = snd_opl3sa2_get_single, .put = snd_opl3sa2_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_opl3sa2_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_opl3sa2_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opl3sa2_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->ctlregs[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_opl3sa2_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opl3sa2_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, oval; + + val = (ucontrol->value.integer.value[0] & mask); + if (invert) + val = mask - val; + val <<= shift; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = chip->ctlregs[reg]; + val = (oval & ~(mask << shift)) | val; + change = val != oval; + __snd_opl3sa2_write(chip, reg, val); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +#define OPL3SA2_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_opl3sa2_info_double, \ + .get = snd_opl3sa2_get_double, .put = snd_opl3sa2_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +static int snd_opl3sa2_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_opl3sa2_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opl3sa2_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->ctlregs[left_reg] >> shift_left) & mask; + ucontrol->value.integer.value[1] = (chip->ctlregs[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_opl3sa2_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opl3sa2_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, oval1, oval2; + + 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) { + oval1 = chip->ctlregs[left_reg]; + oval2 = chip->ctlregs[right_reg]; + val1 = (oval1 & ~(mask << shift_left)) | val1; + val2 = (oval2 & ~(mask << shift_right)) | val2; + change = val1 != oval1 || val2 != oval2; + __snd_opl3sa2_write(chip, left_reg, val1); + __snd_opl3sa2_write(chip, right_reg, val2); + } else { + oval1 = chip->ctlregs[left_reg]; + val1 = (oval1 & ~((mask << shift_left) | (mask << shift_right))) | val1 | val2; + change = val1 != oval1; + __snd_opl3sa2_write(chip, left_reg, val1); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_opl3sa2_controls[] = { +OPL3SA2_DOUBLE("Master Playback Switch", 0, 0x07, 0x08, 7, 7, 1, 1), +OPL3SA2_DOUBLE("Master Playback Volume", 0, 0x07, 0x08, 0, 0, 15, 1), +OPL3SA2_SINGLE("Mic Playback Switch", 0, 0x09, 7, 1, 1), +OPL3SA2_SINGLE("Mic Playback Volume", 0, 0x09, 0, 31, 1) +}; + +static snd_kcontrol_new_t snd_opl3sa2_tone_controls[] = { +OPL3SA2_DOUBLE("3D Control - Wide", 0, 0x14, 0x14, 4, 0, 7, 0), +OPL3SA2_DOUBLE("Tone Control - Bass", 0, 0x15, 0x15, 4, 0, 7, 0), +OPL3SA2_DOUBLE("Tone Control - Treble", 0, 0x16, 0x16, 4, 0, 7, 0) +}; + +static void snd_opl3sa2_master_free(snd_kcontrol_t *kcontrol) +{ + opl3sa2_t *chip = snd_kcontrol_chip(kcontrol); + chip->master_switch = NULL; + chip->master_volume = NULL; +} + +static int __init snd_opl3sa2_mixer(opl3sa2_t *chip) +{ + snd_card_t *card = chip->card; + snd_ctl_elem_id_t id1, id2; + snd_kcontrol_t *kctl; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX0 to CD */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "CD Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "CD Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUX1 to FM */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "FM Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "FM Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* add OPL3SA2 controls */ + for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_controls); idx++) { + if ((err = snd_ctl_add(card, kctl = snd_ctl_new1(&snd_opl3sa2_controls[idx], chip))) < 0) + return err; + switch (idx) { + case 0: chip->master_switch = kctl; kctl->private_free = snd_opl3sa2_master_free; break; + case 1: chip->master_volume = kctl; kctl->private_free = snd_opl3sa2_master_free; break; + } + } + if (chip->version > 2) { + for (idx = 0; idx < ARRAY_SIZE(snd_opl3sa2_tone_controls); idx++) + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opl3sa2_tone_controls[idx], chip))) < 0) + return err; + } + return 0; +} + +/* Power Management support functions */ +#ifdef CONFIG_PM +static int snd_opl3sa2_suspend(snd_card_t *card, pm_message_t state) +{ + opl3sa2_t *chip = card->pm_private_data; + + snd_pcm_suspend_all(chip->cs4231->pcm); /* stop before saving regs */ + chip->cs4231_suspend(chip->cs4231); + + /* power down */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D3); + + return 0; +} + +static int snd_opl3sa2_resume(snd_card_t *card) +{ + opl3sa2_t *chip = card->pm_private_data; + int i; + + /* power up */ + snd_opl3sa2_write(chip, OPL3SA2_PM_CTRL, OPL3SA2_PM_D0); + + /* restore registers */ + for (i = 2; i <= 0x0a; i++) { + if (i != OPL3SA2_IRQ_STATUS) + snd_opl3sa2_write(chip, i, chip->ctlregs[i]); + } + if (chip->version > 2) { + for (i = 0x12; i <= 0x16; i++) + snd_opl3sa2_write(chip, i, chip->ctlregs[i]); + } + /* restore cs4231 */ + chip->cs4231_resume(chip->cs4231); + + return 0; +} +#endif /* CONFIG_PM */ + +#ifdef CONFIG_PNP +static int __init snd_opl3sa2_pnp(int dev, opl3sa2_t *chip, + 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; + pdev = chip->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (chip->dev == NULL) { + kfree(cfg); + return -EBUSY; + } + /* PnP initialization */ + pnp_init_resource_table(cfg); + if (sb_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], sb_port[dev], 16); + if (wss_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], wss_port[dev], 8); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], fm_port[dev], 4); + if (midi_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[3], midi_port[dev], 2); + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[4], port[dev], 2); + 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], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + err = pnp_manual_config_dev(pdev, cfg, 0); + if (err < 0) + snd_printk(KERN_ERR "PnP manual resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + kfree(cfg); + snd_printk(KERN_ERR "PnP configure failure (out of resources?) err = %d\n", err); + return -EBUSY; + } + sb_port[dev] = pnp_port_start(pdev, 0); + wss_port[dev] = pnp_port_start(pdev, 1); + fm_port[dev] = pnp_port_start(pdev, 2); + midi_port[dev] = pnp_port_start(pdev, 3); + port[dev] = pnp_port_start(pdev, 4); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("PnP OPL3-SA: sb port=0x%lx, wss port=0x%lx, fm port=0x%lx, midi port=0x%lx\n", + sb_port[dev], wss_port[dev], fm_port[dev], midi_port[dev]); + snd_printdd("PnP OPL3-SA: control port=0x%lx, dma1=%i, dma2=%i, irq=%i\n", + port[dev], dma1[dev], dma2[dev], irq[dev]); + kfree(cfg); + return 0; +} +#endif /* CONFIG_PNP */ + +static int snd_opl3sa2_free(opl3sa2_t *chip) +{ + if (chip->irq >= 0) + free_irq(chip->irq, (void *)chip); + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + kfree(chip); + return 0; +} + +static int snd_opl3sa2_dev_free(snd_device_t *device) +{ + opl3sa2_t *chip = device->device_data; + return snd_opl3sa2_free(chip); +} + +static int __devinit snd_opl3sa2_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int xirq, xdma1, xdma2; + snd_card_t *card; + struct snd_opl3sa2 *chip; + cs4231_t *cs4231; + opl3_t *opl3; + static snd_device_ops_t ops = { + .dev_free = snd_opl3sa2_dev_free, + }; + int err; + +#ifdef CONFIG_PNP + if (!isapnp[dev]) { +#endif + if (port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify port\n"); + return -EINVAL; + } + if (wss_port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify wss_port\n"); + return -EINVAL; + } + if (fm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify fm_port\n"); + return -EINVAL; + } + if (midi_port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify midi_port\n"); + return -EINVAL; + } +#ifdef CONFIG_PNP + } +#endif + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + strcpy(card->driver, "OPL3SA2"); + strcpy(card->shortname, "Yamaha OPL3-SA2"); + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) { + err = -ENOMEM; + goto __error; + } + spin_lock_init(&chip->reg_lock); + chip->irq = -1; + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) + goto __error; +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if ((err = snd_opl3sa2_pnp(dev, chip, pcard, pid)) < 0) + goto __error; + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + chip->ymode = opl3sa3_ymode[dev] & 0x03 ; /* initialise this card from supplied (or default) parameter*/ + chip->card = card; + chip->port = port[dev]; + xirq = irq[dev]; + xdma1 = dma1[dev]; + xdma2 = dma2[dev]; + if (xdma2 < 0) + chip->single_dma = 1; + if ((err = snd_opl3sa2_detect(chip)) < 0) + goto __error; + if (request_irq(xirq, snd_opl3sa2_interrupt, SA_INTERRUPT, "OPL3-SA2", (void *)chip)) { + snd_printk(KERN_ERR "opl3sa2: can't grab IRQ %d\n", xirq); + err = -ENODEV; + goto __error; + } + chip->irq = xirq; + if ((err = snd_cs4231_create(card, + wss_port[dev] + 4, -1, + xirq, xdma1, xdma2, + CS4231_HW_OPL3SA2, + CS4231_HWSHARE_IRQ, + &cs4231)) < 0) { + snd_printd("Oops, WSS not detected at 0x%lx\n", wss_port[dev] + 4); + goto __error; + } + chip->cs4231 = cs4231; + if ((err = snd_cs4231_pcm(cs4231, 0, NULL)) < 0) + goto __error; + if ((err = snd_cs4231_mixer(cs4231)) < 0) + goto __error; + if ((err = snd_opl3sa2_mixer(chip)) < 0) + goto __error; + if ((err = snd_cs4231_timer(cs4231, 0, NULL)) < 0) + goto __error; + if (fm_port[dev] >= 0x340 && fm_port[dev] < 0x400) { + if ((err = snd_opl3_create(card, fm_port[dev], + fm_port[dev] + 2, + OPL3_HW_OPL3, 0, &opl3)) < 0) + goto __error; + if ((err = snd_opl3_timer_new(opl3, 1, 2)) < 0) + goto __error; + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, &chip->synth)) < 0) + goto __error; + } + if (midi_port[dev] >= 0x300 && midi_port[dev] < 0x340) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_OPL3SA2, + midi_port[dev], 0, + xirq, 0, &chip->rmidi)) < 0) + goto __error; + } +#ifdef CONFIG_PM + chip->cs4231_suspend = chip->cs4231->suspend; + chip->cs4231_resume = chip->cs4231->resume; + /* now clear callbacks for cs4231 */ + chip->cs4231->suspend = NULL; + chip->cs4231->resume = NULL; + snd_card_set_isa_pm_callback(card, snd_opl3sa2_suspend, snd_opl3sa2_resume, chip); +#endif + + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + card->shortname, chip->port, xirq, xdma1); + if (dma2 >= 0) + sprintf(card->longname + strlen(card->longname), "&%d", xdma2); + + if ((err = snd_card_register(card)) < 0) + goto __error; + + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_opl3sa2_legacy[dev] = card; + return 0; + + __error: + snd_card_free(card); + return err; +} + +#ifdef CONFIG_PNP +static int __devinit snd_opl3sa2_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_opl3sa2_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_opl3sa2_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 opl3sa2_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "opl3sa2", + .id_table = snd_opl3sa2_pnpids, + .probe = snd_opl3sa2_pnp_detect, + .remove = __devexit_p(snd_opl3sa2_pnp_remove), +}; +#endif /* CONFIG_PNP */ + +static int __init alsa_card_opl3sa2_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_opl3sa2_probe(dev, NULL, NULL) >= 0) + cards++; + } +#ifdef CONFIG_PNP + cards += pnp_register_card_driver(&opl3sa2_pnpc_driver); +#endif + if (!cards) { +#ifdef MODULE + snd_printk(KERN_ERR "Yamaha OPL3-SA soundcard not found or device busy\n"); +#endif +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&opl3sa2_pnpc_driver); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_opl3sa2_exit(void) +{ + int idx; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&opl3sa2_pnpc_driver); +#endif + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_opl3sa2_legacy[idx]); +} + +module_init(alsa_card_opl3sa2_init) +module_exit(alsa_card_opl3sa2_exit) diff --git a/sound/isa/opti9xx/Makefile b/sound/isa/opti9xx/Makefile new file mode 100644 index 00000000000..28c64070cd5 --- /dev/null +++ b/sound/isa/opti9xx/Makefile @@ -0,0 +1,13 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-opti92x-ad1848-objs := opti92x-ad1848.o +snd-opti92x-cs4231-objs := opti92x-cs4231.o +snd-opti93x-objs := opti93x.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_OPTI92X_AD1848) += snd-opti92x-ad1848.o +obj-$(CONFIG_SND_OPTI92X_CS4231) += snd-opti92x-cs4231.o +obj-$(CONFIG_SND_OPTI93X) += snd-opti93x.o diff --git a/sound/isa/opti9xx/opti92x-ad1848.c b/sound/isa/opti9xx/opti92x-ad1848.c new file mode 100644 index 00000000000..411a702d85b --- /dev/null +++ b/sound/isa/opti9xx/opti92x-ad1848.c @@ -0,0 +1,2226 @@ +/* + card-opti92x-ad1848.c - driver for OPTi 82c92x based soundcards. + Copyright (C) 1998-2000 by Massimo Piccioni <dafastidio@libero.it> + + Part of this code was developed at the Italian Ministry of Air Defence, + Sixth Division (oh, che pace ...), Rome. + + Thanks to Maria Grazia Pollarini, Salvatore Vassallo. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#ifdef CS4231 +#include <sound/cs4231.h> +#else +#ifndef OPTi93X +#include <sound/ad1848.h> +#else +#include <sound/control.h> +#include <sound/pcm.h> +#endif /* OPTi93X */ +#endif /* CS4231 */ +#include <sound/mpu401.h> +#include <sound/opl3.h> +#ifndef OPTi93X +#include <sound/opl4.h> +#endif +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_LICENSE("GPL"); +#ifdef OPTi93X +MODULE_DESCRIPTION("OPTi93X"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C931/3}}"); +#else /* OPTi93X */ +#ifdef CS4231 +MODULE_DESCRIPTION("OPTi92X - CS4231"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (CS4231)}," + "{OPTi,82C925 (CS4231)}}"); +#else /* CS4231 */ +MODULE_DESCRIPTION("OPTi92X - AD1848"); +MODULE_SUPPORTED_DEVICE("{{OPTi,82C924 (AD1848)}," + "{OPTi,82C925 (AD1848)}," + "{OAK,Mozart}}"); +#endif /* CS4231 */ +#endif /* OPTi93X */ + +static int index = SNDRV_DEFAULT_IDX1; /* Index 0-MAX */ +static char *id = SNDRV_DEFAULT_STR1; /* ID for this card */ +//static int enable = SNDRV_DEFAULT_ENABLE1; /* Enable this card */ +static int isapnp = 1; /* Enable ISA PnP detection */ +static long port = SNDRV_DEFAULT_PORT1; /* 0x530,0xe80,0xf40,0x604 */ +static long mpu_port = SNDRV_DEFAULT_PORT1; /* 0x300,0x310,0x320,0x330 */ +static long fm_port = SNDRV_DEFAULT_PORT1; /* 0x388 */ +static int irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10,11 */ +static int mpu_irq = SNDRV_DEFAULT_IRQ1; /* 5,7,9,10 */ +static int dma1 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#if defined(CS4231) || defined(OPTi93X) +static int dma2 = SNDRV_DEFAULT_DMA1; /* 0,1,3 */ +#endif /* CS4231 || OPTi93X */ + +module_param(index, int, 0444); +MODULE_PARM_DESC(index, "Index value for opti9xx based soundcard."); +module_param(id, charp, 0444); +MODULE_PARM_DESC(id, "ID string for opti9xx based soundcard."); +//module_param(enable, bool, 0444); +//MODULE_PARM_DESC(enable, "Enable opti9xx soundcard."); +module_param(isapnp, bool, 0444); +MODULE_PARM_DESC(isapnp, "Enable ISA PnP detection for specified soundcard."); +module_param(port, long, 0444); +MODULE_PARM_DESC(port, "WSS port # for opti9xx driver."); +module_param(mpu_port, long, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for opti9xx driver."); +module_param(fm_port, long, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for opti9xx driver."); +module_param(irq, int, 0444); +MODULE_PARM_DESC(irq, "WSS irq # for opti9xx driver."); +module_param(mpu_irq, int, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU-401 irq # for opti9xx driver."); +module_param(dma1, int, 0444); +MODULE_PARM_DESC(dma1, "1st dma # for opti9xx driver."); +#if defined(CS4231) || defined(OPTi93X) +module_param(dma2, int, 0444); +MODULE_PARM_DESC(dma2, "2nd dma # for opti9xx driver."); +#endif /* CS4231 || OPTi93X */ + +#define OPTi9XX_HW_DETECT 0 +#define OPTi9XX_HW_82C928 1 +#define OPTi9XX_HW_82C929 2 +#define OPTi9XX_HW_82C924 3 +#define OPTi9XX_HW_82C925 4 +#define OPTi9XX_HW_82C930 5 +#define OPTi9XX_HW_82C931 6 +#define OPTi9XX_HW_82C933 7 +#define OPTi9XX_HW_LAST OPTi9XX_HW_82C933 + +#define OPTi9XX_MC_REG(n) n + +typedef struct _snd_opti9xx opti9xx_t; + +#ifdef OPTi93X + +#define OPTi93X_INDEX 0x00 +#define OPTi93X_DATA 0x01 +#define OPTi93X_STATUS 0x02 +#define OPTi93X_DDATA 0x03 +#define OPTi93X_PORT(chip, r) ((chip)->port + OPTi93X_##r) + +#define OPTi93X_MIXOUT_LEFT 0x00 +#define OPTi93X_MIXOUT_RIGHT 0x01 +#define OPTi93X_CD_LEFT_INPUT 0x02 +#define OPTi93X_CD_RIGHT_INPUT 0x03 +#define OPTi930_AUX_LEFT_INPUT 0x04 +#define OPTi930_AUX_RIGHT_INPUT 0x05 +#define OPTi931_FM_LEFT_INPUT 0x04 +#define OPTi931_FM_RIGHT_INPUT 0x05 +#define OPTi93X_DAC_LEFT 0x06 +#define OPTi93X_DAC_RIGHT 0x07 +#define OPTi93X_PLAY_FORMAT 0x08 +#define OPTi93X_IFACE_CONF 0x09 +#define OPTi93X_PIN_CTRL 0x0a +#define OPTi93X_ERR_INIT 0x0b +#define OPTi93X_ID 0x0c +#define OPTi93X_PLAY_UPR_CNT 0x0e +#define OPTi93X_PLAY_LWR_CNT 0x0f +#define OPTi931_AUX_LEFT_INPUT 0x10 +#define OPTi931_AUX_RIGHT_INPUT 0x11 +#define OPTi93X_LINE_LEFT_INPUT 0x12 +#define OPTi93X_LINE_RIGHT_INPUT 0x13 +#define OPTi93X_MIC_LEFT_INPUT 0x14 +#define OPTi93X_MIC_RIGHT_INPUT 0x15 +#define OPTi93X_OUT_LEFT 0x16 +#define OPTi93X_OUT_RIGHT 0x17 +#define OPTi93X_CAPT_FORMAT 0x1c +#define OPTi93X_CAPT_UPR_CNT 0x1e +#define OPTi93X_CAPT_LWR_CNT 0x1f + +#define OPTi93X_TRD 0x20 +#define OPTi93X_MCE 0x40 +#define OPTi93X_INIT 0x80 + +#define OPTi93X_MIXOUT_MIC_GAIN 0x20 +#define OPTi93X_MIXOUT_LINE 0x00 +#define OPTi93X_MIXOUT_CD 0x40 +#define OPTi93X_MIXOUT_MIC 0x80 +#define OPTi93X_MIXOUT_MIXER 0xc0 + +#define OPTi93X_STEREO 0x10 +#define OPTi93X_LINEAR_8 0x00 +#define OPTi93X_ULAW_8 0x20 +#define OPTi93X_LINEAR_16_LIT 0x40 +#define OPTi93X_ALAW_8 0x60 +#define OPTi93X_ADPCM_16 0xa0 +#define OPTi93X_LINEAR_16_BIG 0xc0 + +#define OPTi93X_CAPTURE_PIO 0x80 +#define OPTi93X_PLAYBACK_PIO 0x40 +#define OPTi93X_AUTOCALIB 0x08 +#define OPTi93X_SINGLE_DMA 0x04 +#define OPTi93X_CAPTURE_ENABLE 0x02 +#define OPTi93X_PLAYBACK_ENABLE 0x01 + +#define OPTi93X_IRQ_ENABLE 0x02 + +#define OPTi93X_DMA_REQUEST 0x10 +#define OPTi93X_CALIB_IN_PROGRESS 0x20 + +#define OPTi93X_IRQ_PLAYBACK 0x04 +#define OPTi93X_IRQ_CAPTURE 0x08 + + +typedef struct _snd_opti93x opti93x_t; + +struct _snd_opti93x { + unsigned long port; + struct resource *res_port; + int irq; + int dma1; + int dma2; + + opti9xx_t *chip; + unsigned short hardware; + unsigned char image[32]; + + unsigned char mce_bit; + unsigned short mode; + int mute; + + spinlock_t lock; + + snd_card_t *card; + snd_pcm_t *pcm; + snd_pcm_substream_t *playback_substream; + snd_pcm_substream_t *capture_substream; + unsigned int p_dma_size; + unsigned int c_dma_size; +}; + +#define OPTi93X_MODE_NONE 0x00 +#define OPTi93X_MODE_PLAY 0x01 +#define OPTi93X_MODE_CAPTURE 0x02 +#define OPTi93X_MODE_OPEN (OPTi93X_MODE_PLAY | OPTi93X_MODE_CAPTURE) + +#endif /* OPTi93X */ + +struct _snd_opti9xx { + unsigned short hardware; + unsigned char password; + char name[7]; + + unsigned long mc_base; + struct resource *res_mc_base; + unsigned long mc_base_size; +#ifdef OPTi93X + unsigned long mc_indir_index; +#endif /* OPTi93X */ + unsigned long pwd_reg; + + spinlock_t lock; + + long wss_base; + int irq; + int dma1; +#if defined(CS4231) || defined(OPTi93X) + int dma2; +#endif /* CS4231 || OPTi93X */ + + long fm_port; + + long mpu_port; + int mpu_irq; + +#ifdef CONFIG_PNP + struct pnp_dev *dev; + struct pnp_dev *devmpu; +#endif /* CONFIG_PNP */ +}; + +static int snd_opti9xx_first_hit = 1; +static snd_card_t *snd_opti9xx_legacy = SNDRV_DEFAULT_PTR1; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_opti9xx_pnpids[] = { +#ifndef OPTi93X + /* OPTi 82C924 */ + { .id = "OPT0924", .devs = { { "OPT0000" }, { "OPT0002" } }, .driver_data = 0x0924 }, + /* OPTi 82C925 */ + { .id = "OPT0925", .devs = { { "OPT9250" }, { "OPT0002" } }, .driver_data = 0x0925 }, +#else + /* OPTi 82C931/3 */ + { .id = "OPT0931", .devs = { { "OPT9310" }, { "OPT0002" } }, .driver_data = 0x0931 }, +#endif /* OPTi93X */ + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_opti9xx_pnpids); + +#endif /* CONFIG_PNP */ + +#ifdef OPTi93X +#define DRIVER_NAME "snd-card-opti93x" +#else +#define DRIVER_NAME "snd-card-opti92x" +#endif /* OPTi93X */ + +static char * snd_opti9xx_names[] = { + "unkown", + "82C928", "82C929", + "82C924", "82C925", + "82C930", "82C931", "82C933" +}; + + +static long snd_legacy_find_free_ioport(long *port_table, long size) +{ + while (*port_table != -1) { + struct resource *res; + if ((res = request_region(*port_table, size, "ALSA test")) != NULL) { + release_resource(res); + kfree_nocheck(res); + return *port_table; + } + port_table++; + } + return -1; +} + +static int __devinit snd_opti9xx_init(opti9xx_t *chip, unsigned short hardware) +{ + static int opti9xx_mc_size[] = {7, 7, 10, 10, 2, 2, 2}; + + chip->hardware = hardware; + strcpy(chip->name, snd_opti9xx_names[hardware]); + + chip->mc_base_size = opti9xx_mc_size[hardware]; + + spin_lock_init(&chip->lock); + + chip->wss_base = -1; + chip->irq = -1; + chip->dma1 = -1; +#if defined(CS4231) || defined (OPTi93X) + chip->dma2 = -1; +#endif /* CS4231 || OPTi93X */ + chip->fm_port = -1; + chip->mpu_port = -1; + chip->mpu_irq = -1; + + switch (hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + chip->mc_base = 0xf8c; + chip->password = (hardware == OPTi9XX_HW_82C928) ? 0xe2 : 0xe3; + chip->pwd_reg = 3; + break; + + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + chip->mc_base = 0xf8c; + chip->password = 0xe5; + chip->pwd_reg = 3; + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + chip->mc_base = (hardware == OPTi9XX_HW_82C930) ? 0xf8f : 0xf8d; + chip->mc_indir_index = 0xe0e; + chip->password = 0xe4; + chip->pwd_reg = 0; + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", hardware); + return -ENODEV; + } + return 0; +} + +static unsigned char snd_opti9xx_read(opti9xx_t *chip, + unsigned char reg) +{ + unsigned long flags; + unsigned char retval = 0xff; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + retval = inb(chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + retval = inb(chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); + return retval; +} + +static void snd_opti9xx_write(opti9xx_t *chip, unsigned char reg, + unsigned char value) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + case OPTi9XX_HW_82C925: + if (reg > 7) { + outb(reg, chip->mc_base + 8); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_base + 9); + break; + } + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + outb(value, chip->mc_base + reg); + break; +#else /* OPTi93X */ + + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + outb(reg, chip->mc_indir_index); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(value, chip->mc_indir_index + 1); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + } + + spin_unlock_irqrestore(&chip->lock, flags); +} + + +#define snd_opti9xx_write_mask(chip, reg, value, mask) \ + snd_opti9xx_write(chip, reg, \ + (snd_opti9xx_read(chip, reg) & ~(mask)) | ((value) & (mask))) + + +static int __devinit snd_opti9xx_configure(opti9xx_t *chip) +{ + unsigned char wss_base_bits; + unsigned char irq_bits; + unsigned char dma_bits; + unsigned char mpu_port_bits = 0; + unsigned char mpu_irq_bits; + + switch (chip->hardware) { +#ifndef OPTi93X + case OPTi9XX_HW_82C924: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0xf0, 0xfc); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x02); + + case OPTi9XX_HW_82C925: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xf0, 0xff); +#ifdef CS4231 + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + + case OPTi9XX_HW_82C928: + case OPTi9XX_HW_82C929: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), 0x80, 0x80); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), 0x00, 0x20); + /* + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0xa2, 0xae); + */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x00, 0x0c); +#ifdef CS4231 + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x02, 0x02); +#else + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x00, 0x02); +#endif /* CS4231 */ + break; + +#else /* OPTi93X */ + case OPTi9XX_HW_82C930: + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), 0x02, 0x03); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(3), 0x00, 0xff); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(4), 0x10 | + (chip->hardware == OPTi9XX_HW_82C930 ? 0x00 : 0x04), + 0x34); + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(5), 0x20, 0xbf); + break; +#endif /* OPTi93X */ + + default: + snd_printk("chip %d not supported\n", chip->hardware); + return -EINVAL; + } + + switch (chip->wss_base) { + case 0x530: + wss_base_bits = 0x00; + break; + case 0x604: + wss_base_bits = 0x03; + break; + case 0xe80: + wss_base_bits = 0x01; + break; + case 0xf40: + wss_base_bits = 0x02; + break; + default: + snd_printk("WSS port 0x%lx not valid\n", chip->wss_base); + goto __skip_base; + } + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(1), wss_base_bits << 4, 0x30); + +__skip_base: + switch (chip->irq) { +//#ifdef OPTi93X + case 5: + irq_bits = 0x05; + break; +//#endif /* OPTi93X */ + case 7: + irq_bits = 0x01; + break; + case 9: + irq_bits = 0x02; + break; + case 10: + irq_bits = 0x03; + break; + case 11: + irq_bits = 0x04; + break; + default: + snd_printk("WSS irq # %d not valid\n", chip->irq); + goto __skip_resources; + } + + switch (chip->dma1) { + case 0: + dma_bits = 0x01; + break; + case 1: + dma_bits = 0x02; + break; + case 3: + dma_bits = 0x03; + break; + default: + snd_printk("WSS dma1 # %d not valid\n", chip->dma1); + goto __skip_resources; + } + +#if defined(CS4231) || defined(OPTi93X) + if (chip->dma1 == chip->dma2) { + snd_printk("don't want to share dmas\n"); + return -EBUSY; + } + + switch (chip->dma2) { + case 0: + case 1: + break; + default: + snd_printk("WSS dma2 # %d not valid\n", chip->dma2); + goto __skip_resources; + } + dma_bits |= 0x04; +#endif /* CS4231 || OPTi93X */ + +#ifndef OPTi93X + outb(irq_bits << 3 | dma_bits, chip->wss_base); +#else /* OPTi93X */ + snd_opti9xx_write(chip, OPTi9XX_MC_REG(3), (irq_bits << 3 | dma_bits)); +#endif /* OPTi93X */ + +__skip_resources: + if (chip->hardware > OPTi9XX_HW_82C928) { + switch (chip->mpu_port) { + case 0: + case -1: + break; + case 0x300: + mpu_port_bits = 0x03; + break; + case 0x310: + mpu_port_bits = 0x02; + break; + case 0x320: + mpu_port_bits = 0x01; + break; + case 0x330: + mpu_port_bits = 0x00; + break; + default: + snd_printk("MPU-401 port 0x%lx not valid\n", + chip->mpu_port); + goto __skip_mpu; + } + + switch (chip->mpu_irq) { + case 5: + mpu_irq_bits = 0x02; + break; + case 7: + mpu_irq_bits = 0x03; + break; + case 9: + mpu_irq_bits = 0x00; + break; + case 10: + mpu_irq_bits = 0x01; + break; + default: + snd_printk("MPU-401 irq # %d not valid\n", + chip->mpu_irq); + goto __skip_mpu; + } + + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(6), + (chip->mpu_port <= 0) ? 0x00 : + 0x80 | mpu_port_bits << 5 | mpu_irq_bits << 3, + 0xf8); + } +__skip_mpu: + + return 0; +} + +#ifdef OPTi93X + +static unsigned char snd_opti93x_default_image[32] = +{ + 0x00, /* 00/00 - l_mixout_outctrl */ + 0x00, /* 01/01 - r_mixout_outctrl */ + 0x88, /* 02/02 - l_cd_inctrl */ + 0x88, /* 03/03 - r_cd_inctrl */ + 0x88, /* 04/04 - l_a1/fm_inctrl */ + 0x88, /* 05/05 - r_a1/fm_inctrl */ + 0x80, /* 06/06 - l_dac_inctrl */ + 0x80, /* 07/07 - r_dac_inctrl */ + 0x00, /* 08/08 - ply_dataform_reg */ + 0x00, /* 09/09 - if_conf */ + 0x00, /* 0a/10 - pin_ctrl */ + 0x00, /* 0b/11 - err_init_reg */ + 0x0a, /* 0c/12 - id_reg */ + 0x00, /* 0d/13 - reserved */ + 0x00, /* 0e/14 - ply_upcount_reg */ + 0x00, /* 0f/15 - ply_lowcount_reg */ + 0x88, /* 10/16 - reserved/l_a1_inctrl */ + 0x88, /* 11/17 - reserved/r_a1_inctrl */ + 0x88, /* 12/18 - l_line_inctrl */ + 0x88, /* 13/19 - r_line_inctrl */ + 0x88, /* 14/20 - l_mic_inctrl */ + 0x88, /* 15/21 - r_mic_inctrl */ + 0x80, /* 16/22 - l_out_outctrl */ + 0x80, /* 17/23 - r_out_outctrl */ + 0x00, /* 18/24 - reserved */ + 0x00, /* 19/25 - reserved */ + 0x00, /* 1a/26 - reserved */ + 0x00, /* 1b/27 - reserved */ + 0x00, /* 1c/28 - cap_dataform_reg */ + 0x00, /* 1d/29 - reserved */ + 0x00, /* 1e/30 - cap_upcount_reg */ + 0x00 /* 1f/31 - cap_lowcount_reg */ +}; + + +static int snd_opti93x_busy_wait(opti93x_t *chip) +{ + int timeout; + + for (timeout = 250; timeout-- > 0; udelay(10)) + if (!(inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_INIT)) + return 0; + + snd_printk("chip still busy.\n"); + return -EBUSY; +} + +static unsigned char snd_opti93x_in(opti93x_t *chip, unsigned char reg) +{ + snd_opti93x_busy_wait(chip); + outb(chip->mce_bit | (reg & 0x1f), OPTi93X_PORT(chip, INDEX)); + return inb(OPTi93X_PORT(chip, DATA)); +} + +static void snd_opti93x_out(opti93x_t *chip, unsigned char reg, + unsigned char value) +{ + snd_opti93x_busy_wait(chip); + outb(chip->mce_bit | (reg & 0x1f), OPTi93X_PORT(chip, INDEX)); + outb(value, OPTi93X_PORT(chip, DATA)); +} + +static void snd_opti93x_out_image(opti93x_t *chip, unsigned char reg, + unsigned char value) +{ + snd_opti93x_out(chip, reg, chip->image[reg] = value); +} + +static void snd_opti93x_out_mask(opti93x_t *chip, unsigned char reg, + unsigned char mask, unsigned char value) +{ + snd_opti93x_out_image(chip, reg, + (chip->image[reg] & ~mask) | (value & mask)); +} + + +static void snd_opti93x_mce_up(opti93x_t *chip) +{ + snd_opti93x_busy_wait(chip); + + chip->mce_bit = OPTi93X_MCE; + if (!(inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_MCE)) + outb(chip->mce_bit, OPTi93X_PORT(chip, INDEX)); +} + +static void snd_opti93x_mce_down(opti93x_t *chip) +{ + snd_opti93x_busy_wait(chip); + + chip->mce_bit = 0; + if (inb(OPTi93X_PORT(chip, INDEX)) & OPTi93X_MCE) + outb(chip->mce_bit, OPTi93X_PORT(chip, INDEX)); +} + +#define snd_opti93x_mute_reg(chip, reg, mute) \ + snd_opti93x_out(chip, reg, mute ? 0x80 : chip->image[reg]); + +static void snd_opti93x_mute(opti93x_t *chip, int mute) +{ + mute = mute ? 1 : 0; + if (chip->mute == mute) + return; + + chip->mute = mute; + + snd_opti93x_mute_reg(chip, OPTi93X_CD_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_CD_RIGHT_INPUT, mute); + switch (chip->hardware) { + case OPTi9XX_HW_82C930: + snd_opti93x_mute_reg(chip, OPTi930_AUX_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi930_AUX_RIGHT_INPUT, mute); + break; + case OPTi9XX_HW_82C931: + case OPTi9XX_HW_82C933: + snd_opti93x_mute_reg(chip, OPTi931_FM_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi931_FM_RIGHT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi931_AUX_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi931_AUX_RIGHT_INPUT, mute); + } + snd_opti93x_mute_reg(chip, OPTi93X_DAC_LEFT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_DAC_RIGHT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_LINE_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_LINE_RIGHT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_MIC_LEFT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_MIC_RIGHT_INPUT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_OUT_LEFT, mute); + snd_opti93x_mute_reg(chip, OPTi93X_OUT_RIGHT, mute); +} + + +static unsigned int snd_opti93x_get_count(unsigned char format, + unsigned int size) +{ + switch (format & 0xe0) { + case OPTi93X_LINEAR_16_LIT: + case OPTi93X_LINEAR_16_BIG: + size >>= 1; + break; + case OPTi93X_ADPCM_16: + return size >> 2; + } + return (format & OPTi93X_STEREO) ? (size >> 1) : size; +} + +static unsigned int rates[] = { 5512, 6615, 8000, 9600, 11025, 16000, + 18900, 22050, 27428, 32000, 33075, 37800, + 44100, 48000 }; +#define RATES ARRAY_SIZE(rates) + +static snd_pcm_hw_constraint_list_t hw_constraints_rates = { + .count = RATES, + .list = rates, + .mask = 0, +}; + +static unsigned char bits[] = { 0x01, 0x0f, 0x00, 0x0e, 0x03, 0x02, + 0x05, 0x07, 0x04, 0x06, 0x0d, 0x09, + 0x0b, 0x0c}; + +static unsigned char snd_opti93x_get_freq(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < RATES; i++) { + if (rate == rates[i]) + return bits[i]; + } + snd_BUG(); + return bits[RATES-1]; +} + +static unsigned char snd_opti93x_get_format(opti93x_t *chip, + unsigned int format, int channels) +{ + unsigned char retval = OPTi93X_LINEAR_8; + + switch (format) { + case SNDRV_PCM_FORMAT_MU_LAW: + retval = OPTi93X_ULAW_8; + break; + case SNDRV_PCM_FORMAT_A_LAW: + retval = OPTi93X_ALAW_8; + break; + case SNDRV_PCM_FORMAT_S16_LE: + retval = OPTi93X_LINEAR_16_LIT; + break; + case SNDRV_PCM_FORMAT_S16_BE: + retval = OPTi93X_LINEAR_16_BIG; + break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: + retval = OPTi93X_ADPCM_16; + } + return (channels > 1) ? (retval | OPTi93X_STEREO) : retval; +} + + +static void snd_opti93x_playback_format(opti93x_t *chip, unsigned char fmt) +{ + unsigned char mask; + + snd_opti93x_mute(chip, 1); + + snd_opti93x_mce_up(chip); + mask = (chip->mode & OPTi93X_MODE_CAPTURE) ? 0xf0 : 0xff; + snd_opti93x_out_mask(chip, OPTi93X_PLAY_FORMAT, mask, fmt); + snd_opti93x_mce_down(chip); + + snd_opti93x_mute(chip, 0); +} + +static void snd_opti93x_capture_format(opti93x_t *chip, unsigned char fmt) +{ + snd_opti93x_mute(chip, 1); + + snd_opti93x_mce_up(chip); + if (!(chip->mode & OPTi93X_MODE_PLAY)) + snd_opti93x_out_mask(chip, OPTi93X_PLAY_FORMAT, 0x0f, fmt); + else + fmt = chip->image[OPTi93X_PLAY_FORMAT] & 0xf0; + snd_opti93x_out_image(chip, OPTi93X_CAPT_FORMAT, fmt); + snd_opti93x_mce_down(chip); + + snd_opti93x_mute(chip, 0); +} + + +static int snd_opti93x_open(opti93x_t *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + if (chip->mode & mode) { + spin_unlock_irqrestore(&chip->lock, flags); + return -EAGAIN; + } + + if (!(chip->mode & OPTi93X_MODE_OPEN)) { + outb(0x00, OPTi93X_PORT(chip, STATUS)); + snd_opti93x_out_mask(chip, OPTi93X_PIN_CTRL, + OPTi93X_IRQ_ENABLE, OPTi93X_IRQ_ENABLE); + chip->mode = mode; + } + else + chip->mode |= mode; + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static void snd_opti93x_close(opti93x_t *chip, unsigned int mode) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + chip->mode &= ~mode; + if (chip->mode & OPTi93X_MODE_OPEN) { + spin_unlock_irqrestore(&chip->lock, flags); + return; + } + + snd_opti93x_mute(chip, 1); + + outb(0, OPTi93X_PORT(chip, STATUS)); + snd_opti93x_out_mask(chip, OPTi93X_PIN_CTRL, OPTi93X_IRQ_ENABLE, + ~OPTi93X_IRQ_ENABLE); + + snd_opti93x_mce_up(chip); + snd_opti93x_out_image(chip, OPTi93X_IFACE_CONF, 0x00); + snd_opti93x_mce_down(chip); + chip->mode = 0; + + snd_opti93x_mute(chip, 0); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int snd_opti93x_trigger(snd_pcm_substream_t *substream, + unsigned char what, int cmd) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_STOP: + { + unsigned int what = 0; + struct list_head *pos; + snd_pcm_substream_t *s; + snd_pcm_group_for_each(pos, substream) { + s = snd_pcm_group_substream_entry(pos); + if (s == chip->playback_substream) { + what |= OPTi93X_PLAYBACK_ENABLE; + snd_pcm_trigger_done(s, substream); + } else if (s == chip->capture_substream) { + what |= OPTi93X_CAPTURE_ENABLE; + snd_pcm_trigger_done(s, substream); + } + } + spin_lock(&chip->lock); + if (cmd == SNDRV_PCM_TRIGGER_START) { + snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, what, what); + if (what & OPTi93X_CAPTURE_ENABLE) + udelay(50); + } else + snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, what, 0x00); + spin_unlock(&chip->lock); + break; + } + default: + return -EINVAL; + } + return 0; +} + +static int snd_opti93x_playback_trigger(snd_pcm_substream_t *substream, int cmd) +{ + return snd_opti93x_trigger(substream, + OPTi93X_PLAYBACK_ENABLE, cmd); +} + +static int snd_opti93x_capture_trigger(snd_pcm_substream_t * substream, int cmd) +{ + return snd_opti93x_trigger(substream, + OPTi93X_CAPTURE_ENABLE, cmd); +} + +static int snd_opti93x_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + + +static int snd_opti93x_hw_free(snd_pcm_substream_t * substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + + +static int snd_opti93x_playback_prepare(snd_pcm_substream_t * substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + unsigned char format; + unsigned int count = snd_pcm_lib_period_bytes(substream); + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + + spin_lock_irqsave(&chip->lock, flags); + + chip->p_dma_size = size; + snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, + OPTi93X_PLAYBACK_ENABLE | OPTi93X_PLAYBACK_PIO, + ~(OPTi93X_PLAYBACK_ENABLE | OPTi93X_PLAYBACK_PIO)); + + snd_dma_program(chip->dma1, runtime->dma_addr, size, + DMA_MODE_WRITE | DMA_AUTOINIT); + + format = snd_opti93x_get_freq(runtime->rate); + format |= snd_opti93x_get_format(chip, runtime->format, + runtime->channels); + snd_opti93x_playback_format(chip, format); + format = chip->image[OPTi93X_PLAY_FORMAT]; + + count = snd_opti93x_get_count(format, count) - 1; + snd_opti93x_out_image(chip, OPTi93X_PLAY_LWR_CNT, count); + snd_opti93x_out_image(chip, OPTi93X_PLAY_UPR_CNT, count >> 8); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_opti93x_capture_prepare(snd_pcm_substream_t *substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + unsigned char format; + unsigned int count = snd_pcm_lib_period_bytes(substream); + unsigned int size = snd_pcm_lib_buffer_bytes(substream); + + spin_lock_irqsave(&chip->lock, flags); + + chip->c_dma_size = size; + snd_opti93x_out_mask(chip, OPTi93X_IFACE_CONF, + OPTi93X_CAPTURE_ENABLE | OPTi93X_CAPTURE_PIO, + (unsigned char)~(OPTi93X_CAPTURE_ENABLE | OPTi93X_CAPTURE_PIO)); + + snd_dma_program(chip->dma2, runtime->dma_addr, size, + DMA_MODE_READ | DMA_AUTOINIT); + + format = snd_opti93x_get_freq(runtime->rate); + format |= snd_opti93x_get_format(chip, runtime->format, + runtime->channels); + snd_opti93x_capture_format(chip, format); + format = chip->image[OPTi93X_CAPT_FORMAT]; + + count = snd_opti93x_get_count(format, count) - 1; + snd_opti93x_out_image(chip, OPTi93X_CAPT_LWR_CNT, count); + snd_opti93x_out_image(chip, OPTi93X_CAPT_UPR_CNT, count >> 8); + + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static snd_pcm_uframes_t snd_opti93x_playback_pointer(snd_pcm_substream_t *substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[OPTi93X_IFACE_CONF] & OPTi93X_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_opti93x_capture_pointer(snd_pcm_substream_t *substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (!(chip->image[OPTi93X_IFACE_CONF] & OPTi93X_CAPTURE_ENABLE)) + return 0; + + ptr = snd_dma_pointer(chip->dma2, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + + +static void snd_opti93x_overrange(opti93x_t *chip) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + + if (snd_opti93x_in(chip, OPTi93X_ERR_INIT) & (0x08 | 0x02)) + chip->capture_substream->runtime->overrange++; + + spin_unlock_irqrestore(&chip->lock, flags); +} + +static irqreturn_t snd_opti93x_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + opti93x_t *codec = dev_id; + unsigned char status; + + status = snd_opti9xx_read(codec->chip, OPTi9XX_MC_REG(11)); + if ((status & OPTi93X_IRQ_PLAYBACK) && codec->playback_substream) + snd_pcm_period_elapsed(codec->playback_substream); + if ((status & OPTi93X_IRQ_CAPTURE) && codec->capture_substream) { + snd_opti93x_overrange(codec); + snd_pcm_period_elapsed(codec->capture_substream); + } + outb(0x00, OPTi93X_PORT(codec, STATUS)); + return IRQ_HANDLED; +} + + +static snd_pcm_hardware_t snd_opti93x_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | 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 = 5512, + .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_opti93x_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID | 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_8000_48000, + .rate_min = 5512, + .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_opti93x_playback_open(snd_pcm_substream_t *substream) +{ + int error; + opti93x_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + if ((error = snd_opti93x_open(chip, OPTi93X_MODE_PLAY)) < 0) + return error; + snd_pcm_set_sync(substream); + chip->playback_substream = substream; + runtime->hw = snd_opti93x_playback; + snd_pcm_limit_isa_dma_size(chip->dma1, &runtime->hw.buffer_bytes_max); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + return error; +} + +static int snd_opti93x_capture_open(snd_pcm_substream_t *substream) +{ + int error; + opti93x_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + if ((error = snd_opti93x_open(chip, OPTi93X_MODE_CAPTURE)) < 0) + return error; + runtime->hw = snd_opti93x_capture; + snd_pcm_set_sync(substream); + chip->capture_substream = substream; + snd_pcm_limit_isa_dma_size(chip->dma2, &runtime->hw.buffer_bytes_max); + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); + return error; +} + +static int snd_opti93x_playback_close(snd_pcm_substream_t *substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + snd_opti93x_close(chip, OPTi93X_MODE_PLAY); + return 0; +} + +static int snd_opti93x_capture_close(snd_pcm_substream_t *substream) +{ + opti93x_t *chip = snd_pcm_substream_chip(substream); + + chip->capture_substream = NULL; + snd_opti93x_close(chip, OPTi93X_MODE_CAPTURE); + return 0; +} + + +static void snd_opti93x_init(opti93x_t *chip) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&chip->lock, flags); + snd_opti93x_mce_up(chip); + + for (i = 0; i < 32; i++) + snd_opti93x_out_image(chip, i, snd_opti93x_default_image[i]); + + snd_opti93x_mce_down(chip); + spin_unlock_irqrestore(&chip->lock, flags); +} + +static int snd_opti93x_probe(opti93x_t *chip) +{ + unsigned long flags; + unsigned char val; + + spin_lock_irqsave(&chip->lock, flags); + val = snd_opti93x_in(chip, OPTi93X_ID) & 0x0f; + spin_unlock_irqrestore(&chip->lock, flags); + + return (val == 0x0a) ? 0 : -ENODEV; +} + +static int snd_opti93x_free(opti93x_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->dma1 >= 0) { + disable_dma(chip->dma1); + free_dma(chip->dma1); + } + if (chip->dma2 >= 0) { + disable_dma(chip->dma2); + free_dma(chip->dma2); + } + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + } + kfree(chip); + return 0; +} + +static int snd_opti93x_dev_free(snd_device_t *device) +{ + opti93x_t *chip = device->device_data; + return snd_opti93x_free(chip); +} + +static const char *snd_opti93x_chip_id(opti93x_t *codec) +{ + switch (codec->hardware) { + case OPTi9XX_HW_82C930: return "82C930"; + case OPTi9XX_HW_82C931: return "82C931"; + case OPTi9XX_HW_82C933: return "82C933"; + default: return "???"; + } +} + +static int snd_opti93x_create(snd_card_t *card, opti9xx_t *chip, + int dma1, int dma2, + opti93x_t **rcodec) +{ + static snd_device_ops_t ops = { + .dev_free = snd_opti93x_dev_free, + }; + int error; + opti93x_t *codec; + + *rcodec = NULL; + codec = kcalloc(1, sizeof(*codec), GFP_KERNEL); + if (codec == NULL) + return -ENOMEM; + codec->irq = -1; + codec->dma1 = -1; + codec->dma2 = -1; + + if ((codec->res_port = request_region(chip->wss_base + 4, 4, "OPTI93x CODEC")) == NULL) { + snd_printk(KERN_ERR "opti9xx: can't grab port 0x%lx\n", chip->wss_base + 4); + snd_opti93x_free(codec); + return -EBUSY; + } + if (request_dma(dma1, "OPTI93x - 1")) { + snd_printk(KERN_ERR "opti9xx: can't grab DMA1 %d\n", dma1); + snd_opti93x_free(codec); + return -EBUSY; + } + codec->dma1 = chip->dma1; + if (request_dma(dma2, "OPTI93x - 2")) { + snd_printk(KERN_ERR "opti9xx: can't grab DMA2 %d\n", dma2); + snd_opti93x_free(codec); + return -EBUSY; + } + codec->dma2 = chip->dma2; + + if (request_irq(chip->irq, snd_opti93x_interrupt, SA_INTERRUPT, DRIVER_NAME" - WSS", codec)) { + snd_printk(KERN_ERR "opti9xx: can't grab IRQ %d\n", chip->irq); + snd_opti93x_free(codec); + return -EBUSY; + } + + codec->card = card; + codec->port = chip->wss_base + 4; + codec->irq = chip->irq; + + spin_lock_init(&codec->lock); + codec->hardware = chip->hardware; + codec->chip = chip; + + if ((error = snd_opti93x_probe(codec))) { + snd_opti93x_free(codec); + return error; + } + + snd_opti93x_init(codec); + + /* Register device */ + if ((error = snd_device_new(card, SNDRV_DEV_LOWLEVEL, codec, &ops)) < 0) { + snd_opti93x_free(codec); + return error; + } + + *rcodec = codec; + return 0; +} + +static snd_pcm_ops_t snd_opti93x_playback_ops = { + .open = snd_opti93x_playback_open, + .close = snd_opti93x_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_opti93x_hw_params, + .hw_free = snd_opti93x_hw_free, + .prepare = snd_opti93x_playback_prepare, + .trigger = snd_opti93x_playback_trigger, + .pointer = snd_opti93x_playback_pointer, +}; + +static snd_pcm_ops_t snd_opti93x_capture_ops = { + .open = snd_opti93x_capture_open, + .close = snd_opti93x_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_opti93x_hw_params, + .hw_free = snd_opti93x_hw_free, + .prepare = snd_opti93x_capture_prepare, + .trigger = snd_opti93x_capture_trigger, + .pointer = snd_opti93x_capture_pointer, +}; + +static void snd_opti93x_pcm_free(snd_pcm_t *pcm) +{ + opti93x_t *codec = pcm->private_data; + codec->pcm = NULL; + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int snd_opti93x_pcm(opti93x_t *codec, int device, snd_pcm_t **rpcm) +{ + int error; + snd_pcm_t *pcm; + + if ((error = snd_pcm_new(codec->card, "OPTi 82C93X", device, 1, 1, &pcm))) + return error; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_opti93x_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_opti93x_capture_ops); + + pcm->private_data = codec; + pcm->private_free = snd_opti93x_pcm_free; + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + + strcpy(pcm->name, snd_opti93x_chip_id(codec)); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, codec->dma1 > 3 || codec->dma2 > 3 ? 128*1024 : 64*1024); + + codec->pcm = pcm; + if (rpcm) + *rpcm = pcm; + return 0; +} + +/* + * MIXER part + */ + +static int snd_opti93x_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[4] = { + "Line1", "Aux", "Mic", "Mix" + }; + + 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; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_opti93x_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->lock, flags); + ucontrol->value.enumerated.item[0] = (chip->image[OPTi93X_MIXOUT_LEFT] & OPTi93X_MIXOUT_MIXER) >> 6; + ucontrol->value.enumerated.item[1] = (chip->image[OPTi93X_MIXOUT_RIGHT] & OPTi93X_MIXOUT_MIXER) >> 6; + spin_unlock_irqrestore(&chip->lock, flags); + return 0; +} + +static int snd_opti93x_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_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->lock, flags); + left = (chip->image[OPTi93X_MIXOUT_LEFT] & ~OPTi93X_MIXOUT_MIXER) | left; + right = (chip->image[OPTi93X_MIXOUT_RIGHT] & ~OPTi93X_MIXOUT_MIXER) | right; + change = left != chip->image[OPTi93X_MIXOUT_LEFT] || + right != chip->image[OPTi93X_MIXOUT_RIGHT]; + snd_opti93x_out_image(chip, OPTi93X_MIXOUT_LEFT, left); + snd_opti93x_out_image(chip, OPTi93X_MIXOUT_RIGHT, right); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#if 0 + +#define OPTi93X_SINGLE(xname, xindex, reg, shift, mask, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xindex, \ + .info = snd_opti93x_info_single, \ + .get = snd_opti93x_get_single, .put = snd_opti93x_put_single, \ + .private_value = reg | (shift << 8) | (mask << 16) | (invert << 24) } + +static int snd_opti93x_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_opti93x_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_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->lock, flags); + ucontrol->value.integer.value[0] = (chip->image[reg] >> shift) & mask; + spin_unlock_irqrestore(&chip->lock, flags); + if (invert) + ucontrol->value.integer.value[0] = mask - ucontrol->value.integer.value[0]; + return 0; +} + +static int snd_opti93x_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_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->lock, flags); + val = (chip->image[reg] & ~(mask << shift)) | val; + change = val != chip->image[reg]; + snd_opti93x_out(chip, reg, val); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +#endif /* single */ + +#define OPTi93X_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_opti93x_info_double, \ + .get = snd_opti93x_get_double, .put = snd_opti93x_put_double, \ + .private_value = left_reg | (right_reg << 8) | (shift_left << 16) | (shift_right << 19) | (mask << 24) | (invert << 22) } + +#define OPTi93X_DOUBLE_INVERT_INVERT(xctl) \ + do { xctl.private_value ^= 22; } while (0) +#define OPTi93X_DOUBLE_CHANGE_REGS(xctl, left_reg, right_reg) \ + do { xctl.private_value &= ~0x0000ffff; \ + xctl.private_value |= left_reg | (right_reg << 8); } while (0) + +static int snd_opti93x_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_opti93x_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_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->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->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_opti93x_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + opti93x_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->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_opti93x_out_image(chip, left_reg, val1); + snd_opti93x_out_image(chip, right_reg, val2); + spin_unlock_irqrestore(&chip->lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_opti93x_controls[] = { +OPTi93X_DOUBLE("Master Playback Switch", 0, OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 7, 7, 1, 1), +OPTi93X_DOUBLE("Master Playback Volume", 0, OPTi93X_OUT_LEFT, OPTi93X_OUT_RIGHT, 1, 1, 31, 1), +OPTi93X_DOUBLE("PCM Playback Switch", 0, OPTi93X_DAC_LEFT, OPTi93X_DAC_RIGHT, 7, 7, 1, 1), +OPTi93X_DOUBLE("PCM Playback Volume", 0, OPTi93X_DAC_LEFT, OPTi93X_DAC_RIGHT, 0, 0, 31, 1), +OPTi93X_DOUBLE("FM Playback Switch", 0, OPTi931_FM_LEFT_INPUT, OPTi931_FM_RIGHT_INPUT, 7, 7, 1, 1), +OPTi93X_DOUBLE("FM Playback Volume", 0, OPTi931_FM_LEFT_INPUT, OPTi931_FM_RIGHT_INPUT, 1, 1, 15, 1), +OPTi93X_DOUBLE("Line Playback Switch", 0, OPTi93X_LINE_LEFT_INPUT, OPTi93X_LINE_RIGHT_INPUT, 7, 7, 1, 1), +OPTi93X_DOUBLE("Line Playback Volume", 0, OPTi93X_LINE_LEFT_INPUT, OPTi93X_LINE_RIGHT_INPUT, 1, 1, 15, 1), +OPTi93X_DOUBLE("Mic Playback Switch", 0, OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 7, 7, 1, 1), +OPTi93X_DOUBLE("Mic Playback Volume", 0, OPTi93X_MIC_LEFT_INPUT, OPTi93X_MIC_RIGHT_INPUT, 1, 1, 15, 1), +OPTi93X_DOUBLE("Mic Boost", 0, OPTi93X_MIXOUT_LEFT, OPTi93X_MIXOUT_RIGHT, 5, 5, 1, 1), +OPTi93X_DOUBLE("CD Playback Switch", 0, OPTi93X_CD_LEFT_INPUT, OPTi93X_CD_RIGHT_INPUT, 7, 7, 1, 1), +OPTi93X_DOUBLE("CD Playback Volume", 0, OPTi93X_CD_LEFT_INPUT, OPTi93X_CD_RIGHT_INPUT, 1, 1, 15, 1), +OPTi93X_DOUBLE("Aux Playback Switch", 0, OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 7, 7, 1, 1), +OPTi93X_DOUBLE("Aux Playback Volume", 0, OPTi931_AUX_LEFT_INPUT, OPTi931_AUX_RIGHT_INPUT, 1, 1, 15, 1), +OPTi93X_DOUBLE("Capture Volume", 0, OPTi93X_MIXOUT_LEFT, OPTi93X_MIXOUT_RIGHT, 0, 0, 15, 0), +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = snd_opti93x_info_mux, + .get = snd_opti93x_get_mux, + .put = snd_opti93x_put_mux, +} +}; + +static int snd_opti93x_mixer(opti93x_t *chip) +{ + snd_card_t *card; + snd_kcontrol_new_t knew; + int err; + unsigned int idx; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + + card = chip->card; + + strcpy(card->mixername, snd_opti93x_chip_id(chip)); + + for (idx = 0; idx < ARRAY_SIZE(snd_opti93x_controls); idx++) { + knew = snd_opti93x_controls[idx]; + if (chip->hardware == OPTi9XX_HW_82C930) { + if (strstr(knew.name, "FM")) /* skip FM controls */ + continue; + else if (strcmp(knew.name, "Mic Playback Volume")) + OPTi93X_DOUBLE_INVERT_INVERT(knew); + else if (strstr(knew.name, "Aux")) + OPTi93X_DOUBLE_CHANGE_REGS(knew, OPTi930_AUX_LEFT_INPUT, OPTi930_AUX_RIGHT_INPUT); + else if (strcmp(knew.name, "PCM Playback Volume")) + OPTi93X_DOUBLE_INVERT_INVERT(knew); + else if (strcmp(knew.name, "Master Playback Volume")) + OPTi93X_DOUBLE_INVERT_INVERT(knew); + } + if ((err = snd_ctl_add(card, snd_ctl_new1(&snd_opti93x_controls[idx], chip))) < 0) + return err; + } + return 0; +} + +#endif /* OPTi93X */ + +static int __devinit snd_card_opti9xx_detect(snd_card_t *card, opti9xx_t *chip) +{ + int i, err; + +#ifndef OPTi93X + for (i = OPTi9XX_HW_82C928; i < OPTi9XX_HW_82C930; i++) { + unsigned char value; + + if ((err = snd_opti9xx_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(1)); + if ((value != 0xff) && (value != inb(chip->mc_base + 1))) + if (value == snd_opti9xx_read(chip, OPTi9XX_MC_REG(1))) + return 1; + + release_resource(chip->res_mc_base); + kfree_nocheck(chip->res_mc_base); + chip->res_mc_base = NULL; + + } +#else /* OPTi93X */ + for (i = OPTi9XX_HW_82C931; i >= OPTi9XX_HW_82C930; i--) { + unsigned long flags; + unsigned char value; + + if ((err = snd_opti9xx_init(chip, i)) < 0) + return err; + + if ((chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) + continue; + + spin_lock_irqsave(&chip->lock, flags); + outb(chip->password, chip->mc_base + chip->pwd_reg); + outb(((chip->mc_indir_index & (1 << 8)) >> 4) | + ((chip->mc_indir_index & 0xf0) >> 4), chip->mc_base); + spin_unlock_irqrestore(&chip->lock, flags); + + value = snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)); + snd_opti9xx_write(chip, OPTi9XX_MC_REG(7), 0xff - value); + if (snd_opti9xx_read(chip, OPTi9XX_MC_REG(7)) == 0xff - value) + return 1; + + release_resource(chip->res_mc_base); + kfree_nocheck(chip->res_mc_base); + chip->res_mc_base = NULL; + } +#endif /* OPTi93X */ + + return -ENODEV; +} + +#ifdef CONFIG_PNP +static int __devinit snd_card_opti9xx_pnp(opti9xx_t *chip, struct pnp_card_link *card, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *pdev; + struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + int err; + + chip->dev = pnp_request_card_device(card, pid->devs[0].id, NULL); + if (chip->dev == NULL) { + kfree(cfg); + return -EBUSY; + } + chip->devmpu = pnp_request_card_device(card, pid->devs[1].id, NULL); + + pdev = chip->dev; + pnp_init_resource_table(cfg); + +#ifdef OPTi93X + if (port != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port + 4, 4); +#else + if (pid->driver_data != 0x0924 && port != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], port, 4); +#endif /* OPTi93X */ + if (irq != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq, 1); + if (dma1 != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma1, 1); +#if defined(CS4231) || defined(OPTi93X) + if (dma2 != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[1], dma2, 1); +#else +#ifdef snd_opti9xx_fixup_dma2 + snd_opti9xx_fixup_dma2(pdev); +#endif +#endif /* CS4231 || OPTi93X */ +#ifdef OPTi93X + if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], fm_port, 4); +#else + if (fm_port > 0 && fm_port != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], fm_port, 4); +#endif + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure: %d\n", err); + kfree(cfg); + return err; + } + +#ifdef OPTi93X + port = pnp_port_start(pdev, 0) - 4; + fm_port = pnp_port_start(pdev, 1); +#else + if (pid->driver_data != 0x0924) + port = pnp_port_start(pdev, 1); + fm_port = pnp_port_start(pdev, 2); +#endif /* OPTi93X */ + irq = pnp_irq(pdev, 0); + dma1 = pnp_dma(pdev, 0); +#if defined(CS4231) || defined(OPTi93X) + dma2 = pnp_dma(pdev, 1); +#endif /* CS4231 || OPTi93X */ + + pdev = chip->devmpu; + if (pdev && mpu_port > 0) { + pnp_init_resource_table(cfg); + + if (mpu_port != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], mpu_port, 2); + if (mpu_irq != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], mpu_irq, 1); + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "AUDIO pnp configure failure\n"); + mpu_port = -1; + chip->devmpu = NULL; + } else { + mpu_port = pnp_port_start(pdev, 0); + mpu_irq = pnp_irq(pdev, 0); + } + } + kfree(cfg); + return pid->driver_data; +} +#endif /* CONFIG_PNP */ + +#if 0 +static int __devinit snd_card_opti9xx_resources(struct snd_card_opti9xx *chip, + snd_card_t *card) +{ + int error, i, pnp = 0; + +#ifdef CONFIG_PNP + pnp = chip->dev != NULL; +#endif /* CONFIG_PNP */ + +#ifndef OPTi93X + if (chip->chip->hardware == OPTi9XX_HW_82C928) + mpu_port = -1; +#endif /* OPTi93X */ + error = 0; + if (!pnp && (mpu_port == SNDRV_DEFAULT_PORT1)) { + for (i = 0; possible_mpu_ports[i] != -1; i++) + if (!snd_register_ioport(card, possible_mpu_ports[i], 2, + DRIVER_NAME" - MPU-401", NULL)) { + mpu_port = possible_mpu_ports[i]; + break; + } + if (mpu_port == SNDRV_DEFAULT_PORT1) + error = -EBUSY; + } + else + error = (mpu_port == -1) ? -ENODEV : + snd_register_ioport(card, mpu_port, 2, + DRIVER_NAME" - MPU-401", NULL); + if (error) + chip->chip->mpu_port = -1; + else if (pnp && (irq == mpu_irq)) + chip->chip->mpu_irq = mpu_irq; + else if (!snd_register_interrupt(card, + DRIVER_NAME" - MPU-401", + mpu_irq, SNDRV_IRQ_TYPE_ISA, + snd_card_opti9xx_mpu_interrupt, chip, + pnp ? no_alternatives : possible_mpu_irqs, + &chip->mpuirqptr)) { + chip->chip->mpu_port = mpu_port; + chip->chip->mpu_irq = chip->mpuirqptr->irq; + } + else + chip->chip->mpu_port = -1; + + if (!pnp && (port == SNDRV_DEFAULT_PORT1)) { + for (i = 0; possible_ports[i] != -1; i++) + if (!snd_register_ioport(card, possible_ports[i], 8, + DRIVER_NAME" - WSS", NULL)) { + port = possible_ports[i]; + break; + } + if (port == SNDRV_DEFAULT_PORT1) + return -EBUSY; + } + else if ((error = snd_register_ioport(card, port, 8, + DRIVER_NAME" - WSS", NULL)) < 0) + return error; + chip->chip->wss_base = port; + if ((error = snd_register_interrupt(card, DRIVER_NAME" - WSS", + irq, SNDRV_IRQ_TYPE_ISA, + snd_card_opti9xx_interrupt, chip, + pnp ? no_alternatives : possible_irqs, + &chip->irqptr)) < 0) + return error; + chip->chip->irq = chip->irqptr->irq; + if ((error = snd_register_dma_channel(card, +#if defined(CS4231) || defined(OPTi93X) + DRIVER_NAME" - WSS playback", +#else + DRIVER_NAME" - WSS", +#endif /* CS4231 || OPTi93X */ + dma1, SNDRV_DMA_TYPE_ISA, dma1_size, + pnp ? no_alternatives : possible_dma1s, + &chip->dma1ptr)) < 0) + return error; + chip->chip->dma1 = chip->dma1ptr->dma; +#if defined(CS4231) || defined(OPTi93X) + if ((error = snd_register_dma_channel(card, DRIVER_NAME" - WSS capture", + dma2, SNDRV_DMA_TYPE_ISA, dma2_size, + pnp ? no_alternatives : + possible_dma2s[chip->dma1ptr->dma], + &chip->dma2ptr)) < 0) + return error; + chip->chip->dma2 = chip->dma2ptr->dma; +#endif /* CS4231 || OPTi93X */ + + if (snd_register_ioport(card, + pnp ? fm_port : fm_port = 0x388, 4, + DRIVER_NAME" - OPL", NULL) < 0) + fm_port = -1; + chip->chip->fm_port = fm_port; + + return 0; +} +#endif + +static void snd_card_opti9xx_free(snd_card_t *card) +{ + opti9xx_t *chip = (opti9xx_t *)card->private_data; + + if (chip) { + if (chip->res_mc_base) { + release_resource(chip->res_mc_base); + kfree_nocheck(chip->res_mc_base); + } + } +} + +static int __devinit snd_card_opti9xx_probe(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static long possible_ports[] = {0x530, 0xe80, 0xf40, 0x604, -1}; + static long possible_mpu_ports[] = {0x300, 0x310, 0x320, 0x330, -1}; +#ifdef OPTi93X + static int possible_irqs[] = {5, 9, 10, 11, 7, -1}; +#else + static int possible_irqs[] = {9, 10, 11, 7, -1}; +#endif /* OPTi93X */ + static int possible_mpu_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dma1s[] = {3, 1, 0, -1}; +#if defined(CS4231) || defined(OPTi93X) + static int possible_dma2s[][2] = {{1,-1}, {0,-1}, {-1,-1}, {0,-1}}; +#endif /* CS4231 || OPTi93X */ + int error; + opti9xx_t *chip; +#if defined(OPTi93X) + opti93x_t *codec; +#elif defined(CS4231) + cs4231_t *codec; + snd_timer_t *timer; +#else + ad1848_t *codec; +#endif + snd_card_t *card; + snd_pcm_t *pcm; + snd_rawmidi_t *rmidi; + snd_hwdep_t *synth; +#ifdef CONFIG_PNP + int hw; +#endif /* CONFIG_PNP */ + + if (pcard && !snd_opti9xx_first_hit) + return -EBUSY; + if (!(card = snd_card_new(index, id, THIS_MODULE, + sizeof(opti9xx_t)))) + return -ENOMEM; + card->private_free = snd_card_opti9xx_free; + chip = (opti9xx_t *)card->private_data; + +#ifdef CONFIG_PNP + if (isapnp && pcard && (hw = snd_card_opti9xx_pnp(chip, pcard, pid)) > 0) { + switch (hw) { + case 0x0924: + hw = OPTi9XX_HW_82C924; + break; + case 0x0925: + hw = OPTi9XX_HW_82C925; + break; + case 0x0931: + hw = OPTi9XX_HW_82C931; + break; + default: + snd_card_free(card); + return -ENODEV; + } + + if ((error = snd_opti9xx_init(chip, hw))) { + snd_card_free(card); + return error; + } + if (hw <= OPTi9XX_HW_82C930) + chip->mc_base -= 0x80; + snd_card_set_dev(card, &pcard->card->dev); + } else { +#endif /* CONFIG_PNP */ + if ((error = snd_card_opti9xx_detect(card, chip)) < 0) { + snd_card_free(card); + return error; + } +#ifdef CONFIG_PNP + } +#endif /* CONFIG_PNP */ + + if (! chip->res_mc_base && + (chip->res_mc_base = request_region(chip->mc_base, chip->mc_base_size, "OPTi9xx MC")) == NULL) { + snd_card_free(card); + return -ENOMEM; + } + + chip->wss_base = port; + chip->fm_port = fm_port; + chip->mpu_port = mpu_port; + chip->irq = irq; + chip->mpu_irq = mpu_irq; + chip->dma1 = dma1; +#if defined(CS4231) || defined(OPTi93X) + chip->dma2 = dma2; +#endif + + if (chip->wss_base == SNDRV_AUTO_PORT) { + if ((chip->wss_base = snd_legacy_find_free_ioport(possible_ports, 4)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free WSS port\n"); + return -EBUSY; + } + } +#ifdef CONFIG_PNP + if (!isapnp) { +#endif + if (chip->mpu_port == SNDRV_AUTO_PORT) { + if ((chip->mpu_port = snd_legacy_find_free_ioport(possible_mpu_ports, 2)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free MPU401 port\n"); + return -EBUSY; + } + } + if (chip->irq == SNDRV_AUTO_IRQ) { + if ((chip->irq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (chip->mpu_irq == SNDRV_AUTO_IRQ) { + if ((chip->mpu_irq = snd_legacy_find_free_irq(possible_mpu_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free MPU401 IRQ\n"); + return -EBUSY; + } + } + if (chip->dma1 == SNDRV_AUTO_DMA) { + if ((chip->dma1 = snd_legacy_find_free_dma(possible_dma1s)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA1\n"); + return -EBUSY; + } + } +#if defined(CS4231) || defined(OPTi93X) + if (chip->dma2 == SNDRV_AUTO_DMA) { + if ((chip->dma2 = snd_legacy_find_free_dma(possible_dma2s[chip->dma1 % 4])) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA2\n"); + return -EBUSY; + } + } +#endif + +#ifdef CONFIG_PNP + } +#endif + + if ((error = snd_opti9xx_configure(chip))) { + snd_card_free(card); + return error; + } + +#if defined(OPTi93X) + if ((error = snd_opti93x_create(card, chip, chip->dma1, chip->dma2, &codec))) { + snd_card_free(card); + return error; + } + if ((error = snd_opti93x_pcm(codec, 0, &pcm)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_opti93x_mixer(codec)) < 0) { + snd_card_free(card); + return error; + } +#elif defined(CS4231) + if ((error = snd_cs4231_create(card, chip->wss_base + 4, -1, + chip->irq, chip->dma1, chip->dma2, + CS4231_HW_DETECT, + 0, + &codec)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_pcm(codec, 0, &pcm)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_mixer(codec)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_cs4231_timer(codec, 0, &timer)) < 0) { + snd_card_free(card); + return error; + } +#else + if ((error = snd_ad1848_create(card, chip->wss_base + 4, + chip->irq, chip->dma1, + AD1848_HW_DETECT, &codec)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_ad1848_pcm(codec, 0, &pcm)) < 0) { + snd_card_free(card); + return error; + } + if ((error = snd_ad1848_mixer(codec)) < 0) { + snd_card_free(card); + return error; + } +#endif + strcpy(card->driver, chip->name); + sprintf(card->shortname, "OPTi %s", card->driver); +#if defined(CS4231) || defined(OPTi93X) + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d&%d", + card->shortname, pcm->name, chip->wss_base + 4, + chip->irq, chip->dma1, chip->dma2); +#else + sprintf(card->longname, "%s, %s at 0x%lx, irq %d, dma %d", + card->shortname, pcm->name, chip->wss_base + 4, + chip->irq, chip->dma1); +#endif /* CS4231 || OPTi93X */ + + if (chip->mpu_port <= 0 || chip->mpu_port == SNDRV_AUTO_PORT) + rmidi = NULL; + else + if ((error = snd_mpu401_uart_new(card, 0, MPU401_HW_MPU401, + chip->mpu_port, 0, chip->mpu_irq, SA_INTERRUPT, + &rmidi))) + snd_printk("no MPU-401 device at 0x%lx?\n", chip->mpu_port); + + if (chip->fm_port > 0 && chip->fm_port != SNDRV_AUTO_PORT) { + opl3_t *opl3 = NULL; +#ifndef OPTi93X + if (chip->hardware == OPTi9XX_HW_82C928 || + chip->hardware == OPTi9XX_HW_82C929 || + chip->hardware == OPTi9XX_HW_82C924) { + opl4_t *opl4; + /* assume we have an OPL4 */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x20, 0x20); + if (snd_opl4_create(card, + chip->fm_port, + chip->fm_port - 8, + 2, &opl3, &opl4) < 0) { + /* no luck, use OPL3 instead */ + snd_opti9xx_write_mask(chip, OPTi9XX_MC_REG(2), + 0x00, 0x20); + } + } +#endif /* !OPTi93X */ + if (!opl3 && snd_opl3_create(card, + chip->fm_port, + chip->fm_port + 2, + OPL3_HW_AUTO, 0, &opl3) < 0) { + snd_printk("no OPL device at 0x%lx-0x%lx\n", + chip->fm_port, chip->fm_port + 4 - 1); + } + if (opl3) { + if ((error = snd_opl3_timer_new(opl3, +#ifdef CS4231 + 1, 2)) < 0) { +#else + 0, 1)) < 0) { +#endif /* CS4231 */ + snd_card_free(card); + return error; + } + if ((error = snd_opl3_hwdep_new(opl3, 0, 1, &synth)) < 0) { + snd_card_free(card); + return error; + } + } + } + + if ((error = snd_card_register(card))) { + snd_card_free(card); + return error; + } + snd_opti9xx_first_hit = 0; + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_opti9xx_legacy = card; + return 0; +} + +#ifdef CONFIG_PNP +static void __devexit snd_opti9xx_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); + snd_opti9xx_first_hit = 0; +} + +static struct pnp_card_driver opti9xx_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "opti9xx", + .id_table = snd_opti9xx_pnpids, + .probe = snd_card_opti9xx_probe, + .remove = __devexit_p(snd_opti9xx_pnp_remove), +}; +#endif + +static int __init alsa_card_opti9xx_init(void) +{ + int cards, error; + +#ifdef CONFIG_PNP + cards = pnp_register_card_driver(&opti9xx_pnpc_driver); +#else + cards = 0; +#endif + if (cards == 0 && (error = snd_card_opti9xx_probe(NULL, NULL)) < 0) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif +#ifdef MODULE +#ifdef OPTi93X + printk(KERN_ERR "no OPTi 82C93x soundcard found\n"); +#else + printk(KERN_ERR "no OPTi 82C92x soundcard found\n"); +#endif /* OPTi93X */ +#endif + return error; + } + return 0; +} + +static void __exit alsa_card_opti9xx_exit(void) +{ +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&opti9xx_pnpc_driver); +#endif + if (snd_opti9xx_legacy) + snd_card_free(snd_opti9xx_legacy); +} + +module_init(alsa_card_opti9xx_init) +module_exit(alsa_card_opti9xx_exit) diff --git a/sound/isa/opti9xx/opti92x-cs4231.c b/sound/isa/opti9xx/opti92x-cs4231.c new file mode 100644 index 00000000000..b17ab19f6d8 --- /dev/null +++ b/sound/isa/opti9xx/opti92x-cs4231.c @@ -0,0 +1,2 @@ +#define CS4231 +#include "opti92x-ad1848.c" diff --git a/sound/isa/opti9xx/opti93x.c b/sound/isa/opti9xx/opti93x.c new file mode 100644 index 00000000000..bad9da521bf --- /dev/null +++ b/sound/isa/opti9xx/opti93x.c @@ -0,0 +1,3 @@ +#define OPTi93X +#include "opti92x-ad1848.c" + diff --git a/sound/isa/sb/Makefile b/sound/isa/sb/Makefile new file mode 100644 index 00000000000..fd9d9c5726f --- /dev/null +++ b/sound/isa/sb/Makefile @@ -0,0 +1,39 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-sb-common-objs := sb_common.o sb_mixer.o +snd-sb8-dsp-objs := sb8_main.o sb8_midi.o +snd-sb16-dsp-objs := sb16_main.o +snd-sb16-csp-objs := sb16_csp.o +snd-sb8-objs := sb8.o +snd-sb16-objs := sb16.o +snd-sbawe-objs := sbawe.o emu8000.o +snd-emu8000-synth-objs := emu8000_synth.o emu8000_callback.o emu8000_patch.o emu8000_pcm.o +snd-es968-objs := es968.o + +# +# this function returns: +# "m" - CONFIG_SND_SEQUENCER is m +# <empty string> - CONFIG_SND_SEQUENCER is undefined +# otherwise parameter #1 value +# +sequencer = $(if $(subst y,,$(CONFIG_SND_SEQUENCER)),$(if $(1),m),$(if $(CONFIG_SND_SEQUENCER),$(1))) + +# Toplevel Module Dependency +obj-$(CONFIG_SND_ALS100) += snd-sb16-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_CMI8330) += snd-sb16-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_DT019X) += snd-sb16-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_SB8) += snd-sb8.o snd-sb8-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_SB16) += snd-sb16.o snd-sb16-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_SBAWE) += snd-sbawe.o snd-sb16-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_ES968) += snd-es968.o snd-sb8-dsp.o snd-sb-common.o +obj-$(CONFIG_SND_ALS4000) += snd-sb-common.o +ifeq ($(CONFIG_SND_SB16_CSP),y) + obj-$(CONFIG_SND_SB16) += snd-sb16-csp.o + obj-$(CONFIG_SND_SBAWE) += snd-sb16-csp.o +endif +obj-$(call sequencer,$(CONFIG_SND_SBAWE)) += snd-emu8000-synth.o + +obj-m := $(sort $(obj-m)) diff --git a/sound/isa/sb/emu8000.c b/sound/isa/sb/emu8000.c new file mode 100644 index 00000000000..028af406659 --- /dev/null +++ b/sound/isa/sb/emu8000.c @@ -0,0 +1,1170 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk> + * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de> + * + * Routines for control of EMU8000 chip + * + * 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/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/emu8000.h> +#include <sound/emu8000_reg.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <linux/init.h> +#include <sound/control.h> +#include <sound/initval.h> + +/* + * emu8000 register controls + */ + +/* + * The following routines read and write registers on the emu8000. They + * should always be called via the EMU8000*READ/WRITE macros and never + * directly. The macros handle the port number and command word. + */ +/* Write a word */ +void snd_emu8000_poke(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val) +{ + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + outw((unsigned short)val, port); /* Send data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); +} + +/* Read a word */ +unsigned short snd_emu8000_peek(emu8000_t *emu, unsigned int port, unsigned int reg) +{ + unsigned short res; + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + res = inw(port); /* Read data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); + return res; +} + +/* Write a double word */ +void snd_emu8000_poke_dw(emu8000_t *emu, unsigned int port, unsigned int reg, unsigned int val) +{ + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + outw((unsigned short)val, port); /* Send low word of data */ + outw((unsigned short)(val>>16), port+2); /* Send high word of data */ + spin_unlock_irqrestore(&emu->reg_lock, flags); +} + +/* Read a double word */ +unsigned int snd_emu8000_peek_dw(emu8000_t *emu, unsigned int port, unsigned int reg) +{ + unsigned short low; + unsigned int res; + unsigned long flags; + spin_lock_irqsave(&emu->reg_lock, flags); + if (reg != emu->last_reg) { + outw((unsigned short)reg, EMU8000_PTR(emu)); /* Set register */ + emu->last_reg = reg; + } + low = inw(port); /* Read low word of data */ + res = low + (inw(port+2) << 16); + spin_unlock_irqrestore(&emu->reg_lock, flags); + return res; +} + +/* + * Set up / close a channel to be used for DMA. + */ +/*exported*/ void +snd_emu8000_dma_chan(emu8000_t *emu, int ch, int mode) +{ + unsigned right_bit = (mode & EMU8000_RAM_RIGHT) ? 0x01000000 : 0; + mode &= EMU8000_RAM_MODE_MASK; + if (mode == EMU8000_RAM_CLOSE) { + EMU8000_CCCA_WRITE(emu, ch, 0); + EMU8000_DCYSUSV_WRITE(emu, ch, 0x807F); + return; + } + EMU8000_DCYSUSV_WRITE(emu, ch, 0x80); + EMU8000_VTFT_WRITE(emu, ch, 0); + EMU8000_CVCF_WRITE(emu, ch, 0); + EMU8000_PTRX_WRITE(emu, ch, 0x40000000); + EMU8000_CPF_WRITE(emu, ch, 0x40000000); + EMU8000_PSST_WRITE(emu, ch, 0); + EMU8000_CSL_WRITE(emu, ch, 0); + if (mode == EMU8000_RAM_WRITE) /* DMA write */ + EMU8000_CCCA_WRITE(emu, ch, 0x06000000 | right_bit); + else /* DMA read */ + EMU8000_CCCA_WRITE(emu, ch, 0x04000000 | right_bit); +} + +/* + */ +static void __init +snd_emu8000_read_wait(emu8000_t *emu) +{ + while ((EMU8000_SMALR_READ(emu) & 0x80000000) != 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + } +} + +/* + */ +static void __init +snd_emu8000_write_wait(emu8000_t *emu) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + } +} + +/* + * detect a card at the given port + */ +static int __init +snd_emu8000_detect(emu8000_t *emu) +{ + /* Initialise */ + EMU8000_HWCF1_WRITE(emu, 0x0059); + EMU8000_HWCF2_WRITE(emu, 0x0020); + EMU8000_HWCF3_WRITE(emu, 0x0000); + /* Check for a recognisable emu8000 */ + /* + if ((EMU8000_U1_READ(emu) & 0x000f) != 0x000c) + return -ENODEV; + */ + if ((EMU8000_HWCF1_READ(emu) & 0x007e) != 0x0058) + return -ENODEV; + if ((EMU8000_HWCF2_READ(emu) & 0x0003) != 0x0003) + return -ENODEV; + + snd_printdd("EMU8000 [0x%lx]: Synth chip found\n", + emu->port1); + return 0; +} + + +/* + * intiailize audio channels + */ +static void __init +init_audio(emu8000_t *emu) +{ + int ch; + + /* turn off envelope engines */ + for (ch = 0; ch < EMU8000_CHANNELS; ch++) + EMU8000_DCYSUSV_WRITE(emu, ch, 0x80); + + /* reset all other parameters to zero */ + for (ch = 0; ch < EMU8000_CHANNELS; ch++) { + EMU8000_ENVVOL_WRITE(emu, ch, 0); + EMU8000_ENVVAL_WRITE(emu, ch, 0); + EMU8000_DCYSUS_WRITE(emu, ch, 0); + EMU8000_ATKHLDV_WRITE(emu, ch, 0); + EMU8000_LFO1VAL_WRITE(emu, ch, 0); + EMU8000_ATKHLD_WRITE(emu, ch, 0); + EMU8000_LFO2VAL_WRITE(emu, ch, 0); + EMU8000_IP_WRITE(emu, ch, 0); + EMU8000_IFATN_WRITE(emu, ch, 0); + EMU8000_PEFE_WRITE(emu, ch, 0); + EMU8000_FMMOD_WRITE(emu, ch, 0); + EMU8000_TREMFRQ_WRITE(emu, ch, 0); + EMU8000_FM2FRQ2_WRITE(emu, ch, 0); + EMU8000_PTRX_WRITE(emu, ch, 0); + EMU8000_VTFT_WRITE(emu, ch, 0); + EMU8000_PSST_WRITE(emu, ch, 0); + EMU8000_CSL_WRITE(emu, ch, 0); + EMU8000_CCCA_WRITE(emu, ch, 0); + } + + for (ch = 0; ch < EMU8000_CHANNELS; ch++) { + EMU8000_CPF_WRITE(emu, ch, 0); + EMU8000_CVCF_WRITE(emu, ch, 0); + } +} + + +/* + * initialize DMA address + */ +static void __init +init_dma(emu8000_t *emu) +{ + EMU8000_SMALR_WRITE(emu, 0); + EMU8000_SMARR_WRITE(emu, 0); + EMU8000_SMALW_WRITE(emu, 0); + EMU8000_SMARW_WRITE(emu, 0); +} + +/* + * initialization arrays; from ADIP + */ +static unsigned short init1[128] /*__devinitdata*/ = { + 0x03ff, 0x0030, 0x07ff, 0x0130, 0x0bff, 0x0230, 0x0fff, 0x0330, + 0x13ff, 0x0430, 0x17ff, 0x0530, 0x1bff, 0x0630, 0x1fff, 0x0730, + 0x23ff, 0x0830, 0x27ff, 0x0930, 0x2bff, 0x0a30, 0x2fff, 0x0b30, + 0x33ff, 0x0c30, 0x37ff, 0x0d30, 0x3bff, 0x0e30, 0x3fff, 0x0f30, + + 0x43ff, 0x0030, 0x47ff, 0x0130, 0x4bff, 0x0230, 0x4fff, 0x0330, + 0x53ff, 0x0430, 0x57ff, 0x0530, 0x5bff, 0x0630, 0x5fff, 0x0730, + 0x63ff, 0x0830, 0x67ff, 0x0930, 0x6bff, 0x0a30, 0x6fff, 0x0b30, + 0x73ff, 0x0c30, 0x77ff, 0x0d30, 0x7bff, 0x0e30, 0x7fff, 0x0f30, + + 0x83ff, 0x0030, 0x87ff, 0x0130, 0x8bff, 0x0230, 0x8fff, 0x0330, + 0x93ff, 0x0430, 0x97ff, 0x0530, 0x9bff, 0x0630, 0x9fff, 0x0730, + 0xa3ff, 0x0830, 0xa7ff, 0x0930, 0xabff, 0x0a30, 0xafff, 0x0b30, + 0xb3ff, 0x0c30, 0xb7ff, 0x0d30, 0xbbff, 0x0e30, 0xbfff, 0x0f30, + + 0xc3ff, 0x0030, 0xc7ff, 0x0130, 0xcbff, 0x0230, 0xcfff, 0x0330, + 0xd3ff, 0x0430, 0xd7ff, 0x0530, 0xdbff, 0x0630, 0xdfff, 0x0730, + 0xe3ff, 0x0830, 0xe7ff, 0x0930, 0xebff, 0x0a30, 0xefff, 0x0b30, + 0xf3ff, 0x0c30, 0xf7ff, 0x0d30, 0xfbff, 0x0e30, 0xffff, 0x0f30, +}; + +static unsigned short init2[128] /*__devinitdata*/ = { + 0x03ff, 0x8030, 0x07ff, 0x8130, 0x0bff, 0x8230, 0x0fff, 0x8330, + 0x13ff, 0x8430, 0x17ff, 0x8530, 0x1bff, 0x8630, 0x1fff, 0x8730, + 0x23ff, 0x8830, 0x27ff, 0x8930, 0x2bff, 0x8a30, 0x2fff, 0x8b30, + 0x33ff, 0x8c30, 0x37ff, 0x8d30, 0x3bff, 0x8e30, 0x3fff, 0x8f30, + + 0x43ff, 0x8030, 0x47ff, 0x8130, 0x4bff, 0x8230, 0x4fff, 0x8330, + 0x53ff, 0x8430, 0x57ff, 0x8530, 0x5bff, 0x8630, 0x5fff, 0x8730, + 0x63ff, 0x8830, 0x67ff, 0x8930, 0x6bff, 0x8a30, 0x6fff, 0x8b30, + 0x73ff, 0x8c30, 0x77ff, 0x8d30, 0x7bff, 0x8e30, 0x7fff, 0x8f30, + + 0x83ff, 0x8030, 0x87ff, 0x8130, 0x8bff, 0x8230, 0x8fff, 0x8330, + 0x93ff, 0x8430, 0x97ff, 0x8530, 0x9bff, 0x8630, 0x9fff, 0x8730, + 0xa3ff, 0x8830, 0xa7ff, 0x8930, 0xabff, 0x8a30, 0xafff, 0x8b30, + 0xb3ff, 0x8c30, 0xb7ff, 0x8d30, 0xbbff, 0x8e30, 0xbfff, 0x8f30, + + 0xc3ff, 0x8030, 0xc7ff, 0x8130, 0xcbff, 0x8230, 0xcfff, 0x8330, + 0xd3ff, 0x8430, 0xd7ff, 0x8530, 0xdbff, 0x8630, 0xdfff, 0x8730, + 0xe3ff, 0x8830, 0xe7ff, 0x8930, 0xebff, 0x8a30, 0xefff, 0x8b30, + 0xf3ff, 0x8c30, 0xf7ff, 0x8d30, 0xfbff, 0x8e30, 0xffff, 0x8f30, +}; + +static unsigned short init3[128] /*__devinitdata*/ = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x8F7C, 0x167E, 0xF254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x8BAA, 0x1B6D, 0xF234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x86E7, 0x229E, 0xF224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x87F6, 0x2C28, 0xF254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x8F02, 0x1341, 0xF264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x8FA9, 0x3EB5, 0xF294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0xC4C3, 0x3EBB, 0xC5C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x8671, 0x14FD, 0x8287, + 0x3EBC, 0xE610, 0x3EC8, 0x8C7B, 0x031A, 0x87E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x821F, 0x3ECA, 0x8386, + 0x3EC1, 0x8C03, 0x3EC9, 0x831E, 0x3ECA, 0x8C4C, 0x3EBF, 0x8C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x8EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x8219, 0x3ECB, 0xD26E, 0x3EC5, 0x831F, + 0x3EC6, 0xC308, 0x3EC3, 0xB2FF, 0x3EC9, 0x8265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0xB3FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +static unsigned short init4[128] /*__devinitdata*/ = { + 0x0C10, 0x8470, 0x14FE, 0xB488, 0x167F, 0xA470, 0x18E7, 0x84B5, + 0x1B6E, 0x842A, 0x1F1D, 0x852A, 0x0DA3, 0x0F7C, 0x167E, 0x7254, + 0x0000, 0x842A, 0x0001, 0x852A, 0x18E6, 0x0BAA, 0x1B6D, 0x7234, + 0x229F, 0x8429, 0x2746, 0x8529, 0x1F1C, 0x06E7, 0x229E, 0x7224, + + 0x0DA4, 0x8429, 0x2C29, 0x8529, 0x2745, 0x07F6, 0x2C28, 0x7254, + 0x383B, 0x8428, 0x320F, 0x8528, 0x320E, 0x0F02, 0x1341, 0x7264, + 0x3EB6, 0x8428, 0x3EB9, 0x8528, 0x383A, 0x0FA9, 0x3EB5, 0x7294, + 0x3EB7, 0x8474, 0x3EBA, 0x8575, 0x3EB8, 0x44C3, 0x3EBB, 0x45C3, + + 0x0000, 0xA404, 0x0001, 0xA504, 0x141F, 0x0671, 0x14FD, 0x0287, + 0x3EBC, 0xE610, 0x3EC8, 0x0C7B, 0x031A, 0x07E6, 0x3EC8, 0x86F7, + 0x3EC0, 0x821E, 0x3EBE, 0xD208, 0x3EBD, 0x021F, 0x3ECA, 0x0386, + 0x3EC1, 0x0C03, 0x3EC9, 0x031E, 0x3ECA, 0x8C4C, 0x3EBF, 0x0C55, + + 0x3EC9, 0xC208, 0x3EC4, 0xBC84, 0x3EC8, 0x0EAD, 0x3EC8, 0xD308, + 0x3EC2, 0x8F7E, 0x3ECB, 0x0219, 0x3ECB, 0xD26E, 0x3EC5, 0x031F, + 0x3EC6, 0xC308, 0x3EC3, 0x32FF, 0x3EC9, 0x0265, 0x3EC9, 0x8319, + 0x1342, 0xD36E, 0x3EC7, 0x33FF, 0x0000, 0x8365, 0x1420, 0x9570, +}; + +/* send an initialization array + * Taken from the oss driver, not obvious from the doc how this + * is meant to work + */ +static void __init +send_array(emu8000_t *emu, unsigned short *data, int size) +{ + int i; + unsigned short *p; + + p = data; + for (i = 0; i < size; i++, p++) + EMU8000_INIT1_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT2_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT3_WRITE(emu, i, *p); + for (i = 0; i < size; i++, p++) + EMU8000_INIT4_WRITE(emu, i, *p); +} + + +/* + * Send initialization arrays to start up, this just follows the + * initialisation sequence in the adip. + */ +static void __init +init_arrays(emu8000_t *emu) +{ + send_array(emu, init1, ARRAY_SIZE(init1)/4); + + msleep((1024 * 1000) / 44100); /* wait for 1024 clocks */ + send_array(emu, init2, ARRAY_SIZE(init2)/4); + send_array(emu, init3, ARRAY_SIZE(init3)/4); + + EMU8000_HWCF4_WRITE(emu, 0); + EMU8000_HWCF5_WRITE(emu, 0x83); + EMU8000_HWCF6_WRITE(emu, 0x8000); + + send_array(emu, init4, ARRAY_SIZE(init4)/4); +} + + +#define UNIQUE_ID1 0xa5b9 +#define UNIQUE_ID2 0x9d53 + +/* + * Size the onboard memory. + * This is written so as not to need arbitary delays after the write. It + * seems that the only way to do this is to use the one channel and keep + * reallocating between read and write. + */ +static void __init +size_dram(emu8000_t *emu) +{ + int i, size; + + if (emu->dram_checked) + return; + + size = 0; + + /* write out a magic number */ + snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE); + snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_READ); + EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET); + EMU8000_SMLD_WRITE(emu, UNIQUE_ID1); + snd_emu8000_init_fm(emu); /* This must really be here and not 2 lines back even */ + + while (size < EMU8000_MAX_DRAM) { + + size += 512 * 1024; /* increment 512kbytes */ + + /* Write a unique data on the test address. + * if the address is out of range, the data is written on + * 0x200000(=EMU8000_DRAM_OFFSET). Then the id word is + * changed by this data. + */ + /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_WRITE);*/ + EMU8000_SMALW_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1)); + EMU8000_SMLD_WRITE(emu, UNIQUE_ID2); + snd_emu8000_write_wait(emu); + + /* + * read the data on the just written DRAM address + * if not the same then we have reached the end of ram. + */ + /*snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_READ);*/ + EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET + (size>>1)); + /*snd_emu8000_read_wait(emu);*/ + EMU8000_SMLD_READ(emu); /* discard stale data */ + if (EMU8000_SMLD_READ(emu) != UNIQUE_ID2) + break; /* we must have wrapped around */ + + snd_emu8000_read_wait(emu); + + /* + * If it is the same it could be that the address just + * wraps back to the beginning; so check to see if the + * initial value has been overwritten. + */ + EMU8000_SMALR_WRITE(emu, EMU8000_DRAM_OFFSET); + EMU8000_SMLD_READ(emu); /* discard stale data */ + if (EMU8000_SMLD_READ(emu) != UNIQUE_ID1) + break; /* we must have wrapped around */ + snd_emu8000_read_wait(emu); + } + + /* wait until FULL bit in SMAxW register is false */ + for (i = 0; i < 10000; i++) { + if ((EMU8000_SMALW_READ(emu) & 0x80000000) == 0) + break; + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + } + snd_emu8000_dma_chan(emu, 0, EMU8000_RAM_CLOSE); + snd_emu8000_dma_chan(emu, 1, EMU8000_RAM_CLOSE); + + snd_printdd("EMU8000 [0x%lx]: %d Kb on-board memory detected\n", + emu->port1, size/1024); + + emu->mem_size = size; + emu->dram_checked = 1; +} + + +/* + * Initiailise the FM section. You have to do this to use sample RAM + * and therefore lose 2 voices. + */ +/*exported*/ void +snd_emu8000_init_fm(emu8000_t *emu) +{ + unsigned long flags; + + /* Initialize the last two channels for DRAM refresh and producing + the reverb and chorus effects for Yamaha OPL-3 synthesizer */ + + /* 31: FM left channel, 0xffffe0-0xffffe8 */ + EMU8000_DCYSUSV_WRITE(emu, 30, 0x80); + EMU8000_PSST_WRITE(emu, 30, 0xFFFFFFE0); /* full left */ + EMU8000_CSL_WRITE(emu, 30, 0x00FFFFE8 | (emu->fm_chorus_depth << 24)); + EMU8000_PTRX_WRITE(emu, 30, (emu->fm_reverb_depth << 8)); + EMU8000_CPF_WRITE(emu, 30, 0); + EMU8000_CCCA_WRITE(emu, 30, 0x00FFFFE3); + + /* 32: FM right channel, 0xfffff0-0xfffff8 */ + EMU8000_DCYSUSV_WRITE(emu, 31, 0x80); + EMU8000_PSST_WRITE(emu, 31, 0x00FFFFF0); /* full right */ + EMU8000_CSL_WRITE(emu, 31, 0x00FFFFF8 | (emu->fm_chorus_depth << 24)); + EMU8000_PTRX_WRITE(emu, 31, (emu->fm_reverb_depth << 8)); + EMU8000_CPF_WRITE(emu, 31, 0x8000); + EMU8000_CCCA_WRITE(emu, 31, 0x00FFFFF3); + + snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0); + + spin_lock_irqsave(&emu->reg_lock, flags); + while (!(inw(EMU8000_PTR(emu)) & 0x1000)) + ; + while ((inw(EMU8000_PTR(emu)) & 0x1000)) + ; + spin_unlock_irqrestore(&emu->reg_lock, flags); + snd_emu8000_poke((emu), EMU8000_DATA0(emu), EMU8000_CMD(1, (30)), 0x4828); + /* this is really odd part.. */ + outb(0x3C, EMU8000_PTR(emu)); + outb(0, EMU8000_DATA1(emu)); + + /* skew volume & cutoff */ + EMU8000_VTFT_WRITE(emu, 30, 0x8000FFFF); + EMU8000_VTFT_WRITE(emu, 31, 0x8000FFFF); +} + + +/* + * The main initialization routine. + */ +static void __init +snd_emu8000_init_hw(emu8000_t *emu) +{ + int i; + + emu->last_reg = 0xffff; /* reset the last register index */ + + /* initialize hardware configuration */ + EMU8000_HWCF1_WRITE(emu, 0x0059); + EMU8000_HWCF2_WRITE(emu, 0x0020); + + /* disable audio; this seems to reduce a clicking noise a bit.. */ + EMU8000_HWCF3_WRITE(emu, 0); + + /* initialize audio channels */ + init_audio(emu); + + /* initialize DMA */ + init_dma(emu); + + /* initialize init arrays */ + init_arrays(emu); + + /* + * Initialize the FM section of the AWE32, this is needed + * for DRAM refresh as well + */ + snd_emu8000_init_fm(emu); + + /* terminate all voices */ + for (i = 0; i < EMU8000_DRAM_VOICES; i++) + EMU8000_DCYSUSV_WRITE(emu, 0, 0x807F); + + /* check DRAM memory size */ + size_dram(emu); + + /* enable audio */ + EMU8000_HWCF3_WRITE(emu, 0x4); + + /* set equzlier, chorus and reverb modes */ + snd_emu8000_update_equalizer(emu); + snd_emu8000_update_chorus_mode(emu); + snd_emu8000_update_reverb_mode(emu); +} + + +/*---------------------------------------------------------------- + * Bass/Treble Equalizer + *----------------------------------------------------------------*/ + +static unsigned short bass_parm[12][3] = { + {0xD26A, 0xD36A, 0x0000}, /* -12 dB */ + {0xD25B, 0xD35B, 0x0000}, /* -8 */ + {0xD24C, 0xD34C, 0x0000}, /* -6 */ + {0xD23D, 0xD33D, 0x0000}, /* -4 */ + {0xD21F, 0xD31F, 0x0000}, /* -2 */ + {0xC208, 0xC308, 0x0001}, /* 0 (HW default) */ + {0xC219, 0xC319, 0x0001}, /* +2 */ + {0xC22A, 0xC32A, 0x0001}, /* +4 */ + {0xC24C, 0xC34C, 0x0001}, /* +6 */ + {0xC26E, 0xC36E, 0x0001}, /* +8 */ + {0xC248, 0xC384, 0x0002}, /* +10 */ + {0xC26A, 0xC36A, 0x0002}, /* +12 dB */ +}; + +static unsigned short treble_parm[12][9] = { + {0x821E, 0xC26A, 0x031E, 0xC36A, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, /* -12 dB */ + {0x821E, 0xC25B, 0x031E, 0xC35B, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC24C, 0x031E, 0xC34C, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC23D, 0x031E, 0xC33D, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xC21F, 0x031E, 0xC31F, 0x021E, 0xD208, 0x831E, 0xD308, 0x0001}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021E, 0xD208, 0x831E, 0xD308, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021D, 0xD219, 0x831D, 0xD319, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021C, 0xD22A, 0x831C, 0xD32A, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x021A, 0xD24C, 0x831A, 0xD34C, 0x0002}, + {0x821E, 0xD208, 0x031E, 0xD308, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, /* +8 (HW default) */ + {0x821D, 0xD219, 0x031D, 0xD319, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002}, + {0x821C, 0xD22A, 0x031C, 0xD32A, 0x0219, 0xD26E, 0x8319, 0xD36E, 0x0002} /* +12 dB */ +}; + + +/* + * set Emu8000 digital equalizer; from 0 to 11 [-12dB - 12dB] + */ +/*exported*/ void +snd_emu8000_update_equalizer(emu8000_t *emu) +{ + unsigned short w; + int bass = emu->bass_level; + int treble = emu->treble_level; + + if (bass < 0 || bass > 11 || treble < 0 || treble > 11) + return; + EMU8000_INIT4_WRITE(emu, 0x01, bass_parm[bass][0]); + EMU8000_INIT4_WRITE(emu, 0x11, bass_parm[bass][1]); + EMU8000_INIT3_WRITE(emu, 0x11, treble_parm[treble][0]); + EMU8000_INIT3_WRITE(emu, 0x13, treble_parm[treble][1]); + EMU8000_INIT3_WRITE(emu, 0x1b, treble_parm[treble][2]); + EMU8000_INIT4_WRITE(emu, 0x07, treble_parm[treble][3]); + EMU8000_INIT4_WRITE(emu, 0x0b, treble_parm[treble][4]); + EMU8000_INIT4_WRITE(emu, 0x0d, treble_parm[treble][5]); + EMU8000_INIT4_WRITE(emu, 0x17, treble_parm[treble][6]); + EMU8000_INIT4_WRITE(emu, 0x19, treble_parm[treble][7]); + w = bass_parm[bass][2] + treble_parm[treble][8]; + EMU8000_INIT4_WRITE(emu, 0x15, (unsigned short)(w + 0x0262)); + EMU8000_INIT4_WRITE(emu, 0x1d, (unsigned short)(w + 0x8362)); +} + + +/*---------------------------------------------------------------- + * Chorus mode control + *----------------------------------------------------------------*/ + +/* + * chorus mode parameters + */ +#define SNDRV_EMU8000_CHORUS_1 0 +#define SNDRV_EMU8000_CHORUS_2 1 +#define SNDRV_EMU8000_CHORUS_3 2 +#define SNDRV_EMU8000_CHORUS_4 3 +#define SNDRV_EMU8000_CHORUS_FEEDBACK 4 +#define SNDRV_EMU8000_CHORUS_FLANGER 5 +#define SNDRV_EMU8000_CHORUS_SHORTDELAY 6 +#define SNDRV_EMU8000_CHORUS_SHORTDELAY2 7 +#define SNDRV_EMU8000_CHORUS_PREDEFINED 8 +/* user can define chorus modes up to 32 */ +#define SNDRV_EMU8000_CHORUS_NUMBERS 32 + +typedef struct soundfont_chorus_fx_t { + unsigned short feedback; /* feedback level (0xE600-0xE6FF) */ + unsigned short delay_offset; /* delay (0-0x0DA3) [1/44100 sec] */ + unsigned short lfo_depth; /* LFO depth (0xBC00-0xBCFF) */ + unsigned int delay; /* right delay (0-0xFFFFFFFF) [1/256/44100 sec] */ + unsigned int lfo_freq; /* LFO freq LFO freq (0-0xFFFFFFFF) */ +} soundfont_chorus_fx_t; + +/* 5 parameters for each chorus mode; 3 x 16bit, 2 x 32bit */ +static char chorus_defined[SNDRV_EMU8000_CHORUS_NUMBERS]; +static soundfont_chorus_fx_t chorus_parm[SNDRV_EMU8000_CHORUS_NUMBERS] = { + {0xE600, 0x03F6, 0xBC2C ,0x00000000, 0x0000006D}, /* chorus 1 */ + {0xE608, 0x031A, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 2 */ + {0xE610, 0x031A, 0xBC84, 0x00000000, 0x00000083}, /* chorus 3 */ + {0xE620, 0x0269, 0xBC6E, 0x00000000, 0x0000017C}, /* chorus 4 */ + {0xE680, 0x04D3, 0xBCA6, 0x00000000, 0x0000005B}, /* feedback */ + {0xE6E0, 0x044E, 0xBC37, 0x00000000, 0x00000026}, /* flanger */ + {0xE600, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay */ + {0xE6C0, 0x0B06, 0xBC00, 0x0006E000, 0x00000083}, /* short delay + feedback */ +}; + +/*exported*/ int +snd_emu8000_load_chorus_fx(emu8000_t *emu, int mode, const void __user *buf, long len) +{ + soundfont_chorus_fx_t rec; + if (mode < SNDRV_EMU8000_CHORUS_PREDEFINED || mode >= SNDRV_EMU8000_CHORUS_NUMBERS) { + snd_printk(KERN_WARNING "invalid chorus mode %d for uploading\n", mode); + return -EINVAL; + } + if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec))) + return -EFAULT; + chorus_parm[mode] = rec; + chorus_defined[mode] = 1; + return 0; +} + +/*exported*/ void +snd_emu8000_update_chorus_mode(emu8000_t *emu) +{ + int effect = emu->chorus_mode; + if (effect < 0 || effect >= SNDRV_EMU8000_CHORUS_NUMBERS || + (effect >= SNDRV_EMU8000_CHORUS_PREDEFINED && !chorus_defined[effect])) + return; + EMU8000_INIT3_WRITE(emu, 0x09, chorus_parm[effect].feedback); + EMU8000_INIT3_WRITE(emu, 0x0c, chorus_parm[effect].delay_offset); + EMU8000_INIT4_WRITE(emu, 0x03, chorus_parm[effect].lfo_depth); + EMU8000_HWCF4_WRITE(emu, chorus_parm[effect].delay); + EMU8000_HWCF5_WRITE(emu, chorus_parm[effect].lfo_freq); + EMU8000_HWCF6_WRITE(emu, 0x8000); + EMU8000_HWCF7_WRITE(emu, 0x0000); +} + +/*---------------------------------------------------------------- + * Reverb mode control + *----------------------------------------------------------------*/ + +/* + * reverb mode parameters + */ +#define SNDRV_EMU8000_REVERB_ROOM1 0 +#define SNDRV_EMU8000_REVERB_ROOM2 1 +#define SNDRV_EMU8000_REVERB_ROOM3 2 +#define SNDRV_EMU8000_REVERB_HALL1 3 +#define SNDRV_EMU8000_REVERB_HALL2 4 +#define SNDRV_EMU8000_REVERB_PLATE 5 +#define SNDRV_EMU8000_REVERB_DELAY 6 +#define SNDRV_EMU8000_REVERB_PANNINGDELAY 7 +#define SNDRV_EMU8000_REVERB_PREDEFINED 8 +/* user can define reverb modes up to 32 */ +#define SNDRV_EMU8000_REVERB_NUMBERS 32 + +typedef struct soundfont_reverb_fx_t { + unsigned short parms[28]; +} soundfont_reverb_fx_t; + +/* reverb mode settings; write the following 28 data of 16 bit length + * on the corresponding ports in the reverb_cmds array + */ +static char reverb_defined[SNDRV_EMU8000_CHORUS_NUMBERS]; +static soundfont_reverb_fx_t reverb_parm[SNDRV_EMU8000_REVERB_NUMBERS] = { +{{ /* room 1 */ + 0xB488, 0xA450, 0x9550, 0x84B5, 0x383A, 0x3EB5, 0x72F4, + 0x72A4, 0x7254, 0x7204, 0x7204, 0x7204, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 2 */ + 0xB488, 0xA458, 0x9558, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* room 3 */ + 0xB488, 0xA460, 0x9560, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4416, 0x4516, + 0xA490, 0xA590, 0x842C, 0x852C, 0x842C, 0x852C, 0x842B, + 0x852B, 0x842B, 0x852B, 0x842A, 0x852A, 0x842A, 0x852A, +}}, +{{ /* hall 1 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7284, + 0x7254, 0x7224, 0x7224, 0x7254, 0x7284, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842B, 0x852B, 0x842B, 0x852B, 0x842A, + 0x852A, 0x842A, 0x852A, 0x8429, 0x8529, 0x8429, 0x8529, +}}, +{{ /* hall 2 */ + 0xB488, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7254, + 0x7234, 0x7224, 0x7254, 0x7264, 0x7294, 0x44C3, 0x45C3, + 0xA404, 0xA504, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* plate */ + 0xB4FF, 0xA470, 0x9570, 0x84B5, 0x383A, 0x3EB5, 0x7234, + 0x7234, 0x7234, 0x7234, 0x7234, 0x7234, 0x4448, 0x4548, + 0xA440, 0xA540, 0x842A, 0x852A, 0x842A, 0x852A, 0x8429, + 0x8529, 0x8429, 0x8529, 0x8428, 0x8528, 0x8428, 0x8528, +}}, +{{ /* delay */ + 0xB4FF, 0xA470, 0x9500, 0x84B5, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +{{ /* panning delay */ + 0xB4FF, 0xA490, 0x9590, 0x8474, 0x333A, 0x39B5, 0x7204, + 0x7204, 0x7204, 0x7204, 0x7204, 0x72F4, 0x4400, 0x4500, + 0xA4FF, 0xA5FF, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, + 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, 0x8420, 0x8520, +}}, +}; + +enum { DATA1, DATA2 }; +#define AWE_INIT1(c) EMU8000_CMD(2,c), DATA1 +#define AWE_INIT2(c) EMU8000_CMD(2,c), DATA2 +#define AWE_INIT3(c) EMU8000_CMD(3,c), DATA1 +#define AWE_INIT4(c) EMU8000_CMD(3,c), DATA2 + +static struct reverb_cmd_pair { + unsigned short cmd, port; +} reverb_cmds[28] = { + {AWE_INIT1(0x03)}, {AWE_INIT1(0x05)}, {AWE_INIT4(0x1F)}, {AWE_INIT1(0x07)}, + {AWE_INIT2(0x14)}, {AWE_INIT2(0x16)}, {AWE_INIT1(0x0F)}, {AWE_INIT1(0x17)}, + {AWE_INIT1(0x1F)}, {AWE_INIT2(0x07)}, {AWE_INIT2(0x0F)}, {AWE_INIT2(0x17)}, + {AWE_INIT2(0x1D)}, {AWE_INIT2(0x1F)}, {AWE_INIT3(0x01)}, {AWE_INIT3(0x03)}, + {AWE_INIT1(0x09)}, {AWE_INIT1(0x0B)}, {AWE_INIT1(0x11)}, {AWE_INIT1(0x13)}, + {AWE_INIT1(0x19)}, {AWE_INIT1(0x1B)}, {AWE_INIT2(0x01)}, {AWE_INIT2(0x03)}, + {AWE_INIT2(0x09)}, {AWE_INIT2(0x0B)}, {AWE_INIT2(0x11)}, {AWE_INIT2(0x13)}, +}; + +/*exported*/ int +snd_emu8000_load_reverb_fx(emu8000_t *emu, int mode, const void __user *buf, long len) +{ + soundfont_reverb_fx_t rec; + + if (mode < SNDRV_EMU8000_REVERB_PREDEFINED || mode >= SNDRV_EMU8000_REVERB_NUMBERS) { + snd_printk(KERN_WARNING "invalid reverb mode %d for uploading\n", mode); + return -EINVAL; + } + if (len < (long)sizeof(rec) || copy_from_user(&rec, buf, sizeof(rec))) + return -EFAULT; + reverb_parm[mode] = rec; + reverb_defined[mode] = 1; + return 0; +} + +/*exported*/ void +snd_emu8000_update_reverb_mode(emu8000_t *emu) +{ + int effect = emu->reverb_mode; + int i; + + if (effect < 0 || effect >= SNDRV_EMU8000_REVERB_NUMBERS || + (effect >= SNDRV_EMU8000_REVERB_PREDEFINED && !reverb_defined[effect])) + return; + for (i = 0; i < 28; i++) { + int port; + if (reverb_cmds[i].port == DATA1) + port = EMU8000_DATA1(emu); + else + port = EMU8000_DATA2(emu); + snd_emu8000_poke(emu, port, reverb_cmds[i].cmd, reverb_parm[effect].parms[i]); + } +} + + +/*---------------------------------------------------------------- + * mixer interface + *----------------------------------------------------------------*/ + +/* + * bass/treble + */ +static int mixer_bass_treble_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 11; + return 0; +} + +static int mixer_bass_treble_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->treble_level : emu->bass_level; + return 0; +} + +static int mixer_bass_treble_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + val1 = ucontrol->value.integer.value[0] % 12; + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + change = val1 != emu->treble_level; + emu->treble_level = val1; + } else { + change = val1 != emu->bass_level; + emu->bass_level = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + snd_emu8000_update_equalizer(emu); + return change; +} + +static snd_kcontrol_new_t mixer_bass_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Synth Tone Control - Bass", + .info = mixer_bass_treble_info, + .get = mixer_bass_treble_get, + .put = mixer_bass_treble_put, + .private_value = 0, +}; + +static snd_kcontrol_new_t mixer_treble_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Synth Tone Control - Treble", + .info = mixer_bass_treble_info, + .get = mixer_bass_treble_get, + .put = mixer_bass_treble_put, + .private_value = 1, +}; + +/* + * chorus/reverb mode + */ +static int mixer_chorus_reverb_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = kcontrol->private_value ? (SNDRV_EMU8000_CHORUS_NUMBERS-1) : (SNDRV_EMU8000_REVERB_NUMBERS-1); + return 0; +} + +static int mixer_chorus_reverb_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->chorus_mode : emu->reverb_mode; + return 0; +} + +static int mixer_chorus_reverb_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_CHORUS_NUMBERS; + change = val1 != emu->chorus_mode; + emu->chorus_mode = val1; + } else { + val1 = ucontrol->value.integer.value[0] % SNDRV_EMU8000_REVERB_NUMBERS; + change = val1 != emu->reverb_mode; + emu->reverb_mode = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + if (change) { + if (kcontrol->private_value) + snd_emu8000_update_chorus_mode(emu); + else + snd_emu8000_update_reverb_mode(emu); + } + return change; +} + +static snd_kcontrol_new_t mixer_chorus_mode_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Chorus Mode", + .info = mixer_chorus_reverb_info, + .get = mixer_chorus_reverb_get, + .put = mixer_chorus_reverb_put, + .private_value = 1, +}; + +static snd_kcontrol_new_t mixer_reverb_mode_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Reverb Mode", + .info = mixer_chorus_reverb_info, + .get = mixer_chorus_reverb_get, + .put = mixer_chorus_reverb_put, + .private_value = 0, +}; + +/* + * FM OPL3 chorus/reverb depth + */ +static int mixer_fm_depth_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 255; + return 0; +} + +static int mixer_fm_depth_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = kcontrol->private_value ? emu->fm_chorus_depth : emu->fm_reverb_depth; + return 0; +} + +static int mixer_fm_depth_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + emu8000_t *emu = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned short val1; + + val1 = ucontrol->value.integer.value[0] % 256; + spin_lock_irqsave(&emu->control_lock, flags); + if (kcontrol->private_value) { + change = val1 != emu->fm_chorus_depth; + emu->fm_chorus_depth = val1; + } else { + change = val1 != emu->fm_reverb_depth; + emu->fm_reverb_depth = val1; + } + spin_unlock_irqrestore(&emu->control_lock, flags); + if (change) + snd_emu8000_init_fm(emu); + return change; +} + +static snd_kcontrol_new_t mixer_fm_chorus_depth_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FM Chorus Depth", + .info = mixer_fm_depth_info, + .get = mixer_fm_depth_get, + .put = mixer_fm_depth_put, + .private_value = 1, +}; + +static snd_kcontrol_new_t mixer_fm_reverb_depth_control = +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "FM Reverb Depth", + .info = mixer_fm_depth_info, + .get = mixer_fm_depth_get, + .put = mixer_fm_depth_put, + .private_value = 0, +}; + + +static snd_kcontrol_new_t *mixer_defs[EMU8000_NUM_CONTROLS] = { + &mixer_bass_control, + &mixer_treble_control, + &mixer_chorus_mode_control, + &mixer_reverb_mode_control, + &mixer_fm_chorus_depth_control, + &mixer_fm_reverb_depth_control, +}; + +/* + * create and attach mixer elements for WaveTable treble/bass controls + */ +static int __init +snd_emu8000_create_mixer(snd_card_t *card, emu8000_t *emu) +{ + int i, err = 0; + + snd_assert(emu != NULL && card != NULL, return -EINVAL); + + spin_lock_init(&emu->control_lock); + + memset(emu->controls, 0, sizeof(emu->controls)); + for (i = 0; i < EMU8000_NUM_CONTROLS; i++) { + if ((err = snd_ctl_add(card, emu->controls[i] = snd_ctl_new1(mixer_defs[i], emu))) < 0) + goto __error; + } + return 0; + +__error: + for (i = 0; i < EMU8000_NUM_CONTROLS; i++) { + down_write(&card->controls_rwsem); + if (emu->controls[i]) + snd_ctl_remove(card, emu->controls[i]); + up_write(&card->controls_rwsem); + } + return err; +} + + +/* + * free resources + */ +static int snd_emu8000_free(emu8000_t *hw) +{ + if (hw->res_port1) { + release_resource(hw->res_port1); + kfree_nocheck(hw->res_port1); + } + if (hw->res_port2) { + release_resource(hw->res_port2); + kfree_nocheck(hw->res_port2); + } + if (hw->res_port3) { + release_resource(hw->res_port3); + kfree_nocheck(hw->res_port3); + } + kfree(hw); + return 0; +} + +/* + */ +static int snd_emu8000_dev_free(snd_device_t *device) +{ + emu8000_t *hw = device->device_data; + return snd_emu8000_free(hw); +} + +/* + * initialize and register emu8000 synth device. + */ +int __init +snd_emu8000_new(snd_card_t *card, int index, long port, int seq_ports, snd_seq_device_t **awe_ret) +{ + snd_seq_device_t *awe; + emu8000_t *hw; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_emu8000_dev_free, + }; + + if (awe_ret) + *awe_ret = NULL; + + if (seq_ports <= 0) + return 0; + + hw = kcalloc(1, sizeof(*hw), GFP_KERNEL); + if (hw == NULL) + return -ENOMEM; + spin_lock_init(&hw->reg_lock); + hw->index = index; + hw->port1 = port; + hw->port2 = port + 0x400; + hw->port3 = port + 0x800; + if (!(hw->res_port1 = request_region(hw->port1, 4, "Emu8000-1")) || + !(hw->res_port2 = request_region(hw->port2, 4, "Emu8000-2")) || + !(hw->res_port3 = request_region(hw->port3, 4, "Emu8000-3"))) { + snd_printk(KERN_ERR "sbawe: can't grab ports 0x%lx, 0x%lx, 0x%lx\n", hw->port1, hw->port2, hw->port3); + snd_emu8000_free(hw); + return -EBUSY; + } + hw->mem_size = 0; + hw->card = card; + hw->seq_ports = seq_ports; + hw->bass_level = 5; + hw->treble_level = 9; + hw->chorus_mode = 2; + hw->reverb_mode = 4; + hw->fm_chorus_depth = 0; + hw->fm_reverb_depth = 0; + + if (snd_emu8000_detect(hw) < 0) { + snd_emu8000_free(hw); + return -ENODEV; + } + + snd_emu8000_init_hw(hw); + if ((err = snd_emu8000_create_mixer(card, hw)) < 0) { + snd_emu8000_free(hw); + return err; + } + + if ((err = snd_device_new(card, SNDRV_DEV_CODEC, hw, &ops)) < 0) { + snd_emu8000_free(hw); + return err; + } +#if defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE)) + if (snd_seq_device_new(card, index, SNDRV_SEQ_DEV_ID_EMU8000, + sizeof(emu8000_t*), &awe) >= 0) { + strcpy(awe->name, "EMU-8000"); + *(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(awe) = hw; + } +#else + awe = NULL; +#endif + if (awe_ret) + *awe_ret = awe; + + return 0; +} + + +/* + * exported stuff + */ + +EXPORT_SYMBOL(snd_emu8000_poke); +EXPORT_SYMBOL(snd_emu8000_peek); +EXPORT_SYMBOL(snd_emu8000_poke_dw); +EXPORT_SYMBOL(snd_emu8000_peek_dw); +EXPORT_SYMBOL(snd_emu8000_dma_chan); +EXPORT_SYMBOL(snd_emu8000_init_fm); +EXPORT_SYMBOL(snd_emu8000_load_chorus_fx); +EXPORT_SYMBOL(snd_emu8000_load_reverb_fx); +EXPORT_SYMBOL(snd_emu8000_update_chorus_mode); +EXPORT_SYMBOL(snd_emu8000_update_reverb_mode); +EXPORT_SYMBOL(snd_emu8000_update_equalizer); diff --git a/sound/isa/sb/emu8000_callback.c b/sound/isa/sb/emu8000_callback.c new file mode 100644 index 00000000000..1cc4101a17a --- /dev/null +++ b/sound/isa/sb/emu8000_callback.c @@ -0,0 +1,543 @@ +/* + * synth callback routines for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de> + * + * 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 "emu8000_local.h" +#include <sound/asoundef.h> + +/* + * prototypes + */ +static snd_emux_voice_t *get_voice(snd_emux_t *emu, snd_emux_port_t *port); +static int start_voice(snd_emux_voice_t *vp); +static void trigger_voice(snd_emux_voice_t *vp); +static void release_voice(snd_emux_voice_t *vp); +static void update_voice(snd_emux_voice_t *vp, int update); +static void reset_voice(snd_emux_t *emu, int ch); +static void terminate_voice(snd_emux_voice_t *vp); +static void sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset); +#ifdef CONFIG_SND_SEQUENCER_OSS +static int oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2); +#endif +static int load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len); + +static void set_pitch(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_volume(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_pan(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp); +static void set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp); +static void snd_emu8000_tweak_voice(emu8000_t *emu, int ch); + +/* + * Ensure a value is between two points + * macro evaluates its args more than once, so changed to upper-case. + */ +#define LIMITVALUE(x, a, b) do { if ((x) < (a)) (x) = (a); else if ((x) > (b)) (x) = (b); } while (0) +#define LIMITMAX(x, a) do {if ((x) > (a)) (x) = (a); } while (0) + + +/* + * set up operators + */ +static snd_emux_operators_t emu8000_ops = { + .owner = THIS_MODULE, + .get_voice = get_voice, + .prepare = start_voice, + .trigger = trigger_voice, + .release = release_voice, + .update = update_voice, + .terminate = terminate_voice, + .reset = reset_voice, + .sample_new = snd_emu8000_sample_new, + .sample_free = snd_emu8000_sample_free, + .sample_reset = snd_emu8000_sample_reset, + .load_fx = load_fx, + .sysex = sysex, +#ifdef CONFIG_SND_SEQUENCER_OSS + .oss_ioctl = oss_ioctl, +#endif +}; + +void +snd_emu8000_ops_setup(emu8000_t *hw) +{ + hw->emu->ops = emu8000_ops; +} + + + +/* + * Terminate a voice + */ +static void +release_voice(snd_emux_voice_t *vp) +{ + int dcysusv; + emu8000_t *hw; + + hw = vp->hw; + dcysusv = 0x8000 | (unsigned char)vp->reg.parm.modrelease; + EMU8000_DCYSUS_WRITE(hw, vp->ch, dcysusv); + dcysusv = 0x8000 | (unsigned char)vp->reg.parm.volrelease; + EMU8000_DCYSUSV_WRITE(hw, vp->ch, dcysusv); +} + + +/* + */ +static void +terminate_voice(snd_emux_voice_t *vp) +{ + emu8000_t *hw; + + hw = vp->hw; + EMU8000_DCYSUSV_WRITE(hw, vp->ch, 0x807F); +} + + +/* + */ +static void +update_voice(snd_emux_voice_t *vp, int update) +{ + emu8000_t *hw; + + hw = vp->hw; + if (update & SNDRV_EMUX_UPDATE_VOLUME) + set_volume(hw, vp); + if (update & SNDRV_EMUX_UPDATE_PITCH) + set_pitch(hw, vp); + if ((update & SNDRV_EMUX_UPDATE_PAN) && + vp->port->ctrls[EMUX_MD_REALTIME_PAN]) + set_pan(hw, vp); + if (update & SNDRV_EMUX_UPDATE_FMMOD) + set_fmmod(hw, vp); + if (update & SNDRV_EMUX_UPDATE_TREMFREQ) + set_tremfreq(hw, vp); + if (update & SNDRV_EMUX_UPDATE_FM2FRQ2) + set_fm2frq2(hw, vp); + if (update & SNDRV_EMUX_UPDATE_Q) + set_filterQ(hw, vp); +} + + +/* + * Find a channel (voice) within the EMU that is not in use or at least + * less in use than other channels. Always returns a valid pointer + * no matter what. If there is a real shortage of voices then one + * will be cut. Such is life. + * + * The channel index (vp->ch) must be initialized in this routine. + * In Emu8k, it is identical with the array index. + */ +static snd_emux_voice_t * +get_voice(snd_emux_t *emu, snd_emux_port_t *port) +{ + int i; + snd_emux_voice_t *vp; + emu8000_t *hw; + + /* what we are looking for, in order of preference */ + enum { + OFF=0, RELEASED, PLAYING, END + }; + + /* Keeps track of what we are finding */ + struct best { + unsigned int time; + int voice; + } best[END]; + struct best *bp; + + hw = emu->hw; + + for (i = 0; i < END; i++) { + best[i].time = (unsigned int)(-1); /* XXX MAX_?INT really */; + best[i].voice = -1; + } + + /* + * Go through them all and get a best one to use. + */ + for (i = 0; i < emu->max_voices; i++) { + int state, val; + + vp = &emu->voices[i]; + state = vp->state; + + if (state == SNDRV_EMUX_ST_OFF) + bp = best + OFF; + else if (state == SNDRV_EMUX_ST_RELEASED || + state == SNDRV_EMUX_ST_PENDING) { + bp = best + RELEASED; + val = (EMU8000_CVCF_READ(hw, vp->ch) >> 16) & 0xffff; + if (! val) + bp = best + OFF; + } + else if (state & SNDRV_EMUX_ST_ON) + bp = best + PLAYING; + else + continue; + + /* check if sample is finished playing (non-looping only) */ + if (state != SNDRV_EMUX_ST_OFF && + (vp->reg.sample_mode & SNDRV_SFNT_SAMPLE_SINGLESHOT)) { + val = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff; + if (val >= vp->reg.loopstart) + bp = best + OFF; + } + + if (vp->time < bp->time) { + bp->time = vp->time; + bp->voice = i; + } + } + + for (i = 0; i < END; i++) { + if (best[i].voice >= 0) { + vp = &emu->voices[best[i].voice]; + vp->ch = best[i].voice; + return vp; + } + } + + /* not found */ + return NULL; +} + +/* + */ +static int +start_voice(snd_emux_voice_t *vp) +{ + unsigned int temp; + int ch; + int addr; + snd_midi_channel_t *chan; + emu8000_t *hw; + + hw = vp->hw; + ch = vp->ch; + chan = vp->chan; + + /* channel to be silent and idle */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); + EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); + EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); + EMU8000_PTRX_WRITE(hw, ch, 0); + EMU8000_CPF_WRITE(hw, ch, 0); + + /* set pitch offset */ + set_pitch(hw, vp); + + /* set envelope parameters */ + EMU8000_ENVVAL_WRITE(hw, ch, vp->reg.parm.moddelay); + EMU8000_ATKHLD_WRITE(hw, ch, vp->reg.parm.modatkhld); + EMU8000_DCYSUS_WRITE(hw, ch, vp->reg.parm.moddcysus); + EMU8000_ENVVOL_WRITE(hw, ch, vp->reg.parm.voldelay); + EMU8000_ATKHLDV_WRITE(hw, ch, vp->reg.parm.volatkhld); + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + + /* cutoff and volume */ + set_volume(hw, vp); + + /* modulation envelope heights */ + EMU8000_PEFE_WRITE(hw, ch, vp->reg.parm.pefe); + + /* lfo1/2 delay */ + EMU8000_LFO1VAL_WRITE(hw, ch, vp->reg.parm.lfo1delay); + EMU8000_LFO2VAL_WRITE(hw, ch, vp->reg.parm.lfo2delay); + + /* lfo1 pitch & cutoff shift */ + set_fmmod(hw, vp); + /* lfo1 volume & freq */ + set_tremfreq(hw, vp); + /* lfo2 pitch & freq */ + set_fm2frq2(hw, vp); + /* pan & loop start */ + set_pan(hw, vp); + + /* chorus & loop end (chorus 8bit, MSB) */ + addr = vp->reg.loopend - 1; + temp = vp->reg.parm.chorus; + temp += (int)chan->control[MIDI_CTL_E3_CHORUS_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + temp = (temp <<24) | (unsigned int)addr; + EMU8000_CSL_WRITE(hw, ch, temp); + + /* Q & current address (Q 4bit value, MSB) */ + addr = vp->reg.start - 1; + temp = vp->reg.parm.filterQ; + temp = (temp<<28) | (unsigned int)addr; + EMU8000_CCCA_WRITE(hw, ch, temp); + + /* clear unknown registers */ + EMU8000_00A0_WRITE(hw, ch, 0); + EMU8000_0080_WRITE(hw, ch, 0); + + /* reset volume */ + temp = vp->vtarget << 16; + EMU8000_VTFT_WRITE(hw, ch, temp | vp->ftarget); + EMU8000_CVCF_WRITE(hw, ch, temp | 0xff00); + + return 0; +} + +/* + * Start envelope + */ +static void +trigger_voice(snd_emux_voice_t *vp) +{ + int ch = vp->ch; + unsigned int temp; + emu8000_t *hw; + + hw = vp->hw; + + /* set reverb and pitch target */ + temp = vp->reg.parm.reverb; + temp += (int)vp->chan->control[MIDI_CTL_E1_REVERB_DEPTH] * 9 / 10; + LIMITMAX(temp, 255); + temp = (temp << 8) | (vp->ptarget << 16) | vp->aaux; + EMU8000_PTRX_WRITE(hw, ch, temp); + EMU8000_CPF_WRITE(hw, ch, vp->ptarget << 16); + EMU8000_DCYSUSV_WRITE(hw, ch, vp->reg.parm.voldcysus); +} + +/* + * reset voice parameters + */ +static void +reset_voice(snd_emux_t *emu, int ch) +{ + emu8000_t *hw; + + hw = emu->hw; + EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); + snd_emu8000_tweak_voice(hw, ch); +} + +/* + * Set the pitch of a possibly playing note. + */ +static void +set_pitch(emu8000_t *hw, snd_emux_voice_t *vp) +{ + EMU8000_IP_WRITE(hw, vp->ch, vp->apitch); +} + +/* + * Set the volume of a possibly already playing note + */ +static void +set_volume(emu8000_t *hw, snd_emux_voice_t *vp) +{ + int ifatn; + + ifatn = (unsigned char)vp->acutoff; + ifatn = (ifatn << 8); + ifatn |= (unsigned char)vp->avol; + EMU8000_IFATN_WRITE(hw, vp->ch, ifatn); +} + +/* + * Set pan and loop start address. + */ +static void +set_pan(emu8000_t *hw, snd_emux_voice_t *vp) +{ + unsigned int temp; + + temp = ((unsigned int)vp->apan<<24) | ((unsigned int)vp->reg.loopstart - 1); + EMU8000_PSST_WRITE(hw, vp->ch, temp); +} + +#define MOD_SENSE 18 + +static void +set_fmmod(emu8000_t *hw, snd_emux_voice_t *vp) +{ + unsigned short fmmod; + short pitch; + unsigned char cutoff; + int modulation; + + pitch = (char)(vp->reg.parm.fmmod>>8); + cutoff = (vp->reg.parm.fmmod & 0xff); + modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; + pitch += (MOD_SENSE * modulation) / 1200; + LIMITVALUE(pitch, -128, 127); + fmmod = ((unsigned char)pitch<<8) | cutoff; + EMU8000_FMMOD_WRITE(hw, vp->ch, fmmod); +} + +/* set tremolo (lfo1) volume & frequency */ +static void +set_tremfreq(emu8000_t *hw, snd_emux_voice_t *vp) +{ + EMU8000_TREMFRQ_WRITE(hw, vp->ch, vp->reg.parm.tremfrq); +} + +/* set lfo2 pitch & frequency */ +static void +set_fm2frq2(emu8000_t *hw, snd_emux_voice_t *vp) +{ + unsigned short fm2frq2; + short pitch; + unsigned char freq; + int modulation; + + pitch = (char)(vp->reg.parm.fm2frq2>>8); + freq = vp->reg.parm.fm2frq2 & 0xff; + modulation = vp->chan->gm_modulation + vp->chan->midi_pressure; + pitch += (MOD_SENSE * modulation) / 1200; + LIMITVALUE(pitch, -128, 127); + fm2frq2 = ((unsigned char)pitch<<8) | freq; + EMU8000_FM2FRQ2_WRITE(hw, vp->ch, fm2frq2); +} + +/* set filterQ */ +static void +set_filterQ(emu8000_t *hw, snd_emux_voice_t *vp) +{ + unsigned int addr; + addr = EMU8000_CCCA_READ(hw, vp->ch) & 0xffffff; + addr |= (vp->reg.parm.filterQ << 28); + EMU8000_CCCA_WRITE(hw, vp->ch, addr); +} + +/* + * set the envelope & LFO parameters to the default values + */ +static void +snd_emu8000_tweak_voice(emu8000_t *emu, int i) +{ + /* set all mod/vol envelope shape to minimum */ + EMU8000_ENVVOL_WRITE(emu, i, 0x8000); + EMU8000_ENVVAL_WRITE(emu, i, 0x8000); + EMU8000_DCYSUS_WRITE(emu, i, 0x7F7F); + EMU8000_ATKHLDV_WRITE(emu, i, 0x7F7F); + EMU8000_ATKHLD_WRITE(emu, i, 0x7F7F); + EMU8000_PEFE_WRITE(emu, i, 0); /* mod envelope height to zero */ + EMU8000_LFO1VAL_WRITE(emu, i, 0x8000); /* no delay for LFO1 */ + EMU8000_LFO2VAL_WRITE(emu, i, 0x8000); + EMU8000_IP_WRITE(emu, i, 0xE000); /* no pitch shift */ + EMU8000_IFATN_WRITE(emu, i, 0xFF00); /* volume to minimum */ + EMU8000_FMMOD_WRITE(emu, i, 0); + EMU8000_TREMFRQ_WRITE(emu, i, 0); + EMU8000_FM2FRQ2_WRITE(emu, i, 0); +} + +/* + * sysex callback + */ +static void +sysex(snd_emux_t *emu, char *buf, int len, int parsed, snd_midi_channel_set_t *chset) +{ + emu8000_t *hw; + + hw = emu->hw; + + switch (parsed) { + case SNDRV_MIDI_SYSEX_GS_CHORUS_MODE: + hw->chorus_mode = chset->gs_chorus_mode; + snd_emu8000_update_chorus_mode(hw); + break; + + case SNDRV_MIDI_SYSEX_GS_REVERB_MODE: + hw->reverb_mode = chset->gs_reverb_mode; + snd_emu8000_update_reverb_mode(hw); + break; + } +} + + +#ifdef CONFIG_SND_SEQUENCER_OSS +/* + * OSS ioctl callback + */ +static int +oss_ioctl(snd_emux_t *emu, int cmd, int p1, int p2) +{ + emu8000_t *hw; + + hw = emu->hw; + + switch (cmd) { + case _EMUX_OSS_REVERB_MODE: + hw->reverb_mode = p1; + snd_emu8000_update_reverb_mode(hw); + break; + + case _EMUX_OSS_CHORUS_MODE: + hw->chorus_mode = p1; + snd_emu8000_update_chorus_mode(hw); + break; + + case _EMUX_OSS_INITIALIZE_CHIP: + /* snd_emu8000_init(hw); */ /*ignored*/ + break; + + case _EMUX_OSS_EQUALIZER: + hw->bass_level = p1; + hw->treble_level = p2; + snd_emu8000_update_equalizer(hw); + break; + } + return 0; +} +#endif + + +/* + * additional patch keys + */ + +#define SNDRV_EMU8000_LOAD_CHORUS_FX 0x10 /* optarg=mode */ +#define SNDRV_EMU8000_LOAD_REVERB_FX 0x11 /* optarg=mode */ + + +/* + * callback routine + */ + +static int +load_fx(snd_emux_t *emu, int type, int mode, const void __user *buf, long len) +{ + emu8000_t *hw; + hw = emu->hw; + + /* skip header */ + buf += 16; + len -= 16; + + switch (type) { + case SNDRV_EMU8000_LOAD_CHORUS_FX: + return snd_emu8000_load_chorus_fx(hw, mode, buf, len); + case SNDRV_EMU8000_LOAD_REVERB_FX: + return snd_emu8000_load_reverb_fx(hw, mode, buf, len); + } + return -EINVAL; +} + diff --git a/sound/isa/sb/emu8000_local.h b/sound/isa/sb/emu8000_local.h new file mode 100644 index 00000000000..ea4996a895f --- /dev/null +++ b/sound/isa/sb/emu8000_local.h @@ -0,0 +1,43 @@ +#ifndef __EMU8000_LOCAL_H +#define __EMU8000_LOCAL_H +/* + * Local defininitons for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de> + * + * 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/wait.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/emu8000.h> +#include <sound/emu8000_reg.h> + +/* emu8000_patch.c */ +int snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr, const void __user *data, long count); +int snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr); +void snd_emu8000_sample_reset(snd_emux_t *rec); + +/* emu8000_callback.c */ +void snd_emu8000_ops_setup(emu8000_t *emu); + +/* emu8000_pcm.c */ +int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index); + +#endif /* __EMU8000_LOCAL_H */ diff --git a/sound/isa/sb/emu8000_patch.c b/sound/isa/sb/emu8000_patch.c new file mode 100644 index 00000000000..4afc4a1bc14 --- /dev/null +++ b/sound/isa/sb/emu8000_patch.c @@ -0,0 +1,303 @@ +/* + * Patch routines for the emu8000 (AWE32/64) + * + * Copyright (C) 1999 Steve Ratcliffe + * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de> + * + * 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 "emu8000_local.h" +#include <asm/uaccess.h> +#include <linux/moduleparam.h> + +static int emu8000_reset_addr = 0; +module_param(emu8000_reset_addr, int, 0444); +MODULE_PARM_DESC(emu8000_reset_addr, "reset write address at each time (makes slowdown)"); + + +/* + * Open up channels. + */ +static int +snd_emu8000_open_dma(emu8000_t *emu, int write) +{ + int i; + + /* reserve all 30 voices for loading */ + for (i = 0; i < EMU8000_DRAM_VOICES; i++) { + snd_emux_lock_voice(emu->emu, i); + snd_emu8000_dma_chan(emu, i, write); + } + + /* assign voice 31 and 32 to ROM */ + EMU8000_VTFT_WRITE(emu, 30, 0); + EMU8000_PSST_WRITE(emu, 30, 0x1d8); + EMU8000_CSL_WRITE(emu, 30, 0x1e0); + EMU8000_CCCA_WRITE(emu, 30, 0x1d8); + EMU8000_VTFT_WRITE(emu, 31, 0); + EMU8000_PSST_WRITE(emu, 31, 0x1d8); + EMU8000_CSL_WRITE(emu, 31, 0x1e0); + EMU8000_CCCA_WRITE(emu, 31, 0x1d8); + + return 0; +} + +/* + * Close all dram channels. + */ +static void +snd_emu8000_close_dma(emu8000_t *emu) +{ + int i; + + for (i = 0; i < EMU8000_DRAM_VOICES; i++) { + snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); + snd_emux_unlock_voice(emu->emu, i); + } +} + +/* + */ + +#define BLANK_LOOP_START 4 +#define BLANK_LOOP_END 8 +#define BLANK_LOOP_SIZE 12 +#define BLANK_HEAD_SIZE 48 + +/* + * Read a word from userland, taking care of conversions from + * 8bit samples etc. + */ +static unsigned short +read_word(const void __user *buf, int offset, int mode) +{ + unsigned short c; + if (mode & SNDRV_SFNT_SAMPLE_8BITS) { + unsigned char cc; + get_user(cc, (unsigned char __user *)buf + offset); + c = cc << 8; /* convert 8bit -> 16bit */ + } else { +#ifdef SNDRV_LITTLE_ENDIAN + get_user(c, (unsigned short __user *)buf + offset); +#else + unsigned short cc; + get_user(cc, (unsigned short __user *)buf + offset); + c = swab16(cc); +#endif + } + if (mode & SNDRV_SFNT_SAMPLE_UNSIGNED) + c ^= 0x8000; /* unsigned -> signed */ + return c; +} + +/* + */ +static void +snd_emu8000_write_wait(emu8000_t *emu) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + } +} + +/* + * write sample word data + * + * You should not have to keep resetting the address each time + * as the chip is supposed to step on the next address automatically. + * It mostly does, but during writes of some samples at random it + * completely loses words (every one in 16 roughly but with no + * obvious pattern). + * + * This is therefore much slower than need be, but is at least + * working. + */ +inline static void +write_word(emu8000_t *emu, int *offset, unsigned short data) +{ + if (emu8000_reset_addr) { + if (emu8000_reset_addr > 1) + snd_emu8000_write_wait(emu); + EMU8000_SMALW_WRITE(emu, *offset); + } + EMU8000_SMLD_WRITE(emu, data); + *offset += 1; +} + +/* + * Write the sample to EMU800 memory. This routine is invoked out of + * the generic soundfont routines as a callback. + */ +int +snd_emu8000_sample_new(snd_emux_t *rec, snd_sf_sample_t *sp, + snd_util_memhdr_t *hdr, const void __user *data, long count) +{ + int i; + int rc; + int offset; + int truesize; + int dram_offset, dram_start; + emu8000_t *emu; + + emu = rec->hw; + snd_assert(sp != NULL, return -EINVAL); + + if (sp->v.size == 0) + return 0; + + /* be sure loop points start < end */ + if (sp->v.loopstart > sp->v.loopend) { + int tmp = sp->v.loopstart; + sp->v.loopstart = sp->v.loopend; + sp->v.loopend = tmp; + } + + /* compute true data size to be loaded */ + truesize = sp->v.size; + if (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP)) + truesize += sp->v.loopend - sp->v.loopstart; + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) + truesize += BLANK_LOOP_SIZE; + + sp->block = snd_util_mem_alloc(hdr, truesize * 2); + if (sp->block == NULL) { + /*snd_printd("EMU8000: out of memory\n");*/ + /* not ENOMEM (for compatibility) */ + return -ENOSPC; + } + + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_8BITS) { + if (!access_ok(VERIFY_READ, data, sp->v.size)) + return -EFAULT; + } else { + if (!access_ok(VERIFY_READ, data, sp->v.size * 2)) + return -EFAULT; + } + + /* recalculate address offset */ + sp->v.end -= sp->v.start; + sp->v.loopstart -= sp->v.start; + sp->v.loopend -= sp->v.start; + sp->v.start = 0; + + /* dram position (in word) -- mem_offset is byte */ + dram_offset = EMU8000_DRAM_OFFSET + (sp->block->offset >> 1); + dram_start = dram_offset; + + /* set the total size (store onto obsolete checksum value) */ + sp->v.truesize = truesize * 2; /* in bytes */ + + snd_emux_terminate_all(emu->emu); + if ((rc = snd_emu8000_open_dma(emu, EMU8000_RAM_WRITE)) != 0) + return rc; + + /* Set the address to start writing at */ + snd_emu8000_write_wait(emu); + EMU8000_SMALW_WRITE(emu, dram_offset); + + /*snd_emu8000_init_fm(emu);*/ + +#if 0 + /* first block - write 48 samples for silence */ + if (! sp->block->offset) { + for (i = 0; i < BLANK_HEAD_SIZE; i++) { + write_word(emu, &dram_offset, 0); + } + } +#endif + + offset = 0; + for (i = 0; i < sp->v.size; i++) { + unsigned short s; + + s = read_word(data, offset, sp->v.mode_flags); + offset++; + write_word(emu, &dram_offset, s); + + /* we may take too long time in this loop. + * so give controls back to kernel if needed. + */ + cond_resched(); + + if (i == sp->v.loopend && + (sp->v.mode_flags & (SNDRV_SFNT_SAMPLE_BIDIR_LOOP|SNDRV_SFNT_SAMPLE_REVERSE_LOOP))) + { + int looplen = sp->v.loopend - sp->v.loopstart; + int k; + + /* copy reverse loop */ + for (k = 1; k <= looplen; k++) { + s = read_word(data, offset - k, sp->v.mode_flags); + write_word(emu, &dram_offset, s); + } + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_BIDIR_LOOP) { + sp->v.loopend += looplen; + } else { + sp->v.loopstart += looplen; + sp->v.loopend += looplen; + } + sp->v.end += looplen; + } + } + + /* if no blank loop is attached in the sample, add it */ + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_NO_BLANK) { + for (i = 0; i < BLANK_LOOP_SIZE; i++) { + write_word(emu, &dram_offset, 0); + } + if (sp->v.mode_flags & SNDRV_SFNT_SAMPLE_SINGLESHOT) { + sp->v.loopstart = sp->v.end + BLANK_LOOP_START; + sp->v.loopend = sp->v.end + BLANK_LOOP_END; + } + } + + /* add dram offset */ + sp->v.start += dram_start; + sp->v.end += dram_start; + sp->v.loopstart += dram_start; + sp->v.loopend += dram_start; + + snd_emu8000_close_dma(emu); + snd_emu8000_init_fm(emu); + + return 0; +} + +/* + * free a sample block + */ +int +snd_emu8000_sample_free(snd_emux_t *rec, snd_sf_sample_t *sp, snd_util_memhdr_t *hdr) +{ + if (sp->block) { + snd_util_mem_free(hdr, sp->block); + sp->block = NULL; + } + return 0; +} + + +/* + * sample_reset callback - terminate voices + */ +void +snd_emu8000_sample_reset(snd_emux_t *rec) +{ + snd_emux_terminate_all(rec); +} diff --git a/sound/isa/sb/emu8000_pcm.c b/sound/isa/sb/emu8000_pcm.c new file mode 100644 index 00000000000..db5eb8b5505 --- /dev/null +++ b/sound/isa/sb/emu8000_pcm.c @@ -0,0 +1,704 @@ +/* + * pcm emulation on emu8000 wavetable + * + * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> + * + * 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 "emu8000_local.h" +#include <linux/init.h> +#include <sound/initval.h> +#include <sound/pcm.h> + +/* + * define the following if you want to use this pcm with non-interleaved mode + */ +/* #define USE_NONINTERLEAVE */ + +/* NOTE: for using the non-interleaved mode with alsa-lib, you have to set + * mmap_emulation flag to 1 in your .asoundrc, such like + * + * pcm.emu8k { + * type plug + * slave.pcm { + * type hw + * card 0 + * device 1 + * mmap_emulation 1 + * } + * } + * + * besides, for the time being, the non-interleaved mode doesn't work well on + * alsa-lib... + */ + + +typedef struct snd_emu8k_pcm emu8k_pcm_t; + +struct snd_emu8k_pcm { + emu8000_t *emu; + snd_pcm_substream_t *substream; + + unsigned int allocated_bytes; + snd_util_memblk_t *block; + unsigned int offset; + unsigned int buf_size; + unsigned int period_size; + unsigned int loop_start[2]; + unsigned int pitch; + int panning[2]; + int last_ptr; + int period_pos; + int voices; + unsigned int dram_opened: 1; + unsigned int running: 1; + unsigned int timer_running: 1; + struct timer_list timer; + spinlock_t timer_lock; +}; + +#define LOOP_BLANK_SIZE 8 + + +/* + * open up channels for the simultaneous data transfer and playback + */ +static int +emu8k_open_dram_for_pcm(emu8000_t *emu, int channels) +{ + int i; + + /* reserve up to 2 voices for playback */ + snd_emux_lock_voice(emu->emu, 0); + if (channels > 1) + snd_emux_lock_voice(emu->emu, 1); + + /* reserve 28 voices for loading */ + for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { + unsigned int mode = EMU8000_RAM_WRITE; + snd_emux_lock_voice(emu->emu, i); +#ifndef USE_NONINTERLEAVE + if (channels > 1 && (i & 1) != 0) + mode |= EMU8000_RAM_RIGHT; +#endif + snd_emu8000_dma_chan(emu, i, mode); + } + + /* assign voice 31 and 32 to ROM */ + EMU8000_VTFT_WRITE(emu, 30, 0); + EMU8000_PSST_WRITE(emu, 30, 0x1d8); + EMU8000_CSL_WRITE(emu, 30, 0x1e0); + EMU8000_CCCA_WRITE(emu, 30, 0x1d8); + EMU8000_VTFT_WRITE(emu, 31, 0); + EMU8000_PSST_WRITE(emu, 31, 0x1d8); + EMU8000_CSL_WRITE(emu, 31, 0x1e0); + EMU8000_CCCA_WRITE(emu, 31, 0x1d8); + + return 0; +} + +/* + */ +static void +snd_emu8000_write_wait(emu8000_t *emu, int can_schedule) +{ + while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { + if (can_schedule) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + if (signal_pending(current)) + break; + } + } +} + +/* + * close all channels + */ +static void +emu8k_close_dram(emu8000_t *emu) +{ + int i; + + for (i = 0; i < 2; i++) + snd_emux_unlock_voice(emu->emu, i); + for (; i < EMU8000_DRAM_VOICES; i++) { + snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); + snd_emux_unlock_voice(emu->emu, i); + } +} + +/* + * convert Hz to AWE32 rate offset (see emux/soundfont.c) + */ + +#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */ +#define SAMPLERATE_RATIO 4096 + +static int calc_rate_offset(int hz) +{ + return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO); +} + + +/* + */ + +static snd_pcm_hardware_t emu8k_pcm_hw = { +#ifdef USE_NONINTERLEAVE + .info = SNDRV_PCM_INFO_NONINTERLEAVED, +#else + .info = SNDRV_PCM_INFO_INTERLEAVED, +#endif + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = 1024, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0, + +}; + +/* + * get the current position at the given channel from CCCA register + */ +static inline int emu8k_get_curpos(emu8k_pcm_t *rec, int ch) +{ + int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; + val -= rec->loop_start[ch] - 1; + return val; +} + + +/* + * timer interrupt handler + * check the current position and update the period if necessary. + */ +static void emu8k_pcm_timer_func(unsigned long data) +{ + emu8k_pcm_t *rec = (emu8k_pcm_t *)data; + int ptr, delta; + + spin_lock(&rec->timer_lock); + /* update the current pointer */ + ptr = emu8k_get_curpos(rec, 0); + if (ptr < rec->last_ptr) + delta = ptr + rec->buf_size - rec->last_ptr; + else + delta = ptr - rec->last_ptr; + rec->period_pos += delta; + rec->last_ptr = ptr; + + /* reprogram timer */ + rec->timer.expires = jiffies + 1; + add_timer(&rec->timer); + + /* update period */ + if (rec->period_pos >= (int)rec->period_size) { + rec->period_pos %= rec->period_size; + spin_unlock(&rec->timer_lock); + snd_pcm_period_elapsed(rec->substream); + return; + } + spin_unlock(&rec->timer_lock); +} + + +/* + * open pcm + * creating an instance here + */ +static int emu8k_pcm_open(snd_pcm_substream_t *subs) +{ + emu8000_t *emu = snd_pcm_substream_chip(subs); + emu8k_pcm_t *rec; + snd_pcm_runtime_t *runtime = subs->runtime; + + rec = kcalloc(1, sizeof(*rec), GFP_KERNEL); + if (! rec) + return -ENOMEM; + + rec->emu = emu; + rec->substream = subs; + runtime->private_data = rec; + + spin_lock_init(&rec->timer_lock); + init_timer(&rec->timer); + rec->timer.function = emu8k_pcm_timer_func; + rec->timer.data = (unsigned long)rec; + + runtime->hw = emu8k_pcm_hw; + runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; + runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; + + /* use timer to update periods.. (specified in msec) */ + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, + (1000000 + HZ - 1) / HZ, UINT_MAX); + + return 0; +} + +static int emu8k_pcm_close(snd_pcm_substream_t *subs) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + kfree(rec); + subs->runtime->private_data = NULL; + return 0; +} + +/* + * calculate pitch target + */ +static int calc_pitch_target(int pitch) +{ + int ptarget = 1 << (pitch >> 12); + if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; + if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; + if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; + ptarget += (ptarget >> 1); + if (ptarget > 0xffff) ptarget = 0xffff; + return ptarget; +} + +/* + * set up the voice + */ +static void setup_voice(emu8k_pcm_t *rec, int ch) +{ + emu8000_t *hw = rec->emu; + unsigned int temp; + + /* channel to be silent and idle */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); + EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); + EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); + EMU8000_PTRX_WRITE(hw, ch, 0); + EMU8000_CPF_WRITE(hw, ch, 0); + + /* pitch offset */ + EMU8000_IP_WRITE(hw, ch, rec->pitch); + /* set envelope parameters */ + EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); + EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); + EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); + EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); + EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); + /* decay/sustain parameter for volume envelope is used + for triggerg the voice */ + /* modulation envelope heights */ + EMU8000_PEFE_WRITE(hw, ch, 0x0); + /* lfo1/2 delay */ + EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); + EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); + /* lfo1 pitch & cutoff shift */ + EMU8000_FMMOD_WRITE(hw, ch, 0); + /* lfo1 volume & freq */ + EMU8000_TREMFRQ_WRITE(hw, ch, 0); + /* lfo2 pitch & freq */ + EMU8000_FM2FRQ2_WRITE(hw, ch, 0); + /* pan & loop start */ + temp = rec->panning[ch]; + temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); + EMU8000_PSST_WRITE(hw, ch, temp); + /* chorus & loop end (chorus 8bit, MSB) */ + temp = 0; // chorus + temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); + EMU8000_CSL_WRITE(hw, ch, temp); + /* Q & current address (Q 4bit value, MSB) */ + temp = 0; // filterQ + temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); + EMU8000_CCCA_WRITE(hw, ch, temp); + /* clear unknown registers */ + EMU8000_00A0_WRITE(hw, ch, 0); + EMU8000_0080_WRITE(hw, ch, 0); +} + +/* + * trigger the voice + */ +static void start_voice(emu8k_pcm_t *rec, int ch) +{ + unsigned long flags; + emu8000_t *hw = rec->emu; + unsigned int temp, aux; + int pt = calc_pitch_target(rec->pitch); + + /* cutoff and volume */ + EMU8000_IFATN_WRITE(hw, ch, 0xff00); + EMU8000_VTFT_WRITE(hw, ch, 0xffff); + EMU8000_CVCF_WRITE(hw, ch, 0xffff); + /* trigger envelope */ + EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); + /* set reverb and pitch target */ + temp = 0; // reverb + if (rec->panning[ch] == 0) + aux = 0xff; + else + aux = (-rec->panning[ch]) & 0xff; + temp = (temp << 8) | (pt << 16) | aux; + EMU8000_PTRX_WRITE(hw, ch, temp); + EMU8000_CPF_WRITE(hw, ch, pt << 16); + + /* start timer */ + spin_lock_irqsave(&rec->timer_lock, flags); + if (! rec->timer_running) { + rec->timer.expires = jiffies + 1; + add_timer(&rec->timer); + rec->timer_running = 1; + } + spin_unlock_irqrestore(&rec->timer_lock, flags); +} + +/* + * stop the voice immediately + */ +static void stop_voice(emu8k_pcm_t *rec, int ch) +{ + unsigned long flags; + emu8000_t *hw = rec->emu; + + EMU8000_DCYSUSV_WRITE(hw, ch, 0x807F); + + /* stop timer */ + spin_lock_irqsave(&rec->timer_lock, flags); + if (rec->timer_running) { + del_timer(&rec->timer); + rec->timer_running = 0; + } + spin_unlock_irqrestore(&rec->timer_lock, flags); +} + +static int emu8k_pcm_trigger(snd_pcm_substream_t *subs, int cmd) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + int ch; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + for (ch = 0; ch < rec->voices; ch++) + start_voice(rec, ch); + rec->running = 1; + break; + case SNDRV_PCM_TRIGGER_STOP: + rec->running = 0; + for (ch = 0; ch < rec->voices; ch++) + stop_voice(rec, ch); + break; + default: + return -EINVAL; + } + return 0; +} + + +/* + * copy / silence ops + */ + +/* + * this macro should be inserted in the copy/silence loops + * to reduce the latency. without this, the system will hang up + * during the whole loop. + */ +#define CHECK_SCHEDULER() \ +do { \ + cond_resched();\ + if (signal_pending(current))\ + return -EAGAIN;\ +} while (0) + + +#ifdef USE_NONINTERLEAVE +/* copy one channel block */ +static int emu8k_transfer_block(emu8000_t *emu, int offset, unsigned short *buf, int count) +{ + EMU8000_SMALW_WRITE(emu, offset); + while (count > 0) { + unsigned short sval; + CHECK_SCHEDULER(); + get_user(sval, buf); + EMU8000_SMLD_WRITE(emu, sval); + buf++; + count--; + } + return 0; +} + +static int emu8k_pcm_copy(snd_pcm_substream_t *subs, + int voice, + snd_pcm_uframes_t pos, + void *src, + snd_pcm_uframes_t count) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + emu8000_t *emu = rec->emu; + + snd_emu8000_write_wait(emu, 1); + if (voice == -1) { + unsigned short *buf = src; + int i, err; + count /= rec->voices; + for (i = 0; i < rec->voices; i++) { + err = emu8k_transfer_block(emu, pos + rec->loop_start[i], buf, count); + if (err < 0) + return err; + buf += count; + } + return 0; + } else { + return emu8k_transfer_block(emu, pos + rec->loop_start[voice], src, count); + } +} + +/* make a channel block silence */ +static int emu8k_silence_block(emu8000_t *emu, int offset, int count) +{ + EMU8000_SMALW_WRITE(emu, offset); + while (count > 0) { + CHECK_SCHEDULER(); + EMU8000_SMLD_WRITE(emu, 0); + count--; + } + return 0; +} + +static int emu8k_pcm_silence(snd_pcm_substream_t *subs, + int voice, + snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + emu8000_t *emu = rec->emu; + + snd_emu8000_write_wait(emu, 1); + if (voice == -1 && rec->voices == 1) + voice = 0; + if (voice == -1) { + int err; + err = emu8k_silence_block(emu, pos + rec->loop_start[0], count / 2); + if (err < 0) + return err; + return emu8k_silence_block(emu, pos + rec->loop_start[1], count / 2); + } else { + return emu8k_silence_block(emu, pos + rec->loop_start[voice], count); + } +} + +#else /* interleave */ + +/* + * copy the interleaved data can be done easily by using + * DMA "left" and "right" channels on emu8k engine. + */ +static int emu8k_pcm_copy(snd_pcm_substream_t *subs, + int voice, + snd_pcm_uframes_t pos, + void __user *src, + snd_pcm_uframes_t count) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + emu8000_t *emu = rec->emu; + unsigned short __user *buf = src; + + snd_emu8000_write_wait(emu, 1); + EMU8000_SMALW_WRITE(emu, pos + rec->loop_start[0]); + if (rec->voices > 1) + EMU8000_SMARW_WRITE(emu, pos + rec->loop_start[1]); + + while (count-- > 0) { + unsigned short sval; + CHECK_SCHEDULER(); + get_user(sval, buf); + EMU8000_SMLD_WRITE(emu, sval); + buf++; + if (rec->voices > 1) { + CHECK_SCHEDULER(); + get_user(sval, buf); + EMU8000_SMRD_WRITE(emu, sval); + buf++; + } + } + return 0; +} + +static int emu8k_pcm_silence(snd_pcm_substream_t *subs, + int voice, + snd_pcm_uframes_t pos, + snd_pcm_uframes_t count) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + emu8000_t *emu = rec->emu; + + snd_emu8000_write_wait(emu, 1); + EMU8000_SMALW_WRITE(emu, rec->loop_start[0] + pos); + if (rec->voices > 1) + EMU8000_SMARW_WRITE(emu, rec->loop_start[1] + pos); + while (count-- > 0) { + CHECK_SCHEDULER(); + EMU8000_SMLD_WRITE(emu, 0); + if (rec->voices > 1) { + CHECK_SCHEDULER(); + EMU8000_SMRD_WRITE(emu, 0); + } + } + return 0; +} +#endif + + +/* + * allocate a memory block + */ +static int emu8k_pcm_hw_params(snd_pcm_substream_t *subs, + snd_pcm_hw_params_t *hw_params) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + + if (rec->block) { + /* reallocation - release the old block */ + snd_util_mem_free(rec->emu->memhdr, rec->block); + rec->block = NULL; + } + + rec->allocated_bytes = params_buffer_bytes(hw_params) + LOOP_BLANK_SIZE * 4; + rec->block = snd_util_mem_alloc(rec->emu->memhdr, rec->allocated_bytes); + if (! rec->block) + return -ENOMEM; + rec->offset = EMU8000_DRAM_OFFSET + (rec->block->offset >> 1); /* in word */ + /* at least dma_bytes must be set for non-interleaved mode */ + subs->dma_buffer.bytes = params_buffer_bytes(hw_params); + + return 0; +} + +/* + * free the memory block + */ +static int emu8k_pcm_hw_free(snd_pcm_substream_t *subs) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + + if (rec->block) { + int ch; + for (ch = 0; ch < rec->voices; ch++) + stop_voice(rec, ch); // to be sure + if (rec->dram_opened) + emu8k_close_dram(rec->emu); + snd_util_mem_free(rec->emu->memhdr, rec->block); + rec->block = NULL; + } + return 0; +} + +/* + */ +static int emu8k_pcm_prepare(snd_pcm_substream_t *subs) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + + rec->pitch = 0xe000 + calc_rate_offset(subs->runtime->rate); + rec->last_ptr = 0; + rec->period_pos = 0; + + rec->buf_size = subs->runtime->buffer_size; + rec->period_size = subs->runtime->period_size; + rec->voices = subs->runtime->channels; + rec->loop_start[0] = rec->offset + LOOP_BLANK_SIZE; + if (rec->voices > 1) + rec->loop_start[1] = rec->loop_start[0] + rec->buf_size + LOOP_BLANK_SIZE; + if (rec->voices > 1) { + rec->panning[0] = 0xff; + rec->panning[1] = 0x00; + } else + rec->panning[0] = 0x80; + + if (! rec->dram_opened) { + int err, i, ch; + + snd_emux_terminate_all(rec->emu->emu); + if ((err = emu8k_open_dram_for_pcm(rec->emu, rec->voices)) != 0) + return err; + rec->dram_opened = 1; + + /* clear loop blanks */ + snd_emu8000_write_wait(rec->emu, 0); + EMU8000_SMALW_WRITE(rec->emu, rec->offset); + for (i = 0; i < LOOP_BLANK_SIZE; i++) + EMU8000_SMLD_WRITE(rec->emu, 0); + for (ch = 0; ch < rec->voices; ch++) { + EMU8000_SMALW_WRITE(rec->emu, rec->loop_start[ch] + rec->buf_size); + for (i = 0; i < LOOP_BLANK_SIZE; i++) + EMU8000_SMLD_WRITE(rec->emu, 0); + } + } + + setup_voice(rec, 0); + if (rec->voices > 1) + setup_voice(rec, 1); + return 0; +} + +static snd_pcm_uframes_t emu8k_pcm_pointer(snd_pcm_substream_t *subs) +{ + emu8k_pcm_t *rec = subs->runtime->private_data; + if (rec->running) + return emu8k_get_curpos(rec, 0); + return 0; +} + + +static snd_pcm_ops_t emu8k_pcm_ops = { + .open = emu8k_pcm_open, + .close = emu8k_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = emu8k_pcm_hw_params, + .hw_free = emu8k_pcm_hw_free, + .prepare = emu8k_pcm_prepare, + .trigger = emu8k_pcm_trigger, + .pointer = emu8k_pcm_pointer, + .copy = emu8k_pcm_copy, + .silence = emu8k_pcm_silence, +}; + + +static void snd_emu8000_pcm_free(snd_pcm_t *pcm) +{ + emu8000_t *emu = pcm->private_data; + emu->pcm = NULL; +} + +int snd_emu8000_pcm_new(snd_card_t *card, emu8000_t *emu, int index) +{ + snd_pcm_t *pcm; + int err; + + if ((err = snd_pcm_new(card, "Emu8000 PCM", index, 1, 0, &pcm)) < 0) + return err; + pcm->private_data = emu; + pcm->private_free = snd_emu8000_pcm_free; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &emu8k_pcm_ops); + emu->pcm = pcm; + + snd_device_register(card, pcm); + + return 0; +} diff --git a/sound/isa/sb/emu8000_synth.c b/sound/isa/sb/emu8000_synth.c new file mode 100644 index 00000000000..1f63aa52d59 --- /dev/null +++ b/sound/isa/sb/emu8000_synth.c @@ -0,0 +1,134 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * and (c) 1999 Steve Ratcliffe <steve@parabola.demon.co.uk> + * Copyright (C) 1999-2000 Takashi Iwai <tiwai@suse.de> + * + * Emu8000 synth plug-in routine + * + * 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 "emu8000_local.h" +#include <linux/init.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Takashi Iwai, Steve Ratcliffe"); +MODULE_DESCRIPTION("Emu8000 synth plug-in routine"); +MODULE_LICENSE("GPL"); + +/*----------------------------------------------------------------*/ + +/* + * create a new hardware dependent device for Emu8000 + */ +static int snd_emu8000_new_device(snd_seq_device_t *dev) +{ + emu8000_t *hw; + snd_emux_t *emu; + + hw = *(emu8000_t**)SNDRV_SEQ_DEVICE_ARGPTR(dev); + if (hw == NULL) + return -EINVAL; + + if (hw->emu) + return -EBUSY; /* already exists..? */ + + if (snd_emux_new(&emu) < 0) + return -ENOMEM; + + hw->emu = emu; + snd_emu8000_ops_setup(hw); + + emu->hw = hw; + emu->max_voices = EMU8000_DRAM_VOICES; + emu->num_ports = hw->seq_ports; + + if (hw->memhdr) { + snd_printk("memhdr is already initialized!?\n"); + snd_util_memhdr_free(hw->memhdr); + } + hw->memhdr = snd_util_memhdr_new(hw->mem_size); + if (hw->memhdr == NULL) { + snd_emux_free(emu); + hw->emu = NULL; + return -ENOMEM; + } + + emu->memhdr = hw->memhdr; + emu->midi_ports = hw->seq_ports < 2 ? hw->seq_ports : 2; /* number of virmidi ports */ + emu->midi_devidx = 1; + emu->linear_panning = 1; + emu->hwdep_idx = 2; /* FIXED */ + + if (snd_emux_register(emu, dev->card, hw->index, "Emu8000") < 0) { + snd_emux_free(emu); + snd_util_memhdr_free(hw->memhdr); + hw->emu = NULL; + hw->memhdr = NULL; + return -ENOMEM; + } + + if (hw->mem_size > 0) + snd_emu8000_pcm_new(dev->card, hw, 1); + + dev->driver_data = hw; + + return 0; +} + + +/* + * free all resources + */ +static int snd_emu8000_delete_device(snd_seq_device_t *dev) +{ + emu8000_t *hw; + + if (dev->driver_data == NULL) + return 0; /* no synth was allocated actually */ + + hw = dev->driver_data; + if (hw->pcm) + snd_device_free(dev->card, hw->pcm); + if (hw->emu) + snd_emux_free(hw->emu); + if (hw->memhdr) + snd_util_memhdr_free(hw->memhdr); + hw->emu = NULL; + hw->memhdr = NULL; + return 0; +} + +/* + * INIT part + */ + +static int __init alsa_emu8000_init(void) +{ + + static snd_seq_dev_ops_t ops = { + snd_emu8000_new_device, + snd_emu8000_delete_device, + }; + return snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_EMU8000, &ops, sizeof(emu8000_t*)); +} + +static void __exit alsa_emu8000_exit(void) +{ + snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_EMU8000); +} + +module_init(alsa_emu8000_init) +module_exit(alsa_emu8000_exit) diff --git a/sound/isa/sb/es968.c b/sound/isa/sb/es968.c new file mode 100644 index 00000000000..c859917c14d --- /dev/null +++ b/sound/isa/sb/es968.c @@ -0,0 +1,235 @@ + +/* + card-es968.c - driver for ESS AudioDrive ES968 based soundcards. + Copyright (C) 1999 by Massimo Piccioni <dafastidio@libero.it> + + Thanks to Pierfrancesco 'qM2' Passerini. + + 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/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/sb.h> + +#define PFX "es968: " + +MODULE_AUTHOR("Massimo Piccioni <dafastidio@libero.it>"); +MODULE_DESCRIPTION("ESS AudioDrive ES968"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{ESS,AudioDrive ES968}}"); + +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 */ +static long port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* Pnp setup */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* PnP setup */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for es968 based soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for es968 based soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable es968 based soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for es968 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for es968 driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for es968 driver."); + +struct snd_card_es968 { + struct pnp_dev *dev; +}; + +static struct pnp_card_device_id snd_es968_pnpids[] = { + { .id = "ESS0968", .devs = { { "@@@0968" }, } }, + { .id = "", } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_es968_pnpids); + +#define DRIVER_NAME "snd-card-es968" + +static irqreturn_t snd_card_es968_interrupt(int irq, void *dev_id, + struct pt_regs *regs) +{ + sb_t *chip = dev_id; + + if (chip->open & SB_OPEN_PCM) { + return snd_sb8dsp_interrupt(chip); + } else { + return snd_sb8dsp_midi_interrupt(chip); + } +} + +static int __devinit snd_card_es968_pnp(int dev, struct snd_card_es968 *acard, + struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + int err; + if (!cfg) + return -ENOMEM; + acard->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -ENODEV; + } + + pdev = acard->dev; + + pnp_init_resource_table(cfg); + + /* override resources */ + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (dma8[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + kfree(cfg); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + dma8[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + + kfree(cfg); + return 0; +} + +static int __init snd_card_es968_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + int error; + sb_t *chip; + snd_card_t *card; + struct snd_card_es968 *acard; + + if ((card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_es968))) == NULL) + return -ENOMEM; + acard = (struct snd_card_es968 *)card->private_data; + if ((error = snd_card_es968_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return error; + } + snd_card_set_dev(card, &pcard->card->dev); + + if ((error = snd_sbdsp_create(card, port[dev], + irq[dev], + snd_card_es968_interrupt, + dma8[dev], + -1, + SB_HW_AUTO, &chip)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return error; + } + + if ((error = snd_sb8dsp_midi(chip, 0, NULL)) < 0) { + snd_card_free(card); + return error; + } + + strcpy(card->driver, "ES968"); + strcpy(card->shortname, "ESS ES968"); + sprintf(card->longname, "%s soundcard, %s at 0x%lx, irq %d, dma %d", + card->shortname, chip->name, chip->port, irq[dev], dma8[dev]); + + if ((error = snd_card_register(card)) < 0) { + snd_card_free(card); + return error; + } + pnp_set_card_drvdata(pcard, card); + return 0; +} + +static int __devinit snd_es968_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]) + continue; + res = snd_card_es968_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + return -ENODEV; +} + +static void __devexit snd_es968_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 es968_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "es968", + .id_table = snd_es968_pnpids, + .probe = snd_es968_pnp_detect, + .remove = __devexit_p(snd_es968_pnp_remove), +}; + +static int __init alsa_card_es968_init(void) +{ + int cards = pnp_register_card_driver(&es968_pnpc_driver); +#ifdef MODULE + if (cards == 0) { + pnp_unregister_card_driver(&es968_pnpc_driver); + snd_printk(KERN_ERR "no ES968 based soundcards found\n"); + } +#endif + return cards ? 0 : -ENODEV; +} + +static void __exit alsa_card_es968_exit(void) +{ + pnp_unregister_card_driver(&es968_pnpc_driver); +} + +module_init(alsa_card_es968_init) +module_exit(alsa_card_es968_exit) diff --git a/sound/isa/sb/sb16.c b/sound/isa/sb/sb16.c new file mode 100644 index 00000000000..60e2c53c49f --- /dev/null +++ b/sound/isa/sb/sb16.c @@ -0,0 +1,678 @@ +/* + * Driver for SoundBlaster 16/AWE32/AWE64 soundcards + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/sb16_csp.h> +#include <sound/mpu401.h> +#include <sound/opl3.h> +#include <sound/emu8000.h> +#include <sound/seq_device.h> +#define SNDRV_LEGACY_AUTO_PROBE +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +#ifdef SNDRV_SBAWE +#define PFX "sbawe: " +#else +#define PFX "sb16: " +#endif + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_LICENSE("GPL"); +#ifndef SNDRV_SBAWE +MODULE_DESCRIPTION("Sound Blaster 16"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 16}," + "{Creative Labs,SB Vibra16S}," + "{Creative Labs,SB Vibra16C}," + "{Creative Labs,SB Vibra16CL}," + "{Creative Labs,SB Vibra16X}}"); +#else +MODULE_DESCRIPTION("Sound Blaster AWE"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB AWE 32}," + "{Creative Labs,SB AWE 64}," + "{Creative Labs,SB AWE 64 Gold}}"); +#endif + +#if 0 +#define SNDRV_DEBUG_IRQ +#endif + +#if defined(SNDRV_SBAWE) && (defined(CONFIG_SND_SEQUENCER) || (defined(MODULE) && defined(CONFIG_SND_SEQUENCER_MODULE))) +#define SNDRV_SBAWE_EMU8000 +#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; /* 0x220,0x240,0x260,0x280 */ +static long mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x330,0x300 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#ifdef SNDRV_SBAWE_EMU8000 +static long awe_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; +#endif +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ +static int dma16[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 5,6,7 */ +static int mic_agc[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +#ifdef CONFIG_SND_SB16_CSP +static int csp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0}; +#endif +#ifdef SNDRV_SBAWE_EMU8000 +static int seq_ports[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 4}; +#endif + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for SoundBlaster 16 soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for SoundBlaster 16 soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable SoundBlaster 16 soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "PnP detection for specified soundcard."); +#endif +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SB16 driver."); +module_param_array(mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(mpu_port, "MPU-401 port # for SB16 driver."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port # for SB16 PnP driver."); +#ifdef SNDRV_SBAWE_EMU8000 +module_param_array(awe_port, long, NULL, 0444); +MODULE_PARM_DESC(awe_port, "AWE port # for SB16 PnP driver."); +#endif +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SB16 driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for SB16 driver."); +module_param_array(dma16, int, NULL, 0444); +MODULE_PARM_DESC(dma16, "16-bit DMA # for SB16 driver."); +module_param_array(mic_agc, int, NULL, 0444); +MODULE_PARM_DESC(mic_agc, "Mic Auto-Gain-Control switch."); +#ifdef CONFIG_SND_SB16_CSP +module_param_array(csp, int, NULL, 0444); +MODULE_PARM_DESC(csp, "ASP/CSP chip support."); +#endif +#ifdef SNDRV_SBAWE_EMU8000 +module_param_array(seq_ports, int, NULL, 0444); +MODULE_PARM_DESC(seq_ports, "Number of sequencer ports for WaveTable synth."); +#endif + +struct snd_card_sb16 { + struct resource *fm_res; /* used to block FM i/o region for legacy cards */ +#ifdef CONFIG_PNP + int dev_no; + struct pnp_dev *dev; +#ifdef SNDRV_SBAWE_EMU8000 + struct pnp_dev *devwt; +#endif +#endif +}; + +static snd_card_t *snd_sb16_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_sb16_pnpids[] = { +#ifndef SNDRV_SBAWE + /* Sound Blaster 16 PnP */ + { .id = "CTL0024", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0025", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0026", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0027", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0028", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL0029", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL002a", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL002b", .devs = { { "CTL0031" } } }, + /* Sound Blaster 16 PnP */ + { .id = "CTL002c", .devs = { { "CTL0031" } } }, + /* Sound Blaster Vibra16S */ + { .id = "CTL0051", .devs = { { "CTL0001" } } }, + /* Sound Blaster Vibra16C */ + { .id = "CTL0070", .devs = { { "CTL0001" } } }, + /* Sound Blaster Vibra16CL - added by ctm@ardi.com */ + { .id = "CTL0080", .devs = { { "CTL0041" } } }, + /* Sound Blaster 16 'value' PnP. It says model ct4130 on the pcb, */ + /* but ct4131 on a sticker on the board.. */ + { .id = "CTL0086", .devs = { { "CTL0041" } } }, + /* Sound Blaster Vibra16X */ + { .id = "CTL00f0", .devs = { { "CTL0043" } } }, +#else /* SNDRV_SBAWE defined */ + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0035", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0039", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0042", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0043", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL0044", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + /* Note: This card has also a CTL0051:StereoEnhance device!!! */ + { .id = "CTL0045", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0046", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0047", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0048", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL0054", .devs = { { "CTL0031" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL009a", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster AWE 32 PnP */ + { .id = "CTL009c", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster 32 PnP */ + { .id = "CTL009f", .devs = { { "CTL0041" }, { "CTL0021" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL009d", .devs = { { "CTL0042" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP Gold */ + { .id = "CTL009e", .devs = { { "CTL0044" }, { "CTL0023" } } }, + /* Sound Blaster AWE 64 PnP Gold */ + { .id = "CTL00b2", .devs = { { "CTL0044" }, { "CTL0023" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c1", .devs = { { "CTL0042" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c3", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c5", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00c7", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00e4", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster AWE 64 PnP */ + { .id = "CTL00e9", .devs = { { "CTL0045" }, { "CTL0022" } } }, + /* Sound Blaster 16 PnP (AWE) */ + { .id = "CTL00ed", .devs = { { "CTL0041" }, { "CTL0070" } } }, + /* Generic entries */ + { .id = "CTLXXXX" , .devs = { { "CTL0031" }, { "CTL0021" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0041" }, { "CTL0021" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0042" }, { "CTL0022" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0044" }, { "CTL0023" } } }, + { .id = "CTLXXXX" , .devs = { { "CTL0045" }, { "CTL0022" } } }, +#endif /* SNDRV_SBAWE */ + /* Sound Blaster 16 PnP (Virtual PC 2004)*/ + { .id = "tBA03b0", .devs = { { "PNPb003" } } }, + { .id = "", } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_sb16_pnpids); + +#endif /* CONFIG_PNP */ + +#ifdef SNDRV_SBAWE_EMU8000 +#define DRIVER_NAME "snd-card-sbawe" +#else +#define DRIVER_NAME "snd-card-sb16" +#endif + +#ifdef CONFIG_PNP + +static int __devinit snd_card_sb16_pnp(int dev, struct snd_card_sb16 *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->dev = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->dev == NULL) { + kfree(cfg); + return -ENODEV; + } +#ifdef SNDRV_SBAWE_EMU8000 + acard->devwt = pnp_request_card_device(card, id->devs[1].id, acard->dev); +#endif + /* Audio initialization */ + pdev = acard->dev; + + pnp_init_resource_table(cfg); + + /* override resources */ + + if (port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], port[dev], 16); + if (mpu_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], mpu_port[dev], 2); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[2], fm_port[dev], 4); + if (dma8[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[0], dma8[dev], 1); + if (dma16[dev] != SNDRV_AUTO_DMA) + pnp_resource_change(&cfg->dma_resource[1], dma16[dev], 1); + if (irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], irq[dev], 1); + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR PFX "AUDIO the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR PFX "AUDIO pnp configure failure\n"); + kfree(cfg); + return err; + } + port[dev] = pnp_port_start(pdev, 0); + mpu_port[dev] = pnp_port_start(pdev, 1); + fm_port[dev] = pnp_port_start(pdev, 2); + dma8[dev] = pnp_dma(pdev, 0); + dma16[dev] = pnp_dma(pdev, 1); + irq[dev] = pnp_irq(pdev, 0); + snd_printdd("pnp SB16: port=0x%lx, mpu port=0x%lx, fm port=0x%lx\n", + port[dev], mpu_port[dev], fm_port[dev]); + snd_printdd("pnp SB16: dma1=%i, dma2=%i, irq=%i\n", + dma8[dev], dma16[dev], irq[dev]); +#ifdef SNDRV_SBAWE_EMU8000 + /* WaveTable initialization */ + pdev = acard->devwt; + if (pdev != NULL) { + pnp_init_resource_table(cfg); + + /* override resources */ + + if (awe_port[dev] != SNDRV_AUTO_PORT) { + pnp_resource_change(&cfg->port_resource[0], awe_port[dev], 4); + pnp_resource_change(&cfg->port_resource[1], awe_port[dev] + 0x400, 4); + pnp_resource_change(&cfg->port_resource[2], awe_port[dev] + 0x800, 4); + } + if ((pnp_manual_config_dev(pdev, cfg, 0)) < 0) + snd_printk(KERN_ERR PFX "WaveTable the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + goto __wt_error; + } + awe_port[dev] = pnp_port_start(pdev, 0); + snd_printdd("pnp SB16: wavetable port=0x%lx\n", pnp_port_start(pdev, 0)); + } else { +__wt_error: + if (pdev) { + pnp_release_card_device(pdev); + snd_printk(KERN_ERR PFX "WaveTable pnp configure failure\n"); + } + acard->devwt = NULL; + awe_port[dev] = -1; + } +#endif + kfree(cfg); + return 0; +} + +#endif /* CONFIG_PNP */ + +static void snd_sb16_free(snd_card_t *card) +{ + struct snd_card_sb16 *acard = (struct snd_card_sb16 *)card->private_data; + + if (acard == NULL) + return; + if (acard->fm_res) { + release_resource(acard->fm_res); + kfree_nocheck(acard->fm_res); + } +} + +static int __init snd_sb16_probe(int dev, + struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + static int possible_irqs[] = {5, 9, 10, 7, -1}; + static int possible_dmas8[] = {1, 3, 0, -1}; + static int possible_dmas16[] = {5, 6, 7, -1}; + int xirq, xdma8, xdma16; + sb_t *chip; + snd_card_t *card; + struct snd_card_sb16 *acard; + opl3_t *opl3; + snd_hwdep_t *synth = NULL; +#ifdef CONFIG_SND_SB16_CSP + snd_hwdep_t *xcsp = NULL; +#endif + unsigned long flags; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_card_sb16)); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_card_sb16 *) card->private_data; + card->private_free = snd_sb16_free; +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if ((err = snd_card_sb16_pnp(dev, acard, pcard, pid))) { + snd_card_free(card); + return err; + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif + + xirq = irq[dev]; + xdma8 = dma8[dev]; + xdma16 = dma16[dev]; +#ifdef CONFIG_PNP + if (!isapnp[dev]) { +#endif + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR PFX "unable to find a free IRQ\n"); + return -EBUSY; + } + } + if (xdma8 == SNDRV_AUTO_DMA) { + if ((xdma8 = snd_legacy_find_free_dma(possible_dmas8)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR PFX "unable to find a free 8-bit DMA\n"); + return -EBUSY; + } + } + if (xdma16 == SNDRV_AUTO_DMA) { + if ((xdma16 = snd_legacy_find_free_dma(possible_dmas16)) < 0) { + snd_card_free(card); + snd_printk(KERN_ERR PFX "unable to find a free 16-bit DMA\n"); + return -EBUSY; + } + } + /* non-PnP FM port address is hardwired with base port address */ + fm_port[dev] = port[dev]; + /* block the 0x388 port to avoid PnP conflicts */ + acard->fm_res = request_region(0x388, 4, "SoundBlaster FM"); +#ifdef SNDRV_SBAWE_EMU8000 + /* non-PnP AWE port address is hardwired with base port address */ + awe_port[dev] = port[dev] + 0x400; +#endif +#ifdef CONFIG_PNP + } +#endif + + if ((err = snd_sbdsp_create(card, + port[dev], + xirq, + snd_sb16dsp_interrupt, + xdma8, + xdma16, + SB_HW_AUTO, + &chip)) < 0) { + snd_card_free(card); + return err; + } + if (chip->hardware != SB_HW_16) { + snd_card_free(card); + snd_printdd("SB 16 chip was not detected at 0x%lx\n", port[dev]); + return -ENODEV; + } + chip->mpu_port = mpu_port[dev]; +#ifdef CONFIG_PNP + if (!isapnp[dev] && (err = snd_sb16dsp_configure(chip)) < 0) { +#else + if ((err = snd_sb16dsp_configure(chip)) < 0) { +#endif + snd_card_free(card); + return -ENXIO; + } + if ((err = snd_sb16dsp_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return -ENXIO; + } + + strcpy(card->driver, +#ifdef SNDRV_SBAWE_EMU8000 + awe_port[dev] > 0 ? "SB AWE" : +#endif + "SB16"); + strcpy(card->shortname, chip->name); + sprintf(card->longname, "%s at 0x%lx, irq %i, dma ", + chip->name, + chip->port, + xirq); + if (xdma8 >= 0) + sprintf(card->longname + strlen(card->longname), "%d", xdma8); + if (xdma16 >= 0) + sprintf(card->longname + strlen(card->longname), "%s%d", + xdma8 >= 0 ? "&" : "", xdma16); + + if (chip->mpu_port > 0 && chip->mpu_port != SNDRV_AUTO_PORT) { + if ((err = snd_mpu401_uart_new(card, 0, MPU401_HW_SB, + chip->mpu_port, 0, + xirq, 0, &chip->rmidi)) < 0) { + snd_card_free(card); + return -ENXIO; + } + chip->rmidi_callback = snd_mpu401_uart_interrupt; + } + +#ifdef SNDRV_SBAWE_EMU8000 + if (awe_port[dev] == SNDRV_AUTO_PORT) + awe_port[dev] = 0; /* disable */ +#endif + + 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, + acard->fm_res != NULL || fm_port[dev] == port[dev], + &opl3) < 0) { + snd_printk(KERN_ERR PFX "no OPL device at 0x%lx-0x%lx\n", + fm_port[dev], fm_port[dev] + 2); + } else { +#ifdef SNDRV_SBAWE_EMU8000 + int seqdev = awe_port[dev] > 0 ? 2 : 1; +#else + int seqdev = 1; +#endif + if ((err = snd_opl3_hwdep_new(opl3, 0, seqdev, &synth)) < 0) { + snd_card_free(card); + return -ENXIO; + } + } + } + + if ((err = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return -ENXIO; + } + +#ifdef CONFIG_SND_SB16_CSP + /* CSP chip on SB16ASP/AWE32 */ + if ((chip->hardware == SB_HW_16) && csp[dev]) { + snd_sb_csp_new(chip, synth != NULL ? 1 : 0, &xcsp); + if (xcsp) { + chip->csp = xcsp->private_data; + chip->hardware = SB_HW_16CSP; + } else { + snd_printk(KERN_INFO PFX "warning - CSP chip not detected on soundcard #%i\n", dev + 1); + } + } +#endif +#ifdef SNDRV_SBAWE_EMU8000 + if (awe_port[dev] > 0) { + if (snd_emu8000_new(card, 1, awe_port[dev], + seq_ports[dev], NULL) < 0) { + snd_printk(KERN_ERR PFX "fatal error - EMU-8000 synthesizer not detected at 0x%lx\n", awe_port[dev]); + snd_card_free(card); + return -ENXIO; + } + } +#endif + + /* setup Mic AGC */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, SB_DSP4_MIC_AGC, + (snd_sbmixer_read(chip, SB_DSP4_MIC_AGC) & 0x01) | + (mic_agc[dev] ? 0x00 : 0x01)); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_sb16_legacy[dev] = card; + return 0; +} + +static int __init snd_sb16_probe_legacy_port(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + port[dev] = xport; + res = snd_sb16_probe(dev, NULL, NULL); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +#ifdef CONFIG_PNP + +static int __devinit snd_sb16_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_sb16_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + + return -ENODEV; +} + +static void __devexit snd_sb16_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 sb16_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "sb16", + .id_table = snd_sb16_pnpids, + .probe = snd_sb16_pnp_detect, + .remove = __devexit_p(snd_sb16_pnp_remove), +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_sb16_init(void) +{ + int dev, cards = 0, i; + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, 0x280, -1}; + + /* legacy non-auto cards at first */ + for (dev = 0; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] == SNDRV_AUTO_PORT) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + if (!snd_sb16_probe(dev, NULL, NULL)) { + cards++; + continue; + } +#ifdef MODULE + snd_printk(KERN_ERR "Sound Blaster 16+ soundcard #%i not found at 0x%lx or device busy\n", dev, port[dev]); +#endif + } + /* legacy auto configured cards */ + i = snd_legacy_auto_probe(possible_ports, snd_sb16_probe_legacy_port); + if (i > 0) + cards += i; + +#ifdef CONFIG_PNP + /* PnP cards at last */ + i = pnp_register_card_driver(&sb16_pnpc_driver); + if (i >0) + cards += i; +#endif + + if (!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&sb16_pnpc_driver); +#endif +#ifdef MODULE + snd_printk(KERN_ERR "Sound Blaster 16 soundcard not found or device busy\n"); +#ifdef SNDRV_SBAWE_EMU8000 + snd_printk(KERN_ERR "In case, if you have non-AWE card, try snd-sb16 module\n"); +#else + snd_printk(KERN_ERR "In case, if you have AWE card, try snd-sbawe module\n"); +#endif +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_sb16_exit(void) +{ + int dev; + +#ifdef CONFIG_PNP + /* PnP cards first */ + pnp_unregister_card_driver(&sb16_pnpc_driver); +#endif + for (dev = 0; dev < SNDRV_CARDS; dev++) + snd_card_free(snd_sb16_legacy[dev]); +} + +module_init(alsa_card_sb16_init) +module_exit(alsa_card_sb16_exit) diff --git a/sound/isa/sb/sb16_csp.c b/sound/isa/sb/sb16_csp.c new file mode 100644 index 00000000000..b62920eead3 --- /dev/null +++ b/sound/isa/sb/sb16_csp.c @@ -0,0 +1,1175 @@ +/* + * Copyright (c) 1999 by Uros Bizjak <uros@kss-loka.si> + * Takashi Iwai <tiwai@suse.de> + * + * SB16ASP/AWE32 CSP control + * + * CSP microcode loader: + * alsa-tools/sb16_csp/ + * + * 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/init.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/info.h> +#include <sound/sb16_csp.h> +#include <sound/initval.h> + +MODULE_AUTHOR("Uros Bizjak <uros@kss-loka.si>"); +MODULE_DESCRIPTION("ALSA driver for SB16 Creative Signal Processor"); +MODULE_LICENSE("GPL"); + +#ifdef SNDRV_LITTLE_ENDIAN +#define CSP_HDR_VALUE(a,b,c,d) ((a) | ((b)<<8) | ((c)<<16) | ((d)<<24)) +#else +#define CSP_HDR_VALUE(a,b,c,d) ((d) | ((c)<<8) | ((b)<<16) | ((a)<<24)) +#endif +#define LE_SHORT(v) le16_to_cpu(v) +#define LE_INT(v) le32_to_cpu(v) + +#define RIFF_HEADER CSP_HDR_VALUE('R', 'I', 'F', 'F') +#define CSP__HEADER CSP_HDR_VALUE('C', 'S', 'P', ' ') +#define LIST_HEADER CSP_HDR_VALUE('L', 'I', 'S', 'T') +#define FUNC_HEADER CSP_HDR_VALUE('f', 'u', 'n', 'c') +#define CODE_HEADER CSP_HDR_VALUE('c', 'o', 'd', 'e') +#define INIT_HEADER CSP_HDR_VALUE('i', 'n', 'i', 't') +#define MAIN_HEADER CSP_HDR_VALUE('m', 'a', 'i', 'n') + +/* + * RIFF data format + */ +typedef struct riff_header { + __u32 name; + __u32 len; +} riff_header_t; + +typedef struct desc_header { + riff_header_t info; + __u16 func_nr; + __u16 VOC_type; + __u16 flags_play_rec; + __u16 flags_16bit_8bit; + __u16 flags_stereo_mono; + __u16 flags_rates; +} desc_header_t; + +/* + * prototypes + */ +static void snd_sb_csp_free(snd_hwdep_t *hw); +static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file); +static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg); +static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file); + +static int csp_detect(sb_t *chip, int *version); +static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val); +static int set_register(sb_t *chip, unsigned char reg, unsigned char val); +static int read_register(sb_t *chip, unsigned char reg); +static int set_mode_register(sb_t *chip, unsigned char mode); +static int get_version(sb_t *chip); + +static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * code); +static int snd_sb_csp_unload(snd_sb_csp_t * p); +static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags); +static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode); +static int snd_sb_csp_check_version(snd_sb_csp_t * p); + +static int snd_sb_csp_use(snd_sb_csp_t * p); +static int snd_sb_csp_unuse(snd_sb_csp_t * p); +static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels); +static int snd_sb_csp_stop(snd_sb_csp_t * p); +static int snd_sb_csp_pause(snd_sb_csp_t * p); +static int snd_sb_csp_restart(snd_sb_csp_t * p); + +static int snd_sb_qsound_build(snd_sb_csp_t * p); +static void snd_sb_qsound_destroy(snd_sb_csp_t * p); +static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p); + +static int init_proc_entry(snd_sb_csp_t * p, int device); +static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer); + +/* + * Detect CSP chip and create a new instance + */ +int snd_sb_csp_new(sb_t *chip, int device, snd_hwdep_t ** rhwdep) +{ + snd_sb_csp_t *p; + int version, err; + snd_hwdep_t *hw; + + if (rhwdep) + *rhwdep = NULL; + + if (csp_detect(chip, &version)) + return -ENODEV; + + if ((err = snd_hwdep_new(chip->card, "SB16-CSP", device, &hw)) < 0) + return err; + + if ((p = kcalloc(1, sizeof(*p), GFP_KERNEL)) == NULL) { + snd_device_free(chip->card, hw); + return -ENOMEM; + } + p->chip = chip; + p->version = version; + + /* CSP operators */ + p->ops.csp_use = snd_sb_csp_use; + p->ops.csp_unuse = snd_sb_csp_unuse; + p->ops.csp_autoload = snd_sb_csp_autoload; + p->ops.csp_start = snd_sb_csp_start; + p->ops.csp_stop = snd_sb_csp_stop; + p->ops.csp_qsound_transfer = snd_sb_csp_qsound_transfer; + + init_MUTEX(&p->access_mutex); + sprintf(hw->name, "CSP v%d.%d", (version >> 4), (version & 0x0f)); + hw->iface = SNDRV_HWDEP_IFACE_SB16CSP; + hw->private_data = p; + hw->private_free = snd_sb_csp_free; + + /* operators - only write/ioctl */ + hw->ops.open = snd_sb_csp_open; + hw->ops.ioctl = snd_sb_csp_ioctl; + hw->ops.release = snd_sb_csp_release; + + /* create a proc entry */ + init_proc_entry(p, device); + if (rhwdep) + *rhwdep = hw; + return 0; +} + +/* + * free_private for hwdep instance + */ +static void snd_sb_csp_free(snd_hwdep_t *hwdep) +{ + snd_sb_csp_t *p = hwdep->private_data; + if (p) { + if (p->running & SNDRV_SB_CSP_ST_RUNNING) + snd_sb_csp_stop(p); + kfree(p); + } +} + +/* ------------------------------ */ + +/* + * open the device exclusively + */ +static int snd_sb_csp_open(snd_hwdep_t * hw, struct file *file) +{ + snd_sb_csp_t *p = hw->private_data; + return (snd_sb_csp_use(p)); +} + +/* + * ioctl for hwdep device: + */ +static int snd_sb_csp_ioctl(snd_hwdep_t * hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + snd_sb_csp_t *p = hw->private_data; + snd_sb_csp_info_t info; + snd_sb_csp_start_t start_info; + int err; + + snd_assert(p != NULL, return -EINVAL); + + if (snd_sb_csp_check_version(p)) + return -ENODEV; + + switch (cmd) { + /* get information */ + case SNDRV_SB_CSP_IOCTL_INFO: + *info.codec_name = *p->codec_name; + info.func_nr = p->func_nr; + info.acc_format = p->acc_format; + info.acc_channels = p->acc_channels; + info.acc_width = p->acc_width; + info.acc_rates = p->acc_rates; + info.csp_mode = p->mode; + info.run_channels = p->run_channels; + info.run_width = p->run_width; + info.version = p->version; + info.state = p->running; + if (copy_to_user((void __user *)arg, &info, sizeof(info))) + err = -EFAULT; + else + err = 0; + break; + + /* load CSP microcode */ + case SNDRV_SB_CSP_IOCTL_LOAD_CODE: + err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? + -EBUSY : snd_sb_csp_riff_load(p, (snd_sb_csp_microcode_t __user *) arg)); + break; + case SNDRV_SB_CSP_IOCTL_UNLOAD_CODE: + err = (p->running & SNDRV_SB_CSP_ST_RUNNING ? + -EBUSY : snd_sb_csp_unload(p)); + break; + + /* change CSP running state */ + case SNDRV_SB_CSP_IOCTL_START: + if (copy_from_user(&start_info, (void __user *) arg, sizeof(start_info))) + err = -EFAULT; + else + err = snd_sb_csp_start(p, start_info.sample_width, start_info.channels); + break; + case SNDRV_SB_CSP_IOCTL_STOP: + err = snd_sb_csp_stop(p); + break; + case SNDRV_SB_CSP_IOCTL_PAUSE: + err = snd_sb_csp_pause(p); + break; + case SNDRV_SB_CSP_IOCTL_RESTART: + err = snd_sb_csp_restart(p); + break; + default: + err = -ENOTTY; + break; + } + + return err; +} + +/* + * close the device + */ +static int snd_sb_csp_release(snd_hwdep_t * hw, struct file *file) +{ + snd_sb_csp_t *p = hw->private_data; + return (snd_sb_csp_unuse(p)); +} + +/* ------------------------------ */ + +/* + * acquire device + */ +static int snd_sb_csp_use(snd_sb_csp_t * p) +{ + down(&p->access_mutex); + if (p->used) { + up(&p->access_mutex); + return -EAGAIN; + } + p->used++; + up(&p->access_mutex); + + return 0; + +} + +/* + * release device + */ +static int snd_sb_csp_unuse(snd_sb_csp_t * p) +{ + down(&p->access_mutex); + p->used--; + up(&p->access_mutex); + + return 0; +} + +/* + * load microcode via ioctl: + * code is user-space pointer + */ +static int snd_sb_csp_riff_load(snd_sb_csp_t * p, snd_sb_csp_microcode_t __user * mcode) +{ + snd_sb_csp_mc_header_t info; + + unsigned char __user *data_ptr; + unsigned char __user *data_end; + unsigned short func_nr = 0; + + riff_header_t file_h, item_h, code_h; + __u32 item_type; + desc_header_t funcdesc_h; + + unsigned long flags; + int err; + + if (copy_from_user(&info, mcode, sizeof(info))) + return -EFAULT; + data_ptr = mcode->data; + + if (copy_from_user(&file_h, data_ptr, sizeof(file_h))) + return -EFAULT; + if ((file_h.name != RIFF_HEADER) || + (LE_INT(file_h.len) >= SNDRV_SB_CSP_MAX_MICROCODE_FILE_SIZE - sizeof(file_h))) { + snd_printd("%s: Invalid RIFF header\n", __FUNCTION__); + return -EINVAL; + } + data_ptr += sizeof(file_h); + data_end = data_ptr + LE_INT(file_h.len); + + if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) + return -EFAULT; + if (item_type != CSP__HEADER) { + snd_printd("%s: Invalid RIFF file type\n", __FUNCTION__); + return -EINVAL; + } + data_ptr += sizeof (item_type); + + for (; data_ptr < data_end; data_ptr += LE_INT(item_h.len)) { + if (copy_from_user(&item_h, data_ptr, sizeof(item_h))) + return -EFAULT; + data_ptr += sizeof(item_h); + if (item_h.name != LIST_HEADER) + continue; + + if (copy_from_user(&item_type, data_ptr, sizeof(item_type))) + return -EFAULT; + switch (item_type) { + case FUNC_HEADER: + if (copy_from_user(&funcdesc_h, data_ptr + sizeof(item_type), sizeof(funcdesc_h))) + return -EFAULT; + func_nr = LE_SHORT(funcdesc_h.func_nr); + break; + case CODE_HEADER: + if (func_nr != info.func_req) + break; /* not required function, try next */ + data_ptr += sizeof(item_type); + + /* destroy QSound mixer element */ + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_sb_qsound_destroy(p); + } + /* Clear all flags */ + p->running = 0; + p->mode = 0; + + /* load microcode blocks */ + for (;;) { + if (data_ptr >= data_end) + return -EINVAL; + if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) + return -EFAULT; + + /* init microcode blocks */ + if (code_h.name != INIT_HEADER) + break; + data_ptr += sizeof(code_h); + err = snd_sb_csp_load_user(p, data_ptr, LE_INT(code_h.len), + SNDRV_SB_CSP_LOAD_INITBLOCK); + if (err) + return err; + data_ptr += LE_INT(code_h.len); + } + /* main microcode block */ + if (copy_from_user(&code_h, data_ptr, sizeof(code_h))) + return -EFAULT; + + if (code_h.name != MAIN_HEADER) { + snd_printd("%s: Missing 'main' microcode\n", __FUNCTION__); + return -EINVAL; + } + data_ptr += sizeof(code_h); + err = snd_sb_csp_load_user(p, data_ptr, + LE_INT(code_h.len), 0); + if (err) + return err; + + /* fill in codec header */ + strlcpy(p->codec_name, info.codec_name, sizeof(p->codec_name)); + p->func_nr = func_nr; + p->mode = LE_SHORT(funcdesc_h.flags_play_rec); + switch (LE_SHORT(funcdesc_h.VOC_type)) { + case 0x0001: /* QSound decoder */ + if (LE_SHORT(funcdesc_h.flags_play_rec) == SNDRV_SB_CSP_MODE_DSP_WRITE) { + if (snd_sb_qsound_build(p) == 0) + /* set QSound flag and clear all other mode flags */ + p->mode = SNDRV_SB_CSP_MODE_QSOUND; + } + p->acc_format = 0; + break; + case 0x0006: /* A Law codec */ + p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; + break; + case 0x0007: /* Mu Law codec */ + p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; + break; + case 0x0011: /* what Creative thinks is IMA ADPCM codec */ + case 0x0200: /* Creative ADPCM codec */ + p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; + break; + case 201: /* Text 2 Speech decoder */ + /* TODO: Text2Speech handling routines */ + p->acc_format = 0; + break; + case 0x0202: /* Fast Speech 8 codec */ + case 0x0203: /* Fast Speech 10 codec */ + p->acc_format = SNDRV_PCM_FMTBIT_SPECIAL; + break; + default: /* other codecs are unsupported */ + p->acc_format = p->acc_width = p->acc_rates = 0; + p->mode = 0; + snd_printd("%s: Unsupported CSP codec type: 0x%04x\n", + __FUNCTION__, + LE_SHORT(funcdesc_h.VOC_type)); + return -EINVAL; + } + p->acc_channels = LE_SHORT(funcdesc_h.flags_stereo_mono); + p->acc_width = LE_SHORT(funcdesc_h.flags_16bit_8bit); + p->acc_rates = LE_SHORT(funcdesc_h.flags_rates); + + /* Decouple CSP from IRQ and DMAREQ lines */ + spin_lock_irqsave(&p->chip->reg_lock, flags); + set_mode_register(p->chip, 0xfc); + set_mode_register(p->chip, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + + /* finished loading successfully */ + p->running = SNDRV_SB_CSP_ST_LOADED; /* set LOADED flag */ + return 0; + } + } + snd_printd("%s: Function #%d not found\n", __FUNCTION__, info.func_req); + return -EINVAL; +} + +/* + * unload CSP microcode + */ +static int snd_sb_csp_unload(snd_sb_csp_t * p) +{ + if (p->running & SNDRV_SB_CSP_ST_RUNNING) + return -EBUSY; + if (!(p->running & SNDRV_SB_CSP_ST_LOADED)) + return -ENXIO; + + /* clear supported formats */ + p->acc_format = 0; + p->acc_channels = p->acc_width = p->acc_rates = 0; + /* destroy QSound mixer element */ + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_sb_qsound_destroy(p); + } + /* clear all flags */ + p->running = 0; + p->mode = 0; + return 0; +} + +/* + * send command sequence to DSP + */ +static inline int command_seq(sb_t *chip, const unsigned char *seq, int size) +{ + int i; + for (i = 0; i < size; i++) { + if (!snd_sbdsp_command(chip, seq[i])) + return -EIO; + } + return 0; +} + +/* + * set CSP codec parameter + */ +static int set_codec_parameter(sb_t *chip, unsigned char par, unsigned char val) +{ + unsigned char dsp_cmd[3]; + + dsp_cmd[0] = 0x05; /* CSP set codec parameter */ + dsp_cmd[1] = val; /* Parameter value */ + dsp_cmd[2] = par; /* Parameter */ + command_seq(chip, dsp_cmd, 3); + snd_sbdsp_command(chip, 0x03); /* DSP read? */ + if (snd_sbdsp_get_byte(chip) != par) + return -EIO; + return 0; +} + +/* + * set CSP register + */ +static int set_register(sb_t *chip, unsigned char reg, unsigned char val) +{ + unsigned char dsp_cmd[3]; + + dsp_cmd[0] = 0x0e; /* CSP set register */ + dsp_cmd[1] = reg; /* CSP Register */ + dsp_cmd[2] = val; /* value */ + return command_seq(chip, dsp_cmd, 3); +} + +/* + * read CSP register + * return < 0 -> error + */ +static int read_register(sb_t *chip, unsigned char reg) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x0f; /* CSP read register */ + dsp_cmd[1] = reg; /* CSP Register */ + command_seq(chip, dsp_cmd, 2); + return snd_sbdsp_get_byte(chip); /* Read DSP value */ +} + +/* + * set CSP mode register + */ +static int set_mode_register(sb_t *chip, unsigned char mode) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x04; /* CSP set mode register */ + dsp_cmd[1] = mode; /* mode */ + return command_seq(chip, dsp_cmd, 2); +} + +/* + * Detect CSP + * return 0 if CSP exists. + */ +static int csp_detect(sb_t *chip, int *version) +{ + unsigned char csp_test1, csp_test2; + unsigned long flags; + int result = -ENODEV; + + spin_lock_irqsave(&chip->reg_lock, flags); + + set_codec_parameter(chip, 0x00, 0x00); + set_mode_register(chip, 0xfc); /* 0xfc = ?? */ + + csp_test1 = read_register(chip, 0x83); + set_register(chip, 0x83, ~csp_test1); + csp_test2 = read_register(chip, 0x83); + if (csp_test2 != (csp_test1 ^ 0xff)) + goto __fail; + + set_register(chip, 0x83, csp_test1); + csp_test2 = read_register(chip, 0x83); + if (csp_test2 != csp_test1) + goto __fail; + + set_mode_register(chip, 0x00); /* 0x00 = ? */ + + *version = get_version(chip); + snd_sbdsp_reset(chip); /* reset DSP after getversion! */ + if (*version >= 0x10 && *version <= 0x1f) + result = 0; /* valid version id */ + + __fail: + spin_unlock_irqrestore(&chip->reg_lock, flags); + return result; +} + +/* + * get CSP version number + */ +static int get_version(sb_t *chip) +{ + unsigned char dsp_cmd[2]; + + dsp_cmd[0] = 0x08; /* SB_DSP_!something! */ + dsp_cmd[1] = 0x03; /* get chip version id? */ + command_seq(chip, dsp_cmd, 2); + + return (snd_sbdsp_get_byte(chip)); +} + +/* + * check if the CSP version is valid + */ +static int snd_sb_csp_check_version(snd_sb_csp_t * p) +{ + if (p->version < 0x10 || p->version > 0x1f) { + snd_printd("%s: Invalid CSP version: 0x%x\n", __FUNCTION__, p->version); + return 1; + } + return 0; +} + +/* + * download microcode to CSP (microcode should have one "main" block). + */ +static int snd_sb_csp_load(snd_sb_csp_t * p, const unsigned char *buf, int size, int load_flags) +{ + int status, i; + int err; + int result = -EIO; + unsigned long flags; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + snd_sbdsp_command(p->chip, 0x01); /* CSP download command */ + if (snd_sbdsp_get_byte(p->chip)) { + snd_printd("%s: Download command failed\n", __FUNCTION__); + goto __fail; + } + /* Send CSP low byte (size - 1) */ + snd_sbdsp_command(p->chip, (unsigned char)(size - 1)); + /* Send high byte */ + snd_sbdsp_command(p->chip, (unsigned char)((size - 1) >> 8)); + /* send microcode sequence */ + /* load from kernel space */ + while (size--) { + if (!snd_sbdsp_command(p->chip, *buf++)) + goto __fail; + } + if (snd_sbdsp_get_byte(p->chip)) + goto __fail; + + if (load_flags & SNDRV_SB_CSP_LOAD_INITBLOCK) { + i = 0; + /* some codecs (FastSpeech) take some time to initialize */ + while (1) { + snd_sbdsp_command(p->chip, 0x03); + status = snd_sbdsp_get_byte(p->chip); + if (status == 0x55 || ++i >= 10) + break; + udelay (10); + } + if (status != 0x55) { + snd_printd("%s: Microcode initialization failed\n", __FUNCTION__); + goto __fail; + } + } else { + /* + * Read mixer register SB_DSP4_DMASETUP after loading 'main' code. + * Start CSP chip if no 16bit DMA channel is set - some kind + * of autorun or perhaps a bugfix? + */ + spin_lock(&p->chip->mixer_lock); + status = snd_sbmixer_read(p->chip, SB_DSP4_DMASETUP); + spin_unlock(&p->chip->mixer_lock); + if (!(status & (SB_DMASETUP_DMA7 | SB_DMASETUP_DMA6 | SB_DMASETUP_DMA5))) { + err = (set_codec_parameter(p->chip, 0xaa, 0x00) || + set_codec_parameter(p->chip, 0xff, 0x00)); + snd_sbdsp_reset(p->chip); /* really! */ + if (err) + goto __fail; + set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + set_mode_register(p->chip, 0x70); /* 70 = RUN */ + } + } + result = 0; + + __fail: + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + return result; +} + +static int snd_sb_csp_load_user(snd_sb_csp_t * p, const unsigned char __user *buf, int size, int load_flags) +{ + int err = -ENOMEM; + unsigned char *kbuf = kmalloc(size, GFP_KERNEL); + if (kbuf) { + if (copy_from_user(kbuf, buf, size)) + err = -EFAULT; + else + err = snd_sb_csp_load(p, kbuf, size, load_flags); + kfree(kbuf); + } + return err; +} + +#include "sb16_csp_codecs.h" + +/* + * autoload hardware codec if necessary + * return 0 if CSP is loaded and ready to run (p->running != 0) + */ +static int snd_sb_csp_autoload(snd_sb_csp_t * p, int pcm_sfmt, int play_rec_mode) +{ + unsigned long flags; + int err = 0; + + /* if CSP is running or manually loaded then exit */ + if (p->running & (SNDRV_SB_CSP_ST_RUNNING | SNDRV_SB_CSP_ST_LOADED)) + return -EBUSY; + + /* autoload microcode only if requested hardware codec is not already loaded */ + if (((1 << pcm_sfmt) & p->acc_format) && (play_rec_mode & p->mode)) { + p->running = SNDRV_SB_CSP_ST_AUTO; + } else { + switch (pcm_sfmt) { + case SNDRV_PCM_FORMAT_MU_LAW: + err = snd_sb_csp_load(p, &mulaw_main[0], sizeof(mulaw_main), 0); + p->acc_format = SNDRV_PCM_FMTBIT_MU_LAW; + p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; + break; + case SNDRV_PCM_FORMAT_A_LAW: + err = snd_sb_csp_load(p, &alaw_main[0], sizeof(alaw_main), 0); + p->acc_format = SNDRV_PCM_FMTBIT_A_LAW; + p->mode = SNDRV_SB_CSP_MODE_DSP_READ | SNDRV_SB_CSP_MODE_DSP_WRITE; + break; + case SNDRV_PCM_FORMAT_IMA_ADPCM: + err = snd_sb_csp_load(p, &ima_adpcm_init[0], sizeof(ima_adpcm_init), + SNDRV_SB_CSP_LOAD_INITBLOCK); + if (err) + break; + if (play_rec_mode == SNDRV_SB_CSP_MODE_DSP_WRITE) { + err = snd_sb_csp_load(p, &ima_adpcm_playback[0], + sizeof(ima_adpcm_playback), 0); + p->mode = SNDRV_SB_CSP_MODE_DSP_WRITE; + } else { + err = snd_sb_csp_load(p, &ima_adpcm_capture[0], + sizeof(ima_adpcm_capture), 0); + p->mode = SNDRV_SB_CSP_MODE_DSP_READ; + } + p->acc_format = SNDRV_PCM_FMTBIT_IMA_ADPCM; + break; + default: + /* Decouple CSP from IRQ and DMAREQ lines */ + if (p->running & SNDRV_SB_CSP_ST_AUTO) { + spin_lock_irqsave(&p->chip->reg_lock, flags); + set_mode_register(p->chip, 0xfc); + set_mode_register(p->chip, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + p->running = 0; /* clear autoloaded flag */ + } + return -EINVAL; + } + if (err) { + p->acc_format = 0; + p->acc_channels = p->acc_width = p->acc_rates = 0; + + p->running = 0; /* clear autoloaded flag */ + p->mode = 0; + return (err); + } else { + p->running = SNDRV_SB_CSP_ST_AUTO; /* set autoloaded flag */ + p->acc_width = SNDRV_SB_CSP_SAMPLE_16BIT; /* only 16 bit data */ + p->acc_channels = SNDRV_SB_CSP_MONO | SNDRV_SB_CSP_STEREO; + p->acc_rates = SNDRV_SB_CSP_RATE_ALL; /* HW codecs accept all rates */ + } + + } + return (p->running & SNDRV_SB_CSP_ST_AUTO) ? 0 : -ENXIO; +} + +/* + * start CSP + */ +static int snd_sb_csp_start(snd_sb_csp_t * p, int sample_width, int channels) +{ + unsigned char s_type; /* sample type */ + unsigned char mixL, mixR; + int result = -EIO; + unsigned long flags; + + if (!(p->running & (SNDRV_SB_CSP_ST_LOADED | SNDRV_SB_CSP_ST_AUTO))) { + snd_printd("%s: Microcode not loaded\n", __FUNCTION__); + return -ENXIO; + } + if (p->running & SNDRV_SB_CSP_ST_RUNNING) { + snd_printd("%s: CSP already running\n", __FUNCTION__); + return -EBUSY; + } + if (!(sample_width & p->acc_width)) { + snd_printd("%s: Unsupported PCM sample width\n", __FUNCTION__); + return -EINVAL; + } + if (!(channels & p->acc_channels)) { + snd_printd("%s: Invalid number of channels\n", __FUNCTION__); + return -EINVAL; + } + + /* Mute PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); + mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); + + spin_lock(&p->chip->reg_lock); + set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + set_mode_register(p->chip, 0x70); /* 70 = RUN */ + + s_type = 0x00; + if (channels == SNDRV_SB_CSP_MONO) + s_type = 0x11; /* 000n 000n (n = 1 if mono) */ + if (sample_width == SNDRV_SB_CSP_SAMPLE_8BIT) + s_type |= 0x22; /* 00dX 00dX (d = 1 if 8 bit samples) */ + + if (set_codec_parameter(p->chip, 0x81, s_type)) { + snd_printd("%s: Set sample type command failed\n", __FUNCTION__); + goto __fail; + } + if (set_codec_parameter(p->chip, 0x80, 0x00)) { + snd_printd("%s: Codec start command failed\n", __FUNCTION__); + goto __fail; + } + p->run_width = sample_width; + p->run_channels = channels; + + p->running |= SNDRV_SB_CSP_ST_RUNNING; + + if (p->mode & SNDRV_SB_CSP_MODE_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* enable QSound decoder */ + set_codec_parameter(p->chip, 0x00, 0xff); + set_codec_parameter(p->chip, 0x01, 0xff); + p->running |= SNDRV_SB_CSP_ST_QSOUND; + /* set QSound startup value */ + snd_sb_csp_qsound_transfer(p); + } + result = 0; + + __fail: + spin_unlock(&p->chip->reg_lock); + + /* restore PCM volume */ + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + return result; +} + +/* + * stop CSP + */ +static int snd_sb_csp_stop(snd_sb_csp_t * p) +{ + int result; + unsigned char mixL, mixR; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) + return 0; + + /* Mute PCM volume */ + spin_lock_irqsave(&p->chip->mixer_lock, flags); + mixL = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV); + mixR = snd_sbmixer_read(p->chip, SB_DSP4_PCM_DEV + 1); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL & 0x7); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR & 0x7); + + spin_lock(&p->chip->reg_lock); + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* disable QSound decoder */ + set_codec_parameter(p->chip, 0x00, 0x00); + set_codec_parameter(p->chip, 0x01, 0x00); + + p->running &= ~SNDRV_SB_CSP_ST_QSOUND; + } + result = set_mode_register(p->chip, 0xc0); /* c0 = STOP */ + spin_unlock(&p->chip->reg_lock); + + /* restore PCM volume */ + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV, mixL); + snd_sbmixer_write(p->chip, SB_DSP4_PCM_DEV + 1, mixR); + spin_unlock_irqrestore(&p->chip->mixer_lock, flags); + + if (!(result)) + p->running &= ~(SNDRV_SB_CSP_ST_PAUSED | SNDRV_SB_CSP_ST_RUNNING); + return result; +} + +/* + * pause CSP codec and hold DMA transfer + */ +static int snd_sb_csp_pause(snd_sb_csp_t * p) +{ + int result; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_RUNNING)) + return -EBUSY; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + result = set_codec_parameter(p->chip, 0x80, 0xff); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + if (!(result)) + p->running |= SNDRV_SB_CSP_ST_PAUSED; + + return result; +} + +/* + * restart CSP codec and resume DMA transfer + */ +static int snd_sb_csp_restart(snd_sb_csp_t * p) +{ + int result; + unsigned long flags; + + if (!(p->running & SNDRV_SB_CSP_ST_PAUSED)) + return -EBUSY; + + spin_lock_irqsave(&p->chip->reg_lock, flags); + result = set_codec_parameter(p->chip, 0x80, 0x00); + spin_unlock_irqrestore(&p->chip->reg_lock, flags); + if (!(result)) + p->running &= ~SNDRV_SB_CSP_ST_PAUSED; + + return result; +} + +/* ------------------------------ */ + +/* + * QSound mixer control for PCM + */ + +static int snd_sb_qsound_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_sb_qsound_switch_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = p->q_enabled ? 1 : 0; + return 0; +} + +static int snd_sb_qsound_switch_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval; + + nval = ucontrol->value.integer.value[0] & 0x01; + spin_lock_irqsave(&p->q_lock, flags); + change = p->q_enabled != nval; + p->q_enabled = nval; + spin_unlock_irqrestore(&p->q_lock, flags); + return change; +} + +static int snd_sb_qsound_space_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + return 0; +} + +static int snd_sb_qsound_space_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&p->q_lock, flags); + ucontrol->value.integer.value[0] = p->qpos_left; + ucontrol->value.integer.value[1] = p->qpos_right; + spin_unlock_irqrestore(&p->q_lock, flags); + return 0; +} + +static int snd_sb_qsound_space_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + snd_sb_csp_t *p = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval1, nval2; + + nval1 = ucontrol->value.integer.value[0]; + if (nval1 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) + nval1 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + nval2 = ucontrol->value.integer.value[1]; + if (nval2 > SNDRV_SB_CSP_QSOUND_MAX_RIGHT) + nval2 = SNDRV_SB_CSP_QSOUND_MAX_RIGHT; + spin_lock_irqsave(&p->q_lock, flags); + change = p->qpos_left != nval1 || p->qpos_right != nval2; + p->qpos_left = nval1; + p->qpos_right = nval2; + p->qpos_changed = change; + spin_unlock_irqrestore(&p->q_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_sb_qsound_switch = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Switch", + .info = snd_sb_qsound_switch_info, + .get = snd_sb_qsound_switch_get, + .put = snd_sb_qsound_switch_put +}; + +static snd_kcontrol_new_t snd_sb_qsound_space = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "3D Control - Space", + .info = snd_sb_qsound_space_info, + .get = snd_sb_qsound_space_get, + .put = snd_sb_qsound_space_put +}; + +static int snd_sb_qsound_build(snd_sb_csp_t * p) +{ + snd_card_t * card; + int err; + + snd_assert(p != NULL, return -EINVAL); + + card = p->chip->card; + p->qpos_left = p->qpos_right = SNDRV_SB_CSP_QSOUND_MAX_RIGHT / 2; + p->qpos_changed = 0; + + spin_lock_init(&p->q_lock); + + if ((err = snd_ctl_add(card, p->qsound_switch = snd_ctl_new1(&snd_sb_qsound_switch, p))) < 0) + goto __error; + if ((err = snd_ctl_add(card, p->qsound_space = snd_ctl_new1(&snd_sb_qsound_space, p))) < 0) + goto __error; + + return 0; + + __error: + snd_sb_qsound_destroy(p); + return err; +} + +static void snd_sb_qsound_destroy(snd_sb_csp_t * p) +{ + snd_card_t * card; + unsigned long flags; + + snd_assert(p != NULL, return); + + card = p->chip->card; + + down_write(&card->controls_rwsem); + if (p->qsound_switch) + snd_ctl_remove(card, p->qsound_switch); + if (p->qsound_space) + snd_ctl_remove(card, p->qsound_space); + up_write(&card->controls_rwsem); + + /* cancel pending transfer of QSound parameters */ + spin_lock_irqsave (&p->q_lock, flags); + p->qpos_changed = 0; + spin_unlock_irqrestore (&p->q_lock, flags); +} + +/* + * Transfer qsound parameters to CSP, + * function should be called from interrupt routine + */ +static int snd_sb_csp_qsound_transfer(snd_sb_csp_t * p) +{ + int err = -ENXIO; + + spin_lock(&p->q_lock); + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + set_codec_parameter(p->chip, 0xe0, 0x01); + /* left channel */ + set_codec_parameter(p->chip, 0x00, p->qpos_left); + set_codec_parameter(p->chip, 0x02, 0x00); + /* right channel */ + set_codec_parameter(p->chip, 0x00, p->qpos_right); + set_codec_parameter(p->chip, 0x03, 0x00); + err = 0; + } + p->qpos_changed = 0; + spin_unlock(&p->q_lock); + return err; +} + +/* ------------------------------ */ + +/* + * proc interface + */ +static int init_proc_entry(snd_sb_csp_t * p, int device) +{ + char name[16]; + snd_info_entry_t *entry; + sprintf(name, "cspD%d", device); + if (! snd_card_proc_new(p->chip->card, name, &entry)) + snd_info_set_text_ops(entry, p, 1024, info_read); + return 0; +} + +static void info_read(snd_info_entry_t *entry, snd_info_buffer_t * buffer) +{ + snd_sb_csp_t *p = entry->private_data; + + snd_iprintf(buffer, "Creative Signal Processor [v%d.%d]\n", (p->version >> 4), (p->version & 0x0f)); + snd_iprintf(buffer, "State: %cx%c%c%c\n", ((p->running & SNDRV_SB_CSP_ST_QSOUND) ? 'Q' : '-'), + ((p->running & SNDRV_SB_CSP_ST_PAUSED) ? 'P' : '-'), + ((p->running & SNDRV_SB_CSP_ST_RUNNING) ? 'R' : '-'), + ((p->running & SNDRV_SB_CSP_ST_LOADED) ? 'L' : '-')); + if (p->running & SNDRV_SB_CSP_ST_LOADED) { + snd_iprintf(buffer, "Codec: %s [func #%d]\n", p->codec_name, p->func_nr); + snd_iprintf(buffer, "Sample rates: "); + if (p->acc_rates == SNDRV_SB_CSP_RATE_ALL) { + snd_iprintf(buffer, "All\n"); + } else { + snd_iprintf(buffer, "%s%s%s%s\n", + ((p->acc_rates & SNDRV_SB_CSP_RATE_8000) ? "8000Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_11025) ? "11025Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_22050) ? "22050Hz " : ""), + ((p->acc_rates & SNDRV_SB_CSP_RATE_44100) ? "44100Hz" : "")); + } + if (p->mode == SNDRV_SB_CSP_MODE_QSOUND) { + snd_iprintf(buffer, "QSound decoder %sabled\n", + p->q_enabled ? "en" : "dis"); + } else { + snd_iprintf(buffer, "PCM format ID: 0x%x (%s/%s) [%s/%s] [%s/%s]\n", + p->acc_format, + ((p->acc_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? "16bit" : "-"), + ((p->acc_width & SNDRV_SB_CSP_SAMPLE_8BIT) ? "8bit" : "-"), + ((p->acc_channels & SNDRV_SB_CSP_MONO) ? "mono" : "-"), + ((p->acc_channels & SNDRV_SB_CSP_STEREO) ? "stereo" : "-"), + ((p->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) ? "playback" : "-"), + ((p->mode & SNDRV_SB_CSP_MODE_DSP_READ) ? "capture" : "-")); + } + } + if (p->running & SNDRV_SB_CSP_ST_AUTO) { + snd_iprintf(buffer, "Autoloaded Mu-Law, A-Law or Ima-ADPCM hardware codec\n"); + } + if (p->running & SNDRV_SB_CSP_ST_RUNNING) { + snd_iprintf(buffer, "Processing %dbit %s PCM samples\n", + ((p->run_width & SNDRV_SB_CSP_SAMPLE_16BIT) ? 16 : 8), + ((p->run_channels & SNDRV_SB_CSP_MONO) ? "mono" : "stereo")); + } + if (p->running & SNDRV_SB_CSP_ST_QSOUND) { + snd_iprintf(buffer, "Qsound position: left = 0x%x, right = 0x%x\n", + p->qpos_left, p->qpos_right); + } +} + +/* */ + +EXPORT_SYMBOL(snd_sb_csp_new); + +/* + * INIT part + */ + +static int __init alsa_sb_csp_init(void) +{ + return 0; +} + +static void __exit alsa_sb_csp_exit(void) +{ +} + +module_init(alsa_sb_csp_init) +module_exit(alsa_sb_csp_exit) diff --git a/sound/isa/sb/sb16_csp_codecs.h b/sound/isa/sb/sb16_csp_codecs.h new file mode 100644 index 00000000000..f0e8b0dcb57 --- /dev/null +++ b/sound/isa/sb/sb16_csp_codecs.h @@ -0,0 +1,949 @@ +/* + * Copyright (c) 1994 Creative Technology Ltd. + * Microcode files for SB16 Advanced Signal Processor + * + * 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 + * + */ + +static unsigned char mulaw_main[] = { + 0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44, + 0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44, + 0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8, + 0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b, + 0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49, + 0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b, + 0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80, + 0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b, + 0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80, + 0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80, + 0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82, + 0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19, + 0xa2, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19, + 0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19, + 0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24, + 0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44, + 0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45, + 0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39, + 0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef, + 0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1, + 0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80, + 0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54, + 0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0, + 0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a, + 0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44, + 0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44, + 0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80, + 0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4, + 0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4, + 0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4, + 0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4, + 0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4, + 0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4, + 0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4, + 0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4, + 0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4, + 0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80, + 0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4, + 0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b, + 0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4, + 0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88, + 0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39, + 0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44, + 0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39, + 0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02, + 0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19, + 0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab, + 0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0, + 0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44, + 0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84, + 0x03, 0x03, 0x04, 0x49, 0x08, 0xc2, 0x00, 0x54, + 0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44, + 0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44, + 0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84, + 0x3e, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39, + 0x08, 0x01, 0x00, 0x44, 0x6c, 0x00, 0x51, 0x8b, + 0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b, + 0x04, 0x21, 0x00, 0x84, 0xfd, 0x00, 0x51, 0x8b, + 0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44, + 0xfe, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39, + 0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39, + 0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19, + 0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19, + 0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b, + 0xc7, 0x20, 0x04, 0x19, 0x5e, 0x00, 0x71, 0x8b, + 0xcf, 0x00, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80, + 0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19, + 0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19, + 0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44, + 0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19, + 0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19, + 0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a, + 0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45, + 0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f, + 0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e, + 0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22, + 0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80, + 0x08, 0xa8, 0x00, 0x44, 0x20, 0x31, 0x49, 0x5c, + 0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e, + 0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0, + 0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b, + 0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02, + 0xcb, 0x00, 0xa8, 0x58, 0xb0, 0x05, 0xf3, 0x80, + 0x20, 0x04, 0xa8, 0x10, 0x00, 0x00, 0x10, 0x39, + 0xb0, 0x00, 0xe0, 0x8b, 0x20, 0x01, 0x00, 0x80, + 0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02, + 0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0, + 0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e, + 0xff, 0xff, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22, + 0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80, + 0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80, + 0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a, + 0x08, 0x83, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80, + 0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39, + 0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a, + 0x0c, 0xa3, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82, + 0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80, + 0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a, + 0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a, + 0x00, 0x73, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80, + 0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4, + 0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0, + 0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02, + 0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80, + 0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50, + 0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80, + 0x00, 0xf3, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a, + 0x00, 0xd3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80, + 0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4, + 0x00, 0x00, 0xb3, 0x10, 0xcc, 0x20, 0x00, 0x39, + 0x00, 0x00, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44, + 0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x71, 0xc0, + 0x00, 0x30, 0x71, 0xc0, 0x00, 0xf3, 0x00, 0x44, + 0x20, 0x01, 0x00, 0x80, 0xff, 0xff, 0x62, 0x8b, + 0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0x20, 0xe1, 0x09, 0x5c, + 0x82, 0x00, 0x09, 0x2f, 0x80, 0x4a, 0x09, 0x8e, + 0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80, + 0x00, 0x00, 0x7a, 0xcb, 0x03, 0x00, 0xa8, 0x18, + 0x00, 0x00, 0x10, 0x39, 0x08, 0x04, 0xea, 0x10, + 0x08, 0x04, 0x7a, 0x10, 0x20, 0x00, 0x00, 0x80, + 0x40, 0x00, 0x21, 0xcb, 0x0c, 0x00, 0xe8, 0x10, + 0x00, 0x00, 0x41, 0x02, 0x0c, 0x00, 0xeb, 0x10, + 0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02, + 0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19, + 0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a, + 0x0c, 0xe8, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a, + 0x03, 0x00, 0x48, 0x0a, 0x00, 0xb8, 0x04, 0x54, + 0xc3, 0x00, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0x44, + 0x08, 0x00, 0xc8, 0x0a, 0x0c, 0xb8, 0x04, 0x54, + 0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a, + 0x09, 0x00, 0x48, 0x0a, 0x00, 0x68, 0x04, 0x54, + 0xc9, 0x00, 0x04, 0x19, 0x0c, 0x68, 0x00, 0x44, + 0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0x68, 0x04, 0x54, + 0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a, + 0x06, 0x00, 0x48, 0x0a, 0x00, 0x78, 0x04, 0x54, + 0xc6, 0x00, 0x04, 0x19, 0x0c, 0x78, 0x00, 0x44, + 0x05, 0x00, 0xc8, 0x0a, 0x0c, 0x78, 0x04, 0x54, + 0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a, + 0x0c, 0x00, 0x48, 0x0a, 0x00, 0xe8, 0x04, 0x54, + 0xcc, 0x00, 0x04, 0x19, 0x0c, 0xe8, 0x00, 0x44, + 0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xe8, 0x04, 0x54, + 0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x20, 0x10, 0x71, 0x8b, 0x09, 0x3f, 0x07, 0x00 +}; + +static unsigned char alaw_main[] = { + 0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44, + 0x00, 0xb1, 0x00, 0x44, 0x00, 0x61, 0x00, 0x44, + 0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8, + 0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b, + 0x50, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0xff, 0x2e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49, + 0x20, 0x01, 0x09, 0x0e, 0x20, 0x00, 0x71, 0x8b, + 0xa8, 0x01, 0xa8, 0x80, 0x88, 0x01, 0xa8, 0x80, + 0xa8, 0x00, 0x00, 0x80, 0xd2, 0x00, 0x71, 0x8b, + 0x88, 0x00, 0xa8, 0x80, 0xa8, 0x04, 0xb3, 0x80, + 0x20, 0x07, 0xb3, 0x80, 0x88, 0x03, 0xb1, 0x80, + 0xc0, 0x00, 0x09, 0x5c, 0xc2, 0x01, 0x00, 0x82, + 0xa1, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x04, 0x19, + 0x21, 0x20, 0x71, 0x8b, 0xcf, 0x00, 0x04, 0x19, + 0x00, 0x00, 0xb1, 0x80, 0xc2, 0x00, 0x04, 0x19, + 0x00, 0x40, 0x00, 0x14, 0x08, 0x40, 0x04, 0x24, + 0x00, 0x00, 0x34, 0x49, 0x0c, 0x40, 0x00, 0x44, + 0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45, + 0x32, 0x00, 0x09, 0x5c, 0x00, 0x00, 0x0c, 0x39, + 0x00, 0x00, 0x40, 0x45, 0x40, 0x40, 0x09, 0xef, + 0xff, 0x20, 0x09, 0xcf, 0x00, 0x04, 0x63, 0xa1, + 0x50, 0x03, 0x33, 0x80, 0x00, 0x04, 0xa3, 0x80, + 0x00, 0xff, 0xc2, 0x8b, 0x00, 0xd0, 0x04, 0x54, + 0x04, 0xe0, 0x00, 0xc4, 0x20, 0x03, 0x80, 0xc0, + 0x30, 0x00, 0x00, 0x88, 0x00, 0x00, 0x7a, 0x0a, + 0xd0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44, + 0xc0, 0x00, 0x00, 0x99, 0x00, 0x60, 0x00, 0x44, + 0x00, 0xff, 0xc2, 0x8b, 0x20, 0x00, 0x00, 0x80, + 0x00, 0x0d, 0x42, 0x8b, 0x08, 0x32, 0x00, 0xc4, + 0x00, 0x0e, 0x42, 0x8b, 0x00, 0xa2, 0x00, 0xc4, + 0x00, 0x1e, 0x42, 0x8b, 0x0c, 0xb2, 0x00, 0xc4, + 0x00, 0x8e, 0x42, 0x8b, 0x00, 0x62, 0x00, 0xc4, + 0x00, 0x9e, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4, + 0x00, 0xbe, 0x42, 0x8b, 0x08, 0x52, 0x00, 0xc4, + 0x00, 0x04, 0x42, 0x8b, 0x04, 0x72, 0x00, 0xc4, + 0x00, 0x24, 0x42, 0x8b, 0x00, 0xd2, 0x00, 0xc4, + 0x00, 0x55, 0x42, 0x8b, 0x00, 0x60, 0x00, 0xc4, + 0x00, 0x00, 0x40, 0x45, 0x20, 0x01, 0x79, 0x80, + 0x00, 0x30, 0x42, 0x8b, 0x08, 0x82, 0x00, 0xc4, + 0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x71, 0x8b, + 0x40, 0x01, 0x00, 0x80, 0x00, 0x60, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0x00, 0xb2, 0x00, 0xc4, + 0x0f, 0xf2, 0xa8, 0xa8, 0x20, 0x00, 0xb1, 0x88, + 0x00, 0x00, 0x41, 0x02, 0x4d, 0xf2, 0x00, 0x39, + 0xc0, 0x01, 0x00, 0x82, 0x00, 0x60, 0x00, 0x44, + 0x0d, 0xf2, 0xa3, 0xa8, 0x4d, 0xf2, 0x00, 0x39, + 0x00, 0x60, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0x20, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x02, + 0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0xa0, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x61, 0x10, 0x4d, 0xf2, 0x04, 0x19, + 0x00, 0x60, 0x00, 0x44, 0xff, 0x20, 0xe2, 0xab, + 0x60, 0x00, 0x00, 0x88, 0x00, 0x00, 0x71, 0xc0, + 0x4d, 0xf2, 0x04, 0x19, 0x00, 0x60, 0x00, 0x44, + 0x00, 0x00, 0x79, 0x80, 0x00, 0xe2, 0x00, 0x84, + 0x03, 0x03, 0x04, 0x49, 0x04, 0xc2, 0x00, 0x54, + 0x00, 0x60, 0x04, 0x64, 0x00, 0x60, 0x00, 0x44, + 0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44, + 0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x00, 0x20, 0xe2, 0x8b, 0x0c, 0xf2, 0x00, 0x84, + 0xbe, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39, + 0x08, 0x01, 0x00, 0x44, 0xec, 0x00, 0x51, 0x8b, + 0xc0, 0x20, 0x00, 0x39, 0x00, 0x02, 0xe2, 0x8b, + 0x04, 0x21, 0x00, 0x84, 0x3f, 0x00, 0x51, 0x8b, + 0xc2, 0x20, 0x00, 0x39, 0x00, 0x11, 0x00, 0x44, + 0x3d, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39, + 0xe5, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39, + 0x00, 0x00, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x19, + 0xcb, 0x20, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19, + 0xc3, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b, + 0xc7, 0x20, 0x04, 0x19, 0xde, 0x00, 0x51, 0x8b, + 0xcf, 0x00, 0x00, 0x39, 0x00, 0x01, 0xb1, 0x80, + 0xc4, 0x20, 0x04, 0x19, 0xc6, 0x20, 0x04, 0x19, + 0xc8, 0x20, 0x04, 0x19, 0xca, 0x20, 0x04, 0x19, + 0x20, 0x00, 0x71, 0x8b, 0xcc, 0x20, 0x04, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x00, 0x60, 0x00, 0x44, + 0x09, 0x04, 0x61, 0xa8, 0xc1, 0x00, 0x04, 0x19, + 0x0b, 0x04, 0x61, 0xa8, 0xca, 0x00, 0x04, 0x19, + 0x04, 0x60, 0x00, 0xd4, 0x0d, 0x00, 0x61, 0x0a, + 0x90, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45, + 0x0f, 0x00, 0x61, 0x0a, 0x00, 0x40, 0x09, 0x8f, + 0x00, 0x01, 0x00, 0x45, 0x82, 0x00, 0x09, 0x2e, + 0x80, 0x40, 0x09, 0xcf, 0x02, 0x00, 0x61, 0x22, + 0x43, 0x25, 0x61, 0x22, 0x40, 0x33, 0x00, 0x80, + 0x08, 0x48, 0x00, 0x44, 0x20, 0xb1, 0x49, 0x5c, + 0x92, 0x00, 0x09, 0x4e, 0x02, 0x03, 0x09, 0x2e, + 0x00, 0x00, 0xa3, 0x02, 0xc0, 0x00, 0x71, 0xc0, + 0x20, 0x00, 0xeb, 0x80, 0x00, 0x04, 0xc2, 0x8b, + 0x20, 0x04, 0x61, 0x80, 0x00, 0x04, 0x7a, 0x02, + 0xc0, 0x00, 0x00, 0x82, 0x0c, 0xc3, 0x08, 0x49, + 0xb0, 0x01, 0xf3, 0x80, 0x00, 0x00, 0x10, 0x39, + 0x20, 0x00, 0x0c, 0x89, 0x0c, 0x88, 0x08, 0x49, + 0x03, 0x00, 0xa8, 0x18, 0x00, 0x00, 0x10, 0x39, + 0xbd, 0xff, 0x62, 0x8b, 0x20, 0x01, 0x00, 0x80, + 0x00, 0x00, 0x63, 0xcb, 0x00, 0x00, 0x7a, 0x02, + 0x40, 0x00, 0x01, 0x5b, 0x20, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0x13, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0xe0, 0x21, 0x00, 0xc0, + 0x08, 0x00, 0x08, 0x49, 0x10, 0x41, 0x09, 0x8e, + 0xae, 0xae, 0x62, 0x8b, 0x00, 0x04, 0x61, 0x22, + 0x00, 0x03, 0x00, 0x45, 0x22, 0x01, 0x33, 0x80, + 0x20, 0x01, 0xa3, 0x02, 0x00, 0x00, 0x7a, 0x80, + 0xc0, 0x00, 0x00, 0x82, 0x07, 0x20, 0x40, 0x0a, + 0x08, 0xa3, 0x00, 0x84, 0x40, 0x21, 0x00, 0x80, + 0x40, 0x05, 0x93, 0x10, 0xc7, 0x20, 0x00, 0x39, + 0x00, 0x00, 0x40, 0x45, 0x07, 0x20, 0x40, 0x0a, + 0x0c, 0x93, 0x00, 0x84, 0x08, 0x00, 0x00, 0x82, + 0x0c, 0x24, 0x61, 0x50, 0x40, 0x01, 0x00, 0x80, + 0xc7, 0x20, 0x00, 0x39, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0x42, 0x01, 0x09, 0x0e, 0x02, 0x20, 0x61, 0x0a, + 0x00, 0x01, 0x00, 0x45, 0x0c, 0x20, 0x60, 0x0a, + 0x00, 0xc3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80, + 0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4, + 0x00, 0x24, 0x71, 0xc0, 0x20, 0x33, 0x33, 0xc0, + 0xe0, 0x01, 0xa3, 0x82, 0x22, 0x03, 0x7a, 0x02, + 0xc3, 0x01, 0xa3, 0x82, 0x20, 0x01, 0x33, 0x80, + 0x00, 0x00, 0x7a, 0x80, 0xc2, 0x01, 0xb3, 0x50, + 0xcc, 0x20, 0x00, 0x39, 0x00, 0x00, 0x71, 0x80, + 0x00, 0x08, 0x00, 0x44, 0x0c, 0x20, 0x60, 0x0a, + 0x00, 0xf3, 0x00, 0x84, 0x00, 0x04, 0xb1, 0x80, + 0x00, 0x00, 0x06, 0x39, 0x0c, 0x61, 0x04, 0xd4, + 0x00, 0x00, 0x71, 0xc0, 0x00, 0x00, 0x93, 0x10, + 0xcc, 0x20, 0x00, 0x39, 0x00, 0x08, 0x00, 0x44, + 0xcc, 0x20, 0x00, 0x39, 0x00, 0x20, 0x00, 0xc0, + 0x00, 0x30, 0x71, 0xc0, 0x00, 0x08, 0x00, 0x44, + 0x20, 0x01, 0x00, 0x80, 0xae, 0xae, 0x62, 0x8b, + 0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0x20, 0xa1, 0x49, 0x5c, + 0x82, 0x00, 0x09, 0x6e, 0x80, 0x4a, 0x09, 0x8e, + 0xe0, 0x01, 0xb3, 0x82, 0x20, 0x04, 0xa3, 0x80, + 0x00, 0x00, 0x7a, 0xcb, 0x28, 0x04, 0xea, 0x10, + 0x0c, 0x04, 0x7a, 0x10, 0x70, 0x00, 0xc0, 0x8b, + 0x00, 0x00, 0x10, 0x39, 0x90, 0x03, 0x00, 0x80, + 0x40, 0x00, 0x21, 0x5b, 0x90, 0x00, 0x61, 0x80, + 0x0c, 0x8a, 0x08, 0x49, 0x00, 0x00, 0x1c, 0x19, + 0x40, 0x00, 0x08, 0x5b, 0x08, 0x00, 0x08, 0x49, + 0x20, 0x02, 0x00, 0x80, 0x03, 0x00, 0xa8, 0x18, + 0x00, 0x00, 0x14, 0x19, 0x40, 0x00, 0x21, 0xcb, + 0x00, 0x00, 0x41, 0x02, 0x00, 0x00, 0xeb, 0x80, + 0xf2, 0x01, 0x00, 0x82, 0x40, 0x21, 0x33, 0x02, + 0x08, 0x20, 0x61, 0x0a, 0xc4, 0x00, 0x04, 0x19, + 0xc7, 0x00, 0x00, 0x99, 0x02, 0x00, 0x61, 0x0a, + 0x0c, 0x0a, 0x04, 0x14, 0x01, 0x00, 0x61, 0x0a, + 0x03, 0x00, 0x48, 0x0a, 0x00, 0x58, 0x04, 0x54, + 0xc3, 0x00, 0x04, 0x19, 0x0c, 0x58, 0x00, 0x44, + 0x08, 0x00, 0xc8, 0x0a, 0x0c, 0x58, 0x04, 0x54, + 0xc8, 0x00, 0x04, 0x19, 0x0a, 0x00, 0x61, 0x0a, + 0x09, 0x00, 0x48, 0x0a, 0x00, 0xc8, 0x04, 0x54, + 0xc9, 0x00, 0x04, 0x19, 0x0c, 0xc8, 0x00, 0x44, + 0x0b, 0x00, 0xc8, 0x0a, 0x0c, 0xc8, 0x04, 0x54, + 0xcb, 0x00, 0x04, 0x19, 0x04, 0x00, 0x61, 0x0a, + 0x06, 0x00, 0x48, 0x0a, 0x00, 0xd8, 0x04, 0x54, + 0xc6, 0x00, 0x04, 0x19, 0x0c, 0xd8, 0x00, 0x44, + 0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xd8, 0x04, 0x54, + 0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a, + 0x0c, 0x00, 0x48, 0x0a, 0x00, 0x0a, 0x04, 0x54, + 0xcc, 0x00, 0x04, 0x19, 0x0c, 0x0a, 0x00, 0x44, + 0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0x0a, 0x04, 0x54, + 0xce, 0x00, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x20, 0x10, 0x71, 0x8b, 0x08, 0x42, 0x06, 0x00 +}; + + +static unsigned char ima_adpcm_init[] = { + 0x00, 0x10, 0x00, 0x44, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x00, 0x40, 0x45, 0xaa, 0xaa, 0x71, 0x8b, + 0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0xff, 0x6e, 0x21, 0x49, 0xff, 0x0f, 0xd4, 0x49, + 0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b, + 0x50, 0x05, 0xb1, 0x80, 0x62, 0x00, 0x19, 0x0e, + 0x21, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xb0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x40, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x60, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x50, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x70, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xe0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xd0, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x02, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x22, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x32, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xa2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xb2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x62, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xf2, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x11, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xa1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x61, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xe1, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x13, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xb3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc3, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x18, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x68, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x0a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x4a, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x29, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x79, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x9b, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x14, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xf4, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xe6, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xe5, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xd7, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x2e, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x9d, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xef, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xb2, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x33, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x2a, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x3b, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x46, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x2c, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xdd, 0x20, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x01, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x9a, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x16, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x8e, 0x10, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc2, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc9, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x3c, 0x30, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x81, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xd4, 0x80, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x10, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x34, 0xa0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x02, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x75, 0x90, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x9a, 0xb0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x12, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x0d, 0x40, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x3c, 0x60, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xe7, 0x50, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x0e, 0x70, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xff, 0xc0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc8, 0xd0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x57, 0xf0, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xc8, 0x22, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xb0, 0x32, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xdd, 0x82, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x90, 0xb2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x8a, 0x62, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xce, 0x72, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xa5, 0xd2, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x97, 0x21, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xa2, 0xa1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x5c, 0x41, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xfe, 0xc1, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x7a, 0x23, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x78, 0x93, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x67, 0x73, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x17, 0x28, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x88, 0x48, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xdb, 0xf8, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x2b, 0xba, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xf1, 0x09, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xdc, 0x69, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x19, 0x8b, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0xff, 0xfb, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x20, 0x00, 0x71, 0x8b, 0x88, 0x00, 0x00, 0x80, + 0x52, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0xff, 0xff, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82, + 0xc2, 0x00, 0x00, 0x82, 0x10, 0x00, 0x71, 0x8b, + 0xc2, 0x00, 0x00, 0x82, 0x80, 0x00, 0x71, 0x8b, + 0xc2, 0x00, 0x00, 0x82, 0x90, 0x00, 0x71, 0x8b, + 0xc2, 0x00, 0x00, 0x82, 0x40, 0x00, 0x71, 0x8b, + 0xc2, 0x00, 0x00, 0x82, 0xff, 0xff, 0x71, 0x8b, + 0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82, + 0xc2, 0x00, 0x00, 0x82, 0xc2, 0x00, 0x00, 0x82, + 0x10, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x80, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x90, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x40, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0xff, 0xfb, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x00, 0x04, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x4a, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0x00, 0x00, 0x71, 0x8b, 0xc2, 0x00, 0x00, 0x82, + 0xc2, 0x00, 0x00, 0x82, 0xc2, 0x30, 0x04, 0x19, + 0x10, 0x00, 0x09, 0x4f, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0xc2, 0x01, 0x00, 0x82, 0xc2, 0x01, 0x00, 0x82, + 0x00, 0x10, 0x71, 0x8b, 0xc1, 0x30, 0x04, 0x19, + 0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09, + 0xcf, 0x30, 0x00, 0x09, 0x00, 0x00, 0x34, 0x49, + 0x00, 0x08, 0x00, 0x44, 0xc8, 0x54, 0x11, 0x00 +}; + +static unsigned char ima_adpcm_playback[] = { + 0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44, + 0x0c, 0x50, 0x00, 0x44, 0x00, 0x70, 0x00, 0x44, + 0x04, 0x70, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8, + 0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0xff, 0x2e, 0x21, 0x49, 0xff, 0x0d, 0xd4, 0x49, + 0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b, + 0x50, 0x01, 0xb1, 0x80, 0x00, 0x01, 0xb1, 0x80, + 0xc9, 0x20, 0x04, 0x19, 0x51, 0x00, 0x71, 0x8b, + 0xcd, 0x00, 0x04, 0x19, 0xe4, 0x20, 0x71, 0x8b, + 0xcf, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b, + 0xcb, 0x20, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b, + 0xc4, 0x20, 0x04, 0x19, 0x65, 0x00, 0x51, 0x8b, + 0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80, + 0xc2, 0x30, 0x04, 0x19, 0x00, 0x00, 0x63, 0x80, + 0xc1, 0xa0, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f, + 0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09, + 0x04, 0x40, 0x00, 0x14, 0x0c, 0x40, 0x00, 0x14, + 0x00, 0x04, 0x61, 0xa8, 0x02, 0x04, 0x61, 0xa8, + 0x04, 0x60, 0x04, 0x24, 0x00, 0x00, 0x34, 0x49, + 0x00, 0x50, 0x00, 0x44, 0x44, 0x04, 0x04, 0x39, + 0x00, 0x00, 0x40, 0x45, 0x00, 0x00, 0x40, 0x45, + 0x0f, 0x00, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45, + 0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf, + 0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80, + 0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b, + 0x08, 0xf0, 0x04, 0x54, 0x0c, 0xd0, 0x00, 0xc4, + 0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82, + 0x08, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99, + 0x08, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b, + 0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b, + 0x00, 0xa2, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b, + 0x0c, 0x92, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b, + 0x04, 0x62, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b, + 0x0c, 0x52, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b, + 0x00, 0xc2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b, + 0x00, 0xc2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b, + 0x00, 0xf2, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b, + 0x00, 0x91, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b, + 0x08, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b, + 0x08, 0xe2, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45, + 0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b, + 0x00, 0x92, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80, + 0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0x0c, 0x42, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8, + 0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02, + 0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82, + 0x08, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8, + 0x4d, 0xf2, 0x00, 0x39, 0x08, 0x50, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19, + 0x08, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10, + 0x4d, 0xf2, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44, + 0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19, + 0x08, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a, + 0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a, + 0x04, 0xd2, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80, + 0xc1, 0x30, 0x04, 0x19, 0x08, 0x50, 0x00, 0x44, + 0x00, 0x00, 0x79, 0x80, 0x00, 0xa1, 0x00, 0x84, + 0xb5, 0x00, 0x51, 0x8b, 0xcf, 0x00, 0x00, 0x39, + 0x00, 0x01, 0xb1, 0x80, 0x88, 0x00, 0x04, 0x19, + 0x8a, 0x00, 0x04, 0x19, 0xc8, 0x20, 0x04, 0x19, + 0xca, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19, + 0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19, + 0xb0, 0x00, 0x71, 0x8b, 0x8c, 0x00, 0x04, 0x19, + 0x8e, 0x00, 0x04, 0x19, 0x10, 0x00, 0x71, 0x8b, + 0xc4, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f, + 0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09, + 0x03, 0x03, 0x04, 0x49, 0x04, 0x81, 0x00, 0x54, + 0x08, 0x50, 0x04, 0x64, 0x08, 0x50, 0x00, 0x44, + 0x00, 0x00, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x08, 0x50, 0x00, 0x44, + 0x20, 0x01, 0x63, 0x80, 0x00, 0x00, 0x06, 0x19, + 0x00, 0x02, 0xe2, 0x8b, 0x08, 0x41, 0x00, 0x84, + 0x65, 0x00, 0x51, 0x8b, 0xc2, 0x20, 0x00, 0x39, + 0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19, + 0x08, 0x61, 0x00, 0x44, 0x2d, 0x00, 0x51, 0x8b, + 0xc2, 0x20, 0x00, 0x39, 0x00, 0x00, 0xb1, 0x80, + 0xc1, 0xa0, 0x04, 0x19, 0x03, 0x00, 0x04, 0x49, + 0x08, 0x50, 0x00, 0x44, 0x02, 0x20, 0x61, 0x0a, + 0x00, 0x01, 0x00, 0x45, 0x02, 0x30, 0x61, 0x0a, + 0x04, 0x03, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18, + 0x04, 0x71, 0x00, 0xc4, 0x00, 0x13, 0x00, 0x44, + 0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80, + 0x00, 0x49, 0x00, 0xc4, 0xca, 0x20, 0x04, 0x19, + 0x4a, 0x04, 0x04, 0x19, 0xff, 0x00, 0xe2, 0x8b, + 0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19, + 0x0c, 0x2b, 0x08, 0x44, 0x8e, 0x00, 0x04, 0x19, + 0x03, 0x30, 0x61, 0x0a, 0xc8, 0x20, 0x00, 0x39, + 0x48, 0x04, 0x00, 0x39, 0x0a, 0x30, 0x61, 0x0a, + 0x0c, 0xf9, 0x08, 0x44, 0xcd, 0x10, 0x04, 0x19, + 0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19, + 0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44, + 0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80, + 0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19, + 0xca, 0x30, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44, + 0x42, 0x0a, 0x09, 0x0e, 0x00, 0x01, 0x33, 0x11, + 0x8c, 0x01, 0xa3, 0x80, 0x00, 0x01, 0x7a, 0x10, + 0x80, 0x05, 0xb1, 0x80, 0x05, 0xb0, 0xe0, 0x18, + 0x00, 0x93, 0x00, 0x84, 0x00, 0x79, 0x08, 0x44, + 0x00, 0x04, 0x79, 0x80, 0x00, 0x49, 0x00, 0xc4, + 0x0c, 0x1b, 0x08, 0x44, 0x88, 0x00, 0x04, 0x19, + 0x8a, 0x00, 0x00, 0x99, 0x0c, 0xd9, 0x08, 0x44, + 0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b, + 0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b, + 0x00, 0x43, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a, + 0x01, 0x30, 0xc8, 0x0a, 0x00, 0x43, 0x00, 0x84, + 0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19, + 0x0c, 0xa8, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a, + 0x00, 0xd3, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18, + 0x04, 0x63, 0x00, 0xc4, 0x08, 0xf3, 0x00, 0x44, + 0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80, + 0x00, 0x49, 0x00, 0xc4, 0x20, 0x00, 0x04, 0x19, + 0xff, 0x00, 0xe2, 0x8b, 0x0c, 0xf9, 0x08, 0x44, + 0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19, + 0x0c, 0x2b, 0x08, 0x44, 0x8c, 0x00, 0x04, 0x19, + 0x8e, 0x00, 0x04, 0x19, 0x03, 0x30, 0x61, 0x0a, + 0xc8, 0x20, 0x00, 0x39, 0xca, 0x20, 0x00, 0x39, + 0x48, 0x04, 0x00, 0x39, 0x4a, 0x04, 0x00, 0x39, + 0x0c, 0xd9, 0x08, 0x44, 0x0c, 0x5a, 0x00, 0x44, + 0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80, + 0x00, 0x49, 0x00, 0xc4, 0xc3, 0x30, 0x04, 0x19, + 0x0c, 0xd9, 0x08, 0x44, 0x42, 0x0a, 0x09, 0x0e, + 0x05, 0xb0, 0xe0, 0x18, 0x00, 0x18, 0x00, 0x84, + 0x00, 0x79, 0x08, 0x44, 0x00, 0x04, 0x79, 0x80, + 0x00, 0x49, 0x00, 0xc4, 0x0c, 0x1b, 0x08, 0x44, + 0x80, 0x01, 0x00, 0x80, 0x0c, 0xd9, 0x08, 0x44, + 0x42, 0x0a, 0x09, 0x0e, 0x80, 0x00, 0x71, 0x8b, + 0xc0, 0x04, 0xb1, 0x82, 0x10, 0x00, 0xe0, 0x0b, + 0x00, 0x88, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a, + 0x01, 0x30, 0xc8, 0x0a, 0x00, 0x88, 0x00, 0x84, + 0x00, 0x00, 0xb1, 0x80, 0xc2, 0x30, 0x04, 0x19, + 0x00, 0x01, 0x00, 0x11, 0x00, 0x0f, 0xe2, 0x8b, + 0x00, 0x00, 0x41, 0xcb, 0x8c, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x48, 0xcb, 0x20, 0x00, 0x7a, 0x80, + 0x80, 0x01, 0x00, 0x80, 0x82, 0x0c, 0x09, 0x6e, + 0x03, 0x08, 0x09, 0x0e, 0x80, 0x40, 0x09, 0xcf, + 0x00, 0x01, 0x71, 0xc2, 0x00, 0x08, 0xc2, 0x1b, + 0x04, 0xb8, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80, + 0x20, 0x01, 0xf0, 0x80, 0x00, 0x01, 0xc2, 0x1b, + 0x04, 0x48, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80, + 0x20, 0x01, 0xf0, 0x80, 0x00, 0x02, 0xc2, 0x1b, + 0x04, 0x68, 0x00, 0xc4, 0x20, 0x05, 0xa8, 0x80, + 0x20, 0x01, 0xf0, 0x80, 0x20, 0x03, 0xa8, 0x80, + 0x00, 0x01, 0x00, 0x11, 0x00, 0x04, 0xc2, 0x8b, + 0x08, 0x78, 0x00, 0xc4, 0x00, 0x00, 0xe9, 0x80, + 0x05, 0xb0, 0xa8, 0x18, 0x00, 0x00, 0x4a, 0xcb, + 0x20, 0x00, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82, + 0x40, 0x01, 0x00, 0x80, 0xc4, 0x00, 0x04, 0x19, + 0xb0, 0x00, 0xe2, 0x8b, 0x06, 0x20, 0xa8, 0x0a, + 0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x08, 0x09, 0x2e, + 0x00, 0x01, 0xa8, 0x02, 0x0c, 0xf9, 0x08, 0x44, + 0xcd, 0x10, 0x04, 0x19, 0x0c, 0x2b, 0x08, 0x44, + 0x03, 0x08, 0x09, 0x0e, 0x9a, 0x25, 0xb1, 0x60, + 0xa2, 0x0e, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0f, + 0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80, + 0x00, 0x00, 0x61, 0xcb, 0x80, 0x01, 0x00, 0x80, + 0x03, 0x00, 0x09, 0x0f, 0x00, 0x01, 0x71, 0xc2, + 0x00, 0x08, 0xc2, 0x1b, 0x0c, 0x2a, 0x00, 0xc4, + 0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80, + 0x00, 0x01, 0xc2, 0x1b, 0x0c, 0x1a, 0x00, 0xc4, + 0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80, + 0x00, 0x02, 0xc2, 0x1b, 0x0c, 0x3a, 0x00, 0xc4, + 0x20, 0x05, 0xa8, 0x80, 0x20, 0x01, 0xf0, 0x80, + 0x20, 0x03, 0xa8, 0x80, 0x00, 0x01, 0x00, 0x11, + 0x00, 0x04, 0xc2, 0x8b, 0x04, 0xaa, 0x00, 0xc4, + 0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18, + 0x00, 0x00, 0x4a, 0xcb, 0x20, 0x00, 0xa8, 0x22, + 0xd0, 0x01, 0x00, 0x82, 0x40, 0x01, 0x00, 0x80, + 0xc7, 0x00, 0x04, 0x19, 0xb0, 0x00, 0xe2, 0x8b, + 0x06, 0x20, 0xa8, 0x0a, 0x2f, 0x10, 0x61, 0x0a, + 0xf1, 0x08, 0x09, 0x2e, 0x00, 0x01, 0xa8, 0x02, + 0x0c, 0xf9, 0x08, 0x44, 0xcf, 0x10, 0x04, 0x19, + 0x0c, 0x2b, 0x08, 0x44, 0x9f, 0x35, 0xb1, 0x60, + 0x03, 0x08, 0x09, 0x0e, 0x00, 0x01, 0x71, 0x82, + 0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x61, 0xcb, + 0x80, 0x01, 0x00, 0x80, 0xe4, 0x20, 0x71, 0x8b, + 0x00, 0x01, 0x00, 0x45, 0x90, 0x40, 0x09, 0x8f, + 0x00, 0x05, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0x08, 0x19, 0x04, 0xd4, 0x93, 0x00, 0x01, 0x4f, + 0xe7, 0x00, 0x01, 0x6f, 0x0d, 0x30, 0x61, 0x0a, + 0x20, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82, + 0x02, 0x04, 0x61, 0xa8, 0xc2, 0x00, 0x00, 0x82, + 0xcd, 0x30, 0x00, 0x09, 0x02, 0x00, 0x00, 0x02, + 0x02, 0x00, 0x00, 0x02, 0xc0, 0x80, 0x00, 0x09, + 0x20, 0x00, 0x09, 0x49, 0x0f, 0x30, 0x61, 0x0a, + 0x0d, 0x30, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4, + 0x00, 0x80, 0xc8, 0x0a, 0x00, 0x29, 0x00, 0xc4, + 0x00, 0x04, 0xb1, 0x80, 0x00, 0x00, 0x06, 0x39, + 0xc9, 0x20, 0x04, 0x39, 0x00, 0x39, 0x00, 0x44, + 0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0x00, 0x04, 0xb1, 0x80, 0xc9, 0x20, 0x04, 0x39, + 0x00, 0x39, 0x00, 0x44, 0x09, 0x20, 0x23, 0x0a, + 0x00, 0x00, 0x06, 0x19, 0xc9, 0x20, 0x04, 0x19, + 0x00, 0x00, 0x40, 0x45, 0x02, 0x00, 0x61, 0x0a, + 0x0c, 0xb9, 0x04, 0x14, 0x04, 0x00, 0x61, 0x0a, + 0x06, 0x00, 0x48, 0x0a, 0x00, 0xa9, 0x04, 0x54, + 0xc6, 0x00, 0x04, 0x19, 0x0c, 0xa9, 0x00, 0x44, + 0x05, 0x00, 0xc8, 0x0a, 0x0c, 0xa9, 0x04, 0x54, + 0xc5, 0x00, 0x04, 0x19, 0x07, 0x00, 0x61, 0x0a, + 0x0c, 0x00, 0x48, 0x0a, 0x00, 0xb9, 0x04, 0x54, + 0xcc, 0x00, 0x04, 0x19, 0x0c, 0xb9, 0x00, 0x44, + 0x0e, 0x00, 0xc8, 0x0a, 0x0c, 0xb9, 0x04, 0x54, + 0xce, 0x00, 0x04, 0x19, 0x0c, 0x5a, 0x00, 0x44, + 0x82, 0x0d, 0x09, 0x2e, 0x80, 0x40, 0x09, 0xcf, + 0x00, 0xdf, 0x71, 0x8b, 0x80, 0x01, 0x00, 0x80, + 0x02, 0xc1, 0x00, 0x22, 0x03, 0xc1, 0x00, 0x22, + 0x00, 0x01, 0x65, 0x80, 0xd2, 0x05, 0x65, 0x82, + 0x40, 0x21, 0x00, 0x80, 0xd3, 0x03, 0x00, 0x82, + 0x40, 0x33, 0x00, 0x80, 0x0c, 0x5a, 0x00, 0x44, + 0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a, + 0x08, 0xd9, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f, + 0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a, + 0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02, + 0x02, 0x00, 0x00, 0x03, 0xcf, 0x30, 0x00, 0x09, + 0x20, 0x00, 0x09, 0x49, 0x00, 0x04, 0x63, 0x80, + 0x04, 0xd9, 0x00, 0x44, 0x00, 0x04, 0xb1, 0x80, + 0x00, 0x00, 0x00, 0x46, 0x02, 0x30, 0x61, 0x0a, + 0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19, + 0x00, 0x00, 0x00, 0x46, 0x0e, 0x10, 0xc8, 0x0a, + 0x0c, 0x0b, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a, + 0x04, 0x2b, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a, + 0x04, 0x2b, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a, + 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0xa8, 0x18, + 0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82, + 0x00, 0x00, 0x00, 0x46, 0x00, 0x04, 0x33, 0x80, + 0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80, + 0x20, 0x01, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0x20, 0x03, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x46, 0x16, 0xce, 0x11, 0x00 +}; + +static unsigned char ima_adpcm_capture[] = { + 0x00, 0x10, 0x00, 0x44, 0x08, 0x00, 0x00, 0x44, + 0x00, 0x70, 0x00, 0x44, 0x08, 0xd0, 0x00, 0x44, + 0x00, 0xf0, 0x00, 0x44, 0x0d, 0xf2, 0x61, 0xa8, + 0x44, 0x04, 0x04, 0x19, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x04, 0x63, 0x80, 0x00, 0x00, 0x06, 0x39, + 0xff, 0x2e, 0x21, 0x49, 0xff, 0x0c, 0xd4, 0x49, + 0x40, 0x49, 0x39, 0xac, 0x55, 0x55, 0x71, 0x8b, + 0x50, 0x01, 0xb1, 0x80, 0x00, 0x00, 0x71, 0x8b, + 0xc2, 0x30, 0x04, 0x19, 0xc0, 0xa0, 0x04, 0x19, + 0xc2, 0xa0, 0x04, 0x19, 0x89, 0x00, 0x71, 0x8b, + 0xc8, 0x30, 0x04, 0x19, 0x71, 0x00, 0x71, 0x8b, + 0xcd, 0x00, 0x04, 0x19, 0xcf, 0x00, 0x04, 0x19, + 0x80, 0x00, 0x71, 0x8b, 0xcb, 0x20, 0x04, 0x19, + 0x20, 0x00, 0x71, 0x8b, 0xc4, 0x20, 0x04, 0x19, + 0x47, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39, + 0x00, 0x00, 0x63, 0x80, 0xc1, 0xa0, 0x04, 0x19, + 0x93, 0x00, 0x01, 0x4f, 0xcd, 0x30, 0x00, 0x09, + 0xcf, 0x30, 0x00, 0x09, 0x0c, 0x40, 0x00, 0x14, + 0x00, 0x60, 0x00, 0x14, 0x00, 0x04, 0x61, 0xa8, + 0x02, 0x04, 0x61, 0xa8, 0x0c, 0x60, 0x04, 0x24, + 0x00, 0x00, 0x34, 0x49, 0x08, 0x50, 0x00, 0x44, + 0x44, 0x04, 0x04, 0x39, 0x00, 0x00, 0x40, 0x45, + 0x08, 0x30, 0x61, 0x0a, 0x05, 0xb0, 0xe8, 0x18, + 0x0c, 0xc0, 0x04, 0x54, 0xc8, 0x30, 0x04, 0x19, + 0x09, 0x04, 0x00, 0xa8, 0x0b, 0x04, 0x00, 0xa8, + 0x00, 0x00, 0x40, 0x45, 0x09, 0x04, 0x61, 0xa8, + 0xc1, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8, + 0xca, 0x00, 0x04, 0x19, 0x0d, 0x00, 0x61, 0x0a, + 0x00, 0x01, 0x00, 0x45, 0x0f, 0x00, 0x61, 0x0a, + 0x00, 0x40, 0x09, 0x8f, 0x00, 0x01, 0x00, 0x45, + 0x40, 0x40, 0x09, 0xef, 0xff, 0x20, 0x09, 0xcf, + 0x00, 0x04, 0x63, 0xa1, 0x50, 0x03, 0x33, 0x80, + 0x00, 0x04, 0xa3, 0x80, 0x00, 0xff, 0xc2, 0x8b, + 0x0c, 0x12, 0x04, 0x54, 0x08, 0x12, 0x00, 0xc4, + 0x20, 0x03, 0x80, 0xc0, 0x30, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x7a, 0x0a, 0xd0, 0x01, 0x00, 0x82, + 0x04, 0x50, 0x00, 0x44, 0xc0, 0x00, 0x00, 0x99, + 0x04, 0x50, 0x00, 0x44, 0x00, 0xff, 0xc2, 0x8b, + 0x20, 0x00, 0x00, 0x80, 0x00, 0x0d, 0x42, 0x8b, + 0x04, 0x42, 0x00, 0xc4, 0x00, 0x0e, 0x42, 0x8b, + 0x08, 0x52, 0x00, 0xc4, 0x00, 0x1e, 0x42, 0x8b, + 0x00, 0xe2, 0x00, 0xc4, 0x00, 0x8e, 0x42, 0x8b, + 0x08, 0xd2, 0x00, 0xc4, 0x00, 0x9e, 0x42, 0x8b, + 0x04, 0xf2, 0x00, 0xc4, 0x00, 0xbe, 0x42, 0x8b, + 0x04, 0xf2, 0x00, 0xc4, 0x00, 0x04, 0x42, 0x8b, + 0x04, 0x11, 0x00, 0xc4, 0x00, 0x24, 0x42, 0x8b, + 0x0c, 0x61, 0x00, 0xc4, 0x00, 0x55, 0x42, 0x8b, + 0x04, 0x50, 0x00, 0xc4, 0x00, 0x3f, 0x42, 0x8b, + 0x0c, 0x01, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45, + 0x20, 0x01, 0x79, 0x80, 0x00, 0x30, 0x42, 0x8b, + 0x04, 0x62, 0x00, 0xc4, 0x00, 0x00, 0x40, 0x45, + 0x00, 0x00, 0x71, 0x8b, 0x40, 0x01, 0x00, 0x80, + 0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0x08, 0xc2, 0x00, 0xc4, 0x0f, 0xf2, 0xa8, 0xa8, + 0x20, 0x00, 0xb1, 0x88, 0x00, 0x00, 0x41, 0x02, + 0x4d, 0xf2, 0x00, 0x39, 0xc0, 0x01, 0x00, 0x82, + 0x04, 0x50, 0x00, 0x44, 0x0d, 0xf2, 0xa3, 0xa8, + 0x4d, 0xf2, 0x00, 0x39, 0x04, 0x50, 0x00, 0x44, + 0xff, 0x00, 0xe2, 0xab, 0x20, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x61, 0x02, 0x4d, 0xf2, 0x04, 0x19, + 0x04, 0x50, 0x00, 0x44, 0xff, 0x00, 0xe2, 0xab, + 0xa0, 0x00, 0x00, 0x88, 0x00, 0x00, 0x61, 0x10, + 0x4d, 0xf2, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44, + 0xff, 0x20, 0xe2, 0xab, 0x60, 0x00, 0x00, 0x88, + 0x00, 0x00, 0x71, 0xc0, 0x4d, 0xf2, 0x04, 0x19, + 0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x7a, 0x0a, + 0x20, 0x01, 0xf0, 0x80, 0x01, 0xa0, 0x41, 0x0a, + 0x00, 0x11, 0x00, 0xc4, 0x20, 0x01, 0xf0, 0x80, + 0xc1, 0x30, 0x04, 0x19, 0x04, 0x50, 0x00, 0x44, + 0x00, 0x00, 0x79, 0x80, 0x0c, 0x41, 0x00, 0x84, + 0x89, 0x00, 0x71, 0x8b, 0xc8, 0x30, 0x04, 0x19, + 0x97, 0x00, 0x71, 0x8b, 0xcd, 0x00, 0x00, 0x39, + 0x00, 0x01, 0xb1, 0x80, 0x80, 0x00, 0x04, 0x19, + 0x82, 0x00, 0x04, 0x19, 0xc1, 0x20, 0x04, 0x19, + 0xc3, 0x20, 0x04, 0x19, 0xc2, 0x30, 0x04, 0x19, + 0xcd, 0x10, 0x04, 0x19, 0xcf, 0x10, 0x04, 0x19, + 0xb0, 0x00, 0x71, 0x8b, 0x84, 0x00, 0x04, 0x19, + 0x86, 0x00, 0x04, 0x19, 0x80, 0x00, 0x71, 0x8b, + 0xcb, 0x20, 0x04, 0x19, 0x93, 0x00, 0x01, 0x4f, + 0xcd, 0x30, 0x00, 0x09, 0xcf, 0x30, 0x00, 0x09, + 0x03, 0x02, 0x04, 0x49, 0x08, 0x41, 0x00, 0x14, + 0x04, 0x50, 0x00, 0x44, 0x00, 0x00, 0x63, 0x80, + 0x00, 0x00, 0x06, 0x19, 0x03, 0x00, 0x04, 0x49, + 0x04, 0x50, 0x00, 0x44, 0x20, 0x01, 0x63, 0x80, + 0x00, 0x00, 0x06, 0x19, 0x00, 0x20, 0xe2, 0x8b, + 0x00, 0xc1, 0x00, 0x84, 0x47, 0x00, 0x51, 0x8b, + 0xc0, 0x20, 0x00, 0x39, 0x00, 0x00, 0x63, 0x80, + 0xc1, 0xa0, 0x04, 0x19, 0x00, 0xe1, 0x00, 0x44, + 0xbd, 0x00, 0x51, 0x8b, 0xc0, 0x20, 0x00, 0x39, + 0x00, 0x00, 0xb1, 0x80, 0xc1, 0xa0, 0x04, 0x19, + 0x03, 0x00, 0x04, 0x49, 0x04, 0x50, 0x00, 0x44, + 0x00, 0x20, 0x61, 0x0a, 0x00, 0x01, 0x00, 0x45, + 0x02, 0x30, 0x61, 0x0a, 0x0c, 0x83, 0x00, 0xc4, + 0x0c, 0x78, 0x08, 0x44, 0x04, 0x5a, 0x08, 0x44, + 0xb2, 0x00, 0x09, 0x4f, 0x10, 0x42, 0x09, 0x8e, + 0x05, 0xb0, 0xe0, 0x18, 0x04, 0x23, 0x00, 0x84, + 0x0c, 0x01, 0x00, 0x11, 0x08, 0x05, 0x61, 0x10, + 0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44, + 0xb2, 0x00, 0x09, 0x4f, 0x80, 0x00, 0x71, 0x8b, + 0xc0, 0x00, 0x00, 0x82, 0x0c, 0x01, 0x33, 0x10, + 0x28, 0x01, 0xa3, 0x10, 0x00, 0x01, 0x7a, 0x80, + 0x8c, 0x01, 0x00, 0x80, 0x02, 0x30, 0x61, 0x0a, + 0x20, 0x00, 0x04, 0x19, 0x0c, 0x83, 0x00, 0xc4, + 0x05, 0xb0, 0xc8, 0x18, 0x08, 0x43, 0x00, 0xc4, + 0x01, 0x30, 0xc8, 0x0a, 0x0c, 0x38, 0x00, 0xc4, + 0x08, 0x88, 0x00, 0x44, 0x0c, 0x78, 0x08, 0x44, + 0x04, 0x5a, 0x08, 0x44, 0x00, 0x00, 0xa3, 0x18, + 0x80, 0x00, 0x04, 0x19, 0x0b, 0x04, 0x61, 0xa8, + 0xc3, 0x20, 0x00, 0x39, 0xc3, 0x30, 0x04, 0x19, + 0x0f, 0x10, 0x61, 0x0a, 0xca, 0x30, 0x04, 0x19, + 0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39, + 0xd1, 0x00, 0x09, 0x4f, 0x00, 0x04, 0x61, 0x02, + 0x08, 0x63, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a, + 0x20, 0x00, 0x00, 0x39, 0xa3, 0x00, 0x09, 0x4f, + 0x00, 0x04, 0x61, 0x02, 0x00, 0x48, 0x08, 0x44, + 0x08, 0x88, 0x00, 0x44, 0x02, 0x30, 0x61, 0x0a, + 0x00, 0x08, 0x00, 0xc4, 0x0c, 0x78, 0x08, 0x44, + 0x04, 0x5a, 0x08, 0x44, 0xb2, 0x00, 0x09, 0x0f, + 0x10, 0x40, 0x09, 0x8e, 0x00, 0x00, 0x68, 0x5b, + 0x20, 0x04, 0xb1, 0x80, 0x02, 0x00, 0x61, 0x5b, + 0x88, 0x03, 0x7a, 0x80, 0xac, 0x01, 0x00, 0x80, + 0x05, 0xb0, 0xe0, 0x18, 0x00, 0xd3, 0x00, 0x84, + 0x00, 0x49, 0x08, 0x44, 0x00, 0x48, 0x08, 0x44, + 0xb2, 0x00, 0x09, 0x0f, 0x80, 0x00, 0x71, 0x8b, + 0xc0, 0x00, 0x00, 0x82, 0x02, 0x30, 0x61, 0x0a, + 0x00, 0x08, 0x00, 0xc4, 0x05, 0xb0, 0xc8, 0x18, + 0x0c, 0x18, 0x00, 0xc4, 0x01, 0x30, 0xc8, 0x0a, + 0x0c, 0x38, 0x00, 0xc4, 0x08, 0x88, 0x00, 0x44, + 0x0c, 0x78, 0x08, 0x44, 0x00, 0x00, 0x61, 0x18, + 0x20, 0x05, 0xb1, 0x80, 0x00, 0x00, 0x68, 0xcb, + 0x80, 0x00, 0x04, 0x19, 0x0d, 0x10, 0x61, 0x0a, + 0xc3, 0x30, 0x04, 0x19, 0x0b, 0x04, 0x41, 0xa8, + 0x09, 0x04, 0x41, 0xa8, 0xe1, 0x20, 0x00, 0x39, + 0x08, 0x38, 0x00, 0x44, 0x03, 0x30, 0x41, 0x0a, + 0x20, 0x04, 0xb1, 0x80, 0x00, 0x48, 0x08, 0x44, + 0x08, 0x88, 0x00, 0x44, 0x00, 0x00, 0xb1, 0x80, + 0xc2, 0x30, 0x04, 0x19, 0x0c, 0xb8, 0x00, 0xd4, + 0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a, + 0x0c, 0xb8, 0x00, 0xc4, 0x93, 0x00, 0x01, 0x4f, + 0xe7, 0x00, 0x01, 0x6f, 0x0f, 0x30, 0x61, 0x0a, + 0x20, 0x00, 0x00, 0x88, 0x02, 0x00, 0x61, 0x02, + 0x41, 0x04, 0x04, 0x19, 0x02, 0x04, 0x61, 0x02, + 0x43, 0x04, 0x04, 0x39, 0xcf, 0x30, 0x00, 0x09, + 0x20, 0x00, 0x09, 0x49, 0x00, 0x59, 0x00, 0x44, + 0x93, 0x00, 0x01, 0x4f, 0xe7, 0x00, 0x01, 0x6f, + 0x0d, 0x30, 0x61, 0x0a, 0x20, 0x00, 0x61, 0x88, + 0xc2, 0x00, 0x00, 0x82, 0xc2, 0x03, 0x00, 0x82, + 0xcd, 0x30, 0x00, 0x09, 0x20, 0x00, 0x09, 0x49, + 0x0f, 0x30, 0x61, 0x0a, 0x0d, 0x30, 0xc8, 0x0a, + 0x0c, 0x58, 0x00, 0x84, 0x02, 0x30, 0x61, 0x0a, + 0x05, 0xb0, 0xa8, 0x18, 0xc2, 0x30, 0x04, 0x19, + 0x00, 0x00, 0x00, 0x46, 0x90, 0x40, 0x09, 0x8f, + 0x12, 0x04, 0x09, 0x6e, 0x03, 0x00, 0x09, 0x0e, + 0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80, + 0x00, 0x00, 0x61, 0xcb, 0x80, 0x04, 0xb1, 0x80, + 0x00, 0x01, 0xe0, 0x60, 0x0c, 0xd8, 0x04, 0x14, + 0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b, + 0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2, + 0x20, 0x00, 0xc0, 0x80, 0x08, 0x0a, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80, + 0x20, 0x00, 0xc0, 0x80, 0x0c, 0x2a, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80, + 0x20, 0x00, 0xc0, 0x80, 0x08, 0x3a, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82, + 0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11, + 0x40, 0x00, 0xc2, 0x8b, 0x00, 0xaa, 0x00, 0xc4, + 0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18, + 0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82, + 0xf0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a, + 0x2d, 0x10, 0x61, 0x0a, 0xd1, 0x00, 0x09, 0x2e, + 0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a, + 0x0c, 0xba, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a, + 0x04, 0x4a, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a, + 0x04, 0x4a, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a, + 0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18, + 0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82, + 0x03, 0x00, 0x09, 0x0e, 0x9a, 0x01, 0x00, 0x60, + 0x32, 0x00, 0x09, 0x2e, 0x00, 0x00, 0x00, 0x46, + 0x00, 0x01, 0x71, 0x82, 0x20, 0x01, 0x00, 0x80, + 0x00, 0x00, 0x61, 0xcb, 0x80, 0x24, 0xb1, 0xc0, + 0x00, 0x31, 0xe0, 0x60, 0x0c, 0xca, 0x04, 0x14, + 0x00, 0x01, 0xeb, 0x80, 0x40, 0x00, 0x52, 0x1b, + 0x80, 0x00, 0x79, 0x80, 0xc0, 0x01, 0x71, 0xc2, + 0x20, 0x00, 0xc0, 0x80, 0x08, 0xda, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x80, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80, + 0x20, 0x00, 0xc0, 0x80, 0x0c, 0xfa, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x10, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0x00, 0x01, 0xf0, 0x80, + 0x20, 0x00, 0xc0, 0x80, 0x08, 0x29, 0x04, 0x54, + 0xc0, 0x04, 0xa8, 0x82, 0x20, 0x00, 0x72, 0x1b, + 0x80, 0x00, 0x00, 0x80, 0xc0, 0x03, 0xf0, 0x82, + 0x20, 0x00, 0xa0, 0x80, 0x00, 0x01, 0x00, 0x11, + 0x40, 0x00, 0xc2, 0x8b, 0x00, 0x39, 0x00, 0xc4, + 0x00, 0x00, 0xe9, 0x80, 0x05, 0xb0, 0xa8, 0x18, + 0x00, 0x01, 0xa8, 0x22, 0xd0, 0x01, 0x00, 0x82, + 0xb0, 0x00, 0xe2, 0x1b, 0x06, 0x20, 0xa8, 0x0a, + 0x2f, 0x10, 0x61, 0x0a, 0xf1, 0x00, 0x09, 0x2e, + 0x00, 0x01, 0xa8, 0x02, 0x0e, 0x10, 0xc8, 0x0a, + 0x0c, 0xa9, 0x04, 0x14, 0x0e, 0x10, 0x61, 0x0a, + 0x04, 0x99, 0x00, 0x44, 0x0c, 0x10, 0xc8, 0x0a, + 0x04, 0x99, 0x04, 0x54, 0x0c, 0x10, 0x61, 0x0a, + 0xd0, 0x01, 0x00, 0x82, 0x00, 0x10, 0xa8, 0x18, + 0xa0, 0x00, 0x00, 0x88, 0x00, 0x01, 0x71, 0x82, + 0x9f, 0x01, 0x00, 0x60, 0x00, 0x00, 0x00, 0x46, + 0x00, 0x00, 0x33, 0x80, 0x00, 0x00, 0x83, 0x80, + 0x20, 0x00, 0x7a, 0x80, 0x20, 0x07, 0x33, 0x80, + 0x00, 0x00, 0x83, 0x80, 0x20, 0x04, 0x7a, 0x80, + 0x20, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x46, + 0x02, 0x00, 0x61, 0x0a, 0x04, 0x1b, 0x04, 0x14, + 0x01, 0x00, 0x61, 0x0a, 0x03, 0x00, 0x48, 0x0a, + 0x0c, 0x79, 0x04, 0x54, 0xc3, 0x00, 0x04, 0x19, + 0x04, 0xc9, 0x00, 0x44, 0x08, 0x00, 0xc8, 0x0a, + 0x04, 0xc9, 0x04, 0x54, 0xc8, 0x00, 0x04, 0x19, + 0x0a, 0x00, 0x61, 0x0a, 0x09, 0x00, 0x48, 0x0a, + 0x0c, 0xe9, 0x04, 0x54, 0xc9, 0x00, 0x04, 0x19, + 0x04, 0xd9, 0x00, 0x44, 0x0b, 0x00, 0xc8, 0x0a, + 0x04, 0xd9, 0x04, 0x54, 0xcb, 0x00, 0x04, 0x19, + 0x04, 0x00, 0x61, 0x0a, 0x06, 0x00, 0x48, 0x0a, + 0x0c, 0xf9, 0x04, 0x54, 0xc6, 0x00, 0x04, 0x19, + 0x04, 0x0b, 0x00, 0x44, 0x05, 0x00, 0xc8, 0x0a, + 0x04, 0x0b, 0x04, 0x54, 0xc5, 0x00, 0x04, 0x19, + 0x07, 0x00, 0x61, 0x0a, 0x0c, 0x00, 0x48, 0x0a, + 0x0c, 0x2b, 0x04, 0x54, 0xcc, 0x00, 0x04, 0x19, + 0x04, 0x1b, 0x00, 0x44, 0x0e, 0x00, 0xc8, 0x0a, + 0x04, 0x1b, 0x04, 0x54, 0xce, 0x00, 0x04, 0x19, + 0x00, 0x00, 0x40, 0x45, 0x92, 0x20, 0x71, 0x8b, + 0xa6, 0xc5, 0x11, 0x00 +}; diff --git a/sound/isa/sb/sb16_main.c b/sound/isa/sb/sb16_main.c new file mode 100644 index 00000000000..a6a0fa51626 --- /dev/null +++ b/sound/isa/sb/sb16_main.c @@ -0,0 +1,916 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of 16-bit SoundBlaster cards and clones + * Note: This is very ugly hardware which uses one 8-bit DMA channel and + * second 16-bit DMA channel. Unfortunately 8-bit DMA channel can't + * transfer 16-bit samples and 16-bit DMA channels can't transfer + * 8-bit samples. This make full duplex more complicated than + * can be... People, don't buy these soundcards for full 16-bit + * duplex!!! + * Note: 16-bit wide is assigned to first direction which made request. + * With full duplex - playback is preferred with abstract layer. + * + * Note: Some chip revisions have hardware bug. Changing capture + * channel from full-duplex 8bit DMA to 16bit DMA will block + * 16bit DMA transfers from DSP chip (capture) until 8bit transfer + * to DSP chip (playback) starts. This bug can be avoided with + * "16bit DMA Allocation" setting set to Playback or Capture. + * + * + * 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 <asm/io.h> +#include <asm/dma.h> +#include <linux/init.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/sb16_csp.h> +#include <sound/mpu401.h> +#include <sound/control.h> +#include <sound/info.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Routines for control of 16-bit SoundBlaster cards and clones"); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_SND_SB16_CSP +static void snd_sb16_csp_playback_prepare(sb_t *chip, snd_pcm_runtime_t *runtime) +{ + if (chip->hardware == SB_HW_16CSP) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) && + ((1U << runtime->format) == csp->acc_format)) { + /* Supported runtime PCM format for playback */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } else if ((csp->mode & SNDRV_SB_CSP_MODE_QSOUND) && (csp->q_enabled)) { + /* QSound decoder is loaded and enabled */ + if ((1 << runtime->format) & (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE)) { + /* Only for simple PCM formats */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } + } + } else if (csp->ops.csp_use(csp) == 0) { + /* Acquire CSP and try to autoload hardware codec */ + if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_WRITE)) { + /* Unsupported format, release CSP */ + csp->ops.csp_unuse(csp); + } else { + __start_CSP: + /* Try to start CSP */ + if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_PLAYBACK_16) ? + SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT, + (runtime->channels > 1) ? + SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) { + /* Failed, release CSP */ + csp->ops.csp_unuse(csp); + } else { + /* Success, CSP acquired and running */ + chip->open = SNDRV_SB_CSP_MODE_DSP_WRITE; + } + } + } + } +} + +static void snd_sb16_csp_capture_prepare(sb_t *chip, snd_pcm_runtime_t *runtime) +{ + if (chip->hardware == SB_HW_16CSP) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if ((csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) && + ((1U << runtime->format) == csp->acc_format)) { + /* Supported runtime PCM format for capture */ + if (csp->ops.csp_use(csp) == 0) { + /* If CSP was successfully acquired */ + goto __start_CSP; + } + } + } else if (csp->ops.csp_use(csp) == 0) { + /* Acquire CSP and try to autoload hardware codec */ + if (csp->ops.csp_autoload(csp, runtime->format, SNDRV_SB_CSP_MODE_DSP_READ)) { + /* Unsupported format, release CSP */ + csp->ops.csp_unuse(csp); + } else { + __start_CSP: + /* Try to start CSP */ + if (csp->ops.csp_start(csp, (chip->mode & SB_MODE_CAPTURE_16) ? + SNDRV_SB_CSP_SAMPLE_16BIT : SNDRV_SB_CSP_SAMPLE_8BIT, + (runtime->channels > 1) ? + SNDRV_SB_CSP_STEREO : SNDRV_SB_CSP_MONO)) { + /* Failed, release CSP */ + csp->ops.csp_unuse(csp); + } else { + /* Success, CSP acquired and running */ + chip->open = SNDRV_SB_CSP_MODE_DSP_READ; + } + } + } + } +} + +static void snd_sb16_csp_update(sb_t *chip) +{ + if (chip->hardware == SB_HW_16CSP) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->qpos_changed) { + spin_lock(&chip->reg_lock); + csp->ops.csp_qsound_transfer (csp); + spin_unlock(&chip->reg_lock); + } + } +} + +static void snd_sb16_csp_playback_open(sb_t *chip, snd_pcm_runtime_t *runtime) +{ + /* CSP decoders (QSound excluded) support only 16bit transfers */ + if (chip->hardware == SB_HW_16CSP) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if (csp->mode & SNDRV_SB_CSP_MODE_DSP_WRITE) { + runtime->hw.formats |= csp->acc_format; + } + } else { + /* autoloaded codecs */ + runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM; + } + } +} + +static void snd_sb16_csp_playback_close(sb_t *chip) +{ + if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_WRITE)) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->ops.csp_stop(csp) == 0) { + csp->ops.csp_unuse(csp); + chip->open = 0; + } + } +} + +static void snd_sb16_csp_capture_open(sb_t *chip, snd_pcm_runtime_t *runtime) +{ + /* CSP coders support only 16bit transfers */ + if (chip->hardware == SB_HW_16CSP) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->running & SNDRV_SB_CSP_ST_LOADED) { + /* manually loaded codec */ + if (csp->mode & SNDRV_SB_CSP_MODE_DSP_READ) { + runtime->hw.formats |= csp->acc_format; + } + } else { + /* autoloaded codecs */ + runtime->hw.formats |= SNDRV_PCM_FMTBIT_MU_LAW | SNDRV_PCM_FMTBIT_A_LAW | + SNDRV_PCM_FMTBIT_IMA_ADPCM; + } + } +} + +static void snd_sb16_csp_capture_close(sb_t *chip) +{ + if ((chip->hardware == SB_HW_16CSP) && (chip->open == SNDRV_SB_CSP_MODE_DSP_READ)) { + snd_sb_csp_t *csp = chip->csp; + + if (csp->ops.csp_stop(csp) == 0) { + csp->ops.csp_unuse(csp); + chip->open = 0; + } + } +} +#else +#define snd_sb16_csp_playback_prepare(chip, runtime) /*nop*/ +#define snd_sb16_csp_capture_prepare(chip, runtime) /*nop*/ +#define snd_sb16_csp_update(chip) /*nop*/ +#define snd_sb16_csp_playback_open(chip, runtime) /*nop*/ +#define snd_sb16_csp_playback_close(chip) /*nop*/ +#define snd_sb16_csp_capture_open(chip, runtime) /*nop*/ +#define snd_sb16_csp_capture_close(chip) /*nop*/ +#endif + + +static void snd_sb16_setup_rate(sb_t *chip, + unsigned short rate, + int channel) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & (channel == SNDRV_PCM_STREAM_PLAYBACK ? SB_MODE_PLAYBACK_16 : SB_MODE_CAPTURE_16)) + snd_sb_ack_16bit(chip); + else + snd_sb_ack_8bit(chip); + if (!(chip->mode & SB_RATE_LOCK)) { + chip->locked_rate = rate; + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_IN); + snd_sbdsp_command(chip, rate >> 8); + snd_sbdsp_command(chip, rate & 0xff); + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT); + snd_sbdsp_command(chip, rate >> 8); + snd_sbdsp_command(chip, rate & 0xff); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +static int snd_sb16_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_sb16_hw_free(snd_pcm_substream_t * substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_sb16_playback_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned char format; + unsigned int size, count, dma; + + snd_sb16_csp_playback_prepare(chip, runtime); + if (snd_pcm_format_unsigned(runtime->format) > 0) { + format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO; + } else { + format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO; + } + + snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_PLAYBACK); + size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream); + dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16; + snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_WRITE | DMA_AUTOINIT); + + count = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & SB_MODE_PLAYBACK_16) { + count >>= 1; + count--; + snd_sbdsp_command(chip, SB_DSP4_OUT16_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + } else { + count--; + snd_sbdsp_command(chip, SB_DSP4_OUT8_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_playback_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + int result = 0; + + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + chip->mode |= SB_RATE_LOCK_PLAYBACK; + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF); + /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */ + if (chip->mode & SB_RATE_LOCK_CAPTURE) + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + chip->mode &= ~SB_RATE_LOCK_PLAYBACK; + break; + default: + result = -EINVAL; + } + spin_unlock(&chip->reg_lock); + return result; +} + +static int snd_sb16_capture_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned char format; + unsigned int size, count, dma; + + snd_sb16_csp_capture_prepare(chip, runtime); + if (snd_pcm_format_unsigned(runtime->format) > 0) { + format = runtime->channels > 1 ? SB_DSP4_MODE_UNS_STEREO : SB_DSP4_MODE_UNS_MONO; + } else { + format = runtime->channels > 1 ? SB_DSP4_MODE_SIGN_STEREO : SB_DSP4_MODE_SIGN_MONO; + } + snd_sb16_setup_rate(chip, runtime->rate, SNDRV_PCM_STREAM_CAPTURE); + size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream); + dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16; + snd_dma_program(dma, runtime->dma_addr, size, DMA_MODE_READ | DMA_AUTOINIT); + + count = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + if (chip->mode & SB_MODE_CAPTURE_16) { + count >>= 1; + count--; + snd_sbdsp_command(chip, SB_DSP4_IN16_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + } else { + count--; + snd_sbdsp_command(chip, SB_DSP4_IN8_AI); + snd_sbdsp_command(chip, format); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_capture_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + int result = 0; + + spin_lock(&chip->reg_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + chip->mode |= SB_RATE_LOCK_CAPTURE; + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_sbdsp_command(chip, chip->mode & SB_MODE_CAPTURE_16 ? SB_DSP_DMA16_OFF : SB_DSP_DMA8_OFF); + /* next two lines are needed for some types of DSP4 (SB AWE 32 - 4.13) */ + if (chip->mode & SB_RATE_LOCK_PLAYBACK) + snd_sbdsp_command(chip, chip->mode & SB_MODE_PLAYBACK_16 ? SB_DSP_DMA16_ON : SB_DSP_DMA8_ON); + chip->mode &= ~SB_RATE_LOCK_CAPTURE; + break; + default: + result = -EINVAL; + } + spin_unlock(&chip->reg_lock); + return result; +} + +irqreturn_t snd_sb16dsp_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + sb_t *chip = dev_id; + unsigned char status; + int ok; + + spin_lock(&chip->mixer_lock); + status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS); + spin_unlock(&chip->mixer_lock); + if ((status & SB_IRQTYPE_MPUIN) && chip->rmidi_callback) + chip->rmidi_callback(irq, chip->rmidi->private_data, regs); + if (status & SB_IRQTYPE_8BIT) { + ok = 0; + if (chip->mode & SB_MODE_PLAYBACK_8) { + snd_pcm_period_elapsed(chip->playback_substream); + snd_sb16_csp_update(chip); + ok++; + } + if (chip->mode & SB_MODE_CAPTURE_8) { + snd_pcm_period_elapsed(chip->capture_substream); + ok++; + } + spin_lock(&chip->reg_lock); + if (!ok) + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + snd_sb_ack_8bit(chip); + spin_unlock(&chip->reg_lock); + } + if (status & SB_IRQTYPE_16BIT) { + ok = 0; + if (chip->mode & SB_MODE_PLAYBACK_16) { + snd_pcm_period_elapsed(chip->playback_substream); + snd_sb16_csp_update(chip); + ok++; + } + if (chip->mode & SB_MODE_CAPTURE_16) { + snd_pcm_period_elapsed(chip->capture_substream); + ok++; + } + spin_lock(&chip->reg_lock); + if (!ok) + snd_sbdsp_command(chip, SB_DSP_DMA16_OFF); + snd_sb_ack_16bit(chip); + spin_unlock(&chip->reg_lock); + } + return IRQ_HANDLED; +} + +/* + + */ + +static snd_pcm_uframes_t snd_sb16_playback_pointer(snd_pcm_substream_t * substream) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + unsigned int dma; + size_t ptr; + + dma = (chip->mode & SB_MODE_PLAYBACK_8) ? chip->dma8 : chip->dma16; + ptr = snd_dma_pointer(dma, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_sb16_capture_pointer(snd_pcm_substream_t * substream) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + unsigned int dma; + size_t ptr; + + dma = (chip->mode & SB_MODE_CAPTURE_8) ? chip->dma8 : chip->dma16; + ptr = snd_dma_pointer(dma, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static snd_pcm_hardware_t snd_sb16_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = 0, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 4000, + .rate_max = 44100, + .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_sb16_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = 0, + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_44100, + .rate_min = 4000, + .rate_max = 44100, + .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, +}; + +/* + * open/close + */ + +static int snd_sb16_playback_open(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->mode & SB_MODE_PLAYBACK) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + runtime->hw = snd_sb16_playback; + + /* skip if 16 bit DMA was reserved for capture */ + if (chip->force_mode16 & SB_MODE_CAPTURE_16) + goto __skip_16bit; + + if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_CAPTURE_16)) { + chip->mode |= SB_MODE_PLAYBACK_16; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + /* Vibra16X hack */ + if (chip->dma16 <= 3) { + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + } else { + snd_sb16_csp_playback_open(chip, runtime); + } + goto __open_ok; + } + + __skip_16bit: + if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_CAPTURE_8)) { + chip->mode |= SB_MODE_PLAYBACK_8; + /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */ + if (chip->dma16 < 0) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + chip->mode |= SB_MODE_PLAYBACK_16; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8; + } + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + goto __open_ok; + } + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + + __open_ok: + if (chip->hardware == SB_HW_ALS100) + runtime->hw.rate_max = 48000; + if (chip->mode & SB_RATE_LOCK) + runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; + chip->playback_substream = substream; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_playback_close(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + + snd_sb16_csp_playback_close(chip); + spin_lock_irqsave(&chip->open_lock, flags); + chip->playback_substream = NULL; + chip->mode &= ~SB_MODE_PLAYBACK; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_capture_open(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->mode & SB_MODE_CAPTURE) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + runtime->hw = snd_sb16_capture; + + /* skip if 16 bit DMA was reserved for playback */ + if (chip->force_mode16 & SB_MODE_PLAYBACK_16) + goto __skip_16bit; + + if (chip->dma16 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_16)) { + chip->mode |= SB_MODE_CAPTURE_16; + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + /* Vibra16X hack */ + if (chip->dma16 <= 3) { + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + } else { + snd_sb16_csp_capture_open(chip, runtime); + } + goto __open_ok; + } + + __skip_16bit: + if (chip->dma8 >= 0 && !(chip->mode & SB_MODE_PLAYBACK_8)) { + chip->mode |= SB_MODE_CAPTURE_8; + /* DSP v 4.xx can transfer 16bit data through 8bit DMA channel, SBHWPG 2-7 */ + if (chip->dma16 < 0) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE; + chip->mode |= SB_MODE_CAPTURE_16; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S8; + } + runtime->hw.buffer_bytes_max = + runtime->hw.period_bytes_max = 64 * 1024; + goto __open_ok; + } + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + + __open_ok: + if (chip->hardware == SB_HW_ALS100) + runtime->hw.rate_max = 48000; + if (chip->mode & SB_RATE_LOCK) + runtime->hw.rate_min = runtime->hw.rate_max = chip->locked_rate; + chip->capture_substream = substream; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +static int snd_sb16_capture_close(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + + snd_sb16_csp_capture_close(chip); + spin_lock_irqsave(&chip->open_lock, flags); + chip->capture_substream = NULL; + chip->mode &= ~SB_MODE_CAPTURE; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +/* + * DMA control interface + */ + +static int snd_sb16_set_dma_mode(sb_t *chip, int what) +{ + if (chip->dma8 < 0 || chip->dma16 < 0) { + snd_assert(what == 0, return -EINVAL); + return 0; + } + if (what == 0) { + chip->force_mode16 = 0; + } else if (what == 1) { + chip->force_mode16 = SB_MODE_PLAYBACK_16; + } else if (what == 2) { + chip->force_mode16 = SB_MODE_CAPTURE_16; + } else { + return -EINVAL; + } + return 0; +} + +static int snd_sb16_get_dma_mode(sb_t *chip) +{ + if (chip->dma8 < 0 || chip->dma16 < 0) + return 0; + switch (chip->force_mode16) { + case SB_MODE_PLAYBACK_16: + return 1; + case SB_MODE_CAPTURE_16: + return 2; + default: + return 0; + } +} + +static int snd_sb16_dma_control_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[3] = { + "Auto", "Playback", "Capture" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + if (uinfo->value.enumerated.item > 2) + uinfo->value.enumerated.item = 2; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_sb16_dma_control_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + + spin_lock_irqsave(&chip->reg_lock, flags); + ucontrol->value.enumerated.item[0] = snd_sb16_get_dma_mode(chip); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return 0; +} + +static int snd_sb16_dma_control_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *chip = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char nval, oval; + int change; + + if ((nval = ucontrol->value.enumerated.item[0]) > 2) + return -EINVAL; + spin_lock_irqsave(&chip->reg_lock, flags); + oval = snd_sb16_get_dma_mode(chip); + change = nval != oval; + snd_sb16_set_dma_mode(chip, nval); + spin_unlock_irqrestore(&chip->reg_lock, flags); + return change; +} + +static snd_kcontrol_new_t snd_sb16_dma_control = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "16-bit DMA Allocation", + .info = snd_sb16_dma_control_info, + .get = snd_sb16_dma_control_get, + .put = snd_sb16_dma_control_put +}; + +/* + * Initialization part + */ + +int snd_sb16dsp_configure(sb_t * chip) +{ + unsigned long flags; + unsigned char irqreg = 0, dmareg = 0, mpureg; + unsigned char realirq, realdma, realmpureg; + /* note: mpu register should be present only on SB16 Vibra soundcards */ + + // printk("codec->irq=%i, codec->dma8=%i, codec->dma16=%i\n", chip->irq, chip->dma8, chip->dma16); + spin_lock_irqsave(&chip->mixer_lock, flags); + mpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP) & ~0x06; + spin_unlock_irqrestore(&chip->mixer_lock, flags); + switch (chip->irq) { + case 2: + case 9: + irqreg |= SB_IRQSETUP_IRQ9; + break; + case 5: + irqreg |= SB_IRQSETUP_IRQ5; + break; + case 7: + irqreg |= SB_IRQSETUP_IRQ7; + break; + case 10: + irqreg |= SB_IRQSETUP_IRQ10; + break; + default: + return -EINVAL; + } + if (chip->dma8 >= 0) { + switch (chip->dma8) { + case 0: + dmareg |= SB_DMASETUP_DMA0; + break; + case 1: + dmareg |= SB_DMASETUP_DMA1; + break; + case 3: + dmareg |= SB_DMASETUP_DMA3; + break; + default: + return -EINVAL; + } + } + if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) { + switch (chip->dma16) { + case 5: + dmareg |= SB_DMASETUP_DMA5; + break; + case 6: + dmareg |= SB_DMASETUP_DMA6; + break; + case 7: + dmareg |= SB_DMASETUP_DMA7; + break; + default: + return -EINVAL; + } + } + switch (chip->mpu_port) { + case 0x300: + mpureg |= 0x04; + break; + case 0x330: + mpureg |= 0x00; + break; + default: + mpureg |= 0x02; /* disable MPU */ + } + spin_lock_irqsave(&chip->mixer_lock, flags); + + snd_sbmixer_write(chip, SB_DSP4_IRQSETUP, irqreg); + realirq = snd_sbmixer_read(chip, SB_DSP4_IRQSETUP); + + snd_sbmixer_write(chip, SB_DSP4_DMASETUP, dmareg); + realdma = snd_sbmixer_read(chip, SB_DSP4_DMASETUP); + + snd_sbmixer_write(chip, SB_DSP4_MPUSETUP, mpureg); + realmpureg = snd_sbmixer_read(chip, SB_DSP4_MPUSETUP); + + spin_unlock_irqrestore(&chip->mixer_lock, flags); + if ((~realirq) & irqreg || (~realdma) & dmareg) { + snd_printk("SB16 [0x%lx]: unable to set DMA & IRQ (PnP device?)\n", chip->port); + snd_printk("SB16 [0x%lx]: wanted: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, realirq, realdma, realmpureg); + snd_printk("SB16 [0x%lx]: got: irqreg=0x%x, dmareg=0x%x, mpureg = 0x%x\n", chip->port, irqreg, dmareg, mpureg); + return -ENODEV; + } + return 0; +} + +static snd_pcm_ops_t snd_sb16_playback_ops = { + .open = snd_sb16_playback_open, + .close = snd_sb16_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb16_hw_params, + .hw_free = snd_sb16_hw_free, + .prepare = snd_sb16_playback_prepare, + .trigger = snd_sb16_playback_trigger, + .pointer = snd_sb16_playback_pointer, +}; + +static snd_pcm_ops_t snd_sb16_capture_ops = { + .open = snd_sb16_capture_open, + .close = snd_sb16_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb16_hw_params, + .hw_free = snd_sb16_hw_free, + .prepare = snd_sb16_capture_prepare, + .trigger = snd_sb16_capture_trigger, + .pointer = snd_sb16_capture_pointer, +}; + +static void snd_sb16dsp_pcm_free(snd_pcm_t *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_sb16dsp_pcm(sb_t * chip, int device, snd_pcm_t ** rpcm) +{ + snd_card_t *card = chip->card; + snd_pcm_t *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + if ((err = snd_pcm_new(card, "SB16 DSP", device, 1, 1, &pcm)) < 0) + return err; + sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff); + pcm->info_flags = SNDRV_PCM_INFO_JOINT_DUPLEX; + pcm->private_data = chip; + pcm->private_free = snd_sb16dsp_pcm_free; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb16_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb16_capture_ops); + + if (chip->dma16 >= 0 && chip->dma8 != chip->dma16) + snd_ctl_add(card, snd_ctl_new1(&snd_sb16_dma_control, chip)); + else + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 128*1024); + + if (rpcm) + *rpcm = pcm; + return 0; +} + +const snd_pcm_ops_t *snd_sb16dsp_get_pcm_ops(int direction) +{ + return direction == SNDRV_PCM_STREAM_PLAYBACK ? + &snd_sb16_playback_ops : &snd_sb16_capture_ops; +} + +EXPORT_SYMBOL(snd_sb16dsp_pcm); +EXPORT_SYMBOL(snd_sb16dsp_get_pcm_ops); +EXPORT_SYMBOL(snd_sb16dsp_configure); +EXPORT_SYMBOL(snd_sb16dsp_interrupt); + +/* + * INIT part + */ + +static int __init alsa_sb16_init(void) +{ + return 0; +} + +static void __exit alsa_sb16_exit(void) +{ +} + +module_init(alsa_sb16_init) +module_exit(alsa_sb16_exit) diff --git a/sound/isa/sb/sb8.c b/sound/isa/sb/sb8.c new file mode 100644 index 00000000000..e2cbc4202b3 --- /dev/null +++ b/sound/isa/sb/sb8.c @@ -0,0 +1,223 @@ +/* + * Driver for SoundBlaster 1.0/2.0/Pro soundcards and compatible + * 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/ioport.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/opl3.h> +#define SNDRV_LEGACY_AUTO_PROBE +#include <sound/initval.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("Sound Blaster 1.0/2.0/Pro"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Creative Labs,SB 1.0/SB 2.0/SB Pro}}"); + +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; /* 0x220,0x240,0x260 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,10 */ +static int dma8[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sound Blaster soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sound Blaster soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable Sound Blaster soundcard."); +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SB8 driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SB8 driver."); +module_param_array(dma8, int, NULL, 0444); +MODULE_PARM_DESC(dma8, "8-bit DMA # for SB8 driver."); + +struct snd_sb8 { + struct resource *fm_res; /* used to block FM i/o region for legacy cards */ +}; + +static snd_card_t *snd_sb8_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static irqreturn_t snd_sb8_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + sb_t *chip = dev_id; + + if (chip->open & SB_OPEN_PCM) { + return snd_sb8dsp_interrupt(chip); + } else { + return snd_sb8dsp_midi_interrupt(chip); + } +} + +static void snd_sb8_free(snd_card_t *card) +{ + struct snd_sb8 *acard = (struct snd_sb8 *)card->private_data; + + if (acard == NULL) + return; + if (acard->fm_res) { + release_resource(acard->fm_res); + kfree_nocheck(acard->fm_res); + } +} + +static int __init snd_sb8_probe(int dev) +{ + sb_t *chip; + snd_card_t *card; + struct snd_sb8 *acard; + opl3_t *opl3; + int err; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, + sizeof(struct snd_sb8)); + if (card == NULL) + return -ENOMEM; + acard = (struct snd_sb8 *)card->private_data; + card->private_free = snd_sb8_free; + + /* block the 0x388 port to avoid PnP conflicts */ + acard->fm_res = request_region(0x388, 4, "SoundBlaster FM"); + + if ((err = snd_sbdsp_create(card, port[dev], irq[dev], + snd_sb8_interrupt, + dma8[dev], + -1, + SB_HW_AUTO, + &chip)) < 0) { + snd_card_free(card); + return err; + } + if (chip->hardware >= SB_HW_16) { + snd_card_free(card); + if (chip->hardware == SB_HW_ALS100) + snd_printdd("ALS100 chip detected at 0x%lx, try snd-als100 module\n", + port[dev]); + else + snd_printdd("SB 16 chip detected at 0x%lx, try snd-sb16 module\n", + port[dev]); + return -ENODEV; + } + + if ((err = snd_sb8dsp_pcm(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_sbmixer_new(chip)) < 0) { + snd_card_free(card); + return err; + } + if (chip->hardware == SB_HW_10 || chip->hardware == SB_HW_20) { + if ((err = snd_opl3_create(card, chip->port + 8, 0, + OPL3_HW_AUTO, 1, + &opl3)) < 0) { + snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx\n", chip->port + 8); + } + } else { + if ((err = snd_opl3_create(card, chip->port, chip->port + 2, + OPL3_HW_AUTO, 1, + &opl3)) < 0) { + snd_printk(KERN_ERR "sb8: no OPL device at 0x%lx-0x%lx\n", + chip->port, chip->port + 2); + } + } + if (err >= 0) { + if ((err = snd_opl3_hwdep_new(opl3, 0, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + } + + if ((err = snd_sb8dsp_midi(chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + + strcpy(card->driver, chip->hardware == SB_HW_PRO ? "SB Pro" : "SB8"); + strcpy(card->shortname, chip->name); + sprintf(card->longname, "%s at 0x%lx, irq %d, dma %d", + chip->name, + chip->port, + irq[dev], dma8[dev]); + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_sb8_cards[dev] = card; + return 0; +} + +static int __init snd_card_sb8_legacy_auto_probe(unsigned long xport) +{ + static int dev; + int res; + + for ( ; dev < SNDRV_CARDS; dev++) { + if (!enable[dev] || port[dev] != SNDRV_AUTO_PORT) + continue; + port[dev] = xport; + res = snd_sb8_probe(dev); + if (res < 0) + port[dev] = SNDRV_AUTO_PORT; + return res; + } + return -ENODEV; +} + +static int __init alsa_card_sb8_init(void) +{ + static unsigned long possible_ports[] = {0x220, 0x240, 0x260, -1}; + int dev, cards, i; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) { + if (port[dev] == SNDRV_AUTO_PORT) + continue; + if (snd_sb8_probe(dev) >= 0) + cards++; + } + i = snd_legacy_auto_probe(possible_ports, snd_card_sb8_legacy_auto_probe); + if (i > 0) + cards += i; + + if (!cards) { +#ifdef MODULE + snd_printk(KERN_ERR "Sound Blaster soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_sb8_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_sb8_cards[idx]); +} + +module_init(alsa_card_sb8_init) +module_exit(alsa_card_sb8_exit) diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c new file mode 100644 index 00000000000..87c9b1ba06c --- /dev/null +++ b/sound/isa/sb/sb8_main.c @@ -0,0 +1,565 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Uros Bizjak <uros@kss-loka.si> + * + * Routines for control of 8-bit SoundBlaster cards and clones + * Please note: I don't have access to old SB8 soundcards. + * + * + * 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 + * + * -- + * + * Thu Apr 29 20:36:17 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk> + * DSP can't respond to commands whilst in "high speed" mode. Caused + * glitching during playback. Fixed. + * + * Wed Jul 12 22:02:55 CEST 2000 Uros Bizjak <uros@kss-loka.si> + * Cleaned up and rewrote lowlevel routines. + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <asm/dma.h> +#include <linux/init.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/sb.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>, Uros Bizjak <uros@kss-loka.si>"); +MODULE_DESCRIPTION("Routines for control of 8-bit SoundBlaster cards and clones"); +MODULE_LICENSE("GPL"); + +#define SB8_CLOCK 1000000 +#define SB8_DEN(v) ((SB8_CLOCK + (v) / 2) / (v)) +#define SB8_RATE(v) (SB8_CLOCK / SB8_DEN(v)) + +static ratnum_t clock = { + .num = SB8_CLOCK, + .den_min = 1, + .den_max = 256, + .den_step = 1, +}; + +static snd_pcm_hw_constraint_ratnums_t hw_constraints_clock = { + .nrats = 1, + .rats = &clock, +}; + +static ratnum_t stereo_clocks[] = { + { + .num = SB8_CLOCK, + .den_min = SB8_DEN(22050), + .den_max = SB8_DEN(22050), + .den_step = 1, + }, + { + .num = SB8_CLOCK, + .den_min = SB8_DEN(11025), + .den_max = SB8_DEN(11025), + .den_step = 1, + } +}; + +static int snd_sb8_hw_constraint_rate_channels(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t *c = hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + if (c->min > 1) { + unsigned int num = 0, den = 0; + int err = snd_interval_ratnum(hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE), + 2, stereo_clocks, &num, &den); + if (err >= 0 && den) { + params->rate_num = num; + params->rate_den = den; + } + return err; + } + return 0; +} + +static int snd_sb8_hw_constraint_channels_rate(snd_pcm_hw_params_t *params, + snd_pcm_hw_rule_t *rule) +{ + snd_interval_t *r = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + if (r->min > SB8_RATE(22050) || r->max <= SB8_RATE(11025)) { + snd_interval_t t = { .min = 1, .max = 1 }; + return snd_interval_refine(hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS), &t); + } + return 0; +} + +static int snd_sb8_playback_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int mixreg, rate, size, count; + + rate = runtime->rate; + switch (chip->hardware) { + case SB_HW_PRO: + if (runtime->channels > 1) { + snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL); + chip->playback_format = SB_DSP_HI_OUTPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_201: + if (rate > 23000) { + chip->playback_format = SB_DSP_HI_OUTPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_20: + chip->playback_format = SB_DSP_LO_OUTPUT_AUTO; + break; + case SB_HW_10: + chip->playback_format = SB_DSP_OUTPUT; + break; + default: + return -EINVAL; + } + size = chip->p_dma_size = snd_pcm_lib_buffer_bytes(substream); + count = chip->p_period_size = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON); + if (runtime->channels > 1) { + /* set playback stereo mode */ + spin_lock(&chip->mixer_lock); + mixreg = snd_sbmixer_read(chip, SB_DSP_STEREO_SW); + snd_sbmixer_write(chip, SB_DSP_STEREO_SW, mixreg | 0x02); + spin_unlock(&chip->mixer_lock); + + /* Soundblaster hardware programming reference guide, 3-23 */ + snd_sbdsp_command(chip, SB_DSP_DMA8_EXIT); + runtime->dma_area[0] = 0x80; + snd_dma_program(chip->dma8, runtime->dma_addr, 1, DMA_MODE_WRITE); + /* force interrupt */ + chip->mode = SB_MODE_HALT; + snd_sbdsp_command(chip, SB_DSP_OUTPUT); + snd_sbdsp_command(chip, 0); + snd_sbdsp_command(chip, 0); + } + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE); + if (runtime->channels > 1) { + snd_sbdsp_command(chip, 256 - runtime->rate_den / 2); + spin_lock(&chip->mixer_lock); + /* save output filter status and turn it off */ + mixreg = snd_sbmixer_read(chip, SB_DSP_PLAYBACK_FILT); + snd_sbmixer_write(chip, SB_DSP_PLAYBACK_FILT, mixreg | 0x20); + spin_unlock(&chip->mixer_lock); + /* just use force_mode16 for temporary storate... */ + chip->force_mode16 = mixreg; + } else { + snd_sbdsp_command(chip, 256 - runtime->rate_den); + } + if (chip->playback_format != SB_DSP_OUTPUT) { + count--; + snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_dma_program(chip->dma8, runtime->dma_addr, + size, DMA_MODE_WRITE | DMA_AUTOINIT); + return 0; +} + +static int snd_sb8_playback_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + unsigned int count; + + spin_lock_irqsave(&chip->reg_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_sbdsp_command(chip, chip->playback_format); + if (chip->playback_format == SB_DSP_OUTPUT) { + count = chip->p_period_size - 1; + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (chip->playback_format == SB_DSP_HI_OUTPUT_AUTO) { + snd_pcm_runtime_t *runtime = substream->runtime; + snd_sbdsp_reset(chip); + if (runtime->channels > 1) { + spin_lock(&chip->mixer_lock); + /* restore output filter and set hardware to mono mode */ + snd_sbmixer_write(chip, SB_DSP_STEREO_SW, chip->force_mode16 & ~0x02); + spin_unlock(&chip->mixer_lock); + } + } else { + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_PLAYBACK_8 : SB_MODE_HALT; + return 0; +} + +static int snd_sb8_hw_params(snd_pcm_substream_t * substream, + snd_pcm_hw_params_t * hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); +} + +static int snd_sb8_hw_free(snd_pcm_substream_t * substream) +{ + snd_pcm_lib_free_pages(substream); + return 0; +} + +static int snd_sb8_capture_prepare(snd_pcm_substream_t * substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned int mixreg, rate, size, count; + + rate = runtime->rate; + switch (chip->hardware) { + case SB_HW_PRO: + if (runtime->channels > 1) { + snd_assert(rate == SB8_RATE(11025) || rate == SB8_RATE(22050), return -EINVAL); + chip->capture_format = SB_DSP_HI_INPUT_AUTO; + break; + } + chip->capture_format = (rate > 23000) ? SB_DSP_HI_INPUT_AUTO : SB_DSP_LO_INPUT_AUTO; + break; + case SB_HW_201: + if (rate > 13000) { + chip->capture_format = SB_DSP_HI_INPUT_AUTO; + break; + } + /* fallthru */ + case SB_HW_20: + chip->capture_format = SB_DSP_LO_INPUT_AUTO; + break; + case SB_HW_10: + chip->capture_format = SB_DSP_INPUT; + break; + default: + return -EINVAL; + } + size = chip->c_dma_size = snd_pcm_lib_buffer_bytes(substream); + count = chip->c_period_size = snd_pcm_lib_period_bytes(substream); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + if (runtime->channels > 1) + snd_sbdsp_command(chip, SB_DSP_STEREO_8BIT); + snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE); + if (runtime->channels > 1) { + snd_sbdsp_command(chip, 256 - runtime->rate_den / 2); + spin_lock(&chip->mixer_lock); + /* save input filter status and turn it off */ + mixreg = snd_sbmixer_read(chip, SB_DSP_CAPTURE_FILT); + snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, mixreg | 0x20); + spin_unlock(&chip->mixer_lock); + /* just use force_mode16 for temporary storate... */ + chip->force_mode16 = mixreg; + } else { + snd_sbdsp_command(chip, 256 - runtime->rate_den); + } + if (chip->capture_format != SB_DSP_OUTPUT) { + count--; + snd_sbdsp_command(chip, SB_DSP_BLOCK_SIZE); + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_dma_program(chip->dma8, runtime->dma_addr, + size, DMA_MODE_READ | DMA_AUTOINIT); + return 0; +} + +static int snd_sb8_capture_trigger(snd_pcm_substream_t * substream, + int cmd) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + unsigned int count; + + spin_lock_irqsave(&chip->reg_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_sbdsp_command(chip, chip->capture_format); + if (chip->capture_format == SB_DSP_INPUT) { + count = chip->c_period_size - 1; + snd_sbdsp_command(chip, count & 0xff); + snd_sbdsp_command(chip, count >> 8); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (chip->capture_format == SB_DSP_HI_INPUT_AUTO) { + snd_pcm_runtime_t *runtime = substream->runtime; + snd_sbdsp_reset(chip); + if (runtime->channels > 1) { + /* restore input filter status */ + spin_lock(&chip->mixer_lock); + snd_sbmixer_write(chip, SB_DSP_CAPTURE_FILT, chip->force_mode16); + spin_unlock(&chip->mixer_lock); + /* set hardware to mono mode */ + snd_sbdsp_command(chip, SB_DSP_MONO_8BIT); + } + } else { + snd_sbdsp_command(chip, SB_DSP_DMA8_OFF); + } + snd_sbdsp_command(chip, SB_DSP_SPEAKER_OFF); + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + chip->mode = (cmd == SNDRV_PCM_TRIGGER_START) ? SB_MODE_CAPTURE_8 : SB_MODE_HALT; + return 0; +} + +irqreturn_t snd_sb8dsp_interrupt(sb_t *chip) +{ + snd_pcm_substream_t *substream; + snd_pcm_runtime_t *runtime; + +#if 0 + snd_printk("sb8: interrupt\n"); +#endif + snd_sb_ack_8bit(chip); + switch (chip->mode) { + case SB_MODE_PLAYBACK_8: /* ok.. playback is active */ + substream = chip->playback_substream; + runtime = substream->runtime; + if (chip->playback_format == SB_DSP_OUTPUT) + snd_sb8_playback_trigger(substream, SNDRV_PCM_TRIGGER_START); + snd_pcm_period_elapsed(substream); + break; + case SB_MODE_CAPTURE_8: + substream = chip->capture_substream; + runtime = substream->runtime; + if (chip->capture_format == SB_DSP_INPUT) + snd_sb8_capture_trigger(substream, SNDRV_PCM_TRIGGER_START); + snd_pcm_period_elapsed(substream); + break; + } + return IRQ_HANDLED; +} + +static snd_pcm_uframes_t snd_sb8_playback_pointer(snd_pcm_substream_t * substream) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->mode != SB_MODE_PLAYBACK_8) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->p_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +static snd_pcm_uframes_t snd_sb8_capture_pointer(snd_pcm_substream_t * substream) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + size_t ptr; + + if (chip->mode != SB_MODE_CAPTURE_8) + return 0; + ptr = snd_dma_pointer(chip->dma8, chip->c_dma_size); + return bytes_to_frames(substream->runtime, ptr); +} + +/* + + */ + +static snd_pcm_hardware_t snd_sb8_playback = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025 | SNDRV_PCM_RATE_22050), + .rate_min = 4000, + .rate_max = 23000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +static snd_pcm_hardware_t snd_sb8_capture = +{ + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_U8, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_11025), + .rate_min = 4000, + .rate_max = 13000, + .channels_min = 1, + .channels_max = 1, + .buffer_bytes_max = 65536, + .period_bytes_min = 64, + .period_bytes_max = 65536, + .periods_min = 1, + .periods_max = 1024, + .fifo_size = 0, +}; + +/* + * + */ + +static int snd_sb8_open(snd_pcm_substream_t *substream) +{ + sb_t *chip = snd_pcm_substream_chip(substream); + snd_pcm_runtime_t *runtime = substream->runtime; + unsigned long flags; + + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_PCM; + spin_unlock_irqrestore(&chip->open_lock, flags); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + chip->playback_substream = substream; + runtime->hw = snd_sb8_playback; + } else { + chip->capture_substream = substream; + runtime->hw = snd_sb8_capture; + } + switch (chip->hardware) { + case SB_HW_PRO: + runtime->hw.rate_max = 44100; + runtime->hw.channels_max = 2; + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + snd_sb8_hw_constraint_rate_channels, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + snd_sb8_hw_constraint_channels_rate, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + break; + case SB_HW_201: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.rate_max = 44100; + } else { + runtime->hw.rate_max = 15000; + } + default: + break; + } + snd_pcm_hw_constraint_ratnums(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + &hw_constraints_clock); + return 0; +} + +static int snd_sb8_close(snd_pcm_substream_t *substream) +{ + unsigned long flags; + sb_t *chip = snd_pcm_substream_chip(substream); + + chip->playback_substream = NULL; + chip->capture_substream = NULL; + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~SB_OPEN_PCM; + spin_unlock_irqrestore(&chip->open_lock, flags); + return 0; +} + +/* + * Initialization part + */ + +static snd_pcm_ops_t snd_sb8_playback_ops = { + .open = snd_sb8_open, + .close = snd_sb8_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb8_hw_params, + .hw_free = snd_sb8_hw_free, + .prepare = snd_sb8_playback_prepare, + .trigger = snd_sb8_playback_trigger, + .pointer = snd_sb8_playback_pointer, +}; + +static snd_pcm_ops_t snd_sb8_capture_ops = { + .open = snd_sb8_open, + .close = snd_sb8_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_sb8_hw_params, + .hw_free = snd_sb8_hw_free, + .prepare = snd_sb8_capture_prepare, + .trigger = snd_sb8_capture_trigger, + .pointer = snd_sb8_capture_pointer, +}; + +static void snd_sb8dsp_pcm_free(snd_pcm_t *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int snd_sb8dsp_pcm(sb_t *chip, int device, snd_pcm_t ** rpcm) +{ + snd_card_t *card = chip->card; + snd_pcm_t *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + if ((err = snd_pcm_new(card, "SB8 DSP", device, 1, 1, &pcm)) < 0) + return err; + sprintf(pcm->name, "DSP v%i.%i", chip->version >> 8, chip->version & 0xff); + pcm->info_flags = SNDRV_PCM_INFO_HALF_DUPLEX; + pcm->private_data = chip; + pcm->private_free = snd_sb8dsp_pcm_free; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_sb8_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_sb8_capture_ops); + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_isa_data(), + 64*1024, 64*1024); + + if (rpcm) + *rpcm = pcm; + return 0; +} + +EXPORT_SYMBOL(snd_sb8dsp_pcm); +EXPORT_SYMBOL(snd_sb8dsp_interrupt); + /* sb8_midi.c */ +EXPORT_SYMBOL(snd_sb8dsp_midi_interrupt); +EXPORT_SYMBOL(snd_sb8dsp_midi); + +/* + * INIT part + */ + +static int __init alsa_sb8_init(void) +{ + return 0; +} + +static void __exit alsa_sb8_exit(void) +{ +} + +module_init(alsa_sb8_init) +module_exit(alsa_sb8_exit) diff --git a/sound/isa/sb/sb8_midi.c b/sound/isa/sb/sb8_midi.c new file mode 100644 index 00000000000..d2c633a40e7 --- /dev/null +++ b/sound/isa/sb/sb8_midi.c @@ -0,0 +1,293 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for control of SoundBlaster cards - MIDI interface + * + * 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 + * + * -- + * + * Sun May 9 22:54:38 BST 1999 George David Morrison <gdm@gedamo.demon.co.uk> + * Fixed typo in snd_sb8dsp_midi_new_device which prevented midi from + * working. + * + * Sun May 11 12:34:56 UTC 2003 Clemens Ladisch <clemens@ladisch.de> + * Added full duplex UART mode for DSP version 2.0 and later. + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/sb.h> + +/* + + */ + +irqreturn_t snd_sb8dsp_midi_interrupt(sb_t * chip) +{ + snd_rawmidi_t *rmidi; + int max = 64; + char byte; + + if (chip == NULL || (rmidi = chip->rmidi) == NULL) { + inb(SBP(chip, DATA_AVAIL)); /* ack interrupt */ + return IRQ_NONE; + } + spin_lock(&chip->midi_input_lock); + while (max-- > 0) { + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + byte = inb(SBP(chip, READ)); + if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { + snd_rawmidi_receive(chip->midi_substream_input, &byte, 1); + } + } + } + spin_unlock(&chip->midi_input_lock); + return IRQ_HANDLED; +} + +/* + + */ + +static int snd_sb8dsp_midi_input_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + sb_t *chip; + unsigned int valid_open_flags; + + chip = substream->rmidi->private_data; + valid_open_flags = chip->hardware >= SB_HW_20 + ? SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER : 0; + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open & ~valid_open_flags) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_MIDI_INPUT; + chip->midi_substream_input = substream; + if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + if (chip->hardware >= SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_output_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + sb_t *chip; + unsigned int valid_open_flags; + + chip = substream->rmidi->private_data; + valid_open_flags = chip->hardware >= SB_HW_20 + ? SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER : 0; + spin_lock_irqsave(&chip->open_lock, flags); + if (chip->open & ~valid_open_flags) { + spin_unlock_irqrestore(&chip->open_lock, flags); + return -EAGAIN; + } + chip->open |= SB_OPEN_MIDI_OUTPUT; + chip->midi_substream_output = substream; + if (!(chip->open & SB_OPEN_MIDI_INPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + if (chip->hardware >= SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_UART_IRQ); + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_input_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + sb_t *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~(SB_OPEN_MIDI_INPUT | SB_OPEN_MIDI_INPUT_TRIGGER); + chip->midi_substream_input = NULL; + if (!(chip->open & SB_OPEN_MIDI_OUTPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static int snd_sb8dsp_midi_output_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + sb_t *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + chip->open &= ~(SB_OPEN_MIDI_OUTPUT | SB_OPEN_MIDI_OUTPUT_TRIGGER); + chip->midi_substream_output = NULL; + if (!(chip->open & SB_OPEN_MIDI_INPUT)) { + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sbdsp_reset(chip); /* reset DSP */ + } else { + spin_unlock_irqrestore(&chip->open_lock, flags); + } + return 0; +} + +static void snd_sb8dsp_midi_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + unsigned long flags; + sb_t *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + if (up) { + if (!(chip->open & SB_OPEN_MIDI_INPUT_TRIGGER)) { + if (chip->hardware < SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); + chip->open |= SB_OPEN_MIDI_INPUT_TRIGGER; + } + } else { + if (chip->open & SB_OPEN_MIDI_INPUT_TRIGGER) { + if (chip->hardware < SB_HW_20) + snd_sbdsp_command(chip, SB_DSP_MIDI_INPUT_IRQ); + chip->open &= ~SB_OPEN_MIDI_INPUT_TRIGGER; + } + } + spin_unlock_irqrestore(&chip->open_lock, flags); +} + +static void snd_sb8dsp_midi_output_write(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + sb_t *chip; + char byte; + int max = 32; + + /* how big is Tx FIFO? */ + chip = substream->rmidi->private_data; + while (max-- > 0) { + spin_lock_irqsave(&chip->open_lock, flags); + if (snd_rawmidi_transmit_peek(substream, &byte, 1) != 1) { + chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; + del_timer(&chip->midi_timer); + spin_unlock_irqrestore(&chip->open_lock, flags); + break; + } + if (chip->hardware >= SB_HW_20) { + int timeout = 8; + while ((inb(SBP(chip, STATUS)) & 0x80) != 0 && --timeout > 0) + ; + if (timeout == 0) { + /* Tx FIFO full - try again later */ + spin_unlock_irqrestore(&chip->open_lock, flags); + break; + } + outb(byte, SBP(chip, WRITE)); + } else { + snd_sbdsp_command(chip, SB_DSP_MIDI_OUTPUT); + snd_sbdsp_command(chip, byte); + } + snd_rawmidi_transmit_ack(substream, 1); + spin_unlock_irqrestore(&chip->open_lock, flags); + } +} + +static void snd_sb8dsp_midi_output_timer(unsigned long data) +{ + snd_rawmidi_substream_t * substream = (snd_rawmidi_substream_t *) data; + sb_t * chip = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&chip->open_lock, flags); + chip->midi_timer.expires = 1 + jiffies; + add_timer(&chip->midi_timer); + spin_unlock_irqrestore(&chip->open_lock, flags); + snd_sb8dsp_midi_output_write(substream); +} + +static void snd_sb8dsp_midi_output_trigger(snd_rawmidi_substream_t * substream, int up) +{ + unsigned long flags; + sb_t *chip; + + chip = substream->rmidi->private_data; + spin_lock_irqsave(&chip->open_lock, flags); + if (up) { + if (!(chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER)) { + init_timer(&chip->midi_timer); + chip->midi_timer.function = snd_sb8dsp_midi_output_timer; + chip->midi_timer.data = (unsigned long) substream; + chip->midi_timer.expires = 1 + jiffies; + add_timer(&chip->midi_timer); + chip->open |= SB_OPEN_MIDI_OUTPUT_TRIGGER; + } + } else { + if (chip->open & SB_OPEN_MIDI_OUTPUT_TRIGGER) { + chip->open &= ~SB_OPEN_MIDI_OUTPUT_TRIGGER; + } + } + spin_unlock_irqrestore(&chip->open_lock, flags); + + if (up) + snd_sb8dsp_midi_output_write(substream); +} + +/* + + */ + +static snd_rawmidi_ops_t snd_sb8dsp_midi_output = +{ + .open = snd_sb8dsp_midi_output_open, + .close = snd_sb8dsp_midi_output_close, + .trigger = snd_sb8dsp_midi_output_trigger, +}; + +static snd_rawmidi_ops_t snd_sb8dsp_midi_input = +{ + .open = snd_sb8dsp_midi_input_open, + .close = snd_sb8dsp_midi_input_close, + .trigger = snd_sb8dsp_midi_input_trigger, +}; + +int snd_sb8dsp_midi(sb_t *chip, int device, snd_rawmidi_t ** rrawmidi) +{ + snd_rawmidi_t *rmidi; + int err; + + if (rrawmidi) + *rrawmidi = NULL; + if ((err = snd_rawmidi_new(chip->card, "SB8 MIDI", device, 1, 1, &rmidi)) < 0) + return err; + strcpy(rmidi->name, "SB8 MIDI"); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_sb8dsp_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_sb8dsp_midi_input); + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | SNDRV_RAWMIDI_INFO_INPUT; + if (chip->hardware >= SB_HW_20) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + rmidi->private_data = chip; + chip->rmidi = rmidi; + if (rrawmidi) + *rrawmidi = rmidi; + return 0; +} diff --git a/sound/isa/sb/sb_common.c b/sound/isa/sb/sb_common.c new file mode 100644 index 00000000000..5b6bde213ea --- /dev/null +++ b/sound/isa/sb/sb_common.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Uros Bizjak <uros@kss-loka.si> + * + * Lowlevel routines for control of Sound Blaster cards + * + * 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/init.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/initval.h> + +#include <asm/io.h> +#include <asm/dma.h> + +MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>"); +MODULE_DESCRIPTION("ALSA lowlevel driver for Sound Blaster cards"); +MODULE_LICENSE("GPL"); + +#define BUSY_LOOPS 100000 + +#undef IO_DEBUG + +int snd_sbdsp_command(sb_t *chip, unsigned char val) +{ + int i; +#ifdef IO_DEBUG + snd_printk("command 0x%x\n", val); +#endif + for (i = BUSY_LOOPS; i; i--) + if ((inb(SBP(chip, STATUS)) & 0x80) == 0) { + outb(val, SBP(chip, COMMAND)); + return 1; + } + snd_printd("%s [0x%lx]: timeout (0x%x)\n", __FUNCTION__, chip->port, val); + return 0; +} + +int snd_sbdsp_get_byte(sb_t *chip) +{ + int val; + int i; + for (i = BUSY_LOOPS; i; i--) { + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + val = inb(SBP(chip, READ)); +#ifdef IO_DEBUG + snd_printk("get_byte 0x%x\n", val); +#endif + return val; + } + } + snd_printd("%s [0x%lx]: timeout\n", __FUNCTION__, chip->port); + return -ENODEV; +} + +int snd_sbdsp_reset(sb_t *chip) +{ + int i; + + outb(1, SBP(chip, RESET)); + udelay(10); + outb(0, SBP(chip, RESET)); + udelay(30); + for (i = BUSY_LOOPS; i; i--) + if (inb(SBP(chip, DATA_AVAIL)) & 0x80) { + if (inb(SBP(chip, READ)) == 0xaa) + return 0; + else + break; + } + snd_printdd("%s [0x%lx] failed...\n", __FUNCTION__, chip->port); + return -ENODEV; +} + +static int snd_sbdsp_version(sb_t * chip) +{ + unsigned int result = -ENODEV; + + snd_sbdsp_command(chip, SB_DSP_GET_VERSION); + result = (short) snd_sbdsp_get_byte(chip) << 8; + result |= (short) snd_sbdsp_get_byte(chip); + return result; +} + +static int snd_sbdsp_probe(sb_t * chip) +{ + int version; + int major, minor; + char *str; + unsigned long flags; + + /* + * initialization sequence + */ + + spin_lock_irqsave(&chip->reg_lock, flags); + if (snd_sbdsp_reset(chip) < 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + version = snd_sbdsp_version(chip); + if (version < 0) { + spin_unlock_irqrestore(&chip->reg_lock, flags); + return -ENODEV; + } + spin_unlock_irqrestore(&chip->reg_lock, flags); + major = version >> 8; + minor = version & 0xff; + snd_printdd("SB [0x%lx]: DSP chip found, version = %i.%i\n", + chip->port, major, minor); + + switch (chip->hardware) { + case SB_HW_AUTO: + switch (major) { + case 1: + chip->hardware = SB_HW_10; + str = "1.0"; + break; + case 2: + if (minor) { + chip->hardware = SB_HW_201; + str = "2.01+"; + } else { + chip->hardware = SB_HW_20; + str = "2.0"; + } + break; + case 3: + chip->hardware = SB_HW_PRO; + str = "Pro"; + break; + case 4: + chip->hardware = SB_HW_16; + str = "16"; + break; + default: + snd_printk("SB [0x%lx]: unknown DSP chip version %i.%i\n", + chip->port, major, minor); + return -ENODEV; + } + break; + case SB_HW_ALS100: + str = "16 (ALS-100)"; + break; + case SB_HW_ALS4000: + str = "16 (ALS-4000)"; + break; + case SB_HW_DT019X: + str = "(DT019X/ALS007)"; + break; + default: + return -ENODEV; + } + sprintf(chip->name, "Sound Blaster %s", str); + chip->version = (major << 8) | minor; + return 0; +} + +static int snd_sbdsp_free(sb_t *chip) +{ + if (chip->res_port) { + release_resource(chip->res_port); + kfree_nocheck(chip->res_port); + } + if (chip->irq >= 0) + free_irq(chip->irq, (void *) chip); +#ifdef CONFIG_ISA + if (chip->dma8 >= 0) { + disable_dma(chip->dma8); + free_dma(chip->dma8); + } + if (chip->dma16 >= 0 && chip->dma16 != chip->dma8) { + disable_dma(chip->dma16); + free_dma(chip->dma16); + } +#endif + kfree(chip); + return 0; +} + +static int snd_sbdsp_dev_free(snd_device_t *device) +{ + sb_t *chip = device->device_data; + return snd_sbdsp_free(chip); +} + +int snd_sbdsp_create(snd_card_t *card, + unsigned long port, + int irq, + irqreturn_t (*irq_handler)(int, void *, struct pt_regs *), + int dma8, + int dma16, + unsigned short hardware, + sb_t **r_chip) +{ + sb_t *chip; + int err; + static snd_device_ops_t ops = { + .dev_free = snd_sbdsp_dev_free, + }; + + snd_assert(r_chip != NULL, return -EINVAL); + *r_chip = NULL; + chip = kcalloc(1, sizeof(*chip), GFP_KERNEL); + if (chip == NULL) + return -ENOMEM; + spin_lock_init(&chip->reg_lock); + spin_lock_init(&chip->open_lock); + spin_lock_init(&chip->midi_input_lock); + spin_lock_init(&chip->mixer_lock); + chip->irq = -1; + chip->dma8 = -1; + chip->dma16 = -1; + chip->port = port; + + if (request_irq(irq, irq_handler, hardware == SB_HW_ALS4000 ? + SA_INTERRUPT | SA_SHIRQ : SA_INTERRUPT, + "SoundBlaster", (void *) chip)) { + snd_printk(KERN_ERR "sb: can't grab irq %d\n", irq); + snd_sbdsp_free(chip); + return -EBUSY; + } + chip->irq = irq; + + if (hardware == SB_HW_ALS4000) + goto __skip_allocation; + + if ((chip->res_port = request_region(port, 16, "SoundBlaster")) == NULL) { + snd_printk(KERN_ERR "sb: can't grab port 0x%lx\n", port); + snd_sbdsp_free(chip); + return -EBUSY; + } + +#ifdef CONFIG_ISA + if (dma8 >= 0 && request_dma(dma8, "SoundBlaster - 8bit")) { + snd_printk(KERN_ERR "sb: can't grab DMA8 %d\n", dma8); + snd_sbdsp_free(chip); + return -EBUSY; + } + chip->dma8 = dma8; + if (dma16 >= 0) { + if (hardware != SB_HW_ALS100 && (dma16 < 5 || dma16 > 7)) { + /* no duplex */ + dma16 = -1; + } else if (request_dma(dma16, "SoundBlaster - 16bit")) { + snd_printk(KERN_ERR "sb: can't grab DMA16 %d\n", dma16); + snd_sbdsp_free(chip); + return -EBUSY; + } + } + chip->dma16 = dma16; +#endif + + __skip_allocation: + chip->card = card; + chip->hardware = hardware; + if ((err = snd_sbdsp_probe(chip)) < 0) { + snd_sbdsp_free(chip); + return err; + } + if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) { + snd_sbdsp_free(chip); + return err; + } + *r_chip = chip; + return 0; +} + +EXPORT_SYMBOL(snd_sbdsp_command); +EXPORT_SYMBOL(snd_sbdsp_get_byte); +EXPORT_SYMBOL(snd_sbdsp_reset); +EXPORT_SYMBOL(snd_sbdsp_create); +/* sb_mixer.c */ +EXPORT_SYMBOL(snd_sbmixer_write); +EXPORT_SYMBOL(snd_sbmixer_read); +EXPORT_SYMBOL(snd_sbmixer_new); +EXPORT_SYMBOL(snd_sbmixer_add_ctl); + +/* + * INIT part + */ + +static int __init alsa_sb_common_init(void) +{ + return 0; +} + +static void __exit alsa_sb_common_exit(void) +{ +} + +module_init(alsa_sb_common_init) +module_exit(alsa_sb_common_exit) diff --git a/sound/isa/sb/sb_mixer.c b/sound/isa/sb/sb_mixer.c new file mode 100644 index 00000000000..cc5a2c6dec1 --- /dev/null +++ b/sound/isa/sb/sb_mixer.c @@ -0,0 +1,844 @@ +/* + * Copyright (c) by Jaroslav Kysela <perex@suse.cz> + * Routines for Sound Blaster mixer control + * + * + * 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 <asm/io.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/control.h> + +#undef IO_DEBUG + +void snd_sbmixer_write(sb_t *chip, unsigned char reg, unsigned char data) +{ + outb(reg, SBP(chip, MIXER_ADDR)); + udelay(10); + outb(data, SBP(chip, MIXER_DATA)); + udelay(10); +#ifdef IO_DEBUG + snd_printk("mixer_write 0x%x 0x%x\n", reg, data); +#endif +} + +unsigned char snd_sbmixer_read(sb_t *chip, unsigned char reg) +{ + unsigned char result; + + outb(reg, SBP(chip, MIXER_ADDR)); + udelay(10); + result = inb(SBP(chip, MIXER_DATA)); + udelay(10); +#ifdef IO_DEBUG + snd_printk("mixer_read 0x%x 0x%x\n", reg, result); +#endif + return result; +} + +/* + * Single channel mixer element + */ + +static int snd_sbmixer_info_single(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 = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = mask; + return 0; +} + +static int snd_sbmixer_get_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0xff; + int mask = (kcontrol->private_value >> 24) & 0xff; + unsigned char val; + + spin_lock_irqsave(&sb->mixer_lock, flags); + val = (snd_sbmixer_read(sb, reg) >> shift) & mask; + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = val; + return 0; +} + +static int snd_sbmixer_put_single(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg = kcontrol->private_value & 0xff; + int shift = (kcontrol->private_value >> 16) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned char val, oval; + + val = (ucontrol->value.integer.value[0] & mask) << shift; + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, reg); + val = (oval & ~(mask << shift)) | val; + change = val != oval; + if (change) + snd_sbmixer_write(sb, reg, val); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * Double channel mixer element + */ + +static int snd_sbmixer_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_sbmixer_get_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x07; + int right_shift = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + unsigned char left, right; + + spin_lock_irqsave(&sb->mixer_lock, flags); + left = (snd_sbmixer_read(sb, left_reg) >> left_shift) & mask; + right = (snd_sbmixer_read(sb, right_reg) >> right_shift) & mask; + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = left; + ucontrol->value.integer.value[1] = right; + return 0; +} + +static int snd_sbmixer_put_double(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int left_reg = kcontrol->private_value & 0xff; + int right_reg = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x07; + int right_shift = (kcontrol->private_value >> 19) & 0x07; + int mask = (kcontrol->private_value >> 24) & 0xff; + int change; + unsigned char left, right, oleft, oright; + + left = (ucontrol->value.integer.value[0] & mask) << left_shift; + right = (ucontrol->value.integer.value[1] & mask) << right_shift; + spin_lock_irqsave(&sb->mixer_lock, flags); + if (left_reg == right_reg) { + oleft = snd_sbmixer_read(sb, left_reg); + left = (oleft & ~((mask << left_shift) | (mask << right_shift))) | left | right; + change = left != oleft; + if (change) + snd_sbmixer_write(sb, left_reg, left); + } else { + oleft = snd_sbmixer_read(sb, left_reg); + oright = snd_sbmixer_read(sb, right_reg); + left = (oleft & ~(mask << left_shift)) | left; + right = (oright & ~(mask << right_shift)) | right; + change = left != oleft || right != oright; + if (change) { + snd_sbmixer_write(sb, left_reg, left); + snd_sbmixer_write(sb, right_reg, right); + } + } + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * DT-019x / ALS-007 capture/input switch + */ + +static int snd_dt019x_input_sw_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[5] = { + "CD", "Mic", "Line", "Synth", "Master" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 5; + if (uinfo->value.enumerated.item > 4) + uinfo->value.enumerated.item = 4; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int snd_dt019x_input_sw_get(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + switch (oval & 0x07) { + case SB_DT019X_CAP_CD: + ucontrol->value.enumerated.item[0] = 0; + break; + case SB_DT019X_CAP_MIC: + ucontrol->value.enumerated.item[0] = 1; + break; + case SB_DT019X_CAP_LINE: + ucontrol->value.enumerated.item[0] = 2; + break; + case SB_DT019X_CAP_MAIN: + ucontrol->value.enumerated.item[0] = 4; + break; + /* To record the synth on these cards you must record the main. */ + /* Thus SB_DT019X_CAP_SYNTH == SB_DT019X_CAP_MAIN and would cause */ + /* duplicate case labels if left uncommented. */ + /* case SB_DT019X_CAP_SYNTH: + * ucontrol->value.enumerated.item[0] = 3; + * break; + */ + default: + ucontrol->value.enumerated.item[0] = 4; + break; + } + return 0; +} + +static int snd_dt019x_input_sw_put(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval, oval; + + if (ucontrol->value.enumerated.item[0] > 4) + return -EINVAL; + switch (ucontrol->value.enumerated.item[0]) { + case 0: + nval = SB_DT019X_CAP_CD; + break; + case 1: + nval = SB_DT019X_CAP_MIC; + break; + case 2: + nval = SB_DT019X_CAP_LINE; + break; + case 3: + nval = SB_DT019X_CAP_SYNTH; + break; + case 4: + nval = SB_DT019X_CAP_MAIN; + break; + default: + nval = SB_DT019X_CAP_MAIN; + } + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DT019X_CAPTURE_SW); + change = nval != oval; + if (change) + snd_sbmixer_write(sb, SB_DT019X_CAPTURE_SW, nval); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * SBPRO input multiplexer + */ + +static int snd_sb8mixer_info_mux(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t * uinfo) +{ + static char *texts[3] = { + "Mic", "CD", "Line" + }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 3; + if (uinfo->value.enumerated.item > 2) + uinfo->value.enumerated.item = 2; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + + +static int snd_sb8mixer_get_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + unsigned char oval; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + switch ((oval >> 0x01) & 0x03) { + case SB_DSP_MIXS_CD: + ucontrol->value.enumerated.item[0] = 1; + break; + case SB_DSP_MIXS_LINE: + ucontrol->value.enumerated.item[0] = 2; + break; + default: + ucontrol->value.enumerated.item[0] = 0; + break; + } + return 0; +} + +static int snd_sb8mixer_put_mux(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int change; + unsigned char nval, oval; + + if (ucontrol->value.enumerated.item[0] > 2) + return -EINVAL; + switch (ucontrol->value.enumerated.item[0]) { + case 1: + nval = SB_DSP_MIXS_CD; + break; + case 2: + nval = SB_DSP_MIXS_LINE; + break; + default: + nval = SB_DSP_MIXS_MIC; + } + nval <<= 1; + spin_lock_irqsave(&sb->mixer_lock, flags); + oval = snd_sbmixer_read(sb, SB_DSP_CAPTURE_SOURCE); + nval |= oval & ~0x06; + change = nval != oval; + if (change) + snd_sbmixer_write(sb, SB_DSP_CAPTURE_SOURCE, nval); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + +/* + * SB16 input switch + */ + +static int snd_sb16mixer_info_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 4; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int snd_sb16mixer_get_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg1 = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x0f; + int right_shift = (kcontrol->private_value >> 24) & 0x0f; + unsigned char val1, val2; + + spin_lock_irqsave(&sb->mixer_lock, flags); + val1 = snd_sbmixer_read(sb, reg1); + val2 = snd_sbmixer_read(sb, reg2); + spin_unlock_irqrestore(&sb->mixer_lock, flags); + ucontrol->value.integer.value[0] = (val1 >> left_shift) & 0x01; + ucontrol->value.integer.value[1] = (val2 >> left_shift) & 0x01; + ucontrol->value.integer.value[2] = (val1 >> right_shift) & 0x01; + ucontrol->value.integer.value[3] = (val2 >> right_shift) & 0x01; + return 0; +} + +static int snd_sb16mixer_put_input_sw(snd_kcontrol_t * kcontrol, snd_ctl_elem_value_t * ucontrol) +{ + sb_t *sb = snd_kcontrol_chip(kcontrol); + unsigned long flags; + int reg1 = kcontrol->private_value & 0xff; + int reg2 = (kcontrol->private_value >> 8) & 0xff; + int left_shift = (kcontrol->private_value >> 16) & 0x0f; + int right_shift = (kcontrol->private_value >> 24) & 0x0f; + int change; + unsigned char val1, val2, oval1, oval2; + + spin_lock_irqsave(&sb->mixer_lock, flags); + oval1 = snd_sbmixer_read(sb, reg1); + oval2 = snd_sbmixer_read(sb, reg2); + val1 = oval1 & ~((1 << left_shift) | (1 << right_shift)); + val2 = oval2 & ~((1 << left_shift) | (1 << right_shift)); + val1 |= (ucontrol->value.integer.value[0] & 1) << left_shift; + val2 |= (ucontrol->value.integer.value[1] & 1) << left_shift; + val1 |= (ucontrol->value.integer.value[2] & 1) << right_shift; + val2 |= (ucontrol->value.integer.value[3] & 1) << right_shift; + change = val1 != oval1 || val2 != oval2; + if (change) { + snd_sbmixer_write(sb, reg1, val1); + snd_sbmixer_write(sb, reg2, val2); + } + spin_unlock_irqrestore(&sb->mixer_lock, flags); + return change; +} + + +/* + */ +/* + */ +int snd_sbmixer_add_ctl(sb_t *chip, const char *name, int index, int type, unsigned long value) +{ + static snd_kcontrol_new_t newctls[] = { + [SB_MIX_SINGLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sbmixer_info_single, + .get = snd_sbmixer_get_single, + .put = snd_sbmixer_put_single, + }, + [SB_MIX_DOUBLE] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sbmixer_info_double, + .get = snd_sbmixer_get_double, + .put = snd_sbmixer_put_double, + }, + [SB_MIX_INPUT_SW] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sb16mixer_info_input_sw, + .get = snd_sb16mixer_get_input_sw, + .put = snd_sb16mixer_put_input_sw, + }, + [SB_MIX_CAPTURE_PRO] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_sb8mixer_info_mux, + .get = snd_sb8mixer_get_mux, + .put = snd_sb8mixer_put_mux, + }, + [SB_MIX_CAPTURE_DT019X] = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = snd_dt019x_input_sw_info, + .get = snd_dt019x_input_sw_get, + .put = snd_dt019x_input_sw_put, + }, + }; + snd_kcontrol_t *ctl; + int err; + + ctl = snd_ctl_new1(&newctls[type], chip); + if (! ctl) + return -ENOMEM; + strlcpy(ctl->id.name, name, sizeof(ctl->id.name)); + ctl->id.index = index; + ctl->private_value = value; + if ((err = snd_ctl_add(chip->card, ctl)) < 0) { + snd_ctl_free_one(ctl); + return err; + } + return 0; +} + +/* + * SB 2.0 specific mixer elements + */ + +static struct sbmix_elem snd_sb20_ctl_master_play_vol = + SB_SINGLE("Master Playback Volume", SB_DSP20_MASTER_DEV, 1, 7); +static struct sbmix_elem snd_sb20_ctl_pcm_play_vol = + SB_SINGLE("PCM Playback Volume", SB_DSP20_PCM_DEV, 1, 3); +static struct sbmix_elem snd_sb20_ctl_synth_play_vol = + SB_SINGLE("Synth Playback Volume", SB_DSP20_FM_DEV, 1, 7); +static struct sbmix_elem snd_sb20_ctl_cd_play_vol = + SB_SINGLE("CD Playback Volume", SB_DSP20_CD_DEV, 1, 7); + +static struct sbmix_elem *snd_sb20_controls[] = { + &snd_sb20_ctl_master_play_vol, + &snd_sb20_ctl_pcm_play_vol, + &snd_sb20_ctl_synth_play_vol, + &snd_sb20_ctl_cd_play_vol +}; + +static unsigned char snd_sb20_init_values[][2] = { + { SB_DSP20_MASTER_DEV, 0 }, + { SB_DSP20_FM_DEV, 0 }, +}; + +/* + * SB Pro specific mixer elements + */ +static struct sbmix_elem snd_sbpro_ctl_master_play_vol = + SB_DOUBLE("Master Playback Volume", SB_DSP_MASTER_DEV, SB_DSP_MASTER_DEV, 5, 1, 7); +static struct sbmix_elem snd_sbpro_ctl_pcm_play_vol = + SB_DOUBLE("PCM Playback Volume", SB_DSP_PCM_DEV, SB_DSP_PCM_DEV, 5, 1, 7); +static struct sbmix_elem snd_sbpro_ctl_pcm_play_filter = + SB_SINGLE("PCM Playback Filter", SB_DSP_PLAYBACK_FILT, 5, 1); +static struct sbmix_elem snd_sbpro_ctl_synth_play_vol = + SB_DOUBLE("Synth Playback Volume", SB_DSP_FM_DEV, SB_DSP_FM_DEV, 5, 1, 7); +static struct sbmix_elem snd_sbpro_ctl_cd_play_vol = + SB_DOUBLE("CD Playback Volume", SB_DSP_CD_DEV, SB_DSP_CD_DEV, 5, 1, 7); +static struct sbmix_elem snd_sbpro_ctl_line_play_vol = + SB_DOUBLE("Line Playback Volume", SB_DSP_LINE_DEV, SB_DSP_LINE_DEV, 5, 1, 7); +static struct sbmix_elem snd_sbpro_ctl_mic_play_vol = + SB_SINGLE("Mic Playback Volume", SB_DSP_MIC_DEV, 1, 3); +static struct sbmix_elem snd_sbpro_ctl_capture_source = + { + .name = "Capture Source", + .type = SB_MIX_CAPTURE_PRO + }; +static struct sbmix_elem snd_sbpro_ctl_capture_filter = + SB_SINGLE("Capture Filter", SB_DSP_CAPTURE_FILT, 5, 1); +static struct sbmix_elem snd_sbpro_ctl_capture_low_filter = + SB_SINGLE("Capture Low-Pass Filter", SB_DSP_CAPTURE_FILT, 3, 1); + +static struct sbmix_elem *snd_sbpro_controls[] = { + &snd_sbpro_ctl_master_play_vol, + &snd_sbpro_ctl_pcm_play_vol, + &snd_sbpro_ctl_pcm_play_filter, + &snd_sbpro_ctl_synth_play_vol, + &snd_sbpro_ctl_cd_play_vol, + &snd_sbpro_ctl_line_play_vol, + &snd_sbpro_ctl_mic_play_vol, + &snd_sbpro_ctl_capture_source, + &snd_sbpro_ctl_capture_filter, + &snd_sbpro_ctl_capture_low_filter +}; + +static unsigned char snd_sbpro_init_values[][2] = { + { SB_DSP_MASTER_DEV, 0 }, + { SB_DSP_PCM_DEV, 0 }, + { SB_DSP_FM_DEV, 0 }, +}; + +/* + * SB16 specific mixer elements + */ +static struct sbmix_elem snd_sb16_ctl_master_play_vol = + SB_DOUBLE("Master Playback Volume", SB_DSP4_MASTER_DEV, (SB_DSP4_MASTER_DEV + 1), 3, 3, 31); +static struct sbmix_elem snd_sb16_ctl_3d_enhance_switch = + SB_SINGLE("3D Enhancement Switch", SB_DSP4_3DSE, 0, 1); +static struct sbmix_elem snd_sb16_ctl_tone_bass = + SB_DOUBLE("Tone Control - Bass", SB_DSP4_BASS_DEV, (SB_DSP4_BASS_DEV + 1), 4, 4, 15); +static struct sbmix_elem snd_sb16_ctl_tone_treble = + SB_DOUBLE("Tone Control - Treble", SB_DSP4_TREBLE_DEV, (SB_DSP4_TREBLE_DEV + 1), 4, 4, 15); +static struct sbmix_elem snd_sb16_ctl_pcm_play_vol = + SB_DOUBLE("PCM Playback Volume", SB_DSP4_PCM_DEV, (SB_DSP4_PCM_DEV + 1), 3, 3, 31); +static struct sbmix_elem snd_sb16_ctl_synth_capture_route = + SB16_INPUT_SW("Synth Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 6, 5); +static struct sbmix_elem snd_sb16_ctl_synth_play_vol = + SB_DOUBLE("Synth Playback Volume", SB_DSP4_SYNTH_DEV, (SB_DSP4_SYNTH_DEV + 1), 3, 3, 31); +static struct sbmix_elem snd_sb16_ctl_cd_capture_route = + SB16_INPUT_SW("CD Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 2, 1); +static struct sbmix_elem snd_sb16_ctl_cd_play_switch = + SB_DOUBLE("CD Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 2, 1, 1); +static struct sbmix_elem snd_sb16_ctl_cd_play_vol = + SB_DOUBLE("CD Playback Volume", SB_DSP4_CD_DEV, (SB_DSP4_CD_DEV + 1), 3, 3, 31); +static struct sbmix_elem snd_sb16_ctl_line_capture_route = + SB16_INPUT_SW("Line Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 4, 3); +static struct sbmix_elem snd_sb16_ctl_line_play_switch = + SB_DOUBLE("Line Playback Switch", SB_DSP4_OUTPUT_SW, SB_DSP4_OUTPUT_SW, 4, 3, 1); +static struct sbmix_elem snd_sb16_ctl_line_play_vol = + SB_DOUBLE("Line Playback Volume", SB_DSP4_LINE_DEV, (SB_DSP4_LINE_DEV + 1), 3, 3, 31); +static struct sbmix_elem snd_sb16_ctl_mic_capture_route = + SB16_INPUT_SW("Mic Capture Route", SB_DSP4_INPUT_LEFT, SB_DSP4_INPUT_RIGHT, 0, 0); +static struct sbmix_elem snd_sb16_ctl_mic_play_switch = + SB_SINGLE("Mic Playback Switch", SB_DSP4_OUTPUT_SW, 0, 1); +static struct sbmix_elem snd_sb16_ctl_mic_play_vol = + SB_SINGLE("Mic Playback Volume", SB_DSP4_MIC_DEV, 3, 31); +static struct sbmix_elem snd_sb16_ctl_pc_speaker_vol = + SB_SINGLE("PC Speaker Volume", SB_DSP4_SPEAKER_DEV, 6, 3); +static struct sbmix_elem snd_sb16_ctl_capture_vol = + SB_DOUBLE("Capture Volume", SB_DSP4_IGAIN_DEV, (SB_DSP4_IGAIN_DEV + 1), 6, 6, 3); +static struct sbmix_elem snd_sb16_ctl_play_vol = + SB_DOUBLE("Playback Volume", SB_DSP4_OGAIN_DEV, (SB_DSP4_OGAIN_DEV + 1), 6, 6, 3); +static struct sbmix_elem snd_sb16_ctl_auto_mic_gain = + SB_SINGLE("Mic Auto Gain", SB_DSP4_MIC_AGC, 0, 1); + +static struct sbmix_elem *snd_sb16_controls[] = { + &snd_sb16_ctl_master_play_vol, + &snd_sb16_ctl_3d_enhance_switch, + &snd_sb16_ctl_tone_bass, + &snd_sb16_ctl_tone_treble, + &snd_sb16_ctl_pcm_play_vol, + &snd_sb16_ctl_synth_capture_route, + &snd_sb16_ctl_synth_play_vol, + &snd_sb16_ctl_cd_capture_route, + &snd_sb16_ctl_cd_play_switch, + &snd_sb16_ctl_cd_play_vol, + &snd_sb16_ctl_line_capture_route, + &snd_sb16_ctl_line_play_switch, + &snd_sb16_ctl_line_play_vol, + &snd_sb16_ctl_mic_capture_route, + &snd_sb16_ctl_mic_play_switch, + &snd_sb16_ctl_mic_play_vol, + &snd_sb16_ctl_pc_speaker_vol, + &snd_sb16_ctl_capture_vol, + &snd_sb16_ctl_play_vol, + &snd_sb16_ctl_auto_mic_gain +}; + +static unsigned char snd_sb16_init_values[][2] = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, +}; + +/* + * DT019x specific mixer elements + */ +static struct sbmix_elem snd_dt019x_ctl_master_play_vol = + SB_DOUBLE("Master Playback Volume", SB_DT019X_MASTER_DEV, SB_DT019X_MASTER_DEV, 4,0, 15); +static struct sbmix_elem snd_dt019x_ctl_pcm_play_vol = + SB_DOUBLE("PCM Playback Volume", SB_DT019X_PCM_DEV, SB_DT019X_PCM_DEV, 4,0, 15); +static struct sbmix_elem snd_dt019x_ctl_synth_play_vol = + SB_DOUBLE("Synth Playback Volume", SB_DT019X_SYNTH_DEV, SB_DT019X_SYNTH_DEV, 4,0, 15); +static struct sbmix_elem snd_dt019x_ctl_cd_play_vol = + SB_DOUBLE("CD Playback Volume", SB_DT019X_CD_DEV, SB_DT019X_CD_DEV, 4,0, 15); +static struct sbmix_elem snd_dt019x_ctl_mic_play_vol = + SB_SINGLE("Mic Playback Volume", SB_DT019X_MIC_DEV, 4, 7); +static struct sbmix_elem snd_dt019x_ctl_pc_speaker_vol = + SB_SINGLE("PC Speaker Volume", SB_DT019X_SPKR_DEV, 0, 7); +static struct sbmix_elem snd_dt019x_ctl_line_play_vol = + SB_DOUBLE("Line Playback Volume", SB_DT019X_LINE_DEV, SB_DT019X_LINE_DEV, 4,0, 15); +static struct sbmix_elem snd_dt019x_ctl_pcm_play_switch = + SB_DOUBLE("PCM Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 2,1, 1); +static struct sbmix_elem snd_dt019x_ctl_synth_play_switch = + SB_DOUBLE("Synth Playback Switch", SB_DT019X_OUTPUT_SW2, SB_DT019X_OUTPUT_SW2, 4,3, 1); +static struct sbmix_elem snd_dt019x_ctl_capture_source = + { + .name = "Capture Source", + .type = SB_MIX_CAPTURE_DT019X + }; + +static struct sbmix_elem *snd_dt019x_controls[] = { + &snd_dt019x_ctl_master_play_vol, + &snd_dt019x_ctl_pcm_play_vol, + &snd_dt019x_ctl_synth_play_vol, + &snd_dt019x_ctl_cd_play_vol, + &snd_dt019x_ctl_mic_play_vol, + &snd_dt019x_ctl_pc_speaker_vol, + &snd_dt019x_ctl_line_play_vol, + &snd_sb16_ctl_mic_play_switch, + &snd_sb16_ctl_cd_play_switch, + &snd_sb16_ctl_line_play_switch, + &snd_dt019x_ctl_pcm_play_switch, + &snd_dt019x_ctl_synth_play_switch, + &snd_dt019x_ctl_capture_source +}; + +static unsigned char snd_dt019x_init_values[][2] = { + { SB_DT019X_MASTER_DEV, 0 }, + { SB_DT019X_PCM_DEV, 0 }, + { SB_DT019X_SYNTH_DEV, 0 }, + { SB_DT019X_CD_DEV, 0 }, + { SB_DT019X_MIC_DEV, 0 }, /* Includes PC-speaker in high nibble */ + { SB_DT019X_LINE_DEV, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DT019X_OUTPUT_SW2, 0 }, + { SB_DT019X_CAPTURE_SW, 0x06 }, +}; + +/* + * ALS4000 specific mixer elements + */ +/* FIXME: SB_ALS4000_MONO_IO_CTRL needs output select ctrl ! */ +static struct sbmix_elem snd_als4000_ctl_mono_output_switch = + SB_SINGLE("Mono Output Switch", SB_ALS4000_MONO_IO_CTRL, 5, 1); +/* FIXME: mono input switch also available on DT019X ? */ +static struct sbmix_elem snd_als4000_ctl_mono_input_switch = + SB_SINGLE("Mono Input Switch", SB_DT019X_OUTPUT_SW2, 0, 1); +static struct sbmix_elem snd_als4000_ctl_mic_20db_boost = + SB_SINGLE("Mic Boost (+20dB)", SB_ALS4000_MIC_IN_GAIN, 0, 0x03); +static struct sbmix_elem snd_als4000_ctl_mixer_out_to_in = + SB_SINGLE("Mixer Out To In", SB_ALS4000_MIC_IN_GAIN, 7, 0x01); +/* FIXME: 3D needs much more sophisticated controls, many more features ! */ +static struct sbmix_elem snd_als4000_ctl_3d_output_switch = + SB_SINGLE("3D Output Switch", SB_ALS4000_3D_SND_FX, 6, 0x01); +static struct sbmix_elem snd_als4000_ctl_3d_output_ratio = + SB_SINGLE("3D Output Ratio", SB_ALS4000_3D_SND_FX, 0, 0x07); +static struct sbmix_elem snd_als4000_ctl_3d_poweroff_switch = + SB_SINGLE("3D PowerOff Switch", SB_ALS4000_3D_TIME_DELAY, 4, 0x01); +static struct sbmix_elem snd_als4000_ctl_3d_delay = + SB_SINGLE("3D Delay", SB_ALS4000_3D_TIME_DELAY, 0, 0x0f); +#if NOT_AVAILABLE +static struct sbmix_elem snd_als4000_ctl_fmdac = + SB_SINGLE("FMDAC Switch (Option ?)", SB_ALS4000_FMDAC, 0, 0x01); +static struct sbmix_elem snd_als4000_ctl_qsound = + SB_SINGLE("QSound Mode", SB_ALS4000_QSOUND, 1, 0x1f); +#endif + +static struct sbmix_elem *snd_als4000_controls[] = { + &snd_sb16_ctl_master_play_vol, + &snd_dt019x_ctl_pcm_play_switch, + &snd_sb16_ctl_pcm_play_vol, + &snd_sb16_ctl_synth_capture_route, + &snd_dt019x_ctl_synth_play_switch, + &snd_sb16_ctl_synth_play_vol, + &snd_sb16_ctl_cd_capture_route, + &snd_sb16_ctl_cd_play_switch, + &snd_sb16_ctl_cd_play_vol, + &snd_sb16_ctl_line_capture_route, + &snd_sb16_ctl_line_play_switch, + &snd_sb16_ctl_line_play_vol, + &snd_sb16_ctl_mic_capture_route, + &snd_als4000_ctl_mic_20db_boost, + &snd_sb16_ctl_auto_mic_gain, + &snd_sb16_ctl_mic_play_switch, + &snd_sb16_ctl_mic_play_vol, + &snd_sb16_ctl_pc_speaker_vol, + &snd_sb16_ctl_capture_vol, + &snd_sb16_ctl_play_vol, + &snd_als4000_ctl_mono_output_switch, + &snd_als4000_ctl_mono_input_switch, + &snd_als4000_ctl_mixer_out_to_in, + &snd_als4000_ctl_3d_output_switch, + &snd_als4000_ctl_3d_output_ratio, + &snd_als4000_ctl_3d_delay, + &snd_als4000_ctl_3d_poweroff_switch, +#if NOT_AVAILABLE + &snd_als4000_ctl_fmdac, + &snd_als4000_ctl_qsound, +#endif +}; + +static unsigned char snd_als4000_init_values[][2] = { + { SB_DSP4_MASTER_DEV + 0, 0 }, + { SB_DSP4_MASTER_DEV + 1, 0 }, + { SB_DSP4_PCM_DEV + 0, 0 }, + { SB_DSP4_PCM_DEV + 1, 0 }, + { SB_DSP4_SYNTH_DEV + 0, 0 }, + { SB_DSP4_SYNTH_DEV + 1, 0 }, + { SB_DSP4_SPEAKER_DEV, 0 }, + { SB_DSP4_OUTPUT_SW, 0 }, + { SB_DSP4_INPUT_LEFT, 0 }, + { SB_DSP4_INPUT_RIGHT, 0 }, + { SB_DT019X_OUTPUT_SW2, 0 }, + { SB_ALS4000_MIC_IN_GAIN, 0 }, +}; + + +/* + */ +static int snd_sbmixer_init(sb_t *chip, + struct sbmix_elem **controls, + int controls_count, + unsigned char map[][2], + int map_count, + char *name) +{ + unsigned long flags; + snd_card_t *card = chip->card; + int idx, err; + + /* mixer reset */ + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, 0x00, 0x00); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + + /* mute and zero volume channels */ + for (idx = 0; idx < map_count; idx++) { + spin_lock_irqsave(&chip->mixer_lock, flags); + snd_sbmixer_write(chip, map[idx][0], map[idx][1]); + spin_unlock_irqrestore(&chip->mixer_lock, flags); + } + + for (idx = 0; idx < controls_count; idx++) { + if ((err = snd_sbmixer_add_ctl_elem(chip, controls[idx])) < 0) + return err; + } + snd_component_add(card, name); + strcpy(card->mixername, name); + return 0; +} + +int snd_sbmixer_new(sb_t *chip) +{ + snd_card_t * card; + int err; + + snd_assert(chip != NULL && chip->card != NULL, return -EINVAL); + + card = chip->card; + + switch (chip->hardware) { + case SB_HW_10: + return 0; /* no mixer chip on SB1.x */ + case SB_HW_20: + case SB_HW_201: + if ((err = snd_sbmixer_init(chip, + snd_sb20_controls, + ARRAY_SIZE(snd_sb20_controls), + snd_sb20_init_values, + ARRAY_SIZE(snd_sb20_init_values), + "CTL1335")) < 0) + return err; + break; + case SB_HW_PRO: + if ((err = snd_sbmixer_init(chip, + snd_sbpro_controls, + ARRAY_SIZE(snd_sbpro_controls), + snd_sbpro_init_values, + ARRAY_SIZE(snd_sbpro_init_values), + "CTL1345")) < 0) + return err; + break; + case SB_HW_16: + case SB_HW_ALS100: + if ((err = snd_sbmixer_init(chip, + snd_sb16_controls, + ARRAY_SIZE(snd_sb16_controls), + snd_sb16_init_values, + ARRAY_SIZE(snd_sb16_init_values), + "CTL1745")) < 0) + return err; + break; + case SB_HW_ALS4000: + if ((err = snd_sbmixer_init(chip, + snd_als4000_controls, + ARRAY_SIZE(snd_als4000_controls), + snd_als4000_init_values, + ARRAY_SIZE(snd_als4000_init_values), + "ALS4000")) < 0) + return err; + break; + case SB_HW_DT019X: + if ((err = snd_sbmixer_init(chip, + snd_dt019x_controls, + ARRAY_SIZE(snd_dt019x_controls), + snd_dt019x_init_values, + ARRAY_SIZE(snd_dt019x_init_values), + "DT019X")) < 0) + break; + default: + strcpy(card->mixername, "???"); + } + return 0; +} diff --git a/sound/isa/sb/sbawe.c b/sound/isa/sb/sbawe.c new file mode 100644 index 00000000000..2ec52a3473a --- /dev/null +++ b/sound/isa/sb/sbawe.c @@ -0,0 +1,2 @@ +#define SNDRV_SBAWE +#include "sb16.c" diff --git a/sound/isa/sgalaxy.c b/sound/isa/sgalaxy.c new file mode 100644 index 00000000000..17f585b0ddc --- /dev/null +++ b/sound/isa/sgalaxy.c @@ -0,0 +1,322 @@ +/* + * Driver for Aztech Sound Galaxy cards + * Copyright (c) by Christopher Butler <chrisb@sandy.force9.co.uk. + * + * I don't have documentation for this card, I based this driver on the + * driver for OSS/Free included in the kernel source (drivers/sound/sgalaxy.c) + * + * 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 <asm/dma.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/interrupt.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/sb.h> +#include <sound/ad1848.h> +#include <sound/control.h> +#define SNDRV_LEGACY_FIND_FREE_IRQ +#define SNDRV_LEGACY_FIND_FREE_DMA +#include <sound/initval.h> + +MODULE_AUTHOR("Christopher Butler <chrisb@sandy.force9.co.uk>"); +MODULE_DESCRIPTION("Aztech Sound Galaxy"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Aztech Systems,Sound Galaxy}}"); + +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 sbport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x220,0x240 */ +static long wssport[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* 0x530,0xe80,0xf40,0x604 */ +static int irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 7,9,10,11 */ +static int dma1[SNDRV_CARDS] = SNDRV_DEFAULT_DMA; /* 0,1,3 */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for Sound Galaxy soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for Sound Galaxy soundcard."); +module_param_array(sbport, long, NULL, 0444); +MODULE_PARM_DESC(sbport, "Port # for Sound Galaxy SB driver."); +module_param_array(wssport, long, NULL, 0444); +MODULE_PARM_DESC(wssport, "Port # for Sound Galaxy WSS driver."); +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for Sound Galaxy driver."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for Sound Galaxy driver."); + +#define SGALAXY_AUXC_LEFT 18 +#define SGALAXY_AUXC_RIGHT 19 + +static snd_card_t *snd_sgalaxy_cards[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +/* + + */ + +#define AD1848P1( port, x ) ( port + c_d_c_AD1848##x ) + +/* from lowlevel/sb/sb.c - to avoid having to allocate a sb_t for the */ +/* short time we actually need it.. */ + +static int snd_sgalaxy_sbdsp_reset(unsigned long port) +{ + int i; + + outb(1, SBP1(port, RESET)); + udelay(10); + outb(0, SBP1(port, RESET)); + udelay(30); + for (i = 0; i < 1000 && !(inb(SBP1(port, DATA_AVAIL)) & 0x80); i++); + if (inb(SBP1(port, READ)) != 0xaa) { + snd_printd("sb_reset: failed at 0x%lx!!!\n", port); + return -ENODEV; + } + return 0; +} + +static int __init snd_sgalaxy_sbdsp_command(unsigned long port, unsigned char val) +{ + int i; + + for (i = 10000; i; i--) + if ((inb(SBP1(port, STATUS)) & 0x80) == 0) { + outb(val, SBP1(port, COMMAND)); + return 1; + } + + return 0; +} + +static irqreturn_t snd_sgalaxy_dummy_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + return IRQ_NONE; +} + +static int __init snd_sgalaxy_setup_wss(unsigned long port, int irq, int dma) +{ + static int interrupt_bits[] = {-1, -1, -1, -1, -1, -1, -1, 0x08, -1, + 0x10, 0x18, 0x20, -1, -1, -1, -1}; + static int dma_bits[] = {1, 2, 0, 3}; + int tmp, tmp1; + + if ((tmp = inb(port + 3)) == 0xff) + { + snd_printdd("I/O address dead (0x%lx)\n", port); + return 0; + } +#if 0 + snd_printdd("WSS signature = 0x%x\n", tmp); +#endif + + if ((tmp & 0x3f) != 0x04 && + (tmp & 0x3f) != 0x0f && + (tmp & 0x3f) != 0x00) { + snd_printdd("No WSS signature detected on port 0x%lx\n", + port + 3); + return 0; + } + +#if 0 + snd_printdd("sgalaxy - setting up IRQ/DMA for WSS\n"); +#endif + + /* initialize IRQ for WSS codec */ + tmp = interrupt_bits[irq % 16]; + if (tmp < 0) + return -EINVAL; + + if (request_irq(irq, snd_sgalaxy_dummy_interrupt, SA_INTERRUPT, "sgalaxy", NULL)) { + snd_printk(KERN_ERR "sgalaxy: can't grab irq %d\n", irq); + return -EIO; + } + + outb(tmp | 0x40, port); + tmp1 = dma_bits[dma % 4]; + outb(tmp | tmp1, port); + + free_irq(irq, NULL); + + return 0; +} + +static int __init snd_sgalaxy_detect(int dev, int irq, int dma) +{ +#if 0 + snd_printdd("sgalaxy - switching to WSS mode\n"); +#endif + + /* switch to WSS mode */ + snd_sgalaxy_sbdsp_reset(sbport[dev]); + + snd_sgalaxy_sbdsp_command(sbport[dev], 9); + snd_sgalaxy_sbdsp_command(sbport[dev], 0); + + udelay(400); + return snd_sgalaxy_setup_wss(wssport[dev], irq, dma); +} + +static struct ad1848_mix_elem snd_sgalaxy_controls[] = { +AD1848_DOUBLE("Aux Playback Switch", 0, SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 7, 7, 1, 1), +AD1848_DOUBLE("Aux Playback Volume", 0, SGALAXY_AUXC_LEFT, SGALAXY_AUXC_RIGHT, 0, 0, 31, 0) +}; + +static int __init snd_sgalaxy_mixer(ad1848_t *chip) +{ + snd_card_t *card = chip->card; + snd_ctl_elem_id_t id1, id2; + unsigned int idx; + int err; + + memset(&id1, 0, sizeof(id1)); + memset(&id2, 0, sizeof(id2)); + id1.iface = id2.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + /* reassign AUX0 to LINE */ + strcpy(id1.name, "Aux Playback Switch"); + strcpy(id2.name, "Line Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "Line Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* reassign AUX1 to FM */ + strcpy(id1.name, "Aux Playback Switch"); id1.index = 1; + strcpy(id2.name, "FM Playback Switch"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + strcpy(id1.name, "Aux Playback Volume"); + strcpy(id2.name, "FM Playback Volume"); + if ((err = snd_ctl_rename_id(card, &id1, &id2)) < 0) + return err; + /* build AUX2 input */ + for (idx = 0; idx < ARRAY_SIZE(snd_sgalaxy_controls); idx++) { + if ((err = snd_ad1848_add_ctl_elem(chip, &snd_sgalaxy_controls[idx])) < 0) + return err; + } + return 0; +} + +static int __init snd_sgalaxy_probe(int dev) +{ + static int possible_irqs[] = {7, 9, 10, 11, -1}; + static int possible_dmas[] = {1, 3, 0, -1}; + int err, xirq, xdma1; + snd_card_t *card; + ad1848_t *chip; + + if (sbport[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify SB port\n"); + return -EINVAL; + } + if (wssport[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify WSS port\n"); + return -EINVAL; + } + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + + xirq = irq[dev]; + if (xirq == SNDRV_AUTO_IRQ) { + if ((xirq = snd_legacy_find_free_irq(possible_irqs)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free IRQ\n"); + return -EBUSY; + } + } + xdma1 = dma1[dev]; + if (xdma1 == SNDRV_AUTO_DMA) { + if ((xdma1 = snd_legacy_find_free_dma(possible_dmas)) < 0) { + snd_card_free(card); + snd_printk("unable to find a free DMA\n"); + return -EBUSY; + } + } + + if ((err = snd_sgalaxy_detect(dev, xirq, xdma1)) < 0) { + snd_card_free(card); + return err; + } + + if ((err = snd_ad1848_create(card, wssport[dev] + 4, + xirq, xdma1, + AD1848_HW_DETECT, &chip)) < 0) { + snd_card_free(card); + return err; + } + + if ((err = snd_ad1848_pcm(chip, 0, NULL)) < 0) { + snd_printdd("sgalaxy - error creating new ad1848 PCM device\n"); + snd_card_free(card); + return err; + } + if ((err = snd_ad1848_mixer(chip)) < 0) { + snd_printdd("sgalaxy - error creating new ad1848 mixer\n"); + snd_card_free(card); + return err; + } + if (snd_sgalaxy_mixer(chip) < 0) { + snd_printdd("sgalaxy - the mixer rewrite failed\n"); + snd_card_free(card); + return err; + } + + strcpy(card->driver, "Sound Galaxy"); + strcpy(card->shortname, "Sound Galaxy"); + sprintf(card->longname, "Sound Galaxy at 0x%lx, irq %d, dma %d", + wssport[dev], xirq, xdma1); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + snd_sgalaxy_cards[dev] = card; + return 0; +} + +static int __init alsa_card_sgalaxy_init(void) +{ + int dev, cards; + + for (dev = cards = 0; dev < SNDRV_CARDS && enable[dev]; dev++) { + if (snd_sgalaxy_probe(dev) >= 0) + cards++; + } + if (!cards) { +#ifdef MODULE + snd_printk(KERN_ERR "Sound Galaxy soundcard not found or device busy\n"); +#endif + return -ENODEV; + } + + return 0; +} + +static void __exit alsa_card_sgalaxy_exit(void) +{ + int idx; + + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_sgalaxy_cards[idx]); +} + +module_init(alsa_card_sgalaxy_init) +module_exit(alsa_card_sgalaxy_exit) diff --git a/sound/isa/sscape.c b/sound/isa/sscape.c new file mode 100644 index 00000000000..3959ed694ee --- /dev/null +++ b/sound/isa/sscape.c @@ -0,0 +1,1517 @@ +/* + * Low-level ALSA driver for the ENSONIQ SoundScape PnP + * Copyright (c) by Chris Rankin + * + * This driver was written in part using information obtained from + * the OSS/Free SoundScape driver, written by Hannu Savolainen. + * + * + * 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/delay.h> +#include <linux/pnp.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> +#include <asm/dma.h> +#include <sound/core.h> +#include <sound/hwdep.h> +#include <sound/cs4231.h> +#include <sound/mpu401.h> +#include <sound/initval.h> + +#include <sound/sscape_ioctl.h> + + +MODULE_AUTHOR("Chris Rankin"); +MODULE_DESCRIPTION("ENSONIQ SoundScape PnP driver"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IDX; +static char* id[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_STR; +static long port[SNDRV_CARDS] __devinitdata = { [0 ... (SNDRV_CARDS-1)] = SNDRV_AUTO_PORT }; +static int irq[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IRQ; +static int mpu_irq[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_IRQ; +static int dma[SNDRV_CARDS] __devinitdata = SNDRV_DEFAULT_DMA; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index number for SoundScape soundcard"); + +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "Description for SoundScape card"); + +module_param_array(port, long, NULL, 0444); +MODULE_PARM_DESC(port, "Port # for SoundScape driver."); + +module_param_array(irq, int, NULL, 0444); +MODULE_PARM_DESC(irq, "IRQ # for SoundScape driver."); + +module_param_array(mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(mpu_irq, "MPU401 IRQ # for SoundScape driver."); + +module_param_array(dma, int, NULL, 0444); +MODULE_PARM_DESC(dma, "DMA # for SoundScape driver."); + +#ifdef CONFIG_PNP +static struct pnp_card_device_id sscape_pnpids[] = { + { .id = "ENS3081", .devs = { { "ENS0000" } } }, + { .id = "" } /* end */ +}; + +MODULE_DEVICE_TABLE(pnp_card, sscape_pnpids); +#endif + +static snd_card_t *sscape_card[SNDRV_CARDS]; + + +#define MPU401_IO(i) ((i) + 0) +#define MIDI_DATA_IO(i) ((i) + 0) +#define MIDI_CTRL_IO(i) ((i) + 1) +#define HOST_CTRL_IO(i) ((i) + 2) +#define HOST_DATA_IO(i) ((i) + 3) +#define ODIE_ADDR_IO(i) ((i) + 4) +#define ODIE_DATA_IO(i) ((i) + 5) +#define CODEC_IO(i) ((i) + 8) + +#define IC_ODIE 1 +#define IC_OPUS 2 + +#define RX_READY 0x01 +#define TX_READY 0x02 + +#define CMD_ACK 0x80 +#define CMD_SET_MIDI_VOL 0x84 +#define CMD_GET_MIDI_VOL 0x85 +#define CMD_XXX_MIDI_VOL 0x86 +#define CMD_SET_EXTMIDI 0x8a +#define CMD_GET_EXTMIDI 0x8b +#define CMD_SET_MT32 0x8c +#define CMD_GET_MT32 0x8d + +enum GA_REG { + GA_INTSTAT_REG = 0, + GA_INTENA_REG, + GA_DMAA_REG, + GA_DMAB_REG, + GA_INTCFG_REG, + GA_DMACFG_REG, + GA_CDCFG_REG, + GA_SMCFGA_REG, + GA_SMCFGB_REG, + GA_HMCTL_REG +}; + +#define DMA_8BIT 0x80 + + +#define AD1845_FREQ_SEL_MSB 0x16 +#define AD1845_FREQ_SEL_LSB 0x17 + +struct soundscape { + spinlock_t lock; + unsigned io_base; + int codec_type; + int ic_type; + struct resource *io_res; + cs4231_t *chip; + mpu401_t *mpu; + snd_hwdep_t *hw; + + /* + * The MIDI device won't work until we've loaded + * its firmware via a hardware-dependent device IOCTL + */ + spinlock_t fwlock; + int hw_in_use; + unsigned long midi_usage; + unsigned char midi_vol; +}; + +#define INVALID_IRQ ((unsigned)-1) + + +static inline struct soundscape *get_card_soundscape(snd_card_t * c) +{ + return (struct soundscape *) (c->private_data); +} + +static inline struct soundscape *get_mpu401_soundscape(mpu401_t * mpu) +{ + return (struct soundscape *) (mpu->private_data); +} + +static inline struct soundscape *get_hwdep_soundscape(snd_hwdep_t * hw) +{ + return (struct soundscape *) (hw->private_data); +} + + +/* + * Allocates some kernel memory that we can use for DMA. + * I think this means that the memory has to map to + * contiguous pages of physical memory. + */ +static struct snd_dma_buffer *get_dmabuf(struct snd_dma_buffer *buf, unsigned long size) +{ + if (buf) { + if (snd_dma_alloc_pages_fallback(SNDRV_DMA_TYPE_DEV, snd_dma_isa_data(), + size, buf) < 0) { + snd_printk(KERN_ERR "sscape: Failed to allocate %lu bytes for DMA\n", size); + return NULL; + } + } + + return buf; +} + +/* + * Release the DMA-able kernel memory ... + */ +static void free_dmabuf(struct snd_dma_buffer *buf) +{ + if (buf && buf->area) + snd_dma_free_pages(buf); +} + + +/* + * This function writes to the SoundScape's control registers, + * but doesn't do any locking. It's up to the caller to do that. + * This is why this function is "unsafe" ... + */ +static inline void sscape_write_unsafe(unsigned io_base, enum GA_REG reg, unsigned char val) +{ + outb(reg, ODIE_ADDR_IO(io_base)); + outb(val, ODIE_DATA_IO(io_base)); +} + +/* + * Write to the SoundScape's control registers, and do the + * necessary locking ... + */ +static void sscape_write(struct soundscape *s, enum GA_REG reg, unsigned char val) +{ + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + sscape_write_unsafe(s->io_base, reg, val); + spin_unlock_irqrestore(&s->lock, flags); +} + +/* + * Read from the SoundScape's control registers, but leave any + * locking to the caller. This is why the function is "unsafe" ... + */ +static inline unsigned char sscape_read_unsafe(unsigned io_base, enum GA_REG reg) +{ + outb(reg, ODIE_ADDR_IO(io_base)); + return inb(ODIE_DATA_IO(io_base)); +} + +/* + * Puts the SoundScape into "host" mode, as compared to "MIDI" mode + */ +static inline void set_host_mode_unsafe(unsigned io_base) +{ + outb(0x0, HOST_CTRL_IO(io_base)); +} + +/* + * Puts the SoundScape into "MIDI" mode, as compared to "host" mode + */ +static inline void set_midi_mode_unsafe(unsigned io_base) +{ + outb(0x3, HOST_CTRL_IO(io_base)); +} + +/* + * Read the SoundScape's host-mode control register, but leave + * any locking issues to the caller ... + */ +static inline int host_read_unsafe(unsigned io_base) +{ + int data = -1; + if ((inb(HOST_CTRL_IO(io_base)) & RX_READY) != 0) { + data = inb(HOST_DATA_IO(io_base)); + } + + return data; +} + +/* + * Read the SoundScape's host-mode control register, performing + * a limited amount of busy-waiting if the register isn't ready. + * Also leaves all locking-issues to the caller ... + */ +static int host_read_ctrl_unsafe(unsigned io_base, unsigned timeout) +{ + int data; + + while (((data = host_read_unsafe(io_base)) < 0) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return data; +} + +/* + * Write to the SoundScape's host-mode control registers, but + * leave any locking issues to the caller ... + */ +static inline int host_write_unsafe(unsigned io_base, unsigned char data) +{ + if ((inb(HOST_CTRL_IO(io_base)) & TX_READY) != 0) { + outb(data, HOST_DATA_IO(io_base)); + return 1; + } + + return 0; +} + +/* + * Write to the SoundScape's host-mode control registers, performing + * a limited amount of busy-waiting if the register isn't ready. + * Also leaves all locking-issues to the caller ... + */ +static int host_write_ctrl_unsafe(unsigned io_base, unsigned char data, + unsigned timeout) +{ + int err; + + while (!(err = host_write_unsafe(io_base, data)) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return err; +} + + +/* + * Check that the MIDI subsystem is operational. If it isn't, + * then we will hang the computer if we try to use it ... + * + * NOTE: This check is based upon observation, not documentation. + */ +static inline int verify_mpu401(const mpu401_t * mpu) +{ + return ((inb(MIDI_CTRL_IO(mpu->port)) & 0xc0) == 0x80); +} + +/* + * This is apparently the standard way to initailise an MPU-401 + */ +static inline void initialise_mpu401(const mpu401_t * mpu) +{ + outb(0, MIDI_DATA_IO(mpu->port)); +} + +/* + * Tell the SoundScape to activate the AD1845 chip (I think). + * The AD1845 detection fails if we *don't* do this, so I + * think that this is a good idea ... + */ +static inline void activate_ad1845_unsafe(unsigned io_base) +{ + sscape_write_unsafe(io_base, GA_HMCTL_REG, (sscape_read_unsafe(io_base, GA_HMCTL_REG) & 0xcf) | 0x10); + sscape_write_unsafe(io_base, GA_CDCFG_REG, 0x80); +} + +/* + * Do the necessary ALSA-level cleanup to deallocate our driver ... + */ +static void soundscape_free(snd_card_t * c) +{ + register struct soundscape *sscape = get_card_soundscape(c); + release_resource(sscape->io_res); + kfree_nocheck(sscape->io_res); + free_dma(sscape->chip->dma1); +} + +/* + * Put this process into an idle wait-state for a certain number + * of "jiffies". The process can almost certainly be rescheduled + * while we're waiting, and so we must NOT be holding any spinlocks + * when we call this function. If we are then we risk DEADLOCK in + * SMP (Ha!) or pre-emptible kernels. + */ +static inline void sleep(long jiffs, int state) +{ + set_current_state(state); + schedule_timeout(jiffs); +} + +/* + * Tell the SoundScape to begin a DMA tranfer using the given channel. + * All locking issues are left to the caller. + */ +static inline void sscape_start_dma_unsafe(unsigned io_base, enum GA_REG reg) +{ + sscape_write_unsafe(io_base, reg, sscape_read_unsafe(io_base, reg) | 0x01); + sscape_write_unsafe(io_base, reg, sscape_read_unsafe(io_base, reg) & 0xfe); +} + +/* + * Wait for a DMA transfer to complete. This is a "limited busy-wait", + * and all locking issues are left to the caller. + */ +static int sscape_wait_dma_unsafe(unsigned io_base, enum GA_REG reg, unsigned timeout) +{ + while (!(sscape_read_unsafe(io_base, reg) & 0x01) && (timeout != 0)) { + udelay(100); + --timeout; + } /* while */ + + return (sscape_read_unsafe(io_base, reg) & 0x01); +} + +/* + * Wait for the On-Board Processor to return its start-up + * acknowledgement sequence. This wait is too long for + * us to perform "busy-waiting", and so we must sleep. + * This in turn means that we must not be holding any + * spinlocks when we call this function. + */ +static int obp_startup_ack(struct soundscape *s, unsigned timeout) +{ + while (timeout != 0) { + unsigned long flags; + unsigned char x; + + sleep(1, TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&s->lock, flags); + x = inb(HOST_DATA_IO(s->io_base)); + spin_unlock_irqrestore(&s->lock, flags); + if ((x & 0xfe) == 0xfe) + return 1; + + --timeout; + } /* while */ + + return 0; +} + +/* + * Wait for the host to return its start-up acknowledgement + * sequence. This wait is too long for us to perform + * "busy-waiting", and so we must sleep. This in turn means + * that we must not be holding any spinlocks when we call + * this function. + */ +static int host_startup_ack(struct soundscape *s, unsigned timeout) +{ + while (timeout != 0) { + unsigned long flags; + unsigned char x; + + sleep(1, TASK_INTERRUPTIBLE); + + spin_lock_irqsave(&s->lock, flags); + x = inb(HOST_DATA_IO(s->io_base)); + spin_unlock_irqrestore(&s->lock, flags); + if (x == 0xfe) + return 1; + + --timeout; + } /* while */ + + return 0; +} + +/* + * Upload a byte-stream into the SoundScape using DMA channel A. + */ +static int upload_dma_data(struct soundscape *s, + const unsigned char __user *data, + size_t size) +{ + unsigned long flags; + struct snd_dma_buffer dma; + int ret; + + if (!get_dmabuf(&dma, PAGE_ALIGN(size))) + return -ENOMEM; + + spin_lock_irqsave(&s->lock, flags); + + /* + * Reset the board ... + */ + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) & 0x3f); + + /* + * Enable the DMA channels and configure them ... + */ + sscape_write_unsafe(s->io_base, GA_DMACFG_REG, 0x50); + sscape_write_unsafe(s->io_base, GA_DMAA_REG, (s->chip->dma1 << 4) | DMA_8BIT); + sscape_write_unsafe(s->io_base, GA_DMAB_REG, 0x20); + + /* + * Take the board out of reset ... + */ + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) | 0x80); + + /* + * Upload the user's data (firmware?) to the SoundScape + * board through the DMA channel ... + */ + while (size != 0) { + unsigned long len; + + /* + * Apparently, copying to/from userspace can sleep. + * We are therefore forbidden from holding any + * spinlocks while we copy ... + */ + spin_unlock_irqrestore(&s->lock, flags); + + /* + * Remember that the data that we want to DMA + * comes from USERSPACE. We have already verified + * the userspace pointer ... + */ + len = min(size, dma.bytes); + len -= __copy_from_user(dma.area, data, len); + data += len; + size -= len; + + /* + * Grab that spinlock again, now that we've + * finished copying! + */ + spin_lock_irqsave(&s->lock, flags); + + snd_dma_program(s->chip->dma1, dma.addr, len, DMA_MODE_WRITE); + sscape_start_dma_unsafe(s->io_base, GA_DMAA_REG); + if (!sscape_wait_dma_unsafe(s->io_base, GA_DMAA_REG, 5000)) { + /* + * Don't forget to release this spinlock we're holding ... + */ + spin_unlock_irqrestore(&s->lock, flags); + + snd_printk(KERN_ERR "sscape: DMA upload has timed out\n"); + ret = -EAGAIN; + goto _release_dma; + } + } /* while */ + + set_host_mode_unsafe(s->io_base); + + /* + * Boot the board ... (I think) + */ + sscape_write_unsafe(s->io_base, GA_HMCTL_REG, sscape_read_unsafe(s->io_base, GA_HMCTL_REG) | 0x40); + spin_unlock_irqrestore(&s->lock, flags); + + /* + * If all has gone well, then the board should acknowledge + * the new upload and tell us that it has rebooted OK. We + * give it 5 seconds (max) ... + */ + ret = 0; + if (!obp_startup_ack(s, 5)) { + snd_printk(KERN_ERR "sscape: No response from on-board processor after upload\n"); + ret = -EAGAIN; + } else if (!host_startup_ack(s, 5)) { + snd_printk(KERN_ERR "sscape: SoundScape failed to initialise\n"); + ret = -EAGAIN; + } + + _release_dma: + /* + * NOTE!!! We are NOT holding any spinlocks at this point !!! + */ + sscape_write(s, GA_DMAA_REG, (s->ic_type == IC_ODIE ? 0x70 : 0x40)); + free_dmabuf(&dma); + + return ret; +} + +/* + * Upload the bootblock(?) into the SoundScape. The only + * purpose of this block of code seems to be to tell + * us which version of the microcode we should be using. + * + * NOTE: The boot-block data resides in USER-SPACE!!! + * However, we have already verified its memory + * addresses by the time we get here. + */ +static int sscape_upload_bootblock(struct soundscape *sscape, struct sscape_bootblock __user *bb) +{ + unsigned long flags; + int data = 0; + int ret; + + ret = upload_dma_data(sscape, bb->code, sizeof(bb->code)); + + spin_lock_irqsave(&sscape->lock, flags); + if (ret == 0) { + data = host_read_ctrl_unsafe(sscape->io_base, 100); + } + set_midi_mode_unsafe(sscape->io_base); + spin_unlock_irqrestore(&sscape->lock, flags); + + if (ret == 0) { + if (data < 0) { + snd_printk(KERN_ERR "sscape: timeout reading firmware version\n"); + ret = -EAGAIN; + } + else if (__copy_to_user(&bb->version, &data, sizeof(bb->version))) { + ret = -EFAULT; + } + } + + return ret; +} + +/* + * Upload the microcode into the SoundScape. The + * microcode is 64K of data, and if we try to copy + * it into a local variable then we will SMASH THE + * KERNEL'S STACK! We therefore leave it in USER + * SPACE, and save ourselves from copying it at all. + */ +static int sscape_upload_microcode(struct soundscape *sscape, + const struct sscape_microcode __user *mc) +{ + unsigned long flags; + char __user *code; + int err; + + /* + * We are going to have to copy this data into a special + * DMA-able buffer before we can upload it. We shall therefore + * just check that the data pointer is valid for now. + * + * NOTE: This buffer is 64K long! That's WAY too big to + * copy into a stack-temporary anyway. + */ + if ( get_user(code, &mc->code) || + !access_ok(VERIFY_READ, code, SSCAPE_MICROCODE_SIZE) ) + return -EFAULT; + + if ((err = upload_dma_data(sscape, code, SSCAPE_MICROCODE_SIZE)) == 0) { + snd_printk(KERN_INFO "sscape: MIDI firmware loaded\n"); + } + + spin_lock_irqsave(&sscape->lock, flags); + set_midi_mode_unsafe(sscape->io_base); + spin_unlock_irqrestore(&sscape->lock, flags); + + initialise_mpu401(sscape->mpu); + + return err; +} + +/* + * Hardware-specific device functions, to implement special + * IOCTLs for the SoundScape card. This is how we upload + * the microcode into the card, for example, and so we + * must ensure that no two processes can open this device + * simultaneously, and that we can't open it at all if + * someone is using the MIDI device. + */ +static int sscape_hw_open(snd_hwdep_t * hw, struct file *file) +{ + register struct soundscape *sscape = get_hwdep_soundscape(hw); + unsigned long flags; + int err; + + spin_lock_irqsave(&sscape->fwlock, flags); + + if ((sscape->midi_usage != 0) || sscape->hw_in_use) { + err = -EBUSY; + } else { + sscape->hw_in_use = 1; + err = 0; + } + + spin_unlock_irqrestore(&sscape->fwlock, flags); + return err; +} + +static int sscape_hw_release(snd_hwdep_t * hw, struct file *file) +{ + register struct soundscape *sscape = get_hwdep_soundscape(hw); + unsigned long flags; + + spin_lock_irqsave(&sscape->fwlock, flags); + sscape->hw_in_use = 0; + spin_unlock_irqrestore(&sscape->fwlock, flags); + return 0; +} + +static int sscape_hw_ioctl(snd_hwdep_t * hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct soundscape *sscape = get_hwdep_soundscape(hw); + int err = -EBUSY; + + switch (cmd) { + case SND_SSCAPE_LOAD_BOOTB: + { + register struct sscape_bootblock __user *bb = (struct sscape_bootblock __user *) arg; + + /* + * We are going to have to copy this data into a special + * DMA-able buffer before we can upload it. We shall therefore + * just check that the data pointer is valid for now ... + */ + if ( !access_ok(VERIFY_READ, bb->code, sizeof(bb->code)) ) + return -EFAULT; + + /* + * Now check that we can write the firmware version number too... + */ + if ( !access_ok(VERIFY_WRITE, &bb->version, sizeof(bb->version)) ) + return -EFAULT; + + err = sscape_upload_bootblock(sscape, bb); + } + break; + + case SND_SSCAPE_LOAD_MCODE: + { + register const struct sscape_microcode __user *mc = (const struct sscape_microcode __user *) arg; + + err = sscape_upload_microcode(sscape, mc); + } + break; + + default: + err = -EINVAL; + break; + } /* switch */ + + return err; +} + + +/* + * Mixer control for the SoundScape's MIDI device. + */ +static int sscape_midi_info(snd_kcontrol_t * ctl, + snd_ctl_elem_info_t * uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 127; + return 0; +} + +static int sscape_midi_get(snd_kcontrol_t * kctl, + snd_ctl_elem_value_t * uctl) +{ + cs4231_t *chip = snd_kcontrol_chip(kctl); + snd_card_t *card = chip->card; + register struct soundscape *s = get_card_soundscape(card); + unsigned long flags; + + spin_lock_irqsave(&s->lock, flags); + set_host_mode_unsafe(s->io_base); + + if (host_write_ctrl_unsafe(s->io_base, CMD_GET_MIDI_VOL, 100)) { + uctl->value.integer.value[0] = host_read_ctrl_unsafe(s->io_base, 100); + } + + set_midi_mode_unsafe(s->io_base); + spin_unlock_irqrestore(&s->lock, flags); + return 0; +} + +static int sscape_midi_put(snd_kcontrol_t * kctl, + snd_ctl_elem_value_t * uctl) +{ + cs4231_t *chip = snd_kcontrol_chip(kctl); + snd_card_t *card = chip->card; + register struct soundscape *s = get_card_soundscape(card); + unsigned long flags; + int change; + + spin_lock_irqsave(&s->lock, flags); + + /* + * We need to put the board into HOST mode before we + * can send any volume-changing HOST commands ... + */ + set_host_mode_unsafe(s->io_base); + + /* + * To successfully change the MIDI volume setting, you seem to + * have to write a volume command, write the new volume value, + * and then perform another volume-related command. Perhaps the + * first command is an "open" and the second command is a "close"? + */ + if (s->midi_vol == ((unsigned char) uctl->value.integer. value[0] & 127)) { + change = 0; + goto __skip_change; + } + change = (host_write_ctrl_unsafe(s->io_base, CMD_SET_MIDI_VOL, 100) + && host_write_ctrl_unsafe(s->io_base, ((unsigned char) uctl->value.integer. value[0]) & 127, 100) + && host_write_ctrl_unsafe(s->io_base, CMD_XXX_MIDI_VOL, 100)); + __skip_change: + + /* + * Take the board out of HOST mode and back into MIDI mode ... + */ + set_midi_mode_unsafe(s->io_base); + + spin_unlock_irqrestore(&s->lock, flags); + return change; +} + +static snd_kcontrol_new_t midi_mixer_ctl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "MIDI", + .info = sscape_midi_info, + .get = sscape_midi_get, + .put = sscape_midi_put +}; + +/* + * The SoundScape can use two IRQs from a possible set of four. + * These IRQs are encoded as bit patterns so that they can be + * written to the control registers. + */ +static unsigned __devinit get_irq_config(int irq) +{ + static const int valid_irq[] = { 9, 5, 7, 10 }; + unsigned cfg; + + for (cfg = 0; cfg < ARRAY_SIZE(valid_irq); ++cfg) { + if (irq == valid_irq[cfg]) + return cfg; + } /* for */ + + return INVALID_IRQ; +} + + +/* + * Perform certain arcane port-checks to see whether there + * is a SoundScape board lurking behind the given ports. + */ +static int __devinit detect_sscape(struct soundscape *s) +{ + unsigned long flags; + unsigned d; + int retval = 0; + + spin_lock_irqsave(&s->lock, flags); + + /* + * The following code is lifted from the original OSS driver, + * and as I don't have a datasheet I cannot really comment + * on what it is doing... + */ + if ((inb(HOST_CTRL_IO(s->io_base)) & 0x78) != 0) + goto _done; + + d = inb(ODIE_ADDR_IO(s->io_base)) & 0xf0; + if ((d & 0x80) != 0) + goto _done; + + if (d == 0) { + s->codec_type = 1; + s->ic_type = IC_ODIE; + } else if ((d & 0x60) != 0) { + s->codec_type = 2; + s->ic_type = IC_OPUS; + } else + goto _done; + + outb(0xfa, ODIE_ADDR_IO(s->io_base)); + if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0a) + goto _done; + + outb(0xfe, ODIE_ADDR_IO(s->io_base)); + if ((inb(ODIE_ADDR_IO(s->io_base)) & 0x9f) != 0x0e) + goto _done; + if ((inb(ODIE_DATA_IO(s->io_base)) & 0x9f) != 0x0e) + goto _done; + + /* + * SoundScape successfully detected! + */ + retval = 1; + + _done: + spin_unlock_irqrestore(&s->lock, flags); + return retval; +} + +/* + * ALSA callback function, called when attempting to open the MIDI device. + * Check that the MIDI firmware has been loaded, because we don't want + * to crash the machine. Also check that someone isn't using the hardware + * IOCTL device. + */ +static int mpu401_open(mpu401_t * mpu) +{ + int err; + + if (!verify_mpu401(mpu)) { + snd_printk(KERN_ERR "sscape: MIDI disabled, please load firmware\n"); + err = -ENODEV; + } else { + register struct soundscape *sscape = get_mpu401_soundscape(mpu); + unsigned long flags; + + spin_lock_irqsave(&sscape->fwlock, flags); + + if (sscape->hw_in_use || (sscape->midi_usage == ULONG_MAX)) { + err = -EBUSY; + } else { + ++(sscape->midi_usage); + err = 0; + } + + spin_unlock_irqrestore(&sscape->fwlock, flags); + } + + return err; +} + +static void mpu401_close(mpu401_t * mpu) +{ + register struct soundscape *sscape = get_mpu401_soundscape(mpu); + unsigned long flags; + + spin_lock_irqsave(&sscape->fwlock, flags); + --(sscape->midi_usage); + spin_unlock_irqrestore(&sscape->fwlock, flags); +} + +/* + * Initialse an MPU-401 subdevice for MIDI support on the SoundScape. + */ +static int __devinit create_mpu401(snd_card_t * card, int devnum, unsigned long port, int irq) +{ + struct soundscape *sscape = get_card_soundscape(card); + snd_rawmidi_t *rawmidi; + int err; + +#define MPU401_SHARE_HARDWARE 1 + if ((err = snd_mpu401_uart_new(card, devnum, + MPU401_HW_MPU401, + port, MPU401_SHARE_HARDWARE, + irq, SA_INTERRUPT, + &rawmidi)) == 0) { + mpu401_t *mpu = (mpu401_t *) rawmidi->private_data; + mpu->open_input = mpu401_open; + mpu->open_output = mpu401_open; + mpu->close_input = mpu401_close; + mpu->close_output = mpu401_close; + mpu->private_data = sscape; + sscape->mpu = mpu; + + initialise_mpu401(mpu); + } + + return err; +} + + +/* + * Override for the CS4231 playback format function. + * The AD1845 has much simpler format and rate selection. + */ +static void ad1845_playback_format(cs4231_t * chip, snd_pcm_hw_params_t * params, unsigned char format) +{ + unsigned long flags; + unsigned rate = params_rate(params); + + /* + * The AD1845 can't handle sample frequencies + * outside of 4 kHZ to 50 kHZ + */ + if (rate > 50000) + rate = 50000; + else if (rate < 4000) + rate = 4000; + + spin_lock_irqsave(&chip->reg_lock, flags); + + /* + * Program the AD1845 correctly for the playback stream. + * Note that we do NOT need to toggle the MCE bit because + * the PLAYBACK_ENABLE bit of the Interface Configuration + * register is set. + * + * NOTE: We seem to need to write to the MSB before the LSB + * to get the correct sample frequency. + */ + snd_cs4231_out(chip, CS4231_PLAYBK_FORMAT, (format & 0xf0)); + snd_cs4231_out(chip, AD1845_FREQ_SEL_MSB, (unsigned char) (rate >> 8)); + snd_cs4231_out(chip, AD1845_FREQ_SEL_LSB, (unsigned char) rate); + + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +/* + * Override for the CS4231 capture format function. + * The AD1845 has much simpler format and rate selection. + */ +static void ad1845_capture_format(cs4231_t * chip, snd_pcm_hw_params_t * params, unsigned char format) +{ + unsigned long flags; + unsigned rate = params_rate(params); + + /* + * The AD1845 can't handle sample frequencies + * outside of 4 kHZ to 50 kHZ + */ + if (rate > 50000) + rate = 50000; + else if (rate < 4000) + rate = 4000; + + spin_lock_irqsave(&chip->reg_lock, flags); + + /* + * Program the AD1845 correctly for the playback stream. + * Note that we do NOT need to toggle the MCE bit because + * the CAPTURE_ENABLE bit of the Interface Configuration + * register is set. + * + * NOTE: We seem to need to write to the MSB before the LSB + * to get the correct sample frequency. + */ + snd_cs4231_out(chip, CS4231_REC_FORMAT, (format & 0xf0)); + snd_cs4231_out(chip, AD1845_FREQ_SEL_MSB, (unsigned char) (rate >> 8)); + snd_cs4231_out(chip, AD1845_FREQ_SEL_LSB, (unsigned char) rate); + + spin_unlock_irqrestore(&chip->reg_lock, flags); +} + +/* + * Create an AD1845 PCM subdevice on the SoundScape. The AD1845 + * is very much like a CS4231, with a few extra bits. We will + * try to support at least some of the extra bits by overriding + * some of the CS4231 callback. + */ +static int __devinit create_ad1845(snd_card_t * card, unsigned port, int irq, int dma1) +{ + register struct soundscape *sscape = get_card_soundscape(card); + cs4231_t *chip; + int err; + +#define CS4231_SHARE_HARDWARE (CS4231_HWSHARE_DMA1 | CS4231_HWSHARE_DMA2) + /* + * The AD1845 PCM device is only half-duplex, and so + * we only give it one DMA channel ... + */ + if ((err = snd_cs4231_create(card, + port, -1, irq, dma1, dma1, + CS4231_HW_DETECT, + CS4231_HWSHARE_DMA1, &chip)) == 0) { + unsigned long flags; + snd_pcm_t *pcm; + +#define AD1845_FREQ_SEL_ENABLE 0x08 + +#define AD1845_PWR_DOWN_CTRL 0x1b +#define AD1845_CRYS_CLOCK_SEL 0x1d + +/* + * It turns out that the PLAYBACK_ENABLE bit is set + * by the lowlevel driver ... + * +#define AD1845_IFACE_CONFIG \ + (CS4231_AUTOCALIB | CS4231_RECORD_ENABLE | CS4231_PLAYBACK_ENABLE) + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_IFACE_CTRL, AD1845_IFACE_CONFIG); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + */ + + /* + * The input clock frequency on the SoundScape must + * be 14.31818 MHz, because we must set this register + * to get the playback to sound correct ... + */ + snd_cs4231_mce_up(chip); + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, AD1845_CRYS_CLOCK_SEL, 0x20); + spin_unlock_irqrestore(&chip->reg_lock, flags); + snd_cs4231_mce_down(chip); + + /* + * More custom configuration: + * a) select "mode 2", and provide a current drive of 8 mA + * b) enable frequency selection (for capture/playback) + */ + spin_lock_irqsave(&chip->reg_lock, flags); + snd_cs4231_out(chip, CS4231_MISC_INFO, (CS4231_MODE2 | 0x10)); + snd_cs4231_out(chip, AD1845_PWR_DOWN_CTRL, snd_cs4231_in(chip, AD1845_PWR_DOWN_CTRL) | AD1845_FREQ_SEL_ENABLE); + spin_unlock_irqrestore(&chip->reg_lock, flags); + + if ((err = snd_cs4231_pcm(chip, 0, &pcm)) < 0) { + snd_printk(KERN_ERR "sscape: No PCM device for AD1845 chip\n"); + goto _error; + } + + if ((err = snd_cs4231_mixer(chip)) < 0) { + snd_printk(KERN_ERR "sscape: No mixer device for AD1845 chip\n"); + goto _error; + } + + if ((err = snd_ctl_add(card, snd_ctl_new1(&midi_mixer_ctl, chip))) < 0) { + snd_printk(KERN_ERR "sscape: Could not create MIDI mixer control\n"); + goto _error; + } + + strcpy(card->driver, "SoundScape"); + strcpy(card->shortname, pcm->name); + snprintf(card->longname, sizeof(card->longname), + "%s at 0x%lx, IRQ %d, DMA %d\n", + pcm->name, chip->port, chip->irq, chip->dma1); + chip->set_playback_format = ad1845_playback_format; + chip->set_capture_format = ad1845_capture_format; + sscape->chip = chip; + } + + _error: + return err; +} + + +struct params +{ + int index; + const char *id; + unsigned port; + int irq; + int mpu_irq; + int dma1; +}; + + +static inline struct params* +init_params(struct params *params, + int index, + const char *id, + unsigned port, + int irq, + int mpu_irq, + int dma1) +{ + params->index = index; + params->id = id; + params->port = port; + params->irq = irq; + params->mpu_irq = mpu_irq; + params->dma1 = (dma1 & 0x03); + + return params; +} + + +/* + * Create an ALSA soundcard entry for the SoundScape, using + * the given list of port, IRQ and DMA resources. + */ +static int __devinit create_sscape(const struct params *params, snd_card_t **rcardp) +{ + snd_card_t *card; + register struct soundscape *sscape; + register unsigned dma_cfg; + unsigned irq_cfg; + unsigned mpu_irq_cfg; + struct resource *io_res; + unsigned long flags; + int err; + + /* + * Check that the user didn't pass us garbage data ... + */ + irq_cfg = get_irq_config(params->irq); + if (irq_cfg == INVALID_IRQ) { + snd_printk(KERN_ERR "sscape: Invalid IRQ %d\n", params->irq); + return -ENXIO; + } + + mpu_irq_cfg = get_irq_config(params->mpu_irq); + if (mpu_irq_cfg == INVALID_IRQ) { + printk(KERN_ERR "sscape: Invalid IRQ %d\n", params->mpu_irq); + return -ENXIO; + } + + /* + * Grab IO ports that we will need to probe so that we + * can detect and control this hardware ... + */ + if ((io_res = request_region(params->port, 8, "SoundScape")) == NULL) { + snd_printk(KERN_ERR "sscape: can't grab port 0x%x\n", params->port); + return -EBUSY; + } + + /* + * Grab both DMA channels (OK, only one for now) ... + */ + if ((err = request_dma(params->dma1, "SoundScape")) < 0) { + snd_printk(KERN_ERR "sscape: can't grab DMA %d\n", params->dma1); + goto _release_region; + } + + /* + * Create a new ALSA sound card entry, in anticipation + * of detecting our hardware ... + */ + if ((card = snd_card_new(params->index, params->id, THIS_MODULE, sizeof(struct soundscape))) == NULL) { + err = -ENOMEM; + goto _release_dma; + } + + sscape = get_card_soundscape(card); + spin_lock_init(&sscape->lock); + spin_lock_init(&sscape->fwlock); + sscape->io_res = io_res; + sscape->io_base = params->port; + + if (!detect_sscape(sscape)) { + printk(KERN_ERR "sscape: hardware not detected at 0x%x\n", sscape->io_base); + err = -ENODEV; + goto _release_card; + } + + printk(KERN_INFO "sscape: hardware detected at 0x%x, using IRQ %d, DMA %d\n", + sscape->io_base, params->irq, params->dma1); + + /* + * Now create the hardware-specific device so that we can + * load the microcode into the on-board processor. + * We cannot use the MPU-401 MIDI system until this firmware + * has been loaded into the card. + */ + if ((err = snd_hwdep_new(card, "MC68EC000", 0, &(sscape->hw))) < 0) { + printk(KERN_ERR "sscape: Failed to create firmware device\n"); + goto _release_card; + } + strlcpy(sscape->hw->name, "SoundScape M68K", sizeof(sscape->hw->name)); + sscape->hw->name[sizeof(sscape->hw->name) - 1] = '\0'; + sscape->hw->iface = SNDRV_HWDEP_IFACE_SSCAPE; + sscape->hw->ops.open = sscape_hw_open; + sscape->hw->ops.release = sscape_hw_release; + sscape->hw->ops.ioctl = sscape_hw_ioctl; + sscape->hw->private_data = sscape; + + /* + * Tell the on-board devices where their resources are (I think - + * I can't be sure without a datasheet ... So many magic values!) + */ + spin_lock_irqsave(&sscape->lock, flags); + + activate_ad1845_unsafe(sscape->io_base); + + sscape_write_unsafe(sscape->io_base, GA_INTENA_REG, 0x00); /* disable */ + sscape_write_unsafe(sscape->io_base, GA_SMCFGA_REG, 0x2e); + sscape_write_unsafe(sscape->io_base, GA_SMCFGB_REG, 0x00); + + /* + * Enable and configure the DMA channels ... + */ + sscape_write_unsafe(sscape->io_base, GA_DMACFG_REG, 0x50); + dma_cfg = (sscape->ic_type == IC_ODIE ? 0x70 : 0x40); + sscape_write_unsafe(sscape->io_base, GA_DMAA_REG, dma_cfg); + sscape_write_unsafe(sscape->io_base, GA_DMAB_REG, 0x20); + + sscape_write_unsafe(sscape->io_base, + GA_INTCFG_REG, 0xf0 | (mpu_irq_cfg << 2) | mpu_irq_cfg); + sscape_write_unsafe(sscape->io_base, + GA_CDCFG_REG, 0x09 | DMA_8BIT | (params->dma1 << 4) | (irq_cfg << 1)); + + spin_unlock_irqrestore(&sscape->lock, flags); + + /* + * We have now enabled the codec chip, and so we should + * detect the AD1845 device ... + */ + if ((err = create_ad1845(card, CODEC_IO(params->port), params->irq, params->dma1)) < 0) { + printk(KERN_ERR "sscape: No AD1845 device at 0x%x, IRQ %d\n", + CODEC_IO(params->port), params->irq); + goto _release_card; + } +#define MIDI_DEVNUM 0 + if ((err = create_mpu401(card, MIDI_DEVNUM, MPU401_IO(params->port), params->mpu_irq)) < 0) { + printk(KERN_ERR "sscape: Failed to create MPU-401 device at 0x%x\n", + MPU401_IO(params->port)); + goto _release_card; + } + + /* + * Enable the master IRQ ... + */ + sscape_write(sscape, GA_INTENA_REG, 0x80); + + if ((err = snd_card_register(card)) < 0) { + printk(KERN_ERR "sscape: Failed to register sound card\n"); + goto _release_card; + } + + /* + * Initialize mixer + */ + sscape->midi_vol = 0; + host_write_ctrl_unsafe(sscape->io_base, CMD_SET_MIDI_VOL, 100); + host_write_ctrl_unsafe(sscape->io_base, 0, 100); + host_write_ctrl_unsafe(sscape->io_base, CMD_XXX_MIDI_VOL, 100); + + /* + * Now that we have successfully created this sound card, + * it is safe to store the pointer. + * NOTE: we only register the sound card's "destructor" + * function now that our "constructor" has completed. + */ + card->private_free = soundscape_free; + *rcardp = card; + + return 0; + + _release_card: + snd_card_free(card); + + _release_dma: + free_dma(params->dma1); + + _release_region: + release_resource(io_res); + kfree_nocheck(io_res); + + return err; +} + + +static int sscape_cards __devinitdata; +static struct params sscape_params[SNDRV_CARDS] __devinitdata; + +#ifdef CONFIG_PNP +static inline int __devinit get_next_autoindex(int i) +{ + while ((i < SNDRV_CARDS) && (port[i] != SNDRV_AUTO_PORT)) { + ++i; + } /* while */ + + return i; +} + + +static inline int __devinit is_port_known(unsigned io, struct params *params, int cards) +{ + while (--cards >= 0) { + if (params[cards].port == io) + return 1; + } /* while */ + + return 0; +} + +static int __devinit sscape_pnp_detect(struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + struct pnp_dev *dev; + static int idx = 0; + int ret; + + /* + * Allow this function to fail *quietly* if all the ISA PnP + * devices were configured using module parameters instead. + */ + if ((idx = get_next_autoindex(idx)) >= SNDRV_CARDS) { + return -ENOSPC; + } + + /* + * We have found a candidate ISA PnP card. Now we + * have to check that it has the devices that we + * expect it to have. + * + * We will NOT try and autoconfigure all of the resources + * needed and then activate the card as we are assuming that + * has already been done at boot-time using /proc/isapnp. + * We shall simply try to give each active card the resources + * that it wants. This is a sensible strategy for a modular + * system where unused modules are unloaded regularly. + * + * This strategy is utterly useless if we compile the driver + * into the kernel, of course. + */ + // printk(KERN_INFO "sscape: %s\n", card->name); + + /* + * Check that we still have room for another sound card ... + */ + if (sscape_cards >= SNDRV_CARDS) { + printk(KERN_ERR "sscape: No room for another ALSA device\n"); + return -ENOSPC; + } + + ret = -ENODEV; + + dev = pnp_request_card_device(pcard, pid->devs[0].id, NULL); + if (dev) { + struct params *this; + if (!pnp_is_active(dev)) { + if (pnp_activate_dev(dev) < 0) { + printk(KERN_INFO "sscape: device is inactive\n"); + return -EBUSY; + } + } + /* + * Read the correct parameters off the ISA PnP bus ... + */ + this = init_params(&sscape_params[sscape_cards], + index[idx], + id[idx], + pnp_port_start(dev, 0), + pnp_irq(dev, 0), + pnp_irq(dev, 1), + pnp_dma(dev, 0)); + + /* + * Do we know about this sound card already? + */ + if ( !is_port_known(this->port, sscape_params, sscape_cards) ) { + snd_card_t *card; + + ret = create_sscape(this, &card); + if (ret < 0) + return ret; + snd_card_set_dev(card, &pcard->card->dev); + pnp_set_card_drvdata(pcard, card); + ++sscape_cards; + ++idx; + } + } + + return ret; +} + +static void __devexit sscape_pnp_remove(struct pnp_card_link * pcard) +{ + snd_card_t *card = (snd_card_t *) pnp_get_card_drvdata(pcard); + + pnp_set_card_drvdata(pcard, NULL); + snd_card_disconnect(card); + snd_card_free_in_thread(card); +} + +static struct pnp_card_driver sscape_pnpc_driver = { + .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, + .name = "sscape", + .id_table = sscape_pnpids, + .probe = sscape_pnp_detect, + .remove = __devexit_p(sscape_pnp_remove), +}; + +#endif /* CONFIG_PNP */ + +static int __init sscape_manual_probe(struct params *params) +{ + int ret; + unsigned i; + snd_card_t *card; + + for (i = 0; i < SNDRV_CARDS; ++i) { + /* + * We do NOT probe for ports. + * If we're not given a port number for this + * card then we completely ignore this line + * of parameters. + */ + if (port[i] == SNDRV_AUTO_PORT) + continue; + + /* + * Make sure we were given ALL of the other parameters. + */ + if ( (irq[i] == SNDRV_AUTO_IRQ) || + (mpu_irq[i] == SNDRV_AUTO_IRQ) || + (dma[i] == SNDRV_AUTO_DMA) ) { + printk(KERN_INFO + "sscape: insufficient parameters, need IO, IRQ, MPU-IRQ and DMA\n"); + return -ENXIO; + } + + /* + * This cards looks OK ... + */ + init_params(params, index[i], id[i], port[i], irq[i], mpu_irq[i], dma[i]); + + ret = create_sscape(params, &card); + if (ret < 0) + return ret; + + sscape_card[sscape_cards] = card; + params++; + sscape_cards++; + } /* for */ + + return 0; +} + + +static void sscape_exit(void) +{ + unsigned i; + +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&sscape_pnpc_driver); +#endif + for (i = 0; i < ARRAY_SIZE(sscape_card); ++i) { + snd_card_free(sscape_card[i]); + } /* for */ +} + + +static int __init sscape_init(void) +{ + int ret; + + /* + * First check whether we were passed any parameters. + * These MUST take precedence over ANY automatic way + * of allocating cards, because the operator is + * S-P-E-L-L-I-N-G it out for us... + */ + ret = sscape_manual_probe(sscape_params); + if (ret < 0) { + int i; + for (i = 0; i < sscape_cards; ++i) + snd_card_free(sscape_card[i]); + return ret; + } + +#ifdef CONFIG_PNP + if (sscape_cards < SNDRV_CARDS) { + ret = pnp_register_card_driver(&sscape_pnpc_driver); + if (ret < 0) { + sscape_exit(); + return ret; + } + } +#endif + + return 0; +} + +module_init(sscape_init); +module_exit(sscape_exit); diff --git a/sound/isa/wavefront/Makefile b/sound/isa/wavefront/Makefile new file mode 100644 index 00000000000..b4cb28422db --- /dev/null +++ b/sound/isa/wavefront/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for ALSA +# Copyright (c) 2001 by Jaroslav Kysela <perex@suse.cz> +# + +snd-wavefront-objs := wavefront.o wavefront_fx.o wavefront_synth.o wavefront_midi.o + +# Toplevel Module Dependency +obj-$(CONFIG_SND_WAVEFRONT) += snd-wavefront.o diff --git a/sound/isa/wavefront/wavefront.c b/sound/isa/wavefront/wavefront.c new file mode 100644 index 00000000000..79b022070ba --- /dev/null +++ b/sound/isa/wavefront/wavefront.c @@ -0,0 +1,716 @@ +/* + * ALSA card-level driver for Turtle Beach Wavefront cards + * (Maui,Tropez,Tropez+) + * + * Copyright (c) 1997-1999 by Paul Barton-Davis <pbd@op.net> + * + * 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/interrupt.h> +#include <linux/slab.h> +#include <linux/pnp.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/opl3.h> +#include <sound/snd_wavefront.h> + +MODULE_AUTHOR("Paul Barton-Davis <pbd@op.net>"); +MODULE_DESCRIPTION("Turtle Beach Wavefront"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Turtle Beach,Maui/Tropez/Tropez+}}"); + +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 int isapnp[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1}; +static long cs4232_pcm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int cs4232_pcm_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 5,7,9,11,12,15 */ +static long cs4232_mpu_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int cs4232_mpu_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 9,11,12,15 */ +static long ics2115_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +static int ics2115_irq[SNDRV_CARDS] = SNDRV_DEFAULT_IRQ; /* 2,9,11,12,15 */ +static long fm_port[SNDRV_CARDS] = SNDRV_DEFAULT_PORT; /* PnP setup */ +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 */ +static int use_cs4232_midi[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 0}; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for WaveFront soundcard."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for WaveFront soundcard."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable WaveFront soundcard."); +#ifdef CONFIG_PNP +module_param_array(isapnp, bool, NULL, 0444); +MODULE_PARM_DESC(isapnp, "ISA PnP detection for WaveFront soundcards."); +#endif +module_param_array(cs4232_pcm_port, long, NULL, 0444); +MODULE_PARM_DESC(cs4232_pcm_port, "Port # for CS4232 PCM interface."); +module_param_array(cs4232_pcm_irq, int, NULL, 0444); +MODULE_PARM_DESC(cs4232_pcm_irq, "IRQ # for CS4232 PCM interface."); +module_param_array(dma1, int, NULL, 0444); +MODULE_PARM_DESC(dma1, "DMA1 # for CS4232 PCM interface."); +module_param_array(dma2, int, NULL, 0444); +MODULE_PARM_DESC(dma2, "DMA2 # for CS4232 PCM interface."); +module_param_array(cs4232_mpu_port, long, NULL, 0444); +MODULE_PARM_DESC(cs4232_mpu_port, "port # for CS4232 MPU-401 interface."); +module_param_array(cs4232_mpu_irq, int, NULL, 0444); +MODULE_PARM_DESC(cs4232_mpu_irq, "IRQ # for CS4232 MPU-401 interface."); +module_param_array(ics2115_irq, int, NULL, 0444); +MODULE_PARM_DESC(ics2115_irq, "IRQ # for ICS2115."); +module_param_array(ics2115_port, long, NULL, 0444); +MODULE_PARM_DESC(ics2115_port, "Port # for ICS2115."); +module_param_array(fm_port, long, NULL, 0444); +MODULE_PARM_DESC(fm_port, "FM port #."); +module_param_array(use_cs4232_midi, bool, NULL, 0444); +MODULE_PARM_DESC(use_cs4232_midi, "Use CS4232 MPU-401 interface (inaccessibly located inside your computer)"); + +static snd_card_t *snd_wavefront_legacy[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +#ifdef CONFIG_PNP + +static struct pnp_card_device_id snd_wavefront_pnpids[] = { + /* Tropez */ + { .id = "CSC7532", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } }, + /* Tropez+ */ + { .id = "CSC7632", .devs = { { "CSC0000" }, { "CSC0010" }, { "PnPb006" }, { "CSC0004" } } }, + { .id = "" } +}; + +MODULE_DEVICE_TABLE(pnp_card, snd_wavefront_pnpids); + +static int __devinit +snd_wavefront_pnp (int dev, snd_wavefront_card_t *acard, struct pnp_card_link *card, + const struct pnp_card_device_id *id) +{ + struct pnp_dev *pdev; + struct pnp_resource_table *cfg = kmalloc(sizeof(*cfg), GFP_KERNEL); + int err; + + if (!cfg) + return -ENOMEM; + + /* Check for each logical device. */ + + /* CS4232 chip (aka "windows sound system") is logical device 0 */ + + acard->wss = pnp_request_card_device(card, id->devs[0].id, NULL); + if (acard->wss == NULL) { + kfree(cfg); + return -EBUSY; + } + + /* there is a game port at logical device 1, but we ignore it completely */ + + /* the control interface is logical device 2, but we ignore it + completely. in fact, nobody even seems to know what it + does. + */ + + /* Only configure the CS4232 MIDI interface if its been + specifically requested. It is logical device 3. + */ + + if (use_cs4232_midi[dev]) { + acard->mpu = pnp_request_card_device(card, id->devs[2].id, NULL); + if (acard->mpu == NULL) { + kfree(cfg); + return -EBUSY; + } + } + + /* The ICS2115 synth is logical device 4 */ + + acard->synth = pnp_request_card_device(card, id->devs[3].id, NULL); + if (acard->synth == NULL) { + kfree(cfg); + return -EBUSY; + } + + /* PCM/FM initialization */ + + pdev = acard->wss; + + pnp_init_resource_table(cfg); + + /* An interesting note from the Tropez+ FAQ: + + Q. [Ports] Why is the base address of the WSS I/O ports off by 4? + + A. WSS I/O requires a block of 8 I/O addresses ("ports"). Of these, the first + 4 are used to identify and configure the board. With the advent of PnP, + these first 4 addresses have become obsolete, and software applications + only use the last 4 addresses to control the codec chip. Therefore, the + base address setting "skips past" the 4 unused addresses. + + */ + + if (cs4232_pcm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], cs4232_pcm_port[dev], 4); + if (fm_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[1], fm_port[dev], 4); + 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], 1); + if (cs4232_pcm_irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->irq_resource[0], cs4232_pcm_irq[dev], 1); + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "PnP WSS the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP WSS pnp configure failure\n"); + kfree(cfg); + return err; + } + + cs4232_pcm_port[dev] = pnp_port_start(pdev, 0); + fm_port[dev] = pnp_port_start(pdev, 1); + dma1[dev] = pnp_dma(pdev, 0); + dma2[dev] = pnp_dma(pdev, 1); + cs4232_pcm_irq[dev] = pnp_irq(pdev, 0); + + /* Synth initialization */ + + pdev = acard->synth; + + pnp_init_resource_table(cfg); + + if (ics2115_port[dev] != SNDRV_AUTO_PORT) { + pnp_resource_change(&cfg->port_resource[0], ics2115_port[dev], 16); + } + + if (ics2115_port[dev] != SNDRV_AUTO_IRQ) { + pnp_resource_change(&cfg->irq_resource[0], ics2115_irq[dev], 1); + } + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "PnP ICS2115 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP ICS2115 pnp configure failure\n"); + kfree(cfg); + return err; + } + + ics2115_port[dev] = pnp_port_start(pdev, 0); + ics2115_irq[dev] = pnp_irq(pdev, 0); + + /* CS4232 MPU initialization. Configure this only if + explicitly requested, since its physically inaccessible and + consumes another IRQ. + */ + + if (use_cs4232_midi[dev]) { + + pdev = acard->mpu; + + pnp_init_resource_table(cfg); + + if (cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) + pnp_resource_change(&cfg->port_resource[0], cs4232_mpu_port[dev], 2); + if (cs4232_mpu_irq[dev] != SNDRV_AUTO_IRQ) + pnp_resource_change(&cfg->port_resource[0], cs4232_mpu_irq[dev], 1); + + if (pnp_manual_config_dev(pdev, cfg, 0) < 0) + snd_printk(KERN_ERR "PnP MPU401 the requested resources are invalid, using auto config\n"); + err = pnp_activate_dev(pdev); + if (err < 0) { + snd_printk(KERN_ERR "PnP MPU401 pnp configure failure\n"); + cs4232_mpu_port[dev] = SNDRV_AUTO_PORT; + } else { + cs4232_mpu_port[dev] = pnp_port_start(pdev, 0); + cs4232_mpu_irq[dev] = pnp_irq(pdev, 0); + } + + snd_printk ("CS4232 MPU: port=0x%lx, irq=%i\n", + cs4232_mpu_port[dev], + cs4232_mpu_irq[dev]); + } + + snd_printdd ("CS4232: pcm port=0x%lx, fm port=0x%lx, dma1=%i, dma2=%i, irq=%i\nICS2115: port=0x%lx, irq=%i\n", + cs4232_pcm_port[dev], + fm_port[dev], + dma1[dev], + dma2[dev], + cs4232_pcm_irq[dev], + ics2115_port[dev], + ics2115_irq[dev]); + + kfree(cfg); + return 0; +} + +#endif /* CONFIG_PNP */ + +static irqreturn_t snd_wavefront_ics2115_interrupt(int irq, + void *dev_id, + struct pt_regs *regs) +{ + snd_wavefront_card_t *acard; + + acard = (snd_wavefront_card_t *) dev_id; + + if (acard == NULL) + return IRQ_NONE; + + if (acard->wavefront.interrupts_are_midi) { + snd_wavefront_midi_interrupt (acard); + } else { + snd_wavefront_internal_interrupt (acard); + } + return IRQ_HANDLED; +} + +static snd_hwdep_t * __devinit +snd_wavefront_new_synth (snd_card_t *card, + int hw_dev, + snd_wavefront_card_t *acard) +{ + snd_hwdep_t *wavefront_synth; + + if (snd_wavefront_detect (acard) < 0) { + return NULL; + } + + if (snd_wavefront_start (&acard->wavefront) < 0) { + return NULL; + } + + if (snd_hwdep_new(card, "WaveFront", hw_dev, &wavefront_synth) < 0) + return NULL; + strcpy (wavefront_synth->name, + "WaveFront (ICS2115) wavetable synthesizer"); + wavefront_synth->ops.open = snd_wavefront_synth_open; + wavefront_synth->ops.release = snd_wavefront_synth_release; + wavefront_synth->ops.ioctl = snd_wavefront_synth_ioctl; + + return wavefront_synth; +} + +static snd_hwdep_t * __devinit +snd_wavefront_new_fx (snd_card_t *card, + int hw_dev, + snd_wavefront_card_t *acard, + unsigned long port) + +{ + snd_hwdep_t *fx_processor; + + if (snd_wavefront_fx_start (&acard->wavefront)) { + snd_printk ("cannot initialize YSS225 FX processor"); + return NULL; + } + + if (snd_hwdep_new (card, "YSS225", hw_dev, &fx_processor) < 0) + return NULL; + sprintf (fx_processor->name, "YSS225 FX Processor at 0x%lx", port); + fx_processor->ops.open = snd_wavefront_fx_open; + fx_processor->ops.release = snd_wavefront_fx_release; + fx_processor->ops.ioctl = snd_wavefront_fx_ioctl; + + return fx_processor; +} + +static snd_wavefront_mpu_id internal_id = internal_mpu; +static snd_wavefront_mpu_id external_id = external_mpu; + +static snd_rawmidi_t * __devinit +snd_wavefront_new_midi (snd_card_t *card, + int midi_dev, + snd_wavefront_card_t *acard, + unsigned long port, + snd_wavefront_mpu_id mpu) + +{ + snd_rawmidi_t *rmidi; + static int first = 1; + + if (first) { + first = 0; + acard->wavefront.midi.base = port; + if (snd_wavefront_midi_start (acard)) { + snd_printk ("cannot initialize MIDI interface\n"); + return NULL; + } + } + + if (snd_rawmidi_new (card, "WaveFront MIDI", midi_dev, 1, 1, &rmidi) < 0) + return NULL; + + if (mpu == internal_mpu) { + strcpy(rmidi->name, "WaveFront MIDI (Internal)"); + rmidi->private_data = &internal_id; + } else { + strcpy(rmidi->name, "WaveFront MIDI (External)"); + rmidi->private_data = &external_id; + } + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &snd_wavefront_midi_output); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &snd_wavefront_midi_input); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + return rmidi; +} + +static void +snd_wavefront_free(snd_card_t *card) +{ + snd_wavefront_card_t *acard = (snd_wavefront_card_t *)card->private_data; + + if (acard) { + if (acard->wavefront.res_base != NULL) { + release_resource(acard->wavefront.res_base); + kfree_nocheck(acard->wavefront.res_base); + } + if (acard->wavefront.irq > 0) + free_irq(acard->wavefront.irq, (void *)acard); + } +} + +static int __devinit +snd_wavefront_probe (int dev, struct pnp_card_link *pcard, + const struct pnp_card_device_id *pid) +{ + snd_card_t *card; + snd_wavefront_card_t *acard; + cs4231_t *chip; + snd_hwdep_t *wavefront_synth; + snd_rawmidi_t *ics2115_internal_rmidi = NULL; + snd_rawmidi_t *ics2115_external_rmidi = NULL; + snd_hwdep_t *fx_processor; + int hw_dev = 0, midi_dev = 0, err; + +#ifdef CONFIG_PNP + if (!isapnp[dev]) { +#endif + if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify CS4232 port\n"); + return -EINVAL; + } + if (ics2115_port[dev] == SNDRV_AUTO_PORT) { + snd_printk("specify ICS2115 port\n"); + return -ENODEV; + } +#ifdef CONFIG_PNP + } +#endif + card = snd_card_new (index[dev], + id[dev], + THIS_MODULE, + sizeof(snd_wavefront_card_t)); + + if (card == NULL) { + return -ENOMEM; + } + acard = (snd_wavefront_card_t *)card->private_data; + acard->wavefront.irq = -1; + spin_lock_init(&acard->wavefront.irq_lock); + init_waitqueue_head(&acard->wavefront.interrupt_sleeper); + spin_lock_init(&acard->wavefront.midi.open); + spin_lock_init(&acard->wavefront.midi.virtual); + card->private_free = snd_wavefront_free; + +#ifdef CONFIG_PNP + if (isapnp[dev]) { + if (snd_wavefront_pnp (dev, acard, pcard, pid) < 0) { + if (cs4232_pcm_port[dev] == SNDRV_AUTO_PORT) { + snd_printk ("isapnp detection failed\n"); + snd_card_free (card); + return -ENODEV; + } + } + snd_card_set_dev(card, &pcard->card->dev); + } +#endif /* CONFIG_PNP */ + + /* --------- PCM --------------- */ + + if ((err = snd_cs4231_create (card, + cs4232_pcm_port[dev], + -1, + cs4232_pcm_irq[dev], + dma1[dev], + dma2[dev], + CS4231_HW_DETECT, 0, &chip)) < 0) { + snd_card_free(card); + snd_printk ("can't allocate CS4231 device\n"); + return err; + } + + if ((err = snd_cs4231_pcm (chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + if ((err = snd_cs4231_timer (chip, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + + /* ---------- OPL3 synth --------- */ + + if (fm_port[dev] > 0 && fm_port[dev] != SNDRV_AUTO_PORT) { + opl3_t *opl3; + + if ((err = snd_opl3_create(card, + fm_port[dev], + fm_port[dev] + 2, + OPL3_HW_OPL3_CS, + 0, &opl3)) < 0) { + snd_printk ("can't allocate or detect OPL3 synth\n"); + snd_card_free(card); + return err; + } + + if ((err = snd_opl3_hwdep_new(opl3, hw_dev, 1, NULL)) < 0) { + snd_card_free(card); + return err; + } + hw_dev++; + } + + /* ------- ICS2115 Wavetable synth ------- */ + + if ((acard->wavefront.res_base = request_region(ics2115_port[dev], 16, "ICS2115")) == NULL) { + snd_printk("unable to grab ICS2115 i/o region 0x%lx-0x%lx\n", ics2115_port[dev], ics2115_port[dev] + 16 - 1); + snd_card_free(card); + return -EBUSY; + } + if (request_irq(ics2115_irq[dev], snd_wavefront_ics2115_interrupt, SA_INTERRUPT, "ICS2115", (void *)acard)) { + snd_printk("unable to use ICS2115 IRQ %d\n", ics2115_irq[dev]); + snd_card_free(card); + return -EBUSY; + } + + acard->wavefront.irq = ics2115_irq[dev]; + acard->wavefront.base = ics2115_port[dev]; + + if ((wavefront_synth = snd_wavefront_new_synth (card, hw_dev, acard)) == NULL) { + snd_printk ("can't create WaveFront synth device\n"); + snd_card_free(card); + return -ENOMEM; + } + + strcpy (wavefront_synth->name, "ICS2115 Wavetable MIDI Synthesizer"); + wavefront_synth->iface = SNDRV_HWDEP_IFACE_ICS2115; + hw_dev++; + + /* --------- Mixer ------------ */ + + if ((err = snd_cs4231_mixer(chip)) < 0) { + snd_printk ("can't allocate mixer device\n"); + snd_card_free(card); + return err; + } + + /* -------- CS4232 MPU-401 interface -------- */ + + if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) { + if ((err = snd_mpu401_uart_new(card, midi_dev, MPU401_HW_CS4232, + cs4232_mpu_port[dev], 0, + cs4232_mpu_irq[dev], + SA_INTERRUPT, + NULL)) < 0) { + snd_printk ("can't allocate CS4232 MPU-401 device\n"); + snd_card_free(card); + return err; + } + midi_dev++; + } + + /* ------ ICS2115 internal MIDI ------------ */ + + if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) { + ics2115_internal_rmidi = + snd_wavefront_new_midi (card, + midi_dev, + acard, + ics2115_port[dev], + internal_mpu); + if (ics2115_internal_rmidi == NULL) { + snd_printk ("can't setup ICS2115 internal MIDI device\n"); + snd_card_free(card); + return -ENOMEM; + } + midi_dev++; + } + + /* ------ ICS2115 external MIDI ------------ */ + + if (ics2115_port[dev] > 0 && ics2115_port[dev] != SNDRV_AUTO_PORT) { + ics2115_external_rmidi = + snd_wavefront_new_midi (card, + midi_dev, + acard, + ics2115_port[dev], + external_mpu); + if (ics2115_external_rmidi == NULL) { + snd_printk ("can't setup ICS2115 external MIDI device\n"); + snd_card_free(card); + return -ENOMEM; + } + midi_dev++; + } + + /* FX processor for Tropez+ */ + + if (acard->wavefront.has_fx) { + fx_processor = snd_wavefront_new_fx (card, + hw_dev, + acard, + ics2115_port[dev]); + if (fx_processor == NULL) { + snd_printk ("can't setup FX device\n"); + snd_card_free(card); + return -ENOMEM; + } + + hw_dev++; + + strcpy(card->driver, "Tropez+"); + strcpy(card->shortname, "Turtle Beach Tropez+"); + } else { + /* Need a way to distinguish between Maui and Tropez */ + strcpy(card->driver, "WaveFront"); + strcpy(card->shortname, "Turtle Beach WaveFront"); + } + + /* ----- Register the card --------- */ + + /* Not safe to include "Turtle Beach" in longname, due to + length restrictions + */ + + sprintf(card->longname, "%s PCM 0x%lx irq %d dma %d", + card->driver, + chip->port, + cs4232_pcm_irq[dev], + dma1[dev]); + + if (dma2[dev] >= 0 && dma2[dev] < 8) + sprintf(card->longname + strlen(card->longname), "&%d", dma2[dev]); + + if (cs4232_mpu_port[dev] > 0 && cs4232_mpu_port[dev] != SNDRV_AUTO_PORT) { + sprintf (card->longname + strlen (card->longname), + " MPU-401 0x%lx irq %d", + cs4232_mpu_port[dev], + cs4232_mpu_irq[dev]); + } + + sprintf (card->longname + strlen (card->longname), + " SYNTH 0x%lx irq %d", + ics2115_port[dev], + ics2115_irq[dev]); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + if (pcard) + pnp_set_card_drvdata(pcard, card); + else + snd_wavefront_legacy[dev] = card; + return 0; +} + +#ifdef CONFIG_PNP + +static int __devinit snd_wavefront_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_wavefront_probe(dev, card, id); + if (res < 0) + return res; + dev++; + return 0; + } + + return -ENODEV; +} + +static void __devexit snd_wavefront_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 wavefront_pnpc_driver = { + .flags = PNP_DRIVER_RES_DISABLE, + .name = "wavefront", + .id_table = snd_wavefront_pnpids, + .probe = snd_wavefront_pnp_detect, + .remove = __devexit_p(snd_wavefront_pnp_remove), +}; + +#endif /* CONFIG_PNP */ + +static int __init alsa_card_wavefront_init(void) +{ + int cards = 0; + int dev; + for (dev = 0; dev < SNDRV_CARDS; dev++) { + if (!enable[dev]) + continue; +#ifdef CONFIG_PNP + if (isapnp[dev]) + continue; +#endif + if (snd_wavefront_probe(dev, NULL, NULL) >= 0) + cards++; + } +#ifdef CONFIG_PNP + cards += pnp_register_card_driver(&wavefront_pnpc_driver); +#endif + if (!cards) { +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&wavefront_pnpc_driver); +#endif +#ifdef MODULE + printk (KERN_ERR "No WaveFront cards found or devices busy\n"); +#endif + return -ENODEV; + } + return 0; +} + +static void __exit alsa_card_wavefront_exit(void) +{ + int idx; + +#ifdef CONFIG_PNP + pnp_unregister_card_driver(&wavefront_pnpc_driver); +#endif + for (idx = 0; idx < SNDRV_CARDS; idx++) + snd_card_free(snd_wavefront_legacy[idx]); +} + +module_init(alsa_card_wavefront_init) +module_exit(alsa_card_wavefront_exit) diff --git a/sound/isa/wavefront/wavefront_fx.c b/sound/isa/wavefront/wavefront_fx.c new file mode 100644 index 00000000000..0e13623f69f --- /dev/null +++ b/sound/isa/wavefront/wavefront_fx.c @@ -0,0 +1,1019 @@ +/* + * Copyright (c) 1998-2002 by Paul Davis <pbd@op.net> + * + * 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 <asm/io.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/snd_wavefront.h> +#include <sound/initval.h> + +/* Control bits for the Load Control Register + */ + +#define FX_LSB_TRANSFER 0x01 /* transfer after DSP LSB byte written */ +#define FX_MSB_TRANSFER 0x02 /* transfer after DSP MSB byte written */ +#define FX_AUTO_INCR 0x04 /* auto-increment DSP address after transfer */ + +/* weird stuff, derived from port I/O tracing with dosemu */ + +unsigned char page_zero[] __initdata = { +0x01, 0x7c, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, 0x00, +0x11, 0x00, 0x20, 0x00, 0x32, 0x00, 0x40, 0x00, 0x13, 0x00, 0x00, +0x00, 0x14, 0x02, 0x76, 0x00, 0x60, 0x00, 0x80, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x19, +0x01, 0x1a, 0x01, 0x20, 0x01, 0x40, 0x01, 0x17, 0x00, 0x00, 0x01, +0x80, 0x01, 0x20, 0x00, 0x10, 0x01, 0xa0, 0x03, 0xd1, 0x00, 0x00, +0x01, 0xf2, 0x02, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0xf4, 0x02, +0xe0, 0x00, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x17, +0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x00, 0x50, 0x00, 0x00, 0x00, +0x40, 0x00, 0x00, 0x00, 0x71, 0x02, 0x00, 0x00, 0x60, 0x00, 0x00, +0x00, 0x92, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0xb3, 0x02, +0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x40, +0x00, 0x80, 0x00, 0xf5, 0x00, 0x20, 0x00, 0x70, 0x00, 0xa0, 0x02, +0x11, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, +0x02, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x17, 0x00, 0x1b, 0x00, +0x1d, 0x02, 0xdf +}; + +unsigned char page_one[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x19, 0x00, +0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xd8, 0x00, 0x00, +0x02, 0x20, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x01, +0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x02, 0x60, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x80, 0x00, +0x00, 0x02, 0xfb, 0x02, 0xa0, 0x00, 0x00, 0x00, 0x1b, 0x02, 0xd7, +0x00, 0x00, 0x02, 0xf7, 0x03, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, +0x1c, 0x03, 0x3c, 0x00, 0x00, 0x03, 0x3f, 0x00, 0x00, 0x03, 0xc0, +0x00, 0x00, 0x03, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x03, 0x5d, 0x00, +0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, 0x7d, 0x00, 0x00, 0x03, 0xc0, +0x00, 0x00, 0x03, 0x9e, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x03, +0xbe, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, +0xdb, 0x00, 0x00, 0x02, 0xdb, 0x00, 0x00, 0x02, 0xe0, 0x00, 0x00, +0x02, 0xfb, 0x00, 0x00, 0x02, 0xc0, 0x02, 0x40, 0x02, 0xfb, 0x02, +0x60, 0x00, 0x1b +}; + +unsigned char page_two[] __initdata = { +0xc4, 0x00, 0x44, 0x07, 0x44, 0x00, 0x40, 0x25, 0x01, 0x06, 0xc4, +0x07, 0x40, 0x25, 0x01, 0x00, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x07, +0x05, 0x05, 0x05, 0x04, 0x07, 0x05, 0x04, 0x07, 0x05, 0x44, 0x46, +0x44, 0x46, 0x46, 0x07, 0x05, 0x44, 0x46, 0x05, 0x46, 0x05, 0x46, +0x05, 0x46, 0x05, 0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07, +0x44, 0x46, 0x05, 0x07, 0x44, 0x46, 0x05, 0x07, 0x44, 0x05, 0x05, +0x05, 0x44, 0x05, 0x05, 0x05, 0x46, 0x05, 0x46, 0x05, 0x46, 0x05, +0x46, 0x05, 0x46, 0x07, 0x46, 0x07, 0x44 +}; + +unsigned char page_three[] __initdata = { +0x07, 0x40, 0x00, 0x00, 0x00, 0x47, 0x00, 0x40, 0x00, 0x40, 0x06, +0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, +0xc0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x00, +0x60, 0x00, 0x70, 0x00, 0x40, 0x00, 0x40, 0x00, 0x42, 0x00, 0x40, +0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, +0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, +0x00, 0x42, 0x00, 0x40, 0x00, 0x42, 0x00, 0x02, 0x00, 0x02, 0x00, +0x02, 0x00, 0x42, 0x00, 0xc0, 0x00, 0x40 +}; + +unsigned char page_four[] __initdata = { +0x63, 0x03, 0x26, 0x02, 0x2c, 0x00, 0x24, 0x00, 0x2e, 0x02, 0x02, +0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, +0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, +0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x60, 0x00, +0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, +0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, 0x60, 0x00, 0x20, 0x00, +0x20, 0x00, 0x22, 0x02, 0x22, 0x02, 0x20, 0x00, 0x60, 0x00, 0x22, +0x02, 0x62, 0x02, 0x20, 0x01, 0x21, 0x01 +}; + +unsigned char page_six[] __initdata = { +0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x00, 0x00, 0x06, 0x00, +0x00, 0x08, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x0e, +0x00, 0x00, 0x10, 0x00, 0x00, 0x12, 0x00, 0x00, 0x14, 0x00, 0x00, +0x16, 0x00, 0x00, 0x18, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x1c, 0x00, +0x00, 0x1e, 0x00, 0x00, 0x20, 0x00, 0x00, 0x22, 0x00, 0x00, 0x24, +0x00, 0x00, 0x26, 0x00, 0x00, 0x28, 0x00, 0x00, 0x2a, 0x00, 0x00, +0x2c, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x30, 0x00, 0x00, 0x32, 0x00, +0x00, 0x34, 0x00, 0x00, 0x36, 0x00, 0x00, 0x38, 0x00, 0x00, 0x3a, +0x00, 0x00, 0x3c, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x40, 0x00, 0x00, +0x42, 0x03, 0x00, 0x44, 0x01, 0x00, 0x46, 0x0a, 0x21, 0x48, 0x0d, +0x23, 0x4a, 0x23, 0x1b, 0x4c, 0x37, 0x8f, 0x4e, 0x45, 0x77, 0x50, +0x52, 0xe2, 0x52, 0x1c, 0x92, 0x54, 0x1c, 0x52, 0x56, 0x07, 0x00, +0x58, 0x2f, 0xc6, 0x5a, 0x0b, 0x00, 0x5c, 0x30, 0x06, 0x5e, 0x17, +0x00, 0x60, 0x3d, 0xda, 0x62, 0x29, 0x00, 0x64, 0x3e, 0x41, 0x66, +0x39, 0x00, 0x68, 0x4c, 0x48, 0x6a, 0x49, 0x00, 0x6c, 0x4c, 0x6c, +0x6e, 0x11, 0xd2, 0x70, 0x16, 0x0c, 0x72, 0x00, 0x00, 0x74, 0x00, +0x80, 0x76, 0x0f, 0x00, 0x78, 0x00, 0x80, 0x7a, 0x13, 0x00, 0x7c, +0x80, 0x00, 0x7e, 0x80, 0x80 +}; + +unsigned char page_seven[] __initdata = { +0x0f, 0xff, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, +0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, +0x08, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0f, +0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, +0x0f, 0xff, 0x0f, 0xff, 0x02, 0xe9, 0x06, 0x8c, 0x06, 0x8c, 0x0f, +0xff, 0x1a, 0x75, 0x0d, 0x8b, 0x04, 0xe9, 0x0b, 0x16, 0x1a, 0x38, +0x0d, 0xc8, 0x04, 0x6f, 0x0b, 0x91, 0x0f, 0xff, 0x06, 0x40, 0x06, +0x40, 0x02, 0x8f, 0x0f, 0xff, 0x06, 0x62, 0x06, 0x62, 0x02, 0x7b, +0x0f, 0xff, 0x06, 0x97, 0x06, 0x97, 0x02, 0x52, 0x0f, 0xff, 0x06, +0xf6, 0x06, 0xf6, 0x02, 0x19, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, +0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x05, 0x55, 0x14, +0xda, 0x0d, 0x93, 0x04, 0xda, 0x05, 0x93, 0x14, 0xda, 0x0d, 0x93, +0x04, 0xda, 0x05, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x02, 0x00 +}; + +unsigned char page_zero_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_one_v2[] __initdata = { +0x01, 0xc0, 0x01, 0xfa, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_two_v2[] __initdata = { +0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; +unsigned char page_three_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; +unsigned char page_four_v2[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00 +}; + +unsigned char page_seven_v2[] __initdata = { +0x0f, 0xff, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +unsigned char mod_v2[] __initdata = { +0x01, 0x00, 0x02, 0x00, 0x01, 0x01, 0x02, 0x00, 0x01, 0x02, 0x02, +0x00, 0x01, 0x03, 0x02, 0x00, 0x01, 0x04, 0x02, 0x00, 0x01, 0x05, +0x02, 0x00, 0x01, 0x06, 0x02, 0x00, 0x01, 0x07, 0x02, 0x00, 0xb0, +0x20, 0xb1, 0x20, 0xb2, 0x20, 0xb3, 0x20, 0xb4, 0x20, 0xb5, 0x20, +0xb6, 0x20, 0xb7, 0x20, 0xf0, 0x20, 0xf1, 0x20, 0xf2, 0x20, 0xf3, +0x20, 0xf4, 0x20, 0xf5, 0x20, 0xf6, 0x20, 0xf7, 0x20, 0x10, 0xff, +0x11, 0xff, 0x12, 0xff, 0x13, 0xff, 0x14, 0xff, 0x15, 0xff, 0x16, +0xff, 0x17, 0xff, 0x20, 0xff, 0x21, 0xff, 0x22, 0xff, 0x23, 0xff, +0x24, 0xff, 0x25, 0xff, 0x26, 0xff, 0x27, 0xff, 0x30, 0x00, 0x31, +0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, +0x37, 0x00, 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x44, +0x00, 0x45, 0x00, 0x46, 0x00, 0x47, 0x00, 0x50, 0x00, 0x51, 0x00, +0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57, +0x00, 0x60, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x64, 0x00, +0x65, 0x00, 0x66, 0x00, 0x67, 0x00, 0x70, 0xc0, 0x71, 0xc0, 0x72, +0xc0, 0x73, 0xc0, 0x74, 0xc0, 0x75, 0xc0, 0x76, 0xc0, 0x77, 0xc0, +0x80, 0x00, 0x81, 0x00, 0x82, 0x00, 0x83, 0x00, 0x84, 0x00, 0x85, +0x00, 0x86, 0x00, 0x87, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92, 0x00, +0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96, 0x00, 0x97, 0x00, 0xa0, +0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa3, 0x00, 0xa4, 0x00, 0xa5, 0x00, +0xa6, 0x00, 0xa7, 0x00, 0xc0, 0x00, 0xc1, 0x00, 0xc2, 0x00, 0xc3, +0x00, 0xc4, 0x00, 0xc5, 0x00, 0xc6, 0x00, 0xc7, 0x00, 0xd0, 0x00, +0xd1, 0x00, 0xd2, 0x00, 0xd3, 0x00, 0xd4, 0x00, 0xd5, 0x00, 0xd6, +0x00, 0xd7, 0x00, 0xe0, 0x00, 0xe1, 0x00, 0xe2, 0x00, 0xe3, 0x00, +0xe4, 0x00, 0xe5, 0x00, 0xe6, 0x00, 0xe7, 0x00, 0x01, 0x00, 0x02, +0x01, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x02, 0x01, 0x01, 0x03, +0x02, 0x01, 0x01, 0x04, 0x02, 0x01, 0x01, 0x05, 0x02, 0x01, 0x01, +0x06, 0x02, 0x01, 0x01, 0x07, 0x02, 0x01 +}; +unsigned char coefficients[] __initdata = { +0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x00, 0x4b, 0x03, +0x11, 0x00, 0x4d, 0x01, 0x32, 0x07, 0x46, 0x00, 0x00, 0x07, 0x49, +0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x01, +0x40, 0x02, 0x40, 0x01, 0x41, 0x02, 0x60, 0x07, 0x40, 0x00, 0x00, +0x07, 0x41, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00, 0x07, 0x4a, 0x00, +0x00, 0x00, 0x47, 0x01, 0x00, 0x00, 0x4a, 0x01, 0x20, 0x07, 0x47, +0x00, 0x00, 0x07, 0x4a, 0x00, 0x00, 0x07, 0x7c, 0x00, 0x00, 0x07, +0x7e, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1c, 0x07, 0x7c, 0x00, 0x00, +0x07, 0x7e, 0x00, 0x00, 0x07, 0x44, 0x00, 0x00, 0x00, 0x44, 0x01, +0x00, 0x07, 0x44, 0x00, 0x00, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43, +0x00, 0x00, 0x00, 0x42, 0x01, 0x1a, 0x00, 0x43, 0x01, 0x20, 0x07, +0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, +0x07, 0x41, 0x00, 0x00, 0x01, 0x40, 0x02, 0x40, 0x01, 0x41, 0x02, +0x60, 0x07, 0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x44, +0x0f, 0xff, 0x07, 0x42, 0x00, 0x00, 0x07, 0x43, 0x00, 0x00, 0x07, +0x40, 0x00, 0x00, 0x07, 0x41, 0x00, 0x00, 0x07, 0x51, 0x06, 0x40, +0x07, 0x50, 0x06, 0x40, 0x07, 0x4f, 0x03, 0x81, 0x07, 0x53, 0x1a, +0x76, 0x07, 0x54, 0x0d, 0x8b, 0x07, 0x55, 0x04, 0xe9, 0x07, 0x56, +0x0b, 0x17, 0x07, 0x57, 0x1a, 0x38, 0x07, 0x58, 0x0d, 0xc9, 0x07, +0x59, 0x04, 0x6f, 0x07, 0x5a, 0x0b, 0x91, 0x07, 0x73, 0x14, 0xda, +0x07, 0x74, 0x0d, 0x93, 0x07, 0x75, 0x04, 0xd9, 0x07, 0x76, 0x05, +0x93, 0x07, 0x77, 0x14, 0xda, 0x07, 0x78, 0x0d, 0x93, 0x07, 0x79, +0x04, 0xd9, 0x07, 0x7a, 0x05, 0x93, 0x07, 0x5e, 0x03, 0x68, 0x07, +0x5c, 0x04, 0x31, 0x07, 0x5d, 0x04, 0x31, 0x07, 0x62, 0x03, 0x52, +0x07, 0x60, 0x04, 0x76, 0x07, 0x61, 0x04, 0x76, 0x07, 0x66, 0x03, +0x2e, 0x07, 0x64, 0x04, 0xda, 0x07, 0x65, 0x04, 0xda, 0x07, 0x6a, +0x02, 0xf6, 0x07, 0x68, 0x05, 0x62, 0x07, 0x69, 0x05, 0x62, 0x06, +0x46, 0x0a, 0x22, 0x06, 0x48, 0x0d, 0x24, 0x06, 0x6e, 0x11, 0xd3, +0x06, 0x70, 0x15, 0xcb, 0x06, 0x52, 0x20, 0x93, 0x06, 0x54, 0x20, +0x54, 0x06, 0x4a, 0x27, 0x1d, 0x06, 0x58, 0x2f, 0xc8, 0x06, 0x5c, +0x30, 0x07, 0x06, 0x4c, 0x37, 0x90, 0x06, 0x60, 0x3d, 0xdb, 0x06, +0x64, 0x3e, 0x42, 0x06, 0x4e, 0x45, 0x78, 0x06, 0x68, 0x4c, 0x48, +0x06, 0x6c, 0x4c, 0x6c, 0x06, 0x50, 0x52, 0xe2, 0x06, 0x42, 0x02, +0xba +}; +unsigned char coefficients2[] __initdata = { +0x07, 0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x45, 0x0f, +0xff, 0x07, 0x48, 0x0f, 0xff, 0x07, 0x7b, 0x04, 0xcc, 0x07, 0x7d, +0x04, 0xcc, 0x07, 0x7c, 0x00, 0x00, 0x07, 0x7e, 0x00, 0x00, 0x07, +0x46, 0x00, 0x00, 0x07, 0x49, 0x00, 0x00, 0x07, 0x47, 0x00, 0x00, +0x07, 0x4a, 0x00, 0x00, 0x07, 0x4c, 0x00, 0x00, 0x07, 0x4e, 0x00, 0x00 +}; +unsigned char coefficients3[] __initdata = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x28, 0x00, 0x51, 0x00, +0x51, 0x00, 0x7a, 0x00, 0x7a, 0x00, 0xa3, 0x00, 0xa3, 0x00, 0xcc, +0x00, 0xcc, 0x00, 0xf5, 0x00, 0xf5, 0x01, 0x1e, 0x01, 0x1e, 0x01, +0x47, 0x01, 0x47, 0x01, 0x70, 0x01, 0x70, 0x01, 0x99, 0x01, 0x99, +0x01, 0xc2, 0x01, 0xc2, 0x01, 0xeb, 0x01, 0xeb, 0x02, 0x14, 0x02, +0x14, 0x02, 0x3d, 0x02, 0x3d, 0x02, 0x66, 0x02, 0x66, 0x02, 0x8f, +0x02, 0x8f, 0x02, 0xb8, 0x02, 0xb8, 0x02, 0xe1, 0x02, 0xe1, 0x03, +0x0a, 0x03, 0x0a, 0x03, 0x33, 0x03, 0x33, 0x03, 0x5c, 0x03, 0x5c, +0x03, 0x85, 0x03, 0x85, 0x03, 0xae, 0x03, 0xae, 0x03, 0xd7, 0x03, +0xd7, 0x04, 0x00, 0x04, 0x00, 0x04, 0x28, 0x04, 0x28, 0x04, 0x51, +0x04, 0x51, 0x04, 0x7a, 0x04, 0x7a, 0x04, 0xa3, 0x04, 0xa3, 0x04, +0xcc, 0x04, 0xcc, 0x04, 0xf5, 0x04, 0xf5, 0x05, 0x1e, 0x05, 0x1e, +0x05, 0x47, 0x05, 0x47, 0x05, 0x70, 0x05, 0x70, 0x05, 0x99, 0x05, +0x99, 0x05, 0xc2, 0x05, 0xc2, 0x05, 0xeb, 0x05, 0xeb, 0x06, 0x14, +0x06, 0x14, 0x06, 0x3d, 0x06, 0x3d, 0x06, 0x66, 0x06, 0x66, 0x06, +0x8f, 0x06, 0x8f, 0x06, 0xb8, 0x06, 0xb8, 0x06, 0xe1, 0x06, 0xe1, +0x07, 0x0a, 0x07, 0x0a, 0x07, 0x33, 0x07, 0x33, 0x07, 0x5c, 0x07, +0x5c, 0x07, 0x85, 0x07, 0x85, 0x07, 0xae, 0x07, 0xae, 0x07, 0xd7, +0x07, 0xd7, 0x08, 0x00, 0x08, 0x00, 0x08, 0x28, 0x08, 0x28, 0x08, +0x51, 0x08, 0x51, 0x08, 0x7a, 0x08, 0x7a, 0x08, 0xa3, 0x08, 0xa3, +0x08, 0xcc, 0x08, 0xcc, 0x08, 0xf5, 0x08, 0xf5, 0x09, 0x1e, 0x09, +0x1e, 0x09, 0x47, 0x09, 0x47, 0x09, 0x70, 0x09, 0x70, 0x09, 0x99, +0x09, 0x99, 0x09, 0xc2, 0x09, 0xc2, 0x09, 0xeb, 0x09, 0xeb, 0x0a, +0x14, 0x0a, 0x14, 0x0a, 0x3d, 0x0a, 0x3d, 0x0a, 0x66, 0x0a, 0x66, +0x0a, 0x8f, 0x0a, 0x8f, 0x0a, 0xb8, 0x0a, 0xb8, 0x0a, 0xe1, 0x0a, +0xe1, 0x0b, 0x0a, 0x0b, 0x0a, 0x0b, 0x33, 0x0b, 0x33, 0x0b, 0x5c, +0x0b, 0x5c, 0x0b, 0x85, 0x0b, 0x85, 0x0b, 0xae, 0x0b, 0xae, 0x0b, +0xd7, 0x0b, 0xd7, 0x0c, 0x00, 0x0c, 0x00, 0x0c, 0x28, 0x0c, 0x28, +0x0c, 0x51, 0x0c, 0x51, 0x0c, 0x7a, 0x0c, 0x7a, 0x0c, 0xa3, 0x0c, +0xa3, 0x0c, 0xcc, 0x0c, 0xcc, 0x0c, 0xf5, 0x0c, 0xf5, 0x0d, 0x1e, +0x0d, 0x1e, 0x0d, 0x47, 0x0d, 0x47, 0x0d, 0x70, 0x0d, 0x70, 0x0d, +0x99, 0x0d, 0x99, 0x0d, 0xc2, 0x0d, 0xc2, 0x0d, 0xeb, 0x0d, 0xeb, +0x0e, 0x14, 0x0e, 0x14, 0x0e, 0x3d, 0x0e, 0x3d, 0x0e, 0x66, 0x0e, +0x66, 0x0e, 0x8f, 0x0e, 0x8f, 0x0e, 0xb8, 0x0e, 0xb8, 0x0e, 0xe1, +0x0e, 0xe1, 0x0f, 0x0a, 0x0f, 0x0a, 0x0f, 0x33, 0x0f, 0x33, 0x0f, +0x5c, 0x0f, 0x5c, 0x0f, 0x85, 0x0f, 0x85, 0x0f, 0xae, 0x0f, 0xae, +0x0f, 0xd7, 0x0f, 0xd7, 0x0f, 0xff, 0x0f, 0xff +}; + +static int +wavefront_fx_idle (snd_wavefront_t *dev) + +{ + int i; + unsigned int x = 0x80; + + for (i = 0; i < 1000; i++) { + x = inb (dev->fx_status); + if ((x & 0x80) == 0) { + break; + } + } + + if (x & 0x80) { + snd_printk ("FX device never idle.\n"); + return 0; + } + + return (1); +} + +static void +wavefront_fx_mute (snd_wavefront_t *dev, int onoff) + +{ + if (!wavefront_fx_idle(dev)) { + return; + } + + outb (onoff ? 0x02 : 0x00, dev->fx_op); +} + +static int +wavefront_fx_memset (snd_wavefront_t *dev, + int page, + int addr, + int cnt, + unsigned short *data) +{ + if (page < 0 || page > 7) { + snd_printk ("FX memset: " + "page must be >= 0 and <= 7\n"); + return -(EINVAL); + } + + if (addr < 0 || addr > 0x7f) { + snd_printk ("FX memset: " + "addr must be >= 0 and <= 7f\n"); + return -(EINVAL); + } + + if (cnt == 1) { + + outb (FX_LSB_TRANSFER, dev->fx_lcr); + outb (page, dev->fx_dsp_page); + outb (addr, dev->fx_dsp_addr); + outb ((data[0] >> 8), dev->fx_dsp_msb); + outb ((data[0] & 0xff), dev->fx_dsp_lsb); + + snd_printk ("FX: addr %d:%x set to 0x%x\n", + page, addr, data[0]); + + } else { + int i; + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (page, dev->fx_dsp_page); + outb (addr, dev->fx_dsp_addr); + + for (i = 0; i < cnt; i++) { + outb ((data[i] >> 8), dev->fx_dsp_msb); + outb ((data[i] & 0xff), dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) { + break; + } + } + + if (i != cnt) { + snd_printk ("FX memset " + "(0x%x, 0x%x, 0x%lx, %d) incomplete\n", + page, addr, (unsigned long) data, cnt); + return -(EIO); + } + } + + return 0; +} + +int +snd_wavefront_fx_detect (snd_wavefront_t *dev) + +{ + /* This is a crude check, but its the best one I have for now. + Certainly on the Maui and the Tropez, wavefront_fx_idle() will + report "never idle", which suggests that this test should + work OK. + */ + + if (inb (dev->fx_status) & 0x80) { + snd_printk ("Hmm, probably a Maui or Tropez.\n"); + return -1; + } + + return 0; +} + +int +snd_wavefront_fx_open (snd_hwdep_t *hw, struct file *file) + +{ + if (!try_module_get(hw->card->module)) + return -EFAULT; + file->private_data = hw; + return 0; +} + +int +snd_wavefront_fx_release (snd_hwdep_t *hw, struct file *file) + +{ + module_put(hw->card->module); + return 0; +} + +int +snd_wavefront_fx_ioctl (snd_hwdep_t *sdev, struct file *file, + unsigned int cmd, unsigned long arg) + +{ + snd_card_t *card; + snd_wavefront_card_t *acard; + snd_wavefront_t *dev; + wavefront_fx_info r; + unsigned short *page_data = NULL; + unsigned short *pd; + int err = 0; + + snd_assert(sdev->card != NULL, return -ENODEV); + + card = sdev->card; + + snd_assert(card->private_data != NULL, return -ENODEV); + + acard = card->private_data; + dev = &acard->wavefront; + + if (copy_from_user (&r, (void __user *)arg, sizeof (wavefront_fx_info))) + return -EFAULT; + + switch (r.request) { + case WFFX_MUTE: + wavefront_fx_mute (dev, r.data[0]); + return -EIO; + + case WFFX_MEMSET: + if (r.data[2] <= 0) { + snd_printk ("cannot write " + "<= 0 bytes to FX\n"); + return -EIO; + } else if (r.data[2] == 1) { + pd = (unsigned short *) &r.data[3]; + } else { + if (r.data[2] > 256) { + snd_printk ("cannot write " + "> 512 bytes to FX\n"); + return -EIO; + } + page_data = kmalloc(r.data[2] * sizeof(short), GFP_KERNEL); + if (!page_data) + return -ENOMEM; + if (copy_from_user (page_data, + (unsigned char __user *) r.data[3], + r.data[2] * sizeof(short))) { + kfree(page_data); + return -EFAULT; + } + pd = page_data; + } + + err = wavefront_fx_memset (dev, + r.data[0], /* page */ + r.data[1], /* addr */ + r.data[2], /* cnt */ + pd); + kfree(page_data); + break; + + default: + snd_printk ("FX: ioctl %d not yet supported\n", + r.request); + return -ENOTTY; + } + return err; +} + +/* YSS225 initialization. + + This code was developed using DOSEMU. The Turtle Beach SETUPSND + utility was run with I/O tracing in DOSEMU enabled, and a reconstruction + of the port I/O done, using the Yamaha faxback document as a guide + to add more logic to the code. Its really pretty weird. + + There was an alternative approach of just dumping the whole I/O + sequence as a series of port/value pairs and a simple loop + that output it. However, I hope that eventually I'll get more + control over what this code does, and so I tried to stick with + a somewhat "algorithmic" approach. +*/ + + +int __init +snd_wavefront_fx_start (snd_wavefront_t *dev) + +{ + unsigned int i, j; + + /* Set all bits for all channels on the MOD unit to zero */ + /* XXX But why do this twice ? */ + + for (j = 0; j < 2; j++) { + for (i = 0x10; i <= 0xff; i++) { + + if (!wavefront_fx_idle (dev)) { + return (-1); + } + + outb (i, dev->fx_mod_addr); + outb (0x0, dev->fx_mod_data); + } + } + + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x02, dev->fx_op); /* mute on */ + + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x44, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x42, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x43, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x7c, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x7e, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x46, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x49, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x47, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x4a, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + + /* either because of stupidity by TB's programmers, or because it + actually does something, rezero the MOD page. + */ + for (i = 0x10; i <= 0xff; i++) { + + if (!wavefront_fx_idle (dev)) { + return (-1); + } + + outb (i, dev->fx_mod_addr); + outb (0x0, dev->fx_mod_data); + } + /* load page zero */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x00, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_zero); i += 2) { + outb (page_zero[i], dev->fx_dsp_msb); + outb (page_zero[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + /* Now load page one */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x01, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_one); i += 2) { + outb (page_one[i], dev->fx_dsp_msb); + outb (page_one[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x02, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_two); i++) { + outb (page_two[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x03, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_three); i++) { + outb (page_three[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x04, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_four); i++) { + outb (page_four[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + /* Load memory area (page six) */ + + outb (FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x06, dev->fx_dsp_page); + + for (i = 0; i < sizeof (page_six); i += 3) { + outb (page_six[i], dev->fx_dsp_addr); + outb (page_six[i+1], dev->fx_dsp_msb); + outb (page_six[i+2], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x07, dev->fx_dsp_page); + outb (0x00, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_seven); i += 2) { + outb (page_seven[i], dev->fx_dsp_msb); + outb (page_seven[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + /* Now setup the MOD area. We do this algorithmically in order to + save a little data space. It could be done in the same fashion + as the "pages". + */ + + for (i = 0x00; i <= 0x0f; i++) { + outb (0x01, dev->fx_mod_addr); + outb (i, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x02, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0xb0; i <= 0xbf; i++) { + outb (i, dev->fx_mod_addr); + outb (0x20, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0xf0; i <= 0xff; i++) { + outb (i, dev->fx_mod_addr); + outb (0x20, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0x10; i <= 0x1d; i++) { + outb (i, dev->fx_mod_addr); + outb (0xff, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x1e, dev->fx_mod_addr); + outb (0x40, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + for (i = 0x1f; i <= 0x2d; i++) { + outb (i, dev->fx_mod_addr); + outb (0xff, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x2e, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + for (i = 0x2f; i <= 0x3e; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x3f, dev->fx_mod_addr); + outb (0x20, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + for (i = 0x40; i <= 0x4d; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x4e, dev->fx_mod_addr); + outb (0x0e, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x4f, dev->fx_mod_addr); + outb (0x0e, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + + for (i = 0x50; i <= 0x6b; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x6c, dev->fx_mod_addr); + outb (0x40, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + outb (0x6d, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + outb (0x6e, dev->fx_mod_addr); + outb (0x40, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + outb (0x6f, dev->fx_mod_addr); + outb (0x40, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + for (i = 0x70; i <= 0x7f; i++) { + outb (i, dev->fx_mod_addr); + outb (0xc0, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0x80; i <= 0xaf; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0xc0; i <= 0xdd; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0xde, dev->fx_mod_addr); + outb (0x10, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0xdf, dev->fx_mod_addr); + outb (0x10, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + + for (i = 0xe0; i <= 0xef; i++) { + outb (i, dev->fx_mod_addr); + outb (0x00, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0x00; i <= 0x0f; i++) { + outb (0x01, dev->fx_mod_addr); + outb (i, dev->fx_mod_data); + outb (0x02, dev->fx_mod_addr); + outb (0x01, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (0x02, dev->fx_op); /* mute on */ + + /* Now set the coefficients and so forth for the programs above */ + + for (i = 0; i < sizeof (coefficients); i += 4) { + outb (coefficients[i], dev->fx_dsp_page); + outb (coefficients[i+1], dev->fx_dsp_addr); + outb (coefficients[i+2], dev->fx_dsp_msb); + outb (coefficients[i+3], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + /* Some settings (?) that are too small to bundle into loops */ + + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x1e, dev->fx_mod_addr); + outb (0x14, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0xde, dev->fx_mod_addr); + outb (0x20, dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0xdf, dev->fx_mod_addr); + outb (0x20, dev->fx_mod_data); + + /* some more coefficients */ + + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x06, dev->fx_dsp_page); + outb (0x78, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x40, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x03, dev->fx_dsp_addr); + outb (0x0f, dev->fx_dsp_msb); + outb (0xff, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x0b, dev->fx_dsp_addr); + outb (0x0f, dev->fx_dsp_msb); + outb (0xff, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x02, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x0a, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x46, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + outb (0x07, dev->fx_dsp_page); + outb (0x49, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + + /* Now, for some strange reason, lets reload every page + and all the coefficients over again. I have *NO* idea + why this is done. I do know that no sound is produced + is this phase is omitted. + */ + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x00, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_zero_v2); i += 2) { + outb (page_zero_v2[i], dev->fx_dsp_msb); + outb (page_zero_v2[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x01, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_one_v2); i += 2) { + outb (page_one_v2[i], dev->fx_dsp_msb); + outb (page_one_v2[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + if (!wavefront_fx_idle (dev)) return (-1); + if (!wavefront_fx_idle (dev)) return (-1); + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x02, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_two_v2); i++) { + outb (page_two_v2[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x03, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_three_v2); i++) { + outb (page_three_v2[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x04, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_four_v2); i++) { + outb (page_four_v2[i], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x06, dev->fx_dsp_page); + + /* Page six v.2 is algorithmic */ + + for (i = 0x10; i <= 0x3e; i += 2) { + outb (i, dev->fx_dsp_addr); + outb (0x00, dev->fx_dsp_msb); + outb (0x00, dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + outb (FX_AUTO_INCR|FX_LSB_TRANSFER, dev->fx_lcr); + outb (0x07, dev->fx_dsp_page); + outb (0x10, dev->fx_dsp_addr); + + for (i = 0; i < sizeof (page_seven_v2); i += 2) { + outb (page_seven_v2[i], dev->fx_dsp_msb); + outb (page_seven_v2[i+1], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0x00; i < sizeof(mod_v2); i += 2) { + outb (mod_v2[i], dev->fx_mod_addr); + outb (mod_v2[i+1], dev->fx_mod_data); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0; i < sizeof (coefficients2); i += 4) { + outb (coefficients2[i], dev->fx_dsp_page); + outb (coefficients2[i+1], dev->fx_dsp_addr); + outb (coefficients2[i+2], dev->fx_dsp_msb); + outb (coefficients2[i+3], dev->fx_dsp_lsb); + if (!wavefront_fx_idle (dev)) return (-1); + } + + for (i = 0; i < sizeof (coefficients3); i += 2) { + int x; + + outb (0x07, dev->fx_dsp_page); + x = (i % 4) ? 0x4e : 0x4c; + outb (x, dev->fx_dsp_addr); + outb (coefficients3[i], dev->fx_dsp_msb); + outb (coefficients3[i+1], dev->fx_dsp_lsb); + } + + outb (0x00, dev->fx_op); /* mute off */ + if (!wavefront_fx_idle (dev)) return (-1); + + return (0); +} diff --git a/sound/isa/wavefront/wavefront_midi.c b/sound/isa/wavefront/wavefront_midi.c new file mode 100644 index 00000000000..6f51d64fb56 --- /dev/null +++ b/sound/isa/wavefront/wavefront_midi.c @@ -0,0 +1,570 @@ +/* + * Copyright (C) by Paul Barton-Davis 1998-1999 + * + * This file is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this + * software for more info. + */ + +/* The low level driver for the WaveFront ICS2115 MIDI interface(s) + * + * Note that there is also an MPU-401 emulation (actually, a UART-401 + * emulation) on the CS4232 on the Tropez and Tropez Plus. This code + * has nothing to do with that interface at all. + * + * The interface is essentially just a UART-401, but is has the + * interesting property of supporting what Turtle Beach called + * "Virtual MIDI" mode. In this mode, there are effectively *two* + * MIDI buses accessible via the interface, one that is routed + * solely to/from the external WaveFront synthesizer and the other + * corresponding to the pin/socket connector used to link external + * MIDI devices to the board. + * + * This driver fully supports this mode, allowing two distinct MIDI + * busses to be used completely independently, giving 32 channels of + * MIDI routing, 16 to the WaveFront synth and 16 to the external MIDI + * bus. The devices are named /dev/snd/midiCnD0 and /dev/snd/midiCnD1, + * where `n' is the card number. Note that the device numbers may be + * something other than 0 and 1 if the CS4232 UART/MPU-401 interface + * is enabled. + * + * Switching between the two is accomplished externally by the driver + * using the two otherwise unused MIDI bytes. See the code for more details. + * + * NOTE: VIRTUAL MIDI MODE IS ON BY DEFAULT (see lowlevel/isa/wavefront.c) + * + * The main reason to turn off Virtual MIDI mode is when you want to + * tightly couple the WaveFront synth with an external MIDI + * device. You won't be able to distinguish the source of any MIDI + * data except via SysEx ID, but thats probably OK, since for the most + * part, the WaveFront won't be sending any MIDI data at all. + * + * The main reason to turn on Virtual MIDI Mode is to provide two + * completely independent 16-channel MIDI buses, one to the + * WaveFront and one to any external MIDI devices. Given the 32 + * voice nature of the WaveFront, its pretty easy to find a use + * for all 16 channels driving just that synth. + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/init.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <sound/core.h> +#include <sound/snd_wavefront.h> + +static inline int +wf_mpu_status (snd_wavefront_midi_t *midi) + +{ + return inb (midi->mpu_status_port); +} + +static inline int +input_avail (snd_wavefront_midi_t *midi) + +{ + return !(wf_mpu_status(midi) & INPUT_AVAIL); +} + +static inline int +output_ready (snd_wavefront_midi_t *midi) + +{ + return !(wf_mpu_status(midi) & OUTPUT_READY); +} + +static inline int +read_data (snd_wavefront_midi_t *midi) + +{ + return inb (midi->mpu_data_port); +} + +static inline void +write_data (snd_wavefront_midi_t *midi, unsigned char byte) + +{ + outb (byte, midi->mpu_data_port); +} + +static snd_wavefront_midi_t * +get_wavefront_midi (snd_rawmidi_substream_t *substream) + +{ + snd_card_t *card; + snd_wavefront_card_t *acard; + + if (substream == NULL || substream->rmidi == NULL) + return NULL; + + card = substream->rmidi->card; + + if (card == NULL) + return NULL; + + if (card->private_data == NULL) + return NULL; + + acard = card->private_data; + + return &acard->wavefront.midi; +} + +static void snd_wavefront_midi_output_write(snd_wavefront_card_t *card) +{ + snd_wavefront_midi_t *midi = &card->wavefront.midi; + snd_wavefront_mpu_id mpu; + unsigned long flags; + unsigned char midi_byte; + int max = 256, mask = 1; + int timeout; + + /* Its not OK to try to change the status of "virtuality" of + the MIDI interface while we're outputting stuff. See + snd_wavefront_midi_{enable,disable}_virtual () for the + other half of this. + + The first loop attempts to flush any data from the + current output device, and then the second + emits the switch byte (if necessary), and starts + outputting data for the output device currently in use. + */ + + if (midi->substream_output[midi->output_mpu] == NULL) { + goto __second; + } + + while (max > 0) { + + /* XXX fix me - no hard timing loops allowed! */ + + for (timeout = 30000; timeout > 0; timeout--) { + if (output_ready (midi)) + break; + } + + spin_lock_irqsave (&midi->virtual, flags); + if ((midi->mode[midi->output_mpu] & MPU401_MODE_OUTPUT) == 0) { + spin_unlock_irqrestore (&midi->virtual, flags); + goto __second; + } + if (output_ready (midi)) { + if (snd_rawmidi_transmit(midi->substream_output[midi->output_mpu], &midi_byte, 1) == 1) { + if (!midi->isvirtual || + (midi_byte != WF_INTERNAL_SWITCH && + midi_byte != WF_EXTERNAL_SWITCH)) + write_data(midi, midi_byte); + max--; + } else { + if (midi->istimer) { + if (--midi->istimer <= 0) + del_timer(&midi->timer); + } + midi->mode[midi->output_mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + spin_unlock_irqrestore (&midi->virtual, flags); + goto __second; + } + } else { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + spin_unlock_irqrestore (&midi->virtual, flags); + } + + __second: + + if (midi->substream_output[!midi->output_mpu] == NULL) { + return; + } + + while (max > 0) { + + /* XXX fix me - no hard timing loops allowed! */ + + for (timeout = 30000; timeout > 0; timeout--) { + if (output_ready (midi)) + break; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (!midi->isvirtual) + mask = 0; + mpu = midi->output_mpu ^ mask; + mask = 0; /* don't invert the value from now */ + if ((midi->mode[mpu] & MPU401_MODE_OUTPUT) == 0) { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + if (snd_rawmidi_transmit_empty(midi->substream_output[mpu])) + goto __timer; + if (output_ready (midi)) { + if (mpu != midi->output_mpu) { + write_data(midi, mpu == internal_mpu ? + WF_INTERNAL_SWITCH : + WF_EXTERNAL_SWITCH); + midi->output_mpu = mpu; + } else if (snd_rawmidi_transmit(midi->substream_output[mpu], &midi_byte, 1) == 1) { + if (!midi->isvirtual || + (midi_byte != WF_INTERNAL_SWITCH && + midi_byte != WF_EXTERNAL_SWITCH)) + write_data(midi, midi_byte); + max--; + } else { + __timer: + if (midi->istimer) { + if (--midi->istimer <= 0) + del_timer(&midi->timer); + } + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + } else { + spin_unlock_irqrestore (&midi->virtual, flags); + return; + } + spin_unlock_irqrestore (&midi->virtual, flags); + } +} + +static int snd_wavefront_midi_input_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO); + snd_assert(substream->rmidi->private_data != NULL, return -EIO); + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] |= MPU401_MODE_INPUT; + midi->substream_input[mpu] = substream; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_output_open(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO); + snd_assert(substream->rmidi->private_data != NULL, return -EIO); + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] |= MPU401_MODE_OUTPUT; + midi->substream_output[mpu] = substream; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_input_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO); + snd_assert(substream->rmidi->private_data != NULL, return -EIO); + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] &= ~MPU401_MODE_INPUT; + spin_unlock_irqrestore (&midi->open, flags); + + return 0; +} + +static int snd_wavefront_midi_output_close(snd_rawmidi_substream_t * substream) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + snd_assert(substream != NULL && substream->rmidi != NULL, return -EIO); + snd_assert(substream->rmidi->private_data != NULL, return -EIO); + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) + return -EIO; + + spin_lock_irqsave (&midi->open, flags); + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT; + spin_unlock_irqrestore (&midi->open, flags); + return 0; +} + +static void snd_wavefront_midi_input_trigger(snd_rawmidi_substream_t * substream, int up) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (substream == NULL || substream->rmidi == NULL) + return; + + if (substream->rmidi->private_data == NULL) + return; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) { + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (up) { + midi->mode[mpu] |= MPU401_MODE_INPUT_TRIGGER; + } else { + midi->mode[mpu] &= ~MPU401_MODE_INPUT_TRIGGER; + } + spin_unlock_irqrestore (&midi->virtual, flags); +} + +static void snd_wavefront_midi_output_timer(unsigned long data) +{ + snd_wavefront_card_t *card = (snd_wavefront_card_t *)data; + snd_wavefront_midi_t *midi = &card->wavefront.midi; + unsigned long flags; + + spin_lock_irqsave (&midi->virtual, flags); + midi->timer.expires = 1 + jiffies; + add_timer(&midi->timer); + spin_unlock_irqrestore (&midi->virtual, flags); + snd_wavefront_midi_output_write(card); +} + +static void snd_wavefront_midi_output_trigger(snd_rawmidi_substream_t * substream, int up) +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + snd_wavefront_mpu_id mpu; + + if (substream == NULL || substream->rmidi == NULL) + return; + + if (substream->rmidi->private_data == NULL) + return; + + mpu = *((snd_wavefront_mpu_id *) substream->rmidi->private_data); + + if ((midi = get_wavefront_midi (substream)) == NULL) { + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + if (up) { + if ((midi->mode[mpu] & MPU401_MODE_OUTPUT_TRIGGER) == 0) { + if (!midi->istimer) { + init_timer(&midi->timer); + midi->timer.function = snd_wavefront_midi_output_timer; + midi->timer.data = (unsigned long) substream->rmidi->card->private_data; + midi->timer.expires = 1 + jiffies; + add_timer(&midi->timer); + } + midi->istimer++; + midi->mode[mpu] |= MPU401_MODE_OUTPUT_TRIGGER; + } + } else { + midi->mode[mpu] &= ~MPU401_MODE_OUTPUT_TRIGGER; + } + spin_unlock_irqrestore (&midi->virtual, flags); + + if (up) + snd_wavefront_midi_output_write((snd_wavefront_card_t *)substream->rmidi->card->private_data); +} + +void +snd_wavefront_midi_interrupt (snd_wavefront_card_t *card) + +{ + unsigned long flags; + snd_wavefront_midi_t *midi; + static snd_rawmidi_substream_t *substream = NULL; + static int mpu = external_mpu; + int max = 128; + unsigned char byte; + + midi = &card->wavefront.midi; + + if (!input_avail (midi)) { /* not for us */ + snd_wavefront_midi_output_write(card); + return; + } + + spin_lock_irqsave (&midi->virtual, flags); + while (--max) { + + if (input_avail (midi)) { + byte = read_data (midi); + + if (midi->isvirtual) { + if (byte == WF_EXTERNAL_SWITCH) { + substream = midi->substream_input[external_mpu]; + mpu = external_mpu; + } else if (byte == WF_INTERNAL_SWITCH) { + substream = midi->substream_output[internal_mpu]; + mpu = internal_mpu; + } /* else just leave it as it is */ + } else { + substream = midi->substream_input[internal_mpu]; + mpu = internal_mpu; + } + + if (substream == NULL) { + continue; + } + + if (midi->mode[mpu] & MPU401_MODE_INPUT_TRIGGER) { + snd_rawmidi_receive(substream, &byte, 1); + } + } else { + break; + } + } + spin_unlock_irqrestore (&midi->virtual, flags); + + snd_wavefront_midi_output_write(card); +} + +void +snd_wavefront_midi_enable_virtual (snd_wavefront_card_t *card) + +{ + unsigned long flags; + + spin_lock_irqsave (&card->wavefront.midi.virtual, flags); + card->wavefront.midi.isvirtual = 1; + card->wavefront.midi.output_mpu = internal_mpu; + card->wavefront.midi.input_mpu = internal_mpu; + spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags); +} + +void +snd_wavefront_midi_disable_virtual (snd_wavefront_card_t *card) + +{ + unsigned long flags; + + spin_lock_irqsave (&card->wavefront.midi.virtual, flags); + // snd_wavefront_midi_input_close (card->ics2115_external_rmidi); + // snd_wavefront_midi_output_close (card->ics2115_external_rmidi); + card->wavefront.midi.isvirtual = 0; + spin_unlock_irqrestore (&card->wavefront.midi.virtual, flags); +} + +int __init +snd_wavefront_midi_start (snd_wavefront_card_t *card) + +{ + int ok, i; + unsigned char rbuf[4], wbuf[4]; + snd_wavefront_t *dev; + snd_wavefront_midi_t *midi; + + dev = &card->wavefront; + midi = &dev->midi; + + /* The ICS2115 MPU-401 interface doesn't do anything + until its set into UART mode. + */ + + /* XXX fix me - no hard timing loops allowed! */ + + for (i = 0; i < 30000 && !output_ready (midi); i++); + + if (!output_ready (midi)) { + snd_printk ("MIDI interface not ready for command\n"); + return -1; + } + + /* Any interrupts received from now on + are owned by the MIDI side of things. + */ + + dev->interrupts_are_midi = 1; + + outb (UART_MODE_ON, midi->mpu_command_port); + + for (ok = 0, i = 50000; i > 0 && !ok; i--) { + if (input_avail (midi)) { + if (read_data (midi) == MPU_ACK) { + ok = 1; + break; + } + } + } + + if (!ok) { + snd_printk ("cannot set UART mode for MIDI interface"); + dev->interrupts_are_midi = 0; + return -1; + } + + /* Route external MIDI to WaveFront synth (by default) */ + + if (snd_wavefront_cmd (dev, WFC_MISYNTH_ON, rbuf, wbuf)) { + snd_printk ("can't enable MIDI-IN-2-synth routing.\n"); + /* XXX error ? */ + } + + /* Turn on Virtual MIDI, but first *always* turn it off, + since otherwise consectutive reloads of the driver will + never cause the hardware to generate the initial "internal" or + "external" source bytes in the MIDI data stream. This + is pretty important, since the internal hardware generally will + be used to generate none or very little MIDI output, and + thus the only source of MIDI data is actually external. Without + the switch bytes, the driver will think it all comes from + the internal interface. Duh. + */ + + if (snd_wavefront_cmd (dev, WFC_VMIDI_OFF, rbuf, wbuf)) { + snd_printk ("virtual MIDI mode not disabled\n"); + return 0; /* We're OK, but missing the external MIDI dev */ + } + + snd_wavefront_midi_enable_virtual (card); + + if (snd_wavefront_cmd (dev, WFC_VMIDI_ON, rbuf, wbuf)) { + snd_printk ("cannot enable virtual MIDI mode.\n"); + snd_wavefront_midi_disable_virtual (card); + } + return 0; +} + +snd_rawmidi_ops_t snd_wavefront_midi_output = +{ + .open = snd_wavefront_midi_output_open, + .close = snd_wavefront_midi_output_close, + .trigger = snd_wavefront_midi_output_trigger, +}; + +snd_rawmidi_ops_t snd_wavefront_midi_input = +{ + .open = snd_wavefront_midi_input_open, + .close = snd_wavefront_midi_input_close, + .trigger = snd_wavefront_midi_input_trigger, +}; + diff --git a/sound/isa/wavefront/wavefront_synth.c b/sound/isa/wavefront/wavefront_synth.c new file mode 100644 index 00000000000..0c3c951009d --- /dev/null +++ b/sound/isa/wavefront/wavefront_synth.c @@ -0,0 +1,2243 @@ +/* Copyright (C) by Paul Barton-Davis 1998-1999 + * + * Some portions of this file are taken from work that is + * copyright (C) by Hannu Savolainen 1993-1996 + * + * This program is distributed under the GNU GENERAL PUBLIC LICENSE (GPL) + * Version 2 (June 1991). See the "COPYING" file distributed with this software + * for more info. + */ + +/* + * An ALSA lowlevel driver for Turtle Beach ICS2115 wavetable synth + * (Maui, Tropez, Tropez Plus) + * + * This driver supports the onboard wavetable synthesizer (an ICS2115), + * including patch, sample and program loading and unloading, conversion + * of GUS patches during loading, and full user-level access to all + * WaveFront commands. It tries to provide semi-intelligent patch and + * sample management as well. + * + */ + +#include <sound/driver.h> +#include <asm/io.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/wait.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/snd_wavefront.h> +#include <sound/initval.h> + +static int wf_raw = 0; /* we normally check for "raw state" to firmware + loading. if non-zero, then during driver loading, the + state of the board is ignored, and we reset the + board and load the firmware anyway. + */ + +static int fx_raw = 1; /* if this is zero, we'll leave the FX processor in + whatever state it is when the driver is loaded. + The default is to download the microprogram and + associated coefficients to set it up for "default" + operation, whatever that means. + */ + +static int debug_default = 0; /* you can set this to control debugging + during driver loading. it takes any combination + of the WF_DEBUG_* flags defined in + wavefront.h + */ + +/* XXX this needs to be made firmware and hardware version dependent */ + +static char *ospath = "/etc/sound/wavefront.os"; /* where to find a processed + version of the WaveFront OS + */ + +static int wait_usecs = 150; /* This magic number seems to give pretty optimal + throughput based on my limited experimentation. + If you want to play around with it and find a better + value, be my guest. Remember, the idea is to + get a number that causes us to just busy wait + for as many WaveFront commands as possible, without + coming up with a number so large that we hog the + whole CPU. + + Specifically, with this number, out of about 134,000 + status waits, only about 250 result in a sleep. + */ + +static int sleep_interval = 100; /* HZ/sleep_interval seconds per sleep */ +static int sleep_tries = 50; /* number of times we'll try to sleep */ + +static int reset_time = 2; /* hundreths of a second we wait after a HW + reset for the expected interrupt. + */ + +static int ramcheck_time = 20; /* time in seconds to wait while ROM code + checks on-board RAM. + */ + +static int osrun_time = 10; /* time in seconds we wait for the OS to + start running. + */ +module_param(wf_raw, int, 0444); +MODULE_PARM_DESC(wf_raw, "if non-zero, assume that we need to boot the OS"); +module_param(fx_raw, int, 0444); +MODULE_PARM_DESC(fx_raw, "if non-zero, assume that the FX process needs help"); +module_param(debug_default, int, 0444); +MODULE_PARM_DESC(debug_default, "debug parameters for card initialization"); +module_param(wait_usecs, int, 0444); +MODULE_PARM_DESC(wait_usecs, "how long to wait without sleeping, usecs"); +module_param(sleep_interval, int, 0444); +MODULE_PARM_DESC(sleep_interval, "how long to sleep when waiting for reply"); +module_param(sleep_tries, int, 0444); +MODULE_PARM_DESC(sleep_tries, "how many times to try sleeping during a wait"); +module_param(ospath, charp, 0444); +MODULE_PARM_DESC(ospath, "full pathname to processed ICS2115 OS firmware"); +module_param(reset_time, int, 0444); +MODULE_PARM_DESC(reset_time, "how long to wait for a reset to take effect"); +module_param(ramcheck_time, int, 0444); +MODULE_PARM_DESC(ramcheck_time, "how many seconds to wait for the RAM test"); +module_param(osrun_time, int, 0444); +MODULE_PARM_DESC(osrun_time, "how many seconds to wait for the ICS2115 OS"); + +/* if WF_DEBUG not defined, no run-time debugging messages will + be available via the debug flag setting. Given the current + beta state of the driver, this will remain set until a future + version. +*/ + +#define WF_DEBUG 1 + +#ifdef WF_DEBUG + +#if defined(NEW_MACRO_VARARGS) || __GNUC__ >= 3 +#define DPRINT(cond, ...) \ + if ((dev->debug & (cond)) == (cond)) { \ + snd_printk (__VA_ARGS__); \ + } +#else +#define DPRINT(cond, args...) \ + if ((dev->debug & (cond)) == (cond)) { \ + snd_printk (args); \ + } +#endif +#else +#define DPRINT(cond, args...) +#endif /* WF_DEBUG */ + +#define LOGNAME "WaveFront: " + +/* bitmasks for WaveFront status port value */ + +#define STAT_RINTR_ENABLED 0x01 +#define STAT_CAN_READ 0x02 +#define STAT_INTR_READ 0x04 +#define STAT_WINTR_ENABLED 0x10 +#define STAT_CAN_WRITE 0x20 +#define STAT_INTR_WRITE 0x40 + +static int wavefront_delete_sample (snd_wavefront_t *, int sampnum); +static int wavefront_find_free_sample (snd_wavefront_t *); + +typedef struct { + int cmd; + char *action; + unsigned int read_cnt; + unsigned int write_cnt; + int need_ack; +} wavefront_command; + +static struct { + int errno; + const char *errstr; +} wavefront_errors[] = { + { 0x01, "Bad sample number" }, + { 0x02, "Out of sample memory" }, + { 0x03, "Bad patch number" }, + { 0x04, "Error in number of voices" }, + { 0x06, "Sample load already in progress" }, + { 0x0B, "No sample load request pending" }, + { 0x0E, "Bad MIDI channel number" }, + { 0x10, "Download Record Error" }, + { 0x80, "Success" }, + { 0x0 } +}; + +#define NEEDS_ACK 1 + +static wavefront_command wavefront_commands[] = { + { WFC_SET_SYNTHVOL, "set synthesizer volume", 0, 1, NEEDS_ACK }, + { WFC_GET_SYNTHVOL, "get synthesizer volume", 1, 0, 0}, + { WFC_SET_NVOICES, "set number of voices", 0, 1, NEEDS_ACK }, + { WFC_GET_NVOICES, "get number of voices", 1, 0, 0 }, + { WFC_SET_TUNING, "set synthesizer tuning", 0, 2, NEEDS_ACK }, + { WFC_GET_TUNING, "get synthesizer tuning", 2, 0, 0 }, + { WFC_DISABLE_CHANNEL, "disable synth channel", 0, 1, NEEDS_ACK }, + { WFC_ENABLE_CHANNEL, "enable synth channel", 0, 1, NEEDS_ACK }, + { WFC_GET_CHANNEL_STATUS, "get synth channel status", 3, 0, 0 }, + { WFC_MISYNTH_OFF, "disable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_MISYNTH_ON, "enable midi-in to synth", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_ON, "enable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_VMIDI_OFF, "disable virtual midi mode", 0, 0, NEEDS_ACK }, + { WFC_MIDI_STATUS, "report midi status", 1, 0, 0 }, + { WFC_FIRMWARE_VERSION, "report firmware version", 2, 0, 0 }, + { WFC_HARDWARE_VERSION, "report hardware version", 2, 0, 0 }, + { WFC_GET_NSAMPLES, "report number of samples", 2, 0, 0 }, + { WFC_INSTOUT_LEVELS, "report instantaneous output levels", 7, 0, 0 }, + { WFC_PEAKOUT_LEVELS, "report peak output levels", 7, 0, 0 }, + { WFC_DOWNLOAD_SAMPLE, "download sample", + 0, WF_SAMPLE_BYTES, NEEDS_ACK }, + { WFC_DOWNLOAD_BLOCK, "download block", 0, 0, NEEDS_ACK}, + { WFC_DOWNLOAD_SAMPLE_HEADER, "download sample header", + 0, WF_SAMPLE_HDR_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_HEADER, "upload sample header", 13, 2, 0 }, + + /* This command requires a variable number of bytes to be written. + There is a hack in snd_wavefront_cmd() to support this. The actual + count is passed in as the read buffer ptr, cast appropriately. + Ugh. + */ + + { WFC_DOWNLOAD_MULTISAMPLE, "download multisample", 0, 0, NEEDS_ACK }, + + /* This one is a hack as well. We just read the first byte of the + response, don't fetch an ACK, and leave the rest to the + calling function. Ugly, ugly, ugly. + */ + + { WFC_UPLOAD_MULTISAMPLE, "upload multisample", 2, 1, 0 }, + { WFC_DOWNLOAD_SAMPLE_ALIAS, "download sample alias", + 0, WF_ALIAS_BYTES, NEEDS_ACK }, + { WFC_UPLOAD_SAMPLE_ALIAS, "upload sample alias", WF_ALIAS_BYTES, 2, 0}, + { WFC_DELETE_SAMPLE, "delete sample", 0, 2, NEEDS_ACK }, + { WFC_IDENTIFY_SAMPLE_TYPE, "identify sample type", 5, 2, 0 }, + { WFC_UPLOAD_SAMPLE_PARAMS, "upload sample parameters" }, + { WFC_REPORT_FREE_MEMORY, "report free memory", 4, 0, 0 }, + { WFC_DOWNLOAD_PATCH, "download patch", 0, 134, NEEDS_ACK }, + { WFC_UPLOAD_PATCH, "upload patch", 132, 2, 0 }, + { WFC_DOWNLOAD_PROGRAM, "download program", 0, 33, NEEDS_ACK }, + { WFC_UPLOAD_PROGRAM, "upload program", 32, 1, 0 }, + { WFC_DOWNLOAD_EDRUM_PROGRAM, "download enhanced drum program", 0, 9, + NEEDS_ACK}, + { WFC_UPLOAD_EDRUM_PROGRAM, "upload enhanced drum program", 8, 1, 0}, + { WFC_SET_EDRUM_CHANNEL, "set enhanced drum program channel", + 0, 1, NEEDS_ACK }, + { WFC_DISABLE_DRUM_PROGRAM, "disable drum program", 0, 1, NEEDS_ACK }, + { WFC_REPORT_CHANNEL_PROGRAMS, "report channel program numbers", + 32, 0, 0 }, + { WFC_NOOP, "the no-op command", 0, 0, NEEDS_ACK }, + { 0x00 } +}; + +static const char * +wavefront_errorstr (int errnum) + +{ + int i; + + for (i = 0; wavefront_errors[i].errstr; i++) { + if (wavefront_errors[i].errno == errnum) { + return wavefront_errors[i].errstr; + } + } + + return "Unknown WaveFront error"; +} + +static wavefront_command * +wavefront_get_command (int cmd) + +{ + int i; + + for (i = 0; wavefront_commands[i].cmd != 0; i++) { + if (cmd == wavefront_commands[i].cmd) { + return &wavefront_commands[i]; + } + } + + return (wavefront_command *) 0; +} + +static inline int +wavefront_status (snd_wavefront_t *dev) + +{ + return inb (dev->status_port); +} + +static int +wavefront_sleep (int limit) + +{ + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(limit); + + return signal_pending(current); +} + +static int +wavefront_wait (snd_wavefront_t *dev, int mask) + +{ + int i; + + /* Spin for a short period of time, because >99% of all + requests to the WaveFront can be serviced inline like this. + */ + + for (i = 0; i < wait_usecs; i += 5) { + if (wavefront_status (dev) & mask) { + return 1; + } + udelay(5); + } + + for (i = 0; i < sleep_tries; i++) { + + if (wavefront_status (dev) & mask) { + return 1; + } + + if (wavefront_sleep (HZ/sleep_interval)) { + return (0); + } + } + + return (0); +} + +static int +wavefront_read (snd_wavefront_t *dev) + +{ + if (wavefront_wait (dev, STAT_CAN_READ)) + return inb (dev->data_port); + + DPRINT (WF_DEBUG_DATA, "read timeout.\n"); + + return -1; +} + +static int +wavefront_write (snd_wavefront_t *dev, unsigned char data) + +{ + if (wavefront_wait (dev, STAT_CAN_WRITE)) { + outb (data, dev->data_port); + return 0; + } + + DPRINT (WF_DEBUG_DATA, "write timeout.\n"); + + return -1; +} + +int +snd_wavefront_cmd (snd_wavefront_t *dev, + int cmd, unsigned char *rbuf, unsigned char *wbuf) + +{ + int ack; + unsigned int i; + int c; + wavefront_command *wfcmd; + + if ((wfcmd = wavefront_get_command (cmd)) == (wavefront_command *) 0) { + snd_printk ("command 0x%x not supported.\n", + cmd); + return 1; + } + + /* Hack to handle the one variable-size write command. See + wavefront_send_multisample() for the other half of this + gross and ugly strategy. + */ + + if (cmd == WFC_DOWNLOAD_MULTISAMPLE) { + wfcmd->write_cnt = (unsigned long) rbuf; + rbuf = NULL; + } + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + + if (wavefront_write (dev, cmd)) { + DPRINT ((WF_DEBUG_IO|WF_DEBUG_CMD), "cannot request " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + } + + if (wfcmd->write_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "writing %d bytes " + "for 0x%x\n", + wfcmd->write_cnt, cmd); + + for (i = 0; i < wfcmd->write_cnt; i++) { + if (wavefront_write (dev, wbuf[i])) { + DPRINT (WF_DEBUG_IO, "bad write for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + DPRINT (WF_DEBUG_DATA, "write[%d] = 0x%x\n", + i, wbuf[i]); + } + } + + if (wfcmd->read_cnt > 0) { + DPRINT (WF_DEBUG_DATA, "reading %d ints " + "for 0x%x\n", + wfcmd->read_cnt, cmd); + + for (i = 0; i < wfcmd->read_cnt; i++) { + + if ((c = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for byte " + "%d of 0x%x [%s].\n", + i, cmd, wfcmd->action); + return 1; + } + + /* Now handle errors. Lots of special cases here */ + + if (c == 0xff) { + if ((c = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_IO, "bad read for " + "error byte at " + "read byte %d " + "of 0x%x [%s].\n", + i, cmd, + wfcmd->action); + return 1; + } + + /* Can you believe this madness ? */ + + if (c == 1 && + wfcmd->cmd == WFC_IDENTIFY_SAMPLE_TYPE) { + rbuf[0] = WF_ST_EMPTY; + return (0); + + } else if (c == 3 && + wfcmd->cmd == WFC_UPLOAD_PATCH) { + + return 3; + + } else if (c == 1 && + wfcmd->cmd == WFC_UPLOAD_PROGRAM) { + + return 1; + + } else { + + DPRINT (WF_DEBUG_IO, "error %d (%s) " + "during " + "read for byte " + "%d of 0x%x " + "[%s].\n", + c, + wavefront_errorstr (c), + i, cmd, + wfcmd->action); + return 1; + + } + + } else { + rbuf[i] = c; + } + + DPRINT (WF_DEBUG_DATA, "read[%d] = 0x%x\n",i, rbuf[i]); + } + } + + if ((wfcmd->read_cnt == 0 && wfcmd->write_cnt == 0) || wfcmd->need_ack) { + + DPRINT (WF_DEBUG_CMD, "reading ACK for 0x%x\n", cmd); + + /* Some commands need an ACK, but return zero instead + of the standard value. + */ + + if ((ack = wavefront_read (dev)) == 0) { + ack = WF_ACK; + } + + if (ack != WF_ACK) { + if (ack == -1) { + DPRINT (WF_DEBUG_IO, "cannot read ack for " + "0x%x [%s].\n", + cmd, wfcmd->action); + return 1; + + } else { + int err = -1; /* something unknown */ + + if (ack == 0xff) { /* explicit error */ + + if ((err = wavefront_read (dev)) == -1) { + DPRINT (WF_DEBUG_DATA, + "cannot read err " + "for 0x%x [%s].\n", + cmd, wfcmd->action); + } + } + + DPRINT (WF_DEBUG_IO, "0x%x [%s] " + "failed (0x%x, 0x%x, %s)\n", + cmd, wfcmd->action, ack, err, + wavefront_errorstr (err)); + + return -err; + } + } + + DPRINT (WF_DEBUG_DATA, "ack received " + "for 0x%x [%s]\n", + cmd, wfcmd->action); + } else { + + DPRINT (WF_DEBUG_CMD, "0x%x [%s] does not need " + "ACK (%d,%d,%d)\n", + cmd, wfcmd->action, wfcmd->read_cnt, + wfcmd->write_cnt, wfcmd->need_ack); + } + + return 0; + +} + +/*********************************************************************** +WaveFront data munging + +Things here are weird. All data written to the board cannot +have its most significant bit set. Any data item with values +potentially > 0x7F (127) must be split across multiple bytes. + +Sometimes, we need to munge numeric values that are represented on +the x86 side as 8-32 bit values. Sometimes, we need to munge data +that is represented on the x86 side as an array of bytes. The most +efficient approach to handling both cases seems to be to use 2 +different functions for munging and 2 for de-munging. This avoids +weird casting and worrying about bit-level offsets. + +**********************************************************************/ + +static unsigned char * +munge_int32 (unsigned int src, + unsigned char *dst, + unsigned int dst_size) +{ + unsigned int i; + + for (i = 0; i < dst_size; i++) { + *dst = src & 0x7F; /* Mask high bit of LSB */ + src = src >> 7; /* Rotate Right 7 bits */ + /* Note: we leave the upper bits in place */ + + dst++; + }; + return dst; +}; + +static int +demunge_int32 (unsigned char* src, int src_size) + +{ + int i; + int outval = 0; + + for (i = src_size - 1; i >= 0; i--) { + outval=(outval<<7)+src[i]; + } + + return outval; +}; + +static +unsigned char * +munge_buf (unsigned char *src, unsigned char *dst, unsigned int dst_size) + +{ + unsigned int i; + unsigned int last = dst_size / 2; + + for (i = 0; i < last; i++) { + *dst++ = src[i] & 0x7f; + *dst++ = src[i] >> 7; + } + return dst; +} + +static +unsigned char * +demunge_buf (unsigned char *src, unsigned char *dst, unsigned int src_bytes) + +{ + int i; + unsigned char *end = src + src_bytes; + + end = src + src_bytes; + + /* NOTE: src and dst *CAN* point to the same address */ + + for (i = 0; src != end; i++) { + dst[i] = *src++; + dst[i] |= (*src++)<<7; + } + + return dst; +} + +/*********************************************************************** +WaveFront: sample, patch and program management. +***********************************************************************/ + +static int +wavefront_delete_sample (snd_wavefront_t *dev, int sample_num) + +{ + unsigned char wbuf[2]; + int x; + + wbuf[0] = sample_num & 0x7f; + wbuf[1] = sample_num >> 7; + + if ((x = snd_wavefront_cmd (dev, WFC_DELETE_SAMPLE, NULL, wbuf)) == 0) { + dev->sample_status[sample_num] = WF_ST_EMPTY; + } + + return x; +} + +static int +wavefront_get_sample_status (snd_wavefront_t *dev, int assume_rom) + +{ + int i; + unsigned char rbuf[32], wbuf[32]; + unsigned int sc_real, sc_alias, sc_multi; + + /* check sample status */ + + if (snd_wavefront_cmd (dev, WFC_GET_NSAMPLES, rbuf, wbuf)) { + snd_printk ("cannot request sample count.\n"); + return -1; + } + + sc_real = sc_alias = sc_multi = dev->samples_used = 0; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + + wbuf[0] = i & 0x7f; + wbuf[1] = i >> 7; + + if (snd_wavefront_cmd (dev, WFC_IDENTIFY_SAMPLE_TYPE, rbuf, wbuf)) { + snd_printk("cannot identify sample " + "type of slot %d\n", i); + dev->sample_status[i] = WF_ST_EMPTY; + continue; + } + + dev->sample_status[i] = (WF_SLOT_FILLED|rbuf[0]); + + if (assume_rom) { + dev->sample_status[i] |= WF_SLOT_ROM; + } + + switch (rbuf[0] & WF_ST_MASK) { + case WF_ST_SAMPLE: + sc_real++; + break; + case WF_ST_MULTISAMPLE: + sc_multi++; + break; + case WF_ST_ALIAS: + sc_alias++; + break; + case WF_ST_EMPTY: + break; + + default: + snd_printk ("unknown sample type for " + "slot %d (0x%x)\n", + i, rbuf[0]); + } + + if (rbuf[0] != WF_ST_EMPTY) { + dev->samples_used++; + } + } + + snd_printk ("%d samples used (%d real, %d aliases, %d multi), " + "%d empty\n", dev->samples_used, sc_real, sc_alias, sc_multi, + WF_MAX_SAMPLE - dev->samples_used); + + + return (0); + +} + +static int +wavefront_get_patch_status (snd_wavefront_t *dev) + +{ + unsigned char patchbuf[WF_PATCH_BYTES]; + unsigned char patchnum[2]; + wavefront_patch *p; + int i, x, cnt, cnt2; + + for (i = 0; i < WF_MAX_PATCH; i++) { + patchnum[0] = i & 0x7f; + patchnum[1] = i >> 7; + + if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PATCH, patchbuf, + patchnum)) == 0) { + + dev->patch_status[i] |= WF_SLOT_FILLED; + p = (wavefront_patch *) patchbuf; + dev->sample_status + [p->sample_number|(p->sample_msb<<7)] |= + WF_SLOT_USED; + + } else if (x == 3) { /* Bad patch number */ + dev->patch_status[i] = 0; + } else { + snd_printk ("upload patch " + "error 0x%x\n", x); + dev->patch_status[i] = 0; + return 1; + } + } + + /* program status has already filled in slot_used bits */ + + for (i = 0, cnt = 0, cnt2 = 0; i < WF_MAX_PATCH; i++) { + if (dev->patch_status[i] & WF_SLOT_FILLED) { + cnt++; + } + if (dev->patch_status[i] & WF_SLOT_USED) { + cnt2++; + } + + } + snd_printk ("%d patch slots filled, %d in use\n", cnt, cnt2); + + return (0); +} + +static int +wavefront_get_program_status (snd_wavefront_t *dev) + +{ + unsigned char progbuf[WF_PROGRAM_BYTES]; + wavefront_program prog; + unsigned char prognum; + int i, x, l, cnt; + + for (i = 0; i < WF_MAX_PROGRAM; i++) { + prognum = i; + + if ((x = snd_wavefront_cmd (dev, WFC_UPLOAD_PROGRAM, progbuf, + &prognum)) == 0) { + + dev->prog_status[i] |= WF_SLOT_USED; + + demunge_buf (progbuf, (unsigned char *) &prog, + WF_PROGRAM_BYTES); + + for (l = 0; l < WF_NUM_LAYERS; l++) { + if (prog.layer[l].mute) { + dev->patch_status + [prog.layer[l].patch_number] |= + WF_SLOT_USED; + } + } + } else if (x == 1) { /* Bad program number */ + dev->prog_status[i] = 0; + } else { + snd_printk ("upload program " + "error 0x%x\n", x); + dev->prog_status[i] = 0; + } + } + + for (i = 0, cnt = 0; i < WF_MAX_PROGRAM; i++) { + if (dev->prog_status[i]) { + cnt++; + } + } + + snd_printk ("%d programs slots in use\n", cnt); + + return (0); +} + +static int +wavefront_send_patch (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char buf[WF_PATCH_BYTES+2]; + unsigned char *bptr; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading patch %d\n", + header->number); + + dev->patch_status[header->number] |= WF_SLOT_FILLED; + + bptr = buf; + bptr = munge_int32 (header->number, buf, 2); + munge_buf ((unsigned char *)&header->hdr.p, bptr, WF_PATCH_BYTES); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PATCH, NULL, buf)) { + snd_printk ("download patch failed\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_send_program (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char buf[WF_PROGRAM_BYTES+1]; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading program %d\n", + header->number); + + dev->prog_status[header->number] = WF_SLOT_USED; + + /* XXX need to zero existing SLOT_USED bit for program_status[i] + where `i' is the program that's being (potentially) overwritten. + */ + + for (i = 0; i < WF_NUM_LAYERS; i++) { + if (header->hdr.pr.layer[i].mute) { + dev->patch_status[header->hdr.pr.layer[i].patch_number] |= + WF_SLOT_USED; + + /* XXX need to mark SLOT_USED for sample used by + patch_number, but this means we have to load it. Ick. + */ + } + } + + buf[0] = header->number; + munge_buf ((unsigned char *)&header->hdr.pr, &buf[1], WF_PROGRAM_BYTES); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_PROGRAM, NULL, buf)) { + snd_printk ("download patch failed\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_freemem (snd_wavefront_t *dev) + +{ + char rbuf[8]; + + if (snd_wavefront_cmd (dev, WFC_REPORT_FREE_MEMORY, rbuf, NULL)) { + snd_printk ("can't get memory stats.\n"); + return -1; + } else { + return demunge_int32 (rbuf, 4); + } +} + +static int +wavefront_send_sample (snd_wavefront_t *dev, + wavefront_patch_info *header, + u16 __user *dataptr, + int data_is_unsigned) + +{ + /* samples are downloaded via a 16-bit wide i/o port + (you could think of it as 2 adjacent 8-bit wide ports + but its less efficient that way). therefore, all + the blocksizes and so forth listed in the documentation, + and used conventionally to refer to sample sizes, + which are given in 8-bit units (bytes), need to be + divided by 2. + */ + + u16 sample_short; + u32 length; + u16 __user *data_end = NULL; + unsigned int i; + const unsigned int max_blksize = 4096/2; + unsigned int written; + unsigned int blocksize; + int dma_ack; + int blocknum; + unsigned char sample_hdr[WF_SAMPLE_HDR_BYTES]; + unsigned char *shptr; + int skip = 0; + int initial_skip = 0; + + DPRINT (WF_DEBUG_LOAD_PATCH, "sample %sdownload for slot %d, " + "type %d, %d bytes from 0x%lx\n", + header->size ? "" : "header ", + header->number, header->subkey, + header->size, + (unsigned long) header->dataptr); + + if (header->number == WAVEFRONT_FIND_FREE_SAMPLE_SLOT) { + int x; + + if ((x = wavefront_find_free_sample (dev)) < 0) { + return -ENOMEM; + } + snd_printk ("unspecified sample => %d\n", x); + header->number = x; + } + + if (header->size) { + + /* XXX it's a debatable point whether or not RDONLY semantics + on the ROM samples should cover just the sample data or + the sample header. For now, it only covers the sample data, + so anyone is free at all times to rewrite sample headers. + + My reason for this is that we have the sample headers + available in the WFB file for General MIDI, and so these + can always be reset if needed. The sample data, however, + cannot be recovered without a complete reset and firmware + reload of the ICS2115, which is a very expensive operation. + + So, doing things this way allows us to honor the notion of + "RESETSAMPLES" reasonably cheaply. Note however, that this + is done purely at user level: there is no WFB parser in + this driver, and so a complete reset (back to General MIDI, + or theoretically some other configuration) is the + responsibility of the user level library. + + To try to do this in the kernel would be a little + crazy: we'd need 158K of kernel space just to hold + a copy of the patch/program/sample header data. + */ + + if (dev->rom_samples_rdonly) { + if (dev->sample_status[header->number] & WF_SLOT_ROM) { + snd_printk ("sample slot %d " + "write protected\n", + header->number); + return -EACCES; + } + } + + wavefront_delete_sample (dev, header->number); + } + + if (header->size) { + dev->freemem = wavefront_freemem (dev); + + if (dev->freemem < (int)header->size) { + snd_printk ("insufficient memory to " + "load %d byte sample.\n", + header->size); + return -ENOMEM; + } + + } + + skip = WF_GET_CHANNEL(&header->hdr.s); + + if (skip > 0 && header->hdr.s.SampleResolution != LINEAR_16BIT) { + snd_printk ("channel selection only " + "possible on 16-bit samples"); + return -(EINVAL); + } + + switch (skip) { + case 0: + initial_skip = 0; + skip = 1; + break; + case 1: + initial_skip = 0; + skip = 2; + break; + case 2: + initial_skip = 1; + skip = 2; + break; + case 3: + initial_skip = 2; + skip = 3; + break; + case 4: + initial_skip = 3; + skip = 4; + break; + case 5: + initial_skip = 4; + skip = 5; + break; + case 6: + initial_skip = 5; + skip = 6; + break; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "channel selection: %d => " + "initial skip = %d, skip = %d\n", + WF_GET_CHANNEL (&header->hdr.s), + initial_skip, skip); + + /* Be safe, and zero the "Unused" bits ... */ + + WF_SET_CHANNEL(&header->hdr.s, 0); + + /* adjust size for 16 bit samples by dividing by two. We always + send 16 bits per write, even for 8 bit samples, so the length + is always half the size of the sample data in bytes. + */ + + length = header->size / 2; + + /* the data we're sent has not been munged, and in fact, the + header we have to send isn't just a munged copy either. + so, build the sample header right here. + */ + + shptr = &sample_hdr[0]; + + shptr = munge_int32 (header->number, shptr, 2); + + if (header->size) { + shptr = munge_int32 (length, shptr, 4); + } + + /* Yes, a 4 byte result doesn't contain all of the offset bits, + but the offset only uses 24 bits. + */ + + shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleStartOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.loopStartOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.loopEndOffset), + shptr, 4); + shptr = munge_int32 (*((u32 *) &header->hdr.s.sampleEndOffset), + shptr, 4); + + /* This one is truly weird. What kind of weirdo decided that in + a system dominated by 16 and 32 bit integers, they would use + a just 12 bits ? + */ + + shptr = munge_int32 (header->hdr.s.FrequencyBias, shptr, 3); + + /* Why is this nybblified, when the MSB is *always* zero ? + Anyway, we can't take address of bitfield, so make a + good-faith guess at where it starts. + */ + + shptr = munge_int32 (*(&header->hdr.s.FrequencyBias+1), + shptr, 2); + + if (snd_wavefront_cmd (dev, + header->size ? + WFC_DOWNLOAD_SAMPLE : WFC_DOWNLOAD_SAMPLE_HEADER, + NULL, sample_hdr)) { + snd_printk ("sample %sdownload refused.\n", + header->size ? "" : "header "); + return -(EIO); + } + + if (header->size == 0) { + goto sent; /* Sorry. Just had to have one somewhere */ + } + + data_end = dataptr + length; + + /* Do any initial skip over an unused channel's data */ + + dataptr += initial_skip; + + for (written = 0, blocknum = 0; + written < length; written += max_blksize, blocknum++) { + + if ((length - written) > max_blksize) { + blocksize = max_blksize; + } else { + /* round to nearest 16-byte value */ + blocksize = ((length-written+7)&~0x7); + } + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_BLOCK, NULL, NULL)) { + snd_printk ("download block " + "request refused.\n"); + return -(EIO); + } + + for (i = 0; i < blocksize; i++) { + + if (dataptr < data_end) { + + __get_user (sample_short, dataptr); + dataptr += skip; + + if (data_is_unsigned) { /* GUS ? */ + + if (WF_SAMPLE_IS_8BIT(&header->hdr.s)) { + + /* 8 bit sample + resolution, sign + extend both bytes. + */ + + ((unsigned char*) + &sample_short)[0] += 0x7f; + ((unsigned char*) + &sample_short)[1] += 0x7f; + + } else { + + /* 16 bit sample + resolution, sign + extend the MSB. + */ + + sample_short += 0x7fff; + } + } + + } else { + + /* In padding section of final block: + + Don't fetch unsupplied data from + user space, just continue with + whatever the final value was. + */ + } + + if (i < blocksize - 1) { + outw (sample_short, dev->block_port); + } else { + outw (sample_short, dev->last_block_port); + } + } + + /* Get "DMA page acknowledge", even though its really + nothing to do with DMA at all. + */ + + if ((dma_ack = wavefront_read (dev)) != WF_DMA_ACK) { + if (dma_ack == -1) { + snd_printk ("upload sample " + "DMA ack timeout\n"); + return -(EIO); + } else { + snd_printk ("upload sample " + "DMA ack error 0x%x\n", + dma_ack); + return -(EIO); + } + } + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_SAMPLE); + + /* Note, label is here because sending the sample header shouldn't + alter the sample_status info at all. + */ + + sent: + return (0); +} + +static int +wavefront_send_alias (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char alias_hdr[WF_ALIAS_BYTES]; + + DPRINT (WF_DEBUG_LOAD_PATCH, "download alias, %d is " + "alias for %d\n", + header->number, + header->hdr.a.OriginalSample); + + munge_int32 (header->number, &alias_hdr[0], 2); + munge_int32 (header->hdr.a.OriginalSample, &alias_hdr[2], 2); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleStartOffset), + &alias_hdr[4], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopStartOffset), + &alias_hdr[8], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.loopEndOffset), + &alias_hdr[12], 4); + munge_int32 (*((unsigned int *)&header->hdr.a.sampleEndOffset), + &alias_hdr[16], 4); + munge_int32 (header->hdr.a.FrequencyBias, &alias_hdr[20], 3); + munge_int32 (*(&header->hdr.a.FrequencyBias+1), &alias_hdr[23], 2); + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_SAMPLE_ALIAS, NULL, alias_hdr)) { + snd_printk ("download alias failed.\n"); + return -(EIO); + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_ALIAS); + + return (0); +} + +static int +wavefront_send_multisample (snd_wavefront_t *dev, wavefront_patch_info *header) +{ + int i; + int num_samples; + unsigned char *msample_hdr; + + msample_hdr = kmalloc(sizeof(WF_MSAMPLE_BYTES), GFP_KERNEL); + if (! msample_hdr) + return -ENOMEM; + + munge_int32 (header->number, &msample_hdr[0], 2); + + /* You'll recall at this point that the "number of samples" value + in a wavefront_multisample struct is actually the log2 of the + real number of samples. + */ + + num_samples = (1<<(header->hdr.ms.NumberOfSamples&7)); + msample_hdr[2] = (unsigned char) header->hdr.ms.NumberOfSamples; + + DPRINT (WF_DEBUG_LOAD_PATCH, "multi %d with %d=%d samples\n", + header->number, + header->hdr.ms.NumberOfSamples, + num_samples); + + for (i = 0; i < num_samples; i++) { + DPRINT(WF_DEBUG_LOAD_PATCH|WF_DEBUG_DATA, "sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + munge_int32 (header->hdr.ms.SampleNumber[i], + &msample_hdr[3+(i*2)], 2); + } + + /* Need a hack here to pass in the number of bytes + to be written to the synth. This is ugly, and perhaps + one day, I'll fix it. + */ + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_MULTISAMPLE, + (unsigned char *) (long) ((num_samples*2)+3), + msample_hdr)) { + snd_printk ("download of multisample failed.\n"); + kfree(msample_hdr); + return -(EIO); + } + + dev->sample_status[header->number] = (WF_SLOT_FILLED|WF_ST_MULTISAMPLE); + + kfree(msample_hdr); + return (0); +} + +static int +wavefront_fetch_multisample (snd_wavefront_t *dev, + wavefront_patch_info *header) +{ + int i; + unsigned char log_ns[1]; + unsigned char number[2]; + int num_samples; + + munge_int32 (header->number, number, 2); + + if (snd_wavefront_cmd (dev, WFC_UPLOAD_MULTISAMPLE, log_ns, number)) { + snd_printk ("upload multisample failed.\n"); + return -(EIO); + } + + DPRINT (WF_DEBUG_DATA, "msample %d has %d samples\n", + header->number, log_ns[0]); + + header->hdr.ms.NumberOfSamples = log_ns[0]; + + /* get the number of samples ... */ + + num_samples = (1 << log_ns[0]); + + for (i = 0; i < num_samples; i++) { + char d[2]; + int val; + + if ((val = wavefront_read (dev)) == -1) { + snd_printk ("upload multisample failed " + "during sample loop.\n"); + return -(EIO); + } + d[0] = val; + + if ((val = wavefront_read (dev)) == -1) { + snd_printk ("upload multisample failed " + "during sample loop.\n"); + return -(EIO); + } + d[1] = val; + + header->hdr.ms.SampleNumber[i] = + demunge_int32 ((unsigned char *) d, 2); + + DPRINT (WF_DEBUG_DATA, "msample sample[%d] = %d\n", + i, header->hdr.ms.SampleNumber[i]); + } + + return (0); +} + + +static int +wavefront_send_drum (snd_wavefront_t *dev, wavefront_patch_info *header) + +{ + unsigned char drumbuf[WF_DRUM_BYTES]; + wavefront_drum *drum = &header->hdr.d; + int i; + + DPRINT (WF_DEBUG_LOAD_PATCH, "downloading edrum for MIDI " + "note %d, patch = %d\n", + header->number, drum->PatchNumber); + + drumbuf[0] = header->number & 0x7f; + + for (i = 0; i < 4; i++) { + munge_int32 (((unsigned char *)drum)[i], &drumbuf[1+(i*2)], 2); + } + + if (snd_wavefront_cmd (dev, WFC_DOWNLOAD_EDRUM_PROGRAM, NULL, drumbuf)) { + snd_printk ("download drum failed.\n"); + return -(EIO); + } + + return (0); +} + +static int +wavefront_find_free_sample (snd_wavefront_t *dev) + +{ + int i; + + for (i = 0; i < WF_MAX_SAMPLE; i++) { + if (!(dev->sample_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + snd_printk ("no free sample slots!\n"); + return -1; +} + +#if 0 +static int +wavefront_find_free_patch (snd_wavefront_t *dev) + +{ + int i; + + for (i = 0; i < WF_MAX_PATCH; i++) { + if (!(dev->patch_status[i] & WF_SLOT_FILLED)) { + return i; + } + } + snd_printk ("no free patch slots!\n"); + return -1; +} +#endif + +static int +wavefront_load_patch (snd_wavefront_t *dev, const char __user *addr) +{ + wavefront_patch_info *header; + int err; + + header = kmalloc(sizeof(*header), GFP_KERNEL); + if (! header) + return -ENOMEM; + + if (copy_from_user (header, addr, sizeof(wavefront_patch_info) - + sizeof(wavefront_any))) { + snd_printk ("bad address for load patch.\n"); + err = -EFAULT; + goto __error; + } + + DPRINT (WF_DEBUG_LOAD_PATCH, "download " + "Sample type: %d " + "Sample number: %d " + "Sample size: %d\n", + header->subkey, + header->number, + header->size); + + switch (header->subkey) { + case WF_ST_SAMPLE: /* sample or sample_header, based on patch->size */ + + if (copy_from_user (&header->hdr.s, header->hdrptr, + sizeof (wavefront_sample))) { + err = -EFAULT; + break; + } + + err = wavefront_send_sample (dev, header, header->dataptr, 0); + break; + + case WF_ST_MULTISAMPLE: + + if (copy_from_user (&header->hdr.s, header->hdrptr, + sizeof (wavefront_multisample))) { + err = -EFAULT; + break; + } + + err = wavefront_send_multisample (dev, header); + break; + + case WF_ST_ALIAS: + + if (copy_from_user (&header->hdr.a, header->hdrptr, + sizeof (wavefront_alias))) { + err = -EFAULT; + break; + } + + err = wavefront_send_alias (dev, header); + break; + + case WF_ST_DRUM: + if (copy_from_user (&header->hdr.d, header->hdrptr, + sizeof (wavefront_drum))) { + err = -EFAULT; + break; + } + + err = wavefront_send_drum (dev, header); + break; + + case WF_ST_PATCH: + if (copy_from_user (&header->hdr.p, header->hdrptr, + sizeof (wavefront_patch))) { + err = -EFAULT; + break; + } + + err = wavefront_send_patch (dev, header); + break; + + case WF_ST_PROGRAM: + if (copy_from_user (&header->hdr.pr, header->hdrptr, + sizeof (wavefront_program))) { + err = -EFAULT; + break; + } + + err = wavefront_send_program (dev, header); + break; + + default: + snd_printk ("unknown patch type %d.\n", + header->subkey); + err = -EINVAL; + break; + } + + __error: + kfree(header); + return err; +} + +/*********************************************************************** +WaveFront: hardware-dependent interface +***********************************************************************/ + +static void +process_sample_hdr (u8 *buf) + +{ + wavefront_sample s; + u8 *ptr; + + ptr = buf; + + /* The board doesn't send us an exact copy of a "wavefront_sample" + in response to an Upload Sample Header command. Instead, we + have to convert the data format back into our data structure, + just as in the Download Sample command, where we have to do + something very similar in the reverse direction. + */ + + *((u32 *) &s.sampleStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.loopStartOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.loopEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.sampleEndOffset) = demunge_int32 (ptr, 4); ptr += 4; + *((u32 *) &s.FrequencyBias) = demunge_int32 (ptr, 3); ptr += 3; + + s.SampleResolution = *ptr & 0x3; + s.Loop = *ptr & 0x8; + s.Bidirectional = *ptr & 0x10; + s.Reverse = *ptr & 0x40; + + /* Now copy it back to where it came from */ + + memcpy (buf, (unsigned char *) &s, sizeof (wavefront_sample)); +} + +static int +wavefront_synth_control (snd_wavefront_card_t *acard, + wavefront_control *wc) + +{ + snd_wavefront_t *dev = &acard->wavefront; + unsigned char patchnumbuf[2]; + int i; + + DPRINT (WF_DEBUG_CMD, "synth control with " + "cmd 0x%x\n", wc->cmd); + + /* Pre-handling of or for various commands */ + + switch (wc->cmd) { + + case WFC_DISABLE_INTERRUPTS: + snd_printk ("interrupts disabled.\n"); + outb (0x80|0x20, dev->control_port); + dev->interrupts_are_midi = 1; + return 0; + + case WFC_ENABLE_INTERRUPTS: + snd_printk ("interrupts enabled.\n"); + outb (0x80|0x40|0x20, dev->control_port); + dev->interrupts_are_midi = 1; + return 0; + + case WFC_INTERRUPT_STATUS: + wc->rbuf[0] = dev->interrupts_are_midi; + return 0; + + case WFC_ROMSAMPLES_RDONLY: + dev->rom_samples_rdonly = wc->wbuf[0]; + wc->status = 0; + return 0; + + case WFC_IDENTIFY_SLOT_TYPE: + i = wc->wbuf[0] | (wc->wbuf[1] << 7); + if (i <0 || i >= WF_MAX_SAMPLE) { + snd_printk ("invalid slot ID %d\n", + i); + wc->status = EINVAL; + return -EINVAL; + } + wc->rbuf[0] = dev->sample_status[i]; + wc->status = 0; + return 0; + + case WFC_DEBUG_DRIVER: + dev->debug = wc->wbuf[0]; + snd_printk ("debug = 0x%x\n", dev->debug); + return 0; + + case WFC_UPLOAD_PATCH: + munge_int32 (*((u32 *) wc->wbuf), patchnumbuf, 2); + memcpy (wc->wbuf, patchnumbuf, 2); + break; + + case WFC_UPLOAD_MULTISAMPLE: + /* multisamples have to be handled differently, and + cannot be dealt with properly by snd_wavefront_cmd() alone. + */ + wc->status = wavefront_fetch_multisample + (dev, (wavefront_patch_info *) wc->rbuf); + return 0; + + case WFC_UPLOAD_SAMPLE_ALIAS: + snd_printk ("support for sample alias upload " + "being considered.\n"); + wc->status = EINVAL; + return -EINVAL; + } + + wc->status = snd_wavefront_cmd (dev, wc->cmd, wc->rbuf, wc->wbuf); + + /* Post-handling of certain commands. + + In particular, if the command was an upload, demunge the data + so that the user-level doesn't have to think about it. + */ + + if (wc->status == 0) { + switch (wc->cmd) { + /* intercept any freemem requests so that we know + we are always current with the user-level view + of things. + */ + + case WFC_REPORT_FREE_MEMORY: + dev->freemem = demunge_int32 (wc->rbuf, 4); + break; + + case WFC_UPLOAD_PATCH: + demunge_buf (wc->rbuf, wc->rbuf, WF_PATCH_BYTES); + break; + + case WFC_UPLOAD_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_PROGRAM_BYTES); + break; + + case WFC_UPLOAD_EDRUM_PROGRAM: + demunge_buf (wc->rbuf, wc->rbuf, WF_DRUM_BYTES - 1); + break; + + case WFC_UPLOAD_SAMPLE_HEADER: + process_sample_hdr (wc->rbuf); + break; + + case WFC_UPLOAD_SAMPLE_ALIAS: + snd_printk ("support for " + "sample aliases still " + "being considered.\n"); + break; + + case WFC_VMIDI_OFF: + snd_wavefront_midi_disable_virtual (acard); + break; + + case WFC_VMIDI_ON: + snd_wavefront_midi_enable_virtual (acard); + break; + } + } + + return 0; +} + +int +snd_wavefront_synth_open (snd_hwdep_t *hw, struct file *file) + +{ + if (!try_module_get(hw->card->module)) + return -EFAULT; + file->private_data = hw; + return 0; +} + +int +snd_wavefront_synth_release (snd_hwdep_t *hw, struct file *file) + +{ + module_put(hw->card->module); + return 0; +} + +int +snd_wavefront_synth_ioctl (snd_hwdep_t *hw, struct file *file, + unsigned int cmd, unsigned long arg) + +{ + snd_card_t *card; + snd_wavefront_t *dev; + snd_wavefront_card_t *acard; + wavefront_control *wc; + void __user *argp = (void __user *)arg; + int err; + + card = (snd_card_t *) hw->card; + + snd_assert(card != NULL, return -ENODEV); + + snd_assert(card->private_data != NULL, return -ENODEV); + + acard = card->private_data; + dev = &acard->wavefront; + + switch (cmd) { + case WFCTL_LOAD_SPP: + if (wavefront_load_patch (dev, argp) != 0) { + return -EIO; + } + break; + + case WFCTL_WFCMD: + wc = kmalloc(sizeof(*wc), GFP_KERNEL); + if (! wc) + return -ENOMEM; + if (copy_from_user (wc, argp, sizeof (*wc))) + err = -EFAULT; + else if (wavefront_synth_control (acard, wc) < 0) + err = -EIO; + else if (copy_to_user (argp, wc, sizeof (*wc))) + err = -EFAULT; + else + err = 0; + kfree(wc); + return err; + + default: + return -EINVAL; + } + + return 0; +} + + +/***********************************************************************/ +/* WaveFront: interface for card-level wavefront module */ +/***********************************************************************/ + +void +snd_wavefront_internal_interrupt (snd_wavefront_card_t *card) +{ + snd_wavefront_t *dev = &card->wavefront; + + /* + Some comments on interrupts. I attempted a version of this + driver that used interrupts throughout the code instead of + doing busy and/or sleep-waiting. Alas, it appears that once + the Motorola firmware is downloaded, the card *never* + generates an RX interrupt. These are successfully generated + during firmware loading, and after that wavefront_status() + reports that an interrupt is pending on the card from time + to time, but it never seems to be delivered to this + driver. Note also that wavefront_status() continues to + report that RX interrupts are enabled, suggesting that I + didn't goof up and disable them by mistake. + + Thus, I stepped back to a prior version of + wavefront_wait(), the only place where this really + matters. Its sad, but I've looked through the code to check + on things, and I really feel certain that the Motorola + firmware prevents RX-ready interrupts. + */ + + if ((wavefront_status(dev) & (STAT_INTR_READ|STAT_INTR_WRITE)) == 0) { + return; + } + + spin_lock(&dev->irq_lock); + dev->irq_ok = 1; + dev->irq_cnt++; + spin_unlock(&dev->irq_lock); + wake_up(&dev->interrupt_sleeper); +} + +/* STATUS REGISTER + +0 Host Rx Interrupt Enable (1=Enabled) +1 Host Rx Register Full (1=Full) +2 Host Rx Interrupt Pending (1=Interrupt) +3 Unused +4 Host Tx Interrupt (1=Enabled) +5 Host Tx Register empty (1=Empty) +6 Host Tx Interrupt Pending (1=Interrupt) +7 Unused +*/ + +static int __init +snd_wavefront_interrupt_bits (int irq) + +{ + int bits; + + switch (irq) { + case 9: + bits = 0x00; + break; + case 5: + bits = 0x08; + break; + case 12: + bits = 0x10; + break; + case 15: + bits = 0x18; + break; + + default: + snd_printk ("invalid IRQ %d\n", irq); + bits = -1; + } + + return bits; +} + +static void __init +wavefront_should_cause_interrupt (snd_wavefront_t *dev, + int val, int port, int timeout) + +{ + wait_queue_t wait; + + init_waitqueue_entry(&wait, current); + spin_lock_irq(&dev->irq_lock); + add_wait_queue(&dev->interrupt_sleeper, &wait); + dev->irq_ok = 0; + outb (val,port); + spin_unlock_irq(&dev->irq_lock); + while (1) { + set_current_state(TASK_INTERRUPTIBLE); + if ((timeout = schedule_timeout(timeout)) == 0) + return; + if (dev->irq_ok) + return; + } +} + +static int __init +wavefront_reset_to_cleanliness (snd_wavefront_t *dev) + +{ + int bits; + int hwv[2]; + + /* IRQ already checked */ + + bits = snd_wavefront_interrupt_bits (dev->irq); + + /* try reset of port */ + + outb (0x0, dev->control_port); + + /* At this point, the board is in reset, and the H/W initialization + register is accessed at the same address as the data port. + + Bit 7 - Enable IRQ Driver + 0 - Tri-state the Wave-Board drivers for the PC Bus IRQs + 1 - Enable IRQ selected by bits 5:3 to be driven onto the PC Bus. + + Bit 6 - MIDI Interface Select + + 0 - Use the MIDI Input from the 26-pin WaveBlaster + compatible header as the serial MIDI source + 1 - Use the MIDI Input from the 9-pin D connector as the + serial MIDI source. + + Bits 5:3 - IRQ Selection + 0 0 0 - IRQ 2/9 + 0 0 1 - IRQ 5 + 0 1 0 - IRQ 12 + 0 1 1 - IRQ 15 + 1 0 0 - Reserved + 1 0 1 - Reserved + 1 1 0 - Reserved + 1 1 1 - Reserved + + Bits 2:1 - Reserved + Bit 0 - Disable Boot ROM + 0 - memory accesses to 03FC30-03FFFFH utilize the internal Boot ROM + 1 - memory accesses to 03FC30-03FFFFH are directed to external + storage. + + */ + + /* configure hardware: IRQ, enable interrupts, + plus external 9-pin MIDI interface selected + */ + + outb (0x80 | 0x40 | bits, dev->data_port); + + /* CONTROL REGISTER + + 0 Host Rx Interrupt Enable (1=Enabled) 0x1 + 1 Unused 0x2 + 2 Unused 0x4 + 3 Unused 0x8 + 4 Host Tx Interrupt Enable 0x10 + 5 Mute (0=Mute; 1=Play) 0x20 + 6 Master Interrupt Enable (1=Enabled) 0x40 + 7 Master Reset (0=Reset; 1=Run) 0x80 + + Take us out of reset, mute output, master + TX + RX interrupts on. + + We'll get an interrupt presumably to tell us that the TX + register is clear. + */ + + wavefront_should_cause_interrupt(dev, 0x80|0x40|0x10|0x1, + dev->control_port, + (reset_time*HZ)/100); + + /* Note: data port is now the data port, not the h/w initialization + port. + */ + + if (!dev->irq_ok) { + snd_printk ("intr not received after h/w un-reset.\n"); + goto gone_bad; + } + + /* Note: data port is now the data port, not the h/w initialization + port. + + At this point, only "HW VERSION" or "DOWNLOAD OS" commands + will work. So, issue one of them, and wait for TX + interrupt. This can take a *long* time after a cold boot, + while the ISC ROM does its RAM test. The SDK says up to 4 + seconds - with 12MB of RAM on a Tropez+, it takes a lot + longer than that (~16secs). Note that the card understands + the difference between a warm and a cold boot, so + subsequent ISC2115 reboots (say, caused by module + reloading) will get through this much faster. + + XXX Interesting question: why is no RX interrupt received first ? + */ + + wavefront_should_cause_interrupt(dev, WFC_HARDWARE_VERSION, + dev->data_port, ramcheck_time*HZ); + + if (!dev->irq_ok) { + snd_printk ("post-RAM-check interrupt not received.\n"); + goto gone_bad; + } + + if (!wavefront_wait (dev, STAT_CAN_READ)) { + snd_printk ("no response to HW version cmd.\n"); + goto gone_bad; + } + + if ((hwv[0] = wavefront_read (dev)) == -1) { + snd_printk ("board not responding correctly.\n"); + goto gone_bad; + } + + if (hwv[0] == 0xFF) { /* NAK */ + + /* Board's RAM test failed. Try to read error code, + and tell us about it either way. + */ + + if ((hwv[0] = wavefront_read (dev)) == -1) { + snd_printk ("on-board RAM test failed " + "(bad error code).\n"); + } else { + snd_printk ("on-board RAM test failed " + "(error code: 0x%x).\n", + hwv[0]); + } + goto gone_bad; + } + + /* We're OK, just get the next byte of the HW version response */ + + if ((hwv[1] = wavefront_read (dev)) == -1) { + snd_printk ("incorrect h/w response.\n"); + goto gone_bad; + } + + snd_printk ("hardware version %d.%d\n", + hwv[0], hwv[1]); + + return 0; + + + gone_bad: + return (1); +} + +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/unistd.h> +#include <linux/syscalls.h> +#include <asm/uaccess.h> + + +static int __init +wavefront_download_firmware (snd_wavefront_t *dev, char *path) + +{ + unsigned char section[WF_SECTION_MAX]; + signed char section_length; /* yes, just a char; max value is WF_SECTION_MAX */ + int section_cnt_downloaded = 0; + int fd; + int c; + int i; + mm_segment_t fs; + + /* This tries to be a bit cleverer than the stuff Alan Cox did for + the generic sound firmware, in that it actually knows + something about the structure of the Motorola firmware. In + particular, it uses a version that has been stripped of the + 20K of useless header information, and had section lengths + added, making it possible to load the entire OS without any + [kv]malloc() activity, since the longest entity we ever read is + 42 bytes (well, WF_SECTION_MAX) long. + */ + + fs = get_fs(); + set_fs (get_ds()); + + if ((fd = sys_open ((char __user *) path, 0, 0)) < 0) { + snd_printk ("Unable to load \"%s\".\n", + path); + return 1; + } + + while (1) { + int x; + + if ((x = sys_read (fd, (char __user *) §ion_length, sizeof (section_length))) != + sizeof (section_length)) { + snd_printk ("firmware read error.\n"); + goto failure; + } + + if (section_length == 0) { + break; + } + + if (section_length < 0 || section_length > WF_SECTION_MAX) { + snd_printk ("invalid firmware section length %d\n", + section_length); + goto failure; + } + + if (sys_read (fd, (char __user *) section, section_length) != section_length) { + snd_printk ("firmware section " + "read error.\n"); + goto failure; + } + + /* Send command */ + + if (wavefront_write (dev, WFC_DOWNLOAD_OS)) { + goto failure; + } + + for (i = 0; i < section_length; i++) { + if (wavefront_write (dev, section[i])) { + goto failure; + } + } + + /* get ACK */ + + if (wavefront_wait (dev, STAT_CAN_READ)) { + + if ((c = inb (dev->data_port)) != WF_ACK) { + + snd_printk ("download " + "of section #%d not " + "acknowledged, ack = 0x%x\n", + section_cnt_downloaded + 1, c); + goto failure; + + } + + } else { + snd_printk ("time out for firmware ACK.\n"); + goto failure; + } + + } + + sys_close (fd); + set_fs (fs); + return 0; + + failure: + sys_close (fd); + set_fs (fs); + snd_printk ("firmware download failed!!!\n"); + return 1; +} + + +static int __init +wavefront_do_reset (snd_wavefront_t *dev) + +{ + char voices[1]; + + if (wavefront_reset_to_cleanliness (dev)) { + snd_printk ("hw reset failed.\n"); + goto gone_bad; + } + + if (dev->israw) { + if (wavefront_download_firmware (dev, ospath)) { + goto gone_bad; + } + + dev->israw = 0; + + /* Wait for the OS to get running. The protocol for + this is non-obvious, and was determined by + using port-IO tracing in DOSemu and some + experimentation here. + + Rather than using timed waits, use interrupts creatively. + */ + + wavefront_should_cause_interrupt (dev, WFC_NOOP, + dev->data_port, + (osrun_time*HZ)); + + if (!dev->irq_ok) { + snd_printk ("no post-OS interrupt.\n"); + goto gone_bad; + } + + /* Now, do it again ! */ + + wavefront_should_cause_interrupt (dev, WFC_NOOP, + dev->data_port, (10*HZ)); + + if (!dev->irq_ok) { + snd_printk ("no post-OS interrupt(2).\n"); + goto gone_bad; + } + + /* OK, no (RX/TX) interrupts any more, but leave mute + in effect. + */ + + outb (0x80|0x40, dev->control_port); + } + + /* SETUPSND.EXE asks for sample memory config here, but since i + have no idea how to interpret the result, we'll forget + about it. + */ + + if ((dev->freemem = wavefront_freemem (dev)) < 0) { + goto gone_bad; + } + + snd_printk ("available DRAM %dk\n", dev->freemem / 1024); + + if (wavefront_write (dev, 0xf0) || + wavefront_write (dev, 1) || + (wavefront_read (dev) < 0)) { + dev->debug = 0; + snd_printk ("MPU emulation mode not set.\n"); + goto gone_bad; + } + + voices[0] = 32; + + if (snd_wavefront_cmd (dev, WFC_SET_NVOICES, NULL, voices)) { + snd_printk ("cannot set number of voices to 32.\n"); + goto gone_bad; + } + + + return 0; + + gone_bad: + /* reset that sucker so that it doesn't bother us. */ + + outb (0x0, dev->control_port); + dev->interrupts_are_midi = 0; + return 1; +} + +int __init +snd_wavefront_start (snd_wavefront_t *dev) + +{ + int samples_are_from_rom; + + /* IMPORTANT: assumes that snd_wavefront_detect() and/or + wavefront_reset_to_cleanliness() has already been called + */ + + if (dev->israw) { + samples_are_from_rom = 1; + } else { + /* XXX is this always true ? */ + samples_are_from_rom = 0; + } + + if (dev->israw || fx_raw) { + if (wavefront_do_reset (dev)) { + return -1; + } + } + /* Check for FX device, present only on Tropez+ */ + + dev->has_fx = (snd_wavefront_fx_detect (dev) == 0); + + if (dev->has_fx && fx_raw) { + snd_wavefront_fx_start (dev); + } + + wavefront_get_sample_status (dev, samples_are_from_rom); + wavefront_get_program_status (dev); + wavefront_get_patch_status (dev); + + /* Start normal operation: unreset, master interrupt enabled, no mute + */ + + outb (0x80|0x40|0x20, dev->control_port); + + return (0); +} + +int __init +snd_wavefront_detect (snd_wavefront_card_t *card) + +{ + unsigned char rbuf[4], wbuf[4]; + snd_wavefront_t *dev = &card->wavefront; + + /* returns zero if a WaveFront card is successfully detected. + negative otherwise. + */ + + dev->israw = 0; + dev->has_fx = 0; + dev->debug = debug_default; + dev->interrupts_are_midi = 0; + dev->irq_cnt = 0; + dev->rom_samples_rdonly = 1; + + if (snd_wavefront_cmd (dev, WFC_FIRMWARE_VERSION, rbuf, wbuf) == 0) { + + dev->fw_version[0] = rbuf[0]; + dev->fw_version[1] = rbuf[1]; + + snd_printk ("firmware %d.%d already loaded.\n", + rbuf[0], rbuf[1]); + + /* check that a command actually works */ + + if (snd_wavefront_cmd (dev, WFC_HARDWARE_VERSION, + rbuf, wbuf) == 0) { + dev->hw_version[0] = rbuf[0]; + dev->hw_version[1] = rbuf[1]; + } else { + snd_printk ("not raw, but no " + "hardware version!\n"); + return -1; + } + + if (!wf_raw) { + return 0; + } else { + snd_printk ("reloading firmware as you requested.\n"); + dev->israw = 1; + } + + } else { + + dev->israw = 1; + snd_printk ("no response to firmware probe, assume raw.\n"); + + } + + return 0; +} |