[alsa-devel] [PATCH 1/2] Xlinx ML403 AC97 Controller Reference device driver
From: Joachim Foerster JOFT@gmx.de
Add ALSA support for the opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403 reference design.
Known issue: Currently this driver hits a WARN_ON_ONCE(1) statement in kernel/irq/resend.c (line 70). According to Linus (http://lkml.org/lkml/2007/8/5/5) this may be ignored, right? I haven't had a look into this "problem" yet.
(Patch for Linus' master branch, date 2007/08/08)
This patchset _will_ be published on http://www.esic-solutions.com/support.html soon (like the first version of the driver (tar file), but this may take some days ...).
Signed-off-by: Joachim Foerster JOFT@gmx.de --- sound/ppc/Kconfig | 13 + sound/ppc/Makefile | 2 + sound/ppc/ml403_ac97cr.c | 1274 +++++++++++++++++++++++++++++++++++++++++++++ sound/ppc/pcm-indirect2.h | 658 +++++++++++++++++++++++ 4 files changed, 1947 insertions(+), 0 deletions(-) create mode 100644 sound/ppc/ml403_ac97cr.c create mode 100644 sound/ppc/pcm-indirect2.h
diff --git a/sound/ppc/Kconfig b/sound/ppc/Kconfig index cacb0b1..cd492bf 100644 --- a/sound/ppc/Kconfig +++ b/sound/ppc/Kconfig @@ -52,4 +52,17 @@ config SND_PS3_DEFAULT_START_DELAY int "Startup delay time in ms" depends on SND_PS3 default "2000" + +config SND_ML403_AC97CR + tristate "Xilinx ML403 AC97 Controller Reference" + depends on SND && XILINX_VIRTEX + select SND_AC97_CODEC + help + Say Y here to include support for the + opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403 + reference design. + + To compile this driver as a module, choose M here: the module + will be called snd-ml403_ac97cr. + endmenu diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index eacee2d..827f2f5 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile @@ -4,7 +4,9 @@ #
snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o +snd-ml403_ac97cr-objs := ml403_ac97cr.o
# Toplevel Module Dependency obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o obj-$(CONFIG_SND_PS3) += snd_ps3.o +obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403_ac97cr.o diff --git a/sound/ppc/ml403_ac97cr.c b/sound/ppc/ml403_ac97cr.c new file mode 100644 index 0000000..99791d7 --- /dev/null +++ b/sound/ppc/ml403_ac97cr.c @@ -0,0 +1,1274 @@ + +/* ALSA driver for Xilinx ML403 AC97 Controller Reference + * IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i) + * IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i) + * + * Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/moduleparam.h> + +#include <linux/platform_device.h> + +#include <linux/ioport.h> +#include <asm/io.h> +#include <linux/interrupt.h> + +/* HZ */ +#include <linux/param.h> +/* jiffies, time_*() */ +#include <linux/jiffies.h> +/* schedule_timeout*() */ +#include <linux/sched.h> +/* spin_lock*() */ +#include <linux/spinlock.h> + +/* snd_printk(), snd_printd() */ +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h> + + +#define SND_ML403_AC97CR_DRIVER "ml403_ac97cr" + +MODULE_AUTHOR("Joachim Foerster JOFT@gmx.de"); +MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}"); +MODULE_VERSION("0.0.1-pre2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ML403 AC97 Controller Reference."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ML403 AC97 Controller Reference."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this ML403 AC97 Controller Reference."); + +/* Special feature options */ +/*#define CODEC_WRITE_CHECK_RAF*/ /* don't return after a write to a codec + * register, while RAF bit is not set + */ +/* Debug options for code which may be removed completely in a final version */ +#ifdef CONFIG_SND_DEBUG +/*#define CODEC_STAT*/ /* turn on some minimal "statistics" + * about codec register usage + */ +#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the + * process of copying bytes from the + * intermediate buffer to the hardware + * fifo and the other way round + */ +#endif + +#include "pcm-indirect2.h" + +/* Definition of a "level/facility dependent" printk(); may be removed + * completely in a final version + */ +#undef PDEBUG +#ifdef CONFIG_SND_DEBUG +/* "facilities" for PDEBUG */ +#define UNKNOWN (1<<0) +#define CODEC_SUCCESS (1<<1) +#define CODEC_FAKE (1<<2) +#define INIT_INFO (1<<3) +#define INIT_FAILURE (1<<4) +#define WORK_INFO (1<<5) +#define WORK_FAILURE (1<<6) + +#define PDEBUG_FACILITIES (UNKNOWN | INIT_FAILURE | WORK_FAILURE) + +#define PDEBUG(fac, fmt, args...) if (fac & PDEBUG_FACILITIES) \ + snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " fmt, ##args) +#else +#define PDEBUG(fac, fmt, args...) /* nothing */ +#endif + + + +/* Defines for "waits"/timeouts (portions of HZ=250 on arch/ppc by default) */ +#define CODEC_TIMEOUT_ON_INIT 5 /* timeout for checking for codec + * readiness (after insmod) + */ +#ifndef CODEC_WRITE_CHECK_RAF +#define CODEC_WAIT_AFTER_WRITE 100 /* general, static wait after a write + * access to a codec register, may be + * 0 to completely remove wait + */ +#else +#define CODEC_TIMEOUT_AFTER_WRITE 5 /* timeout after a write access to a + * codec register, if RAF bit is used + */ +#endif +#define CODEC_TIMEOUT_AFTER_READ 5 /* timeout after a read access to a + * codec register (checking RAF bit) + */ + +/* Infrastructure for codec register shadowing */ +#define LM4550_REG_DONEREAD (1<<0) /* read register once, value should be the + * same currently in the register + */ +#define LM4550_REG_NOSAVE (1<<1) /* values written to this register will + * not be saved in the register + */ +#define LM4550_REG_NOSHADOW (1<<2) /* don't do register shadowing, use plain + * hardware access + */ +#define LM4550_REG_READONLY (1<<3) /* register is read only */ +#define LM4550_REG_FAKEPROBE (1<<4) /* fake write _and_ read actions during + * probe() correctly + */ +#define LM4550_REG_FAKEREAD (1<<5) /* fake read access, always return default + * value + */ +#define LM4550_REG_ALLFAKE (LM4550_REG_FAKEREAD | LM4550_REG_FAKEPROBE) + +struct lm4550_reg { + u16 reg; + u16 value; + u16 flag; + u16 wmask; + u16 def; +}; + +struct lm4550_reg lm4550_regfile[64] = { + {.reg = 0x00, + .flag = LM4550_REG_NOSAVE | LM4550_REG_FAKEREAD, + .def = 0x0D50}, + {.reg = 0x02, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + {.reg = 0x04, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + {.reg = 0x06, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8000}, + {}, + {.reg = 0x0A, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x801E, + .def = 0x0}, + {.reg = 0x0C, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8008}, + {.reg = 0x0E, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x805F, + .def = 0x8008}, + {.reg = 0x10, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + {.reg = 0x12, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + {.reg = 0x14, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + {.reg = 0x16, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + {.reg = 0x18, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8008}, + {.reg = 0x1A, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x707, + .def = 0x0}, + {.reg = 0x1C, + .flag = LM4550_REG_FAKEPROBE, + .wmask = 0x8F0F, + .def = 0x8000}, + {}, + {.reg = 0x20, + .flag = LM4550_REG_FAKEPROBE, + .def = 0x0, + .wmask = 0xA380}, + {.reg = 0x22, + .flag = LM4550_REG_FAKEREAD | LM4550_REG_READONLY, + .def = 0x0101}, + {}, + {.reg = 0x26, + .flag = LM4550_REG_NOSHADOW | LM4550_REG_NOSAVE, + .wmask = 0xFF00}, /* may not write ones to REF/ANL/DAC/ADC bits + * FIXME: Is this ok? + */ + {.reg = 0x28, + .flag = LM4550_REG_FAKEREAD | LM4550_REG_READONLY, + .def = 0x0201}, /* primary codec */ + {.reg = 0x2A, + .flag = LM4550_REG_NOSHADOW | LM4550_REG_NOSAVE, + .wmask = 0x1}, + {.reg = 0x2C, + .flag = LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + {}, {}, + {.reg = 0x32, + .flag = LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, + {.reg = 0x7C, + .flag = LM4550_REG_READONLY | LM4550_REG_FAKEREAD, + .def = 0x4E53}, + {.reg = 0x7E, + .flag = LM4550_REG_READONLY | LM4550_REG_FAKEREAD, + .def = 0x4350} +}; + +#define LM4550_RF_OK(reg) ((lm4550_regfile[reg / 2].reg != 0) \ + || (lm4550_regfile[reg / 2].flag != 0)) +#define LM4550_RF_FLAG(reg) lm4550_regfile[reg / 2].flag +#define LM4550_RF_VAL(reg) lm4550_regfile[reg / 2].value +#define LM4550_RF_DEF(reg) lm4550_regfile[reg / 2].def +#define LM4550_RF_WMASK(reg) lm4550_regfile[reg / 2].wmask + +static void lm4550_regfile_init(void) +{ + int i; + for (i = 0; i < 128; i = i + 2) + if (LM4550_RF_FLAG(i) & LM4550_REG_FAKEPROBE) + LM4550_RF_VAL(i) = LM4550_RF_DEF(i); +} + +static void lm4550_regfile_write_values_after_init(struct snd_ac97 *ac97) +{ + int i; + for (i = 0; i < 128; i = i + 2) + if ((LM4550_RF_FLAG(i) & LM4550_REG_FAKEPROBE) && + (LM4550_RF_VAL(i) != LM4550_RF_DEF(i))) { + PDEBUG(CODEC_FAKE, "lm4550_regfile_write_values_after_" + "init(): reg=0x%x value=0x%x / %d is different " + "from def=0x%x / %d\n", + i, LM4550_RF_VAL(i), LM4550_RF_VAL(i), + LM4550_RF_DEF(i), LM4550_RF_DEF(i)); + snd_ac97_write(ac97, i, LM4550_RF_VAL(i)); + LM4550_RF_FLAG(i) |= LM4550_REG_DONEREAD; + } +} + + +/* direct registers */ +#define CR_REG(ml403_ac97cr,x) ((ml403_ac97cr)->port + CR_REG_##x) + +#define CR_REG_PLAYFIFO 0x00 +#define CR_PLAYDATA(a) ((a) & 0xFFFF) + +#define CR_REG_RECFIFO 0x04 +#define CR_RECDATA(a) ((a) & 0xFFFF) + +#define CR_REG_STATUS 0x08 +#define CR_RECOVER (1<<7) +#define CR_PLAYUNDER (1<<6) +#define CR_CODECREADY (1<<5) +#define CR_RAF (1<<4) +#define CR_RECEMPTY (1<<3) +#define CR_RECFULL (1<<2) +#define CR_PLAYHALF (1<<1) +#define CR_PLAYFULL (1<<0) + +#define CR_REG_RESETFIFO 0x0C +#define CR_RECRESET (1<<1) +#define CR_PLAYRESET (1<<0) + +#define CR_REG_CODEC_ADDR 0x10 +/* UG082 says: + * #define CR_CODEC_ADDR(a) ((a) << 1) + * #define CR_CODEC_READ (1<<0) + * #define CR_CODEC_WRITE (0<<0) + */ +/* RefDesign example says: */ +#define CR_CODEC_ADDR(a) ((a) << 0) +#define CR_CODEC_READ (1<<7) +#define CR_CODEC_WRITE (0<<7) + +#define CR_REG_CODEC_DATAREAD 0x14 +#define CR_CODEC_DATAREAD(v) ((v) & 0xFFFF) + +#define CR_REG_CODEC_DATAWRITE 0x18 +#define CR_CODEC_DATAWRITE(v) ((v) & 0xFFFF) + +#define CR_FIFO_SIZE 32 + +struct snd_ml403_ac97cr { + spinlock_t reg_lock; + + int irq; /* for playback */ + int enable_irq; /* for playback */ + + int capture_irq; + int enable_capture_irq; + + struct resource *res_port; + void *port; + + struct snd_ac97 *ac97; + int ac97_fake; +#ifdef CODEC_STAT + int ac97_read; + int ac97_write; +#endif + + struct platform_device *pfdev; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_pcm_indirect2 ind_rec; /* for playback */ + struct snd_pcm_indirect2 capture_ind2_rec; +}; + +static struct snd_pcm_hardware snd_ml403_ac97cr_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static struct snd_pcm_hardware snd_ml403_ac97cr_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000 | + SNDRV_PCM_RATE_KNOT), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static size_t +snd_ml403_ac97cr_playback_ind2_zero(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), 0); + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_playback_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *src; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + src = substream->runtime->dma_area + rec->sw_data; + + spin_lock(&ml403_ac97cr->reg_lock); + while (((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) && (bytes > 1)) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), + CR_PLAYDATA(src[copied_words])); + copied_words++; + bytes = bytes - 2; + } + if (full != CR_PLAYFULL) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_null(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) { + volatile u32 trash; + + trash = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, RECFIFO))); + /* Hmmmm, really necessary? */ + ++trash; + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *dst; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + dst = substream->runtime->dma_area + rec->sw_data; + + spin_lock(&ml403_ac97cr->reg_lock); + while (((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) && (bytes > 1)) { + dst[copied_words] = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, + RECFIFO))); + copied_words++; + bytes = bytes - 2; + } + if (empty != CR_RECEMPTY) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static snd_pcm_uframes_t +snd_ml403_ac97cr_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_indirect2 *ind2_rec = NULL; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + if (substream == ml403_ac97cr->playback_substream) + ind2_rec = &ml403_ac97cr->ind_rec; + if (substream == ml403_ac97cr->capture_substream) + ind2_rec = &ml403_ac97cr->capture_ind2_rec; + + if (ind2_rec != NULL) + return snd_pcm_indirect2_pointer(substream, ind2_rec); + return (snd_pcm_uframes_t) 0; +} + +static int +snd_ml403_ac97cr_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream == ml403_ac97cr->playback_substream) { + PDEBUG(WORK_INFO, "trigger(playback): START\n"); + ml403_ac97cr->ind_rec.hw_ready = 1; + + /* clear play FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET); + + /* enable play irq */ + ml403_ac97cr->enable_irq = 1; + enable_irq(ml403_ac97cr->irq); + } + if (substream == ml403_ac97cr->capture_substream) { + PDEBUG(WORK_INFO, "trigger(capture): START\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; + + /* clear record FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_RECRESET); + + /* enable record irq */ + ml403_ac97cr->enable_capture_irq = 1; + enable_irq(ml403_ac97cr->capture_irq); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + if (substream == ml403_ac97cr->playback_substream) { + PDEBUG(WORK_INFO, "trigger(playback): STOP\n"); + ml403_ac97cr->ind_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, + &ml403_ac97cr->ind_rec); +#endif + /* disable play irq */ + disable_irq_nosync(ml403_ac97cr->irq); + ml403_ac97cr->enable_irq = 0; + } + if (substream == ml403_ac97cr->capture_substream) { + PDEBUG(WORK_INFO, "trigger(capture): STOP\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, + &ml403_ac97cr->capture_ind2_rec); +#endif + /* disable capture irq */ + disable_irq_nosync(ml403_ac97cr->capture_irq); + ml403_ac97cr->enable_capture_irq = 0; + } + break; + default: + err = -EINVAL; + break; + } + PDEBUG(WORK_INFO, "trigger(): (done)\n"); + return err; +} + +static int snd_ml403_ac97cr_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + if (substream == ml403_ac97cr->playback_substream) { + PDEBUG(WORK_INFO, + "prepare(): period_bytes=%d, minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_FRONT_DAC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->ind_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->ind_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->ind_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->ind_rec.min_periods = -1; + ml403_ac97cr->ind_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", + CR_FIFO_SIZE, ml403_ac97cr->ind_rec.sw_buffer_size, + ml403_ac97cr->ind_rec.min_multiple); + } + if (substream == ml403_ac97cr->capture_substream) { + PDEBUG(WORK_INFO, "prepare(capture): period_bytes=%d, " + "minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_LR_ADC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(capture): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->capture_ind2_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->capture_ind2_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->capture_ind2_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->capture_ind2_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(capture): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", CR_FIFO_SIZE, + ml403_ac97cr->capture_ind2_rec.sw_buffer_size, + ml403_ac97cr->capture_ind2_rec.min_multiple); + } + return 0; +} + +static int snd_ml403_ac97cr_hw_free(struct snd_pcm_substream *substream) +{ + PDEBUG(WORK_INFO, "hw_free()\n"); + return snd_pcm_lib_free_pages(substream); +} + +static int +snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired " + "period bytes=%d\n", + params_buffer_bytes(hw_params), params_period_bytes(hw_params)); + /* check period bytes, has to be multiple of CR_FIFO_SIZE / 2, don't + * know if ALSA takes multiples of period_bytes_min _only_ ...?! + */ + if (params_period_bytes(hw_params) % (CR_FIFO_SIZE / 2) != 0) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "hw_params(): period bytes (%d) are not a multiple " + "of %d bytes!\n", + params_period_bytes(hw_params), CR_FIFO_SIZE / 2); + return -EINVAL; + } + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_ml403_ac97cr_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(playback)\n"); + ml403_ac97cr->playback_substream = substream; + runtime->hw = snd_ml403_ac97cr_playback; + return 0; +} + +static int snd_ml403_ac97cr_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(capture)\n"); + ml403_ac97cr->capture_substream = substream; + runtime->hw = snd_ml403_ac97cr_capture; + return 0; +} + +static int snd_ml403_ac97cr_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(playback)\n"); + ml403_ac97cr->playback_substream = NULL; + return 0; +} + +static int snd_ml403_ac97cr_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(capture)\n"); + ml403_ac97cr->capture_substream = NULL; + return 0; +} + +struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = { + .open = snd_ml403_ac97cr_playback_open, + .close = snd_ml403_ac97cr_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_prepare, + .trigger = snd_ml403_ac97cr_pcm_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +static struct snd_pcm_ops snd_ml403_ac97cr_capture_ops = { + .open = snd_ml403_ac97cr_capture_open, + .close = snd_ml403_ac97cr_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_prepare, + .trigger = snd_ml403_ac97cr_pcm_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id, struct pt_regs *regs) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct platform_device *pfdev; + int cmp_irq; + + if ((ml403_ac97cr = (struct snd_ml403_ac97cr *)dev_id) == NULL) + return IRQ_NONE; + + pfdev = ml403_ac97cr->pfdev; + + /* playback interrupt */ + cmp_irq = platform_get_irq(pfdev, 0); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_irq) { + snd_pcm_indirect2_playback_interrupt( + ml403_ac97cr->playback_substream, + &ml403_ac97cr->ind_rec, + snd_ml403_ac97cr_playback_ind2_copy, + snd_ml403_ac97cr_playback_ind2_zero); + } else + goto __disable_irq; + } else { + /* record interrupt */ + cmp_irq = platform_get_irq(pfdev, 1); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_capture_irq) { + snd_pcm_indirect2_capture_interrupt( + ml403_ac97cr->capture_substream, + &ml403_ac97cr->capture_ind2_rec, + snd_ml403_ac97cr_capture_ind2_copy, + snd_ml403_ac97cr_capture_ind2_null); + } else { + goto __disable_irq; + } + } else { + return IRQ_NONE; + } + } + return IRQ_HANDLED; + + __disable_irq: + PDEBUG(INIT_INFO, "irq(): irq %d is meant to be disabled! So, now try " + "to disable it _really_!\n", irq); + disable_irq_nosync(irq); + return IRQ_HANDLED; +} + +static unsigned short +snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; +#ifdef CODEC_STAT + u32 stat, rafaccess = 0; +#endif + unsigned long end_time; + u16 value = 0; + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return 0; + } + /* check if we can fake/answer this access from our shadow register */ + if ((LM4550_RF_FLAG(reg) & + (LM4550_REG_DONEREAD | LM4550_REG_ALLFAKE)) && + !(LM4550_RF_FLAG(reg) & LM4550_REG_NOSHADOW)) { + if (LM4550_RF_FLAG(reg) & LM4550_REG_FAKEREAD) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d\n", + reg, LM4550_RF_DEF(reg), LM4550_RF_DEF(reg)); + return LM4550_RF_DEF(reg); + } else if ((LM4550_RF_FLAG(reg) & LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d (probe)\n", + reg, LM4550_RF_VAL(reg), LM4550_RF_VAL(reg)); + return LM4550_RF_VAL(reg); + } else { +#ifdef CODEC_STAT + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d) (cw=%d cr=%d)\n", + reg, LM4550_RF_VAL(reg), LM4550_RF_VAL(reg), + ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d)\n", + reg, LM4550_RF_VAL(reg), LM4550_RF_VAL(reg)); +#endif + return LM4550_RF_VAL(reg); + } + } + /* if we are here, we _have_ to access the codec really, no faking */ + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read++; +#endif + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_READ); + end_time = jiffies + (HZ / CODEC_TIMEOUT_AFTER_READ); + do { +#ifdef CODEC_STAT + rafaccess++; + if (((stat = in_be32(CR_REG(ml403_ac97cr, STATUS))) & + CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) reg=0x%x, " + "value=0x%x / %d (STATUS=0x%x)\n", + reg, value, value, stat); +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) " + "reg=0x%x, value=0x%x / %d\n", + reg, value, value); +#endif + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + return value; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + value = + CR_CODEC_DATAREAD(in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! " + "(reg=0x%x, last STATUS=0x%x, DATAREAD=0x%x / %d, %d) " + "(cw=%d, cr=%d)\n", + reg, stat, value, value, rafaccess, ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! (reg=0x%x, DATAREAD=0x%x / %d)\n", + reg, value, value); +#endif + /* BUG: This is PURE speculation! But after _most_ read timeouts the + * value in the register is ok! + */ + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + return value; +} + +static void +snd_ml403_ac97cr_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + +#ifdef CODEC_STAT + u32 stat, rafaccess = 0; +#endif +#ifdef CODEC_WRITE_CHECK_RAF + unsigned long end_time; +#endif + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return; + } + if (LM4550_RF_FLAG(reg) & LM4550_REG_READONLY) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to read only codec register 0x%x " + "ignored!\n", reg); + return; + } + if ((val & LM4550_RF_WMASK(reg)) != val) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to codec register 0x%x with bad value " + "0x%x / %d!\n", + reg, val, val); + val = val & LM4550_RF_WMASK(reg); + } + if (((LM4550_RF_FLAG(reg) & LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) && + !(LM4550_RF_FLAG(reg) & LM4550_REG_NOSHADOW)) { + PDEBUG(CODEC_FAKE, "codec_write(): faking write to reg=0x%x, " + "val=0x%x / %d\n", reg, val, val); + LM4550_RF_VAL(reg) = (val & LM4550_RF_WMASK(reg)); + return; + } + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + ml403_ac97cr->ac97_write++; +#endif + out_be32(CR_REG(ml403_ac97cr, CODEC_DATAWRITE), + CR_CODEC_DATAWRITE(val)); + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_WRITE); +#ifdef CODEC_WRITE_CHECK_RAF + /* check CR_CODEC_RAF bit to see if write access to register is done; + * loop until bit is set or timeout happens + */ + end_time = jiffies + HZ / CODEC_TIMEOUT_AFTER_WRITE; + do { +#ifdef CODEC_STAT + rafaccess++; + if (((stat = in_be32(CR_REG(ml403_ac97cr, STATUS))) & + CR_RAF) == CR_RAF) { +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) reg=0x%x, " + "value=%d / 0x%x\n", reg, val, val); + if (!(LM4550_RF_FLAG(reg) & LM4550_REG_NOSHADOW) && + !(LM4550_RF_FLAG(reg) & LM4550_REG_NOSAVE)) + lm4550_regfile[reg / 2].value = val; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + return; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write " + "(reg=0x%x, val=0x%x / %d, last STATUS=0x%x, %d) " + "(cw=%d, cr=%d)\n", + reg, val, val, stat, rafaccess, ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write (reg=0x%x, val=0x%x / %d)\n", + reg, val, val); +#endif +#else /* CODEC_WRITE_CHECK_RAF */ +#if CODEC_WAIT_AFTER_WRITE > 0 + /* officially, in AC97 spec there is no possibility for a AC97 + * controller to determine, if write access is done or not - so: How + * is Xilinx able to provide a RAF bit for write access? + * => very strange, thus just don't check RAF bit (compare with + * Xilinx's example app in EDK 8.1i) and wait + */ + schedule_timeout_uninterruptible(HZ / CODEC_WAIT_AFTER_WRITE); +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) reg=0x%x, value=%d / 0x%x " + "(no RAF check)\n", + reg, val, val); +#endif + spin_unlock(&ml403_ac97cr->reg_lock); + return; +} + +static int snd_ml403_ac97cr_chip_init(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + unsigned long end_time; + PDEBUG(INIT_INFO, "chip_init():\n"); + end_time = jiffies + HZ / CODEC_TIMEOUT_ON_INIT; + do { + if (in_be32(CR_REG(ml403_ac97cr, STATUS)) & CR_CODECREADY) { + /* clear both hardware FIFOs */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), + CR_RECRESET | CR_PLAYRESET); + PDEBUG(INIT_INFO, "chip_init(): (done)\n"); + return 0; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "timeout while waiting for codec, " + "not ready!\n"); + return -EBUSY; +} + +static int snd_ml403_ac97cr_free(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + PDEBUG(INIT_INFO, "free():\n"); + /* irq release */ + if (ml403_ac97cr->irq >= 0) + free_irq(ml403_ac97cr->irq, ml403_ac97cr); + if (ml403_ac97cr->capture_irq >= 0) + free_irq(ml403_ac97cr->capture_irq, ml403_ac97cr); + /* give back "port" */ + if (ml403_ac97cr->port != NULL) + iounmap(ml403_ac97cr->port); + kfree(ml403_ac97cr); + PDEBUG(INIT_INFO, "free(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_dev_free(struct snd_device *snddev) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = snddev->device_data; + PDEBUG(INIT_INFO, "dev_free():\n"); + return snd_ml403_ac97cr_free(ml403_ac97cr); +} + +static int __init +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev, + struct snd_ml403_ac97cr **rml403_ac97cr) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_ml403_ac97cr_dev_free, + }; + struct resource *resource; + int irq; + + *rml403_ac97cr = NULL; + ml403_ac97cr = kzalloc(sizeof(*ml403_ac97cr), GFP_KERNEL); + if (ml403_ac97cr == NULL) { + return -ENOMEM; + } + spin_lock_init(&ml403_ac97cr->reg_lock); + ml403_ac97cr->card = card; + ml403_ac97cr->pfdev = pfdev; + ml403_ac97cr->irq = -1; + ml403_ac97cr->enable_irq = 0; + ml403_ac97cr->capture_irq = -1; + ml403_ac97cr->enable_capture_irq = 0; + ml403_ac97cr->port = NULL; + ml403_ac97cr->res_port = NULL; + + PDEBUG(INIT_INFO, "Trying to reserve resources now ...\n"); + resource = platform_get_resource(pfdev, IORESOURCE_MEM, 0); + /* get "port" */ + ml403_ac97cr->port = ioremap_nocache(resource->start, + (resource->end) - + (resource->start) + 1); + if (ml403_ac97cr->port == NULL) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to remap memory region (%x to %x)\n", + resource->start, resource->end); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "remap controller memory region to " + "0x%x done\n", (unsigned int)ml403_ac97cr->port); + /* get irq */ + irq = platform_get_irq(pfdev, 0); + if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED, + pfdev->dev.bus_id, (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (playback) irq %d done\n", + ml403_ac97cr->irq); + irq = platform_get_irq(pfdev, 1); + if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED, + pfdev->dev.bus_id, (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->capture_irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (capture) irq %d done\n", + ml403_ac97cr->capture_irq); + + if ((err = snd_ml403_ac97cr_chip_init(ml403_ac97cr)) < 0) { + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml403_ac97cr, &ops); + if (err < 0) { + PDEBUG(INIT_FAILURE, "probe(): snd_device_new() failed!\n"); + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + snd_card_set_dev(card, &pfdev->dev); + + *rml403_ac97cr = ml403_ac97cr; + return 0; +} + +static void snd_ml403_ac97cr_mixer_free(struct snd_ac97 *ac97) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + PDEBUG(INIT_INFO, "mixer_free():\n"); + ml403_ac97cr->ac97 = NULL; + PDEBUG(INIT_INFO, "mixer_free(): (done)\n"); +} + +static int __init snd_ml403_ac97cr_mixer(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + int err; + static struct snd_ac97_bus_ops ops = { + .write = snd_ml403_ac97cr_codec_write, + .read = snd_ml403_ac97cr_codec_read, + }; + PDEBUG(INIT_INFO, "mixer():\n"); + if ((err = snd_ac97_bus(ml403_ac97cr->card, 0, &ops, NULL, &bus)) < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ml403_ac97cr->ac97_fake = 1; + lm4550_regfile_init(); +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read = 0; + ml403_ac97cr->ac97_write = 0; +#endif + ac97.private_data = ml403_ac97cr; + ac97.private_free = snd_ml403_ac97cr_mixer_free; + ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM | + AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(bus, &ac97, &ml403_ac97cr->ac97); + ml403_ac97cr->ac97_fake = 0; + lm4550_regfile_write_values_after_init(ml403_ac97cr->ac97); + PDEBUG(INIT_INFO, "mixer(): (done) snd_ac97_mixer()=%d\n", err); + return err; +} + +static int __init +snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device, + struct snd_pcm **rpcm) +{ + struct snd_pcm *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + if ((err = snd_pcm_new(ml403_ac97cr->card, "ML403AC97CR/1", device, 1, + 1, &pcm)) < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ml403_ac97cr_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_ml403_ac97cr_capture_ops); + pcm->private_data = ml403_ac97cr; + pcm->info_flags = 0; + strcpy(pcm->name, "ML403AC97CR DAC/ADC"); + ml403_ac97cr->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, + 128 * 1024); + if (rpcm) + *rpcm = pcm; + return 0; +} + +static int __init snd_ml403_ac97cr_probe(struct platform_device *pfdev) +{ + struct snd_card *card; + struct snd_ml403_ac97cr *ml403_ac97cr = NULL; + int err; + int dev = pfdev->id; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + if ((err = snd_ml403_ac97cr_create(card, pfdev, &ml403_ac97cr)) < 0) { + PDEBUG(INIT_FAILURE, "probe(): create failed!\n"); + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): create done\n"); + card->private_data = ml403_ac97cr; + if ((err = snd_ml403_ac97cr_mixer(ml403_ac97cr)) < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): mixer done\n"); + if ((err = snd_ml403_ac97cr_pcm(ml403_ac97cr, 0, NULL)) < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): PCM done\n"); + strcpy(card->driver, SND_ML403_AC97CR_DRIVER); + strcpy(card->shortname, "ML403 AC97 Controller Reference"); + sprintf(card->longname, "%s %s at 0x%lx, irq %i & %i, device %i", + card->shortname, card->driver, + (unsigned long)ml403_ac97cr->port, ml403_ac97cr->irq, + ml403_ac97cr->capture_irq, dev + 1); + + if ((err = snd_card_register(card)) < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(pfdev, card); + PDEBUG(INIT_INFO, "probe(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_remove(struct platform_device *pfdev) +{ + snd_card_free(platform_get_drvdata(pfdev)); + platform_set_drvdata(pfdev, NULL); + return 0; +} + +static struct platform_driver snd_ml403_ac97cr_driver = { + .probe = snd_ml403_ac97cr_probe, + .remove = snd_ml403_ac97cr_remove, + .driver = { + .name = SND_ML403_AC97CR_DRIVER, + }, +}; + +static int __init alsa_card_ml403_ac97cr_init(void) +{ + return platform_driver_register(&snd_ml403_ac97cr_driver); +} + +static void __exit alsa_card_ml403_ac97cr_exit(void) +{ + platform_driver_unregister(&snd_ml403_ac97cr_driver); +} + +module_init(alsa_card_ml403_ac97cr_init) +module_exit(alsa_card_ml403_ac97cr_exit) diff --git a/sound/ppc/pcm-indirect2.h b/sound/ppc/pcm-indirect2.h new file mode 100644 index 0000000..c7fe74f --- /dev/null +++ b/sound/ppc/pcm-indirect2.h @@ -0,0 +1,658 @@ +/* + * Helper functions for indirect PCM data transfer to a simple FIFO in + * hardware (small, no possibility to read "hardware io position", + * updating position done by interrupt, ...) + * + * Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de + * + * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by + * + * Copyright (c) by Takashi Iwai tiwai@suse.de + * Jaroslav Kysela perex@suse.cz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SOUND_PCM_INDIRECT2_H +#define __SOUND_PCM_INDIRECT2_H + +#include <sound/core.h> +#include <sound/pcm.h> + +/* jiffies */ +#include <linux/jiffies.h> + +struct snd_pcm_indirect2 { + unsigned int hw_buffer_size; /* Byte size of hardware buffer */ + int hw_ready; /* playback: 1 = hw fifo has room left, + * 0 = hw fifo is full + */ + unsigned int min_multiple; + int min_periods; /* counts number of min. periods until + * min_multiple is reached + */ + int min_period_count; /* counts bytes to count number of + * min. periods + */ + + unsigned int sw_buffer_size; /* Byte size of software buffer */ + + /* sw_data: position in intermediate buffer, where we will read (or + * write) from/to next time (to transfer data to/from HW) + */ + unsigned int sw_data; /* Offset to next dst (or src) in sw + * ring buffer + */ + /* easiest case (playback): + * sw_data is nearly the same as ~ runtime->control->appl_ptr, with the + * exception that sw_data is "behind" by the number if bytes ALSA wrote + * to the intermediate buffer last time. + * A call to ack() callback synchronizes both indirectly. + */ + + /* We have no real sw_io pointer here. Usually sw_io is pointing to the + * current playback/capture position _inside_ the hardware. Devices + * with plain FIFOs often have no possibility to publish this position. + * So we say: if sw_data is updated, that means bytes were copied to + * the hardware, we increase sw_io by that amount, because there have + * to be as much bytes which were played. So sw_io will stay behind + * sw_data all the time and has to converge to sw_data at the end of + * playback. + */ + unsigned int sw_io; /* Current software pointer in bytes */ + + /* sw_ready: number of bytes ALSA copied to the intermediate buffer, so + * it represents the number of bytes which wait for transfer to the HW + */ + int sw_ready; /* Bytes ready to be transferred to/from hw */ + + /* appl_ptr: last known position of ALSA (where ALSA is going to write + * next time into the intermediate buffer + */ + snd_pcm_uframes_t appl_ptr; /* Last seen appl_ptr */ + + unsigned int bytes2hw; + int check_alignment; + +#ifdef SND_PCM_INDIRECT2_STAT + unsigned int zeros2hw; + unsigned int mul_elapsed; + unsigned int mul_elapsed_real; + unsigned long firstbytetime; + unsigned long lastbytetime; + unsigned long firstzerotime; + unsigned int byte_sizes[64]; + unsigned int zero_sizes[64]; + unsigned int min_adds[8]; + unsigned int mul_adds[8]; + unsigned int zero_times[3750]; /* = 15s */ + unsigned int zero_times_saved; + unsigned int zero_times_notsaved; + unsigned int irq_occured; + unsigned int pointer_calls; + unsigned int lastdifftime; +#endif +}; + +typedef size_t(*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream * substream, + struct snd_pcm_indirect2 * rec, + size_t bytes); +typedef size_t(*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream * substream, + struct snd_pcm_indirect2 * rec); + +#ifdef SND_PCM_INDIRECT2_STAT +static inline void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int i, j, k; + int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ; + + snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, " + "irq_occured: %d\n", + rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured); + snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n", + rec->min_multiple); + snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, " + "firstzerotime: %lu\n", + rec->firstbytetime, rec->lastbytetime, rec->firstzerotime); + snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) " + "length: %d s\n", + rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate); + snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => " + "rate: %d Bytes/s = %d Frames/s|Hz\n", + seconds, rec->bytes2hw / seconds, + rec->bytes2hw / 2 / 2 / seconds); + snd_printk(KERN_DEBUG + "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n", + rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) / + runtime->rate, + rec->zeros2hw / (rec->hw_buffer_size / 2), + (rec->hw_buffer_size / 2)); + snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n", + rec->pointer_calls, rec->lastdifftime); + snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io, + rec->sw_data); + snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n"); + k = 0; + for (j = 0; j < 8; j++) { + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->byte_sizes[i] != 0) { + snd_printk(KERN_DEBUG "%u: %u", + i, rec->byte_sizes[i]); + k++; + } + if (((k % 8) == 0) && (k != 0)) { + snd_printk(KERN_DEBUG "\n"); + k = 0; + } + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n"); + for (j = 0; j < 8; j++) { + k = 0; + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->zero_sizes[i] != 0) + snd_printk(KERN_DEBUG "%u: %u", + i, rec->zero_sizes[i]); + else + k++; + if (!k) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: min_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->min_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->mul_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG + "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n", + rec->zero_times_saved, rec->zero_times_notsaved); + /* snd_printk(KERN_DEBUG "STAT: zero_times[]\n"); + i = 0; + for (j = 0; j < 3750; j++) { + if (rec->zero_times[j] != 0) { + snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]); + i++; + } + if (((i % 8) == 0) && (i != 0)) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); */ + return; +} +#endif + +/* + * _internal_ helper function for playback/capture transfer function + */ +static inline void +snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + int isplay, int iscopy, + unsigned int bytes) +{ + if (rec->min_periods >= 0) { + if (iscopy) { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= rec->sw_buffer_size; + } else if (isplay) { + /* If application does not write data in multiples of + * a period, move sw_data to the next correctly aligned + * position, so that sw_io can converge to it (in the + * next step). + */ + if (!rec->check_alignment) { + if (rec->bytes2hw % + snd_pcm_lib_period_bytes(substream)) { + unsigned bytes2hw_aligned = + (1 + + (rec->bytes2hw / + snd_pcm_lib_period_bytes + (substream))) * + snd_pcm_lib_period_bytes(substream); + rec->sw_data = + bytes2hw_aligned % + rec->sw_buffer_size; +#ifdef SND_PCM_INDIRECT2_STAT + snd_printk(KERN_DEBUG + "STAT: @re-align: aligned " + "bytes2hw to next period " + "size boundary: %d " + "(instead of %d)\n", + bytes2hw_aligned, + rec->bytes2hw); + snd_printk(KERN_DEBUG + "STAT: @re-align: sw_data " + "moves to: %d\n", + rec->sw_data); +#endif + } + rec->check_alignment = 1; + } + /* We are at the end and are copying zero into the fifo. + * Now, we have to make sure that sw_io is increased + * until the position of sw_data: Filling the fifo with + * the first zeros means, the last bytes were played. + */ + if (rec->sw_io != rec->sw_data) { + unsigned int diff; + if (rec->sw_data > rec->sw_io) + diff = rec->sw_data - rec->sw_io; + else + diff = + (rec->sw_buffer_size - rec->sw_io) + + rec->sw_data; + if (bytes >= diff) + rec->sw_io = rec->sw_data; + else { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= + rec->sw_buffer_size; + } + } + } + rec->min_period_count += bytes; + if (rec->min_period_count >= (rec->hw_buffer_size / 2)) { + rec->min_periods += + (rec->min_period_count / (rec->hw_buffer_size / 2)); +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_period_count / + (rec->hw_buffer_size / 2)) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) min_adds at " + "once - too big to save!\n", + (rec->min_period_count / + (rec->hw_buffer_size / 2))); + else + rec->min_adds[(rec->min_period_count / + (rec->hw_buffer_size / 2))]++; +#endif + rec->min_period_count = + (rec->min_period_count % (rec->hw_buffer_size / 2)); + } + } else if (isplay && iscopy) + rec->min_periods = 0; +} + +/* + * helper function for playback/capture pointer callback + */ +static inline snd_pcm_uframes_t +snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->pointer_calls++; +#endif + return bytes_to_frames(substream->runtime, rec->sw_io); +} + +/* + * helper function for playback ack callback + */ +static inline void +snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + + /* runtime->control->appl_ptr: position where ALSA will write next time + * rec->appl_ptr: position where ALSA was last time + * diff: obviously ALSA wrote that much bytes into the intermediate + * buffer since we checked last time + */ + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + /* number of bytes "added" by ALSA increases the number of bytes + * which are ready to "be transfered to HW"/"played" + * Then, set rec->appl_ptr to not count bytes twice next time. + */ + rec->sw_ready += (int)frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + if (rec->hw_ready && (rec->sw_ready <= 0)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: mul_elapsed: %d, " + "min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: sw_io: %d, " + "sw_data: %d, appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = zero(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d zero Bytes copied to hardware at " + "once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0, + bytes); + return; + } + while (rec->hw_ready && (rec->sw_ready > 0)) { + /* sw_to_end: max. number of bytes that can be read/take from + * the current position (sw_data) in _one_ step + */ + unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: number of bytes we have available (for reading) */ + unsigned int bytes = rec->sw_ready; + + if (sw_to_end < bytes) { + bytes = sw_to_end; + } + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from intermediate buffer position sw_data to the + * HW and return number of bytes actually written + * Furthermore, set hw_ready to 0, if the fifo isn't empty + * now => more could be transfered to fifo + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d Bytes copied to hardware at once " + "- too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually written bytes + * (= number of taken bytes from intermediate buffer) + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + /* now sw_data is the position where ALSA is going to write + * in the intermediate buffer next time = position we are going + * to read from next time + */ + + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1, + bytes); + + /* we read bytes from intermediate buffer, so we need to say + * that the number of bytes ready for transfer are decreased + * now + */ + rec->sw_ready -= bytes; + } + return; +} + +/* + * helper function for playback interrupt routine + */ +static inline void +snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware played some bytes, so there is room again (in fifo) */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero); + + if (rec->min_periods >= rec->min_multiple) { +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - too big " + "to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / rec->min_multiple); + rec->mul_elapsed++; +#endif + rec->min_periods = 0; + snd_pcm_period_elapsed(substream); + } +} + +/* + * helper function for capture ack callback + */ +static inline void +snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + rec->sw_ready -= frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + /* if hardware has something, but the intermediate buffer is full + * => skip contents of buffer + */ + if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG "STAT: (capture) @firstzerotime: " + "mul_elapsed: %d, min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG "STAT: (capture) @firstzerotime: " + "sw_io: %d, sw_data: %d, appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = null(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d zero Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0, + bytes); + /* report an overrun */ + rec->sw_io = SNDRV_PCM_POS_XRUN; + return; + } + while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) { + /* sw_to_end: max. number of bytes that we can write to the + * intermediate buffer (until it's end) + */ + size_t sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: max. number of bytes, which may be copied to the + * intermediate buffer without overflow (in _one_ step) + */ + size_t bytes = rec->sw_buffer_size - rec->sw_ready; + + /* limit number of bytes (for transfer) by available room in + * the intermediate buffer + */ + if (sw_to_end < bytes) + bytes = sw_to_end; + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from the intermediate buffer (position sw_data) + * to the HW at most and return number of bytes actually copied + * from HW + * Furthermore, set hw_ready to 0, if the fifo is empty now. + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually copied bytes from + * HW + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1, + bytes); + + /* number of bytes in the intermediate buffer, which haven't + * been fetched by ALSA yet. + */ + rec->sw_ready += bytes; + } + return; +} + +/* + * helper function for capture interrupt routine + */ +static inline void +snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware recorded some bytes, so there is something to read from the + * record fifo: + */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_capture_transfer(substream, rec, copy, null); + + if (rec->min_periods >= rec->min_multiple) { + +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - " + "too big to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / rec->min_multiple); + rec->mul_elapsed++; + + if (!(rec->mul_elapsed % 4)) { + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int appl_ptr = + frames_to_bytes(runtime, + (unsigned int)runtime->control-> + appl_ptr) % rec->sw_buffer_size; + int diff = rec->sw_data - appl_ptr; + if (diff < 0) + diff += rec->sw_buffer_size; + snd_printk(KERN_DEBUG + "STAT: mul_elapsed: %d, sw_data: %u, " + "appl_ptr (bytes): %u, diff: %d\n", + rec->mul_elapsed, rec->sw_data, appl_ptr, + diff); + } +#endif + rec->min_periods = 0; + snd_pcm_period_elapsed(substream); + } +} + +#endif /* __SOUND_PCM_INDIRECT2_H */
Hi,
thanks for the patch. Here the quick review at a first glance...
At Thu, 09 Aug 2007 12:36:50 +0200, Joachim Förster wrote:
--- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile @@ -4,7 +4,9 @@ #
snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o +snd-ml403_ac97cr-objs := ml403_ac97cr.o
# Toplevel Module Dependency obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o obj-$(CONFIG_SND_PS3) += snd_ps3.o +obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403_ac97cr.o
Using both _ and - look weird. Let's choose '-' as well as others.
--- /dev/null +++ b/sound/ppc/ml403_ac97cr.c
(snip)
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
Can the driver really support multiple instances? If not, better to avoid these arrays.
+#define PDEBUG(fac, fmt, args...) if (fac & PDEBUG_FACILITIES) \
snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " fmt, ##args)
This should be wrapped with do { } while (0)
+struct lm4550_reg lm4550_regfile[64] = {
- {.reg = 0x00,
.flag = LM4550_REG_NOSAVE | LM4550_REG_FAKEREAD,
.def = 0x0D50},
Better to use C99 style initialization here, e.g.
[0x00] = { .... }, [0x02] = { .... }, [0x7e] = { .... },
so you can avoid writing empty items. The index value could be also AC97_XXX, such as [AC97_MASTER] = {...}.
What is the purpose of reg field at all, BTW? I guess it's superfluous.
+#define LM4550_RF_OK(reg) ((lm4550_regfile[reg / 2].reg != 0) \
|| (lm4550_regfile[reg / 2].flag != 0))
+#define LM4550_RF_FLAG(reg) lm4550_regfile[reg / 2].flag +#define LM4550_RF_VAL(reg) lm4550_regfile[reg / 2].value +#define LM4550_RF_DEF(reg) lm4550_regfile[reg / 2].def +#define LM4550_RF_WMASK(reg) lm4550_regfile[reg / 2].wmask
+static void lm4550_regfile_init(void) +{
- int i;
- for (i = 0; i < 128; i = i + 2)
if (LM4550_RF_FLAG(i) & LM4550_REG_FAKEPROBE)
LM4550_RF_VAL(i) = LM4550_RF_DEF(i);
"MACRO(x) = XXX" looks a bit strange to me.
+static struct snd_pcm_hardware snd_ml403_ac97cr_playback = {
- .info = (SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID),
- .formats = SNDRV_PCM_FMTBIT_S16_BE,
- .rates = (SNDRV_PCM_RATE_CONTINUOUS |
SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_KNOT),
RATE_CONTINUOUS and RATE_KNOW are usually exclusive.
+static int +snd_ml403_ac97cr_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{
- struct snd_ml403_ac97cr *ml403_ac97cr;
- int err = 0;
- ml403_ac97cr = snd_pcm_substream_chip(substream);
- switch (cmd) {
- case SNDRV_PCM_TRIGGER_START:
if (substream == ml403_ac97cr->playback_substream) {
PDEBUG(WORK_INFO, "trigger(playback): START\n");
ml403_ac97cr->ind_rec.hw_ready = 1;
/* clear play FIFO */
out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET);
/* enable play irq */
ml403_ac97cr->enable_irq = 1;
enable_irq(ml403_ac97cr->irq);
}
Maybe better to create two functions, one for playback and one for capture, instead of many if's in a function?
+static int snd_ml403_ac97cr_pcm_prepare(struct snd_pcm_substream *substream) +{
- struct snd_ml403_ac97cr *ml403_ac97cr;
- struct snd_pcm_runtime *runtime;
- ml403_ac97cr = snd_pcm_substream_chip(substream);
- runtime = substream->runtime;
- if (substream == ml403_ac97cr->playback_substream) {
Ditto.
+static int +snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
+{
- PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired "
"period bytes=%d\n",
params_buffer_bytes(hw_params), params_period_bytes(hw_params));
- /* check period bytes, has to be multiple of CR_FIFO_SIZE / 2, don't
* know if ALSA takes multiples of period_bytes_min _only_ ...?!
*/
This should be done via an additional hw_constraint at open,
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, CR_FIFO_SIZE / 2);
+struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = {
Missing static.
+irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id, struct pt_regs *regs)
Hm, this looks like an old style irq handler. Did you test the driver with the recent kernel?
Also, missing static there.
+static unsigned short +snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg)
....
- /* if we are here, we _have_ to access the codec really, no faking */
- spin_lock(&ml403_ac97cr->reg_lock);
....
- do {
....
schedule_timeout_uninterruptible(1);
- } while (time_after(end_time, jiffies));
Sleep in spinlock is bad.
+static int __init +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev,
struct snd_ml403_ac97cr **rml403_ac97cr)
It's no longer __init as long as you use platform_device. It should be __devinit instead.
--- /dev/null +++ b/sound/ppc/pcm-indirect2.h
(snip)
+#ifdef SND_PCM_INDIRECT2_STAT +static inline void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
struct snd_pcm_indirect2 *rec)
Remove inline from the functions in this file. They are too lengthy.
sound/pcm-indirect.h contain inline functions becuase they are relatively small, and I didn't want to add them in the core module unconditionally.
Last but not least, the whole patch appears not following strictly the standard kernel coding style. For example, the style
if ((err = foo()) < 0) ....
should be
err = foo(); if (err < 0) ...
Also, many lines are over 80 chars. Fold them appropriately. At least the new patches should be like that.
In doubt, you can try $KERNEL/scripts/checkpatch.pl script, which was added recently to linux kernel tree.
Takashi
Hi Takashi,
before posting a corrected version, I would like to ask some unclear things (I think I understood the rest):
On Thu, 2007-08-09 at 19:13 +0200, Takashi Iwai wrote:
(snip)
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
Can the driver really support multiple instances? If not, better to avoid these arrays.
Well, while I wrote the driver I considered that there might be more than one instance - though I didn't test it and I won't be able to test it (no such hardware available - with more than one LM4550 chip). So, shall I remove it?
+struct lm4550_reg lm4550_regfile[64] = {
- {.reg = 0x00,
.flag = LM4550_REG_NOSAVE | LM4550_REG_FAKEREAD,
.def = 0x0D50},
Better to use C99 style initialization here, e.g.
[0x00] = { .... }, [0x02] = { .... }, [0x7e] = { .... },
so you can avoid writing empty items. The index value could be also AC97_XXX, such as [AC97_MASTER] = {...}.
What is the purpose of reg field at all, BTW? I guess it's superfluous.
No, it's there to provide a shadow copy of the codec's (LM4550) registers. It contains default values and (while driver is running) current values, which were written to the hardware. I had to introduce this, because Xilinx's AC97 Controller Reference has a very ugly bug: It provides a "register access finish" bit, so the driver is able to tell when a register read or write access is finished. Unfortunately this particular bit only works in the range of 0 to 100 times since board reset. After that many register access it just remains saying: I'm _not_ ready. But in fact, in most cases (98%) the correct value already is the read register (assume we have just said to the control that we want to read a register). I thought, ok we have such RegAccessFinished bit, so use it, if we have to, until it doesn't work anymore. So, through a shadow copy of most registers (some cannot be shadow or it makes no sence) I can provide the values without having to actually read from the controller/codec. The regfile also contains info which register might be shadowed, if values get saved at all (if written) ... Furthermore ALSA's AC97 layer does heavy initialization access series on the codec, which I tried to "mask out" completely (LM4550_REG_FAKEPROBE).
+#define LM4550_RF_FLAG(reg) lm4550_regfile[reg / 2].flag
<snip>
+static void lm4550_regfile_init(void) +{
- int i;
- for (i = 0; i < 128; i = i + 2)
if (LM4550_RF_FLAG(i) & LM4550_REG_FAKEPROBE)
LM4550_RF_VAL(i) = LM4550_RF_DEF(i);
"MACRO(x) = XXX" looks a bit strange to me.
Hmmm, ok. I thought about that, too. I think, I'll spell them out?
RATE_CONTINUOUS and RATE_KNOW are usually exclusive.
Ok, so what I want is RATE_CONTINUOUS, right? (because the LM4550 supports 4000 to 48000kHz in 1Hz steps) BTW: What is RATE_KNOT good for?
Joachim
At Thu, 09 Aug 2007 21:44:46 +0200, Joachim Förster wrote:
Hi Takashi,
before posting a corrected version, I would like to ask some unclear things (I think I understood the rest):
On Thu, 2007-08-09 at 19:13 +0200, Takashi Iwai wrote:
(snip)
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE;
Can the driver really support multiple instances? If not, better to avoid these arrays.
Well, while I wrote the driver I considered that there might be more than one instance - though I didn't test it and I won't be able to test it (no such hardware available - with more than one LM4550 chip). So, shall I remove it?
It's up to you. If it makes the code easier and more maintenable, it's worth to try.
+struct lm4550_reg lm4550_regfile[64] = {
- {.reg = 0x00,
.flag = LM4550_REG_NOSAVE | LM4550_REG_FAKEREAD,
.def = 0x0D50},
Better to use C99 style initialization here, e.g.
[0x00] = { .... }, [0x02] = { .... }, [0x7e] = { .... },
so you can avoid writing empty items. The index value could be also AC97_XXX, such as [AC97_MASTER] = {...}.
What is the purpose of reg field at all, BTW? I guess it's superfluous.
No, it's there to provide a shadow copy of the codec's (LM4550) registers.
That I understood too. My question was the reg _field_. It looks like an index but it can be even easily calculated from the pointer address...
It contains default values and (while driver is running) current values, which were written to the hardware. I had to introduce this, because Xilinx's AC97 Controller Reference has a very ugly bug: It provides a "register access finish" bit, so the driver is able to tell when a register read or write access is finished. Unfortunately this particular bit only works in the range of 0 to 100 times since board reset. After that many register access it just remains saying: I'm _not_ ready. But in fact, in most cases (98%) the correct value already is the read register (assume we have just said to the control that we want to read a register). I thought, ok we have such RegAccessFinished bit, so use it, if we have to, until it doesn't work anymore. So, through a shadow copy of most registers (some cannot be shadow or it makes no sence) I can provide the values without having to actually read from the controller/codec. The regfile also contains info which register might be shadowed, if values get saved at all (if written) ... Furthermore ALSA's AC97 layer does heavy initialization access series on the codec, which I tried to "mask out" completely (LM4550_REG_FAKEPROBE).
+#define LM4550_RF_FLAG(reg) lm4550_regfile[reg / 2].flag
<snip> > > +static void lm4550_regfile_init(void) > > +{ > > + int i; > > + for (i = 0; i < 128; i = i + 2) > > + if (LM4550_RF_FLAG(i) & LM4550_REG_FAKEPROBE) > > + LM4550_RF_VAL(i) = LM4550_RF_DEF(i); > > "MACRO(x) = XXX" looks a bit strange to me.
Hmmm, ok. I thought about that, too. I think, I'll spell them out?
RATE_CONTINUOUS and RATE_KNOW are usually exclusive.
Ok, so what I want is RATE_CONTINUOUS, right? (because the LM4550 supports 4000 to 48000kHz in 1Hz steps) BTW: What is RATE_KNOT good for?
It indicates unusual non-continuous sample rates that don't match with SNDRV_PCM_RATE_XXX bits are supported. The rates in 1Hz step is continuous enough :)
Takashi
Hi Takashi,
again some question came up (while correcting all the other issues):
On Thu, 2007-08-09 at 19:13 +0200, Takashi Iwai wrote:
+static int __init +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev,
struct snd_ml403_ac97cr **rml403_ac97cr)
It's no longer __init as long as you use platform_device. It should be __devinit instead.
Ok, I changed that (for create(), pcm(), mixer(), etc.). Do I have to change it for the module_init and module_exit functions, too? I guess, they are not in the "scope" of platform device, right? So, they will keep __init / __exit ?
--- /dev/null +++ b/sound/ppc/pcm-indirect2.h
(snip)
+#ifdef SND_PCM_INDIRECT2_STAT +static inline void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
struct snd_pcm_indirect2 *rec)
Remove inline from the functions in this file. They are too lengthy.
sound/pcm-indirect.h contain inline functions becuase they are relatively small, and I didn't want to add them in the core module unconditionally.
I looked at my functions again. And I think we could still go with inline for the *_interrupt() and *_pointer() functions since they have just a few lines. With both *_transfer() functions - I don't know. In fact they shouldn't be much longer than yours in pcm-indirect.h . They seem long, because there are lot of comments and the #ifdef SND_PCM_INDIRECT2_STAT stuff, which is/was a debugging feature, which _won't_ be compiled in without CONFIG_SND_DEBUG.
[I removed inline from the _stat() and _min_periods() function - that's true, they are too long.]
The other thing I thought about, is: Is it ok to have non-inline functions in a header file? Don't we need a .c file + .h ? (Are there any CodingStyle rules about that?)
Joachim
At Fri, 10 Aug 2007 13:50:40 +0200, Joachim Förster wrote:
Hi Takashi,
again some question came up (while correcting all the other issues):
On Thu, 2007-08-09 at 19:13 +0200, Takashi Iwai wrote:
+static int __init +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev,
struct snd_ml403_ac97cr **rml403_ac97cr)
It's no longer __init as long as you use platform_device. It should be __devinit instead.
Ok, I changed that (for create(), pcm(), mixer(), etc.). Do I have to change it for the module_init and module_exit functions, too? I guess, they are not in the "scope" of platform device, right? So, they will keep __init / __exit ?
Yes, they can be __init & __exit.
--- /dev/null +++ b/sound/ppc/pcm-indirect2.h
(snip)
+#ifdef SND_PCM_INDIRECT2_STAT +static inline void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream,
struct snd_pcm_indirect2 *rec)
Remove inline from the functions in this file. They are too lengthy.
sound/pcm-indirect.h contain inline functions becuase they are relatively small, and I didn't want to add them in the core module unconditionally.
I looked at my functions again. And I think we could still go with inline for the *_interrupt() and *_pointer() functions since they have just a few lines. With both *_transfer() functions - I don't know. In fact they shouldn't be much longer than yours in pcm-indirect.h . They seem long, because there are lot of comments and the #ifdef SND_PCM_INDIRECT2_STAT stuff, which is/was a debugging feature, which _won't_ be compiled in without CONFIG_SND_DEBUG.
IMO, the inline isn't usually necessary unless it hits really the performance. Let the compiler optimize the codes. The size reduction often wins in the end than inlining.
[I removed inline from the _stat() and _min_periods() function - that's true, they are too long.]
The other thing I thought about, is: Is it ok to have non-inline functions in a header file? Don't we need a .c file + .h ? (Are there any CodingStyle rules about that?)
Yeah, *.c sounds more appropriate.
Takashi
Hi Takashi, Grant, Stephen,
I tried to correct the issues you pointed out and below you'll find a new version of my patch. Maybe important changes: - I moved to sound/drivers, so there will be less problems as soon as somebody confirms that it works on MicroBlaze, too. - I removed the inline stuff from pcm-indirect2.h and split it into a .c file and a .h file. @Stephen: Wolfgang Reissnegger's changes don't exist in any "(un)official" branches, so I thought I stay with XILINX_VIRTEX - anyway, it's a very small change to make - so we might do this later. Or did I overlook something?
From: Joachim Foerster JOFT@gmx.de
Add ALSA support for the opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403 reference design.
Known issue: Currently this driver hits a WARN_ON_ONCE(1) statement in kernel/irq/resend.c (line 70). According to Linus (http://lkml.org/lkml/2007/8/5/5) this may be ignored, right? I haven't had a look into this "problem" yet.
(Patch for Linus' master branch, date 2007/08/08)
This patchset _will_ be published on http://www.esic-solutions.com/support.html soon (like the first version of the driver (tar file), but this may take some time ...).
Signed-off-by: Joachim Foerster JOFT@gmx.de --- sound/drivers/Kconfig | 12 + sound/drivers/Makefile | 2 + sound/drivers/ml403-ac97cr.c | 1353 +++++++++++++++++++++++++++++++++++++++++ sound/drivers/pcm-indirect2.c | 591 ++++++++++++++++++ sound/drivers/pcm-indirect2.h | 140 +++++ 5 files changed, 2098 insertions(+), 0 deletions(-) create mode 100644 sound/drivers/ml403-ac97cr.c create mode 100644 sound/drivers/pcm-indirect2.c create mode 100644 sound/drivers/pcm-indirect2.h
diff --git a/sound/drivers/Kconfig b/sound/drivers/Kconfig index 83529b0..6716adf 100644 --- a/sound/drivers/Kconfig +++ b/sound/drivers/Kconfig @@ -120,4 +120,16 @@ config SND_PORTMAN2X4 To compile this driver as a module, choose M here: the module will be called snd-portman2x4.
+config SND_ML403_AC97CR + tristate "Xilinx ML403 AC97 Controller Reference" + depends on SND && XILINX_VIRTEX + select SND_AC97_CODEC + help + Say Y here to include support for the + opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403 + reference design. + + To compile this driver as a module, choose M here: the module + will be called snd-ml403_ac97cr. + endmenu diff --git a/sound/drivers/Makefile b/sound/drivers/Makefile index 0411264..c5a9471 100644 --- a/sound/drivers/Makefile +++ b/sound/drivers/Makefile @@ -9,6 +9,7 @@ snd-mts64-objs := mts64.o snd-portman2x4-objs := portman2x4.o snd-serial-u16550-objs := serial-u16550.o snd-virmidi-objs := virmidi.o +snd-ml403-ac97cr-objs := ml403-ac97cr.o pcm-indirect2.o
# Toplevel Module Dependency obj-$(CONFIG_SND_DUMMY) += snd-dummy.o @@ -17,5 +18,6 @@ obj-$(CONFIG_SND_SERIAL_U16550) += snd-serial-u16550.o obj-$(CONFIG_SND_MTPAV) += snd-mtpav.o obj-$(CONFIG_SND_MTS64) += snd-mts64.o obj-$(CONFIG_SND_PORTMAN2X4) += snd-portman2x4.o +obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403-ac97cr.o
obj-$(CONFIG_SND) += opl3/ opl4/ mpu401/ vx/ diff --git a/sound/drivers/ml403-ac97cr.c b/sound/drivers/ml403-ac97cr.c new file mode 100644 index 0000000..2222315 --- /dev/null +++ b/sound/drivers/ml403-ac97cr.c @@ -0,0 +1,1353 @@ +/* + * ALSA driver for Xilinx ML403 AC97 Controller Reference + * IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i) + * IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i) + * + * Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* Some notes / status of this driver: + * + * - Don't wonder about some strange implementations of things - especially the + * (heavy) shadowing of codec registers, with which I tried to reduce read + * accesses to a minimum, because after a variable amount of accesses, the AC97 + * controller doesn't raise the register access finished bit anymore ... + * + * - Capture support works - basically, but after ~30s (with rates > ~20kHz) + * ALSA stops reading captured samples from the intermediate buffer and + * therefore a overrun happens - ATM I don't know what's wrong. + * + * - Playback support seems to be pretty stable - no issues here. + */ + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/moduleparam.h> + +#include <linux/platform_device.h> + +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/interrupt.h> + +/* HZ */ +#include <linux/param.h> +/* jiffies, time_*() */ +#include <linux/jiffies.h> +/* schedule_timeout*() */ +#include <linux/sched.h> +/* spin_lock*() */ +#include <linux/spinlock.h> +/* struct mutex, mutex_init(), mutex_*lock() */ +#include <linux/mutex.h> + +/* snd_printk(), snd_printd() */ +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h> + +#include "pcm-indirect2.h" + + +#define SND_ML403_AC97CR_DRIVER "ml403-ac97cr" + +MODULE_AUTHOR("Joachim Foerster JOFT@gmx.de"); +MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for ML403 AC97 Controller Reference."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for ML403 AC97 Controller Reference."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable this ML403 AC97 Controller Reference."); + +/* Special feature options */ +/*#define CODEC_WRITE_CHECK_RAF*/ /* don't return after a write to a codec + * register, while RAF bit is not set + */ +/* Debug options for code which may be removed completely in a final version */ +#ifdef CONFIG_SND_DEBUG +/*#define CODEC_STAT*/ /* turn on some minimal "statistics" + * about codec register usage + */ +#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the + * process of copying bytes from the + * intermediate buffer to the hardware + * fifo and the other way round + */ +#endif + +/* Definition of a "level/facility dependent" printk(); may be removed + * completely in a final version + */ +#undef PDEBUG +#ifdef CONFIG_SND_DEBUG +/* "facilities" for PDEBUG */ +#define UNKNOWN (1<<0) +#define CODEC_SUCCESS (1<<1) +#define CODEC_FAKE (1<<2) +#define INIT_INFO (1<<3) +#define INIT_FAILURE (1<<4) +#define WORK_INFO (1<<5) +#define WORK_FAILURE (1<<6) + +#define PDEBUG_FACILITIES (UNKNOWN | INIT_FAILURE | WORK_FAILURE) + +#define PDEBUG(fac, fmt, args...) do { \ + if (fac & PDEBUG_FACILITIES) \ + snd_printd(KERN_DEBUG SND_ML403_AC97CR_DRIVER ": " \ + fmt, ##args); \ + } while (0) +#else +#define PDEBUG(fac, fmt, args...) /* nothing */ +#endif + + + +/* Defines for "waits"/timeouts (portions of HZ=250 on arch/ppc by default) */ +#define CODEC_TIMEOUT_ON_INIT 5 /* timeout for checking for codec + * readiness (after insmod) + */ +#ifndef CODEC_WRITE_CHECK_RAF +#define CODEC_WAIT_AFTER_WRITE 100 /* general, static wait after a write + * access to a codec register, may be + * 0 to completely remove wait + */ +#else +#define CODEC_TIMEOUT_AFTER_WRITE 5 /* timeout after a write access to a + * codec register, if RAF bit is used + */ +#endif +#define CODEC_TIMEOUT_AFTER_READ 5 /* timeout after a read access to a + * codec register (checking RAF bit) + */ + +/* Infrastructure for codec register shadowing */ +#define LM4550_REG_OK (1<<0) /* register exists */ +#define LM4550_REG_DONEREAD (1<<1) /* read register once, value should be + * the same currently in the register + */ +#define LM4550_REG_NOSAVE (1<<2) /* values written to this register will + * not be saved in the register + */ +#define LM4550_REG_NOSHADOW (1<<3) /* don't do register shadowing, use plain + * hardware access + */ +#define LM4550_REG_READONLY (1<<4) /* register is read only */ +#define LM4550_REG_FAKEPROBE (1<<5) /* fake write _and_ read actions during + * probe() correctly + */ +#define LM4550_REG_FAKEREAD (1<<6) /* fake read access, always return + * default value + */ +#define LM4550_REG_ALLFAKE (LM4550_REG_FAKEREAD | LM4550_REG_FAKEPROBE) + +struct lm4550_reg { + u16 value; + u16 flag; + u16 wmask; + u16 def; +}; + +struct lm4550_reg lm4550_regfile[64] = { + [AC97_RESET / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSAVE \ + | LM4550_REG_FAKEREAD, + .def = 0x0D50}, + [AC97_MASTER / 2] = {.flag = LM4550_REG_OK + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + [AC97_HEADPHONE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8000}, + [AC97_MASTER_MONO / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8000}, + [AC97_PC_BEEP / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801E, + .def = 0x0}, + [AC97_PHONE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x801F, + .def = 0x8008}, + [AC97_MIC / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x805F, + .def = 0x8008}, + [AC97_LINE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_CD / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_VIDEO / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_AUX / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8808}, + [AC97_PCM / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x9F1F, + .def = 0x8008}, + [AC97_REC_SEL / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x707, + .def = 0x0}, + [AC97_REC_GAIN / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .wmask = 0x8F0F, + .def = 0x8000}, + [AC97_GENERAL_PURPOSE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0x0, + .wmask = 0xA380}, + [AC97_3D_CONTROL / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEREAD \ + | LM4550_REG_READONLY, + .def = 0x0101}, + [AC97_POWERDOWN / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSHADOW \ + | LM4550_REG_NOSAVE, + .wmask = 0xFF00}, + /* may not write ones to + * REF/ANL/DAC/ADC bits + * FIXME: Is this ok? + */ + [AC97_EXTENDED_ID / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEREAD \ + | LM4550_REG_READONLY, + .def = 0x0201}, /* primary codec */ + [AC97_EXTENDED_STATUS / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_NOSHADOW \ + | LM4550_REG_NOSAVE, + .wmask = 0x1}, + [AC97_PCM_FRONT_DAC_RATE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + [AC97_PCM_LR_ADC_RATE / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_FAKEPROBE, + .def = 0xBB80, + .wmask = 0xFFFF}, + [AC97_VENDOR_ID1 / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_READONLY \ + | LM4550_REG_FAKEREAD, + .def = 0x4E53}, + [AC97_VENDOR_ID2 / 2] = {.flag = LM4550_REG_OK \ + | LM4550_REG_READONLY \ + | LM4550_REG_FAKEREAD, + .def = 0x4350} +}; + +#define LM4550_RF_OK(reg) (lm4550_regfile[reg / 2].flag & LM4550_REG_OK) + +static void lm4550_regfile_init(void) +{ + int i; + for (i = 0; i < 64; i++) + if (lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) + lm4550_regfile[i].value = lm4550_regfile[i].def; +} + +static void lm4550_regfile_write_values_after_init(struct snd_ac97 *ac97) +{ + int i; + for (i = 0; i < 64; i++) + if ((lm4550_regfile[i].flag & LM4550_REG_FAKEPROBE) && + (lm4550_regfile[i].value != lm4550_regfile[i].def)) { + PDEBUG(CODEC_FAKE, "lm4550_regfile_write_values_after_" + "init(): reg=0x%x value=0x%x / %d is different " + "from def=0x%x / %d\n", + i, lm4550_regfile[i].value, + lm4550_regfile[i].value, lm4550_regfile[i].def, + lm4550_regfile[i].def); + snd_ac97_write(ac97, i * 2, lm4550_regfile[i].value); + lm4550_regfile[i].flag |= LM4550_REG_DONEREAD; + } +} + + +/* direct registers */ +#define CR_REG(ml403_ac97cr, x) ((ml403_ac97cr)->port + CR_REG_##x) + +#define CR_REG_PLAYFIFO 0x00 +#define CR_PLAYDATA(a) ((a) & 0xFFFF) + +#define CR_REG_RECFIFO 0x04 +#define CR_RECDATA(a) ((a) & 0xFFFF) + +#define CR_REG_STATUS 0x08 +#define CR_RECOVER (1<<7) +#define CR_PLAYUNDER (1<<6) +#define CR_CODECREADY (1<<5) +#define CR_RAF (1<<4) +#define CR_RECEMPTY (1<<3) +#define CR_RECFULL (1<<2) +#define CR_PLAYHALF (1<<1) +#define CR_PLAYFULL (1<<0) + +#define CR_REG_RESETFIFO 0x0C +#define CR_RECRESET (1<<1) +#define CR_PLAYRESET (1<<0) + +#define CR_REG_CODEC_ADDR 0x10 +/* UG082 says: + * #define CR_CODEC_ADDR(a) ((a) << 1) + * #define CR_CODEC_READ (1<<0) + * #define CR_CODEC_WRITE (0<<0) + */ +/* RefDesign example says: */ +#define CR_CODEC_ADDR(a) ((a) << 0) +#define CR_CODEC_READ (1<<7) +#define CR_CODEC_WRITE (0<<7) + +#define CR_REG_CODEC_DATAREAD 0x14 +#define CR_CODEC_DATAREAD(v) ((v) & 0xFFFF) + +#define CR_REG_CODEC_DATAWRITE 0x18 +#define CR_CODEC_DATAWRITE(v) ((v) & 0xFFFF) + +#define CR_FIFO_SIZE 32 + +struct snd_ml403_ac97cr { + /* lock for access to (controller) registers */ + spinlock_t reg_lock; + /* mutex for the whole sequence of accesses to (controller) registers + * which affect codec registers + */ + struct mutex cdc_mutex; + + int irq; /* for playback */ + int enable_irq; /* for playback */ + + int capture_irq; + int enable_capture_irq; + + struct resource *res_port; + void *port; + + struct snd_ac97 *ac97; + int ac97_fake; +#ifdef CODEC_STAT + int ac97_read; + int ac97_write; +#endif + + struct platform_device *pfdev; + struct snd_card *card; + struct snd_pcm *pcm; + struct snd_pcm_substream *playback_substream; + struct snd_pcm_substream *capture_substream; + + struct snd_pcm_indirect2 ind_rec; /* for playback */ + struct snd_pcm_indirect2 capture_ind2_rec; +}; + +static struct snd_pcm_hardware snd_ml403_ac97cr_playback = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static struct snd_pcm_hardware snd_ml403_ac97cr_capture = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = (SNDRV_PCM_RATE_CONTINUOUS | + SNDRV_PCM_RATE_8000_48000), + .rate_min = 4000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (128*1024), + .period_bytes_min = CR_FIFO_SIZE/2, + .period_bytes_max = (64*1024), + .periods_min = 2, + .periods_max = (128*1024)/(CR_FIFO_SIZE/2), + .fifo_size = 0, +}; + +static size_t +snd_ml403_ac97cr_playback_ind2_zero(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), 0); + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_playback_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *src; + int copied_words = 0; + u32 full = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + src = (u16 *)(substream->runtime->dma_area + rec->sw_data); + + spin_lock(&ml403_ac97cr->reg_lock); + while (((full = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_PLAYFULL)) != CR_PLAYFULL) && (bytes > 1)) { + out_be32(CR_REG(ml403_ac97cr, PLAYFIFO), + CR_PLAYDATA(src[copied_words])); + copied_words++; + bytes = bytes - 2; + } + if (full != CR_PLAYFULL) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_null(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + spin_lock(&ml403_ac97cr->reg_lock); + while ((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) { + volatile u32 trash; + + trash = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, RECFIFO))); + /* Hmmmm, really necessary? Don't want call to in_be32() + * to be optimised away! + */ + trash++; + copied_words++; + } + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static size_t +snd_ml403_ac97cr_capture_ind2_copy(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, size_t bytes) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + u16 *dst; + int copied_words = 0; + u32 empty = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + dst = (u16 *)(substream->runtime->dma_area + rec->sw_data); + + spin_lock(&ml403_ac97cr->reg_lock); + while (((empty = (in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RECEMPTY)) != CR_RECEMPTY) && (bytes > 1)) { + dst[copied_words] = CR_RECDATA(in_be32(CR_REG(ml403_ac97cr, + RECFIFO))); + copied_words++; + bytes = bytes - 2; + } + if (empty != CR_RECEMPTY) + rec->hw_ready = 1; + else + rec->hw_ready = 0; + spin_unlock(&ml403_ac97cr->reg_lock); + + return (size_t) (copied_words * 2); +} + +static snd_pcm_uframes_t +snd_ml403_ac97cr_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_indirect2 *ind2_rec = NULL; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + if (substream == ml403_ac97cr->playback_substream) + ind2_rec = &ml403_ac97cr->ind_rec; + if (substream == ml403_ac97cr->capture_substream) + ind2_rec = &ml403_ac97cr->capture_ind2_rec; + + if (ind2_rec != NULL) + return snd_pcm_indirect2_pointer(substream, ind2_rec); + return (snd_pcm_uframes_t) 0; +} + +static int +snd_ml403_ac97cr_pcm_playback_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + PDEBUG(WORK_INFO, "trigger(playback): START\n"); + ml403_ac97cr->ind_rec.hw_ready = 1; + + /* clear play FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_PLAYRESET); + + /* enable play irq */ + ml403_ac97cr->enable_irq = 1; + enable_irq(ml403_ac97cr->irq); + break; + case SNDRV_PCM_TRIGGER_STOP: + PDEBUG(WORK_INFO, "trigger(playback): STOP\n"); + ml403_ac97cr->ind_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, &ml403_ac97cr->ind_rec); +#endif + /* disable play irq */ + disable_irq_nosync(ml403_ac97cr->irq); + ml403_ac97cr->enable_irq = 0; + break; + default: + err = -EINVAL; + break; + } + PDEBUG(WORK_INFO, "trigger(playback): (done)\n"); + return err; +} + +static int +snd_ml403_ac97cr_pcm_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err = 0; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + PDEBUG(WORK_INFO, "trigger(capture): START\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; + + /* clear record FIFO */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), CR_RECRESET); + + /* enable record irq */ + ml403_ac97cr->enable_capture_irq = 1; + enable_irq(ml403_ac97cr->capture_irq); + break; + case SNDRV_PCM_TRIGGER_STOP: + PDEBUG(WORK_INFO, "trigger(capture): STOP\n"); + ml403_ac97cr->capture_ind2_rec.hw_ready = 0; +#ifdef SND_PCM_INDIRECT2_STAT + snd_pcm_indirect2_stat(substream, + &ml403_ac97cr->capture_ind2_rec); +#endif + /* disable capture irq */ + disable_irq_nosync(ml403_ac97cr->capture_irq); + ml403_ac97cr->enable_capture_irq = 0; + break; + default: + err = -EINVAL; + break; + } + PDEBUG(WORK_INFO, "trigger(capture): (done)\n"); + return err; +} + +static int +snd_ml403_ac97cr_pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, + "prepare(): period_bytes=%d, minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_FRONT_DAC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->ind_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->ind_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->ind_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->ind_rec.min_periods = -1; + ml403_ac97cr->ind_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", + CR_FIFO_SIZE, ml403_ac97cr->ind_rec.sw_buffer_size, + ml403_ac97cr->ind_rec.min_multiple); + return 0; +} + +static int +snd_ml403_ac97cr_pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, + "prepare(capture): period_bytes=%d, minperiod_bytes=%d\n", + snd_pcm_lib_period_bytes(substream), CR_FIFO_SIZE / 2); + + /* set sampling rate */ + snd_ac97_set_rate(ml403_ac97cr->ac97, AC97_PCM_LR_ADC_RATE, + runtime->rate); + PDEBUG(WORK_INFO, "prepare(capture): rate=%d\n", runtime->rate); + + /* init struct for intermediate buffer */ + memset(&ml403_ac97cr->capture_ind2_rec, 0, + sizeof(struct snd_pcm_indirect2)); + ml403_ac97cr->capture_ind2_rec.hw_buffer_size = CR_FIFO_SIZE; + ml403_ac97cr->capture_ind2_rec.sw_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + ml403_ac97cr->capture_ind2_rec.min_multiple = + snd_pcm_lib_period_bytes(substream) / (CR_FIFO_SIZE / 2); + PDEBUG(WORK_INFO, "prepare(capture): hw_buffer_size=%d, " + "sw_buffer_size=%d, min_multiple=%d\n", CR_FIFO_SIZE, + ml403_ac97cr->capture_ind2_rec.sw_buffer_size, + ml403_ac97cr->capture_ind2_rec.min_multiple); + return 0; +} + +static int snd_ml403_ac97cr_hw_free(struct snd_pcm_substream *substream) +{ + PDEBUG(WORK_INFO, "hw_free()\n"); + return snd_pcm_lib_free_pages(substream); +} + +static int +snd_ml403_ac97cr_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + PDEBUG(WORK_INFO, "hw_params(): desired buffer bytes=%d, desired " + "period bytes=%d\n", + params_buffer_bytes(hw_params), params_period_bytes(hw_params)); + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int snd_ml403_ac97cr_playback_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(playback)\n"); + ml403_ac97cr->playback_substream = substream; + runtime->hw = snd_ml403_ac97cr_playback; + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + CR_FIFO_SIZE / 2); + return 0; +} + +static int snd_ml403_ac97cr_capture_open(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct snd_pcm_runtime *runtime; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + runtime = substream->runtime; + + PDEBUG(WORK_INFO, "open(capture)\n"); + ml403_ac97cr->capture_substream = substream; + runtime->hw = snd_ml403_ac97cr_capture; + + snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + CR_FIFO_SIZE / 2); + return 0; +} + +static int snd_ml403_ac97cr_playback_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(playback)\n"); + ml403_ac97cr->playback_substream = NULL; + return 0; +} + +static int snd_ml403_ac97cr_capture_close(struct snd_pcm_substream *substream) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + + ml403_ac97cr = snd_pcm_substream_chip(substream); + + PDEBUG(WORK_INFO, "close(capture)\n"); + ml403_ac97cr->capture_substream = NULL; + return 0; +} + +static struct snd_pcm_ops snd_ml403_ac97cr_playback_ops = { + .open = snd_ml403_ac97cr_playback_open, + .close = snd_ml403_ac97cr_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_playback_prepare, + .trigger = snd_ml403_ac97cr_pcm_playback_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +static struct snd_pcm_ops snd_ml403_ac97cr_capture_ops = { + .open = snd_ml403_ac97cr_capture_open, + .close = snd_ml403_ac97cr_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_ml403_ac97cr_hw_params, + .hw_free = snd_ml403_ac97cr_hw_free, + .prepare = snd_ml403_ac97cr_pcm_capture_prepare, + .trigger = snd_ml403_ac97cr_pcm_capture_trigger, + .pointer = snd_ml403_ac97cr_pcm_pointer, +}; + +static irqreturn_t snd_ml403_ac97cr_irq(int irq, void *dev_id) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + struct platform_device *pfdev; + int cmp_irq; + + ml403_ac97cr = (struct snd_ml403_ac97cr *)dev_id; + if (ml403_ac97cr == NULL) + return IRQ_NONE; + + pfdev = ml403_ac97cr->pfdev; + + /* playback interrupt */ + cmp_irq = platform_get_irq(pfdev, 0); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_irq) + snd_pcm_indirect2_playback_interrupt( + ml403_ac97cr->playback_substream, + &ml403_ac97cr->ind_rec, + snd_ml403_ac97cr_playback_ind2_copy, + snd_ml403_ac97cr_playback_ind2_zero); + else + goto __disable_irq; + } else { + /* record interrupt */ + cmp_irq = platform_get_irq(pfdev, 1); + if (irq == cmp_irq) { + if (ml403_ac97cr->enable_capture_irq) + snd_pcm_indirect2_capture_interrupt( + ml403_ac97cr->capture_substream, + &ml403_ac97cr->capture_ind2_rec, + snd_ml403_ac97cr_capture_ind2_copy, + snd_ml403_ac97cr_capture_ind2_null); + else + goto __disable_irq; + } else + return IRQ_NONE; + } + return IRQ_HANDLED; + +__disable_irq: + PDEBUG(INIT_INFO, "irq(): irq %d is meant to be disabled! So, now try " + "to disable it _really_!\n", irq); + disable_irq_nosync(irq); + return IRQ_HANDLED; +} + +static unsigned short +snd_ml403_ac97cr_codec_read(struct snd_ac97 *ac97, unsigned short reg) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; +#ifdef CODEC_STAT + u32 stat; + u32 rafaccess = 0; +#endif + unsigned long end_time; + u16 value = 0; + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return 0; + } + /* check if we can fake/answer this access from our shadow register */ + if ((lm4550_regfile[reg / 2].flag & + (LM4550_REG_DONEREAD | LM4550_REG_ALLFAKE)) && + !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) { + if (lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEREAD) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d\n", + reg, lm4550_regfile[reg / 2].def, + lm4550_regfile[reg / 2].def); + return lm4550_regfile[reg / 2].def; + } else if ((lm4550_regfile[reg / 2].flag & + LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) { + PDEBUG(CODEC_FAKE, "codec_read(): faking read from " + "reg=0x%x, val=0x%x / %d (probe)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value); + return lm4550_regfile[reg / 2].value; + } else { +#ifdef CODEC_STAT + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d) (cw=%d cr=%d)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value, + ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + PDEBUG(CODEC_FAKE, "codec_read(): read access " + "answered by shadow register 0x%x (value=0x%x " + "/ %d)\n", + reg, lm4550_regfile[reg / 2].value, + lm4550_regfile[reg / 2].value); +#endif + return lm4550_regfile[reg / 2].value; + } + } + /* if we are here, we _have_ to access the codec really, no faking */ + if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0) + return 0; +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read++; +#endif + spin_lock(&ml403_ac97cr->reg_lock); + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_READ); + spin_unlock(&ml403_ac97cr->reg_lock); + end_time = jiffies + (HZ / CODEC_TIMEOUT_AFTER_READ); + do { + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + rafaccess++; + stat = in_be32(CR_REG(ml403_ac97cr, STATUS)); + if ((stat & CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) reg=0x%x, " + "value=0x%x / %d (STATUS=0x%x)\n", + reg, value, value, stat); +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { + value = CR_CODEC_DATAREAD( + in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + PDEBUG(CODEC_SUCCESS, "codec_read(): (done) " + "reg=0x%x, value=0x%x / %d\n", + reg, value, value); +#endif + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return value; + } + spin_unlock(&ml403_ac97cr->reg_lock); + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + /* read the DATAREAD register anyway, see comment below */ + spin_lock(&ml403_ac97cr->reg_lock); + value = + CR_CODEC_DATAREAD(in_be32(CR_REG(ml403_ac97cr, CODEC_DATAREAD))); + spin_unlock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! " + "(reg=0x%x, last STATUS=0x%x, DATAREAD=0x%x / %d, %d) " + "(cw=%d, cr=%d)\n", + reg, stat, value, value, rafaccess, + ml403_ac97cr->ac97_write, ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec read! " + "(reg=0x%x, DATAREAD=0x%x / %d)\n", + reg, value, value); +#endif + /* BUG: This is PURE speculation! But after _most_ read timeouts the + * value in the register is ok! + */ + lm4550_regfile[reg / 2].value = value; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return value; +} + +static void +snd_ml403_ac97cr_codec_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + +#ifdef CODEC_STAT + u32 stat; + u32 rafaccess = 0; +#endif +#ifdef CODEC_WRITE_CHECK_RAF + unsigned long end_time; +#endif + + if (!LM4550_RF_OK(reg)) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "access to unknown/unused codec register 0x%x " + "ignored!\n", reg); + return; + } + if (lm4550_regfile[reg / 2].flag & LM4550_REG_READONLY) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to read only codec register 0x%x " + "ignored!\n", reg); + return; + } + if ((val & lm4550_regfile[reg / 2].wmask) != val) { + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "write access to codec register 0x%x " + "with bad value 0x%x / %d!\n", + reg, val, val); + val = val & lm4550_regfile[reg / 2].wmask; + } + if (((lm4550_regfile[reg / 2].flag & LM4550_REG_FAKEPROBE) && + ml403_ac97cr->ac97_fake) && + !(lm4550_regfile[reg / 2].flag & LM4550_REG_NOSHADOW)) { + PDEBUG(CODEC_FAKE, "codec_write(): faking write to reg=0x%x, " + "val=0x%x / %d\n", reg, val, val); + lm4550_regfile[reg / 2].value = (val & + lm4550_regfile[reg / 2].wmask); + return; + } + if (mutex_lock_interruptible(&ml403_ac97cr->cdc_mutex) != 0) + return; +#ifdef CODEC_STAT + ml403_ac97cr->ac97_write++; +#endif + spin_lock(&ml403_ac97cr->reg_lock); + out_be32(CR_REG(ml403_ac97cr, CODEC_DATAWRITE), + CR_CODEC_DATAWRITE(val)); + out_be32(CR_REG(ml403_ac97cr, CODEC_ADDR), + CR_CODEC_ADDR(reg) | CR_CODEC_WRITE); + spin_unlock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_WRITE_CHECK_RAF + /* check CR_CODEC_RAF bit to see if write access to register is done; + * loop until bit is set or timeout happens + */ + end_time = jiffies + HZ / CODEC_TIMEOUT_AFTER_WRITE; + do { + spin_lock(&ml403_ac97cr->reg_lock); +#ifdef CODEC_STAT + rafaccess++; + stat = in_be32(CR_REG(ml403_ac97cr, STATUS)) + if ((stat & CR_RAF) == CR_RAF) { +#else + if ((in_be32(CR_REG(ml403_ac97cr, STATUS)) & + CR_RAF) == CR_RAF) { +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) " + "reg=0x%x, value=%d / 0x%x\n", + reg, val, val); + if (!(lm4550_regfile[reg / 2].flag & + LM4550_REG_NOSHADOW) && + !(lm4550_regfile[reg / 2].flag & + LM4550_REG_NOSAVE)) + lm4550_regfile[reg / 2].value = val; + lm4550_regfile[reg / 2].flag |= LM4550_REG_DONEREAD; + spin_unlock(&ml403_ac97cr->reg_lock); + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return; + } + spin_unlock(&ml403_ac97cr->reg_lock); + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); +#ifdef CODEC_STAT + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write " + "(reg=0x%x, val=0x%x / %d, last STATUS=0x%x, %d) " + "(cw=%d, cr=%d)\n", + reg, val, val, stat, rafaccess, ml403_ac97cr->ac97_write, + ml403_ac97cr->ac97_read); +#else + snd_printk(KERN_WARNING SND_ML403_AC97CR_DRIVER ": " + "timeout while codec write (reg=0x%x, val=0x%x / %d)\n", + reg, val, val); +#endif +#else /* CODEC_WRITE_CHECK_RAF */ +#if CODEC_WAIT_AFTER_WRITE > 0 + /* officially, in AC97 spec there is no possibility for a AC97 + * controller to determine, if write access is done or not - so: How + * is Xilinx able to provide a RAF bit for write access? + * => very strange, thus just don't check RAF bit (compare with + * Xilinx's example app in EDK 8.1i) and wait + */ + schedule_timeout_uninterruptible(HZ / CODEC_WAIT_AFTER_WRITE); +#endif + PDEBUG(CODEC_SUCCESS, "codec_write(): (done) " + "reg=0x%x, value=%d / 0x%x (no RAF check)\n", + reg, val, val); +#endif + mutex_unlock(&ml403_ac97cr->cdc_mutex); + return; +} + +static int __devinit +snd_ml403_ac97cr_chip_init(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + unsigned long end_time; + PDEBUG(INIT_INFO, "chip_init():\n"); + end_time = jiffies + HZ / CODEC_TIMEOUT_ON_INIT; + do { + if (in_be32(CR_REG(ml403_ac97cr, STATUS)) & CR_CODECREADY) { + /* clear both hardware FIFOs */ + out_be32(CR_REG(ml403_ac97cr, RESETFIFO), + CR_RECRESET | CR_PLAYRESET); + PDEBUG(INIT_INFO, "chip_init(): (done)\n"); + return 0; + } + schedule_timeout_uninterruptible(1); + } while (time_after(end_time, jiffies)); + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "timeout while waiting for codec, " + "not ready!\n"); + return -EBUSY; +} + +static int snd_ml403_ac97cr_free(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + PDEBUG(INIT_INFO, "free():\n"); + /* irq release */ + if (ml403_ac97cr->irq >= 0) + free_irq(ml403_ac97cr->irq, ml403_ac97cr); + if (ml403_ac97cr->capture_irq >= 0) + free_irq(ml403_ac97cr->capture_irq, ml403_ac97cr); + /* give back "port" */ + if (ml403_ac97cr->port != NULL) + iounmap(ml403_ac97cr->port); + kfree(ml403_ac97cr); + PDEBUG(INIT_INFO, "free(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_dev_free(struct snd_device *snddev) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = snddev->device_data; + PDEBUG(INIT_INFO, "dev_free():\n"); + return snd_ml403_ac97cr_free(ml403_ac97cr); +} + +static int __devinit +snd_ml403_ac97cr_create(struct snd_card *card, struct platform_device *pfdev, + struct snd_ml403_ac97cr **rml403_ac97cr) +{ + struct snd_ml403_ac97cr *ml403_ac97cr; + int err; + static struct snd_device_ops ops = { + .dev_free = snd_ml403_ac97cr_dev_free, + }; + struct resource *resource; + int irq; + + *rml403_ac97cr = NULL; + ml403_ac97cr = kzalloc(sizeof(*ml403_ac97cr), GFP_KERNEL); + if (ml403_ac97cr == NULL) + return -ENOMEM; + spin_lock_init(&ml403_ac97cr->reg_lock); + mutex_init(&ml403_ac97cr->cdc_mutex); + ml403_ac97cr->card = card; + ml403_ac97cr->pfdev = pfdev; + ml403_ac97cr->irq = -1; + ml403_ac97cr->enable_irq = 0; + ml403_ac97cr->capture_irq = -1; + ml403_ac97cr->enable_capture_irq = 0; + ml403_ac97cr->port = NULL; + ml403_ac97cr->res_port = NULL; + + PDEBUG(INIT_INFO, "Trying to reserve resources now ...\n"); + resource = platform_get_resource(pfdev, IORESOURCE_MEM, 0); + /* get "port" */ + ml403_ac97cr->port = ioremap_nocache(resource->start, + (resource->end) - + (resource->start) + 1); + if (ml403_ac97cr->port == NULL) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to remap memory region (%x to %x)\n", + resource->start, resource->end); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "remap controller memory region to " + "0x%x done\n", (unsigned int)ml403_ac97cr->port); + /* get irq */ + irq = platform_get_irq(pfdev, 0); + if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED, + pfdev->dev.bus_id, (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (playback) irq %d done\n", + ml403_ac97cr->irq); + irq = platform_get_irq(pfdev, 1); + if (request_irq(irq, snd_ml403_ac97cr_irq, IRQF_DISABLED, + pfdev->dev.bus_id, (void *)ml403_ac97cr)) { + snd_printk(KERN_ERR SND_ML403_AC97CR_DRIVER ": " + "unable to grab IRQ %d\n", + irq); + snd_ml403_ac97cr_free(ml403_ac97cr); + return -EBUSY; + } + ml403_ac97cr->capture_irq = irq; + snd_printk(KERN_INFO SND_ML403_AC97CR_DRIVER ": " + "request (capture) irq %d done\n", + ml403_ac97cr->capture_irq); + + err = snd_ml403_ac97cr_chip_init(ml403_ac97cr); + if (err < 0) { + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, ml403_ac97cr, &ops); + if (err < 0) { + PDEBUG(INIT_FAILURE, "probe(): snd_device_new() failed!\n"); + snd_ml403_ac97cr_free(ml403_ac97cr); + return err; + } + + snd_card_set_dev(card, &pfdev->dev); + + *rml403_ac97cr = ml403_ac97cr; + return 0; +} + +static void snd_ml403_ac97cr_mixer_free(struct snd_ac97 *ac97) +{ + struct snd_ml403_ac97cr *ml403_ac97cr = ac97->private_data; + PDEBUG(INIT_INFO, "mixer_free():\n"); + ml403_ac97cr->ac97 = NULL; + PDEBUG(INIT_INFO, "mixer_free(): (done)\n"); +} + +static int __devinit +snd_ml403_ac97cr_mixer(struct snd_ml403_ac97cr *ml403_ac97cr) +{ + struct snd_ac97_bus *bus; + struct snd_ac97_template ac97; + int err; + static struct snd_ac97_bus_ops ops = { + .write = snd_ml403_ac97cr_codec_write, + .read = snd_ml403_ac97cr_codec_read, + }; + PDEBUG(INIT_INFO, "mixer():\n"); + err = snd_ac97_bus(ml403_ac97cr->card, 0, &ops, NULL, &bus); + if (err < 0) + return err; + + memset(&ac97, 0, sizeof(ac97)); + ml403_ac97cr->ac97_fake = 1; + lm4550_regfile_init(); +#ifdef CODEC_STAT + ml403_ac97cr->ac97_read = 0; + ml403_ac97cr->ac97_write = 0; +#endif + ac97.private_data = ml403_ac97cr; + ac97.private_free = snd_ml403_ac97cr_mixer_free; + ac97.scaps = AC97_SCAP_AUDIO | AC97_SCAP_SKIP_MODEM | + AC97_SCAP_NO_SPDIF; + err = snd_ac97_mixer(bus, &ac97, &ml403_ac97cr->ac97); + ml403_ac97cr->ac97_fake = 0; + lm4550_regfile_write_values_after_init(ml403_ac97cr->ac97); + PDEBUG(INIT_INFO, "mixer(): (done) snd_ac97_mixer()=%d\n", err); + return err; +} + +static int __devinit +snd_ml403_ac97cr_pcm(struct snd_ml403_ac97cr *ml403_ac97cr, int device, + struct snd_pcm **rpcm) +{ + struct snd_pcm *pcm; + int err; + + if (rpcm) + *rpcm = NULL; + err = snd_pcm_new(ml403_ac97cr->card, "ML403AC97CR/1", device, 1, 1, + &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_ml403_ac97cr_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_ml403_ac97cr_capture_ops); + pcm->private_data = ml403_ac97cr; + pcm->info_flags = 0; + strcpy(pcm->name, "ML403AC97CR DAC/ADC"); + ml403_ac97cr->pcm = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + 64 * 1024, + 128 * 1024); + if (rpcm) + *rpcm = pcm; + return 0; +} + +static int __devinit snd_ml403_ac97cr_probe(struct platform_device *pfdev) +{ + struct snd_card *card; + struct snd_ml403_ac97cr *ml403_ac97cr = NULL; + int err; + int dev = pfdev->id; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) + return -ENOENT; + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (card == NULL) + return -ENOMEM; + err = snd_ml403_ac97cr_create(card, pfdev, &ml403_ac97cr); + if (err < 0) { + PDEBUG(INIT_FAILURE, "probe(): create failed!\n"); + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): create done\n"); + card->private_data = ml403_ac97cr; + err = snd_ml403_ac97cr_mixer(ml403_ac97cr); + if (err < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): mixer done\n"); + err = snd_ml403_ac97cr_pcm(ml403_ac97cr, 0, NULL); + if (err < 0) { + snd_card_free(card); + return err; + } + PDEBUG(INIT_INFO, "probe(): PCM done\n"); + strcpy(card->driver, SND_ML403_AC97CR_DRIVER); + strcpy(card->shortname, "ML403 AC97 Controller Reference"); + sprintf(card->longname, "%s %s at 0x%lx, irq %i & %i, device %i", + card->shortname, card->driver, + (unsigned long)ml403_ac97cr->port, ml403_ac97cr->irq, + ml403_ac97cr->capture_irq, dev + 1); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + platform_set_drvdata(pfdev, card); + PDEBUG(INIT_INFO, "probe(): (done)\n"); + return 0; +} + +static int snd_ml403_ac97cr_remove(struct platform_device *pfdev) +{ + snd_card_free(platform_get_drvdata(pfdev)); + platform_set_drvdata(pfdev, NULL); + return 0; +} + +static struct platform_driver snd_ml403_ac97cr_driver = { + .probe = snd_ml403_ac97cr_probe, + .remove = snd_ml403_ac97cr_remove, + .driver = { + .name = SND_ML403_AC97CR_DRIVER, + }, +}; + +static int __init alsa_card_ml403_ac97cr_init(void) +{ + return platform_driver_register(&snd_ml403_ac97cr_driver); +} + +static void __exit alsa_card_ml403_ac97cr_exit(void) +{ + platform_driver_unregister(&snd_ml403_ac97cr_driver); +} + +module_init(alsa_card_ml403_ac97cr_init) +module_exit(alsa_card_ml403_ac97cr_exit) diff --git a/sound/drivers/pcm-indirect2.c b/sound/drivers/pcm-indirect2.c new file mode 100644 index 0000000..a36215a --- /dev/null +++ b/sound/drivers/pcm-indirect2.c @@ -0,0 +1,591 @@ +/* + * Helper functions for indirect PCM data transfer to a simple FIFO in + * hardware (small, no possibility to read "hardware io position", + * updating position done by interrupt, ...) + * + * Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de + * + * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by + * + * Copyright (c) by Takashi Iwai tiwai@suse.de + * Jaroslav Kysela perex@suse.cz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* #dependency of sound/core.h# */ +#include <sound/driver.h> +/* snd_printk/d() */ +#include <sound/core.h> +/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t + * snd_pcm_period_elapsed() */ +#include <sound/pcm.h> + +#include "pcm-indirect2.h" + +#ifdef SND_PCM_INDIRECT2_STAT +/* jiffies */ +#include <linux/jiffies.h> + +void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int i; + int j; + int k; + int seconds = (rec->lastbytetime - rec->firstbytetime) / HZ; + + snd_printk(KERN_DEBUG "STAT: mul_elapsed: %u, mul_elapsed_real: %d, " + "irq_occured: %d\n", + rec->mul_elapsed, rec->mul_elapsed_real, rec->irq_occured); + snd_printk(KERN_DEBUG "STAT: min_multiple: %d (irqs/period)\n", + rec->min_multiple); + snd_printk(KERN_DEBUG "STAT: firstbytetime: %lu, lastbytetime: %lu, " + "firstzerotime: %lu\n", + rec->firstbytetime, rec->lastbytetime, rec->firstzerotime); + snd_printk(KERN_DEBUG "STAT: bytes2hw: %u Bytes => (by runtime->rate) " + "length: %d s\n", + rec->bytes2hw, rec->bytes2hw / 2 / 2 / runtime->rate); + snd_printk(KERN_DEBUG "STAT: (by measurement) length: %d => " + "rate: %d Bytes/s = %d Frames/s|Hz\n", + seconds, rec->bytes2hw / seconds, + rec->bytes2hw / 2 / 2 / seconds); + snd_printk(KERN_DEBUG + "STAT: zeros2hw: %u = %d ms ~ %d * %d zero copies\n", + rec->zeros2hw, ((rec->zeros2hw / 2 / 2) * 1000) / + runtime->rate, + rec->zeros2hw / (rec->hw_buffer_size / 2), + (rec->hw_buffer_size / 2)); + snd_printk(KERN_DEBUG "STAT: pointer_calls: %u, lastdifftime: %u\n", + rec->pointer_calls, rec->lastdifftime); + snd_printk(KERN_DEBUG "STAT: sw_io: %d, sw_data: %d\n", rec->sw_io, + rec->sw_data); + snd_printk(KERN_DEBUG "STAT: byte_sizes[]:\n"); + k = 0; + for (j = 0; j < 8; j++) { + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->byte_sizes[i] != 0) { + snd_printk(KERN_DEBUG "%u: %u", + i, rec->byte_sizes[i]); + k++; + } + if (((k % 8) == 0) && (k != 0)) { + snd_printk(KERN_DEBUG "\n"); + k = 0; + } + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: zero_sizes[]:\n"); + for (j = 0; j < 8; j++) { + k = 0; + for (i = j * 8; i < (j + 1) * 8; i++) + if (rec->zero_sizes[i] != 0) + snd_printk(KERN_DEBUG "%u: %u", + i, rec->zero_sizes[i]); + else + k++; + if (!k) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: min_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->min_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->min_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG "STAT: mul_adds[]:\n"); + for (j = 0; j < 8; j++) { + if (rec->mul_adds[j] != 0) + snd_printk(KERN_DEBUG "%u: %u", j, rec->mul_adds[j]); + } + snd_printk(KERN_DEBUG "\n"); + snd_printk(KERN_DEBUG + "STAT: zero_times_saved: %d, zero_times_notsaved: %d\n", + rec->zero_times_saved, rec->zero_times_notsaved); + /* snd_printk(KERN_DEBUG "STAT: zero_times[]\n"); + i = 0; + for (j = 0; j < 3750; j++) { + if (rec->zero_times[j] != 0) { + snd_printk(KERN_DEBUG "%u: %u", j, rec->zero_times[j]); + i++; + } + if (((i % 8) == 0) && (i != 0)) + snd_printk(KERN_DEBUG "\n"); + } + snd_printk(KERN_DEBUG "\n"); */ + return; +} +#endif + +/* + * _internal_ helper function for playback/capture transfer function + */ +static void +snd_pcm_indirect2_increase_min_periods(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + int isplay, int iscopy, + unsigned int bytes) +{ + if (rec->min_periods >= 0) { + if (iscopy) { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= rec->sw_buffer_size; + } else if (isplay) { + /* If application does not write data in multiples of + * a period, move sw_data to the next correctly aligned + * position, so that sw_io can converge to it (in the + * next step). + */ + if (!rec->check_alignment) { + if (rec->bytes2hw % + snd_pcm_lib_period_bytes(substream)) { + unsigned bytes2hw_aligned = + (1 + + (rec->bytes2hw / + snd_pcm_lib_period_bytes + (substream))) * + snd_pcm_lib_period_bytes + (substream); + rec->sw_data = + bytes2hw_aligned % + rec->sw_buffer_size; +#ifdef SND_PCM_INDIRECT2_STAT + snd_printk(KERN_DEBUG + "STAT: @re-align: aligned " + "bytes2hw to next period " + "size boundary: %d " + "(instead of %d)\n", + bytes2hw_aligned, + rec->bytes2hw); + snd_printk(KERN_DEBUG + "STAT: @re-align: sw_data " + "moves to: %d\n", + rec->sw_data); +#endif + } + rec->check_alignment = 1; + } + /* We are at the end and are copying zeros into the + * fifo. + * Now, we have to make sure that sw_io is increased + * until the position of sw_data: Filling the fifo with + * the first zeros means, the last bytes were played. + */ + if (rec->sw_io != rec->sw_data) { + unsigned int diff; + if (rec->sw_data > rec->sw_io) + diff = rec->sw_data - rec->sw_io; + else + diff = (rec->sw_buffer_size - + rec->sw_io) + + rec->sw_data; + if (bytes >= diff) + rec->sw_io = rec->sw_data; + else { + rec->sw_io += bytes; + if (rec->sw_io >= rec->sw_buffer_size) + rec->sw_io -= + rec->sw_buffer_size; + } + } + } + rec->min_period_count += bytes; + if (rec->min_period_count >= (rec->hw_buffer_size / 2)) { + rec->min_periods += (rec->min_period_count / + (rec->hw_buffer_size / 2)); +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_period_count / + (rec->hw_buffer_size / 2)) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) min_adds " + "at once - too big to save!\n", + (rec->min_period_count / + (rec->hw_buffer_size / 2))); + else + rec->min_adds[(rec->min_period_count / + (rec->hw_buffer_size / 2))]++; +#endif + rec->min_period_count = (rec->min_period_count % + (rec->hw_buffer_size / 2)); + } + } else if (isplay && iscopy) + rec->min_periods = 0; +} + +/* + * helper function for playback/capture pointer callback + */ +snd_pcm_uframes_t +snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->pointer_calls++; +#endif + return bytes_to_frames(substream->runtime, rec->sw_io); +} + +/* + * _internal_ helper function for playback interrupt callback + */ +static void +snd_pcm_indirect2_playback_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + + /* runtime->control->appl_ptr: position where ALSA will write next time + * rec->appl_ptr: position where ALSA was last time + * diff: obviously ALSA wrote that much bytes into the intermediate + * buffer since we checked last time + */ + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + /* number of bytes "added" by ALSA increases the number of + * bytes which are ready to "be transfered to HW"/"played" + * Then, set rec->appl_ptr to not count bytes twice next time. + */ + rec->sw_ready += (int)frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + if (rec->hw_ready && (rec->sw_ready <= 0)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: mul_elapsed: %d, " + "min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG + "STAT: @firstzerotime: sw_io: %d, " + "sw_data: %d, appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = zero(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d zero Bytes copied to hardware at " + "once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 0, + bytes); + return; + } + while (rec->hw_ready && (rec->sw_ready > 0)) { + /* sw_to_end: max. number of bytes that can be read/take from + * the current position (sw_data) in _one_ step + */ + unsigned int sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: number of bytes we have available (for reading) */ + unsigned int bytes = rec->sw_ready; + + if (sw_to_end < bytes) + bytes = sw_to_end; + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from intermediate buffer position sw_data to the + * HW and return number of bytes actually written + * Furthermore, set hw_ready to 0, if the fifo isn't empty + * now => more could be transfered to fifo + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: %d Bytes copied to hardware at once " + "- too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually written bytes + * (= number of taken bytes from intermediate buffer) + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + /* now sw_data is the position where ALSA is going to write + * in the intermediate buffer next time = position we are going + * to read from next time + */ + + snd_pcm_indirect2_increase_min_periods(substream, rec, 1, 1, + bytes); + + /* we read bytes from intermediate buffer, so we need to say + * that the number of bytes ready for transfer are decreased + * now + */ + rec->sw_ready -= bytes; + } + return; +} + +/* + * helper function for playback interrupt routine + */ +void +snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware played some bytes, so there is room again (in fifo) */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_playback_transfer(substream, rec, copy, zero); + + if (rec->min_periods >= rec->min_multiple) { +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - too big " + "to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / + rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / + rec->min_multiple); + rec->mul_elapsed++; +#endif + rec->min_periods = 0; + snd_pcm_period_elapsed(substream); + } +} + +/* + * _internal_ helper function for capture interrupt callback + */ +static void +snd_pcm_indirect2_capture_transfer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + snd_pcm_uframes_t appl_ptr = runtime->control->appl_ptr; + snd_pcm_sframes_t diff = appl_ptr - rec->appl_ptr; + + if (diff) { +#ifdef SND_PCM_INDIRECT2_STAT + rec->lastdifftime = jiffies; +#endif + if (diff < -(snd_pcm_sframes_t) (runtime->boundary / 2)) + diff += runtime->boundary; + rec->sw_ready -= frames_to_bytes(runtime, diff); + rec->appl_ptr = appl_ptr; + } + /* if hardware has something, but the intermediate buffer is full + * => skip contents of buffer + */ + if (rec->hw_ready && (rec->sw_ready >= (int)rec->sw_buffer_size)) { + unsigned int bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstzerotime == 0) { + rec->firstzerotime = jiffies; + snd_printk(KERN_DEBUG "STAT: (capture) " + "@firstzerotime: mul_elapsed: %d, " + "min_period_count: %d\n", + rec->mul_elapsed, rec->min_period_count); + snd_printk(KERN_DEBUG "STAT: (capture) " + "@firstzerotime: sw_io: %d, sw_data: %d, " + "appl_ptr: %u\n", + rec->sw_io, rec->sw_data, + (unsigned int)appl_ptr); + } + if ((jiffies - rec->firstzerotime) < 3750) { + rec->zero_times[(jiffies - rec->firstzerotime)]++; + rec->zero_times_saved++; + } else + rec->zero_times_notsaved++; +#endif + bytes = null(substream, rec); + +#ifdef SND_PCM_INDIRECT2_STAT + rec->zeros2hw += bytes; + if (bytes < 64) + rec->zero_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d zero Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 0, + bytes); + /* report an overrun */ + rec->sw_io = SNDRV_PCM_POS_XRUN; + return; + } + while (rec->hw_ready && (rec->sw_ready < (int)rec->sw_buffer_size)) { + /* sw_to_end: max. number of bytes that we can write to the + * intermediate buffer (until it's end) + */ + size_t sw_to_end = rec->sw_buffer_size - rec->sw_data; + + /* bytes: max. number of bytes, which may be copied to the + * intermediate buffer without overflow (in _one_ step) + */ + size_t bytes = rec->sw_buffer_size - rec->sw_ready; + + /* limit number of bytes (for transfer) by available room in + * the intermediate buffer + */ + if (sw_to_end < bytes) + bytes = sw_to_end; + if (!bytes) + break; + +#ifdef SND_PCM_INDIRECT2_STAT + if (rec->firstbytetime == 0) + rec->firstbytetime = jiffies; + rec->lastbytetime = jiffies; +#endif + /* copy bytes from the intermediate buffer (position sw_data) + * to the HW at most and return number of bytes actually copied + * from HW + * Furthermore, set hw_ready to 0, if the fifo is empty now. + */ + bytes = copy(substream, rec, bytes); + rec->bytes2hw += bytes; + +#ifdef SND_PCM_INDIRECT2_STAT + if (bytes < 64) + rec->byte_sizes[bytes]++; + else + snd_printk(KERN_DEBUG + "STAT: (capture) %d Bytes copied to " + "hardware at once - too big to save!\n", + bytes); +#endif + /* increase sw_data by the number of actually copied bytes from + * HW + */ + rec->sw_data += bytes; + if (rec->sw_data == rec->sw_buffer_size) + rec->sw_data = 0; + + snd_pcm_indirect2_increase_min_periods(substream, rec, 0, 1, + bytes); + + /* number of bytes in the intermediate buffer, which haven't + * been fetched by ALSA yet. + */ + rec->sw_ready += bytes; + } + return; +} + +/* + * helper function for capture interrupt routine + */ +void +snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null) +{ +#ifdef SND_PCM_INDIRECT2_STAT + rec->irq_occured++; +#endif + /* hardware recorded some bytes, so there is something to read from the + * record fifo: + */ + rec->hw_ready = 1; + + /* don't call ack() now, instead call transfer() function directly + * (normally called by ack() ) + */ + snd_pcm_indirect2_capture_transfer(substream, rec, copy, null); + + if (rec->min_periods >= rec->min_multiple) { + +#ifdef SND_PCM_INDIRECT2_STAT + if ((rec->min_periods / rec->min_multiple) > 7) + snd_printk(KERN_DEBUG + "STAT: more than 7 (%d) mul_adds - " + "too big to save!\n", + (rec->min_periods / rec->min_multiple)); + else + rec->mul_adds[(rec->min_periods / + rec->min_multiple)]++; + rec->mul_elapsed_real += (rec->min_periods / + rec->min_multiple); + rec->mul_elapsed++; + + if (!(rec->mul_elapsed % 4)) { + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int appl_ptr = + frames_to_bytes(runtime, + (unsigned int)runtime->control-> + appl_ptr) % rec->sw_buffer_size; + int diff = rec->sw_data - appl_ptr; + if (diff < 0) + diff += rec->sw_buffer_size; + snd_printk(KERN_DEBUG + "STAT: mul_elapsed: %d, sw_data: %u, " + "appl_ptr (bytes): %u, diff: %d\n", + rec->mul_elapsed, rec->sw_data, appl_ptr, + diff); + } +#endif + rec->min_periods = 0; + snd_pcm_period_elapsed(substream); + } +} diff --git a/sound/drivers/pcm-indirect2.h b/sound/drivers/pcm-indirect2.h new file mode 100644 index 0000000..859cd7a --- /dev/null +++ b/sound/drivers/pcm-indirect2.h @@ -0,0 +1,140 @@ +/* + * Helper functions for indirect PCM data transfer to a simple FIFO in + * hardware (small, no possibility to read "hardware io position", + * updating position done by interrupt, ...) + * + * Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de + * + * Based on "pcm-indirect.h" (alsa-driver-1.0.13) by + * + * Copyright (c) by Takashi Iwai tiwai@suse.de + * Jaroslav Kysela perex@suse.cz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __SOUND_PCM_INDIRECT2_H +#define __SOUND_PCM_INDIRECT2_H + +/* struct snd_pcm_substream, struct snd_pcm_runtime, snd_pcm_uframes_t */ +#include <sound/pcm.h> + +/* Debug options for code which may be removed completely in a final version */ +#ifdef CONFIG_SND_DEBUG +#define SND_PCM_INDIRECT2_STAT /* turn on some "statistics" about the + * process of copying bytes from the + * intermediate buffer to the hardware + * fifo and the other way round + */ +#endif + +struct snd_pcm_indirect2 { + unsigned int hw_buffer_size; /* Byte size of hardware buffer */ + int hw_ready; /* playback: 1 = hw fifo has room left, + * 0 = hw fifo is full + */ + unsigned int min_multiple; + int min_periods; /* counts number of min. periods until + * min_multiple is reached + */ + int min_period_count; /* counts bytes to count number of + * min. periods + */ + + unsigned int sw_buffer_size; /* Byte size of software buffer */ + + /* sw_data: position in intermediate buffer, where we will read (or + * write) from/to next time (to transfer data to/from HW) + */ + unsigned int sw_data; /* Offset to next dst (or src) in sw + * ring buffer + */ + /* easiest case (playback): + * sw_data is nearly the same as ~ runtime->control->appl_ptr, with the + * exception that sw_data is "behind" by the number if bytes ALSA wrote + * to the intermediate buffer last time. + * A call to ack() callback synchronizes both indirectly. + */ + + /* We have no real sw_io pointer here. Usually sw_io is pointing to the + * current playback/capture position _inside_ the hardware. Devices + * with plain FIFOs often have no possibility to publish this position. + * So we say: if sw_data is updated, that means bytes were copied to + * the hardware, we increase sw_io by that amount, because there have + * to be as much bytes which were played. So sw_io will stay behind + * sw_data all the time and has to converge to sw_data at the end of + * playback. + */ + unsigned int sw_io; /* Current software pointer in bytes */ + + /* sw_ready: number of bytes ALSA copied to the intermediate buffer, so + * it represents the number of bytes which wait for transfer to the HW + */ + int sw_ready; /* Bytes ready to be transferred to/from hw */ + + /* appl_ptr: last known position of ALSA (where ALSA is going to write + * next time into the intermediate buffer + */ + snd_pcm_uframes_t appl_ptr; /* Last seen appl_ptr */ + + unsigned int bytes2hw; + int check_alignment; + +#ifdef SND_PCM_INDIRECT2_STAT + unsigned int zeros2hw; + unsigned int mul_elapsed; + unsigned int mul_elapsed_real; + unsigned long firstbytetime; + unsigned long lastbytetime; + unsigned long firstzerotime; + unsigned int byte_sizes[64]; + unsigned int zero_sizes[64]; + unsigned int min_adds[8]; + unsigned int mul_adds[8]; + unsigned int zero_times[3750]; /* = 15s */ + unsigned int zero_times_saved; + unsigned int zero_times_notsaved; + unsigned int irq_occured; + unsigned int pointer_calls; + unsigned int lastdifftime; +#endif +}; + +typedef size_t(*snd_pcm_indirect2_copy_t) (struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + size_t bytes); +typedef size_t(*snd_pcm_indirect2_zero_t) (struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); + +#ifdef SND_PCM_INDIRECT2_STAT +void snd_pcm_indirect2_stat(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); +#endif + +snd_pcm_uframes_t +snd_pcm_indirect2_pointer(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec); +void +snd_pcm_indirect2_playback_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t zero); +void +snd_pcm_indirect2_capture_interrupt(struct snd_pcm_substream *substream, + struct snd_pcm_indirect2 *rec, + snd_pcm_indirect2_copy_t copy, + snd_pcm_indirect2_zero_t null); + +#endif /* __SOUND_PCM_INDIRECT2_H */
Hi all,
below, you'll find a very small patch: In the current state, platform bus _never_ activates (= calls probe() callback on) the driver, because the driver's name has a dash and the device's name has an underscore. I made this horrible change _after_ I tested the final patch, which I posted on Saturday. Sorry.
From: Joachim Foerster JOFT@gmx.de
Names inside struct platform_device and struct platform_driver seem to need underscores instead of dashes.
(Patch for Linus' master branch, date 2007/08/08 _including_ my patches sent on Saturday)
Signed-off-by: Joachim Foerster JOFT@gmx.de --- arch/ppc/syslib/virtex_devices.c | 2 +- sound/drivers/ml403-ac97cr.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/ppc/syslib/virtex_devices.c b/arch/ppc/syslib/virtex_devices.c index d0b04d6..9fa10b1 100644 --- a/arch/ppc/syslib/virtex_devices.c +++ b/arch/ppc/syslib/virtex_devices.c @@ -122,7 +122,7 @@ }
#define XPAR_AC97_CONTROLLER_REFERENCE(num) { \ - .name = "ml403-ac97cr", \ + .name = "ml403_ac97cr", \ .id = num, \ .num_resources = 3, \ .resource = (struct resource[]) { \ diff --git a/sound/drivers/ml403-ac97cr.c b/sound/drivers/ml403-ac97cr.c index 2222315..2636249 100644 --- a/sound/drivers/ml403-ac97cr.c +++ b/sound/drivers/ml403-ac97cr.c @@ -66,7 +66,7 @@ #include "pcm-indirect2.h"
-#define SND_ML403_AC97CR_DRIVER "ml403-ac97cr" +#define SND_ML403_AC97CR_DRIVER "ml403_ac97cr"
MODULE_AUTHOR("Joachim Foerster JOFT@gmx.de"); MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference");
Hi Grant, Takashi, Stephen,
I tried to correct the issues here, too. But unfortunately Xilinx EDK uses such long #define macro names. I guess we have to leave it the way it is?
From: Joachim Foerster JOFT@gmx.de
(Patch for Linus' master branch, date 2007/08/08)
Signed-off-by: Joachim Foerster JOFT@gmx.de --- arch/ppc/syslib/virtex_devices.c | 28 ++++++++++++++++++++++++++++ 1 files changed, 28 insertions(+), 0 deletions(-)
diff --git a/arch/ppc/syslib/virtex_devices.c b/arch/ppc/syslib/virtex_devices.c index 62a9495..d0b04d6 100644 --- a/arch/ppc/syslib/virtex_devices.c +++ b/arch/ppc/syslib/virtex_devices.c @@ -121,6 +121,29 @@ }, \ }
+#define XPAR_AC97_CONTROLLER_REFERENCE(num) { \ + .name = "ml403-ac97cr", \ + .id = num, \ + .num_resources = 3, \ + .resource = (struct resource[]) { \ + { \ + .start = XPAR_OPB_AC97_CONTROLLER_REF_##num##_BASEADDR, \ + .end = XPAR_OPB_AC97_CONTROLLER_REF_##num##_HIGHADDR, \ + .flags = IORESOURCE_MEM, \ + }, \ + { \ + .start = XPAR_OPB_INTC_0_OPB_AC97_CONTROLLER_REF_##num##_PLAYBACK_INTERRUPT_INTR, \ + .end = XPAR_OPB_INTC_0_OPB_AC97_CONTROLLER_REF_##num##_PLAYBACK_INTERRUPT_INTR, \ + .flags = IORESOURCE_IRQ, \ + }, \ + { \ + .start = XPAR_OPB_INTC_0_OPB_AC97_CONTROLLER_REF_##num##_RECORD_INTERRUPT_INTR, \ + .end = XPAR_OPB_INTC_0_OPB_AC97_CONTROLLER_REF_##num##_RECORD_INTERRUPT_INTR, \ + .flags = IORESOURCE_IRQ, \ + }, \ + }, \ +} + /* UART 8250 driver platform data table */ struct plat_serial8250_port virtex_serial_platform_data[] = { #if defined(XPAR_UARTNS550_0_BASEADDR) @@ -221,6 +244,11 @@ struct platform_device virtex_platform_devices[] = { #if defined(XPAR_TFT_3_BASEADDR) XPAR_TFT(3), #endif + + /* AC97 Controller Reference instances */ +#if defined(XPAR_OPB_AC97_CONTROLLER_REF_0_BASEADDR) + XPAR_AC97_CONTROLLER_REFERENCE(0), +#endif };
/* Early serial support functions */
On 8/9/07, Joachim Förster mls.JOFT@gmx.de wrote:
From: Joachim Foerster JOFT@gmx.de
Add ALSA support for the opb_ac97_controller_ref_v1_00_a ip core found in Xilinx' ML403 reference design.
Known issue: Currently this driver hits a WARN_ON_ONCE(1) statement in kernel/irq/resend.c (line 70). According to Linus (http://lkml.org/lkml/2007/8/5/5) this may be ignored, right? I haven't had a look into this "problem" yet.
(Patch for Linus' master branch, date 2007/08/08)
This patchset _will_ be published on http://www.esic-solutions.com/support.html soon (like the first version of the driver (tar file), but this may take some days ...).
Comments below
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index eacee2d..827f2f5 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile
Couldn't this end up on MicroBlaze too? If so, sound/ppc is the wrong place.
@@ -4,7 +4,9 @@ #
snd-powermac-objs := powermac.o pmac.o awacs.o burgundy.o daca.o tumbler.o keywest.o beep.o +snd-ml403_ac97cr-objs := ml403_ac97cr.o
This line is only needed if you're compiling multiple .c files into one .ko
# Toplevel Module Dependency obj-$(CONFIG_SND_POWERMAC) += snd-powermac.o obj-$(CONFIG_SND_PS3) += snd_ps3.o +obj-$(CONFIG_SND_ML403_AC97CR) += snd-ml403_ac97cr.o diff --git a/sound/ppc/ml403_ac97cr.c b/sound/ppc/ml403_ac97cr.c new file mode 100644 index 0000000..99791d7 --- /dev/null +++ b/sound/ppc/ml403_ac97cr.c @@ -0,0 +1,1274 @@
+/* ALSA driver for Xilinx ML403 AC97 Controller Reference
- IP: opb_ac97_controller_ref_v1_00_a (EDK 8.1i)
- IP: opb_ac97_controller_ref_v1_00_a (EDK 9.1i)
- Copyright (c) by 2007 Joachim Foerster JOFT@gmx.de
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
+#include <sound/driver.h> +#include <linux/init.h> +#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h> +#include <asm/io.h> +#include <linux/interrupt.h>
+/* HZ */ +#include <linux/param.h> +/* jiffies, time_*() */ +#include <linux/jiffies.h> +/* schedule_timeout*() */ +#include <linux/sched.h> +/* spin_lock*() */ +#include <linux/spinlock.h>
+/* snd_printk(), snd_printd() */ +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/ac97_codec.h>
+#define SND_ML403_AC97CR_DRIVER "ml403_ac97cr"
+MODULE_AUTHOR("Joachim Foerster JOFT@gmx.de"); +MODULE_DESCRIPTION("Xilinx ML403 AC97 Controller Reference"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Xilinx,ML403 AC97 Controller Reference}}"); +MODULE_VERSION("0.0.1-pre2");
This patch is targeted for mainline inclusion (which gives you the kernel version number). Do you really want to maintain a separate version number that needs to be update manually? I'd drop the MODULE_VERSION line.
<snip>
I'm not an ALSA expert, so I didn't review the rest of the code in detail (but I didn't see anything to comment on with a quick review). I'll try adding your patches to my tree this afternoon.
g.
Hi Grant,
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index eacee2d..827f2f5 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile
Couldn't this end up on MicroBlaze too? If so, sound/ppc is the wrong place.
Well, I don't know. The only arch depended calls I use (am aware of) are in/out_be32(), so this _might_ work for mb, too. (I won't be able to test that in the next few months ...)
On Thu, 2007-08-09 at 11:17 -0700, Stephen Neuendorffer wrote:
I second grant here... this should eventually work on microblaze, too, so putting it drivers/ and using XILINX_DRIVERS instead of XILINX_VIRTEX (based on the patch Wolfgang recently posted) would probably be preferable.
+config SND_ML403_AC97CR
tristate "Xilinx ML403 AC97 Controller Reference"
depends on SND && XILINX_VIRTEX
Steve
So, like Stephen Neuendorffer said, I should move it to sound/drivers, right? Takashi?
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
+snd-ml403_ac97cr-objs := ml403_ac97cr.o
This line is only needed if you're compiling multiple .c files into one .ko
Ah, well, then I have to rename the main .c file. But all the other ALSA drivers seem to _not_ include the "snd-" in front of it (ok, except the new PS3 driver and maybe other new ones?)
I'll try adding your patches to my tree this afternoon.
I made patch against your tree, too (like a said before). Tell me, then I post it ...
Joachim
On 8/9/07, Joachim Förster mls.JOFT@gmx.de wrote:
Hi Grant,
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index eacee2d..827f2f5 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile
Couldn't this end up on MicroBlaze too? If so, sound/ppc is the wrong place.
Well, I don't know. The only arch depended calls I use (am aware of) are in/out_be32(), so this _might_ work for mb, too. (I won't be able to test that in the next few months ...)
On Thu, 2007-08-09 at 11:17 -0700, Stephen Neuendorffer wrote:
I second grant here... this should eventually work on microblaze, too, so putting it drivers/ and using XILINX_DRIVERS instead of XILINX_VIRTEX (based on the patch Wolfgang recently posted) would probably be preferable.
+config SND_ML403_AC97CR
tristate "Xilinx ML403 AC97 Controller Reference"
depends on SND && XILINX_VIRTEX
Steve
So, like Stephen Neuendorffer said, I should move it to sound/drivers, right? Takashi?
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
+snd-ml403_ac97cr-objs := ml403_ac97cr.o
This line is only needed if you're compiling multiple .c files into one .ko
Ah, well, then I have to rename the main .c file. But all the other ALSA drivers seem to _not_ include the "snd-" in front of it (ok, except the new PS3 driver and maybe other new ones?)
I'll try adding your patches to my tree this afternoon.
I made patch against your tree, too (like a said before). Tell me, then I post it ...
No, don't base against my tree. You worry about mainline; I'll worry about my tree. :-)
My tree gets rebased periodically anyway, so it's a much harder target.
Joachim
At Thu, 09 Aug 2007 22:01:40 +0200, Joachim Förster wrote:
Hi Grant,
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
diff --git a/sound/ppc/Makefile b/sound/ppc/Makefile index eacee2d..827f2f5 100644 --- a/sound/ppc/Makefile +++ b/sound/ppc/Makefile
Couldn't this end up on MicroBlaze too? If so, sound/ppc is the wrong place.
Well, I don't know. The only arch depended calls I use (am aware of) are in/out_be32(), so this _might_ work for mb, too. (I won't be able to test that in the next few months ...)
On Thu, 2007-08-09 at 11:17 -0700, Stephen Neuendorffer wrote:
I second grant here... this should eventually work on microblaze, too, so putting it drivers/ and using XILINX_DRIVERS instead of XILINX_VIRTEX (based on the patch Wolfgang recently posted) would probably be preferable.
+config SND_ML403_AC97CR
tristate "Xilinx ML403 AC97 Controller Reference"
depends on SND && XILINX_VIRTEX
Steve
So, like Stephen Neuendorffer said, I should move it to sound/drivers, right? Takashi?
Well, yes, drivers appears like a more reasonable place.
On Thu, 2007-08-09 at 11:49 -0600, Grant Likely wrote:
+snd-ml403_ac97cr-objs := ml403_ac97cr.o
This line is only needed if you're compiling multiple .c files into one .ko
Ah, well, then I have to rename the main .c file. But all the other ALSA drivers seem to _not_ include the "snd-" in front of it (ok, except the new PS3 driver and maybe other new ones?)
Right. I prefer the file name should be without prefix, too. The snd- prefix came from a historical reason, to avoid the name crash with OSS drivers.
Takashi
I second grant here... this should eventually work on microblaze, too, so putting it drivers/ and using XILINX_DRIVERS instead of XILINX_VIRTEX (based on the patch Wolfgang recently posted) would probably be preferable.
+config SND_ML403_AC97CR
tristate "Xilinx ML403 AC97 Controller Reference"
depends on SND && XILINX_VIRTEX
Steve
participants (5)
-
Grant Likely
-
Joachim Foerster
-
Joachim Förster
-
Stephen Neuendorffer
-
Takashi Iwai