[alsa-devel] [PATCH 2/3] saa7146: Emagic Audiowerk8 low-level ALSA driver
Matthias Nyffenegger
matthias.nyffenegger at bluewin.ch
Thu Oct 23 00:04:59 CEST 2008
From: Matthias Nyffenegger <matthias.nyffenegger at bluewin.ch>
Low-level ALSA driver for Emagic Audiowerk8 sound card.
Project page: http://sourceforge.net/projects/aw8-alsa
Built and tested with Vanilla 2.6.25.16
Signed-off-by: Matthias Nyffenegger <matthias.nyffenegger at bluewin.ch>
---
This is a request for submission to ALSA-tree.
diff -uprN ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/README.txt
../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/README.txt
--- ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/README.txt 1970-01-01 01:00:00.000000000 +0100
+++ ../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/README.txt 2008-10-22 23:34:05.000000000 +0200
@@ -0,0 +1 @@
+See sourceforge.net project "Audiowerk8 ALSA driver" (aw8-alsa) Wiki for more information.
diff -uprN ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/Makefile
../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/Makefile
--- ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/Makefile 1970-01-01 01:00:00.000000000 +0100
+++ ../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/Makefile 2008-10-22 23:34:05.000000000 +0200
@@ -0,0 +1,8 @@
+# Makefile for Audiowerk module.
+# run command in source directory:
+# make -C /usr/src/linux M=`pwd` modules modules_install
+
+EXTRA_CFLAGS := -DSAA7146_SUBSYS_LOG_TAG=\"AW8\"
+obj-m := snd-aw8.o
+snd-aw8-objs := saa7146i2c.o saa7146i2s.o saa7146audio.o aw8.o aw8alsa.o
+
diff -uprN ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8alsa.c
../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8alsa.c
--- ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8alsa.c 1970-01-01 01:00:00.000000000 +0100
+++ ../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8alsa.c 2008-10-22 23:34:05.000000000 +0200
@@ -0,0 +1,826 @@
+/*
+ * Emagic Audiowerk ALSA driver
+ * Copyright (c) 2006- by M. Nyffenegger <matthias.nyffenegger[AT]bluewin.ch>
+ *
+ *
+ * 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 <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "log.h"
+#include "saa7146audio.h"
+#include "aw8.h"
+
+/* From Takashi Iwai's "Writing an ALSA Driver" developer guide */
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+/*
+ * Module info
+ */
+MODULE_AUTHOR("Matthias Nyffenegger matthias.nyffenegger[AT]bluewin.ch");
+MODULE_DESCRIPTION(AW_MODULE_DESCRIPTION);
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0:0.2");
+
+/**
+ * Module parameter 'input_type': select capture input digital or analog
+ * (default)
+ */
+int input_type;
+module_param(input_type, int, 0444);
+MODULE_PARM_DESC(input_type, "0=analog (default), 1=digital");
+
+#define PCM_0 0
+
+/**
+ * The ALSA 'chip' data structure.
+ */
+struct awalsa_t {
+ /* the alsa related stuff */
+ struct snd_card *card;
+ struct snd_pcm *pcm_0;
+ struct snd_pcm_substream *pcm_in_substreams[MAX_IN_AUDIO_STREAMS];
+ struct snd_pcm_substream *pcm_out_substreams[MAX_OUT_AUDIO_STREAMS];
+ /* the SAA7146A stuff */
+ struct saa7146audio chipaudio;
+ struct audio_stream *in_streams[MAX_IN_AUDIO_STREAMS];
+ struct audio_stream *out_streams[MAX_OUT_AUDIO_STREAMS];
+ /*/ the PCI stuff */
+ struct pci_dev *pci;
+ int irq;
+ /* other useful things */
+ spinlock_t isr_lock;
+};
+
+static int snd_aw_substream_open(struct snd_pcm_substream *substream, int in);
+static int snd_aw_substream_close(struct snd_pcm_substream *substream, int in);
+static int snd_aw_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+static int snd_aw_pcm_hw_free(struct snd_pcm_substream *substream);
+static int snd_aw_pcm_prepare_substream(struct snd_pcm_substream *substream,
+ int in);
+static int snd_aw_pcm_trigger_substream(struct snd_pcm_substream *substream,
+ int cmd, int in);
+static snd_pcm_uframes_t snd_aw_pcm_pointer_substream(
+ struct snd_pcm_substream *substream,
+ int in);
+
+/**
+ *
+ * ALSA capture stream definitions and callbacks
+ *
+ */
+static struct snd_pcm_hardware snd_aw_capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S32_BE),
+ .rates = AW_PCM_RATE,
+ .rate_min = AW_FS_MIN,
+ .rate_max = AW_FS_MAX,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 16384,
+ .period_bytes_min = 64, /* HW restriction, see SAA7146A spec p.24 */
+ .period_bytes_max = 8192,
+ .periods_min = 2,
+ .periods_max = 2,
+};
+
+/**
+ * ALSA capture stream open callback.
+ */
+static int snd_aw_capture_open(struct snd_pcm_substream *substream)
+{
+ return snd_aw_substream_open(substream, 1);
+}
+
+/**
+ * ALSA capture stream close callback.
+ */
+static int snd_aw_capture_close(struct snd_pcm_substream *substream)
+{
+ return snd_aw_substream_close(substream, 1);
+}
+
+/**
+ * Module scoped helper: converts alsa to Audiowerk capture sample format.
+ */
+static enum aw_formats alsa_2_aw_capture_format(snd_pcm_format_t fmt)
+{
+ switch (fmt) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ return fmt_s16_2le;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ return fmt_s16_2be;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ return fmt_s18_4le;
+ case SNDRV_PCM_FORMAT_S32_BE:
+ return fmt_s18_4be;
+ default:
+ LOG_ERROR("Unsupported capture format %d", fmt);
+ return fmt_s16_2le;
+ }
+}
+
+/**
+ * ALSA capture stream prepare callback.
+ * Be careful that this callback will be called many times at each set up.
+ */
+static int snd_aw_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+ return snd_aw_pcm_prepare_substream(substream, 1);
+}
+
+/**
+ * ALSA capture stream trigger callback. [atomic].
+ */
+static int snd_aw_pcm_trigger_capture(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ return snd_aw_pcm_trigger_substream(substream, cmd, 1);
+}
+
+/**
+ * ALSA capture stream hw-buffer pointer callback. [atomic]
+ */
+static snd_pcm_uframes_t snd_aw_pcm_pointer_capture(
+ struct snd_pcm_substream *substream)
+{
+ return snd_aw_pcm_pointer_substream(substream, 1);
+}
+
+/**
+ * ALSA capture stream callback operations.
+ */
+static struct snd_pcm_ops snd_aw_capture_ops = {
+ .open = snd_aw_capture_open,
+ .close = snd_aw_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_aw_pcm_hw_params,
+ .hw_free = snd_aw_pcm_hw_free,
+ .prepare = snd_aw_pcm_prepare_capture,
+ .trigger = snd_aw_pcm_trigger_capture,
+ .pointer = snd_aw_pcm_pointer_capture,
+};
+
+/**
+ *
+ * ALSA playback stream definitions and callbacks
+ *
+ */
+static struct snd_pcm_hardware snd_aw_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S16_BE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_S32_BE),
+ .rates = AW_PCM_RATE,
+ .rate_min = AW_FS_MIN,
+ .rate_max = AW_FS_MAX,
+ .channels_min = 2,
+ .channels_max = AW_MAX_PLAYBACK_CHANNELS_PER_STREAM,
+ .buffer_bytes_max = 16384,
+ .period_bytes_min = 64, /* HW restriction, see SAA7146A spec p.24 */
+ .period_bytes_max = 8192,
+ .periods_min = 2,
+ .periods_max = 2,
+};
+
+/**
+ * ALSA playback stream open callback.
+ */
+static int snd_aw_playback_open(struct snd_pcm_substream *substream)
+{
+ return snd_aw_substream_open(substream, 0);
+}
+
+/**
+ * ALSA playback stream close callback.
+ */
+static int snd_aw_playback_close(struct snd_pcm_substream *substream)
+{
+ return snd_aw_substream_close(substream, 0);
+}
+
+/**
+ * Module scoped helper: converts alsa to Audiowerk playback sample format.
+ */
+static enum aw_formats alsa_2_aw_playback_format(snd_pcm_format_t fmt)
+{
+ switch (fmt) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ return fmt_s16_2le;
+ case SNDRV_PCM_FORMAT_S16_BE:
+ return fmt_s16_2be;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ return fmt_s20_4le;
+ case SNDRV_PCM_FORMAT_S32_BE:
+ return fmt_s20_4be;
+ default:
+ LOG_ERROR("Unsupported playback format %d", fmt);
+ return fmt_s16_2le;
+ }
+}
+
+/**
+ * ALSA playback stream prepare callback.
+ * Be careful that this callback will be called many times at each set up.
+ */
+static int snd_aw_pcm_prepare_playback(struct snd_pcm_substream *substream)
+{
+ return snd_aw_pcm_prepare_substream(substream, 0);
+}
+
+/**
+ * ALSA playback stream trigger callback. [atomic].
+ */
+static int snd_aw_pcm_trigger_playback(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ return snd_aw_pcm_trigger_substream(substream, cmd, 0);
+}
+
+/**
+ * ALSA playback stream hw-buffer pointer callback. [atomic].
+ */
+static snd_pcm_uframes_t snd_aw_pcm_pointer_playback(
+ struct snd_pcm_substream *substream)
+{
+ return snd_aw_pcm_pointer_substream(substream, 0);
+}
+
+/**
+ * ALSA playback stream callback operations.
+ */
+static struct snd_pcm_ops snd_aw_playback_ops = {
+ .open = snd_aw_playback_open,
+ .close = snd_aw_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_aw_pcm_hw_params,
+ .hw_free = snd_aw_pcm_hw_free,
+ .prepare = snd_aw_pcm_prepare_playback,
+ .trigger = snd_aw_pcm_trigger_playback,
+ .pointer = snd_aw_pcm_pointer_playback,
+};
+
+static int snd_aw_substream_open(struct snd_pcm_substream *substream, int in)
+{
+ struct awalsa_t *awalsa = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ int device = substream->pcm->device;
+ struct snd_pcm_substream **alsa_pcm_substreams = NULL;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ alsa_pcm_substreams =
+ (in ? awalsa->pcm_in_substreams : awalsa->pcm_out_substreams);
+ if (alsa_pcm_substreams[substream->number] != NULL) {
+ LOG_WARN("%s substream %d already open",
+ (in ? "Capture" : "Playback"), substream->number);
+ return -EAGAIN;
+ }
+ /* Remember substream for interrupt handler where we are given the chip
+ instance only. */
+ alsa_pcm_substreams[substream->number] = substream;
+ runtime->hw = (in ? snd_aw_capture_hw : snd_aw_playback_hw);
+ return 0;
+}
+
+static int snd_aw_substream_close(struct snd_pcm_substream *substream, int in)
+{
+ struct awalsa_t *awalsa = snd_pcm_substream_chip(substream);
+ int device = substream->pcm->device;
+ struct snd_pcm_substream **alsa_pcm_substreams = NULL;
+ struct audio_stream **saa7146_streams = NULL;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ alsa_pcm_substreams =
+ (in ? awalsa->pcm_in_substreams : awalsa->pcm_out_substreams);
+ saa7146_streams = (in ? awalsa->in_streams : awalsa->out_streams);
+ alsa_pcm_substreams[substream->number] = NULL;
+ if (saa7146_streams[substream->number] != NULL) {
+ saa7146_stream_unprepare(&awalsa->chipaudio,
+ saa7146_streams[substream->number]);
+ }
+ return 0;
+}
+
+/**
+ * ALSA stream hw_params callback.
+ */
+static int snd_aw_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int err = 0;
+ int device = substream->pcm->device;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0) {
+ LOG_ERROR("snd_pcm_lib_malloc_pages ERROR=%d", err);
+ return err;
+ }
+ return 0;
+}
+
+/**
+ * ALSA stream hw_free callback.
+ */
+static int snd_aw_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ int err = 0;
+ int device = substream->pcm->device;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ err = snd_pcm_lib_free_pages(substream);
+ if (err < 0) {
+ LOG_ERROR("snd_pcm_lib_free_pages ERROR=%d", err);
+ return err;
+ }
+ return 0;
+}
+
+static int snd_aw_pcm_prepare_substream(struct snd_pcm_substream *substream,
+ int in)
+{
+ struct awalsa_t *awalsa = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct audio_stream **saa7146_streams = NULL;
+ struct audio_stream *stream = NULL;
+ int device = substream->pcm->device;
+ enum aw_formats fmt = fmt_s16_2le;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ fmt = (in ? alsa_2_aw_capture_format(runtime->format) :
+ alsa_2_aw_playback_format(runtime->format));
+ saa7146_streams = (in ? awalsa->in_streams : awalsa->out_streams);
+ /* as this function may be called many times we must clean up first */
+ if (saa7146_streams[substream->number] != NULL) {
+ saa7146_stream_unprepare(&awalsa->chipaudio,
+ saa7146_streams[substream->number]);
+ }
+ /* try to prepare a new stream with the given parameters */
+ if (in) {
+ stream = saa7146_stream_prepare_capture(&awalsa->chipaudio,
+ substream->number,
+ runtime->dma_addr, /* stream buffer phys. base addr. */
+ 2 * snd_pcm_lib_period_bytes(substream), /* buf size */
+ runtime->channels,
+ ((fmt == fmt_s16_2le || fmt == fmt_s16_2be) ? 2 : 4),
+ ((fmt == fmt_s16_2le || fmt == fmt_s18_4le) ? le : be));
+ } else {
+ stream = saa7146_stream_prepare_playback(&awalsa->chipaudio,
+ substream->number,
+ runtime->dma_addr, /* stream buffer phys. base addr */
+ 2 * snd_pcm_lib_period_bytes(substream), /* buf size */
+ runtime->channels,
+ ((fmt == fmt_s16_2le || fmt == fmt_s16_2be) ? 2 : 4),
+ ((fmt == fmt_s16_2le || fmt == fmt_s20_4le) ? le : be));
+ }
+ if (stream == NULL)
+ return -EINVAL;
+ saa7146_streams[substream->number] = stream;
+ if (aw8_set_samplerate(&awalsa->chipaudio.chipi2s.chip, runtime->rate)
+ != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int snd_aw_pcm_trigger_substream(struct snd_pcm_substream *substream,
+ int cmd, int in)
+{
+ struct awalsa_t *awalsa = snd_pcm_substream_chip(substream);
+ int device = substream->pcm->device;
+ struct audio_stream **saa7146_streams = NULL;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ saa7146_streams = (in ? awalsa->in_streams : awalsa->out_streams);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ saa7146_stream_start(&awalsa->chipaudio,
+ saa7146_streams[substream->number]);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ saa7146_stream_stop(&awalsa->chipaudio,
+ saa7146_streams[substream->number]);
+ break;
+ default:
+ LOG_ERROR("Unknown trigger command=%d", cmd);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static snd_pcm_uframes_t snd_aw_pcm_pointer_substream(
+ struct snd_pcm_substream *substream,
+ int in)
+{
+ struct awalsa_t *awalsa = snd_pcm_substream_chip(substream);
+ struct audio_stream **saa7146_streams = NULL;
+ unsigned long hw_ptr = 0;
+ snd_pcm_uframes_t frames;
+ int device = substream->pcm->device;
+
+ if (device != PCM_0) {
+ LOG_ERROR("Invalid device index=%d", device);
+ return -EINVAL;
+ }
+ saa7146_streams = (in ? awalsa->in_streams : awalsa->out_streams);
+ hw_ptr = saa7146_stream_get_hw_pointer(&awalsa->chipaudio,
+ saa7146_streams[substream->number]);
+ frames = bytes_to_frames(substream->runtime,
+ hw_ptr - (size_t)(substream->runtime->dma_addr));
+ return frames;
+}
+
+/**
+ *
+ * ALSA device initialization
+ *
+ */
+
+/**
+ * ALSA Setup 1 PCM device:
+ * AW8 substreams: 1 capture /2 playback (1 analog/digital, 1 analog)
+ * AW2 substreams: 1 capture /2 playback (1 analog, 1 digital)
+ */
+static int __devinit snd_aw_new_pcm(struct awalsa_t *awalsa)
+{
+ struct snd_pcm *pcm;
+ int err;
+ int pages = 0;
+
+ /* create the pcm device */
+ err = snd_pcm_new(awalsa->card, "SAA7146A", /* device ID string */
+ PCM_0, /* device index */
+ 2, /* playback substreams AW2/8 */
+ 1, /* capture substreams AW2/8 */
+ &pcm);
+ if (err != 0) {
+ LOG_ERROR("snd_pcm_new ERROR=%d", err);
+ return err;
+ }
+ pcm->private_data = awalsa;
+ strcpy(pcm->name, "Multichannel Capture/Playback");
+ awalsa->pcm_0 = pcm;
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_aw_playback_ops);
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_aw_capture_ops);
+ LOG_INFO("PCM device [Multichannel Capture/Playback]"
+ " successfully created");
+ /* Pre-allocation 128k of dma buffer */
+ pages = 1 << ((17 - PAGE_SHIFT > 0) ? 17 - PAGE_SHIFT : 0);
+ err = snd_pcm_lib_preallocate_pages_for_all(pcm,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data(awalsa->pci),
+ pages * PAGE_SIZE,
+ pages * PAGE_SIZE);
+ if (err != 0) {
+ LOG_ERROR("snd_pcm_lib_preallocate_pages_for_all"
+ " ERROR=%d", err);
+ return err;
+ }
+ return 0;
+}
+
+/**
+ * ALSA Interrupt Service Routine.
+ */
+static irqreturn_t snd_aw_interrupt(int irq, void *dev_id)
+{
+ struct awalsa_t *awalsa = dev_id;
+ uint32_t isr = 0;
+
+ /* lock isr - block other interrupts */
+ spin_lock(awalsa->isr_lock);
+ isr = saa7146_read(&awalsa->chipaudio.chipi2s.chip, ISR);
+ /* was it one of SAA7146A (remember we might share irq line ..) */
+ if (!isr) {
+ spin_unlock(awalsa->isr_lock);
+ return IRQ_NONE;
+ }
+ /* clear all interrupts */
+ saa7146_write(&awalsa->chipaudio.chipi2s.chip, ISR, isr);
+ if (isr & A1_in && awalsa->pcm_in_substreams[0] != NULL)
+ snd_pcm_period_elapsed(awalsa->pcm_in_substreams[0]);
+ if (isr & A1_out && awalsa->pcm_out_substreams[0] != NULL)
+ snd_pcm_period_elapsed(awalsa->pcm_out_substreams[0]);
+ if (isr & A2_out && awalsa->pcm_out_substreams[1] != NULL)
+ snd_pcm_period_elapsed(awalsa->pcm_out_substreams[1]);
+ spin_unlock(awalsa->isr_lock);
+ return IRQ_HANDLED;
+}
+
+/**
+ * ALSA chip-specific destructor.
+ * (see "PCI Resource Managements")
+ */
+static int snd_aw_free(struct awalsa_t *awalsa)
+{
+ struct saa7146i2s *chipi2s = &awalsa->chipaudio.chipi2s;
+
+ /* disable hardware here if any */
+ aw_exit(chipi2s);
+ if (awalsa->irq >= 0)
+ free_irq(awalsa->irq, (void *)awalsa);
+ if (chipi2s->chip.iobase_virt)
+ iounmap(chipi2s->chip.iobase_virt);
+ pci_release_regions(awalsa->pci);
+ pci_disable_device(awalsa->pci);
+ kfree(awalsa);
+ return 0;
+}
+
+/**
+ * ALSA component-destructor.
+ * (see "Management of Cards and Components")
+ */
+static int snd_aw_dev_free(struct snd_device *device)
+{
+ snd_aw_free(device->device_data);
+ return 0;
+}
+
+/**
+ * ALSA chip-specific constructor.
+ * (see "Management of Cards and Components")
+ */
+static int __devinit snd_aw_create(struct snd_card *card,
+ struct pci_dev *pci,
+ struct awalsa_t **rchip)
+{
+ static struct snd_device_ops ops = {
+ .dev_free = snd_aw_dev_free,
+ };
+ int err;
+ struct awalsa_t *awalsa;
+ *rchip = NULL;
+
+ /* Initialize the PCI entry: This function switches the device 'on'.
+ In some cases, IRQ and I/O range is assigned. */
+ err = pci_enable_device(pci);
+ if (err != 0) {
+ LOG_ERROR("pci_enable_device ERROR=%d", err);
+ return err;
+ }
+ pci_set_master(pci);
+ err = pci_set_dma_mask(pci, DMA_32BIT_MASK);
+ if (err != 0) {
+ LOG_ERROR("pci_set_dma_mask ERROR=%d", err);
+ goto snd_aw_create_exit0;
+ }
+ err = pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK);
+ if (err != 0) {
+ LOG_ERROR("pci_set_consistent_dma_mask ERROR=%d", err);
+ goto snd_aw_create_exit0;
+ }
+ awalsa = kzalloc(sizeof(*awalsa), GFP_KERNEL);
+ if (awalsa == NULL) {
+ LOG_ERROR("Could not allocate memory");
+ err = -ENOMEM;
+ goto snd_aw_create_exit0;
+ }
+ awalsa->card = card;
+ awalsa->pci = pci;
+ awalsa->irq = -1;
+ spin_lock_init(&awalsa->isr_lock);
+ /* (1) PCI resource allocation */
+ err = pci_request_regions(pci, "SAA7146A");
+ if (err != 0) {
+ LOG_ERROR("pci_request_regions ERROR=%d", err);
+ goto snd_aw_create_exit1;
+ }
+ /* Get the device's registers physical base address. The device register
+ space is not available for access by the driver now; it has to be
+ mapped to a virtual memory space first. */
+ awalsa->chipaudio.chipi2s.chip.iobase_phys = pci_resource_start(pci, 0);
+ awalsa->chipaudio.chipi2s.chip.iolen = pci_resource_len(pci, 0);
+ /* Map device registers to virtual memory. 'nocache' means that we don't
+ want the device's registers to be read-cached by the CPU (e.g. as a
+ result of compiler optimizations). This is important since the CPU
+ does not necessarily know when a register value has changed (unless
+ this was by a write action of the driver). */
+ awalsa->chipaudio.chipi2s.chip.iobase_virt =
+ ioremap_nocache(awalsa->chipaudio.chipi2s.chip.iobase_phys,
+ awalsa->chipaudio.chipi2s.chip.iolen);
+ err = request_irq(pci->irq, snd_aw_interrupt, IRQF_DISABLED|IRQF_SHARED,
+ "SAA7146A", (void *)awalsa);
+ if (err != 0) {
+ LOG_ERROR("Cannot grab irq %d, ERROR=%d", pci->irq, err);
+ err = -EBUSY;
+ goto snd_aw_create_exit2;
+ }
+ awalsa->irq = pci->irq;
+ /* (2) initialization of the chip hardware */
+ if (aw_init(&awalsa->chipaudio.chipi2s, input_type) != 0)
+ goto snd_aw_create_exit2;
+ /* (3) create alsa device */
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, awalsa, &ops);
+ if (err < 0) {
+ LOG_ERROR("snd_device_new ERROR=%d", err);
+ goto snd_aw_create_exit2;
+ }
+ snd_card_set_dev(card, &pci->dev);
+ *rchip = awalsa;
+ return 0;
+snd_aw_create_exit2:
+ snd_aw_free(awalsa);
+ return err;
+snd_aw_create_exit1:
+ kfree(awalsa);
+snd_aw_create_exit0:
+ pci_disable_device(pci);
+ return err;
+}
+
+/**
+ * ALSA device constructor -- see "Constructor" sub-section in Takashi Iwai's
+ * "Writing an ALSA Driver" developer guide.
+ */
+static int __devinit snd_aw_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ int err;
+ struct snd_card *card;
+ struct awalsa_t *awalsa;
+
+ /* (1) */
+ if (dev >= SNDRV_CARDS) {
+ LOG_ERROR("No device available ERROR=%d", -ENODEV);
+ return -ENODEV;
+ }
+ if (!enable[dev]) {
+ dev++;
+ LOG_ERROR("No device entry ERROR=%d", -ENOENT);
+ return -ENOENT;
+ }
+ /* (2) */
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+ if (card == NULL) {
+ LOG_ERROR("snd_card_new ERROR=%d", -ENOMEM);
+ return -ENOMEM;
+ }
+ /* (3) */
+ err = snd_aw_create(card, pci, &awalsa);
+ if (err < 0) {
+ LOG_ERROR("snd_aw_create ERROR=%d", err);
+ goto snd_aw_probe_exit0;
+ }
+ /* (4) */
+ strcpy(card->driver, "SAA7146A");
+ strcpy(card->shortname, AW_CARD_SHORT_NAME);
+ sprintf(card->longname,
+ "%s at 0x%lx [%i] irq %i",
+ card->shortname,
+ awalsa->chipaudio.chipi2s.chip.iobase_phys,
+ awalsa->chipaudio.chipi2s.chip.iolen,
+ awalsa->irq);
+ LOG_INFO("card-info[%s]", card->longname);
+ /* (5) */
+ err = snd_aw_new_pcm(awalsa);
+ if (err != 0) {
+ LOG_ERROR("snd_aw_new_pcm ERROR=%d", err);
+ goto snd_aw_probe_exit0;
+ }
+ /* (6) */
+ err = snd_card_register(card);
+ if (err < 0) {
+ LOG_ERROR("snd_card_register ERROR=%d", err);
+ goto snd_aw_probe_exit0;
+ }
+ /* (7) */
+ pci_set_drvdata(pci, card);
+ dev++;
+ return 0;
+snd_aw_probe_exit0:
+ snd_card_free(card);
+ return err;
+}
+
+/**
+ * ALSA device destructor -- see "Destructor" sub-section
+ */
+static void __devexit snd_aw_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+ pci_set_drvdata(pci, NULL);
+}
+
+/**
+ *
+ * PCI specific stuff
+ *
+ */
+
+/**
+ * Organisation of EEPROM of the SAA7146A:
+ * The data of the subsystem ID and the subsystem vendor ID is organized in
+ * the EEPROM in the following order:
+ *
+ * EE-Addr Value Configuration Space Addr.
+ * ---------------------------------------------------------------------
+ * 00 Subsys ID (high byte) 2C, bits: 31 - 24
+ * 01 Subsys ID (low byte) 2C, bits: 23 - 16
+ * 02 Subsys Vendor ID (high byte) 2C, bits: 15 - 8
+ * 03 Subsys Vendor ID (low byte) 2C, bits: 7 - 0
+ * 04 Max_Lat 3C, bits: 31 - 24
+ * 05 Min_Gnt 3C, bits: 23 - 16
+ *
+ * For AW8 Subsys ID and Subsys Vendor ID are 0xFF on my card.
+ * For AW2 Subsys ID and Subsys Vendor ID are 0x00 on my card.
+ */
+
+static struct pci_device_id snd_aw_ids[] = {
+ { PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },
+ { 0, }
+};
+
+MODULE_DEVICE_TABLE(pci, snd_aw_ids);
+
+/**
+ * pci_driver definition.
+ */
+static struct pci_driver driver = {
+ .name = AW_DRIVER_NAME,
+ .id_table = snd_aw_ids,
+ .probe = snd_aw_probe,
+ .remove = __devexit_p(snd_aw_remove),
+};
+
+/**
+ * initialization of the module.
+ */
+static int __init alsa_card_aw_init(void)
+{
+ int err;
+
+ err = pci_register_driver(&driver);
+ if (err != 0) {
+ LOG_ERROR("pci_register_driver ERROR=%d", err);
+ return err;
+ }
+ return 0;
+}
+
+/**
+ * clean up the module.
+ */
+static void __exit alsa_card_aw_exit(void)
+{
+ pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_aw_init)
+module_exit(alsa_card_aw_exit)
diff -uprN ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8.c
../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8.c
--- ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8.c 1970-01-01 01:00:00.000000000 +0100
+++ ../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8.c 2008-10-22 23:34:05.000000000 +0200
@@ -0,0 +1,185 @@
+/*
+ * Emagic Audiowerk specific functions
+ * Copyright (c) 2006- by M. Nyffenegger <matthias.nyffenegger[AT]bluewin.ch>
+ * Based on GPLed Emagic Audiowerk8 Windows driver provided by Martijn Sipkema.
+ *
+ * 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 <linux/types.h>
+#include <linux/sched.h>
+#include "log.h"
+#include "saa7146.h"
+#include "saa7146i2s.h"
+#include "saa7146i2c.h"
+#include "aw8.h"
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define SLEEP_DIGIN 100 /* wait [ms] after switching input type adc/daio */
+#define GPIO_0 GPIO_CTRL
+#define MASK_GPIO_HI 0x50
+#define MASK_GPIO_LO 0x40
+#define SLEEP_PLL 500 /* wait [ms] after setting new fs */
+#define PLL_DEVICE_ADDR 0x62 /* I2C device address of AW8 PLL chip */
+#define PLL_FREF 1000 /* reference frequency for PLL */
+
+/**
+ * TODO: Description
+ */
+enum aw_input_types {analog, digital};
+
+/* forward declarations */
+static int aw8_device_init(struct saa7146i2s *chipi2s);
+static void switch_input(struct saa7146reg *chip, enum aw_input_types type);
+
+/**
+ * see aw8.h
+ */
+int aw_init(struct saa7146i2s *chipi2s, int input_type)
+{
+ int err = 0;
+
+ saa7146_write(&chipi2s->chip, IER, 0); /* disable all interrupts */
+ saa7146_write(&chipi2s->chip, MC1, MRST_N<<16); /* reset SAA7146 */
+ /* setup PCI arbitration ctrl (see SAA7146 spec. page 31 for details):
+ o 8DW burst for all FIFOs
+ o 8DW (empty DWs in FIFO) threshold for OUT-FIFOs
+ o 8DW (valid DWs in FIFO) threshold for IN-FIFOs
+ 0x0e0e0e0e => xxx0 1110 xxx0 1110 xxx0 1110 xxx0 1110 */
+ saa7146_write(&chipi2s->chip, PCI_BT_A, 0x0e0e0e0e);
+ /* enable SAA7146 audio pins */
+ saa7146_write(&chipi2s->chip, MC1, (EAP<<16 | EAP));
+ /* setup audio clock source (from EMagic GPL driver) */
+ saa7146_write(&chipi2s->chip, ACON2, ((0UL<<27) | /* A1_CLKSRC: BCLK1 */
+ (1UL<<22) | /* A2_CLKSRC: BCLK1 */
+ (0UL<<21) | /* INVERT_BCLK1 */
+ (0UL<<20) | /* INVERT_BCLK2 */
+ (1UL<<19) | /* BCLK1_OEN: input */
+ (0UL<<18))); /* BCLK2_OEN: output */
+ /* init ACON1: WS1-4 to output active low */
+ chipi2s->acon1 = 0x0000cccc;
+ saa7146_write(&chipi2s->chip, ACON1, chipi2s->acon1);
+ if (input_type)
+ aw_switch_input_daio(&chipi2s->chip);
+ else
+ aw_switch_input_adc(&chipi2s->chip);
+ if (saa7146_i2s_init_audio_interface(chipi2s, a1, 4, 8) != 0
+ || saa7146_i2s_init_audio_interface(chipi2s, a2, 4, 8) != 0)
+ return -1;
+ err = aw8_device_init(chipi2s);
+ /* enable interrupts */
+ saa7146_write(&chipi2s->chip, IER, (A1_out | A2_out | A1_in));
+ return err;
+}
+
+/**
+ * see aw8.h
+ */
+void aw_exit(struct saa7146i2s *chipi2s)
+{
+ saa7146_write(&chipi2s->chip, IER, 0); /* disable all interrupts */
+ saa7146_write(&chipi2s->chip, MC1, (MRST_N<<16)); /* reset SAA7146 */
+}
+
+/**
+ * see aw8.h
+ */
+void aw_switch_input_adc(struct saa7146reg *chip)
+{
+ switch_input(chip, analog);
+}
+
+/**
+ * see aw8.h
+ */
+void aw_switch_input_daio(struct saa7146reg *chip)
+{
+ switch_input(chip, digital);
+}
+
+/**
+ * see aw8.h
+ * Details regarding implementation see project wiki.
+ */
+int aw8_set_samplerate(struct saa7146reg *chip, unsigned int fs)
+{
+ uint32_t counter = 0;
+ uint8_t fs_prg_seq[4];
+
+ fs = fs > AW_FS_MAX ? AW_FS_MAX : fs;
+ fs = fs < AW_FS_MIN ? AW_FS_MIN : fs;
+ /* init TSA6060 counter */
+ counter = (256 * fs * 4) / PLL_FREF;
+ fs_prg_seq[0] = counter << 1;
+ fs_prg_seq[1] = counter >> 7;
+ fs_prg_seq[2] = (counter >> 15 & 0x03) | 0x18;
+ fs_prg_seq[3] = 0;
+ if (saa7146_i2c_write(chip, bps_10k, PLL_DEVICE_ADDR, NULL, 0,
+ fs_prg_seq, 4) < 4)
+ return -1;
+ return 0;
+}
+
+/**
+ * A1: A2:
+ * analog in: WS0/SD4
+ * analog/digital out: WS1/SD0 analog out: WS3/SD1
+ * analog out: WS2/SD2 analog out: WS4/SD3
+ */
+static int aw8_device_init(struct saa7146i2s *chipi2s)
+{
+ if (saa7146_i2s_init_device(chipi2s, a1, in, ws0, sd4_i_a1) != 0
+ || saa7146_i2s_init_device(chipi2s, a1, out, ws1, sd0_o_a1) != 0
+ || saa7146_i2s_init_device(chipi2s, a1, out, ws2, sd2_io_ax)
+ != 0
+ || saa7146_i2s_init_device(chipi2s, a2, out, ws3, sd1_io_ax)
+ != 0
+ || saa7146_i2s_init_device(chipi2s, a2, out, ws4, sd3_io_ax)
+ != 0)
+ return -1;
+ return 0;
+}
+
+/**
+ * TODO: Description
+ */
+static void switch_input(struct saa7146reg *chip, enum aw_input_types type)
+{
+ /* From EMagic driver...
+ // Switch SAA7146 to safe mode
+ setreg(chip, ACON1, 0L);
+ setreg(chip, ACON2, (12L<<27) | // A1 bitclock == ACLK/384
+ (12L<<22) | // A2 bitclock == ACLK/384
+ ( 1L<<19)); // BCLK1 is input
+ setreg(chip, MC1, 0x7fff0000); // Disable audio i/f
+ setreg(chip, A_TIME_SLOT1, (1L<<TSL_EOS));
+ setreg(chip, A_TIME_SLOT2, (1L<<TSL_EOS));
+ */
+ if (type == analog) {
+ saa7146_write(chip, GPIO_0, MASK_GPIO_HI);
+ /* wait until relais is switched and PLL is locked */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MAX(1, SLEEP_PLL*HZ/1000));
+ } else {
+ saa7146_write(chip, GPIO_0, MASK_GPIO_LO);
+ /* wait until relais is switched and digital-in is synced. */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MAX(1, SLEEP_DIGIN*HZ/1000));
+ }
+ /* From EMagic driver...
+ SetReg(MC1, 0x7fff0200, true); // Enable audio i/f
+ */
+}
+
diff -uprN ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8.h
../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8.h
--- ../alsa-driver-1.0.17/alsa-kernel/pci/saa7146/aw8.h 1970-01-01 01:00:00.000000000 +0100
+++ ../alsa-driver-1.0.17.mod/alsa-kernel/pci/saa7146/aw8.h 2008-10-22 23:34:05.000000000 +0200
@@ -0,0 +1,80 @@
+/*
+ * Emagic Audiowerk specific functions
+ * Copyright (c) 2006- by M. Nyffenegger <matthias.nyffenegger[AT]bluewin.ch>
+ *
+ *
+ * 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
+ *
+ */
+#ifndef AUDIOWERK_H_
+#define AUDIOWERK_H_
+
+#include <linux/types.h>
+#include "saa7146.h"
+#include "saa7146i2s.h"
+
+# define AW_MODULE_DESCRIPTION "Emagic Audiowerk 8 Driver"
+# define AW_CARD_SHORT_NAME "Audiowerk 8"
+# define AW_DRIVER_NAME "SAA7146A Audiowerk 8"
+# define AW_PCM_RATE SNDRV_PCM_RATE_CONTINUOUS
+# define AW_FS_MIN 25000 /* fs lower limit for DAC TDA1305 (ADC 18kHz) */
+# define AW_FS_MAX 48000 /* fs upper limit for DAC TDA1305 (ADC 50kHz) */
+# define AW_MAX_PLAYBACK_CHANNELS_PER_STREAM 4
+
+/**
+ * Audiwerk sampling formats.
+ */
+enum aw_formats {fmt_s16_2le, fmt_s16_2be, fmt_s18_4le, fmt_s18_4be,
+ fmt_s20_4le, fmt_s20_4be};
+
+/**
+ * TODO: description
+ */
+int aw_init(struct saa7146i2s *chipi2s, int input_type);
+
+/**
+ * TODO: description
+ */
+void aw_exit(struct saa7146i2s *chipi2s);
+
+/**
+ * Switches the Audiowerk input to analog.
+ * There are two sysclock generators, switched via a relais on GPIO-0:
+ * o TSA 6060 PLL for analog input
+ * o TDA 1315 digital input
+ * @param chip Used for hardware access over PCI bus.
+ */
+void aw_switch_input_adc(struct saa7146reg *chip);
+
+/**
+ * Switches the Audiowerk input to digital.
+ * There are two sysclock generators, switched via a relais on GPIO-0:
+ * o TSA 6060 PLL for analog input
+ * o TDA 1315 digital input
+ * @param chip Used for hardware access over PCI bus.
+ */
+void aw_switch_input_daio(struct saa7146reg *chip);
+
+/**
+ * Set sample rate on AW8.
+ * AW8 supports continuous fs shared by all substreams -> last call to
+ * aw8_set_samplerate() wins.
+ * @param chip Used for hardware access over PCI bus.
+ * @param fs sampling frequency (25-48kHz).
+ * @return 0 in case of success or -1 in case of failure.
+ */
+int aw8_set_samplerate(struct saa7146reg *chip, unsigned int fs);
+
+#endif /*AUDIOWERK_H_*/
More information about the Alsa-devel
mailing list