summaryrefslogtreecommitdiff
path: root/linux/sound
diff options
context:
space:
mode:
authorMauro Carvalho Chehab <mchehab@infradead.org>2006-08-31 15:12:45 -0300
committerMauro Carvalho Chehab <mchehab@infradead.org>2006-08-31 15:12:45 -0300
commitf95e98a1d581ce9770a01fd43d1a8e61161a8e46 (patch)
tree646d2a7875583b08c610629bec9537aa0deb438e /linux/sound
parent536149a42a489ca35bbb426f69504f58147152c8 (diff)
downloadmediapointer-dvb-s2-f95e98a1d581ce9770a01fd43d1a8e61161a8e46.tar.gz
mediapointer-dvb-s2-f95e98a1d581ce9770a01fd43d1a8e61161a8e46.tar.bz2
Added audio-related stuff used on a few multimedia boards
From: Mauro Carvalho Chehab <mchehab@infradead.org> Those files are not part of V4L stuff. However, they are or dependent of V4L apis or used on some video boards. After this patch, it will be possible to compile and test they against changes at V4L API or drivers. Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'linux/sound')
-rw-r--r--linux/sound/i2c/other/tea575x-tuner.c233
-rw-r--r--linux/sound/oss/aci.c712
-rw-r--r--linux/sound/oss/aci.h57
-rw-r--r--linux/sound/oss/btaudio.c1138
4 files changed, 2140 insertions, 0 deletions
diff --git a/linux/sound/i2c/other/tea575x-tuner.c b/linux/sound/i2c/other/tea575x-tuner.c
new file mode 100644
index 000000000..1a4f39b8d
--- /dev/null
+++ b/linux/sound/i2c/other/tea575x-tuner.c
@@ -0,0 +1,233 @@
+/*
+ * ALSA driver for TEA5757/5759 Philips AM/FM radio tuner chips
+ *
+ * Copyright (c) 2004 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/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <sound/core.h>
+#include <sound/tea575x-tuner.h>
+
+MODULE_AUTHOR("Jaroslav Kysela <perex@suse.cz>");
+MODULE_DESCRIPTION("Routines for control of TEA5757/5759 Philips AM/FM radio tuner chips");
+MODULE_LICENSE("GPL");
+
+/*
+ * definitions
+ */
+
+#define TEA575X_BIT_SEARCH (1<<24) /* 1 = search action, 0 = tuned */
+#define TEA575X_BIT_UPDOWN (1<<23) /* 0 = search down, 1 = search up */
+#define TEA575X_BIT_MONO (1<<22) /* 0 = stereo, 1 = mono */
+#define TEA575X_BIT_BAND_MASK (3<<20)
+#define TEA575X_BIT_BAND_FM (0<<20)
+#define TEA575X_BIT_BAND_MW (1<<20)
+#define TEA575X_BIT_BAND_LW (1<<21)
+#define TEA575X_BIT_BAND_SW (1<<22)
+#define TEA575X_BIT_PORT_0 (1<<19) /* user bit */
+#define TEA575X_BIT_PORT_1 (1<<18) /* user bit */
+#define TEA575X_BIT_SEARCH_MASK (3<<16) /* search level */
+#define TEA575X_BIT_SEARCH_5_28 (0<<16) /* FM >5uV, AM >28uV */
+#define TEA575X_BIT_SEARCH_10_40 (1<<16) /* FM >10uV, AM > 40uV */
+#define TEA575X_BIT_SEARCH_30_63 (2<<16) /* FM >30uV, AM > 63uV */
+#define TEA575X_BIT_SEARCH_150_1000 (3<<16) /* FM > 150uV, AM > 1000uV */
+#define TEA575X_BIT_DUMMY (1<<15) /* buffer */
+#define TEA575X_BIT_FREQ_MASK 0x7fff
+
+/*
+ * lowlevel part
+ */
+
+static void snd_tea575x_set_freq(struct snd_tea575x *tea)
+{
+ unsigned long freq;
+
+ freq = tea->freq / 16; /* to kHz */
+ if (freq > 108000)
+ freq = 108000;
+ if (freq < 87000)
+ freq = 87000;
+ /* crystal fixup */
+ if (tea->tea5759)
+ freq -= tea->freq_fixup;
+ else
+ freq += tea->freq_fixup;
+ /* freq /= 12.5 */
+ freq *= 10;
+ freq /= 125;
+
+ tea->val &= ~TEA575X_BIT_FREQ_MASK;
+ tea->val |= freq & TEA575X_BIT_FREQ_MASK;
+ tea->ops->write(tea, tea->val);
+}
+
+/*
+ * Linux Video interface
+ */
+
+static int snd_tea575x_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long data)
+{
+ struct video_device *dev = video_devdata(file);
+ struct snd_tea575x *tea = video_get_drvdata(dev);
+ void __user *arg = (void __user *)data;
+
+ switch(cmd) {
+ case VIDIOCGCAP:
+ {
+ struct video_capability v;
+ v.type = VID_TYPE_TUNER;
+ v.channels = 1;
+ v.audios = 1;
+ /* No we don't do pictures */
+ v.maxwidth = 0;
+ v.maxheight = 0;
+ v.minwidth = 0;
+ v.minheight = 0;
+ strcpy(v.name, tea->tea5759 ? "TEA5759" : "TEA5757");
+ if (copy_to_user(arg,&v,sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCGTUNER:
+ {
+ struct video_tuner v;
+ if (copy_from_user(&v, arg,sizeof(v))!=0)
+ return -EFAULT;
+ if (v.tuner) /* Only 1 tuner */
+ return -EINVAL;
+ v.rangelow = (87*16000);
+ v.rangehigh = (108*16000);
+ v.flags = VIDEO_TUNER_LOW;
+ v.mode = VIDEO_MODE_AUTO;
+ strcpy(v.name, "FM");
+ v.signal = 0xFFFF;
+ if (copy_to_user(arg, &v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSTUNER:
+ {
+ struct video_tuner v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if(v.tuner!=0)
+ return -EINVAL;
+ /* Only 1 tuner so no setting needed ! */
+ return 0;
+ }
+ case VIDIOCGFREQ:
+ if(copy_to_user(arg, &tea->freq, sizeof(tea->freq)))
+ return -EFAULT;
+ return 0;
+ case VIDIOCSFREQ:
+ if(copy_from_user(&tea->freq, arg, sizeof(tea->freq)))
+ return -EFAULT;
+ snd_tea575x_set_freq(tea);
+ return 0;
+ case VIDIOCGAUDIO:
+ {
+ struct video_audio v;
+ memset(&v, 0, sizeof(v));
+ strcpy(v.name, "Radio");
+ if(copy_to_user(arg,&v, sizeof(v)))
+ return -EFAULT;
+ return 0;
+ }
+ case VIDIOCSAUDIO:
+ {
+ struct video_audio v;
+ if(copy_from_user(&v, arg, sizeof(v)))
+ return -EFAULT;
+ if(v.audio)
+ return -EINVAL;
+ return 0;
+ }
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static void snd_tea575x_release(struct video_device *vfd)
+{
+}
+
+/*
+ * initialize all the tea575x chips
+ */
+void snd_tea575x_init(struct snd_tea575x *tea)
+{
+ unsigned int val;
+
+ val = tea->ops->read(tea);
+ if (val == 0x1ffffff || val == 0) {
+ snd_printk(KERN_ERR "Cannot find TEA575x chip\n");
+ return;
+ }
+
+ memset(&tea->vd, 0, sizeof(tea->vd));
+ tea->vd.owner = tea->card->module;
+ strcpy(tea->vd.name, tea->tea5759 ? "TEA5759 radio" : "TEA5757 radio");
+ tea->vd.type = VID_TYPE_TUNER;
+ tea->vd.hardware = VID_HARDWARE_RTRACK; /* FIXME: assign new number */
+ tea->vd.release = snd_tea575x_release;
+ video_set_drvdata(&tea->vd, tea);
+ tea->vd.fops = &tea->fops;
+ tea->fops.owner = tea->card->module;
+ tea->fops.open = video_exclusive_open;
+ tea->fops.release = video_exclusive_release;
+ tea->fops.ioctl = snd_tea575x_ioctl;
+ if (video_register_device(&tea->vd, VFL_TYPE_RADIO, tea->dev_nr - 1) < 0) {
+ snd_printk(KERN_ERR "unable to register tea575x tuner\n");
+ return;
+ }
+ tea->vd_registered = 1;
+
+ tea->val = TEA575X_BIT_BAND_FM | TEA575X_BIT_SEARCH_10_40;
+ tea->freq = 90500 * 16; /* 90.5Mhz default */
+
+ snd_tea575x_set_freq(tea);
+}
+
+void snd_tea575x_exit(struct snd_tea575x *tea)
+{
+ if (tea->vd_registered) {
+ video_unregister_device(&tea->vd);
+ tea->vd_registered = 0;
+ }
+}
+
+static int __init alsa_tea575x_module_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_tea575x_module_exit(void)
+{
+}
+
+module_init(alsa_tea575x_module_init)
+module_exit(alsa_tea575x_module_exit)
+
+EXPORT_SYMBOL(snd_tea575x_init);
+EXPORT_SYMBOL(snd_tea575x_exit);
diff --git a/linux/sound/oss/aci.c b/linux/sound/oss/aci.c
new file mode 100644
index 000000000..97634d457
--- /dev/null
+++ b/linux/sound/oss/aci.c
@@ -0,0 +1,712 @@
+/*
+ * Audio Command Interface (ACI) driver (sound/aci.c)
+ *
+ * ACI is a protocol used to communicate with the microcontroller on
+ * some sound cards produced by miro, e.g. the miroSOUND PCM12 and
+ * PCM20. The ACI has been developed for miro by Norberto Pellicci
+ * <pellicci@home.com>. Special thanks to both him and miro for
+ * providing the ACI specification.
+ *
+ * The main function of the ACI is to control the mixer and to get a
+ * product identification. On the PCM20, ACI also controls the radio
+ * tuner on this card, this is supported in the Video for Linux
+ * miropcm20 driver.
+ * -
+ * This is a fullfeatured implementation. Unsupported features
+ * are bugs... (:
+ *
+ * It is not longer necessary to load the mad16 module first. The
+ * user is currently responsible to set the mad16 mixer correctly.
+ *
+ * To toggle the solo mode for full duplex operation just use the OSS
+ * record switch for the pcm ('wave') controller. Robert
+ * -
+ *
+ * Revision history:
+ *
+ * 1995-11-10 Markus Kuhn <mskuhn@cip.informatik.uni-erlangen.de>
+ * First version written.
+ * 1995-12-31 Markus Kuhn
+ * Second revision, general code cleanup.
+ * 1996-05-16 Hannu Savolainen
+ * Integrated with other parts of the driver.
+ * 1996-05-28 Markus Kuhn
+ * Initialize CS4231A mixer, make ACI first mixer,
+ * use new private mixer API for solo mode.
+ * 1998-08-18 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
+ * Small modification to export ACI functions and
+ * complete modularisation.
+ * 2000-06-20 Robert Siemer <Robert.Siemer@gmx.de>
+ * Don't initialize the CS4231A mixer anymore, so the code is
+ * working again, and other small changes to fit in todays
+ * kernels.
+ * 2000-08-26 Robert Siemer
+ * Clean up and rewrite for 2.4.x. Maybe it's SMP safe now... (:
+ * ioctl bugfix, and integration of solo-mode into OSS-API,
+ * added (OSS-limited) equalizer support, return value bugfix,
+ * changed param aci_reset to reset, new params: ide, wss.
+ * 2001-04-20 Robert Siemer
+ * even more cleanups...
+ * 2001-10-08 Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * Get rid of check_region, .bss optimizations, use set_current_state
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include "sound_config.h"
+
+int aci_port; /* as determined by bit 4 in the OPTi 929 MC4 register */
+static int aci_idcode[2]; /* manufacturer and product ID */
+int aci_version; /* ACI firmware version */
+
+EXPORT_SYMBOL(aci_port);
+EXPORT_SYMBOL(aci_version);
+
+#include "aci.h"
+
+
+static int aci_solo; /* status bit of the card that can't be *
+ * checked with ACI versions prior to 0xb0 */
+static int aci_amp; /* status bit for power-amp/line-out level
+ but I have no docs about what is what... */
+static int aci_micpreamp=3; /* microphone preamp-level that can't be *
+ * checked with ACI versions prior to 0xb0 */
+
+static int mixer_device;
+static struct mutex aci_mutex;
+
+#ifdef MODULE
+static int reset;
+module_param(reset, bool, 0);
+MODULE_PARM_DESC(reset,"When set to 1, reset aci mixer.");
+#else
+static int reset = 1;
+#endif
+
+static int ide=-1;
+module_param(ide, int, 0);
+MODULE_PARM_DESC(ide,"1 enable, 0 disable ide-port - untested"
+ " default: do nothing");
+static int wss=-1;
+module_param(wss, int, 0);
+MODULE_PARM_DESC(wss,"change between ACI/WSS-mixer; use 0 and 1 - untested"
+ " default: do nothing; for PCM1-pro only");
+
+#ifdef DEBUG
+static void print_bits(unsigned char c)
+{
+ int j;
+ printk(KERN_DEBUG "aci: ");
+
+ for (j=7; j>=0; j--) {
+ printk("%d", (c >> j) & 0x1);
+ }
+
+ printk("\n");
+}
+#endif
+
+/*
+ * This busy wait code normally requires less than 15 loops and
+ * practically always less than 100 loops on my i486/DX2 66 MHz.
+ *
+ * Warning: Waiting on the general status flag after reseting the MUTE
+ * function can take a VERY long time, because the PCM12 does some kind
+ * of fade-in effect. For this reason, access to the MUTE function has
+ * not been implemented at all.
+ *
+ * - The OSS interface has no mute option. It takes about 3 seconds to
+ * fade-in on my PCM20. busy_wait() handles it great now... Robert
+ */
+
+static int busy_wait(void)
+{
+ #define MINTIME 500
+ long timeout;
+ unsigned char byte;
+
+ for (timeout = 1; timeout <= MINTIME+30; timeout++) {
+ if (((byte=inb(BUSY_REGISTER)) & 1) == 0) {
+ if (timeout >= MINTIME)
+ printk(KERN_DEBUG "aci: Got READYFLAG in round %ld.\n", timeout-MINTIME);
+ return byte;
+ }
+ if (timeout >= MINTIME) {
+ long out=10*HZ;
+ switch (timeout-MINTIME) {
+ case 0 ... 9:
+ out /= 10;
+ case 10 ... 19:
+ out /= 10;
+ case 20 ... 30:
+ out /= 10;
+ default:
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(out);
+ break;
+ }
+ }
+ }
+ printk(KERN_WARNING "aci: busy_wait() time out.\n");
+ return -EBUSY;
+}
+
+/* The four ACI command types are fucked up. [-:
+ * implied is: 1w - special case for INIT
+ * write is: 2w1r
+ * read is: x(1w1r) where x is 1 or 2 (1 CHECK_SIG, 1 CHECK_STER,
+ * 1 VERSION, 2 IDCODE)
+ * the command is only in the first write, rest is protocol overhead
+ *
+ * indexed is technically a write and used for STATUS
+ * and the special case for TUNE is: 3w1r
+ *
+ * Here the new general sheme: TUNE --> aci_rw_cmd(x, y, z)
+ * indexed and write --> aci_rw_cmd(x, y, -1)
+ * implied and read (x=1) --> aci_rw_cmd(x, -1, -1)
+ *
+ * Read (x>=2) is not implemented (only used during initialization).
+ * Use aci_idcode[2] and aci_version... Robert
+ */
+
+/* Some notes for error detection: theoretically it is possible.
+ * But it doubles the I/O-traffic from ww(r) to wwwrw(r) in the normal
+ * case and doesn't seem to be designed for that... Robert
+ */
+
+static inline int aci_rawwrite(unsigned char byte)
+{
+ if (busy_wait() >= 0) {
+#ifdef DEBUG
+ printk(KERN_DEBUG "aci_rawwrite(%d)\n", byte);
+#endif
+ outb(byte, COMMAND_REGISTER);
+ return 0;
+ } else
+ return -EBUSY;
+}
+
+static inline int aci_rawread(void)
+{
+ unsigned char byte;
+
+ if (busy_wait() >= 0) {
+ byte=inb(STATUS_REGISTER);
+#ifdef DEBUG
+ printk(KERN_DEBUG "%d = aci_rawread()\n", byte);
+#endif
+ return byte;
+ } else
+ return -EBUSY;
+}
+
+
+int aci_rw_cmd(int write1, int write2, int write3)
+{
+ int write[] = {write1, write2, write3};
+ int read = -EINTR, i;
+
+ if (mutex_lock_interruptible(&aci_mutex))
+ goto out;
+
+ for (i=0; i<3; i++) {
+ if (write[i]< 0 || write[i] > 255)
+ break;
+ else {
+ read = aci_rawwrite(write[i]);
+ if (read < 0)
+ goto out_up;
+ }
+
+ }
+
+ read = aci_rawread();
+out_up: mutex_unlock(&aci_mutex);
+out: return read;
+}
+
+EXPORT_SYMBOL(aci_rw_cmd);
+
+static int setvolume(int __user *arg,
+ unsigned char left_index, unsigned char right_index)
+{
+ int vol, ret, uservol, buf;
+
+ __get_user(uservol, arg);
+
+ /* left channel */
+ vol = uservol & 0xff;
+ if (vol > 100)
+ vol = 100;
+ vol = SCALE(100, 0x20, vol);
+ if ((buf=aci_write_cmd(left_index, 0x20 - vol))<0)
+ return buf;
+ ret = SCALE(0x20, 100, vol);
+
+
+ /* right channel */
+ vol = (uservol >> 8) & 0xff;
+ if (vol > 100)
+ vol = 100;
+ vol = SCALE(100, 0x20, vol);
+ if ((buf=aci_write_cmd(right_index, 0x20 - vol))<0)
+ return buf;
+ ret |= SCALE(0x20, 100, vol) << 8;
+
+ __put_user(ret, arg);
+
+ return 0;
+}
+
+static int getvolume(int __user *arg,
+ unsigned char left_index, unsigned char right_index)
+{
+ int vol;
+ int buf;
+
+ /* left channel */
+ if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0)
+ return buf;
+ vol = SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0);
+
+ /* right channel */
+ if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0)
+ return buf;
+ vol |= SCALE(0x20, 100, buf < 0x20 ? 0x20-buf : 0) << 8;
+
+ __put_user(vol, arg);
+
+ return 0;
+}
+
+
+/* The equalizer is somewhat strange on the ACI. From -12dB to +12dB
+ * write: 0xff..down.to..0x80==0x00..up.to..0x7f
+ */
+
+static inline unsigned int eq_oss2aci(unsigned int vol)
+{
+ int boost=0;
+ unsigned int ret;
+
+ if (vol > 100)
+ vol = 100;
+ if (vol > 50) {
+ vol -= 51;
+ boost=1;
+ }
+ if (boost)
+ ret=SCALE(49, 0x7e, vol)+1;
+ else
+ ret=0xff - SCALE(50, 0x7f, vol);
+ return ret;
+}
+
+static inline unsigned int eq_aci2oss(unsigned int vol)
+{
+ if (vol < 0x80)
+ return SCALE(0x7f, 50, vol) + 50;
+ else
+ return SCALE(0x7f, 50, 0xff-vol);
+}
+
+
+static int setequalizer(int __user *arg,
+ unsigned char left_index, unsigned char right_index)
+{
+ int buf;
+ unsigned int vol;
+
+ __get_user(vol, arg);
+
+ /* left channel */
+ if ((buf=aci_write_cmd(left_index, eq_oss2aci(vol & 0xff)))<0)
+ return buf;
+
+ /* right channel */
+ if ((buf=aci_write_cmd(right_index, eq_oss2aci((vol>>8) & 0xff)))<0)
+ return buf;
+
+ /* the ACI equalizer is more precise */
+ return 0;
+}
+
+static int getequalizer(int __user *arg,
+ unsigned char left_index, unsigned char right_index)
+{
+ int buf;
+ unsigned int vol;
+
+ /* left channel */
+ if ((buf=aci_indexed_cmd(ACI_STATUS, left_index))<0)
+ return buf;
+ vol = eq_aci2oss(buf);
+
+ /* right channel */
+ if ((buf=aci_indexed_cmd(ACI_STATUS, right_index))<0)
+ return buf;
+ vol |= eq_aci2oss(buf) << 8;
+
+ __put_user(vol, arg);
+
+ return 0;
+}
+
+static int aci_mixer_ioctl (int dev, unsigned int cmd, void __user * arg)
+{
+ int vol, buf;
+ int __user *p = arg;
+
+ switch (cmd) {
+ case SOUND_MIXER_WRITE_VOLUME:
+ return setvolume(p, 0x01, 0x00);
+ case SOUND_MIXER_WRITE_CD:
+ return setvolume(p, 0x3c, 0x34);
+ case SOUND_MIXER_WRITE_MIC:
+ return setvolume(p, 0x38, 0x30);
+ case SOUND_MIXER_WRITE_LINE:
+ return setvolume(p, 0x39, 0x31);
+ case SOUND_MIXER_WRITE_SYNTH:
+ return setvolume(p, 0x3b, 0x33);
+ case SOUND_MIXER_WRITE_PCM:
+ return setvolume(p, 0x3a, 0x32);
+ case MIXER_WRITE(SOUND_MIXER_RADIO): /* fall through */
+ case SOUND_MIXER_WRITE_LINE1: /* AUX1 or radio */
+ return setvolume(p, 0x3d, 0x35);
+ case SOUND_MIXER_WRITE_LINE2: /* AUX2 */
+ return setvolume(p, 0x3e, 0x36);
+ case SOUND_MIXER_WRITE_BASS: /* set band one and two */
+ if (aci_idcode[1]=='C') {
+ if ((buf=setequalizer(p, 0x48, 0x40)) ||
+ (buf=setequalizer(p, 0x49, 0x41)));
+ return buf;
+ }
+ break;
+ case SOUND_MIXER_WRITE_TREBLE: /* set band six and seven */
+ if (aci_idcode[1]=='C') {
+ if ((buf=setequalizer(p, 0x4d, 0x45)) ||
+ (buf=setequalizer(p, 0x4e, 0x46)));
+ return buf;
+ }
+ break;
+ case SOUND_MIXER_WRITE_IGAIN: /* MIC pre-amp */
+ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') {
+ __get_user(vol, p);
+ vol = vol & 0xff;
+ if (vol > 100)
+ vol = 100;
+ vol = SCALE(100, 3, vol);
+ if ((buf=aci_write_cmd(ACI_WRITE_IGAIN, vol))<0)
+ return buf;
+ aci_micpreamp = vol;
+ vol = SCALE(3, 100, vol);
+ vol |= (vol << 8);
+ __put_user(vol, p);
+ return 0;
+ }
+ break;
+ case SOUND_MIXER_WRITE_OGAIN: /* Power-amp/line-out level */
+ if (aci_idcode[1]=='A' || aci_idcode[1]=='B') {
+ __get_user(buf, p);
+ buf = buf & 0xff;
+ if (buf > 50)
+ vol = 1;
+ else
+ vol = 0;
+ if ((buf=aci_write_cmd(ACI_SET_POWERAMP, vol))<0)
+ return buf;
+ aci_amp = vol;
+ if (aci_amp)
+ buf = (100 || 100<<8);
+ else
+ buf = 0;
+ __put_user(buf, p);
+ return 0;
+ }
+ break;
+ case SOUND_MIXER_WRITE_RECSRC:
+ /* handle solo mode control */
+ __get_user(buf, p);
+ /* unset solo when RECSRC for PCM is requested */
+ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') {
+ vol = !(buf & SOUND_MASK_PCM);
+ if ((buf=aci_write_cmd(ACI_SET_SOLOMODE, vol))<0)
+ return buf;
+ aci_solo = vol;
+ }
+ buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE|
+ SOUND_MASK_SYNTH| SOUND_MASK_LINE2);
+ if (aci_idcode[1] == 'C') /* PCM20 radio */
+ buf |= SOUND_MASK_RADIO;
+ else
+ buf |= SOUND_MASK_LINE1;
+ if (!aci_solo)
+ buf |= SOUND_MASK_PCM;
+ __put_user(buf, p);
+ return 0;
+ case SOUND_MIXER_READ_DEVMASK:
+ buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD |
+ SOUND_MASK_MIC | SOUND_MASK_LINE |
+ SOUND_MASK_SYNTH | SOUND_MASK_PCM |
+ SOUND_MASK_LINE2);
+ switch (aci_idcode[1]) {
+ case 'C': /* PCM20 radio */
+ buf |= (SOUND_MASK_RADIO | SOUND_MASK_IGAIN |
+ SOUND_MASK_BASS | SOUND_MASK_TREBLE);
+ break;
+ case 'B': /* PCM12 */
+ buf |= (SOUND_MASK_LINE1 | SOUND_MASK_IGAIN |
+ SOUND_MASK_OGAIN);
+ break;
+ case 'A': /* PCM1-pro */
+ buf |= (SOUND_MASK_LINE1 | SOUND_MASK_OGAIN);
+ break;
+ default:
+ buf |= SOUND_MASK_LINE1;
+ }
+ __put_user(buf, p);
+ return 0;
+ case SOUND_MIXER_READ_STEREODEVS:
+ buf = (SOUND_MASK_VOLUME | SOUND_MASK_CD |
+ SOUND_MASK_MIC | SOUND_MASK_LINE |
+ SOUND_MASK_SYNTH | SOUND_MASK_PCM |
+ SOUND_MASK_LINE2);
+ switch (aci_idcode[1]) {
+ case 'C': /* PCM20 radio */
+ buf |= (SOUND_MASK_RADIO |
+ SOUND_MASK_BASS | SOUND_MASK_TREBLE);
+ break;
+ default:
+ buf |= SOUND_MASK_LINE1;
+ }
+ __put_user(buf, p);
+ return 0;
+ case SOUND_MIXER_READ_RECMASK:
+ buf = (SOUND_MASK_CD| SOUND_MASK_MIC| SOUND_MASK_LINE|
+ SOUND_MASK_SYNTH| SOUND_MASK_LINE2| SOUND_MASK_PCM);
+ if (aci_idcode[1] == 'C') /* PCM20 radio */
+ buf |= SOUND_MASK_RADIO;
+ else
+ buf |= SOUND_MASK_LINE1;
+
+ __put_user(buf, p);
+ return 0;
+ case SOUND_MIXER_READ_RECSRC:
+ buf = (SOUND_MASK_CD | SOUND_MASK_MIC | SOUND_MASK_LINE |
+ SOUND_MASK_SYNTH | SOUND_MASK_LINE2);
+ /* do we need aci_solo or can I get it from the ACI? */
+ switch (aci_idcode[1]) {
+ case 'B': /* PCM12 */
+ case 'C': /* PCM20 radio */
+ if (aci_version >= 0xb0) {
+ if ((vol=aci_rw_cmd(ACI_STATUS,
+ ACI_S_GENERAL, -1))<0)
+ return vol;
+ if (vol & 0x20)
+ buf |= SOUND_MASK_PCM;
+ }
+ else
+ if (!aci_solo)
+ buf |= SOUND_MASK_PCM;
+ break;
+ default:
+ buf |= SOUND_MASK_PCM;
+ }
+ if (aci_idcode[1] == 'C') /* PCM20 radio */
+ buf |= SOUND_MASK_RADIO;
+ else
+ buf |= SOUND_MASK_LINE1;
+
+ __put_user(buf, p);
+ return 0;
+ case SOUND_MIXER_READ_CAPS:
+ __put_user(0, p);
+ return 0;
+ case SOUND_MIXER_READ_VOLUME:
+ return getvolume(p, 0x04, 0x03);
+ case SOUND_MIXER_READ_CD:
+ return getvolume(p, 0x0a, 0x09);
+ case SOUND_MIXER_READ_MIC:
+ return getvolume(p, 0x06, 0x05);
+ case SOUND_MIXER_READ_LINE:
+ return getvolume(p, 0x08, 0x07);
+ case SOUND_MIXER_READ_SYNTH:
+ return getvolume(p, 0x0c, 0x0b);
+ case SOUND_MIXER_READ_PCM:
+ return getvolume(p, 0x0e, 0x0d);
+ case MIXER_READ(SOUND_MIXER_RADIO): /* fall through */
+ case SOUND_MIXER_READ_LINE1: /* AUX1 */
+ return getvolume(p, 0x11, 0x10);
+ case SOUND_MIXER_READ_LINE2: /* AUX2 */
+ return getvolume(p, 0x13, 0x12);
+ case SOUND_MIXER_READ_BASS: /* get band one */
+ if (aci_idcode[1]=='C') {
+ return getequalizer(p, 0x23, 0x22);
+ }
+ break;
+ case SOUND_MIXER_READ_TREBLE: /* get band seven */
+ if (aci_idcode[1]=='C') {
+ return getequalizer(p, 0x2f, 0x2e);
+ }
+ break;
+ case SOUND_MIXER_READ_IGAIN: /* MIC pre-amp */
+ if (aci_idcode[1]=='B' || aci_idcode[1]=='C') {
+ /* aci_micpreamp or ACI? */
+ if (aci_version >= 0xb0) {
+ if ((buf=aci_indexed_cmd(ACI_STATUS,
+ ACI_S_READ_IGAIN))<0)
+ return buf;
+ }
+ else
+ buf=aci_micpreamp;
+ vol = SCALE(3, 100, buf <= 3 ? buf : 3);
+ vol |= vol << 8;
+ __put_user(vol, p);
+ return 0;
+ }
+ break;
+ case SOUND_MIXER_READ_OGAIN:
+ if (aci_amp)
+ buf = (100 || 100<<8);
+ else
+ buf = 0;
+ __put_user(buf, p);
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static struct mixer_operations aci_mixer_operations =
+{
+ .owner = THIS_MODULE,
+ .id = "ACI",
+ .ioctl = aci_mixer_ioctl
+};
+
+/*
+ * There is also an internal mixer in the codec (CS4231A or AD1845),
+ * that deserves no purpose in an ACI based system which uses an
+ * external ACI controlled stereo mixer. Make sure that this codec
+ * mixer has the AUX1 input selected as the recording source, that the
+ * input gain is set near maximum and that the other channels going
+ * from the inputs to the codec output are muted.
+ */
+
+static int __init attach_aci(void)
+{
+ char *boardname;
+ int i, rc = -EBUSY;
+
+ mutex_init(&aci_mutex);
+
+ outb(0xE3, 0xf8f); /* Write MAD16 password */
+ aci_port = (inb(0xf90) & 0x10) ?
+ 0x344: 0x354; /* Get aci_port from MC4_PORT */
+
+ if (!request_region(aci_port, 3, "sound mixer (ACI)")) {
+ printk(KERN_NOTICE
+ "aci: I/O area 0x%03x-0x%03x already used.\n",
+ aci_port, aci_port+2);
+ goto out;
+ }
+
+ /* force ACI into a known state */
+ rc = -EFAULT;
+ for (i=0; i<3; i++)
+ if (aci_rw_cmd(ACI_ERROR_OP, -1, -1)<0)
+ goto out_release_region;
+
+ /* official this is one aci read call: */
+ rc = -EFAULT;
+ if ((aci_idcode[0]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0 ||
+ (aci_idcode[1]=aci_rw_cmd(ACI_READ_IDCODE, -1, -1))<0) {
+ printk(KERN_ERR "aci: Failed to read idcode on 0x%03x.\n",
+ aci_port);
+ goto out_release_region;
+ }
+
+ if ((aci_version=aci_rw_cmd(ACI_READ_VERSION, -1, -1))<0) {
+ printk(KERN_ERR "aci: Failed to read version on 0x%03x.\n",
+ aci_port);
+ goto out_release_region;
+ }
+
+ if (aci_idcode[0] == 'm') {
+ /* It looks like a miro sound card. */
+ switch (aci_idcode[1]) {
+ case 'A':
+ boardname = "PCM1 pro / early PCM12";
+ break;
+ case 'B':
+ boardname = "PCM12";
+ break;
+ case 'C':
+ boardname = "PCM20 radio";
+ break;
+ default:
+ boardname = "unknown miro";
+ }
+ } else {
+ printk(KERN_WARNING "aci: Warning: unsupported card! - "
+ "no hardware, no specs...\n");
+ boardname = "unknown Cardinal Technologies";
+ }
+
+ printk(KERN_INFO "<ACI 0x%02x, id %02x/%02x \"%c/%c\", (%s)> at 0x%03x\n",
+ aci_version,
+ aci_idcode[0], aci_idcode[1],
+ aci_idcode[0], aci_idcode[1],
+ boardname, aci_port);
+
+ rc = -EBUSY;
+ if (reset) {
+ /* first write()s after reset fail with my PCM20 */
+ if (aci_rw_cmd(ACI_INIT, -1, -1)<0 ||
+ aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0 ||
+ aci_rw_cmd(ACI_ERROR_OP, ACI_ERROR_OP, ACI_ERROR_OP)<0)
+ goto out_release_region;
+ }
+
+ /* the PCM20 is muted after reset (and reboot) */
+ if (aci_rw_cmd(ACI_SET_MUTE, 0x00, -1)<0)
+ goto out_release_region;
+
+ if (ide>=0)
+ if (aci_rw_cmd(ACI_SET_IDE, !ide, -1)<0)
+ goto out_release_region;
+
+ if (wss>=0 && aci_idcode[1]=='A')
+ if (aci_rw_cmd(ACI_SET_WSS, !!wss, -1)<0)
+ goto out_release_region;
+
+ mixer_device = sound_install_mixer(MIXER_DRIVER_VERSION, boardname,
+ &aci_mixer_operations,
+ sizeof(aci_mixer_operations), NULL);
+ rc = 0;
+ if (mixer_device < 0) {
+ printk(KERN_ERR "aci: Failed to install mixer.\n");
+ rc = mixer_device;
+ goto out_release_region;
+ } /* else Maybe initialize the CS4231A mixer here... */
+out: return rc;
+out_release_region:
+ release_region(aci_port, 3);
+ goto out;
+}
+
+static void __exit unload_aci(void)
+{
+ sound_unload_mixerdev(mixer_device);
+ release_region(aci_port, 3);
+}
+
+module_init(attach_aci);
+module_exit(unload_aci);
+MODULE_LICENSE("GPL");
diff --git a/linux/sound/oss/aci.h b/linux/sound/oss/aci.h
new file mode 100644
index 000000000..20102ee08
--- /dev/null
+++ b/linux/sound/oss/aci.h
@@ -0,0 +1,57 @@
+#ifndef _ACI_H_
+#define _ACI_H_
+
+extern int aci_port;
+extern int aci_version; /* ACI firmware version */
+extern int aci_rw_cmd(int write1, int write2, int write3);
+
+#define aci_indexed_cmd(a, b) aci_rw_cmd(a, b, -1)
+#define aci_write_cmd(a, b) aci_rw_cmd(a, b, -1)
+#define aci_read_cmd(a) aci_rw_cmd(a,-1, -1)
+
+#define COMMAND_REGISTER (aci_port) /* write register */
+#define STATUS_REGISTER (aci_port + 1) /* read register */
+#define BUSY_REGISTER (aci_port + 2) /* also used for rds */
+
+#define RDS_REGISTER BUSY_REGISTER
+
+#define ACI_SET_MUTE 0x0d
+#define ACI_SET_POWERAMP 0x0f
+#define ACI_SET_TUNERMUTE 0xa3
+#define ACI_SET_TUNERMONO 0xa4
+#define ACI_SET_IDE 0xd0
+#define ACI_SET_WSS 0xd1
+#define ACI_SET_SOLOMODE 0xd2
+#define ACI_WRITE_IGAIN 0x03
+#define ACI_WRITE_TUNE 0xa7
+#define ACI_READ_TUNERSTEREO 0xa8
+#define ACI_READ_TUNERSTATION 0xa9
+#define ACI_READ_VERSION 0xf1
+#define ACI_READ_IDCODE 0xf2
+#define ACI_INIT 0xff
+#define ACI_STATUS 0xf0
+#define ACI_S_GENERAL 0x00
+#define ACI_S_READ_IGAIN 0x21
+#define ACI_ERROR_OP 0xdf
+
+/*
+ * The following macro SCALE can be used to scale one integer volume
+ * value into another one using only integer arithmetic. If the input
+ * value x is in the range 0 <= x <= xmax, then the result will be in
+ * the range 0 <= SCALE(xmax,ymax,x) <= ymax.
+ *
+ * This macro has for all xmax, ymax > 0 and all 0 <= x <= xmax the
+ * following nice properties:
+ *
+ * - SCALE(xmax,ymax,xmax) = ymax
+ * - SCALE(xmax,ymax,0) = 0
+ * - SCALE(xmax,ymax,SCALE(ymax,xmax,SCALE(xmax,ymax,x))) = SCALE(xmax,ymax,x)
+ *
+ * In addition, the rounding error is minimal and nicely distributed.
+ * The proofs are left as an exercise to the reader.
+ */
+
+#define SCALE(xmax,ymax,x) (((x)*(ymax)+(xmax)/2)/(xmax))
+
+
+#endif /* _ACI_H_ */
diff --git a/linux/sound/oss/btaudio.c b/linux/sound/oss/btaudio.c
new file mode 100644
index 000000000..1fa72e732
--- /dev/null
+++ b/linux/sound/oss/btaudio.c
@@ -0,0 +1,1138 @@
+/*
+ btaudio - bt878 audio dma driver for linux 2.4.x
+
+ (c) 2000-2002 Gerd Knorr <kraxel@bytesex.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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/sound.h>
+#include <linux/soundcard.h>
+#include <linux/slab.h>
+#include <linux/kdev_t.h>
+#include <linux/mutex.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+
+/* mmio access */
+#define btwrite(dat,adr) writel((dat), (bta->mmio+(adr)))
+#define btread(adr) readl(bta->mmio+(adr))
+
+#define btand(dat,adr) btwrite((dat) & btread(adr), adr)
+#define btor(dat,adr) btwrite((dat) | btread(adr), adr)
+#define btaor(dat,mask,adr) btwrite((dat) | ((mask) & btread(adr)), adr)
+
+/* registers (shifted because bta->mmio is long) */
+#define REG_INT_STAT (0x100 >> 2)
+#define REG_INT_MASK (0x104 >> 2)
+#define REG_GPIO_DMA_CTL (0x10c >> 2)
+#define REG_PACKET_LEN (0x110 >> 2)
+#define REG_RISC_STRT_ADD (0x114 >> 2)
+#define REG_RISC_COUNT (0x120 >> 2)
+
+/* IRQ bits - REG_INT_(STAT|MASK) */
+#define IRQ_SCERR (1 << 19)
+#define IRQ_OCERR (1 << 18)
+#define IRQ_PABORT (1 << 17)
+#define IRQ_RIPERR (1 << 16)
+#define IRQ_PPERR (1 << 15)
+#define IRQ_FDSR (1 << 14)
+#define IRQ_FTRGT (1 << 13)
+#define IRQ_FBUS (1 << 12)
+#define IRQ_RISCI (1 << 11)
+#define IRQ_OFLOW (1 << 3)
+
+#define IRQ_BTAUDIO (IRQ_SCERR | IRQ_OCERR | IRQ_PABORT | IRQ_RIPERR |\
+ IRQ_PPERR | IRQ_FDSR | IRQ_FTRGT | IRQ_FBUS |\
+ IRQ_RISCI)
+
+/* REG_GPIO_DMA_CTL bits */
+#define DMA_CTL_A_PWRDN (1 << 26)
+#define DMA_CTL_DA_SBR (1 << 14)
+#define DMA_CTL_DA_ES2 (1 << 13)
+#define DMA_CTL_ACAP_EN (1 << 4)
+#define DMA_CTL_RISC_EN (1 << 1)
+#define DMA_CTL_FIFO_EN (1 << 0)
+
+/* RISC instructions */
+#define RISC_WRITE (0x01 << 28)
+#define RISC_JUMP (0x07 << 28)
+#define RISC_SYNC (0x08 << 28)
+
+/* RISC bits */
+#define RISC_WR_SOL (1 << 27)
+#define RISC_WR_EOL (1 << 26)
+#define RISC_IRQ (1 << 24)
+#define RISC_SYNC_RESYNC (1 << 15)
+#define RISC_SYNC_FM1 0x06
+#define RISC_SYNC_VRO 0x0c
+
+#define HWBASE_AD (448000)
+
+/* -------------------------------------------------------------- */
+
+struct btaudio {
+ /* linked list */
+ struct btaudio *next;
+
+ /* device info */
+ int dsp_digital;
+ int dsp_analog;
+ int mixer_dev;
+ struct pci_dev *pci;
+ unsigned int irq;
+ unsigned long mem;
+ unsigned long __iomem *mmio;
+
+ /* locking */
+ int users;
+ struct mutex lock;
+
+ /* risc instructions */
+ unsigned int risc_size;
+ unsigned long *risc_cpu;
+ dma_addr_t risc_dma;
+
+ /* audio data */
+ unsigned int buf_size;
+ unsigned char *buf_cpu;
+ dma_addr_t buf_dma;
+
+ /* buffer setup */
+ int line_bytes;
+ int line_count;
+ int block_bytes;
+ int block_count;
+
+ /* read fifo management */
+ int recording;
+ int dma_block;
+ int read_offset;
+ int read_count;
+ wait_queue_head_t readq;
+
+ /* settings */
+ int gain[3];
+ int source;
+ int bits;
+ int decimation;
+ int mixcount;
+ int sampleshift;
+ int channels;
+ int analog;
+ int rate;
+};
+
+struct cardinfo {
+ char *name;
+ int rate;
+};
+
+static struct btaudio *btaudios;
+static unsigned int debug;
+static unsigned int irq_debug;
+
+/* -------------------------------------------------------------- */
+
+#define BUF_DEFAULT 128*1024
+#define BUF_MIN 8192
+
+static int alloc_buffer(struct btaudio *bta)
+{
+ if (NULL == bta->buf_cpu) {
+ for (bta->buf_size = BUF_DEFAULT; bta->buf_size >= BUF_MIN;
+ bta->buf_size = bta->buf_size >> 1) {
+ bta->buf_cpu = pci_alloc_consistent
+ (bta->pci, bta->buf_size, &bta->buf_dma);
+ if (NULL != bta->buf_cpu)
+ break;
+ }
+ if (NULL == bta->buf_cpu)
+ return -ENOMEM;
+ memset(bta->buf_cpu,0,bta->buf_size);
+ }
+ if (NULL == bta->risc_cpu) {
+ bta->risc_size = PAGE_SIZE;
+ bta->risc_cpu = pci_alloc_consistent
+ (bta->pci, bta->risc_size, &bta->risc_dma);
+ if (NULL == bta->risc_cpu) {
+ pci_free_consistent(bta->pci, bta->buf_size, bta->buf_cpu, bta->buf_dma);
+ bta->buf_cpu = NULL;
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static void free_buffer(struct btaudio *bta)
+{
+ if (NULL != bta->buf_cpu) {
+ pci_free_consistent(bta->pci, bta->buf_size,
+ bta->buf_cpu, bta->buf_dma);
+ bta->buf_cpu = NULL;
+ }
+ if (NULL != bta->risc_cpu) {
+ pci_free_consistent(bta->pci, bta->risc_size,
+ bta->risc_cpu, bta->risc_dma);
+ bta->risc_cpu = NULL;
+ }
+}
+
+static int make_risc(struct btaudio *bta)
+{
+ int rp, bp, line, block;
+ unsigned long risc;
+
+ bta->block_bytes = bta->buf_size >> 4;
+ bta->block_count = 1 << 4;
+ bta->line_bytes = bta->block_bytes;
+ bta->line_count = bta->block_count;
+ while (bta->line_bytes > 4095) {
+ bta->line_bytes >>= 1;
+ bta->line_count <<= 1;
+ }
+ if (bta->line_count > 255)
+ return -EINVAL;
+ if (debug)
+ printk(KERN_DEBUG
+ "btaudio: bufsize=%d - bs=%d bc=%d - ls=%d, lc=%d\n",
+ bta->buf_size,bta->block_bytes,bta->block_count,
+ bta->line_bytes,bta->line_count);
+ rp = 0; bp = 0;
+ block = 0;
+ bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_FM1);
+ bta->risc_cpu[rp++] = cpu_to_le32(0);
+ for (line = 0; line < bta->line_count; line++) {
+ risc = RISC_WRITE | RISC_WR_SOL | RISC_WR_EOL;
+ risc |= bta->line_bytes;
+ if (0 == (bp & (bta->block_bytes-1))) {
+ risc |= RISC_IRQ;
+ risc |= (block & 0x0f) << 16;
+ risc |= (~block & 0x0f) << 20;
+ block++;
+ }
+ bta->risc_cpu[rp++] = cpu_to_le32(risc);
+ bta->risc_cpu[rp++] = cpu_to_le32(bta->buf_dma + bp);
+ bp += bta->line_bytes;
+ }
+ bta->risc_cpu[rp++] = cpu_to_le32(RISC_SYNC|RISC_SYNC_VRO);
+ bta->risc_cpu[rp++] = cpu_to_le32(0);
+ bta->risc_cpu[rp++] = cpu_to_le32(RISC_JUMP);
+ bta->risc_cpu[rp++] = cpu_to_le32(bta->risc_dma);
+ return 0;
+}
+
+static int start_recording(struct btaudio *bta)
+{
+ int ret;
+
+ if (0 != (ret = alloc_buffer(bta)))
+ return ret;
+ if (0 != (ret = make_risc(bta)))
+ return ret;
+
+ btwrite(bta->risc_dma, REG_RISC_STRT_ADD);
+ btwrite((bta->line_count << 16) | bta->line_bytes,
+ REG_PACKET_LEN);
+ btwrite(IRQ_BTAUDIO, REG_INT_MASK);
+ if (bta->analog) {
+ btwrite(DMA_CTL_ACAP_EN |
+ DMA_CTL_RISC_EN |
+ DMA_CTL_FIFO_EN |
+ DMA_CTL_DA_ES2 |
+ ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) |
+ (bta->gain[bta->source] << 28) |
+ (bta->source << 24) |
+ (bta->decimation << 8),
+ REG_GPIO_DMA_CTL);
+ } else {
+ btwrite(DMA_CTL_ACAP_EN |
+ DMA_CTL_RISC_EN |
+ DMA_CTL_FIFO_EN |
+ DMA_CTL_DA_ES2 |
+ DMA_CTL_A_PWRDN |
+ (1 << 6) |
+ ((bta->bits == 8) ? DMA_CTL_DA_SBR : 0) |
+ (bta->gain[bta->source] << 28) |
+ (bta->source << 24) |
+ (bta->decimation << 8),
+ REG_GPIO_DMA_CTL);
+ }
+ bta->dma_block = 0;
+ bta->read_offset = 0;
+ bta->read_count = 0;
+ bta->recording = 1;
+ if (debug)
+ printk(KERN_DEBUG "btaudio: recording started\n");
+ return 0;
+}
+
+static void stop_recording(struct btaudio *bta)
+{
+ btand(~15, REG_GPIO_DMA_CTL);
+ bta->recording = 0;
+ if (debug)
+ printk(KERN_DEBUG "btaudio: recording stopped\n");
+}
+
+
+/* -------------------------------------------------------------- */
+
+static int btaudio_mixer_open(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct btaudio *bta;
+
+ for (bta = btaudios; bta != NULL; bta = bta->next)
+ if (bta->mixer_dev == minor)
+ break;
+ if (NULL == bta)
+ return -ENODEV;
+
+ if (debug)
+ printk("btaudio: open mixer [%d]\n",minor);
+ file->private_data = bta;
+ return 0;
+}
+
+static int btaudio_mixer_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static int btaudio_mixer_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct btaudio *bta = file->private_data;
+ int ret,val=0,i=0;
+ void __user *argp = (void __user *)arg;
+
+ if (cmd == SOUND_MIXER_INFO) {
+ mixer_info info;
+ memset(&info,0,sizeof(info));
+ strlcpy(info.id,"bt878",sizeof(info.id));
+ strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name));
+ info.modify_counter = bta->mixcount;
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == SOUND_OLD_MIXER_INFO) {
+ _old_mixer_info info;
+ memset(&info,0,sizeof(info));
+ strlcpy(info.id,"bt878",sizeof(info.id)-1);
+ strlcpy(info.name,"Brooktree Bt878 audio",sizeof(info.name));
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+ if (cmd == OSS_GETVERSION)
+ return put_user(SOUND_VERSION, (int __user *)argp);
+
+ /* read */
+ if (_SIOC_DIR(cmd) & _SIOC_WRITE)
+ if (get_user(val, (int __user *)argp))
+ return -EFAULT;
+
+ switch (cmd) {
+ case MIXER_READ(SOUND_MIXER_CAPS):
+ ret = SOUND_CAP_EXCL_INPUT;
+ break;
+ case MIXER_READ(SOUND_MIXER_STEREODEVS):
+ ret = 0;
+ break;
+ case MIXER_READ(SOUND_MIXER_RECMASK):
+ case MIXER_READ(SOUND_MIXER_DEVMASK):
+ ret = SOUND_MASK_LINE1|SOUND_MASK_LINE2|SOUND_MASK_LINE3;
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_RECSRC):
+ if (val & SOUND_MASK_LINE1 && bta->source != 0)
+ bta->source = 0;
+ else if (val & SOUND_MASK_LINE2 && bta->source != 1)
+ bta->source = 1;
+ else if (val & SOUND_MASK_LINE3 && bta->source != 2)
+ bta->source = 2;
+ btaor((bta->gain[bta->source] << 28) |
+ (bta->source << 24),
+ 0x0cffffff, REG_GPIO_DMA_CTL);
+ case MIXER_READ(SOUND_MIXER_RECSRC):
+ switch (bta->source) {
+ case 0: ret = SOUND_MASK_LINE1; break;
+ case 1: ret = SOUND_MASK_LINE2; break;
+ case 2: ret = SOUND_MASK_LINE3; break;
+ default: ret = 0;
+ }
+ break;
+
+ case MIXER_WRITE(SOUND_MIXER_LINE1):
+ case MIXER_WRITE(SOUND_MIXER_LINE2):
+ case MIXER_WRITE(SOUND_MIXER_LINE3):
+ if (MIXER_WRITE(SOUND_MIXER_LINE1) == cmd)
+ i = 0;
+ if (MIXER_WRITE(SOUND_MIXER_LINE2) == cmd)
+ i = 1;
+ if (MIXER_WRITE(SOUND_MIXER_LINE3) == cmd)
+ i = 2;
+ bta->gain[i] = (val & 0xff) * 15 / 100;
+ if (bta->gain[i] > 15) bta->gain[i] = 15;
+ if (bta->gain[i] < 0) bta->gain[i] = 0;
+ if (i == bta->source)
+ btaor((bta->gain[bta->source]<<28),
+ 0x0fffffff, REG_GPIO_DMA_CTL);
+ ret = bta->gain[i] * 100 / 15;
+ ret |= ret << 8;
+ break;
+
+ case MIXER_READ(SOUND_MIXER_LINE1):
+ case MIXER_READ(SOUND_MIXER_LINE2):
+ case MIXER_READ(SOUND_MIXER_LINE3):
+ if (MIXER_READ(SOUND_MIXER_LINE1) == cmd)
+ i = 0;
+ if (MIXER_READ(SOUND_MIXER_LINE2) == cmd)
+ i = 1;
+ if (MIXER_READ(SOUND_MIXER_LINE3) == cmd)
+ i = 2;
+ ret = bta->gain[i] * 100 / 15;
+ ret |= ret << 8;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+ if (put_user(ret, (int __user *)argp))
+ return -EFAULT;
+ return 0;
+}
+
+static struct file_operations btaudio_mixer_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = btaudio_mixer_open,
+ .release = btaudio_mixer_release,
+ .ioctl = btaudio_mixer_ioctl,
+};
+
+/* -------------------------------------------------------------- */
+
+static int btaudio_dsp_open(struct inode *inode, struct file *file,
+ struct btaudio *bta, int analog)
+{
+ mutex_lock(&bta->lock);
+ if (bta->users)
+ goto busy;
+ bta->users++;
+ file->private_data = bta;
+
+ bta->analog = analog;
+ bta->dma_block = 0;
+ bta->read_offset = 0;
+ bta->read_count = 0;
+ bta->sampleshift = 0;
+
+ mutex_unlock(&bta->lock);
+ return 0;
+
+ busy:
+ mutex_unlock(&bta->lock);
+ return -EBUSY;
+}
+
+static int btaudio_dsp_open_digital(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct btaudio *bta;
+
+ for (bta = btaudios; bta != NULL; bta = bta->next)
+ if (bta->dsp_digital == minor)
+ break;
+ if (NULL == bta)
+ return -ENODEV;
+
+ if (debug)
+ printk("btaudio: open digital dsp [%d]\n",minor);
+ return btaudio_dsp_open(inode,file,bta,0);
+}
+
+static int btaudio_dsp_open_analog(struct inode *inode, struct file *file)
+{
+ int minor = iminor(inode);
+ struct btaudio *bta;
+
+ for (bta = btaudios; bta != NULL; bta = bta->next)
+ if (bta->dsp_analog == minor)
+ break;
+ if (NULL == bta)
+ return -ENODEV;
+
+ if (debug)
+ printk("btaudio: open analog dsp [%d]\n",minor);
+ return btaudio_dsp_open(inode,file,bta,1);
+}
+
+static int btaudio_dsp_release(struct inode *inode, struct file *file)
+{
+ struct btaudio *bta = file->private_data;
+
+ mutex_lock(&bta->lock);
+ if (bta->recording)
+ stop_recording(bta);
+ bta->users--;
+ mutex_unlock(&bta->lock);
+ return 0;
+}
+
+static ssize_t btaudio_dsp_read(struct file *file, char __user *buffer,
+ size_t swcount, loff_t *ppos)
+{
+ struct btaudio *bta = file->private_data;
+ int hwcount = swcount << bta->sampleshift;
+ int nsrc, ndst, err, ret = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(&bta->readq, &wait);
+ mutex_lock(&bta->lock);
+ while (swcount > 0) {
+ if (0 == bta->read_count) {
+ if (!bta->recording) {
+ if (0 != (err = start_recording(bta))) {
+ if (0 == ret)
+ ret = err;
+ break;
+ }
+ }
+ if (file->f_flags & O_NONBLOCK) {
+ if (0 == ret)
+ ret = -EAGAIN;
+ break;
+ }
+ mutex_unlock(&bta->lock);
+ current->state = TASK_INTERRUPTIBLE;
+ schedule();
+ mutex_lock(&bta->lock);
+ if(signal_pending(current)) {
+ if (0 == ret)
+ ret = -EINTR;
+ break;
+ }
+ }
+ nsrc = (bta->read_count < hwcount) ? bta->read_count : hwcount;
+ if (nsrc > bta->buf_size - bta->read_offset)
+ nsrc = bta->buf_size - bta->read_offset;
+ ndst = nsrc >> bta->sampleshift;
+
+ if ((bta->analog && 0 == bta->sampleshift) ||
+ (!bta->analog && 2 == bta->channels)) {
+ /* just copy */
+ if (copy_to_user(buffer + ret, bta->buf_cpu + bta->read_offset, nsrc)) {
+ if (0 == ret)
+ ret = -EFAULT;
+ break;
+ }
+
+ } else if (!bta->analog) {
+ /* stereo => mono (digital audio) */
+ __s16 *src = (__s16*)(bta->buf_cpu + bta->read_offset);
+ __s16 __user *dst = (__s16 __user *)(buffer + ret);
+ __s16 avg;
+ int n = ndst>>1;
+ if (!access_ok(VERIFY_WRITE, dst, ndst)) {
+ if (0 == ret)
+ ret = -EFAULT;
+ break;
+ }
+ for (; n; n--, dst++) {
+ avg = (__s16)le16_to_cpu(*src) / 2; src++;
+ avg += (__s16)le16_to_cpu(*src) / 2; src++;
+ __put_user(cpu_to_le16(avg),dst);
+ }
+
+ } else if (8 == bta->bits) {
+ /* copy + byte downsampling (audio A/D) */
+ __u8 *src = bta->buf_cpu + bta->read_offset;
+ __u8 __user *dst = buffer + ret;
+ int n = ndst;
+ if (!access_ok(VERIFY_WRITE, dst, ndst)) {
+ if (0 == ret)
+ ret = -EFAULT;
+ break;
+ }
+ for (; n; n--, src += (1 << bta->sampleshift), dst++)
+ __put_user(*src, dst);
+
+ } else {
+ /* copy + word downsampling (audio A/D) */
+ __u16 *src = (__u16*)(bta->buf_cpu + bta->read_offset);
+ __u16 __user *dst = (__u16 __user *)(buffer + ret);
+ int n = ndst>>1;
+ if (!access_ok(VERIFY_WRITE,dst,ndst)) {
+ if (0 == ret)
+ ret = -EFAULT;
+ break;
+ }
+ for (; n; n--, src += (1 << bta->sampleshift), dst++)
+ __put_user(*src, dst);
+ }
+
+ ret += ndst;
+ swcount -= ndst;
+ hwcount -= nsrc;
+ bta->read_count -= nsrc;
+ bta->read_offset += nsrc;
+ if (bta->read_offset == bta->buf_size)
+ bta->read_offset = 0;
+ }
+ mutex_unlock(&bta->lock);
+ remove_wait_queue(&bta->readq, &wait);
+ current->state = TASK_RUNNING;
+ return ret;
+}
+
+static ssize_t btaudio_dsp_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ return -EINVAL;
+}
+
+static int btaudio_dsp_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct btaudio *bta = file->private_data;
+ int s, i, ret, val = 0;
+ void __user *argp = (void __user *)arg;
+ int __user *p = argp;
+
+ switch (cmd) {
+ case OSS_GETVERSION:
+ return put_user(SOUND_VERSION, p);
+ case SNDCTL_DSP_GETCAPS:
+ return 0;
+
+ case SNDCTL_DSP_SPEED:
+ if (get_user(val, p))
+ return -EFAULT;
+ if (bta->analog) {
+ for (s = 0; s < 16; s++)
+ if (val << s >= HWBASE_AD*4/15)
+ break;
+ for (i = 15; i >= 5; i--)
+ if (val << s <= HWBASE_AD*4/i)
+ break;
+ bta->sampleshift = s;
+ bta->decimation = i;
+ if (debug)
+ printk(KERN_DEBUG "btaudio: rate: req=%d "
+ "dec=%d shift=%d hwrate=%d swrate=%d\n",
+ val,i,s,(HWBASE_AD*4/i),(HWBASE_AD*4/i)>>s);
+ } else {
+ bta->sampleshift = (bta->channels == 2) ? 0 : 1;
+ bta->decimation = 0;
+ }
+ if (bta->recording) {
+ mutex_lock(&bta->lock);
+ stop_recording(bta);
+ start_recording(bta);
+ mutex_unlock(&bta->lock);
+ }
+ /* fall through */
+ case SOUND_PCM_READ_RATE:
+ if (bta->analog) {
+ return put_user(HWBASE_AD*4/bta->decimation>>bta->sampleshift, p);
+ } else {
+ return put_user(bta->rate, p);
+ }
+
+ case SNDCTL_DSP_STEREO:
+ if (!bta->analog) {
+ if (get_user(val, p))
+ return -EFAULT;
+ bta->channels = (val > 0) ? 2 : 1;
+ bta->sampleshift = (bta->channels == 2) ? 0 : 1;
+ if (debug)
+ printk(KERN_INFO
+ "btaudio: stereo=%d channels=%d\n",
+ val,bta->channels);
+ } else {
+ if (val == 1)
+ return -EFAULT;
+ else {
+ bta->channels = 1;
+ if (debug)
+ printk(KERN_INFO
+ "btaudio: stereo=0 channels=1\n");
+ }
+ }
+ return put_user((bta->channels)-1, p);
+
+ case SNDCTL_DSP_CHANNELS:
+ if (!bta->analog) {
+ if (get_user(val, p))
+ return -EFAULT;
+ bta->channels = (val > 1) ? 2 : 1;
+ bta->sampleshift = (bta->channels == 2) ? 0 : 1;
+ if (debug)
+ printk(KERN_DEBUG
+ "btaudio: val=%d channels=%d\n",
+ val,bta->channels);
+ }
+ /* fall through */
+ case SOUND_PCM_READ_CHANNELS:
+ return put_user(bta->channels, p);
+
+ case SNDCTL_DSP_GETFMTS: /* Returns a mask */
+ if (bta->analog)
+ return put_user(AFMT_S16_LE|AFMT_S8, p);
+ else
+ return put_user(AFMT_S16_LE, p);
+
+ case SNDCTL_DSP_SETFMT: /* Selects ONE fmt*/
+ if (get_user(val, p))
+ return -EFAULT;
+ if (val != AFMT_QUERY) {
+ if (bta->analog)
+ bta->bits = (val == AFMT_S8) ? 8 : 16;
+ else
+ bta->bits = 16;
+ if (bta->recording) {
+ mutex_lock(&bta->lock);
+ stop_recording(bta);
+ start_recording(bta);
+ mutex_unlock(&bta->lock);
+ }
+ }
+ if (debug)
+ printk(KERN_DEBUG "btaudio: fmt: bits=%d\n",bta->bits);
+ return put_user((bta->bits==16) ? AFMT_S16_LE : AFMT_S8,
+ p);
+ break;
+ case SOUND_PCM_READ_BITS:
+ return put_user(bta->bits, p);
+
+ case SNDCTL_DSP_NONBLOCK:
+ file->f_flags |= O_NONBLOCK;
+ return 0;
+
+ case SNDCTL_DSP_RESET:
+ if (bta->recording) {
+ mutex_lock(&bta->lock);
+ stop_recording(bta);
+ mutex_unlock(&bta->lock);
+ }
+ return 0;
+ case SNDCTL_DSP_GETBLKSIZE:
+ if (!bta->recording) {
+ if (0 != (ret = alloc_buffer(bta)))
+ return ret;
+ if (0 != (ret = make_risc(bta)))
+ return ret;
+ }
+ return put_user(bta->block_bytes>>bta->sampleshift,p);
+
+ case SNDCTL_DSP_SYNC:
+ /* NOP */
+ return 0;
+ case SNDCTL_DSP_GETISPACE:
+ {
+ audio_buf_info info;
+ if (!bta->recording)
+ return -EINVAL;
+ info.fragsize = bta->block_bytes>>bta->sampleshift;
+ info.fragstotal = bta->block_count;
+ info.bytes = bta->read_count;
+ info.fragments = info.bytes / info.fragsize;
+ if (debug)
+ printk(KERN_DEBUG "btaudio: SNDCTL_DSP_GETISPACE "
+ "returns %d/%d/%d/%d\n",
+ info.fragsize, info.fragstotal,
+ info.bytes, info.fragments);
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+ }
+#if 0 /* TODO */
+ case SNDCTL_DSP_GETTRIGGER:
+ case SNDCTL_DSP_SETTRIGGER:
+ case SNDCTL_DSP_SETFRAGMENT:
+#endif
+ default:
+ return -EINVAL;
+ }
+}
+
+static unsigned int btaudio_dsp_poll(struct file *file, struct poll_table_struct *wait)
+{
+ struct btaudio *bta = file->private_data;
+ unsigned int mask = 0;
+
+ poll_wait(file, &bta->readq, wait);
+
+ if (0 != bta->read_count)
+ mask |= (POLLIN | POLLRDNORM);
+
+ return mask;
+}
+
+static struct file_operations btaudio_digital_dsp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = btaudio_dsp_open_digital,
+ .release = btaudio_dsp_release,
+ .read = btaudio_dsp_read,
+ .write = btaudio_dsp_write,
+ .ioctl = btaudio_dsp_ioctl,
+ .poll = btaudio_dsp_poll,
+};
+
+static struct file_operations btaudio_analog_dsp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .open = btaudio_dsp_open_analog,
+ .release = btaudio_dsp_release,
+ .read = btaudio_dsp_read,
+ .write = btaudio_dsp_write,
+ .ioctl = btaudio_dsp_ioctl,
+ .poll = btaudio_dsp_poll,
+};
+
+/* -------------------------------------------------------------- */
+
+static char *irq_name[] = { "", "", "", "OFLOW", "", "", "", "", "", "", "",
+ "RISCI", "FBUS", "FTRGT", "FDSR", "PPERR",
+ "RIPERR", "PABORT", "OCERR", "SCERR" };
+
+static irqreturn_t btaudio_irq(int irq, void *dev_id, struct pt_regs * regs)
+{
+ int count = 0;
+ u32 stat,astat;
+ struct btaudio *bta = dev_id;
+ int handled = 0;
+
+ for (;;) {
+ count++;
+ stat = btread(REG_INT_STAT);
+ astat = stat & btread(REG_INT_MASK);
+ if (!astat)
+ return IRQ_RETVAL(handled);
+ handled = 1;
+ btwrite(astat,REG_INT_STAT);
+
+ if (irq_debug) {
+ int i;
+ printk(KERN_DEBUG "btaudio: irq loop=%d risc=%x, bits:",
+ count, stat>>28);
+ for (i = 0; i < (sizeof(irq_name)/sizeof(char*)); i++) {
+ if (stat & (1 << i))
+ printk(" %s",irq_name[i]);
+ if (astat & (1 << i))
+ printk("*");
+ }
+ printk("\n");
+ }
+ if (stat & IRQ_RISCI) {
+ int blocks;
+ blocks = (stat >> 28) - bta->dma_block;
+ if (blocks < 0)
+ blocks += bta->block_count;
+ bta->dma_block = stat >> 28;
+ if (bta->read_count + 2*bta->block_bytes > bta->buf_size) {
+ stop_recording(bta);
+ printk(KERN_INFO "btaudio: buffer overrun\n");
+ }
+ if (blocks > 0) {
+ bta->read_count += blocks * bta->block_bytes;
+ wake_up_interruptible(&bta->readq);
+ }
+ }
+ if (count > 10) {
+ printk(KERN_WARNING
+ "btaudio: Oops - irq mask cleared\n");
+ btwrite(0, REG_INT_MASK);
+ }
+ }
+ return IRQ_NONE;
+}
+
+/* -------------------------------------------------------------- */
+
+static unsigned int dsp1 = -1;
+static unsigned int dsp2 = -1;
+static unsigned int mixer = -1;
+static int latency = -1;
+static int digital = 1;
+static int analog = 1;
+static int rate;
+
+#define BTA_OSPREY200 1
+
+static struct cardinfo cards[] = {
+ [0] = {
+ .name = "default",
+ .rate = 32000,
+ },
+ [BTA_OSPREY200] = {
+ .name = "Osprey 200",
+ .rate = 44100,
+ },
+};
+
+static int __devinit btaudio_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *pci_id)
+{
+ struct btaudio *bta;
+ struct cardinfo *card = &cards[pci_id->driver_data];
+ unsigned char revision,lat;
+ int rc = -EBUSY;
+
+ if (pci_enable_device(pci_dev))
+ return -EIO;
+ if (!request_mem_region(pci_resource_start(pci_dev,0),
+ pci_resource_len(pci_dev,0),
+ "btaudio")) {
+ return -EBUSY;
+ }
+
+ bta = kmalloc(sizeof(*bta),GFP_ATOMIC);
+ if (!bta) {
+ rc = -ENOMEM;
+ goto fail0;
+ }
+ memset(bta,0,sizeof(*bta));
+
+ bta->pci = pci_dev;
+ bta->irq = pci_dev->irq;
+ bta->mem = pci_resource_start(pci_dev,0);
+ bta->mmio = ioremap(pci_resource_start(pci_dev,0),
+ pci_resource_len(pci_dev,0));
+
+ bta->source = 1;
+ bta->bits = 8;
+ bta->channels = 1;
+ if (bta->analog) {
+ bta->decimation = 15;
+ } else {
+ bta->decimation = 0;
+ bta->sampleshift = 1;
+ }
+
+ /* sample rate */
+ bta->rate = card->rate;
+ if (rate)
+ bta->rate = rate;
+
+ mutex_init(&bta->lock);
+ init_waitqueue_head(&bta->readq);
+
+ if (-1 != latency) {
+ printk(KERN_INFO "btaudio: setting pci latency timer to %d\n",
+ latency);
+ pci_write_config_byte(pci_dev, PCI_LATENCY_TIMER, latency);
+ }
+ pci_read_config_byte(pci_dev, PCI_CLASS_REVISION, &revision);
+ pci_read_config_byte(pci_dev, PCI_LATENCY_TIMER, &lat);
+ printk(KERN_INFO "btaudio: Bt%x (rev %d) at %02x:%02x.%x, ",
+ pci_dev->device,revision,pci_dev->bus->number,
+ PCI_SLOT(pci_dev->devfn),PCI_FUNC(pci_dev->devfn));
+ printk("irq: %d, latency: %d, mmio: 0x%lx\n",
+ bta->irq, lat, bta->mem);
+ printk("btaudio: using card config \"%s\"\n", card->name);
+
+ /* init hw */
+ btwrite(0, REG_GPIO_DMA_CTL);
+ btwrite(0, REG_INT_MASK);
+ btwrite(~0U, REG_INT_STAT);
+ pci_set_master(pci_dev);
+
+ if ((rc = request_irq(bta->irq, btaudio_irq, IRQF_SHARED|IRQF_DISABLED,
+ "btaudio",(void *)bta)) < 0) {
+ printk(KERN_WARNING
+ "btaudio: can't request irq (rc=%d)\n",rc);
+ goto fail1;
+ }
+
+ /* register devices */
+ if (digital) {
+ rc = bta->dsp_digital =
+ register_sound_dsp(&btaudio_digital_dsp_fops,dsp1);
+ if (rc < 0) {
+ printk(KERN_WARNING
+ "btaudio: can't register digital dsp (rc=%d)\n",rc);
+ goto fail2;
+ }
+ printk(KERN_INFO "btaudio: registered device dsp%d [digital]\n",
+ bta->dsp_digital >> 4);
+ }
+ if (analog) {
+ rc = bta->dsp_analog =
+ register_sound_dsp(&btaudio_analog_dsp_fops,dsp2);
+ if (rc < 0) {
+ printk(KERN_WARNING
+ "btaudio: can't register analog dsp (rc=%d)\n",rc);
+ goto fail3;
+ }
+ printk(KERN_INFO "btaudio: registered device dsp%d [analog]\n",
+ bta->dsp_analog >> 4);
+ rc = bta->mixer_dev = register_sound_mixer(&btaudio_mixer_fops,mixer);
+ if (rc < 0) {
+ printk(KERN_WARNING
+ "btaudio: can't register mixer (rc=%d)\n",rc);
+ goto fail4;
+ }
+ printk(KERN_INFO "btaudio: registered device mixer%d\n",
+ bta->mixer_dev >> 4);
+ }
+
+ /* hook into linked list */
+ bta->next = btaudios;
+ btaudios = bta;
+
+ pci_set_drvdata(pci_dev,bta);
+ return 0;
+
+ fail4:
+ unregister_sound_dsp(bta->dsp_analog);
+ fail3:
+ if (digital)
+ unregister_sound_dsp(bta->dsp_digital);
+ fail2:
+ free_irq(bta->irq,bta);
+ fail1:
+ kfree(bta);
+ fail0:
+ release_mem_region(pci_resource_start(pci_dev,0),
+ pci_resource_len(pci_dev,0));
+ return rc;
+}
+
+static void __devexit btaudio_remove(struct pci_dev *pci_dev)
+{
+ struct btaudio *bta = pci_get_drvdata(pci_dev);
+ struct btaudio *walk;
+
+ /* turn off all DMA / IRQs */
+ btand(~15, REG_GPIO_DMA_CTL);
+ btwrite(0, REG_INT_MASK);
+ btwrite(~0U, REG_INT_STAT);
+
+ /* unregister devices */
+ if (digital) {
+ unregister_sound_dsp(bta->dsp_digital);
+ }
+ if (analog) {
+ unregister_sound_dsp(bta->dsp_analog);
+ unregister_sound_mixer(bta->mixer_dev);
+ }
+
+ /* free resources */
+ free_buffer(bta);
+ free_irq(bta->irq,bta);
+ release_mem_region(pci_resource_start(pci_dev,0),
+ pci_resource_len(pci_dev,0));
+
+ /* remove from linked list */
+ if (bta == btaudios) {
+ btaudios = NULL;
+ } else {
+ for (walk = btaudios; walk->next != bta; walk = walk->next)
+ ; /* if (NULL == walk->next) BUG(); */
+ walk->next = bta->next;
+ }
+
+ pci_set_drvdata(pci_dev, NULL);
+ kfree(bta);
+ return;
+}
+
+/* -------------------------------------------------------------- */
+
+static struct pci_device_id btaudio_pci_tbl[] = {
+ {
+ .vendor = PCI_VENDOR_ID_BROOKTREE,
+ .device = 0x0878,
+ .subvendor = 0x0070,
+ .subdevice = 0xff01,
+ .driver_data = BTA_OSPREY200,
+ },{
+ .vendor = PCI_VENDOR_ID_BROOKTREE,
+ .device = 0x0878,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },{
+ .vendor = PCI_VENDOR_ID_BROOKTREE,
+ .device = 0x0878,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },{
+ /* --- end of list --- */
+ }
+};
+
+static struct pci_driver btaudio_pci_driver = {
+ .name = "btaudio",
+ .id_table = btaudio_pci_tbl,
+ .probe = btaudio_probe,
+ .remove = __devexit_p(btaudio_remove),
+};
+
+static int btaudio_init_module(void)
+{
+ printk(KERN_INFO "btaudio: driver version 0.7 loaded [%s%s%s]\n",
+ digital ? "digital" : "",
+ analog && digital ? "+" : "",
+ analog ? "analog" : "");
+ return pci_register_driver(&btaudio_pci_driver);
+}
+
+static void btaudio_cleanup_module(void)
+{
+ pci_unregister_driver(&btaudio_pci_driver);
+ return;
+}
+
+module_init(btaudio_init_module);
+module_exit(btaudio_cleanup_module);
+
+module_param(dsp1, int, S_IRUGO);
+module_param(dsp2, int, S_IRUGO);
+module_param(mixer, int, S_IRUGO);
+module_param(debug, int, S_IRUGO | S_IWUSR);
+module_param(irq_debug, int, S_IRUGO | S_IWUSR);
+module_param(digital, int, S_IRUGO);
+module_param(analog, int, S_IRUGO);
+module_param(rate, int, S_IRUGO);
+module_param(latency, int, S_IRUGO);
+MODULE_PARM_DESC(latency,"pci latency timer");
+
+MODULE_DEVICE_TABLE(pci, btaudio_pci_tbl);
+MODULE_DESCRIPTION("bt878 audio dma driver");
+MODULE_AUTHOR("Gerd Knorr");
+MODULE_LICENSE("GPL");
+
+/*
+ * Local variables:
+ * c-basic-offset: 8
+ * End:
+ */