summaryrefslogtreecommitdiff
path: root/recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch
diff options
context:
space:
mode:
authorDenys Dmytriyenko <denis@denix.org>2009-03-17 14:32:59 -0400
committerDenys Dmytriyenko <denis@denix.org>2009-03-17 14:32:59 -0400
commit709c4d66e0b107ca606941b988bad717c0b45d9b (patch)
tree37ee08b1eb308f3b2b6426d5793545c38396b838 /recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch
parentfa6cd5a3b993f16c27de4ff82b42684516d433ba (diff)
rename packages/ to recipes/ per earlier agreement
See links below for more details: http://thread.gmane.org/gmane.comp.handhelds.openembedded/21326 http://thread.gmane.org/gmane.comp.handhelds.openembedded/21816 Signed-off-by: Denys Dmytriyenko <denis@denix.org> Acked-by: Mike Westerhof <mwester@dls.net> Acked-by: Philip Balister <philip@balister.org> Acked-by: Khem Raj <raj.khem@gmail.com> Acked-by: Marcin Juszkiewicz <hrw@openembedded.org> Acked-by: Koen Kooi <koen@openembedded.org> Acked-by: Frans Meulenbroeks <fransmeulenbroeks@gmail.com>
Diffstat (limited to 'recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch')
-rw-r--r--recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch1415
1 files changed, 1415 insertions, 0 deletions
diff --git a/recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch b/recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch
new file mode 100644
index 0000000000..efb7e73e15
--- /dev/null
+++ b/recipes/linux/linux-gumstix-2.6.15/bkpxa-pxa-ac97.patch
@@ -0,0 +1,1415 @@
+Index: linux-2.6.15gum/include/linux/ac97_codec.h
+===================================================================
+--- linux-2.6.15gum.orig/include/linux/ac97_codec.h
++++ linux-2.6.15gum/include/linux/ac97_codec.h
+@@ -259,7 +259,8 @@ struct ac97_codec {
+ int type;
+ u32 model;
+
+- int modem:1;
++ unsigned modem:1;
++ unsigned power:1;
+
+ struct ac97_ops *codec_ops;
+
+Index: linux-2.6.15gum/sound/oss/Kconfig
+===================================================================
+--- linux-2.6.15gum.orig/sound/oss/Kconfig
++++ linux-2.6.15gum/sound/oss/Kconfig
+@@ -178,6 +178,14 @@ config SOUND_MAESTRO3
+ Say Y or M if you have a sound system driven by ESS's Maestro 3
+ PCI sound chip.
+
++config SOUND_PXA_AC97
++ tristate "PXA AC97 support"
++ depends on SOUND_PRIME!=n && ARCH_PXA && SOUND
++
++config SOUND_PXA_AUDIO
++ tristate "PXA audio support"
++ depends on SOUND_PXA_AC97
++
+ config SOUND_ICH
+ tristate "Intel ICH (i8xx) audio support"
+ depends on SOUND_PRIME && PCI
+@@ -1125,6 +1133,9 @@ config SOUND_AD1980
+ tristate "AD1980 front/back switch plugin"
+ depends on SOUND_PRIME && OBSOLETE_OSS_DRIVER
+
++config SOUND_WM97XX
++ tristate "WM97XX sound/touchscreen codec"
++
+ config SOUND_SH_DAC_AUDIO
+ tristate "SuperH DAC audio support"
+ depends on SOUND_PRIME && CPU_SH3
+Index: linux-2.6.15gum/sound/oss/Makefile
+===================================================================
+--- linux-2.6.15gum.orig/sound/oss/Makefile
++++ linux-2.6.15gum/sound/oss/Makefile
+@@ -44,6 +44,8 @@ obj-$(CONFIG_SOUND_VIA82CXXX) += via82cx
+ ifeq ($(CONFIG_MIDI_VIA82CXXX),y)
+ obj-$(CONFIG_SOUND_VIA82CXXX) += sound.o uart401.o
+ endif
++obj-$(CONFIG_SOUND_PXA_AC97) += pxa-ac97.o ac97_codec.o
++obj-$(CONFIG_SOUND_PXA_AUDIO) += pxa-audio.o
+ obj-$(CONFIG_SOUND_YMFPCI) += ymfpci.o ac97_codec.o
+ ifeq ($(CONFIG_SOUND_YMFPCI_LEGACY),y)
+ obj-$(CONFIG_SOUND_YMFPCI) += opl3.o uart401.o
+Index: linux-2.6.15gum/sound/oss/ac97_codec.c
+===================================================================
+--- linux-2.6.15gum.orig/sound/oss/ac97_codec.c
++++ linux-2.6.15gum/sound/oss/ac97_codec.c
+@@ -84,6 +84,7 @@ static int crystal_digital_control(struc
+ static int cmedia_init(struct ac97_codec * codec);
+ static int cmedia_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
+ static int generic_digital_control(struct ac97_codec *codec, int slots, int rate, int mode);
++static int ucb1400_init(struct ac97_codec *codec);
+
+
+ /*
+@@ -119,6 +120,7 @@ static struct ac97_ops crystal_digital_o
+ static struct ac97_ops ad1886_ops = { ad1886_init, eapd_control, NULL };
+ static struct ac97_ops cmedia_ops = { NULL, eapd_control, NULL};
+ static struct ac97_ops cmedia_digital_ops = { cmedia_init, eapd_control, cmedia_digital_control};
++static struct ac97_ops ucb1400_ops = { ucb1400_init, eapd_control, NULL };
+
+ /* sorted by vendor/device id */
+ static const struct {
+@@ -164,6 +166,7 @@ static const struct {
+ {0x4e534331, "National Semiconductor LM4549", &null_ops},
+ {0x53494c22, "Silicon Laboratory Si3036", &null_ops},
+ {0x53494c23, "Silicon Laboratory Si3038", &null_ops},
++ {0x50534304, "Philips UCB1400", &ucb1400_ops},
+ {0x545200FF, "TriTech TR?????", &tritech_m_ops},
+ {0x54524102, "TriTech TR28022", &null_ops},
+ {0x54524103, "TriTech TR28023", &null_ops},
+@@ -461,6 +464,17 @@ static void ac97_write_mixer(struct ac97
+ val = codec->codec_read(codec, mh->offset);
+ printk(" -> 0x%04x\n", val);
+ #endif
++
++ if (val & AC97_MUTE)
++ val = 0;
++ else
++ val = 1;
++ if ((oss_channel == SOUND_MIXER_VOLUME) &&
++ (codec->codec_ops->amplifier) &&
++ (codec->power != val)) {
++ codec->power = val;
++ codec->codec_ops->amplifier (codec, codec->power);
++ }
+ }
+
+ /* a thin wrapper for write_mixer */
+@@ -1092,6 +1106,13 @@ static int wolfson_init05(struct ac97_co
+ {
+ /* set front mixer volume */
+ codec->codec_write(codec, AC97_WM97XX_FMIXER_VOL, 0x0808);
++ /*codec->codec_write(codec, 0x78, 0xc004);
++ while(1){
++ codec->codec_write(codec, 0x76, 0xa020);
++ printk("%08x ", codec->codec_read(codec, 0x76));
++ printk("%08x ", codec->codec_read(codec, 0x78));
++ printk("%08x\n", codec->codec_read(codec, 0x7A));
++ }*/
+ return 0;
+ }
+
+@@ -1313,6 +1334,14 @@ static int pt101_init(struct ac97_codec
+ }
+ #endif
+
++static int ucb1400_init(struct ac97_codec *codec)
++{
++ codec->codec_write(codec,AC97_EXTENDED_STATUS,1);
++ //codec->codec_write(codec, 0x6a, 0x1ff7);
++ codec->codec_write(codec, 0x6a, 0x0050);
++ codec->codec_write(codec, 0x6c, 0x0030);
++ return 0;
++}
+
+ EXPORT_SYMBOL(ac97_read_proc);
+ EXPORT_SYMBOL(ac97_probe_codec);
+Index: linux-2.6.15gum/sound/oss/pxa-ac97.c
+===================================================================
+--- /dev/null
++++ linux-2.6.15gum/sound/oss/pxa-ac97.c
+@@ -0,0 +1,357 @@
++/*
++ * linux/drivers/sound/pxa-ac97.c -- AC97 interface for the Cotula chip
++ *
++ * Author: Nicolas Pitre
++ * Created: Aug 15, 2001
++ * Copyright: MontaVista Software Inc.
++ *
++ * Forward ported to 2.6 by Ian Molton 15/09/2003
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/slab.h>
++#include <linux/pci.h>
++#include <linux/interrupt.h>
++#include <linux/completion.h>
++#include <linux/delay.h>
++#include <linux/poll.h>
++#include <linux/sound.h>
++#include <linux/soundcard.h>
++#include <linux/ac97_codec.h>
++
++#include <asm/hardware.h>
++#include <asm/arch/pxa-regs.h>
++#include <asm/irq.h>
++#include <asm/uaccess.h>
++#include <asm/semaphore.h>
++#include <asm/dma.h>
++
++#include "pxa-audio.h"
++
++static struct completion CAR_completion;
++static int waitingForMask;
++static DECLARE_MUTEX(CAR_mutex);
++
++static u16 pxa_ac97_read(struct ac97_codec *codec, u8 reg)
++{
++ u16 val = -1;
++
++ down(&CAR_mutex);
++ if (!(CAR & CAR_CAIP)) {
++ volatile u32 *reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1);
++
++ waitingForMask=GSR_SDONE;
++
++ init_completion(&CAR_completion);
++ (void)*reg_addr; //start read access across the ac97 link
++ wait_for_completion(&CAR_completion);
++
++ if (GSR & GSR_RDCS) {
++ GSR = GSR_RDCS; //write a 1 to clear
++ printk(KERN_CRIT "%s: read codec register timeout.\n", __FUNCTION__);
++ }
++
++ init_completion(&CAR_completion);
++ val = *reg_addr; //valid data now but we've just started another cycle...
++ wait_for_completion(&CAR_completion);
++
++ } else {
++ printk(KERN_CRIT"%s: CAR_CAIP already set\n", __FUNCTION__);
++ }
++ up(&CAR_mutex);
++ //printk("%s(0x%02x) = 0x%04x\n", __FUNCTION__, reg, val);
++ return val;
++}
++
++static void pxa_ac97_write(struct ac97_codec *codec, u8 reg, u16 val)
++{
++ down(&CAR_mutex);
++ if (!(CAR & CAR_CAIP)) {
++ volatile u32 *reg_addr = (u32 *)&PAC_REG_BASE + (reg >> 1);
++
++ waitingForMask=GSR_CDONE;
++ init_completion(&CAR_completion);
++ *reg_addr = val;
++ wait_for_completion(&CAR_completion);
++ } else {
++ printk(KERN_CRIT "%s: CAR_CAIP already set\n", __FUNCTION__);
++ }
++ up(&CAR_mutex);
++ //printk("%s(0x%02x, 0x%04x)\n", __FUNCTION__, reg, val);
++}
++
++static irqreturn_t pxa_ac97_irq(int irq, void *dev_id, struct pt_regs *regs)
++{
++ int gsr = GSR;
++ GSR = gsr & (GSR_SDONE|GSR_CDONE); //write a 1 to clear
++ if (gsr & waitingForMask)
++ complete(&CAR_completion);
++
++ return IRQ_HANDLED;
++}
++
++static struct ac97_codec pxa_ac97_codec = {
++ codec_read: pxa_ac97_read,
++ codec_write: pxa_ac97_write,
++};
++
++static DECLARE_MUTEX(pxa_ac97_mutex);
++static int pxa_ac97_refcount;
++
++int pxa_ac97_get(struct ac97_codec **codec)
++{
++ int ret;
++
++ *codec = NULL;
++ down(&pxa_ac97_mutex);
++
++ if (!pxa_ac97_refcount) {
++ ret = request_irq(IRQ_AC97, pxa_ac97_irq, 0, "AC97", NULL);
++ if (ret)
++ return ret;
++
++ CKEN |= CKEN2_AC97;
++
++ pxa_gpio_mode(GPIO31_SYNC_AC97_MD);
++ pxa_gpio_mode(GPIO30_SDATA_OUT_AC97_MD);
++ pxa_gpio_mode(GPIO28_BITCLK_AC97_MD);
++ pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD);
++
++ GCR = 0;
++ udelay(10);
++ GCR = GCR_COLD_RST|GCR_CDONE_IE|GCR_SDONE_IE;
++ while (!(GSR & GSR_PCR)) {
++ schedule();
++ }
++
++ ret = ac97_probe_codec(&pxa_ac97_codec);
++ if (ret != 1) {
++ free_irq(IRQ_AC97, NULL);
++ GCR = GCR_ACLINK_OFF;
++ CKEN &= ~CKEN2_AC97;
++ return ret;
++ }
++ }
++
++ pxa_ac97_refcount++;
++ up(&pxa_ac97_mutex);
++ *codec = &pxa_ac97_codec;
++ return 0;
++}
++
++void pxa_ac97_put(void)
++{
++ down(&pxa_ac97_mutex);
++ pxa_ac97_refcount--;
++ if (!pxa_ac97_refcount) {
++ GCR = GCR_ACLINK_OFF;
++ CKEN &= ~CKEN2_AC97;
++ free_irq(IRQ_AC97, NULL);
++ }
++ up(&pxa_ac97_mutex);
++}
++
++EXPORT_SYMBOL(pxa_ac97_get);
++EXPORT_SYMBOL(pxa_ac97_put);
++
++
++/*
++ * Audio Mixer stuff
++ */
++
++static audio_state_t ac97_audio_state;
++static audio_stream_t ac97_audio_in;
++
++/*
++ * According to the PXA250 spec, mic-in should use different
++ * DRCMR and different AC97 FIFO.
++ * Unfortunately current UCB1400 versions (up to ver 2A) don't
++ * produce slot 6 for the audio input frame, therefore the PXA
++ * AC97 mic-in FIFO is always starved.
++ * But since UCB1400 is not the only audio CODEC out there,
++ * this is still enabled by default.
++ */
++static void update_audio_in (void)
++{
++#if 1
++ long val;
++
++ /* Use the value stuffed by ac97_recmask_io()
++ * into recording select register
++ */
++ val = pxa_ac97_codec.codec_read(&pxa_ac97_codec, AC97_RECORD_SELECT);
++ pxa_audio_clear_buf(&ac97_audio_in);
++ *ac97_audio_in.drcmr = 0;
++ if (val == 0) {
++ ac97_audio_in.dcmd = DCMD_RXMCDR;
++ ac97_audio_in.drcmr = &DRCMRRXMCDR;
++ ac97_audio_in.dev_addr = __PREG(MCDR);
++ } else {
++ ac97_audio_in.dcmd = DCMD_RXPCDR;
++ ac97_audio_in.drcmr = &DRCMRRXPCDR;
++ ac97_audio_in.dev_addr = __PREG(PCDR);
++ }
++ if (ac97_audio_state.rd_ref)
++ *ac97_audio_in.drcmr =
++ ac97_audio_in.dma_ch | DRCMR_MAPVLD;
++#endif
++}
++
++static int mixer_ioctl( struct inode *inode, struct file *file,
++ unsigned int cmd, unsigned long arg)
++{
++ int ret;
++
++ ret = pxa_ac97_codec.mixer_ioctl(&pxa_ac97_codec, cmd, arg);
++ if (ret)
++ return ret;
++
++ /* We must snoop for some commands to provide our own extra processing */
++ switch (cmd) {
++ case SOUND_MIXER_WRITE_RECSRC:
++ update_audio_in ();
++ break;
++ }
++ return 0;
++}
++
++static struct file_operations mixer_fops = {
++ ioctl: mixer_ioctl,
++ llseek: no_llseek,
++ owner: THIS_MODULE
++};
++
++/*
++ * AC97 codec ioctls
++ */
++
++static int codec_adc_rate = 48000;
++static int codec_dac_rate = 48000;
++
++static int ac97_ioctl(struct inode *inode, struct file *file,
++ unsigned int cmd, unsigned long arg)
++{
++ int ret;
++ long val = 0;
++
++ switch(cmd) {
++ case SNDCTL_DSP_STEREO:
++ ret = get_user(val, (int *) arg);
++ if (ret)
++ return ret;
++ /* FIXME: do we support mono? */
++ ret = (val == 0) ? -EINVAL : 1;
++ return put_user(ret, (int *) arg);
++
++ case SNDCTL_DSP_CHANNELS:
++ case SOUND_PCM_READ_CHANNELS:
++ /* FIXME: do we support mono? */
++ return put_user(2, (long *) arg);
++
++ case SNDCTL_DSP_SPEED:
++ ret = get_user(val, (long *) arg);
++ if (ret)
++ return ret;
++ if (file->f_mode & FMODE_READ)
++ codec_adc_rate = ac97_set_adc_rate(&pxa_ac97_codec, val);
++ if (file->f_mode & FMODE_WRITE)
++ codec_dac_rate = ac97_set_dac_rate(&pxa_ac97_codec, val);
++ /* fall through */
++ case SOUND_PCM_READ_RATE:
++ if (file->f_mode & FMODE_READ)
++ val = codec_adc_rate;
++ if (file->f_mode & FMODE_WRITE)
++ val = codec_dac_rate;
++ return put_user(val, (long *) arg);
++
++ case SNDCTL_DSP_SETFMT:
++ case SNDCTL_DSP_GETFMTS:
++ /* FIXME: can we do other fmts? */
++ return put_user(AFMT_S16_LE, (long *) arg);
++
++ default:
++ /* Maybe this is meant for the mixer (As per OSS Docs) */
++ return mixer_ioctl(inode, file, cmd, arg);
++ }
++ return 0;
++}
++
++
++/*
++ * Audio stuff
++ */
++
++static audio_stream_t ac97_audio_out = {
++ name: "AC97 audio out",
++ dcmd: DCMD_TXPCDR,
++ drcmr: &DRCMRTXPCDR,
++ dev_addr: __PREG(PCDR),
++};
++
++static audio_stream_t ac97_audio_in = {
++ name: "AC97 audio in",
++ dcmd: DCMD_RXPCDR,
++ drcmr: &DRCMRRXPCDR,
++ dev_addr: __PREG(PCDR),
++};
++
++static audio_state_t ac97_audio_state = {
++ output_stream: &ac97_audio_out,
++ input_stream: &ac97_audio_in,
++ client_ioctl: ac97_ioctl,
++ sem: __MUTEX_INITIALIZER(ac97_audio_state.sem),
++};
++
++static int ac97_audio_open(struct inode *inode, struct file *file)
++{
++ return pxa_audio_attach(inode, file, &ac97_audio_state);
++}
++
++/*
++ * Missing fields of this structure will be patched with the call
++ * to pxa_audio_attach().
++ */
++
++static struct file_operations ac97_audio_fops = {
++ open: ac97_audio_open,
++ owner: THIS_MODULE
++};
++
++
++static int __init pxa_ac97_init(void)
++{
++ int ret;
++ struct ac97_codec *dummy;
++
++ ret = pxa_ac97_get(&dummy);
++ if (ret)
++ return ret;
++
++ update_audio_in ();
++
++ ac97_audio_state.dev_dsp = register_sound_dsp(&ac97_audio_fops, -1);
++ pxa_ac97_codec.dev_mixer = register_sound_mixer(&mixer_fops, -1);
++
++ return 0;
++}
++
++static void __exit pxa_ac97_exit(void)
++{
++ unregister_sound_dsp(ac97_audio_state.dev_dsp);
++ unregister_sound_mixer(pxa_ac97_codec.dev_mixer);
++ pxa_ac97_put();
++}
++
++
++module_init(pxa_ac97_init);
++module_exit(pxa_ac97_exit);
++
++MODULE_AUTHOR("Nicolas Pitre");
++MODULE_DESCRIPTION("AC97 interface for the Cotula chip");
++MODULE_LICENSE("GPL");
+Index: linux-2.6.15gum/sound/oss/pxa-audio.c
+===================================================================
+--- /dev/null
++++ linux-2.6.15gum/sound/oss/pxa-audio.c
+@@ -0,0 +1,858 @@
++/*
++ * linux/drivers/sound/pxa-audio.c -- audio interface for the Cotula chip
++ *
++ * Author: Nicolas Pitre
++ * Created: Aug 15, 2001
++ * Copyright: MontaVista Software Inc.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++
++#include <linux/init.h>
++#include <linux/module.h>
++#include <linux/kernel.h>
++#include <linux/slab.h>
++#include <linux/pci.h>
++#include <linux/poll.h>
++#include <linux/sound.h>
++#include <linux/soundcard.h>
++
++#include <asm/hardware.h>
++#include <asm/arch/pxa-regs.h>
++#include <asm/irq.h>
++#include <asm/uaccess.h>
++#include <asm/semaphore.h>
++#include <asm/dma.h>
++
++#include "pxa-audio.h"
++
++
++#define AUDIO_NBFRAGS_DEFAULT 8
++#define AUDIO_FRAGSIZE_DEFAULT 8192
++
++#define MAX_DMA_SIZE 4096
++#define DMA_DESC_SIZE sizeof(pxa_dma_desc)
++
++
++/*
++ * This function frees all buffers
++ */
++#define audio_clear_buf pxa_audio_clear_buf
++
++void pxa_audio_clear_buf(audio_stream_t * s)
++{
++ DECLARE_WAITQUEUE(wait, current);
++ int frag;
++
++ if (!s->buffers)
++ return;
++
++ /* Ensure DMA isn't running */
++ set_current_state(TASK_UNINTERRUPTIBLE);
++ add_wait_queue(&s->stop_wq, &wait);
++ DCSR(s->dma_ch) = DCSR_STOPIRQEN;
++ schedule();
++ remove_wait_queue(&s->stop_wq, &wait);
++
++ /* free DMA buffers */
++ for (frag = 0; frag < s->nbfrags; frag++) {
++ audio_buf_t *b = &s->buffers[frag];
++ if (!b->master)
++ continue;
++ dma_free_writecombine(NULL, b->master, b->data, b->dma_desc->dsadr);
++ }
++
++ /* free descriptor ring */
++ if (s->buffers->dma_desc)
++ dma_free_writecombine(NULL, s->nbfrags * s->descs_per_frag * DMA_DESC_SIZE,
++ s->buffers->dma_desc, s->dma_desc_phys);
++
++ /* free buffer structure array */
++ kfree(s->buffers);
++ s->buffers = NULL;
++}
++
++/*
++ * This function allocates the DMA descriptor array and buffer data space
++ * according to the current number of fragments and fragment size.
++ */
++static int audio_setup_buf(audio_stream_t * s)
++{
++ pxa_dma_desc *dma_desc;
++ dma_addr_t dma_desc_phys;
++ int nb_desc, frag, i, buf_size = 0;
++ char *dma_buf = NULL;
++ dma_addr_t dma_buf_phys = 0;
++
++ if (s->buffers)
++ return -EBUSY;
++
++ /* Our buffer structure array */
++ s->buffers = kmalloc(sizeof(audio_buf_t) * s->nbfrags, GFP_KERNEL);
++ if (!s->buffers)
++ goto err;
++ memzero(s->buffers, sizeof(audio_buf_t) * s->nbfrags);
++
++ /*
++ * Our DMA descriptor array:
++ * for Each fragment we have one checkpoint descriptor plus one
++ * descriptor per MAX_DMA_SIZE byte data blocks.
++ */
++ nb_desc = (1 + (s->fragsize + MAX_DMA_SIZE - 1)/MAX_DMA_SIZE) * s->nbfrags;
++ dma_desc = dma_alloc_writecombine(NULL, nb_desc * DMA_DESC_SIZE,
++ &dma_desc_phys, GFP_KERNEL);
++
++ if (!dma_desc)
++ goto err;
++ s->descs_per_frag = nb_desc / s->nbfrags;
++ s->buffers->dma_desc = dma_desc;
++ s->dma_desc_phys = dma_desc_phys;
++ for (i = 0; i < nb_desc - 1; i++)
++ dma_desc[i].ddadr = dma_desc_phys + (i + 1) * DMA_DESC_SIZE;
++ dma_desc[i].ddadr = dma_desc_phys;
++
++ /* Our actual DMA buffers */
++ for (frag = 0; frag < s->nbfrags; frag++) {
++ audio_buf_t *b = &s->buffers[frag];
++
++ /*
++ * Let's allocate non-cached memory for DMA buffers.
++ * We try to allocate all memory at once.
++ * If this fails (a common reason is memory fragmentation),
++ * then we'll try allocating smaller buffers.
++ */
++ if (!buf_size) {
++ buf_size = (s->nbfrags - frag) * s->fragsize;
++ do {
++ dma_buf = dma_alloc_writecombine(NULL, buf_size,
++ &dma_buf_phys,
++ GFP_KERNEL);
++ if (!dma_buf)
++ buf_size -= s->fragsize;
++ } while (!dma_buf && buf_size);
++ if (!dma_buf)
++ goto err;
++ b->master = buf_size;
++ memzero(dma_buf, buf_size);
++ }
++
++ /*
++ * Set up our checkpoint descriptor. Since the count
++ * is always zero, we'll abuse the dsadr and dtadr fields
++ * just in case this one is picked up by the hardware
++ * while processing SOUND_DSP_GETPTR.
++ */
++ dma_desc->dsadr = dma_buf_phys;
++ dma_desc->dtadr = dma_buf_phys;
++ dma_desc->dcmd = DCMD_ENDIRQEN;
++ if (s->output && !s->mapped)
++ dma_desc->ddadr |= DDADR_STOP;
++ b->dma_desc = dma_desc++;
++
++ /* set up the actual data descriptors */
++ for (i = 0; (i * MAX_DMA_SIZE) < s->fragsize; i++) {
++ dma_desc[i].dsadr = (s->output) ?
++ (dma_buf_phys + i*MAX_DMA_SIZE) : s->dev_addr;
++ dma_desc[i].dtadr = (s->output) ?
++ s->dev_addr : (dma_buf_phys + i*MAX_DMA_SIZE);
++ dma_desc[i].dcmd = s->dcmd |
++ ((s->fragsize < MAX_DMA_SIZE) ?
++ s->fragsize : MAX_DMA_SIZE);
++ }
++ dma_desc += i;
++
++ /* handle buffer pointers */
++ b->data = dma_buf;
++ dma_buf += s->fragsize;
++ dma_buf_phys += s->fragsize;
++ buf_size -= s->fragsize;
++ }
++
++ s->usr_frag = s->dma_frag = 0;
++ s->bytecount = 0;
++ s->fragcount = 0;
++ sema_init(&s->sem, (s->output) ? s->nbfrags : 0);
++ return 0;
++
++err:
++ printk("pxa-audio: unable to allocate audio memory\n ");
++ audio_clear_buf(s);
++ return -ENOMEM;
++}
++
++/*
++ * Our DMA interrupt handler
++ */
++static void audio_dma_irq(int ch, void *dev_id, struct pt_regs *regs)
++{
++ audio_stream_t *s = dev_id;
++ u_int dcsr;
++
++ dcsr = DCSR(ch);
++ DCSR(ch) = dcsr & ~DCSR_STOPIRQEN;
++
++ if (!s->buffers) {
++ printk("AC97 DMA: wow... received IRQ for channel %d but no buffer exists\n", ch);
++ return;
++ }
++
++ if (dcsr & DCSR_BUSERR)
++ printk("AC97 DMA: bus error interrupt on channel %d\n", ch);
++
++ if (dcsr & DCSR_ENDINTR) {
++ u_long cur_dma_desc;
++ u_int cur_dma_frag;
++
++ /*
++ * Find out which DMA desc is current. Note that DDADR
++ * points to the next desc, not the current one.
++ */
++ cur_dma_desc = DDADR(ch) - s->dma_desc_phys - DMA_DESC_SIZE;
++
++ /*
++ * Let the compiler nicely optimize constant divisors into
++ * multiplications for the common cases which is much faster.
++ * Common cases: x = 1 + (1 << y) for y = [0..3]
++ */
++ switch (s->descs_per_frag) {
++ case 2: cur_dma_frag = cur_dma_desc / (2*DMA_DESC_SIZE); break;
++ case 3: cur_dma_frag = cur_dma_desc / (3*DMA_DESC_SIZE); break;
++ case 5: cur_dma_frag = cur_dma_desc / (5*DMA_DESC_SIZE); break;
++ case 9: cur_dma_frag = cur_dma_desc / (9*DMA_DESC_SIZE); break;
++ default: cur_dma_frag =
++ cur_dma_desc / (s->descs_per_frag * DMA_DESC_SIZE);
++ }
++
++ /* Account for possible wrap back of cur_dma_desc above */
++ if (cur_dma_frag >= s->nbfrags)
++ cur_dma_frag = s->nbfrags - 1;
++
++ while (s->dma_frag != cur_dma_frag) {
++ if (!s->mapped) {
++ /*
++ * This fragment is done - set the checkpoint
++ * descriptor to STOP until it is gets
++ * processed by the read or write function.
++ */
++ s->buffers[s->dma_frag].dma_desc->ddadr |= DDADR_STOP;
++ up(&s->sem);
++ }
++ if (++s->dma_frag >= s->nbfrags)
++ s->dma_frag = 0;
++
++ /* Accounting */
++ s->bytecount += s->fragsize;
++ s->fragcount++;
++ }
++
++ /* ... and for polling processes */
++ wake_up(&s->frag_wq);
++ }
++
++ if ((dcsr & DCSR_STOPIRQEN) && (dcsr & DCSR_STOPSTATE))
++ wake_up(&s->stop_wq);
++}
++
++/*
++ * Validate and sets up buffer fragments, etc.
++ */
++static int audio_set_fragments(audio_stream_t *s, int val)
++{
++ if (s->mapped || DCSR(s->dma_ch) & DCSR_RUN)
++ return -EBUSY;
++ if (s->buffers)
++ audio_clear_buf(s);
++ s->nbfrags = (val >> 16) & 0x7FFF;
++ val &= 0xffff;
++ if (val < 5)
++ val = 5;
++ if (val > 15)
++ val = 15;
++ s->fragsize = 1 << val;
++ if (s->nbfrags < 2)
++ s->nbfrags = 2;
++ if (s->nbfrags * s->fragsize > 256 * 1024)
++ s->nbfrags = 256 * 1024 / s->fragsize;
++ if (audio_setup_buf(s))
++ return -ENOMEM;
++ return val|(s->nbfrags << 16);
++}
++
++
++/*
++ * The fops functions
++ */
++
++static int audio_write(struct file *file, const char *buffer,
++ size_t count, loff_t * ppos)
++{
++ const char *buffer0 = buffer;
++ audio_state_t *state = (audio_state_t *)file->private_data;
++ audio_stream_t *s = state->output_stream;
++ int chunksize, ret = 0;
++
++ if (ppos != &file->f_pos)
++ return -ESPIPE;
++ if (s->mapped)
++ return -ENXIO;
++ if (!s->buffers && audio_setup_buf(s))
++ return -ENOMEM;
++
++ while (count > 0) {
++ audio_buf_t *b = &s->buffers[s->usr_frag];
++
++ /* Grab a fragment */
++ if (file->f_flags & O_NONBLOCK) {
++ ret = -EAGAIN;
++ if (down_trylock(&s->sem))
++ break;
++ } else {
++ ret = -ERESTARTSYS;
++ if (down_interruptible(&s->sem))
++ break;
++ }
++
++ /* Feed the current buffer */
++ chunksize = s->fragsize - b->offset;
++ if (chunksize > count)
++ chunksize = count;
++ if (copy_from_user(b->data + b->offset, buffer, chunksize)) {
++ up(&s->sem);
++ return -EFAULT;
++ }
++
++ b->offset += chunksize;
++ buffer += chunksize;
++ count -= chunksize;
++ if (b->offset < s->fragsize) {
++ ret = 0;
++ up(&s->sem);
++ break;
++ }
++
++ /*
++ * Activate DMA on current buffer.
++ * We unlock this fragment's checkpoint descriptor and
++ * kick DMA if it is idle. Using checkpoint descriptors
++ * allows for control operations without the need for
++ * stopping the DMA channel if it is already running.
++ */
++ b->offset = 0;
++ b->dma_desc->ddadr &= ~DDADR_STOP;
++ if (DCSR(s->dma_ch) & DCSR_STOPSTATE) {
++ DDADR(s->dma_ch) = b->dma_desc->ddadr;
++ DCSR(s->dma_ch) = DCSR_RUN;
++ }
++
++ /* move the index to the next fragment */
++ if (++s->usr_frag >= s->nbfrags)
++ s->usr_frag = 0;
++ }
++
++ if ((buffer - buffer0))
++ ret = buffer - buffer0;
++ return ret;
++}
++
++
++static int audio_read(struct file *file, char *buffer,
++ size_t count, loff_t * ppos)
++{
++ char *buffer0 = buffer;
++ audio_state_t *state = file->private_data;
++ audio_stream_t *s = state->input_stream;
++ int chunksize, ret = 0;
++
++ if (ppos != &file->f_pos)
++ return -ESPIPE;
++ if (s->mapped)
++ return -ENXIO;
++ if (!s->buffers && audio_setup_buf(s))
++ return -ENOMEM;
++
++ while (count > 0) {
++ audio_buf_t *b = &s->buffers[s->usr_frag];
++
++ /* prime DMA */
++ if (DCSR(s->dma_ch) & DCSR_STOPSTATE) {
++ DDADR(s->dma_ch) =
++ s->buffers[s->dma_frag].dma_desc->ddadr;
++ DCSR(s->dma_ch) = DCSR_RUN;
++ }
++
++ /* Wait for a buffer to become full */
++ if (file->f_flags & O_NONBLOCK) {
++ ret = -EAGAIN;
++ if (down_trylock(&s->sem))
++ break;
++ } else {
++ ret = -ERESTARTSYS;
++ if (down_interruptible(&s->sem))
++ break;
++ }
++
++ /* Grab data from current buffer */
++ chunksize = s->fragsize - b->offset;
++ if (chunksize > count)
++ chunksize = count;
++ if (copy_to_user(buffer, b->data + b->offset, chunksize)) {
++ up(&s->sem);
++ return -EFAULT;
++ }
++ b->offset += chunksize;
++ buffer += chunksize;
++ count -= chunksize;
++ if (b->offset < s->fragsize) {
++ ret = 0;
++ up(&s->sem);
++ break;
++ }
++
++ /*
++ * Make this buffer available for DMA again.
++ * We unlock this fragment's checkpoint descriptor and
++ * kick DMA if it is idle. Using checkpoint descriptors
++ * allows for control operations without the need for
++ * stopping the DMA channel if it is already running.
++ */
++ b->offset = 0;
++ b->dma_desc->ddadr &= ~DDADR_STOP;
++
++ /* move the index to the next fragment */
++ if (++s->usr_frag >= s->nbfrags)
++ s->usr_frag = 0;
++ }
++
++ if ((buffer - buffer0))
++ ret = buffer - buffer0;
++ return ret;
++}
++
++
++static int audio_sync(struct file *file)
++{
++ audio_state_t *state = file->private_data;
++ audio_stream_t *s = state->output_stream;
++ audio_buf_t *b;
++ pxa_dma_desc *final_desc;
++ u_long dcmd_save = 0;
++ DECLARE_WAITQUEUE(wait, current);
++
++ if (!(file->f_mode & FMODE_WRITE) || !s->buffers || s->mapped)
++ return 0;
++
++ /*
++ * Send current buffer if it contains data. Be sure to send
++ * a full sample count.
++ */
++ final_desc = NULL;
++ b = &s->buffers[s->usr_frag];
++ if (b->offset &= ~3) {
++ final_desc = &b->dma_desc[1 + b->offset/MAX_DMA_SIZE];
++ b->offset &= (MAX_DMA_SIZE-1);
++ dcmd_save = final_desc->dcmd;
++ final_desc->dcmd = b->offset | s->dcmd | DCMD_ENDIRQEN;
++ final_desc->ddadr |= DDADR_STOP;
++ b->offset = 0;
++ b->dma_desc->ddadr &= ~DDADR_STOP;
++ if (DCSR(s->dma_ch) & DCSR_STOPSTATE) {
++ DDADR(s->dma_ch) = b->dma_desc->ddadr;
++ DCSR(s->dma_ch) = DCSR_RUN;
++ }
++ }
++
++ /* Wait for DMA to complete. */
++ set_current_state(TASK_INTERRUPTIBLE);
++#if 0
++ /*
++ * The STOPSTATE IRQ never seem to occur if DCSR_STOPIRQEN is set
++ * along wotj DCSR_RUN. Silicon bug?
++ */
++ add_wait_queue(&s->stop_wq, &wait);
++ DCSR(s->dma_ch) |= DCSR_STOPIRQEN;
++ schedule();
++#else
++ add_wait_queue(&s->frag_wq, &wait);
++ while ((DCSR(s->dma_ch) & DCSR_RUN) && !signal_pending(current)) {
++ schedule();
++ set_current_state(TASK_INTERRUPTIBLE);
++ }
++#endif
++ set_current_state(TASK_RUNNING);
++ remove_wait_queue(&s->frag_wq, &wait);
++
++ /* Restore the descriptor chain. */
++ if (final_desc) {
++ final_desc->dcmd = dcmd_save;
++ final_desc->ddadr &= ~DDADR_STOP;
++ b->dma_desc->ddadr |= DDADR_STOP;
++ }
++ return 0;
++}
++
++
++static unsigned int audio_poll(struct file *file,
++ struct poll_table_struct *wait)
++{
++ audio_state_t *state = file->private_data;
++ audio_stream_t *is = state->input_stream;
++ audio_stream_t *os = state->output_stream;
++ unsigned int mask = 0;
++
++ if (file->f_mode & FMODE_READ) {
++ /* Start audio input if not already active */
++ if (!is->buffers && audio_setup_buf(is))
++ return -ENOMEM;
++ if (DCSR(is->dma_ch) & DCSR_STOPSTATE) {
++ DDADR(is->dma_ch) =
++ is->buffers[is->dma_frag].dma_desc->ddadr;
++ DCSR(is->dma_ch) = DCSR_RUN;
++ }
++ poll_wait(file, &is->frag_wq, wait);
++ }
++
++ if (file->f_mode & FMODE_WRITE) {
++ if (!os->buffers && audio_setup_buf(os))
++ return -ENOMEM;
++ poll_wait(file, &os->frag_wq, wait);
++ }
++
++ if (file->f_mode & FMODE_READ)
++ if (( is->mapped && is->bytecount > 0) ||
++ (!is->mapped && atomic_read(&is->sem.count) > 0))
++ mask |= POLLIN | POLLRDNORM;
++
++ if (file->f_mode & FMODE_WRITE)
++ if (( os->mapped && os->bytecount > 0) ||
++ (!os->mapped && atomic_read(&os->sem.count) > 0))
++ mask |= POLLOUT | POLLWRNORM;
++
++ return mask;
++}
++
++
++static int audio_ioctl( struct inode *inode, struct file *file,
++ uint cmd, ulong arg)
++{
++ audio_state_t *state = file->private_data;
++ audio_stream_t *os = state->output_stream;
++ audio_stream_t *is = state->input_stream;
++ long val;
++
++ switch (cmd) {
++ case OSS_GETVERSION:
++ return put_user(SOUND_VERSION, (int *)arg);
++
++ case SNDCTL_DSP_GETBLKSIZE:
++ if (file->f_mode & FMODE_WRITE)
++ return put_user(os->fragsize, (int *)arg);
++ else
++ return put_user(is->fragsize, (int *)arg);
++
++ case SNDCTL_DSP_GETCAPS:
++ val = DSP_CAP_REALTIME|DSP_CAP_TRIGGER|DSP_CAP_MMAP;
++ if (is && os)
++ val |= DSP_CAP_DUPLEX;
++ return put_user(val, (int *)arg);
++
++ case SNDCTL_DSP_SETFRAGMENT:
++ if (get_user(val, (long *) arg))
++ return -EFAULT;
++ if (file->f_mode & FMODE_READ) {
++ int ret = audio_set_fragments(is, val);
++ if (ret < 0)
++ return ret;
++ ret = put_user(ret, (int *)arg);
++ if (ret)
++ return ret;
++ }
++ if (file->f_mode & FMODE_WRITE) {
++ int ret = audio_set_fragments(os, val);
++ if (ret < 0)
++ return ret;
++ ret = put_user(ret, (int *)arg);
++ if (ret)
++ return ret;
++ }
++ return 0;
++
++ case SNDCTL_DSP_SYNC:
++ return audio_sync(file);
++
++ case SNDCTL_DSP_SETDUPLEX:
++ return 0;
++
++ case SNDCTL_DSP_POST:
++ return 0;
++
++ case SNDCTL_DSP_GETTRIGGER:
++ val = 0;
++ if (file->f_mode & FMODE_READ && DCSR(is->dma_ch) & DCSR_RUN)
++ val |= PCM_ENABLE_INPUT;
++ if (file->f_mode & FMODE_WRITE && DCSR(os->dma_ch) & DCSR_RUN)
++ val |= PCM_ENABLE_OUTPUT;
++ return put_user(val, (int *)arg);
++
++ case SNDCTL_DSP_SETTRIGGER:
++ if (get_user(val, (int *)arg))
++ return -EFAULT;
++ if (file->f_mode & FMODE_READ) {
++ if (val & PCM_ENABLE_INPUT) {
++ if (!is->buffers && audio_setup_buf(is))
++ return -ENOMEM;
++ if (!(DCSR(is->dma_ch) & DCSR_RUN)) {
++ audio_buf_t *b = &is->buffers[is->dma_frag];
++ DDADR(is->dma_ch) = b->dma_desc->ddadr;
++ DCSR(is->dma_ch) = DCSR_RUN;
++ }
++ } else {
++ DCSR(is->dma_ch) = 0;
++ }
++ }
++ if (file->f_mode & FMODE_WRITE) {
++ if (val & PCM_ENABLE_OUTPUT) {
++ if (!os->buffers && audio_setup_buf(os))
++ return -ENOMEM;
++ if (!(DCSR(os->dma_ch) & DCSR_RUN)) {
++ audio_buf_t *b = &os->buffers[os->dma_frag];
++ DDADR(os->dma_ch) = b->dma_desc->ddadr;
++ DCSR(os->dma_ch) = DCSR_RUN;
++ }
++ } else {
++ DCSR(os->dma_ch) = 0;
++ }
++ }
++ return 0;
++
++ case SNDCTL_DSP_GETOSPACE:
++ case SNDCTL_DSP_GETISPACE:
++ {
++ audio_buf_info inf = { 0, };
++ audio_stream_t *s = (cmd == SNDCTL_DSP_GETOSPACE) ? os : is;
++
++ if ((s == is && !(file->f_mode & FMODE_READ)) ||
++ (s == os && !(file->f_mode & FMODE_WRITE)))
++ return -EINVAL;
++ if (!s->buffers && audio_setup_buf(s))
++ return -ENOMEM;
++ inf.bytes = atomic_read(&s->sem.count) * s->fragsize;
++ inf.bytes -= s->buffers[s->usr_frag].offset;
++ inf.fragments = inf.bytes / s->fragsize;
++ inf.fragsize = s->fragsize;
++ inf.fragstotal = s->nbfrags;
++ return copy_to_user((void *)arg, &inf, sizeof(inf));
++ }
++
++ case SNDCTL_DSP_GETOPTR:
++ case SNDCTL_DSP_GETIPTR:
++ {
++ count_info inf = { 0, };
++ audio_stream_t *s = (cmd == SNDCTL_DSP_GETOPTR) ? os : is;
++ dma_addr_t ptr;
++ int bytecount, offset;
++ unsigned long flags;
++
++ if ((s == is && !(file->f_mode & FMODE_READ)) ||
++ (s == os && !(file->f_mode & FMODE_WRITE)))
++ return -EINVAL;
++ local_irq_save(flags);
++ if (DCSR(s->dma_ch) & DCSR_RUN) {
++ audio_buf_t *b;
++ ptr = (s->output) ? DSADR(s->dma_ch) : DTADR(s->dma_ch);
++ b = &s->buffers[s->dma_frag];
++ offset = ptr - b->dma_desc->dsadr;
++ if (offset >= s->fragsize)
++ offset = s->fragsize - 4;
++ } else {
++ offset = 0;
++ }
++ inf.ptr = s->dma_frag * s->fragsize + offset;
++ bytecount = s->bytecount + offset;
++ s->bytecount = -offset;
++ inf.blocks = s->fragcount;
++ s->fragcount = 0;
++ local_irq_restore(flags);
++ if (bytecount < 0)
++ bytecount = 0;
++ inf.bytes = bytecount;
++ return copy_to_user((void *)arg, &inf, sizeof(inf));
++ }
++
++ case SNDCTL_DSP_NONBLOCK:
++ file->f_flags |= O_NONBLOCK;
++ return 0;
++
++ case SNDCTL_DSP_RESET:
++ if (file->f_mode & FMODE_WRITE)
++ audio_clear_buf(os);
++ if (file->f_mode & FMODE_READ)
++ audio_clear_buf(is);
++ return 0;
++
++ default:
++ return state->client_ioctl ?
++ state->client_ioctl(inode, file, cmd, arg) : -EINVAL;
++ }
++
++ return 0;
++}
++
++
++static int audio_mmap(struct file *file, struct vm_area_struct *vma)
++{
++ audio_state_t *state = file->private_data;
++ audio_stream_t *s;
++ unsigned long size, vma_addr;
++ int i, ret;
++
++ if (vma->vm_pgoff != 0)
++ return -EINVAL;
++
++ if (vma->vm_flags & VM_WRITE) {
++ if (!state->wr_ref)
++ return -EINVAL;;
++ s = state->output_stream;
++ } else if (vma->vm_flags & VM_READ) {
++ if (!state->rd_ref)
++ return -EINVAL;
++ s = state->input_stream;
++ } else return -EINVAL;
++
++ if (s->mapped)
++ return -EINVAL;
++ size = vma->vm_end - vma->vm_start;
++ if (size != s->fragsize * s->nbfrags)
++ return -EINVAL;
++ if (!s->buffers && audio_setup_buf(s))
++ return -ENOMEM;
++ vma_addr = vma->vm_start;
++ for (i = 0; i < s->nbfrags; i++) {
++ audio_buf_t *buf = &s->buffers[i];
++ if (!buf->master)
++ continue;
++ ret = remap_page_range(vma, vma->vm_start, buf->dma_desc->dsadr,
++ buf->master, vma->vm_page_prot);
++ if (ret)
++ return ret;
++ vma_addr += buf->master;
++ }
++ for (i = 0; i < s->nbfrags; i++)
++ s->buffers[i].dma_desc->ddadr &= ~DDADR_STOP;
++ s->mapped = 1;
++ return 0;
++}
++
++
++static int audio_release(struct inode *inode, struct file *file)
++{
++ audio_state_t *state = file->private_data;
++
++ down(&state->sem);
++
++ if (file->f_mode & FMODE_READ) {
++ audio_clear_buf(state->input_stream);
++ *state->input_stream->drcmr = 0;
++ pxa_free_dma(state->input_stream->dma_ch);
++ state->rd_ref = 0;
++ }
++
++ if (file->f_mode & FMODE_WRITE) {
++ audio_sync(file);
++ audio_clear_buf(state->output_stream);
++ *state->output_stream->drcmr = 0;
++ pxa_free_dma(state->output_stream->dma_ch);
++ state->wr_ref = 0;
++ }
++
++ up(&state->sem);
++ return 0;
++}
++
++
++int pxa_audio_attach(struct inode *inode, struct file *file,
++ audio_state_t *state)
++{
++ audio_stream_t *is = state->input_stream;
++ audio_stream_t *os = state->output_stream;
++ int err;
++
++ down(&state->sem);
++
++ /* access control */
++ err = -ENODEV;
++ if ((file->f_mode & FMODE_WRITE) && !os)
++ goto out;
++ if ((file->f_mode & FMODE_READ) && !is)
++ goto out;
++ err = -EBUSY;
++ if ((file->f_mode & FMODE_WRITE) && state->wr_ref)
++ goto out;
++ if ((file->f_mode & FMODE_READ) && state->rd_ref)
++ goto out;
++
++ /* request DMA channels */
++ if (file->f_mode & FMODE_WRITE) {
++ err = pxa_request_dma(os->name, DMA_PRIO_LOW,
++ audio_dma_irq, os);
++ if (err < 0)
++ goto out;
++ os->dma_ch = err;
++ }
++ if (file->f_mode & FMODE_READ) {
++ err = pxa_request_dma(is->name, DMA_PRIO_LOW,
++ audio_dma_irq, is);
++ if (err < 0) {
++ if (file->f_mode & FMODE_WRITE) {
++ *os->drcmr = 0;
++ pxa_free_dma(os->dma_ch);
++ }
++ goto out;
++ }
++ is->dma_ch = err;
++ }
++
++ file->private_data = state;
++ file->f_op->release = audio_release;
++ file->f_op->write = audio_write;
++ file->f_op->read = audio_read;
++ file->f_op->mmap = audio_mmap;
++ file->f_op->poll = audio_poll;
++ file->f_op->ioctl = audio_ioctl;
++ file->f_op->llseek = no_llseek;
++
++ if ((file->f_mode & FMODE_WRITE)) {
++ state->wr_ref = 1;
++ os->fragsize = AUDIO_FRAGSIZE_DEFAULT;
++ os->nbfrags = AUDIO_NBFRAGS_DEFAULT;
++ os->output = 1;
++ os->mapped = 0;
++ init_waitqueue_head(&os->frag_wq);
++ init_waitqueue_head(&os->stop_wq);
++ *os->drcmr = os->dma_ch | DRCMR_MAPVLD;
++ }
++ if (file->f_mode & FMODE_READ) {
++ state->rd_ref = 1;
++ is->fragsize = AUDIO_FRAGSIZE_DEFAULT;
++ is->nbfrags = AUDIO_NBFRAGS_DEFAULT;
++ is->output = 0;
++ is->mapped = 0;
++ init_waitqueue_head(&is->frag_wq);
++ init_waitqueue_head(&is->stop_wq);
++ *is->drcmr = is->dma_ch | DRCMR_MAPVLD;
++ }
++
++ err = 0;
++
++out:
++ up(&state->sem);
++ return err;
++}
++
++EXPORT_SYMBOL(pxa_audio_attach);
++EXPORT_SYMBOL(pxa_audio_clear_buf);
++
++MODULE_AUTHOR("Nicolas Pitre, MontaVista Software Inc.");
++MODULE_DESCRIPTION("audio interface for the Cotula chip");
++MODULE_LICENSE("GPL");
+Index: linux-2.6.15gum/sound/oss/pxa-audio.h
+===================================================================
+--- /dev/null
++++ linux-2.6.15gum/sound/oss/pxa-audio.h
+@@ -0,0 +1,54 @@
++/*
++ * linux/drivers/sound/pxa-audio.h -- audio interface for the Cotula chip
++ *
++ * Author: Nicolas Pitre
++ * Created: Aug 15, 2001
++ * Copyright: MontaVista Software Inc.
++ *
++ * This program is free software; you can redistribute it and/or modify
++ * it under the terms of the GNU General Public License version 2 as
++ * published by the Free Software Foundation.
++ */
++
++typedef struct {
++ int offset; /* current buffer position */
++ char *data; /* actual buffer */
++ pxa_dma_desc *dma_desc; /* pointer to the starting desc */
++ int master; /* owner for buffer allocation, contain size whn true */
++} audio_buf_t;
++
++typedef struct {
++ char *name; /* stream identifier */
++ audio_buf_t *buffers; /* pointer to audio buffer array */
++ u_int usr_frag; /* user fragment index */
++ u_int dma_frag; /* DMA fragment index */
++ u_int fragsize; /* fragment size */
++ u_int nbfrags; /* number of fragments */
++ u_int dma_ch; /* DMA channel number */
++ dma_addr_t dma_desc_phys; /* phys addr of descriptor ring */
++ u_int descs_per_frag; /* nbr descriptors per fragment */
++ int bytecount; /* nbr of processed bytes */
++ int fragcount; /* nbr of fragment transitions */
++ struct semaphore sem; /* account for fragment usage */
++ wait_queue_head_t frag_wq; /* for poll(), etc. */
++ wait_queue_head_t stop_wq; /* for users of DCSR_STOPIRQEN */
++ u_long dcmd; /* DMA descriptor dcmd field */
++ volatile u32 *drcmr; /* the DMA request channel to use */
++ u_long dev_addr; /* device physical address for DMA */
++ int mapped:1; /* mmap()'ed buffers */
++ int output:1; /* 0 for input, 1 for output */
++} audio_stream_t;
++
++typedef struct {
++ audio_stream_t *output_stream;
++ audio_stream_t *input_stream;
++ int dev_dsp; /* audio device handle */
++ int rd_ref:1; /* open reference for recording */
++ int wr_ref:1; /* open reference for playback */
++ int (*client_ioctl)(struct inode *, struct file *, uint, ulong);
++ struct semaphore sem; /* prevent races in attach/release */
++} audio_state_t;
++
++extern int pxa_audio_attach(struct inode *inode, struct file *file,
++ audio_state_t *state);
++extern void pxa_audio_clear_buf(audio_stream_t *s);