summaryrefslogtreecommitdiffstats
path: root/sound/oss/cs46xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/cs46xx.c')
-rw-r--r--sound/oss/cs46xx.c5794
1 files changed, 5794 insertions, 0 deletions
diff --git a/sound/oss/cs46xx.c b/sound/oss/cs46xx.c
new file mode 100644
index 000000000000..8ce6b48f1881
--- /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->cctl;
+ CS_DBGOUT(CS_FU