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/ad1848 |
Linux-2.6.12-rc2v2.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/ad1848')
-rw-r--r-- | sound/isa/ad1848/Makefile | 15 | ||||
-rw-r--r-- | sound/isa/ad1848/ad1848.c | 151 | ||||
-rw-r--r-- | sound/isa/ad1848/ad1848_lib.c | 1279 |
3 files changed, 1445 insertions, 0 deletions
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; + |