diff options
Diffstat (limited to 'sound/oss/cs46xx.c')
-rw-r--r-- | sound/oss/cs46xx.c | 5794 |
1 files changed, 5794 insertions, 0 deletions
diff --git a/sound/oss/cs46xx.c b/sound/oss/cs46xx.c new file mode 100644 index 00000000000..8ce6b48f188 --- /dev/null +++ b/sound/oss/cs46xx.c @@ -0,0 +1,5794 @@ +/* + * Crystal SoundFusion CS46xx driver + * + * Copyright 1998-2001 Cirrus Logic Corporation <pcaudio@crystal.cirrus.com> + * <twoller@crystal.cirrus.com> + * Copyright 1999-2000 Jaroslav Kysela <perex@suse.cz> + * Copyright 2000 Alan Cox <alan@redhat.com> + * + * The core of this code is taken from the ALSA project driver by + * Jaroslav. Please send Jaroslav the credit for the driver and + * report bugs in this port to <alan@redhat.com> + * + * 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. + * Current maintainers: + * Cirrus Logic Corporation, Thomas Woller (tw) + * <twoller@crystal.cirrus.com> + * Nils Faerber (nf) + * <nils@kernelconcepts.de> + * Thanks to David Pollard for testing. + * + * Changes: + * 20000909-nf Changed cs_read, cs_write and drain_dac + * 20001025-tw Separate Playback/Capture structs and buffers. + * Added Scatter/Gather support for Playback. + * Added Capture. + * 20001027-nf Port to kernel 2.4.0-test9, some clean-ups + * Start of powermanagement support (CS46XX_PM). + * 20001128-tw Add module parm for default buffer order. + * added DMA_GFP flag to kmalloc dma buffer allocs. + * backfill silence to eliminate stuttering on + * underruns. + * 20001201-tw add resyncing of swptr on underruns. + * 20001205-tw-nf fixed GETOSPACE ioctl() after open() + * 20010113-tw patch from Hans Grobler general cleanup. + * 20010117-tw 2.4.0 pci cleanup, wrapper code for 2.2.16-2.4.0 + * 20010118-tw basic PM support for 2.2.16+ and 2.4.0/2.4.2. + * 20010228-dh patch from David Huggins - cs_update_ptr recursion. + * 20010409-tw add hercules game theatre XP amp code. + * 20010420-tw cleanup powerdown/up code. + * 20010521-tw eliminate pops, and fixes for powerdown. + * 20010525-tw added fixes for thinkpads with powerdown logic. + * 20010723-sh patch from Horms (Simon Horman) - + * SOUND_PCM_READ_BITS returns bits as set in driver + * rather than a logical or of the possible values. + * Various ioctls handle the case where the device + * is open for reading or writing but not both better. + * + * Status: + * Playback/Capture supported from 8k-48k. + * 16Bit Signed LE & 8Bit Unsigned, with Mono or Stereo supported. + * + * APM/PM - 2.2.x APM is enabled and functioning fine. APM can also + * be enabled for 2.4.x by modifying the CS46XX_ACPI_SUPPORT macro + * definition. + * + * Hercules Game Theatre XP - the EGPIO2 pin controls the external Amp, + * so, use the drain/polarity to enable. + * hercules_egpio_disable set to 1, will force a 0 to EGPIODR. + * + * VTB Santa Cruz - the GPIO7/GPIO8 on the Secondary Codec control + * the external amplifier for the "back" speakers, since we do not + * support the secondary codec then this external amp is also not + * turned on. + */ + +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/delay.h> +#include <linux/sound.h> +#include <linux/slab.h> +#include <linux/soundcard.h> +#include <linux/pci.h> +#include <linux/bitops.h> +#include <linux/init.h> +#include <linux/poll.h> +#include <linux/ac97_codec.h> + +#include <asm/io.h> +#include <asm/dma.h> +#include <asm/uaccess.h> + +#include "cs46xxpm-24.h" +#include "cs46xx_wrapper-24.h" +#include "cs461x.h" + +/* MIDI buffer sizes */ +#define CS_MIDIINBUF 500 +#define CS_MIDIOUTBUF 500 + +#define ADC_RUNNING 1 +#define DAC_RUNNING 2 + +#define CS_FMT_16BIT 1 /* These are fixed in fact */ +#define CS_FMT_STEREO 2 +#define CS_FMT_MASK 3 + +#define CS_TYPE_ADC 1 +#define CS_TYPE_DAC 2 + +#define CS_TRUE 1 +#define CS_FALSE 0 + +#define CS_INC_USE_COUNT(m) (atomic_inc(m)) +#define CS_DEC_USE_COUNT(m) (atomic_dec(m)) +#define CS_DEC_AND_TEST(m) (atomic_dec_and_test(m)) +#define CS_IN_USE(m) (atomic_read(m) != 0) + +#define CS_DBGBREAKPOINT {__asm__("INT $3");} +/* + * CS461x definitions + */ + +#define CS461X_BA0_SIZE 0x2000 +#define CS461X_BA1_DATA0_SIZE 0x3000 +#define CS461X_BA1_DATA1_SIZE 0x3800 +#define CS461X_BA1_PRG_SIZE 0x7000 +#define CS461X_BA1_REG_SIZE 0x0100 + +#define GOF_PER_SEC 200 + +#define CSDEBUG_INTERFACE 1 +#define CSDEBUG 1 +/* + * Turn on/off debugging compilation by using 1/0 respectively for CSDEBUG + * + * + * CSDEBUG is usual mode is set to 1, then use the + * cs_debuglevel and cs_debugmask to turn on or off debugging. + * Debug level of 1 has been defined to be kernel errors and info + * that should be printed on any released driver. + */ +#if CSDEBUG +#define CS_DBGOUT(mask,level,x) if((cs_debuglevel >= (level)) && ((mask) & cs_debugmask)) {x;} +#else +#define CS_DBGOUT(mask,level,x) +#endif +/* + * cs_debugmask areas + */ +#define CS_INIT 0x00000001 /* initialization and probe functions */ +#define CS_ERROR 0x00000002 /* tmp debugging bit placeholder */ +#define CS_INTERRUPT 0x00000004 /* interrupt handler (separate from all other) */ +#define CS_FUNCTION 0x00000008 /* enter/leave functions */ +#define CS_WAVE_WRITE 0x00000010 /* write information for wave */ +#define CS_WAVE_READ 0x00000020 /* read information for wave */ +#define CS_MIDI_WRITE 0x00000040 /* write information for midi */ +#define CS_MIDI_READ 0x00000080 /* read information for midi */ +#define CS_MPU401_WRITE 0x00000100 /* write information for mpu401 */ +#define CS_MPU401_READ 0x00000200 /* read information for mpu401 */ +#define CS_OPEN 0x00000400 /* all open functions in the driver */ +#define CS_RELEASE 0x00000800 /* all release functions in the driver */ +#define CS_PARMS 0x00001000 /* functional and operational parameters */ +#define CS_IOCTL 0x00002000 /* ioctl (non-mixer) */ +#define CS_PM 0x00004000 /* PM */ +#define CS_TMP 0x10000000 /* tmp debug mask bit */ + +#define CS_IOCTL_CMD_SUSPEND 0x1 // suspend +#define CS_IOCTL_CMD_RESUME 0x2 // resume + +#if CSDEBUG +static unsigned long cs_debuglevel=1; /* levels range from 1-9 */ +module_param(cs_debuglevel, ulong, 0644); +static unsigned long cs_debugmask=CS_INIT | CS_ERROR; /* use CS_DBGOUT with various mask values */ +module_param(cs_debugmask, ulong, 0644); +#endif +static unsigned long hercules_egpio_disable; /* if non-zero set all EGPIO to 0 */ +module_param(hercules_egpio_disable, ulong, 0); +static unsigned long initdelay=700; /* PM delay in millisecs */ +module_param(initdelay, ulong, 0); +static unsigned long powerdown=-1; /* turn on/off powerdown processing in driver */ +module_param(powerdown, ulong, 0); +#define DMABUF_DEFAULTORDER 3 +static unsigned long defaultorder=DMABUF_DEFAULTORDER; +module_param(defaultorder, ulong, 0); + +static int external_amp; +module_param(external_amp, bool, 0); +static int thinkpad; +module_param(thinkpad, bool, 0); + +/* +* set the powerdown module parm to 0 to disable all +* powerdown. also set thinkpad to 1 to disable powerdown, +* but also to enable the clkrun functionality. +*/ +static unsigned cs_powerdown=1; +static unsigned cs_laptop_wait=1; + +/* An instance of the 4610 channel */ +struct cs_channel +{ + int used; + int num; + void *state; +}; + +#define CS46XX_MAJOR_VERSION "1" +#define CS46XX_MINOR_VERSION "28" + +#ifdef __ia64__ +#define CS46XX_ARCH "64" //architecture key +#else +#define CS46XX_ARCH "32" //architecture key +#endif + +static struct list_head cs46xx_devs = { &cs46xx_devs, &cs46xx_devs }; + +/* magic numbers to protect our data structures */ +#define CS_CARD_MAGIC 0x43525553 /* "CRUS" */ +#define CS_STATE_MAGIC 0x4c4f4749 /* "LOGI" */ +#define NR_HW_CH 3 + +/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */ +#define NR_AC97 2 + +static const unsigned sample_size[] = { 1, 2, 2, 4 }; +static const unsigned sample_shift[] = { 0, 1, 1, 2 }; + +/* "software" or virtual channel, an instance of opened /dev/dsp */ +struct cs_state { + unsigned int magic; + struct cs_card *card; /* Card info */ + + /* single open lock mechanism, only used for recording */ + struct semaphore open_sem; + wait_queue_head_t open_wait; + + /* file mode */ + mode_t open_mode; + + /* virtual channel number */ + int virt; + + struct dmabuf { + /* wave sample stuff */ + unsigned int rate; + unsigned char fmt, enable; + + /* hardware channel */ + struct cs_channel *channel; + int pringbuf; /* Software ring slot */ + void *pbuf; /* 4K hardware DMA buffer */ + + /* OSS buffer management stuff */ + void *rawbuf; + dma_addr_t dma_handle; + unsigned buforder; + unsigned numfrag; + unsigned fragshift; + unsigned divisor; + unsigned type; + void *tmpbuff; /* tmp buffer for sample conversions */ + dma_addr_t dmaaddr; + dma_addr_t dmaaddr_tmpbuff; + unsigned buforder_tmpbuff; /* Log base 2 of size in bytes.. */ + + /* our buffer acts like a circular ring */ + unsigned hwptr; /* where dma last started, updated by update_ptr */ + unsigned swptr; /* where driver last clear/filled, updated by read/write */ + int count; /* bytes to be comsumed or been generated by dma machine */ + unsigned total_bytes; /* total bytes dmaed by hardware */ + unsigned blocks; /* total blocks */ + + unsigned error; /* number of over/underruns */ + unsigned underrun; /* underrun pending before next write has occurred */ + wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */ + + /* redundant, but makes calculations easier */ + unsigned fragsize; + unsigned dmasize; + unsigned fragsamples; + + /* OSS stuff */ + unsigned mapped:1; + unsigned ready:1; + unsigned endcleared:1; + unsigned SGok:1; + unsigned update_flag; + unsigned ossfragshift; + int ossmaxfrags; + unsigned subdivision; + } dmabuf; + /* Guard against mmap/write/read races */ + struct semaphore sem; +}; + +struct cs_card { + struct cs_channel channel[2]; + unsigned int magic; + + /* We keep cs461x cards in a linked list */ + struct cs_card *next; + + /* The cs461x has a certain amount of cross channel interaction + so we use a single per card lock */ + spinlock_t lock; + + /* Keep AC97 sane */ + spinlock_t ac97_lock; + + /* mixer use count */ + atomic_t mixer_use_cnt; + + /* PCI device stuff */ + struct pci_dev * pci_dev; + struct list_head list; + + unsigned int pctl, cctl; /* Hardware DMA flag sets */ + + /* soundcore stuff */ + int dev_audio; + int dev_midi; + + /* structures for abstraction of hardware facilities, codecs, banks and channels*/ + struct ac97_codec *ac97_codec[NR_AC97]; + struct cs_state *states[2]; + + u16 ac97_features; + + int amplifier; /* Amplifier control */ + void (*amplifier_ctrl)(struct cs_card *, int); + void (*amp_init)(struct cs_card *); + + int active; /* Active clocking */ + void (*active_ctrl)(struct cs_card *, int); + + /* hardware resources */ + unsigned long ba0_addr; + unsigned long ba1_addr; + u32 irq; + + /* mappings */ + void __iomem *ba0; + union + { + struct + { + u8 __iomem *data0; + u8 __iomem *data1; + u8 __iomem *pmem; + u8 __iomem *reg; + } name; + u8 __iomem *idx[4]; + } ba1; + + /* Function support */ + struct cs_channel *(*alloc_pcm_channel)(struct cs_card *); + struct cs_channel *(*alloc_rec_pcm_channel)(struct cs_card *); + void (*free_pcm_channel)(struct cs_card *, int chan); + + /* /dev/midi stuff */ + struct { + unsigned ird, iwr, icnt; + unsigned ord, owr, ocnt; + wait_queue_head_t open_wait; + wait_queue_head_t iwait; + wait_queue_head_t owait; + spinlock_t lock; + unsigned char ibuf[CS_MIDIINBUF]; + unsigned char obuf[CS_MIDIOUTBUF]; + mode_t open_mode; + struct semaphore open_sem; + } midi; + struct cs46xx_pm pm; +}; + +static int cs_open_mixdev(struct inode *inode, struct file *file); +static int cs_release_mixdev(struct inode *inode, struct file *file); +static int cs_ioctl_mixdev(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg); +static int cs_hardware_init(struct cs_card *card); +static int cs46xx_powerup(struct cs_card *card, unsigned int type); +static int cs461x_powerdown(struct cs_card *card, unsigned int type, int suspendflag); +static void cs461x_clear_serial_FIFOs(struct cs_card *card, int type); +static int cs46xx_suspend_tbl(struct pci_dev *pcidev, pm_message_t state); +static int cs46xx_resume_tbl(struct pci_dev *pcidev); + +#ifndef CS46XX_ACPI_SUPPORT +static int cs46xx_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data); +#endif + +#if CSDEBUG + +/* DEBUG ROUTINES */ + +#define SOUND_MIXER_CS_GETDBGLEVEL _SIOWR('M',120, int) +#define SOUND_MIXER_CS_SETDBGLEVEL _SIOWR('M',121, int) +#define SOUND_MIXER_CS_GETDBGMASK _SIOWR('M',122, int) +#define SOUND_MIXER_CS_SETDBGMASK _SIOWR('M',123, int) +#define SOUND_MIXER_CS_APM _SIOWR('M',124, int) + +static void printioctl(unsigned int x) +{ + unsigned int i; + unsigned char vidx; + /* these values are incorrect for the ac97 driver, fix. + * Index of mixtable1[] member is Device ID + * and must be <= SOUND_MIXER_NRDEVICES. + * Value of array member is index into s->mix.vol[] + */ + static const unsigned char mixtable1[SOUND_MIXER_NRDEVICES] = { + [SOUND_MIXER_PCM] = 1, /* voice */ + [SOUND_MIXER_LINE1] = 2, /* AUX */ + [SOUND_MIXER_CD] = 3, /* CD */ + [SOUND_MIXER_LINE] = 4, /* Line */ + [SOUND_MIXER_SYNTH] = 5, /* FM */ + [SOUND_MIXER_MIC] = 6, /* Mic */ + [SOUND_MIXER_SPEAKER] = 7, /* Speaker */ + [SOUND_MIXER_RECLEV] = 8, /* Recording level */ + [SOUND_MIXER_VOLUME] = 9 /* Master Volume */ + }; + + switch(x) + { + case SOUND_MIXER_CS_GETDBGMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_GETDBGMASK: ") ); + break; + case SOUND_MIXER_CS_GETDBGLEVEL: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_GETDBGLEVEL: ") ); + break; + case SOUND_MIXER_CS_SETDBGMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_SETDBGMASK: ") ); + break; + case SOUND_MIXER_CS_SETDBGLEVEL: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CS_SETDBGLEVEL: ") ); + break; + case OSS_GETVERSION: + CS_DBGOUT(CS_IOCTL, 4, printk("OSS_GETVERSION: ") ); + break; + case SNDCTL_DSP_SYNC: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SYNC: ") ); + break; + case SNDCTL_DSP_SETDUPLEX: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETDUPLEX: ") ); + break; + case SNDCTL_DSP_GETCAPS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETCAPS: ") ); + break; + case SNDCTL_DSP_RESET: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_RESET: ") ); + break; + case SNDCTL_DSP_SPEED: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SPEED: ") ); + break; + case SNDCTL_DSP_STEREO: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_STEREO: ") ); + break; + case SNDCTL_DSP_CHANNELS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_CHANNELS: ") ); + break; + case SNDCTL_DSP_GETFMTS: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETFMTS: ") ); + break; + case SNDCTL_DSP_SETFMT: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFMT: ") ); + break; + case SNDCTL_DSP_POST: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_POST: ") ); + break; + case SNDCTL_DSP_GETTRIGGER: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETTRIGGER: ") ); + break; + case SNDCTL_DSP_SETTRIGGER: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETTRIGGER: ") ); + break; + case SNDCTL_DSP_GETOSPACE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOSPACE: ") ); + break; + case SNDCTL_DSP_GETISPACE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETISPACE: ") ); + break; + case SNDCTL_DSP_NONBLOCK: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_NONBLOCK: ") ); + break; + case SNDCTL_DSP_GETODELAY: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETODELAY: ") ); + break; + case SNDCTL_DSP_GETIPTR: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETIPTR: ") ); + break; + case SNDCTL_DSP_GETOPTR: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETOPTR: ") ); + break; + case SNDCTL_DSP_GETBLKSIZE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_GETBLKSIZE: ") ); + break; + case SNDCTL_DSP_SETFRAGMENT: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETFRAGMENT: ") ); + break; + case SNDCTL_DSP_SUBDIVIDE: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SUBDIVIDE: ") ); + break; + case SOUND_PCM_READ_RATE: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_RATE: ") ); + break; + case SOUND_PCM_READ_CHANNELS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_CHANNELS: ") ); + break; + case SOUND_PCM_READ_BITS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_BITS: ") ); + break; + case SOUND_PCM_WRITE_FILTER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_WRITE_FILTER: ") ); + break; + case SNDCTL_DSP_SETSYNCRO: + CS_DBGOUT(CS_IOCTL, 4, printk("SNDCTL_DSP_SETSYNCRO: ") ); + break; + case SOUND_PCM_READ_FILTER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_PCM_READ_FILTER: ") ); + break; + + case SOUND_MIXER_PRIVATE1: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE1: ") ); + break; + case SOUND_MIXER_PRIVATE2: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE2: ") ); + break; + case SOUND_MIXER_PRIVATE3: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE3: ") ); + break; + case SOUND_MIXER_PRIVATE4: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE4: ") ); + break; + case SOUND_MIXER_PRIVATE5: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_PRIVATE5: ") ); + break; + case SOUND_MIXER_INFO: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_INFO: ") ); + break; + case SOUND_OLD_MIXER_INFO: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_OLD_MIXER_INFO: ") ); + break; + + default: + switch (_IOC_NR(x)) + { + case SOUND_MIXER_VOLUME: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_VOLUME: ") ); + break; + case SOUND_MIXER_SPEAKER: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_SPEAKER: ") ); + break; + case SOUND_MIXER_RECLEV: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECLEV: ") ); + break; + case SOUND_MIXER_MIC: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_MIC: ") ); + break; + case SOUND_MIXER_SYNTH: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_SYNTH: ") ); + break; + case SOUND_MIXER_RECSRC: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECSRC: ") ); + break; + case SOUND_MIXER_DEVMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_DEVMASK: ") ); + break; + case SOUND_MIXER_RECMASK: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_RECMASK: ") ); + break; + case SOUND_MIXER_STEREODEVS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_STEREODEVS: ") ); + break; + case SOUND_MIXER_CAPS: + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_CAPS:") ); + break; + default: + i = _IOC_NR(x); + if (i >= SOUND_MIXER_NRDEVICES || !(vidx = mixtable1[i])) + { + CS_DBGOUT(CS_IOCTL, 4, printk("UNKNOWN IOCTL: 0x%.8x NR=%d ",x,i) ); + } + else + { + CS_DBGOUT(CS_IOCTL, 4, printk("SOUND_MIXER_IOCTL AC9x: 0x%.8x NR=%d ", + x,i) ); + } + break; + } + } + CS_DBGOUT(CS_IOCTL, 4, printk("command = 0x%x IOC_NR=%d\n",x, _IOC_NR(x)) ); +} +#endif + +/* + * common I/O routines + */ + +static void cs461x_poke(struct cs_card *codec, unsigned long reg, unsigned int val) +{ + writel(val, codec->ba1.idx[(reg >> 16) & 3]+(reg&0xffff)); +} + +static unsigned int cs461x_peek(struct cs_card *codec, unsigned long reg) +{ + return readl(codec->ba1.idx[(reg >> 16) & 3]+(reg&0xffff)); +} + +static void cs461x_pokeBA0(struct cs_card *codec, unsigned long reg, unsigned int val) +{ + writel(val, codec->ba0+reg); +} + +static unsigned int cs461x_peekBA0(struct cs_card *codec, unsigned long reg) +{ + return readl(codec->ba0+reg); +} + + +static u16 cs_ac97_get(struct ac97_codec *dev, u8 reg); +static void cs_ac97_set(struct ac97_codec *dev, u8 reg, u16 data); + +static struct cs_channel *cs_alloc_pcm_channel(struct cs_card *card) +{ + if(card->channel[1].used==1) + return NULL; + card->channel[1].used=1; + card->channel[1].num=1; + return &card->channel[1]; +} + +static struct cs_channel *cs_alloc_rec_pcm_channel(struct cs_card *card) +{ + if(card->channel[0].used==1) + return NULL; + card->channel[0].used=1; + card->channel[0].num=0; + return &card->channel[0]; +} + +static void cs_free_pcm_channel(struct cs_card *card, int channel) +{ + card->channel[channel].state = NULL; + card->channel[channel].used=0; +} + +/* + * setup a divisor value to help with conversion from + * 16bit Stereo, down to 8bit stereo/mono or 16bit mono. + * assign a divisor of 1 if using 16bit Stereo as that is + * the only format that the static image will capture. + */ +static void cs_set_divisor(struct dmabuf *dmabuf) +{ + if(dmabuf->type == CS_TYPE_DAC) + dmabuf->divisor = 1; + else if( !(dmabuf->fmt & CS_FMT_STEREO) && + (dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 2; + else if( (dmabuf->fmt & CS_FMT_STEREO) && + !(dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 2; + else if( !(dmabuf->fmt & CS_FMT_STEREO) && + !(dmabuf->fmt & CS_FMT_16BIT)) + dmabuf->divisor = 4; + else + dmabuf->divisor = 1; + + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 8, printk( + "cs46xx: cs_set_divisor()- %s %d\n", + (dmabuf->type == CS_TYPE_ADC) ? "ADC" : "DAC", + dmabuf->divisor) ); +} + +/* +* mute some of the more prevalent registers to avoid popping. +*/ +static void cs_mute(struct cs_card *card, int state) +{ + struct ac97_codec *dev=card->ac97_codec[0]; + + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO "cs46xx: cs_mute()+ %s\n", + (state == CS_TRUE) ? "Muting" : "UnMuting") ); + + if(state == CS_TRUE) + { + /* + * fix pops when powering up on thinkpads + */ + card->pm.u32AC97_master_volume = (u32)cs_ac97_get( dev, + (u8)BA0_AC97_MASTER_VOLUME); + card->pm.u32AC97_headphone_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_HEADPHONE_VOLUME); + card->pm.u32AC97_master_volume_mono = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_MASTER_VOLUME_MONO); + card->pm.u32AC97_pcm_out_volume = (u32)cs_ac97_get(dev, + (u8)BA0_AC97_PCM_OUT_VOLUME); + + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_HEADPHONE_VOLUME, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME_MONO, 0x8000); + cs_ac97_set(dev, (u8)BA0_AC97_PCM_OUT_VOLUME, 0x8000); + } + else + { + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME, card->pm.u32AC97_master_volume); + cs_ac97_set(dev, (u8)BA0_AC97_HEADPHONE_VOLUME, card->pm.u32AC97_headphone_volume); + cs_ac97_set(dev, (u8)BA0_AC97_MASTER_VOLUME_MONO, card->pm.u32AC97_master_volume_mono); + cs_ac97_set(dev, (u8)BA0_AC97_PCM_OUT_VOLUME, card->pm.u32AC97_pcm_out_volume); + } + CS_DBGOUT(CS_FUNCTION, 2, printk(KERN_INFO "cs46xx: cs_mute()-\n")); +} + +/* set playback sample rate */ +static unsigned int cs_set_dac_rate(struct cs_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + unsigned int tmp1, tmp2; + unsigned int phiIncr; + unsigned int correctionPerGOF, correctionPerSec; + unsigned long flags; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_dac_rate()+ %d\n",rate) ); + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * ulCorrectionPerSec = Fs,in * 2^26 - Fs,out * phiIncr -M + * GOF_PER_SEC * correctionPerGOF + * + * i.e. + * + * phiIncr:other = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + */ + tmp1 = rate << 16; + phiIncr = tmp1 / 48000; + tmp1 -= phiIncr * 48000; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / 48000; + phiIncr += tmp2; + tmp1 -= tmp2 * 48000; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + + /* + * Fill in the SampleRateConverter control block. + */ + + spin_lock_irqsave(&state->card->lock, flags); + cs461x_poke(state->card, BA1_PSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + cs461x_poke(state->card, BA1_PPI, phiIncr); + spin_unlock_irqrestore(&state->card->lock, flags); + dmabuf->rate = rate; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_dac_rate()- %d\n",rate) ); + return rate; +} + +/* set recording sample rate */ +static unsigned int cs_set_adc_rate(struct cs_state * state, unsigned int rate) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int phiIncr, coeffIncr, tmp1, tmp2; + unsigned int correctionPerGOF, correctionPerSec, initialDelay; + unsigned int frameGroupLength, cnt; + unsigned long flags; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_adc_rate()+ %d\n",rate) ); + + /* + * We can only decimate by up to a factor of 1/9th the hardware rate. + * Correct the value if an attempt is made to stray outside that limit. + */ + if ((rate * 9) < 48000) + rate = 48000 / 9; + + /* + * We can not capture at at rate greater than the Input Rate (48000). + * Return an error if an attempt is made to stray outside that limit. + */ + if (rate > 48000) + rate = 48000; + + /* + * Compute the values used to drive the actual sample rate conversion. + * The following formulas are being computed, using inline assembly + * since we need to use 64 bit arithmetic to compute the values: + * + * coeffIncr = -floor((Fs,out * 2^23) / Fs,in) + * phiIncr = floor((Fs,in * 2^26) / Fs,out) + * correctionPerGOF = floor((Fs,in * 2^26 - Fs,out * phiIncr) / + * GOF_PER_SEC) + * correctionPerSec = Fs,in * 2^26 - Fs,out * phiIncr - + * GOF_PER_SEC * correctionPerGOF + * initialDelay = ceil((24 * Fs,in) / Fs,out) + * + * i.e. + * + * coeffIncr = neg(dividend((Fs,out * 2^23) / Fs,in)) + * phiIncr:ulOther = dividend:remainder((Fs,in * 2^26) / Fs,out) + * correctionPerGOF:correctionPerSec = + * dividend:remainder(ulOther / GOF_PER_SEC) + * initialDelay = dividend(((24 * Fs,in) + Fs,out - 1) / Fs,out) + */ + + tmp1 = rate << 16; + coeffIncr = tmp1 / 48000; + tmp1 -= coeffIncr * 48000; + tmp1 <<= 7; + coeffIncr <<= 7; + coeffIncr += tmp1 / 48000; + coeffIncr ^= 0xFFFFFFFF; + coeffIncr++; + tmp1 = 48000 << 16; + phiIncr = tmp1 / rate; + tmp1 -= phiIncr * rate; + tmp1 <<= 10; + phiIncr <<= 10; + tmp2 = tmp1 / rate; + phiIncr += tmp2; + tmp1 -= tmp2 * rate; + correctionPerGOF = tmp1 / GOF_PER_SEC; + tmp1 -= correctionPerGOF * GOF_PER_SEC; + correctionPerSec = tmp1; + initialDelay = ((48000 * 24) + rate - 1) / rate; + + /* + * Fill in the VariDecimate control block. + */ + spin_lock_irqsave(&card->lock, flags); + cs461x_poke(card, BA1_CSRC, + ((correctionPerSec << 16) & 0xFFFF0000) | (correctionPerGOF & 0xFFFF)); + cs461x_poke(card, BA1_CCI, coeffIncr); + cs461x_poke(card, BA1_CD, + (((BA1_VARIDEC_BUF_1 + (initialDelay << 2)) << 16) & 0xFFFF0000) | 0x80); + cs461x_poke(card, BA1_CPI, phiIncr); + spin_unlock_irqrestore(&card->lock, flags); + + /* + * Figure out the frame group length for the write back task. Basically, + * this is just the factors of 24000 (2^6*3*5^3) that are not present in + * the output sample rate. + */ + frameGroupLength = 1; + for (cnt = 2; cnt <= 64; cnt *= 2) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 2; + } + if (((rate / 3) * 3) != rate) { + frameGroupLength *= 3; + } + for (cnt = 5; cnt <= 125; cnt *= 5) { + if (((rate / cnt) * cnt) != rate) + frameGroupLength *= 5; + } + + /* + * Fill in the WriteBack control block. + */ + spin_lock_irqsave(&card->lock, flags); + cs461x_poke(card, BA1_CFG1, frameGroupLength); + cs461x_poke(card, BA1_CFG2, (0x00800000 | frameGroupLength)); + cs461x_poke(card, BA1_CCST, 0x0000FFFF); + cs461x_poke(card, BA1_CSPB, ((65536 * rate) / 24000)); + cs461x_poke(card, (BA1_CSPB + 4), 0x0000FFFF); + spin_unlock_irqrestore(&card->lock, flags); + dmabuf->rate = rate; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_set_adc_rate()- %d\n",rate) ); + return rate; +} + +/* prepare channel attributes for playback */ +static void cs_play_setup(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int tmp, Count, playFormat; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_play_setup()+\n") ); + cs461x_poke(card, BA1_PVOL, 0x80008000); + if(!dmabuf->SGok) + cs461x_poke(card, BA1_PBA, virt_to_bus(dmabuf->pbuf)); + + Count = 4; + playFormat=cs461x_peek(card, BA1_PFIE); + if ((dmabuf->fmt & CS_FMT_STEREO)) { + playFormat &= ~DMA_RQ_C2_AC_MONO_TO_STEREO; + Count *= 2; + } + else + playFormat |= DMA_RQ_C2_AC_MONO_TO_STEREO; + + if ((dmabuf->fmt & CS_FMT_16BIT)) { + playFormat &= ~(DMA_RQ_C2_AC_8_TO_16_BIT + | DMA_RQ_C2_AC_SIGNED_CONVERT); + Count *= 2; + } + else + playFormat |= (DMA_RQ_C2_AC_8_TO_16_BIT + | DMA_RQ_C2_AC_SIGNED_CONVERT); + + cs461x_poke(card, BA1_PFIE, playFormat); + + tmp = cs461x_peek(card, BA1_PDTC); + tmp &= 0xfffffe00; + cs461x_poke(card, BA1_PDTC, tmp | --Count); + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_play_setup()-\n") ); + +} + +static struct InitStruct +{ + u32 off; + u32 val; +} InitArray[] = { {0x00000040, 0x3fc0000f}, + {0x0000004c, 0x04800000}, + + {0x000000b3, 0x00000780}, + {0x000000b7, 0x00000000}, + {0x000000bc, 0x07800000}, + + {0x000000cd, 0x00800000}, + }; + +/* + * "SetCaptureSPValues()" -- Initialize record task values before each + * capture startup. + */ +static void SetCaptureSPValues(struct cs_card *card) +{ + unsigned i, offset; + CS_DBGOUT(CS_FUNCTION, 8, printk("cs46xx: SetCaptureSPValues()+\n") ); + for(i=0; i<sizeof(InitArray)/sizeof(struct InitStruct); i++) + { + offset = InitArray[i].off*4; /* 8bit to 32bit offset value */ + cs461x_poke(card, offset, InitArray[i].val ); + } + CS_DBGOUT(CS_FUNCTION, 8, printk("cs46xx: SetCaptureSPValues()-\n") ); +} + +/* prepare channel attributes for recording */ +static void cs_rec_setup(struct cs_state *state) +{ + struct cs_card *card = state->card; + struct dmabuf *dmabuf = &state->dmabuf; + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_rec_setup()+\n") ); + + SetCaptureSPValues(card); + + /* + * set the attenuation to 0dB + */ + cs461x_poke(card, BA1_CVOL, 0x80008000); + + /* + * set the physical address of the capture buffer into the SP + */ + cs461x_poke(card, BA1_CBA, virt_to_bus(dmabuf->rawbuf)); + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: cs_rec_setup()-\n") ); +} + + +/* get current playback/recording dma buffer pointer (byte offset from LBA), + called with spinlock held! */ + +static inline unsigned cs_get_dma_addr(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + u32 offset; + + if ( (!(dmabuf->enable & DAC_RUNNING)) && + (!(dmabuf->enable & ADC_RUNNING) ) ) + { + CS_DBGOUT(CS_ERROR, 2, printk( + "cs46xx: ERROR cs_get_dma_addr(): not enabled \n") ); + return 0; + } + + /* + * granularity is byte boundary, good part. + */ + if(dmabuf->enable & DAC_RUNNING) + { + offset = cs461x_peek(state->card, BA1_PBA); + } + else /* ADC_RUNNING must be set */ + { + offset = cs461x_peek(state->card, BA1_CBA); + } + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 9, + printk("cs46xx: cs_get_dma_addr() %d\n",offset) ); + offset = (u32)bus_to_virt((unsigned long)offset) - (u32)dmabuf->rawbuf; + CS_DBGOUT(CS_PARMS | CS_FUNCTION, 8, + printk("cs46xx: cs_get_dma_addr()- %d\n",offset) ); + return offset; +} + +static void resync_dma_ptrs(struct cs_state *state) +{ + struct dmabuf *dmabuf; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: resync_dma_ptrs()+ \n") ); + if(state) + { + dmabuf = &state->dmabuf; + dmabuf->hwptr=dmabuf->swptr = 0; + dmabuf->pringbuf = 0; + } + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: resync_dma_ptrs()- \n") ); +} + +/* Stop recording (lock held) */ +static inline void __stop_adc(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned int tmp; + + dmabuf->enable &= ~ADC_RUNNING; + + tmp = cs461x_peek(card, BA1_CCTL); + tmp &= 0xFFFF0000; + cs461x_poke(card, BA1_CCTL, tmp ); +} + +static void stop_adc(struct cs_state *state) +{ + unsigned long flags; + + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_adc()+ \n") ); + spin_lock_irqsave(&state->card->lock, flags); + __stop_adc(state); + spin_unlock_irqrestore(&state->card->lock, flags); + CS_DBGOUT(CS_FUNCTION, 2, printk("cs46xx: stop_adc()- \n") ); +} + +static void start_adc(struct cs_state *state) +{ + struct dmabuf *dmabuf = &state->dmabuf; + struct cs_card *card = state->card; + unsigned long flags; + unsigned int tmp; + + spin_lock_irqsave(&card->lock, flags); + if (!(dmabuf->enable & ADC_RUNNING) && + ((dmabuf->mapped || dmabuf->count < (signed)dmabuf->dmasize) + && dmabuf->ready) && + ((card->pm.flags & CS46XX_PM_IDLE) || + (card->pm.flags & CS46XX_PM_RESUMED)) ) + { + dmabuf->enable |= ADC_RUNNING; + cs_set_divisor(dmabuf); + tmp = cs461x_peek(card, BA1_CCTL); + tmp &= 0xFFFF0000; + tmp |= card-> |