diff options
Diffstat (limited to 'linux/sound')
-rw-r--r-- | linux/sound/i2c/other/tea575x-tuner.c | 233 | ||||
-rw-r--r-- | linux/sound/oss/aci.c | 712 | ||||
-rw-r--r-- | linux/sound/oss/aci.h | 57 | ||||
-rw-r--r-- | linux/sound/oss/btaudio.c | 1138 |
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: + */ |