summaryrefslogtreecommitdiffstats
path: root/sound/pci/aw2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:02:30 +0000
commit76cb841cb886eef6b3bee341a2266c76578724ad (patch)
treef5892e5ba6cc11949952a6ce4ecbe6d516d6ce58 /sound/pci/aw2
parentInitial commit. (diff)
downloadlinux-76cb841cb886eef6b3bee341a2266c76578724ad.tar.xz
linux-76cb841cb886eef6b3bee341a2266c76578724ad.zip
Adding upstream version 4.19.249.upstream/4.19.249upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/pci/aw2')
-rw-r--r--sound/pci/aw2/Makefile3
-rw-r--r--sound/pci/aw2/aw2-alsa.c761
-rw-r--r--sound/pci/aw2/aw2-saa7146.c461
-rw-r--r--sound/pci/aw2/aw2-saa7146.h105
-rw-r--r--sound/pci/aw2/aw2-tsl.c110
-rw-r--r--sound/pci/aw2/saa7146.h168
6 files changed, 1608 insertions, 0 deletions
diff --git a/sound/pci/aw2/Makefile b/sound/pci/aw2/Makefile
new file mode 100644
index 000000000..842335d3b
--- /dev/null
+++ b/sound/pci/aw2/Makefile
@@ -0,0 +1,3 @@
+snd-aw2-objs := aw2-alsa.o aw2-saa7146.o
+
+obj-$(CONFIG_SND_AW2) += snd-aw2.o
diff --git a/sound/pci/aw2/aw2-alsa.c b/sound/pci/aw2/aw2-alsa.c
new file mode 100644
index 000000000..9a49e4243
--- /dev/null
+++ b/sound/pci/aw2/aw2-alsa.c
@@ -0,0 +1,761 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+
+#include "saa7146.h"
+#include "aw2-saa7146.h"
+
+MODULE_AUTHOR("Cedric Bregardis <cedric.bregardis@free.fr>, "
+ "Jean-Christian Hassler <jhassler@free.fr>");
+MODULE_DESCRIPTION("Emagic Audiowerk 2 sound driver");
+MODULE_LICENSE("GPL");
+
+/*********************************
+ * DEFINES
+ ********************************/
+#define CTL_ROUTE_ANALOG 0
+#define CTL_ROUTE_DIGITAL 1
+
+/*********************************
+ * TYPEDEFS
+ ********************************/
+ /* hardware definition */
+static const struct snd_pcm_hardware snd_aw2_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,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 2,
+ .channels_max = 4,
+ .buffer_bytes_max = 32768,
+ .period_bytes_min = 4096,
+ .period_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+static const struct snd_pcm_hardware snd_aw2_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,
+ .rates = SNDRV_PCM_RATE_44100,
+ .rate_min = 44100,
+ .rate_max = 44100,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = 32768,
+ .period_bytes_min = 4096,
+ .period_bytes_max = 32768,
+ .periods_min = 1,
+ .periods_max = 1024,
+};
+
+struct aw2_pcm_device {
+ struct snd_pcm *pcm;
+ unsigned int stream_number;
+ struct aw2 *chip;
+};
+
+struct aw2 {
+ struct snd_aw2_saa7146 saa7146;
+
+ struct pci_dev *pci;
+ int irq;
+ spinlock_t reg_lock;
+ struct mutex mtx;
+
+ unsigned long iobase_phys;
+ void __iomem *iobase_virt;
+
+ struct snd_card *card;
+
+ struct aw2_pcm_device device_playback[NB_STREAM_PLAYBACK];
+ struct aw2_pcm_device device_capture[NB_STREAM_CAPTURE];
+};
+
+/*********************************
+ * FUNCTION DECLARATIONS
+ ********************************/
+static int snd_aw2_dev_free(struct snd_device *device);
+static int snd_aw2_create(struct snd_card *card,
+ struct pci_dev *pci, struct aw2 **rchip);
+static int snd_aw2_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id);
+static void snd_aw2_remove(struct pci_dev *pci);
+static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params);
+static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream);
+static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
+ int cmd);
+static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
+ int cmd);
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
+ *substream);
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
+ *substream);
+static int snd_aw2_new_pcm(struct aw2 *chip);
+
+static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value
+ *ucontrol);
+static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value
+ *ucontrol);
+
+/*********************************
+ * VARIABLES
+ ********************************/
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Audiowerk2 soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for the Audiowerk2 soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Audiowerk2 soundcard.");
+
+static const struct pci_device_id snd_aw2_ids[] = {
+ {PCI_VENDOR_ID_PHILIPS, PCI_DEVICE_ID_PHILIPS_SAA7146, 0, 0,
+ 0, 0, 0},
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, snd_aw2_ids);
+
+/* pci_driver definition */
+static struct pci_driver aw2_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = snd_aw2_ids,
+ .probe = snd_aw2_probe,
+ .remove = snd_aw2_remove,
+};
+
+module_pci_driver(aw2_driver);
+
+/* operators for playback PCM alsa interface */
+static const struct snd_pcm_ops snd_aw2_playback_ops = {
+ .open = snd_aw2_pcm_playback_open,
+ .close = snd_aw2_pcm_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_aw2_pcm_hw_params,
+ .hw_free = snd_aw2_pcm_hw_free,
+ .prepare = snd_aw2_pcm_prepare_playback,
+ .trigger = snd_aw2_pcm_trigger_playback,
+ .pointer = snd_aw2_pcm_pointer_playback,
+};
+
+/* operators for capture PCM alsa interface */
+static const struct snd_pcm_ops snd_aw2_capture_ops = {
+ .open = snd_aw2_pcm_capture_open,
+ .close = snd_aw2_pcm_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = snd_aw2_pcm_hw_params,
+ .hw_free = snd_aw2_pcm_hw_free,
+ .prepare = snd_aw2_pcm_prepare_capture,
+ .trigger = snd_aw2_pcm_trigger_capture,
+ .pointer = snd_aw2_pcm_pointer_capture,
+};
+
+static const struct snd_kcontrol_new aw2_control = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Capture Route",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .private_value = 0xffff,
+ .info = snd_aw2_control_switch_capture_info,
+ .get = snd_aw2_control_switch_capture_get,
+ .put = snd_aw2_control_switch_capture_put
+};
+
+/*********************************
+ * FUNCTION IMPLEMENTATIONS
+ ********************************/
+
+/* component-destructor */
+static int snd_aw2_dev_free(struct snd_device *device)
+{
+ struct aw2 *chip = device->device_data;
+
+ /* Free hardware */
+ snd_aw2_saa7146_free(&chip->saa7146);
+
+ /* release the irq */
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void *)chip);
+ /* release the i/o ports & memory */
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ /* disable the PCI entry */
+ pci_disable_device(chip->pci);
+ /* release the data */
+ kfree(chip);
+
+ return 0;
+}
+
+/* chip-specific constructor */
+static int snd_aw2_create(struct snd_card *card,
+ struct pci_dev *pci, struct aw2 **rchip)
+{
+ struct aw2 *chip;
+ int err;
+ static struct snd_device_ops ops = {
+ .dev_free = snd_aw2_dev_free,
+ };
+
+ *rchip = NULL;
+
+ /* initialize the PCI entry */
+ err = pci_enable_device(pci);
+ if (err < 0)
+ return err;
+ pci_set_master(pci);
+
+ /* check PCI availability (32bit DMA) */
+ if ((dma_set_mask(&pci->dev, DMA_BIT_MASK(32)) < 0) ||
+ (dma_set_coherent_mask(&pci->dev, DMA_BIT_MASK(32)) < 0)) {
+ dev_err(card->dev, "Impossible to set 32bit mask DMA\n");
+ pci_disable_device(pci);
+ return -ENXIO;
+ }
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ /* initialize the stuff */
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ /* (1) PCI resource allocation */
+ err = pci_request_regions(pci, "Audiowerk2");
+ if (err < 0) {
+ pci_disable_device(pci);
+ kfree(chip);
+ return err;
+ }
+ chip->iobase_phys = pci_resource_start(pci, 0);
+ chip->iobase_virt =
+ ioremap_nocache(chip->iobase_phys,
+ pci_resource_len(pci, 0));
+
+ if (chip->iobase_virt == NULL) {
+ dev_err(card->dev, "unable to remap memory region");
+ pci_release_regions(pci);
+ pci_disable_device(pci);
+ kfree(chip);
+ return -ENOMEM;
+ }
+
+ /* (2) initialization of the chip hardware */
+ snd_aw2_saa7146_setup(&chip->saa7146, chip->iobase_virt);
+
+ if (request_irq(pci->irq, snd_aw2_saa7146_interrupt,
+ IRQF_SHARED, KBUILD_MODNAME, chip)) {
+ dev_err(card->dev, "Cannot grab irq %d\n", pci->irq);
+
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return -EBUSY;
+ }
+ chip->irq = pci->irq;
+
+ err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+ if (err < 0) {
+ free_irq(chip->irq, (void *)chip);
+ iounmap(chip->iobase_virt);
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+ return err;
+ }
+
+ *rchip = chip;
+
+ dev_info(card->dev,
+ "Audiowerk 2 sound card (saa7146 chipset) detected and managed\n");
+ return 0;
+}
+
+/* constructor */
+static int snd_aw2_probe(struct pci_dev *pci,
+ const struct pci_device_id *pci_id)
+{
+ static int dev;
+ struct snd_card *card;
+ struct aw2 *chip;
+ int err;
+
+ /* (1) Continue if device is not enabled, else inc dev */
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (!enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ /* (2) Create card instance */
+ err = snd_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE,
+ 0, &card);
+ if (err < 0)
+ return err;
+
+ /* (3) Create main component */
+ err = snd_aw2_create(card, pci, &chip);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* initialize mutex */
+ mutex_init(&chip->mtx);
+ /* init spinlock */
+ spin_lock_init(&chip->reg_lock);
+ /* (4) Define driver ID and name string */
+ strcpy(card->driver, "aw2");
+ strcpy(card->shortname, "Audiowerk2");
+
+ sprintf(card->longname, "%s with SAA7146 irq %i",
+ card->shortname, chip->irq);
+
+ /* (5) Create other components */
+ snd_aw2_new_pcm(chip);
+
+ /* (6) Register card instance */
+ err = snd_card_register(card);
+ if (err < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* (7) Set PCI driver data */
+ pci_set_drvdata(pci, card);
+
+ dev++;
+ return 0;
+}
+
+/* destructor */
+static void snd_aw2_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+}
+
+/* open callback */
+static int snd_aw2_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ dev_dbg(substream->pcm->card->dev, "Playback_open\n");
+ runtime->hw = snd_aw2_playback_hw;
+ return 0;
+}
+
+/* close callback */
+static int snd_aw2_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+ return 0;
+
+}
+
+static int snd_aw2_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+
+ dev_dbg(substream->pcm->card->dev, "Capture_open\n");
+ runtime->hw = snd_aw2_capture_hw;
+ return 0;
+}
+
+/* close callback */
+static int snd_aw2_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+ /* TODO: something to do ? */
+ return 0;
+}
+
+ /* hw_params callback */
+static int snd_aw2_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+/* hw_free callback */
+static int snd_aw2_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ return snd_pcm_lib_free_pages(substream);
+}
+
+/* prepare callback for playback */
+static int snd_aw2_pcm_prepare_playback(struct snd_pcm_substream *substream)
+{
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long period_size, buffer_size;
+
+ mutex_lock(&chip->mtx);
+
+ period_size = snd_pcm_lib_period_bytes(substream);
+ buffer_size = snd_pcm_lib_buffer_bytes(substream);
+
+ snd_aw2_saa7146_pcm_init_playback(&chip->saa7146,
+ pcm_device->stream_number,
+ runtime->dma_addr, period_size,
+ buffer_size);
+
+ /* Define Interrupt callback */
+ snd_aw2_saa7146_define_it_playback_callback(pcm_device->stream_number,
+ (snd_aw2_saa7146_it_cb)
+ snd_pcm_period_elapsed,
+ (void *)substream);
+
+ mutex_unlock(&chip->mtx);
+
+ return 0;
+}
+
+/* prepare callback for capture */
+static int snd_aw2_pcm_prepare_capture(struct snd_pcm_substream *substream)
+{
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long period_size, buffer_size;
+
+ mutex_lock(&chip->mtx);
+
+ period_size = snd_pcm_lib_period_bytes(substream);
+ buffer_size = snd_pcm_lib_buffer_bytes(substream);
+
+ snd_aw2_saa7146_pcm_init_capture(&chip->saa7146,
+ pcm_device->stream_number,
+ runtime->dma_addr, period_size,
+ buffer_size);
+
+ /* Define Interrupt callback */
+ snd_aw2_saa7146_define_it_capture_callback(pcm_device->stream_number,
+ (snd_aw2_saa7146_it_cb)
+ snd_pcm_period_elapsed,
+ (void *)substream);
+
+ mutex_unlock(&chip->mtx);
+
+ return 0;
+}
+
+/* playback trigger callback */
+static int snd_aw2_pcm_trigger_playback(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int status = 0;
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_aw2_saa7146_pcm_trigger_start_playback(&chip->saa7146,
+ pcm_device->
+ stream_number);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_aw2_saa7146_pcm_trigger_stop_playback(&chip->saa7146,
+ pcm_device->
+ stream_number);
+ break;
+ default:
+ status = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return status;
+}
+
+/* capture trigger callback */
+static int snd_aw2_pcm_trigger_capture(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ int status = 0;
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ snd_aw2_saa7146_pcm_trigger_start_capture(&chip->saa7146,
+ pcm_device->
+ stream_number);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ snd_aw2_saa7146_pcm_trigger_stop_capture(&chip->saa7146,
+ pcm_device->
+ stream_number);
+ break;
+ default:
+ status = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ return status;
+}
+
+/* playback pointer callback */
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_playback(struct snd_pcm_substream
+ *substream)
+{
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ unsigned int current_ptr;
+
+ /* get the current hardware pointer */
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ current_ptr =
+ snd_aw2_saa7146_get_hw_ptr_playback(&chip->saa7146,
+ pcm_device->stream_number,
+ runtime->dma_area,
+ runtime->buffer_size);
+
+ return bytes_to_frames(substream->runtime, current_ptr);
+}
+
+/* capture pointer callback */
+static snd_pcm_uframes_t snd_aw2_pcm_pointer_capture(struct snd_pcm_substream
+ *substream)
+{
+ struct aw2_pcm_device *pcm_device = snd_pcm_substream_chip(substream);
+ struct aw2 *chip = pcm_device->chip;
+ unsigned int current_ptr;
+
+ /* get the current hardware pointer */
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ current_ptr =
+ snd_aw2_saa7146_get_hw_ptr_capture(&chip->saa7146,
+ pcm_device->stream_number,
+ runtime->dma_area,
+ runtime->buffer_size);
+
+ return bytes_to_frames(substream->runtime, current_ptr);
+}
+
+/* create a pcm device */
+static int snd_aw2_new_pcm(struct aw2 *chip)
+{
+ struct snd_pcm *pcm_playback_ana;
+ struct snd_pcm *pcm_playback_num;
+ struct snd_pcm *pcm_capture;
+ struct aw2_pcm_device *pcm_device;
+ int err = 0;
+
+ /* Create new Alsa PCM device */
+
+ err = snd_pcm_new(chip->card, "Audiowerk2 analog playback", 0, 1, 0,
+ &pcm_playback_ana);
+ if (err < 0) {
+ dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
+ return err;
+ }
+
+ /* Creation ok */
+ pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_ANA];
+
+ /* Set PCM device name */
+ strcpy(pcm_playback_ana->name, "Analog playback");
+ /* Associate private data to PCM device */
+ pcm_playback_ana->private_data = pcm_device;
+ /* set operators of PCM device */
+ snd_pcm_set_ops(pcm_playback_ana, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_aw2_playback_ops);
+ /* store PCM device */
+ pcm_device->pcm = pcm_playback_ana;
+ /* give base chip pointer to our internal pcm device
+ structure */
+ pcm_device->chip = chip;
+ /* Give stream number to PCM device */
+ pcm_device->stream_number = NUM_STREAM_PLAYBACK_ANA;
+
+ /* pre-allocation of buffers */
+ /* Preallocate continuous pages. */
+ err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_ana,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data
+ (chip->pci),
+ 64 * 1024, 64 * 1024);
+ if (err)
+ dev_err(chip->card->dev,
+ "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
+ err);
+
+ err = snd_pcm_new(chip->card, "Audiowerk2 digital playback", 1, 1, 0,
+ &pcm_playback_num);
+
+ if (err < 0) {
+ dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
+ return err;
+ }
+ /* Creation ok */
+ pcm_device = &chip->device_playback[NUM_STREAM_PLAYBACK_DIG];
+
+ /* Set PCM device name */
+ strcpy(pcm_playback_num->name, "Digital playback");
+ /* Associate private data to PCM device */
+ pcm_playback_num->private_data = pcm_device;
+ /* set operators of PCM device */
+ snd_pcm_set_ops(pcm_playback_num, SNDRV_PCM_STREAM_PLAYBACK,
+ &snd_aw2_playback_ops);
+ /* store PCM device */
+ pcm_device->pcm = pcm_playback_num;
+ /* give base chip pointer to our internal pcm device
+ structure */
+ pcm_device->chip = chip;
+ /* Give stream number to PCM device */
+ pcm_device->stream_number = NUM_STREAM_PLAYBACK_DIG;
+
+ /* pre-allocation of buffers */
+ /* Preallocate continuous pages. */
+ err = snd_pcm_lib_preallocate_pages_for_all(pcm_playback_num,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data
+ (chip->pci),
+ 64 * 1024, 64 * 1024);
+ if (err)
+ dev_err(chip->card->dev,
+ "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
+ err);
+
+ err = snd_pcm_new(chip->card, "Audiowerk2 capture", 2, 0, 1,
+ &pcm_capture);
+
+ if (err < 0) {
+ dev_err(chip->card->dev, "snd_pcm_new error (0x%X)\n", err);
+ return err;
+ }
+
+ /* Creation ok */
+ pcm_device = &chip->device_capture[NUM_STREAM_CAPTURE_ANA];
+
+ /* Set PCM device name */
+ strcpy(pcm_capture->name, "Capture");
+ /* Associate private data to PCM device */
+ pcm_capture->private_data = pcm_device;
+ /* set operators of PCM device */
+ snd_pcm_set_ops(pcm_capture, SNDRV_PCM_STREAM_CAPTURE,
+ &snd_aw2_capture_ops);
+ /* store PCM device */
+ pcm_device->pcm = pcm_capture;
+ /* give base chip pointer to our internal pcm device
+ structure */
+ pcm_device->chip = chip;
+ /* Give stream number to PCM device */
+ pcm_device->stream_number = NUM_STREAM_CAPTURE_ANA;
+
+ /* pre-allocation of buffers */
+ /* Preallocate continuous pages. */
+ err = snd_pcm_lib_preallocate_pages_for_all(pcm_capture,
+ SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data
+ (chip->pci),
+ 64 * 1024, 64 * 1024);
+ if (err)
+ dev_err(chip->card->dev,
+ "snd_pcm_lib_preallocate_pages_for_all error (0x%X)\n",
+ err);
+
+
+ /* Create control */
+ err = snd_ctl_add(chip->card, snd_ctl_new1(&aw2_control, chip));
+ if (err < 0) {
+ dev_err(chip->card->dev, "snd_ctl_add error (0x%X)\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int snd_aw2_control_switch_capture_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ static const char * const texts[2] = {
+ "Analog", "Digital"
+ };
+ return snd_ctl_enum_info(uinfo, 1, 2, texts);
+}
+
+static int snd_aw2_control_switch_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value
+ *ucontrol)
+{
+ struct aw2 *chip = snd_kcontrol_chip(kcontrol);
+ if (snd_aw2_saa7146_is_using_digital_input(&chip->saa7146))
+ ucontrol->value.enumerated.item[0] = CTL_ROUTE_DIGITAL;
+ else
+ ucontrol->value.enumerated.item[0] = CTL_ROUTE_ANALOG;
+ return 0;
+}
+
+static int snd_aw2_control_switch_capture_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value
+ *ucontrol)
+{
+ struct aw2 *chip = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+ int is_disgital =
+ snd_aw2_saa7146_is_using_digital_input(&chip->saa7146);
+
+ if (((ucontrol->value.integer.value[0] == CTL_ROUTE_DIGITAL)
+ && !is_disgital)
+ || ((ucontrol->value.integer.value[0] == CTL_ROUTE_ANALOG)
+ && is_disgital)) {
+ snd_aw2_saa7146_use_digital_input(&chip->saa7146, !is_disgital);
+ changed = 1;
+ }
+ return changed;
+}
diff --git a/sound/pci/aw2/aw2-saa7146.c b/sound/pci/aw2/aw2-saa7146.c
new file mode 100644
index 000000000..1d7890459
--- /dev/null
+++ b/sound/pci/aw2/aw2-saa7146.c
@@ -0,0 +1,461 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#define AW2_SAA7146_M
+
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "saa7146.h"
+#include "aw2-saa7146.h"
+
+#include "aw2-tsl.c"
+
+#define WRITEREG(value, addr) writel((value), chip->base_addr + (addr))
+#define READREG(addr) readl(chip->base_addr + (addr))
+
+static struct snd_aw2_saa7146_cb_param
+ arr_substream_it_playback_cb[NB_STREAM_PLAYBACK];
+static struct snd_aw2_saa7146_cb_param
+ arr_substream_it_capture_cb[NB_STREAM_CAPTURE];
+
+static int snd_aw2_saa7146_get_limit(int size);
+
+/* chip-specific destructor */
+int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip)
+{
+ /* disable all irqs */
+ WRITEREG(0, IER);
+
+ /* reset saa7146 */
+ WRITEREG((MRST_N << 16), MC1);
+
+ /* Unset base addr */
+ chip->base_addr = NULL;
+
+ return 0;
+}
+
+void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
+ void __iomem *pci_base_addr)
+{
+ /* set PCI burst/threshold
+
+ Burst length definition
+ VALUE BURST LENGTH
+ 000 1 Dword
+ 001 2 Dwords
+ 010 4 Dwords
+ 011 8 Dwords
+ 100 16 Dwords
+ 101 32 Dwords
+ 110 64 Dwords
+ 111 128 Dwords
+
+ Threshold definition
+ VALUE WRITE MODE READ MODE
+ 00 1 Dword of valid data 1 empty Dword
+ 01 4 Dwords of valid data 4 empty Dwords
+ 10 8 Dwords of valid data 8 empty Dwords
+ 11 16 Dwords of valid data 16 empty Dwords */
+
+ unsigned int acon2;
+ unsigned int acon1 = 0;
+ int i;
+
+ /* Set base addr */
+ chip->base_addr = pci_base_addr;
+
+ /* disable all irqs */
+ WRITEREG(0, IER);
+
+ /* reset saa7146 */
+ WRITEREG((MRST_N << 16), MC1);
+
+ /* enable audio interface */
+#ifdef __BIG_ENDIAN
+ acon1 |= A1_SWAP;
+ acon1 |= A2_SWAP;
+#endif
+ /* WS0_CTRL, WS0_SYNC: input TSL1, I2S */
+
+ /* At initialization WS1 and WS2 are disabled (configured as input) */
+ acon1 |= 0 * WS1_CTRL;
+ acon1 |= 0 * WS2_CTRL;
+
+ /* WS4 is not used. So it must not restart A2.
+ This is why it is configured as output (force to low) */
+ acon1 |= 3 * WS4_CTRL;
+
+ /* WS3_CTRL, WS3_SYNC: output TSL2, I2S */
+ acon1 |= 2 * WS3_CTRL;
+
+ /* A1 and A2 are active and asynchronous */
+ acon1 |= 3 * AUDIO_MODE;
+ WRITEREG(acon1, ACON1);
+
+ /* The following comes from original windows driver.
+ It is needed to have a correct behavior of input and output
+ simultenously, but I don't know why ! */
+ WRITEREG(3 * (BurstA1_in) + 3 * (ThreshA1_in) +
+ 3 * (BurstA1_out) + 3 * (ThreshA1_out) +
+ 3 * (BurstA2_out) + 3 * (ThreshA2_out), PCI_BT_A);
+
+ /* enable audio port pins */
+ WRITEREG((EAP << 16) | EAP, MC1);
+
+ /* enable I2C */
+ WRITEREG((EI2C << 16) | EI2C, MC1);
+ /* enable interrupts */
+ WRITEREG(A1_out | A2_out | A1_in | IIC_S | IIC_E, IER);
+
+ /* audio configuration */
+ acon2 = A2_CLKSRC | BCLK1_OEN;
+ WRITEREG(acon2, ACON2);
+
+ /* By default use analog input */
+ snd_aw2_saa7146_use_digital_input(chip, 0);
+
+ /* TSL setup */
+ for (i = 0; i < 8; ++i) {
+ WRITEREG(tsl1[i], TSL1 + (i * 4));
+ WRITEREG(tsl2[i], TSL2 + (i * 4));
+ }
+
+}
+
+void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
+ int stream_number,
+ unsigned long dma_addr,
+ unsigned long period_size,
+ unsigned long buffer_size)
+{
+ unsigned long dw_page, dw_limit;
+
+ /* Configure DMA for substream
+ Configuration informations: ALSA has allocated continuous memory
+ pages. So we don't need to use MMU of saa7146.
+ */
+
+ /* No MMU -> nothing to do with PageA1, we only configure the limit of
+ PageAx_out register */
+ /* Disable MMU */
+ dw_page = (0L << 11);
+
+ /* Configure Limit for DMA access.
+ The limit register defines an address limit, which generates
+ an interrupt if passed by the actual PCI address pointer.
+ '0001' means an interrupt will be generated if the lower
+ 6 bits (64 bytes) of the PCI address are zero. '0010'
+ defines a limit of 128 bytes, '0011' one of 256 bytes, and
+ so on up to 1 Mbyte defined by '1111'. This interrupt range
+ can be calculated as follows:
+ Range = 2^(5 + Limit) bytes.
+ */
+ dw_limit = snd_aw2_saa7146_get_limit(period_size);
+ dw_page |= (dw_limit << 4);
+
+ if (stream_number == 0) {
+ WRITEREG(dw_page, PageA2_out);
+
+ /* Base address for DMA transfert. */
+ /* This address has been reserved by ALSA. */
+ /* This is a physical address */
+ WRITEREG(dma_addr, BaseA2_out);
+
+ /* Define upper limit for DMA access */
+ WRITEREG(dma_addr + buffer_size, ProtA2_out);
+
+ } else if (stream_number == 1) {
+ WRITEREG(dw_page, PageA1_out);
+
+ /* Base address for DMA transfert. */
+ /* This address has been reserved by ALSA. */
+ /* This is a physical address */
+ WRITEREG(dma_addr, BaseA1_out);
+
+ /* Define upper limit for DMA access */
+ WRITEREG(dma_addr + buffer_size, ProtA1_out);
+ } else {
+ pr_err("aw2: snd_aw2_saa7146_pcm_init_playback: "
+ "Substream number is not 0 or 1 -> not managed\n");
+ }
+}
+
+void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
+ int stream_number, unsigned long dma_addr,
+ unsigned long period_size,
+ unsigned long buffer_size)
+{
+ unsigned long dw_page, dw_limit;
+
+ /* Configure DMA for substream
+ Configuration informations: ALSA has allocated continuous memory
+ pages. So we don't need to use MMU of saa7146.
+ */
+
+ /* No MMU -> nothing to do with PageA1, we only configure the limit of
+ PageAx_out register */
+ /* Disable MMU */
+ dw_page = (0L << 11);
+
+ /* Configure Limit for DMA access.
+ The limit register defines an address limit, which generates
+ an interrupt if passed by the actual PCI address pointer.
+ '0001' means an interrupt will be generated if the lower
+ 6 bits (64 bytes) of the PCI address are zero. '0010'
+ defines a limit of 128 bytes, '0011' one of 256 bytes, and
+ so on up to 1 Mbyte defined by '1111'. This interrupt range
+ can be calculated as follows:
+ Range = 2^(5 + Limit) bytes.
+ */
+ dw_limit = snd_aw2_saa7146_get_limit(period_size);
+ dw_page |= (dw_limit << 4);
+
+ if (stream_number == 0) {
+ WRITEREG(dw_page, PageA1_in);
+
+ /* Base address for DMA transfert. */
+ /* This address has been reserved by ALSA. */
+ /* This is a physical address */
+ WRITEREG(dma_addr, BaseA1_in);
+
+ /* Define upper limit for DMA access */
+ WRITEREG(dma_addr + buffer_size, ProtA1_in);
+ } else {
+ pr_err("aw2: snd_aw2_saa7146_pcm_init_capture: "
+ "Substream number is not 0 -> not managed\n");
+ }
+}
+
+void snd_aw2_saa7146_define_it_playback_callback(unsigned int stream_number,
+ snd_aw2_saa7146_it_cb
+ p_it_callback,
+ void *p_callback_param)
+{
+ if (stream_number < NB_STREAM_PLAYBACK) {
+ arr_substream_it_playback_cb[stream_number].p_it_callback =
+ (snd_aw2_saa7146_it_cb) p_it_callback;
+ arr_substream_it_playback_cb[stream_number].p_callback_param =
+ (void *)p_callback_param;
+ }
+}
+
+void snd_aw2_saa7146_define_it_capture_callback(unsigned int stream_number,
+ snd_aw2_saa7146_it_cb
+ p_it_callback,
+ void *p_callback_param)
+{
+ if (stream_number < NB_STREAM_CAPTURE) {
+ arr_substream_it_capture_cb[stream_number].p_it_callback =
+ (snd_aw2_saa7146_it_cb) p_it_callback;
+ arr_substream_it_capture_cb[stream_number].p_callback_param =
+ (void *)p_callback_param;
+ }
+}
+
+void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146 *chip,
+ int stream_number)
+{
+ unsigned int acon1 = 0;
+ /* In aw8 driver, dma transfert is always active. It is
+ started and stopped in a larger "space" */
+ acon1 = READREG(ACON1);
+ if (stream_number == 0) {
+ WRITEREG((TR_E_A2_OUT << 16) | TR_E_A2_OUT, MC1);
+
+ /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
+ acon1 |= 2 * WS2_CTRL;
+ WRITEREG(acon1, ACON1);
+
+ } else if (stream_number == 1) {
+ WRITEREG((TR_E_A1_OUT << 16) | TR_E_A1_OUT, MC1);
+
+ /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
+ acon1 |= 1 * WS1_CTRL;
+ WRITEREG(acon1, ACON1);
+ }
+}
+
+void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146 *chip,
+ int stream_number)
+{
+ unsigned int acon1 = 0;
+ acon1 = READREG(ACON1);
+ if (stream_number == 0) {
+ /* WS2_CTRL, WS2_SYNC: output TSL2, I2S */
+ acon1 &= ~(3 * WS2_CTRL);
+ WRITEREG(acon1, ACON1);
+
+ WRITEREG((TR_E_A2_OUT << 16), MC1);
+ } else if (stream_number == 1) {
+ /* WS1_CTRL, WS1_SYNC: output TSL1, I2S */
+ acon1 &= ~(3 * WS1_CTRL);
+ WRITEREG(acon1, ACON1);
+
+ WRITEREG((TR_E_A1_OUT << 16), MC1);
+ }
+}
+
+void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146 *chip,
+ int stream_number)
+{
+ /* In aw8 driver, dma transfert is always active. It is
+ started and stopped in a larger "space" */
+ if (stream_number == 0)
+ WRITEREG((TR_E_A1_IN << 16) | TR_E_A1_IN, MC1);
+}
+
+void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146 *chip,
+ int stream_number)
+{
+ if (stream_number == 0)
+ WRITEREG((TR_E_A1_IN << 16), MC1);
+}
+
+irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id)
+{
+ unsigned int isr;
+ unsigned int iicsta;
+ struct snd_aw2_saa7146 *chip = dev_id;
+
+ isr = READREG(ISR);
+ if (!isr)
+ return IRQ_NONE;
+
+ WRITEREG(isr, ISR);
+
+ if (isr & (IIC_S | IIC_E)) {
+ iicsta = READREG(IICSTA);
+ WRITEREG(0x100, IICSTA);
+ }
+
+ if (isr & A1_out) {
+ if (arr_substream_it_playback_cb[1].p_it_callback != NULL) {
+ arr_substream_it_playback_cb[1].
+ p_it_callback(arr_substream_it_playback_cb[1].
+ p_callback_param);
+ }
+ }
+ if (isr & A2_out) {
+ if (arr_substream_it_playback_cb[0].p_it_callback != NULL) {
+ arr_substream_it_playback_cb[0].
+ p_it_callback(arr_substream_it_playback_cb[0].
+ p_callback_param);
+ }
+
+ }
+ if (isr & A1_in) {
+ if (arr_substream_it_capture_cb[0].p_it_callback != NULL) {
+ arr_substream_it_capture_cb[0].
+ p_it_callback(arr_substream_it_capture_cb[0].
+ p_callback_param);
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146 *chip,
+ int stream_number,
+ unsigned char *start_addr,
+ unsigned int buffer_size)
+{
+ long pci_adp = 0;
+ size_t ptr = 0;
+
+ if (stream_number == 0) {
+ pci_adp = READREG(PCI_ADP3);
+ ptr = pci_adp - (long)start_addr;
+
+ if (ptr == buffer_size)
+ ptr = 0;
+ }
+ if (stream_number == 1) {
+ pci_adp = READREG(PCI_ADP1);
+ ptr = pci_adp - (size_t) start_addr;
+
+ if (ptr == buffer_size)
+ ptr = 0;
+ }
+ return ptr;
+}
+
+unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146 *chip,
+ int stream_number,
+ unsigned char *start_addr,
+ unsigned int buffer_size)
+{
+ size_t pci_adp = 0;
+ size_t ptr = 0;
+ if (stream_number == 0) {
+ pci_adp = READREG(PCI_ADP2);
+ ptr = pci_adp - (size_t) start_addr;
+
+ if (ptr == buffer_size)
+ ptr = 0;
+ }
+ return ptr;
+}
+
+void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
+ int use_digital)
+{
+ /* FIXME: switch between analog and digital input does not always work.
+ It can produce a kind of white noise. It seams that received data
+ are inverted sometime (endian inversion). Why ? I don't know, maybe
+ a problem of synchronization... However for the time being I have
+ not found the problem. Workaround: switch again (and again) between
+ digital and analog input until it works. */
+ if (use_digital)
+ WRITEREG(0x40, GPIO_CTRL);
+ else
+ WRITEREG(0x50, GPIO_CTRL);
+}
+
+int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146 *chip)
+{
+ unsigned int reg_val = READREG(GPIO_CTRL);
+ if ((reg_val & 0xFF) == 0x40)
+ return 1;
+ else
+ return 0;
+}
+
+
+static int snd_aw2_saa7146_get_limit(int size)
+{
+ int limitsize = 32;
+ int limit = 0;
+ while (limitsize < size) {
+ limitsize *= 2;
+ limit++;
+ }
+ return limit;
+}
diff --git a/sound/pci/aw2/aw2-saa7146.h b/sound/pci/aw2/aw2-saa7146.h
new file mode 100644
index 000000000..5b35e3589
--- /dev/null
+++ b/sound/pci/aw2/aw2-saa7146.h
@@ -0,0 +1,105 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#ifndef AW2_SAA7146_H
+#define AW2_SAA7146_H
+
+#define NB_STREAM_PLAYBACK 2
+#define NB_STREAM_CAPTURE 1
+
+#define NUM_STREAM_PLAYBACK_ANA 0
+#define NUM_STREAM_PLAYBACK_DIG 1
+
+#define NUM_STREAM_CAPTURE_ANA 0
+
+typedef void (*snd_aw2_saa7146_it_cb) (void *);
+
+struct snd_aw2_saa7146_cb_param {
+ snd_aw2_saa7146_it_cb p_it_callback;
+ void *p_callback_param;
+};
+
+/* definition of the chip-specific record */
+
+struct snd_aw2_saa7146 {
+ void __iomem *base_addr;
+};
+
+extern void snd_aw2_saa7146_setup(struct snd_aw2_saa7146 *chip,
+ void __iomem *pci_base_addr);
+extern int snd_aw2_saa7146_free(struct snd_aw2_saa7146 *chip);
+
+extern void snd_aw2_saa7146_pcm_init_playback(struct snd_aw2_saa7146 *chip,
+ int stream_number,
+ unsigned long dma_addr,
+ unsigned long period_size,
+ unsigned long buffer_size);
+extern void snd_aw2_saa7146_pcm_init_capture(struct snd_aw2_saa7146 *chip,
+ int stream_number,
+ unsigned long dma_addr,
+ unsigned long period_size,
+ unsigned long buffer_size);
+extern void snd_aw2_saa7146_define_it_playback_callback(unsigned int
+ stream_number,
+ snd_aw2_saa7146_it_cb
+ p_it_callback,
+ void *p_callback_param);
+extern void snd_aw2_saa7146_define_it_capture_callback(unsigned int
+ stream_number,
+ snd_aw2_saa7146_it_cb
+ p_it_callback,
+ void *p_callback_param);
+extern void snd_aw2_saa7146_pcm_trigger_start_capture(struct snd_aw2_saa7146
+ *chip, int stream_number);
+extern void snd_aw2_saa7146_pcm_trigger_stop_capture(struct snd_aw2_saa7146
+ *chip, int stream_number);
+
+extern void snd_aw2_saa7146_pcm_trigger_start_playback(struct snd_aw2_saa7146
+ *chip,
+ int stream_number);
+extern void snd_aw2_saa7146_pcm_trigger_stop_playback(struct snd_aw2_saa7146
+ *chip, int stream_number);
+
+extern irqreturn_t snd_aw2_saa7146_interrupt(int irq, void *dev_id);
+extern unsigned int snd_aw2_saa7146_get_hw_ptr_playback(struct snd_aw2_saa7146
+ *chip,
+ int stream_number,
+ unsigned char
+ *start_addr,
+ unsigned int
+ buffer_size);
+extern unsigned int snd_aw2_saa7146_get_hw_ptr_capture(struct snd_aw2_saa7146
+ *chip,
+ int stream_number,
+ unsigned char
+ *start_addr,
+ unsigned int
+ buffer_size);
+
+extern void snd_aw2_saa7146_use_digital_input(struct snd_aw2_saa7146 *chip,
+ int use_digital);
+
+extern int snd_aw2_saa7146_is_using_digital_input(struct snd_aw2_saa7146
+ *chip);
+
+#endif
diff --git a/sound/pci/aw2/aw2-tsl.c b/sound/pci/aw2/aw2-tsl.c
new file mode 100644
index 000000000..459b0311e
--- /dev/null
+++ b/sound/pci/aw2/aw2-tsl.c
@@ -0,0 +1,110 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ * Copyright 1998 Emagic Soft- und Hardware GmbH
+ * Copyright 2002 Martijn Sipkema
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+#define TSL_WS0 (1UL << 31)
+#define TSL_WS1 (1UL << 30)
+#define TSL_WS2 (1UL << 29)
+#define TSL_WS3 (1UL << 28)
+#define TSL_WS4 (1UL << 27)
+#define TSL_DIS_A1 (1UL << 24)
+#define TSL_SDW_A1 (1UL << 23)
+#define TSL_SIB_A1 (1UL << 22)
+#define TSL_SF_A1 (1UL << 21)
+#define TSL_LF_A1 (1UL << 20)
+#define TSL_BSEL_A1 (1UL << 17)
+#define TSL_DOD_A1 (1UL << 15)
+#define TSL_LOW_A1 (1UL << 14)
+#define TSL_DIS_A2 (1UL << 11)
+#define TSL_SDW_A2 (1UL << 10)
+#define TSL_SIB_A2 (1UL << 9)
+#define TSL_SF_A2 (1UL << 8)
+#define TSL_LF_A2 (1UL << 7)
+#define TSL_BSEL_A2 (1UL << 4)
+#define TSL_DOD_A2 (1UL << 2)
+#define TSL_LOW_A2 (1UL << 1)
+#define TSL_EOS (1UL << 0)
+
+ /* Audiowerk8 hardware setup: */
+ /* WS0, SD4, TSL1 - Analog/ digital in */
+ /* WS1, SD0, TSL1 - Analog out #1, digital out */
+ /* WS2, SD2, TSL1 - Analog out #2 */
+ /* WS3, SD1, TSL2 - Analog out #3 */
+ /* WS4, SD3, TSL2 - Analog out #4 */
+
+ /* Audiowerk8 timing: */
+ /* Timeslot: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | ... */
+
+ /* A1_INPUT: */
+ /* SD4: <_ADC-L_>-------<_ADC-R_>-------< */
+ /* WS0: _______________/---------------\_ */
+
+ /* A1_OUTPUT: */
+ /* SD0: <_1-L___>-------<_1-R___>-------< */
+ /* WS1: _______________/---------------\_ */
+ /* SD2: >-------<_2-L___>-------<_2-R___> */
+ /* WS2: -------\_______________/--------- */
+
+ /* A2_OUTPUT: */
+ /* SD1: <_3-L___>-------<_3-R___>-------< */
+ /* WS3: _______________/---------------\_ */
+ /* SD3: >-------<_4-L___>-------<_4-R___> */
+ /* WS4: -------\_______________/--------- */
+
+static int tsl1[8] = {
+ 1 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_LF_A1,
+
+ 1 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+ 0 * TSL_SDW_A1 | 3 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+ 0 * TSL_SDW_A1 | 2 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1,
+
+ 1 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+ 1 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+ 0 * TSL_SDW_A1 | 1 * TSL_BSEL_A1 |
+ 0 * TSL_DIS_A1 | 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0,
+
+ 0 * TSL_SDW_A1 | 0 * TSL_BSEL_A1 | 0 * TSL_DIS_A1 |
+ 0 * TSL_DOD_A1 | TSL_WS1 | TSL_WS0 | TSL_SF_A1 | TSL_EOS,
+};
+
+static int tsl2[8] = {
+ 0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_LF_A2,
+ 0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+ 0 * TSL_SDW_A2 | 3 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+ 0 * TSL_SDW_A2 | 2 * TSL_BSEL_A2 | 2 * TSL_DOD_A2,
+ 0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+ 0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+ 0 * TSL_SDW_A2 | 1 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2,
+ 0 * TSL_SDW_A2 | 0 * TSL_BSEL_A2 | 2 * TSL_DOD_A2 | TSL_WS2 | TSL_EOS
+};
diff --git a/sound/pci/aw2/saa7146.h b/sound/pci/aw2/saa7146.h
new file mode 100644
index 000000000..ce0ab5f9e
--- /dev/null
+++ b/sound/pci/aw2/saa7146.h
@@ -0,0 +1,168 @@
+/*****************************************************************************
+ *
+ * Copyright (C) 2008 Cedric Bregardis <cedric.bregardis@free.fr> and
+ * Jean-Christian Hassler <jhassler@free.fr>
+ *
+ * This file is part of the Audiowerk2 ALSA driver
+ *
+ * The Audiowerk2 ALSA driver 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; version 2.
+ *
+ * The Audiowerk2 ALSA driver 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 the Audiowerk2 ALSA driver; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ *
+ *****************************************************************************/
+
+/* SAA7146 registers */
+#define PCI_BT_A 0x4C
+#define IICTFR 0x8C
+#define IICSTA 0x90
+#define BaseA1_in 0x94
+#define ProtA1_in 0x98
+#define PageA1_in 0x9C
+#define BaseA1_out 0xA0
+#define ProtA1_out 0xA4
+#define PageA1_out 0xA8
+#define BaseA2_in 0xAC
+#define ProtA2_in 0xB0
+#define PageA2_in 0xB4
+#define BaseA2_out 0xB8
+#define ProtA2_out 0xBC
+#define PageA2_out 0xC0
+#define IER 0xDC
+#define GPIO_CTRL 0xE0
+#define ACON1 0xF4
+#define ACON2 0xF8
+#define MC1 0xFC
+#define MC2 0x100
+#define ISR 0x10C
+#define PSR 0x110
+#define SSR 0x114
+#define PCI_ADP1 0x12C
+#define PCI_ADP2 0x130
+#define PCI_ADP3 0x134
+#define PCI_ADP4 0x138
+#define LEVEL_REP 0x140
+#define FB_BUFFER1 0x144
+#define FB_BUFFER2 0x148
+#define TSL1 0x180
+#define TSL2 0x1C0
+
+#define ME (1UL << 11)
+#define LIMIT (1UL << 4)
+#define PV (1UL << 3)
+
+/* PSR/ISR/IER */
+#define PPEF (1UL << 31)
+#define PABO (1UL << 30)
+#define IIC_S (1UL << 17)
+#define IIC_E (1UL << 16)
+#define A2_in (1UL << 15)
+#define A2_out (1UL << 14)
+#define A1_in (1UL << 13)
+#define A1_out (1UL << 12)
+#define AFOU (1UL << 11)
+#define PIN3 (1UL << 6)
+#define PIN2 (1UL << 5)
+#define PIN1 (1UL << 4)
+#define PIN0 (1UL << 3)
+#define ECS (1UL << 2)
+#define EC3S (1UL << 1)
+#define EC0S (1UL << 0)
+
+/* SSR */
+#define PRQ (1UL << 31)
+#define PMA (1UL << 30)
+#define IIC_EA (1UL << 21)
+#define IIC_EW (1UL << 20)
+#define IIC_ER (1UL << 19)
+#define IIC_EL (1UL << 18)
+#define IIC_EF (1UL << 17)
+#define AF2_in (1UL << 10)
+#define AF2_out (1UL << 9)
+#define AF1_in (1UL << 8)
+#define AF1_out (1UL << 7)
+#define EC5S (1UL << 3)
+#define EC4S (1UL << 2)
+#define EC2S (1UL << 1)
+#define EC1S (1UL << 0)
+
+/* PCI_BT_A */
+#define BurstA1_in (1UL << 26)
+#define ThreshA1_in (1UL << 24)
+#define BurstA1_out (1UL << 18)
+#define ThreshA1_out (1UL << 16)
+#define BurstA2_in (1UL << 10)
+#define ThreshA2_in (1UL << 8)
+#define BurstA2_out (1UL << 2)
+#define ThreshA2_out (1UL << 0)
+
+/* MC1 */
+#define MRST_N (1UL << 15)
+#define EAP (1UL << 9)
+#define EI2C (1UL << 8)
+#define TR_E_A2_OUT (1UL << 3)
+#define TR_E_A2_IN (1UL << 2)
+#define TR_E_A1_OUT (1UL << 1)
+#define TR_E_A1_IN (1UL << 0)
+
+/* MC2 */
+#define UPLD_IIC (1UL << 0)
+
+/* ACON1 */
+#define AUDIO_MODE (1UL << 29)
+#define MAXLEVEL (1UL << 22)
+#define A1_SWAP (1UL << 21)
+#define A2_SWAP (1UL << 20)
+#define WS0_CTRL (1UL << 18)
+#define WS0_SYNC (1UL << 16)
+#define WS1_CTRL (1UL << 14)
+#define WS1_SYNC (1UL << 12)
+#define WS2_CTRL (1UL << 10)
+#define WS2_SYNC (1UL << 8)
+#define WS3_CTRL (1UL << 6)
+#define WS3_SYNC (1UL << 4)
+#define WS4_CTRL (1UL << 2)
+#define WS4_SYNC (1UL << 0)
+
+/* ACON2 */
+#define A1_CLKSRC (1UL << 27)
+#define A2_CLKSRC (1UL << 22)
+#define INVERT_BCLK1 (1UL << 21)
+#define INVERT_BCLK2 (1UL << 20)
+#define BCLK1_OEN (1UL << 19)
+#define BCLK2_OEN (1UL << 18)
+
+/* IICSTA */
+#define IICCC (1UL << 8)
+#define ABORT (1UL << 7)
+#define SPERR (1UL << 6)
+#define APERR (1UL << 5)
+#define DTERR (1UL << 4)
+#define DRERR (1UL << 3)
+#define AL (1UL << 2)
+#define ERR (1UL << 1)
+#define BUSY (1UL << 0)
+
+/* IICTFR */
+#define BYTE2 (1UL << 24)
+#define BYTE1 (1UL << 16)
+#define BYTE0 (1UL << 8)
+#define ATRR2 (1UL << 6)
+#define ATRR1 (1UL << 4)
+#define ATRR0 (1UL << 2)
+#define ERR (1UL << 1)
+#define BUSY (1UL << 0)
+
+#define START 3
+#define CONT 2
+#define STOP 1
+#define NOP 0