Переглянути джерело

Merge branch 'topic/lola' into for-linus

Takashi Iwai 14 роки тому
батько
коміт
b759b3ac9a

+ 7 - 0
Documentation/sound/alsa/ALSA-Configuration.txt

@@ -1230,6 +1230,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
     This module supports multiple cards.
     This module supports multiple cards.
     The driver requires the firmware loader support on kernel.
     The driver requires the firmware loader support on kernel.
 
 
+  Module snd-lola
+  ---------------
+
+    Module for Digigram Lola PCI-e boards
+
+    This module supports multiple cards.
+
   Module snd-lx6464es
   Module snd-lx6464es
   -------------------
   -------------------
 
 

+ 9 - 0
sound/pci/Kconfig

@@ -658,6 +658,15 @@ config SND_KORG1212
 	  To compile this driver as a module, choose M here: the module
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-korg1212.
 	  will be called snd-korg1212.
 
 
+config SND_LOLA
+	tristate "Digigram Lola"
+	select SND_PCM
+	help
+	  Say Y to include support for Digigram Lola boards.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-lola.
+
 config SND_LX6464ES
 config SND_LX6464ES
 	tristate "Digigram LX6464ES"
 	tristate "Digigram LX6464ES"
 	select SND_PCM
 	select SND_PCM

+ 1 - 0
sound/pci/Makefile

@@ -64,6 +64,7 @@ obj-$(CONFIG_SND) += \
 	ca0106/ \
 	ca0106/ \
 	cs46xx/ \
 	cs46xx/ \
 	cs5535audio/ \
 	cs5535audio/ \
+	lola/ \
 	lx6464es/ \
 	lx6464es/ \
 	echoaudio/ \
 	echoaudio/ \
 	emu10k1/ \
 	emu10k1/ \

+ 4 - 0
sound/pci/lola/Makefile

@@ -0,0 +1,4 @@
+snd-lola-y := lola.o lola_pcm.o lola_clock.o lola_mixer.o
+snd-lola-$(CONFIG_SND_DEBUG) += lola_proc.o
+
+obj-$(CONFIG_SND_LOLA) += snd-lola.o

+ 791 - 0
sound/pci/lola/lola.c

@@ -0,0 +1,791 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/moduleparam.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include "lola.h"
+
+/* Standard options */
+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_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Digigram Lola driver.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Digigram Lola driver.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Digigram Lola driver.");
+
+/* Lola-specific options */
+
+/* for instance use always max granularity which is compatible
+ * with all sample rates
+ */
+static int granularity[SNDRV_CARDS] = {
+	[0 ... (SNDRV_CARDS - 1)] = LOLA_GRANULARITY_MAX
+};
+
+/* below a sample_rate of 16kHz the analogue audio quality is NOT excellent */
+static int sample_rate_min[SNDRV_CARDS] = {
+	[0 ... (SNDRV_CARDS - 1) ] = 16000
+};
+
+module_param_array(granularity, int, NULL, 0444);
+MODULE_PARM_DESC(granularity, "Granularity value");
+module_param_array(sample_rate_min, int, NULL, 0444);
+MODULE_PARM_DESC(sample_rate_min, "Minimal sample rate");
+
+/*
+ */
+
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Digigram, Lola}}");
+MODULE_DESCRIPTION("Digigram Lola driver");
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+static int debug;
+module_param(debug, int, 0644);
+#define verbose_debug(fmt, args...)			\
+	do { if (debug > 1) printk(KERN_DEBUG SFX fmt, ##args); } while (0)
+#else
+#define verbose_debug(fmt, args...)
+#endif
+
+/*
+ * pseudo-codec read/write via CORB/RIRB
+ */
+
+static int corb_send_verb(struct lola *chip, unsigned int nid,
+			  unsigned int verb, unsigned int data,
+			  unsigned int extdata)
+{
+	unsigned long flags;
+	int ret = -EIO;
+
+	chip->last_cmd_nid = nid;
+	chip->last_verb = verb;
+	chip->last_data = data;
+	chip->last_extdata = extdata;
+	data |= (nid << 20) | (verb << 8);
+
+	spin_lock_irqsave(&chip->reg_lock, flags);
+	if (chip->rirb.cmds < LOLA_CORB_ENTRIES - 1) {
+		unsigned int wp = chip->corb.wp + 1;
+		wp %= LOLA_CORB_ENTRIES;
+		chip->corb.wp = wp;
+		chip->corb.buf[wp * 2] = cpu_to_le32(data);
+		chip->corb.buf[wp * 2 + 1] = cpu_to_le32(extdata);
+		lola_writew(chip, BAR0, CORBWP, wp);
+		chip->rirb.cmds++;
+		smp_wmb();
+		ret = 0;
+	}
+	spin_unlock_irqrestore(&chip->reg_lock, flags);
+	return ret;
+}
+
+static void lola_queue_unsol_event(struct lola *chip, unsigned int res,
+				   unsigned int res_ex)
+{
+	lola_update_ext_clock_freq(chip, res);
+}
+
+/* retrieve RIRB entry - called from interrupt handler */
+static void lola_update_rirb(struct lola *chip)
+{
+	unsigned int rp, wp;
+	u32 res, res_ex;
+
+	wp = lola_readw(chip, BAR0, RIRBWP);
+	if (wp == chip->rirb.wp)
+		return;
+	chip->rirb.wp = wp;
+
+	while (chip->rirb.rp != wp) {
+		chip->rirb.rp++;
+		chip->rirb.rp %= LOLA_CORB_ENTRIES;
+
+		rp = chip->rirb.rp << 1; /* an RIRB entry is 8-bytes */
+		res_ex = le32_to_cpu(chip->rirb.buf[rp + 1]);
+		res = le32_to_cpu(chip->rirb.buf[rp]);
+		if (res_ex & LOLA_RIRB_EX_UNSOL_EV)
+			lola_queue_unsol_event(chip, res, res_ex);
+		else if (chip->rirb.cmds) {
+			chip->res = res;
+			chip->res_ex = res_ex;
+			smp_wmb();
+			chip->rirb.cmds--;
+		}
+	}
+}
+
+static int rirb_get_response(struct lola *chip, unsigned int *val,
+			     unsigned int *extval)
+{
+	unsigned long timeout;
+
+ again:
+	timeout = jiffies + msecs_to_jiffies(1000);
+	for (;;) {
+		if (chip->polling_mode) {
+			spin_lock_irq(&chip->reg_lock);
+			lola_update_rirb(chip);
+			spin_unlock_irq(&chip->reg_lock);
+		}
+		if (!chip->rirb.cmds) {
+			*val = chip->res;
+			if (extval)
+				*extval = chip->res_ex;
+			verbose_debug("get_response: %x, %x\n",
+				      chip->res, chip->res_ex);
+			if (chip->res_ex & LOLA_RIRB_EX_ERROR) {
+				printk(KERN_WARNING SFX "RIRB ERROR: "
+				       "NID=%x, verb=%x, data=%x, ext=%x\n",
+				       chip->last_cmd_nid,
+				       chip->last_verb, chip->last_data,
+				       chip->last_extdata);
+				return -EIO;
+			}
+			return 0;
+		}
+		if (time_after(jiffies, timeout))
+			break;
+		udelay(20);
+		cond_resched();
+	}
+	printk(KERN_WARNING SFX "RIRB response error\n");
+	if (!chip->polling_mode) {
+		printk(KERN_WARNING SFX "switching to polling mode\n");
+		chip->polling_mode = 1;
+		goto again;
+	}
+	return -EIO;
+}
+
+/* aynchronous write of a codec verb with data */
+int lola_codec_write(struct lola *chip, unsigned int nid, unsigned int verb,
+		     unsigned int data, unsigned int extdata)
+{
+	verbose_debug("codec_write NID=%x, verb=%x, data=%x, ext=%x\n",
+		      nid, verb, data, extdata);
+	return corb_send_verb(chip, nid, verb, data, extdata);
+}
+
+/* write a codec verb with data and read the returned status */
+int lola_codec_read(struct lola *chip, unsigned int nid, unsigned int verb,
+		    unsigned int data, unsigned int extdata,
+		    unsigned int *val, unsigned int *extval)
+{
+	int err;
+
+	verbose_debug("codec_read NID=%x, verb=%x, data=%x, ext=%x\n",
+		      nid, verb, data, extdata);
+	err = corb_send_verb(chip, nid, verb, data, extdata);
+	if (err < 0)
+		return err;
+	err = rirb_get_response(chip, val, extval);
+	return err;
+}
+
+/* flush all pending codec writes */
+int lola_codec_flush(struct lola *chip)
+{
+	unsigned int tmp;
+	return rirb_get_response(chip, &tmp, NULL);
+}
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t lola_interrupt(int irq, void *dev_id)
+{
+	struct lola *chip = dev_id;
+	unsigned int notify_ins, notify_outs, error_ins, error_outs;
+	int handled = 0;
+	int i;
+
+	notify_ins = notify_outs = error_ins = error_outs = 0;
+	spin_lock(&chip->reg_lock);
+	for (;;) {
+		unsigned int status, in_sts, out_sts;
+		unsigned int reg;
+
+		status = lola_readl(chip, BAR1, DINTSTS);
+		if (!status || status == -1)
+			break;
+
+		in_sts = lola_readl(chip, BAR1, DIINTSTS);
+		out_sts = lola_readl(chip, BAR1, DOINTSTS);
+
+		/* clear Input Interrupts */
+		for (i = 0; in_sts && i < chip->pcm[CAPT].num_streams; i++) {
+			if (!(in_sts & (1 << i)))
+				continue;
+			in_sts &= ~(1 << i);
+			reg = lola_dsd_read(chip, i, STS);
+			if (reg & LOLA_DSD_STS_DESE) /* error */
+				error_ins |= (1 << i);
+			if (reg & LOLA_DSD_STS_BCIS) /* notify */
+				notify_ins |= (1 << i);
+			/* clear */
+			lola_dsd_write(chip, i, STS, reg);
+		}
+
+		/* clear Output Interrupts */
+		for (i = 0; out_sts && i < chip->pcm[PLAY].num_streams; i++) {
+			if (!(out_sts & (1 << i)))
+				continue;
+			out_sts &= ~(1 << i);
+			reg = lola_dsd_read(chip, i + MAX_STREAM_IN_COUNT, STS);
+			if (reg & LOLA_DSD_STS_DESE) /* error */
+				error_outs |= (1 << i);
+			if (reg & LOLA_DSD_STS_BCIS) /* notify */
+				notify_outs |= (1 << i);
+			lola_dsd_write(chip, i + MAX_STREAM_IN_COUNT, STS, reg);
+		}
+
+		if (status & LOLA_DINT_CTRL) {
+			unsigned char rbsts; /* ring status is byte access */
+			rbsts = lola_readb(chip, BAR0, RIRBSTS);
+			rbsts &= LOLA_RIRB_INT_MASK;
+			if (rbsts)
+				lola_writeb(chip, BAR0, RIRBSTS, rbsts);
+			rbsts = lola_readb(chip, BAR0, CORBSTS);
+			rbsts &= LOLA_CORB_INT_MASK;
+			if (rbsts)
+				lola_writeb(chip, BAR0, CORBSTS, rbsts);
+
+			lola_update_rirb(chip);
+		}
+
+		if (status & (LOLA_DINT_FIFOERR | LOLA_DINT_MUERR)) {
+			/* clear global fifo error interrupt */
+			lola_writel(chip, BAR1, DINTSTS,
+				    (status & (LOLA_DINT_FIFOERR | LOLA_DINT_MUERR)));
+		}
+		handled = 1;
+	}
+	spin_unlock(&chip->reg_lock);
+
+	lola_pcm_update(chip, &chip->pcm[CAPT], notify_ins);
+	lola_pcm_update(chip, &chip->pcm[PLAY], notify_outs);
+
+	return IRQ_RETVAL(handled);
+}
+
+
+/*
+ * controller
+ */
+static int reset_controller(struct lola *chip)
+{
+	unsigned int gctl = lola_readl(chip, BAR0, GCTL);
+	unsigned long end_time;
+
+	if (gctl) {
+		/* to be sure */
+		lola_writel(chip, BAR1, BOARD_MODE, 0);
+		return 0;
+	}
+
+	chip->cold_reset = 1;
+	lola_writel(chip, BAR0, GCTL, LOLA_GCTL_RESET);
+	end_time = jiffies + msecs_to_jiffies(200);
+	do {
+		msleep(1);
+		gctl = lola_readl(chip, BAR0, GCTL);
+		if (gctl)
+			break;
+	} while (time_before(jiffies, end_time));
+	if (!gctl) {
+		printk(KERN_ERR SFX "cannot reset controller\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+static void lola_irq_enable(struct lola *chip)
+{
+	unsigned int val;
+
+	/* enalbe all I/O streams */
+	val = (1 << chip->pcm[PLAY].num_streams) - 1;
+	lola_writel(chip, BAR1, DOINTCTL, val);
+	val = (1 << chip->pcm[CAPT].num_streams) - 1;
+	lola_writel(chip, BAR1, DIINTCTL, val);
+
+	/* enable global irqs */
+	val = LOLA_DINT_GLOBAL | LOLA_DINT_CTRL | LOLA_DINT_FIFOERR |
+		LOLA_DINT_MUERR;
+	lola_writel(chip, BAR1, DINTCTL, val);
+}
+
+static void lola_irq_disable(struct lola *chip)
+{
+	lola_writel(chip, BAR1, DINTCTL, 0);
+	lola_writel(chip, BAR1, DIINTCTL, 0);
+	lola_writel(chip, BAR1, DOINTCTL, 0);
+}
+
+static int setup_corb_rirb(struct lola *chip)
+{
+	int err;
+	unsigned char tmp;
+	unsigned long end_time;
+
+	err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+				  snd_dma_pci_data(chip->pci),
+				  PAGE_SIZE, &chip->rb);
+	if (err < 0)
+		return err;
+
+	chip->corb.addr = chip->rb.addr;
+	chip->corb.buf = (u32 *)chip->rb.area;
+	chip->rirb.addr = chip->rb.addr + 2048;
+	chip->rirb.buf = (u32 *)(chip->rb.area + 2048);
+
+	/* disable ringbuffer DMAs */
+	lola_writeb(chip, BAR0, RIRBCTL, 0);
+	lola_writeb(chip, BAR0, CORBCTL, 0);
+
+	end_time = jiffies + msecs_to_jiffies(200);
+	do {
+		if (!lola_readb(chip, BAR0, RIRBCTL) &&
+		    !lola_readb(chip, BAR0, CORBCTL))
+			break;
+		msleep(1);
+	} while (time_before(jiffies, end_time));
+
+	/* CORB set up */
+	lola_writel(chip, BAR0, CORBLBASE, (u32)chip->corb.addr);
+	lola_writel(chip, BAR0, CORBUBASE, upper_32_bits(chip->corb.addr));
+	/* set the corb size to 256 entries */
+	lola_writeb(chip, BAR0, CORBSIZE, 0x02);
+	/* set the corb write pointer to 0 */
+	lola_writew(chip, BAR0, CORBWP, 0);
+	/* reset the corb hw read pointer */
+	lola_writew(chip, BAR0, CORBRP, LOLA_RBRWP_CLR);
+	/* enable corb dma */
+	lola_writeb(chip, BAR0, CORBCTL, LOLA_RBCTL_DMA_EN);
+	/* clear flags if set */
+	tmp = lola_readb(chip, BAR0, CORBSTS) & LOLA_CORB_INT_MASK;
+	if (tmp)
+		lola_writeb(chip, BAR0, CORBSTS, tmp);
+	chip->corb.wp = 0;
+
+	/* RIRB set up */
+	lola_writel(chip, BAR0, RIRBLBASE, (u32)chip->rirb.addr);
+	lola_writel(chip, BAR0, RIRBUBASE, upper_32_bits(chip->rirb.addr));
+	/* set the rirb size to 256 entries */
+	lola_writeb(chip, BAR0, RIRBSIZE, 0x02);
+	/* reset the rirb hw write pointer */
+	lola_writew(chip, BAR0, RIRBWP, LOLA_RBRWP_CLR);
+	/* set N=1, get RIRB response interrupt for new entry */
+	lola_writew(chip, BAR0, RINTCNT, 1);
+	/* enable rirb dma and response irq */
+	lola_writeb(chip, BAR0, RIRBCTL, LOLA_RBCTL_DMA_EN | LOLA_RBCTL_IRQ_EN);
+	/* clear flags if set */
+	tmp =  lola_readb(chip, BAR0, RIRBSTS) & LOLA_RIRB_INT_MASK;
+	if (tmp)
+		lola_writeb(chip, BAR0, RIRBSTS, tmp);
+	chip->rirb.rp = chip->rirb.cmds = 0;
+
+	return 0;
+}
+
+static void stop_corb_rirb(struct lola *chip)
+{
+	/* disable ringbuffer DMAs */
+	lola_writeb(chip, BAR0, RIRBCTL, 0);
+	lola_writeb(chip, BAR0, CORBCTL, 0);
+}
+
+static void lola_reset_setups(struct lola *chip)
+{
+	/* update the granularity */
+	lola_set_granularity(chip, chip->granularity, true);
+	/* update the sample clock */
+	lola_set_clock_index(chip, chip->clock.cur_index);
+	/* enable unsolicited events of the clock widget */
+	lola_enable_clock_events(chip);
+	/* update the analog gains */
+	lola_setup_all_analog_gains(chip, CAPT, false); /* input, update */
+	/* update SRC configuration if applicable */
+	lola_set_src_config(chip, chip->input_src_mask, false);
+	/* update the analog outputs */
+	lola_setup_all_analog_gains(chip, PLAY, false); /* output, update */
+}
+
+static int lola_parse_tree(struct lola *chip)
+{
+	unsigned int val;
+	int nid, err;
+
+	err = lola_read_param(chip, 0, LOLA_PAR_VENDOR_ID, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read VENDOR_ID\n");
+		return err;
+	}
+	val >>= 16;
+	if (val != 0x1369) {
+		printk(KERN_ERR SFX "Unknown codec vendor 0x%x\n", val);
+		return -EINVAL;
+	}
+
+	err = lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read FUNCTION_TYPE for 0x%x\n", nid);
+		return err;
+	}
+	if (val != 1) {
+		printk(KERN_ERR SFX "Unknown function type %d\n", val);
+		return -EINVAL;
+	}
+
+	err = lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read SPECCAPS\n");
+		return err;
+	}
+	chip->lola_caps = val;
+	chip->pin[CAPT].num_pins = LOLA_AFG_INPUT_PIN_COUNT(chip->lola_caps);
+	chip->pin[PLAY].num_pins = LOLA_AFG_OUTPUT_PIN_COUNT(chip->lola_caps);
+	snd_printdd(SFX "speccaps=0x%x, pins in=%d, out=%d\n",
+		    chip->lola_caps,
+		    chip->pin[CAPT].num_pins, chip->pin[PLAY].num_pins);
+
+	if (chip->pin[CAPT].num_pins > MAX_AUDIO_INOUT_COUNT ||
+	    chip->pin[PLAY].num_pins > MAX_AUDIO_INOUT_COUNT) {
+		printk(KERN_ERR SFX "Invalid Lola-spec caps 0x%x\n", val);
+		return -EINVAL;
+	}
+
+	nid = 0x02;
+	err = lola_init_pcm(chip, CAPT, &nid);
+	if (err < 0)
+		return err;
+	err = lola_init_pcm(chip, PLAY, &nid);
+	if (err < 0)
+		return err;
+
+	err = lola_init_pins(chip, CAPT, &nid);
+	if (err < 0)
+		return err;
+	err = lola_init_pins(chip, PLAY, &nid);
+	if (err < 0)
+		return err;
+
+	if (LOLA_AFG_CLOCK_WIDGET_PRESENT(chip->lola_caps)) {
+		err = lola_init_clock_widget(chip, nid);
+		if (err < 0)
+			return err;
+		nid++;
+	}
+	if (LOLA_AFG_MIXER_WIDGET_PRESENT(chip->lola_caps)) {
+		err = lola_init_mixer_widget(chip, nid);
+		if (err < 0)
+			return err;
+		nid++;
+	}
+
+	/* enable unsolicited events of the clock widget */
+	err = lola_enable_clock_events(chip);
+	if (err < 0)
+		return err;
+
+	/* if last ResetController was not a ColdReset, we don't know
+	 * the state of the card; initialize here again
+	 */
+	if (!chip->cold_reset) {
+		lola_reset_setups(chip);
+		chip->cold_reset = 1;
+	} else {
+		/* set the granularity if it is not the default */
+		if (chip->granularity != LOLA_GRANULARITY_MIN)
+			lola_set_granularity(chip, chip->granularity, true);
+	}
+
+	return 0;
+}
+
+static void lola_stop_hw(struct lola *chip)
+{
+	stop_corb_rirb(chip);
+	lola_irq_disable(chip);
+}
+
+static void lola_free(struct lola *chip)
+{
+	if (chip->initialized)
+		lola_stop_hw(chip);
+	lola_free_pcm(chip);
+	lola_free_mixer(chip);
+	if (chip->irq >= 0)
+		free_irq(chip->irq, (void *)chip);
+	if (chip->bar[0].remap_addr)
+		iounmap(chip->bar[0].remap_addr);
+	if (chip->bar[1].remap_addr)
+		iounmap(chip->bar[1].remap_addr);
+	if (chip->rb.area)
+		snd_dma_free_pages(&chip->rb);
+	pci_release_regions(chip->pci);
+	pci_disable_device(chip->pci);
+	kfree(chip);
+}
+
+static int lola_dev_free(struct snd_device *device)
+{
+	lola_free(device->device_data);
+	return 0;
+}
+
+static int __devinit lola_create(struct snd_card *card, struct pci_dev *pci,
+				 int dev, struct lola **rchip)
+{
+	struct lola *chip;
+	int err;
+	unsigned int dever;
+	static struct snd_device_ops ops = {
+		.dev_free = lola_dev_free,
+	};
+
+	*rchip = NULL;
+
+	err = pci_enable_device(pci);
+	if (err < 0)
+		return err;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		snd_printk(KERN_ERR SFX "cannot allocate chip\n");
+		pci_disable_device(pci);
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&chip->reg_lock);
+	mutex_init(&chip->open_mutex);
+	chip->card = card;
+	chip->pci = pci;
+	chip->irq = -1;
+
+	chip->granularity = granularity[dev];
+	switch (chip->granularity) {
+	case 8:
+		chip->sample_rate_max = 48000;
+		break;
+	case 16:
+		chip->sample_rate_max = 96000;
+		break;
+	case 32:
+		chip->sample_rate_max = 192000;
+		break;
+	default:
+		snd_printk(KERN_WARNING SFX
+			   "Invalid granularity %d, reset to %d\n",
+			   chip->granularity, LOLA_GRANULARITY_MAX);
+		chip->granularity = LOLA_GRANULARITY_MAX;
+		chip->sample_rate_max = 192000;
+		break;
+	}
+	chip->sample_rate_min = sample_rate_min[dev];
+	if (chip->sample_rate_min > chip->sample_rate_max) {
+		snd_printk(KERN_WARNING SFX
+			   "Invalid sample_rate_min %d, reset to 16000\n",
+			   chip->sample_rate_min);
+		chip->sample_rate_min = 16000;
+	}
+
+	err = pci_request_regions(pci, DRVNAME);
+	if (err < 0) {
+		kfree(chip);
+		pci_disable_device(pci);
+		return err;
+	}
+
+	chip->bar[0].addr = pci_resource_start(pci, 0);
+	chip->bar[0].remap_addr = pci_ioremap_bar(pci, 0);
+	chip->bar[1].addr = pci_resource_start(pci, 2);
+	chip->bar[1].remap_addr = pci_ioremap_bar(pci, 2);
+	if (!chip->bar[0].remap_addr || !chip->bar[1].remap_addr) {
+		snd_printk(KERN_ERR SFX "ioremap error\n");
+		err = -ENXIO;
+		goto errout;
+	}
+
+	pci_set_master(pci);
+
+	err = reset_controller(chip);
+	if (err < 0)
+		goto errout;
+
+	if (request_irq(pci->irq, lola_interrupt, IRQF_SHARED,
+			DRVNAME, chip)) {
+		printk(KERN_ERR SFX "unable to grab IRQ %d\n", pci->irq);
+		err = -EBUSY;
+		goto errout;
+	}
+	chip->irq = pci->irq;
+	synchronize_irq(chip->irq);
+
+	dever = lola_readl(chip, BAR1, DEVER);
+	chip->pcm[CAPT].num_streams = (dever >> 0) & 0x3ff;
+	chip->pcm[PLAY].num_streams = (dever >> 10) & 0x3ff;
+	chip->version = (dever >> 24) & 0xff;
+	snd_printdd(SFX "streams in=%d, out=%d, version=0x%x\n",
+		    chip->pcm[CAPT].num_streams, chip->pcm[PLAY].num_streams,
+		    chip->version);
+
+	/* Test LOLA_BAR1_DEVER */
+	if (chip->pcm[CAPT].num_streams > MAX_STREAM_IN_COUNT ||
+	    chip->pcm[PLAY].num_streams > MAX_STREAM_OUT_COUNT ||
+	    (!chip->pcm[CAPT].num_streams &&
+	     !chip->pcm[PLAY].num_streams)) {
+		printk(KERN_ERR SFX "invalid DEVER = %x\n", dever);
+		err = -EINVAL;
+		goto errout;
+	}
+
+	err = setup_corb_rirb(chip);
+	if (err < 0)
+		goto errout;
+
+	err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (err < 0) {
+		snd_printk(KERN_ERR SFX "Error creating device [card]!\n");
+		goto errout;
+	}
+
+	strcpy(card->driver, "Lola");
+	strlcpy(card->shortname, "Digigram Lola", sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s at 0x%lx irq %i",
+		 card->shortname, chip->bar[0].addr, chip->irq);
+	strcpy(card->mixername, card->shortname);
+
+	lola_irq_enable(chip);
+
+	chip->initialized = 1;
+	*rchip = chip;
+	return 0;
+
+ errout:
+	lola_free(chip);
+	return err;
+}
+
+static int __devinit lola_probe(struct pci_dev *pci,
+				const struct pci_device_id *pci_id)
+{
+	static int dev;
+	struct snd_card *card;
+	struct lola *chip;
+	int err;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+
+	err = snd_card_create(index[dev], id[dev], THIS_MODULE, 0, &card);
+	if (err < 0) {
+		snd_printk(KERN_ERR SFX "Error creating card!\n");
+		return err;
+	}
+
+	snd_card_set_dev(card, &pci->dev);
+
+	err = lola_create(card, pci, dev, &chip);
+	if (err < 0)
+		goto out_free;
+	card->private_data = chip;
+
+	err = lola_parse_tree(chip);
+	if (err < 0)
+		goto out_free;
+
+	err = lola_create_pcm(chip);
+	if (err < 0)
+		goto out_free;
+
+	err = lola_create_mixer(chip);
+	if (err < 0)
+		goto out_free;
+
+	lola_proc_debug_new(chip);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto out_free;
+
+	pci_set_drvdata(pci, card);
+	dev++;
+	return err;
+out_free:
+	snd_card_free(card);
+	return err;
+}
+
+static void __devexit lola_remove(struct pci_dev *pci)
+{
+	snd_card_free(pci_get_drvdata(pci));
+	pci_set_drvdata(pci, NULL);
+}
+
+/* PCI IDs */
+static DEFINE_PCI_DEVICE_TABLE(lola_ids) = {
+	{ PCI_VDEVICE(DIGIGRAM, 0x0001) },
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, lola_ids);
+
+/* pci_driver definition */
+static struct pci_driver driver = {
+	.name = DRVNAME,
+	.id_table = lola_ids,
+	.probe = lola_probe,
+	.remove = __devexit_p(lola_remove),
+};
+
+static int __init alsa_card_lola_init(void)
+{
+	return pci_register_driver(&driver);
+}
+
+static void __exit alsa_card_lola_exit(void)
+{
+	pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_lola_init)
+module_exit(alsa_card_lola_exit)

+ 527 - 0
sound/pci/lola/lola.h

@@ -0,0 +1,527 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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.
+ */
+
+#ifndef _LOLA_H
+#define _LOLA_H
+
+#define DRVNAME	"snd-lola"
+#define SFX	DRVNAME ": "
+
+/*
+ * Lola HD Audio Registers BAR0
+ */
+#define LOLA_BAR0_GCAP		0x00
+#define LOLA_BAR0_VMIN		0x02
+#define LOLA_BAR0_VMAJ		0x03
+#define LOLA_BAR0_OUTPAY	0x04
+#define LOLA_BAR0_INPAY		0x06
+#define LOLA_BAR0_GCTL		0x08
+#define LOLA_BAR0_WAKEEN	0x0c
+#define LOLA_BAR0_STATESTS	0x0e
+#define LOLA_BAR0_GSTS		0x10
+#define LOLA_BAR0_OUTSTRMPAY	0x18
+#define LOLA_BAR0_INSTRMPAY	0x1a
+#define LOLA_BAR0_INTCTL	0x20
+#define LOLA_BAR0_INTSTS	0x24
+#define LOLA_BAR0_WALCLK	0x30
+#define LOLA_BAR0_SSYNC		0x38
+
+#define LOLA_BAR0_CORBLBASE	0x40
+#define LOLA_BAR0_CORBUBASE	0x44
+#define LOLA_BAR0_CORBWP	0x48	/* no ULONG access */
+#define LOLA_BAR0_CORBRP	0x4a	/* no ULONG access */
+#define LOLA_BAR0_CORBCTL	0x4c	/* no ULONG access */
+#define LOLA_BAR0_CORBSTS	0x4d	/* UCHAR access only */
+#define LOLA_BAR0_CORBSIZE	0x4e	/* no ULONG access */
+
+#define LOLA_BAR0_RIRBLBASE	0x50
+#define LOLA_BAR0_RIRBUBASE	0x54
+#define LOLA_BAR0_RIRBWP	0x58
+#define LOLA_BAR0_RINTCNT	0x5a	/* no ULONG access */
+#define LOLA_BAR0_RIRBCTL	0x5c
+#define LOLA_BAR0_RIRBSTS	0x5d	/* UCHAR access only */
+#define LOLA_BAR0_RIRBSIZE	0x5e	/* no ULONG access */
+
+#define LOLA_BAR0_ICW		0x60
+#define LOLA_BAR0_IRR		0x64
+#define LOLA_BAR0_ICS		0x68
+#define LOLA_BAR0_DPLBASE	0x70
+#define LOLA_BAR0_DPUBASE	0x74
+
+/* stream register offsets from stream base 0x80 */
+#define LOLA_BAR0_SD0_OFFSET	0x80
+#define LOLA_REG0_SD_CTL	0x00
+#define LOLA_REG0_SD_STS	0x03
+#define LOLA_REG0_SD_LPIB	0x04
+#define LOLA_REG0_SD_CBL	0x08
+#define LOLA_REG0_SD_LVI	0x0c
+#define LOLA_REG0_SD_FIFOW	0x0e
+#define LOLA_REG0_SD_FIFOSIZE	0x10
+#define LOLA_REG0_SD_FORMAT	0x12
+#define LOLA_REG0_SD_BDLPL	0x18
+#define LOLA_REG0_SD_BDLPU	0x1c
+
+/*
+ * Lola Digigram Registers BAR1
+ */
+#define LOLA_BAR1_FPGAVER	0x00
+#define LOLA_BAR1_DEVER		0x04
+#define LOLA_BAR1_UCBMV		0x08
+#define LOLA_BAR1_JTAG		0x0c
+#define LOLA_BAR1_UARTRX	0x10
+#define LOLA_BAR1_UARTTX	0x14
+#define LOLA_BAR1_UARTCR	0x18
+#define LOLA_BAR1_NVRAMVER	0x1c
+#define LOLA_BAR1_CTRLSPI	0x20
+#define LOLA_BAR1_DSPI		0x24
+#define LOLA_BAR1_AISPI		0x28
+#define LOLA_BAR1_GRAN		0x2c
+
+#define LOLA_BAR1_DINTCTL	0x80
+#define LOLA_BAR1_DIINTCTL	0x84
+#define LOLA_BAR1_DOINTCTL	0x88
+#define LOLA_BAR1_LRC		0x90
+#define LOLA_BAR1_DINTSTS	0x94
+#define LOLA_BAR1_DIINTSTS	0x98
+#define LOLA_BAR1_DOINTSTS	0x9c
+
+#define LOLA_BAR1_DSD0_OFFSET	0xa0
+#define LOLA_BAR1_DSD_SIZE	0x18
+
+#define LOLA_BAR1_DSDnSTS       0x00
+#define LOLA_BAR1_DSDnLPIB      0x04
+#define LOLA_BAR1_DSDnCTL       0x08
+#define LOLA_BAR1_DSDnLVI       0x0c
+#define LOLA_BAR1_DSDnBDPL      0x10
+#define LOLA_BAR1_DSDnBDPU      0x14
+
+#define LOLA_BAR1_SSYNC		0x03e8
+
+#define LOLA_BAR1_BOARD_CTRL	0x0f00
+#define LOLA_BAR1_BOARD_MODE	0x0f02
+
+#define LOLA_BAR1_SOURCE_GAIN_ENABLE		0x1000
+#define LOLA_BAR1_DEST00_MIX_GAIN_ENABLE	0x1004
+#define LOLA_BAR1_DEST31_MIX_GAIN_ENABLE	0x1080
+#define LOLA_BAR1_SOURCE00_01_GAIN		0x1084
+#define LOLA_BAR1_SOURCE30_31_GAIN		0x10c0
+#define LOLA_BAR1_SOURCE_GAIN(src) \
+	(LOLA_BAR1_SOURCE00_01_GAIN + (src) * 2)
+#define LOLA_BAR1_DEST00_MIX00_01_GAIN		0x10c4
+#define LOLA_BAR1_DEST00_MIX30_31_GAIN		0x1100
+#define LOLA_BAR1_DEST01_MIX00_01_GAIN		0x1104
+#define LOLA_BAR1_DEST01_MIX30_31_GAIN		0x1140
+#define LOLA_BAR1_DEST31_MIX00_01_GAIN		0x1884
+#define LOLA_BAR1_DEST31_MIX30_31_GAIN		0x18c0
+#define LOLA_BAR1_MIX_GAIN(dest, mix) \
+	(LOLA_BAR1_DEST00_MIX00_01_GAIN + (dest) * 0x40 + (mix) * 2)
+#define LOLA_BAR1_ANALOG_CLIP_IN		0x18c4
+#define LOLA_BAR1_PEAKMETERS_SOURCE00_01	0x18c8
+#define LOLA_BAR1_PEAKMETERS_SOURCE30_31	0x1904
+#define LOLA_BAR1_PEAKMETERS_SOURCE(src) \
+	(LOLA_BAR1_PEAKMETERS_SOURCE00_01 + (src) * 2)
+#define LOLA_BAR1_PEAKMETERS_DEST00_01		0x1908
+#define LOLA_BAR1_PEAKMETERS_DEST30_31		0x1944
+#define LOLA_BAR1_PEAKMETERS_DEST(dest) \
+	(LOLA_BAR1_PEAKMETERS_DEST00_01 + (dest) * 2)
+#define LOLA_BAR1_PEAKMETERS_AGC00_01		0x1948
+#define LOLA_BAR1_PEAKMETERS_AGC14_15		0x1964
+#define LOLA_BAR1_PEAKMETERS_AGC(x) \
+	(LOLA_BAR1_PEAKMETERS_AGC00_01 + (x) * 2)
+
+/* GCTL reset bit */
+#define LOLA_GCTL_RESET		(1 << 0)
+/* GCTL unsolicited response enable bit */
+#define LOLA_GCTL_UREN		(1 << 8)
+
+/* CORB/RIRB control, read/write pointer */
+#define LOLA_RBCTL_DMA_EN	0x02	/* enable DMA */
+#define LOLA_RBCTL_IRQ_EN	0x01	/* enable IRQ */
+#define LOLA_RBRWP_CLR		0x8000	/* read/write pointer clear */
+
+#define LOLA_RIRB_EX_UNSOL_EV	0x40000000
+#define LOLA_RIRB_EX_ERROR	0x80000000
+
+/* CORB int mask: CMEI[0] */
+#define LOLA_CORB_INT_CMEI	0x01
+#define LOLA_CORB_INT_MASK	LOLA_CORB_INT_CMEI
+
+/* RIRB int mask: overrun[2], response[0] */
+#define LOLA_RIRB_INT_RESPONSE	0x01
+#define LOLA_RIRB_INT_OVERRUN	0x04
+#define LOLA_RIRB_INT_MASK	(LOLA_RIRB_INT_RESPONSE | LOLA_RIRB_INT_OVERRUN)
+
+/* DINTCTL and DINTSTS */
+#define LOLA_DINT_GLOBAL	0x80000000 /* global interrupt enable bit */
+#define LOLA_DINT_CTRL		0x40000000 /* controller interrupt enable bit */
+#define LOLA_DINT_FIFOERR	0x20000000 /* global fifo error enable bit */
+#define LOLA_DINT_MUERR		0x10000000 /* global microcontroller underrun error */
+
+/* DSDnCTL bits */
+#define LOLA_DSD_CTL_SRST	0x01	/* stream reset bit */
+#define LOLA_DSD_CTL_SRUN	0x02	/* stream DMA start bit */
+#define LOLA_DSD_CTL_IOCE	0x04	/* interrupt on completion enable */
+#define LOLA_DSD_CTL_DEIE	0x10	/* descriptor error interrupt enable */
+#define LOLA_DSD_CTL_VLRCV	0x20	/* valid LRCountValue information in bits 8..31 */
+#define LOLA_LRC_MASK		0xffffff00
+
+/* DSDnSTS */
+#define LOLA_DSD_STS_BCIS	0x04	/* buffer completion interrupt status */
+#define LOLA_DSD_STS_DESE	0x10	/* descriptor error interrupt */
+#define LOLA_DSD_STS_FIFORDY	0x20	/* fifo ready */
+
+#define LOLA_CORB_ENTRIES	256
+
+#define MAX_STREAM_IN_COUNT	16
+#define MAX_STREAM_OUT_COUNT	16
+#define MAX_STREAM_COUNT	16
+#define MAX_PINS		MAX_STREAM_COUNT
+#define MAX_STREAM_BUFFER_COUNT	16
+#define MAX_AUDIO_INOUT_COUNT	16
+
+#define LOLA_CLOCK_TYPE_INTERNAL    0
+#define LOLA_CLOCK_TYPE_AES         1
+#define LOLA_CLOCK_TYPE_AES_SYNC    2
+#define LOLA_CLOCK_TYPE_WORDCLOCK   3
+#define LOLA_CLOCK_TYPE_ETHERSOUND  4
+#define LOLA_CLOCK_TYPE_VIDEO       5
+
+#define LOLA_CLOCK_FORMAT_NONE      0
+#define LOLA_CLOCK_FORMAT_NTSC      1
+#define LOLA_CLOCK_FORMAT_PAL       2
+
+#define MAX_SAMPLE_CLOCK_COUNT  48
+
+/* parameters used with mixer widget's mixer capabilities */
+#define LOLA_PEAK_METER_CAN_AGC_MASK		1
+#define LOLA_PEAK_METER_CAN_ANALOG_CLIP_MASK	2
+
+struct lola_bar {
+	unsigned long addr;
+	void __iomem *remap_addr;
+};
+
+/* CORB/RIRB */
+struct lola_rb {
+	u32 *buf;		/* CORB/RIRB buffer, 8 byte per each entry */
+	dma_addr_t addr;	/* physical address of CORB/RIRB buffer */
+	unsigned short rp, wp;	/* read/write pointers */
+	int cmds;		/* number of pending requests */
+};
+
+/* Pin widget setup */
+struct lola_pin {
+	unsigned int nid;
+	bool is_analog;
+	unsigned int amp_mute;
+	unsigned int amp_step_size;
+	unsigned int amp_num_steps;
+	unsigned int amp_offset;
+	unsigned int max_level;
+	unsigned int config_default_reg;
+	unsigned int fixed_gain_list_len;
+	unsigned int cur_gain_step;
+};
+
+struct lola_pin_array {
+	unsigned int num_pins;
+	unsigned int num_analog_pins;
+	struct lola_pin pins[MAX_PINS];
+};
+
+/* Clock widget setup */
+struct lola_sample_clock {
+	unsigned int type;
+	unsigned int format;
+	unsigned int freq;
+};
+
+struct lola_clock_widget {
+	unsigned int nid;
+	unsigned int items;
+	unsigned int cur_index;
+	unsigned int cur_freq;
+	bool cur_valid;
+	struct lola_sample_clock sample_clock[MAX_SAMPLE_CLOCK_COUNT];
+	unsigned int idx_lookup[MAX_SAMPLE_CLOCK_COUNT];
+};
+
+#define LOLA_MIXER_DIM      32
+struct lola_mixer_array {
+	u32 src_gain_enable;
+	u32 dest_mix_gain_enable[LOLA_MIXER_DIM];
+	u16 src_gain[LOLA_MIXER_DIM];
+	u16 dest_mix_gain[LOLA_MIXER_DIM][LOLA_MIXER_DIM];
+};
+
+/* Mixer widget setup */
+struct lola_mixer_widget {
+	unsigned int nid;
+	unsigned int caps;
+	struct lola_mixer_array __user *array;
+	struct lola_mixer_array *array_saved;
+	unsigned int src_stream_outs;
+	unsigned int src_phys_ins;
+	unsigned int dest_stream_ins;
+	unsigned int dest_phys_outs;
+	unsigned int src_stream_out_ofs;
+	unsigned int dest_phys_out_ofs;
+	unsigned int src_mask;
+	unsigned int dest_mask;
+};
+
+/* Audio stream */
+struct lola_stream {
+	unsigned int nid;	/* audio widget NID */
+	unsigned int index;	/* array index */
+	unsigned int dsd;	/* DSD index */
+	bool can_float;
+	struct snd_pcm_substream *substream; /* assigned PCM substream */
+	struct lola_stream *master;	/* master stream (for multi-channel) */
+
+	/* buffer setup */
+	unsigned int bufsize;
+	unsigned int period_bytes;
+	unsigned int frags;
+
+	/* format + channel setup */
+	unsigned int format_verb;
+
+	/* flags */
+	unsigned int opened:1;
+	unsigned int prepared:1;
+	unsigned int paused:1;
+	unsigned int running:1;
+};
+
+#define PLAY	SNDRV_PCM_STREAM_PLAYBACK
+#define CAPT	SNDRV_PCM_STREAM_CAPTURE
+
+struct lola_pcm {
+	unsigned int num_streams;
+	struct snd_dma_buffer bdl; /* BDL buffer */
+	struct lola_stream streams[MAX_STREAM_COUNT];
+};
+
+/* card instance */
+struct lola {
+	struct snd_card *card;
+	struct pci_dev *pci;
+
+	/* pci resources */
+	struct lola_bar bar[2];
+	int irq;
+
+	/* locks */
+	spinlock_t reg_lock;
+	struct mutex open_mutex;
+
+	/* CORB/RIRB */
+	struct lola_rb corb;
+	struct lola_rb rirb;
+	unsigned int res, res_ex;	/* last read values */
+	/* last command (for debugging) */
+	unsigned int last_cmd_nid, last_verb, last_data, last_extdata;
+
+	/* CORB/RIRB buffers */
+	struct snd_dma_buffer rb;
+
+	/* unsolicited events */
+	unsigned int last_unsol_res;
+
+	/* streams */
+	struct lola_pcm pcm[2];
+
+	/* input src */
+	unsigned int input_src_caps_mask;
+	unsigned int input_src_mask;
+
+	/* pins */
+	struct lola_pin_array pin[2];
+
+	/* clock */
+	struct lola_clock_widget clock;
+	int ref_count_rate;
+	unsigned int sample_rate;
+
+	/* mixer */
+	struct lola_mixer_widget mixer;
+
+	/* hw info */
+	unsigned int version;
+	unsigned int lola_caps;
+
+	/* parameters */
+	unsigned int granularity;
+	unsigned int sample_rate_min;
+	unsigned int sample_rate_max;
+
+	/* flags */
+	unsigned int initialized:1;
+	unsigned int cold_reset:1;
+	unsigned int polling_mode:1;
+
+	/* for debugging */
+	unsigned int debug_res;
+	unsigned int debug_res_ex;
+};
+
+#define BAR0	0
+#define BAR1	1
+
+/* Helper macros */
+#define lola_readl(chip, idx, name) \
+	readl((chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+#define lola_readw(chip, idx, name) \
+	readw((chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+#define lola_readb(chip, idx, name) \
+	readb((chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+#define lola_writel(chip, idx, name, val) \
+	writel((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+#define lola_writew(chip, idx, name, val) \
+	writew((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+#define lola_writeb(chip, idx, name, val) \
+	writeb((val), (chip)->bar[idx].remap_addr + LOLA_##idx##_##name)
+
+#define lola_dsd_read(chip, dsd, name) \
+	readl((chip)->bar[BAR1].remap_addr + LOLA_BAR1_DSD0_OFFSET + \
+	      (LOLA_BAR1_DSD_SIZE * (dsd)) + LOLA_BAR1_DSDn##name)
+#define lola_dsd_write(chip, dsd, name, val) \
+	writel((val), (chip)->bar[BAR1].remap_addr + LOLA_BAR1_DSD0_OFFSET + \
+	       (LOLA_BAR1_DSD_SIZE * (dsd)) + LOLA_BAR1_DSDn##name)
+
+/* GET verbs HDAudio */
+#define LOLA_VERB_GET_STREAM_FORMAT		0xa00
+#define LOLA_VERB_GET_AMP_GAIN_MUTE		0xb00
+#define LOLA_VERB_PARAMETERS			0xf00
+#define LOLA_VERB_GET_POWER_STATE		0xf05
+#define LOLA_VERB_GET_CONV			0xf06
+#define LOLA_VERB_GET_UNSOLICITED_RESPONSE	0xf08
+#define LOLA_VERB_GET_DIGI_CONVERT_1		0xf0d
+#define LOLA_VERB_GET_CONFIG_DEFAULT		0xf1c
+#define LOLA_VERB_GET_SUBSYSTEM_ID		0xf20
+/* GET verbs Digigram */
+#define LOLA_VERB_GET_FIXED_GAIN		0xfc0
+#define LOLA_VERB_GET_GAIN_SELECT		0xfc1
+#define LOLA_VERB_GET_MAX_LEVEL			0xfc2
+#define LOLA_VERB_GET_CLOCK_LIST		0xfc3
+#define LOLA_VERB_GET_CLOCK_SELECT		0xfc4
+#define LOLA_VERB_GET_CLOCK_STATUS		0xfc5
+
+/* SET verbs HDAudio */
+#define LOLA_VERB_SET_STREAM_FORMAT		0x200
+#define LOLA_VERB_SET_AMP_GAIN_MUTE		0x300
+#define LOLA_VERB_SET_POWER_STATE		0x705
+#define LOLA_VERB_SET_CHANNEL_STREAMID		0x706
+#define LOLA_VERB_SET_UNSOLICITED_ENABLE	0x708
+#define LOLA_VERB_SET_DIGI_CONVERT_1		0x70d
+/* SET verbs Digigram */
+#define LOLA_VERB_SET_GAIN_SELECT		0xf81
+#define LOLA_VERB_SET_CLOCK_SELECT		0xf84
+#define LOLA_VERB_SET_GRANULARITY_STEPS		0xf86
+#define LOLA_VERB_SET_SOURCE_GAIN		0xf87
+#define LOLA_VERB_SET_MIX_GAIN			0xf88
+#define LOLA_VERB_SET_DESTINATION_GAIN		0xf89
+#define LOLA_VERB_SET_SRC			0xf8a
+
+/* Parameter IDs used with LOLA_VERB_PARAMETERS */
+#define LOLA_PAR_VENDOR_ID			0x00
+#define LOLA_PAR_FUNCTION_TYPE			0x05
+#define LOLA_PAR_AUDIO_WIDGET_CAP		0x09
+#define LOLA_PAR_PCM				0x0a
+#define LOLA_PAR_STREAM_FORMATS			0x0b
+#define LOLA_PAR_PIN_CAP			0x0c
+#define LOLA_PAR_AMP_IN_CAP			0x0d
+#define LOLA_PAR_CONNLIST_LEN			0x0e
+#define LOLA_PAR_POWER_STATE			0x0f
+#define LOLA_PAR_GPIO_CAP			0x11
+#define LOLA_PAR_AMP_OUT_CAP			0x12
+#define LOLA_PAR_SPECIFIC_CAPS			0x80
+#define LOLA_PAR_FIXED_GAIN_LIST		0x81
+
+/* extract results of LOLA_PAR_SPECIFIC_CAPS */
+#define LOLA_AFG_MIXER_WIDGET_PRESENT(res)	((res & (1 << 21)) != 0)
+#define LOLA_AFG_CLOCK_WIDGET_PRESENT(res)	((res & (1 << 20)) != 0)
+#define LOLA_AFG_INPUT_PIN_COUNT(res)		((res >> 10) & 0x2ff)
+#define LOLA_AFG_OUTPUT_PIN_COUNT(res)		((res) & 0x2ff)
+
+/* extract results of LOLA_PAR_AMP_IN_CAP / LOLA_PAR_AMP_OUT_CAP */
+#define LOLA_AMP_MUTE_CAPABLE(res)		((res & (1 << 31)) != 0)
+#define LOLA_AMP_STEP_SIZE(res)			((res >> 24) & 0x7f)
+#define LOLA_AMP_NUM_STEPS(res)			((res >> 12) & 0x3ff)
+#define LOLA_AMP_OFFSET(res)			((res) & 0x3ff)
+
+#define LOLA_GRANULARITY_MIN		8
+#define LOLA_GRANULARITY_MAX		32
+#define LOLA_GRANULARITY_STEP		8
+
+/* parameters used with unsolicited command/response */
+#define LOLA_UNSOLICITED_TAG_MASK	0x3f
+#define LOLA_UNSOLICITED_TAG		0x1a
+#define LOLA_UNSOLICITED_ENABLE		0x80
+#define LOLA_UNSOL_RESP_TAG_OFFSET	26
+
+/* count values in the Vendor Specific Mixer Widget's Audio Widget Capabilities */
+#define LOLA_MIXER_SRC_INPUT_PLAY_SEPARATION(res)   ((res >> 2) & 0x1f)
+#define LOLA_MIXER_DEST_REC_OUTPUT_SEPATATION(res)  ((res >> 7) & 0x1f)
+
+int lola_codec_write(struct lola *chip, unsigned int nid, unsigned int verb,
+		     unsigned int data, unsigned int extdata);
+int lola_codec_read(struct lola *chip, unsigned int nid, unsigned int verb,
+		    unsigned int data, unsigned int extdata,
+		    unsigned int *val, unsigned int *extval);
+int lola_codec_flush(struct lola *chip);
+#define lola_read_param(chip, nid, param, val) \
+	lola_codec_read(chip, nid, LOLA_VERB_PARAMETERS, param, 0, val, NULL)
+
+/* PCM */
+int lola_create_pcm(struct lola *chip);
+void lola_free_pcm(struct lola *chip);
+int lola_init_pcm(struct lola *chip, int dir, int *nidp);
+void lola_pcm_update(struct lola *chip, struct lola_pcm *pcm, unsigned int bits);
+
+/* clock */
+int lola_init_clock_widget(struct lola *chip, int nid);
+int lola_set_granularity(struct lola *chip, unsigned int val, bool force);
+int lola_enable_clock_events(struct lola *chip);
+int lola_set_clock_index(struct lola *chip, unsigned int idx);
+int lola_set_clock(struct lola *chip, int idx);
+int lola_set_sample_rate(struct lola *chip, int rate);
+bool lola_update_ext_clock_freq(struct lola *chip, unsigned int val);
+unsigned int lola_sample_rate_convert(unsigned int coded);
+
+/* mixer */
+int lola_init_pins(struct lola *chip, int dir, int *nidp);
+int lola_init_mixer_widget(struct lola *chip, int nid);
+void lola_free_mixer(struct lola *chip);
+int lola_create_mixer(struct lola *chip);
+int lola_setup_all_analog_gains(struct lola *chip, int dir, bool mute);
+void lola_save_mixer(struct lola *chip);
+void lola_restore_mixer(struct lola *chip);
+int lola_set_src_config(struct lola *chip, unsigned int src_mask, bool update);
+
+/* proc */
+#ifdef CONFIG_SND_DEBUG
+void lola_proc_debug_new(struct lola *chip);
+#else
+#define lola_proc_debug_new(chip)
+#endif
+
+#endif /* _LOLA_H */

+ 323 - 0
sound/pci/lola/lola_clock.c

@@ -0,0 +1,323 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "lola.h"
+
+unsigned int lola_sample_rate_convert(unsigned int coded)
+{
+	unsigned int freq;
+
+	/* base frequency */
+	switch (coded & 0x3) {
+	case 0:     freq = 48000; break;
+	case 1:     freq = 44100; break;
+	case 2:     freq = 32000; break;
+	default:    return 0;   /* error */
+	}
+
+	/* multiplier / devisor */
+	switch (coded & 0x1c) {
+	case (0 << 2):    break;
+	case (4 << 2):    break;
+	case (1 << 2):    freq *= 2; break;
+	case (2 << 2):    freq *= 4; break;
+	case (5 << 2):    freq /= 2; break;
+	case (6 << 2):    freq /= 4; break;
+	default:        return 0;   /* error */
+	}
+
+	/* ajustement */
+	switch (coded & 0x60) {
+	case (0 << 5):    break;
+	case (1 << 5):    freq = (freq * 999) / 1000; break;
+	case (2 << 5):    freq = (freq * 1001) / 1000; break;
+	default:        return 0;   /* error */
+	}
+	return freq;
+}
+
+/*
+ * Granualrity
+ */
+
+#define LOLA_MAXFREQ_AT_GRANULARITY_MIN         48000
+#define LOLA_MAXFREQ_AT_GRANULARITY_BELOW_MAX   96000
+
+static bool check_gran_clock_compatibility(struct lola *chip,
+					   unsigned int val,
+					   unsigned int freq)
+{
+	if (!chip->granularity)
+		return true;
+
+	if (val < LOLA_GRANULARITY_MIN || val > LOLA_GRANULARITY_MAX ||
+	    (val % LOLA_GRANULARITY_STEP) != 0)
+		return false;
+
+	if (val == LOLA_GRANULARITY_MIN) {
+		if (freq > LOLA_MAXFREQ_AT_GRANULARITY_MIN)
+			return false;
+	} else if (val < LOLA_GRANULARITY_MAX) {
+		if (freq > LOLA_MAXFREQ_AT_GRANULARITY_BELOW_MAX)
+			return false;
+	}
+	return true;
+}
+
+int lola_set_granularity(struct lola *chip, unsigned int val, bool force)
+{
+	int err;
+
+	if (!force) {
+		if (val == chip->granularity)
+			return 0;
+#if 0
+		/* change Gran only if there are no streams allocated ! */
+		if (chip->audio_in_alloc_mask || chip->audio_out_alloc_mask)
+			return -EBUSY;
+#endif
+		if (!check_gran_clock_compatibility(chip, val,
+						    chip->clock.cur_freq))
+			return -EINVAL;
+	}
+
+	chip->granularity = val;
+	val /= LOLA_GRANULARITY_STEP;
+
+	/* audio function group */
+	err = lola_codec_write(chip, 1, LOLA_VERB_SET_GRANULARITY_STEPS,
+			       val, 0);
+	if (err < 0)
+		return err;
+	/* this can be a very slow function !!! */
+	usleep_range(400 * val, 20000);
+	return lola_codec_flush(chip);
+}
+
+/*
+ * Clock widget handling
+ */
+
+int __devinit lola_init_clock_widget(struct lola *chip, int nid)
+{
+	unsigned int val;
+	int i, j, nitems, nb_verbs, idx, idx_list;
+	int err;
+
+	err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read wcaps for 0x%x\n", nid);
+		return err;
+	}
+
+	if ((val & 0xfff00000) != 0x01f00000) { /* test SubType and Type */
+		snd_printdd("No valid clock widget\n");
+		return 0;
+	}
+
+	chip->clock.nid = nid;
+	chip->clock.items = val & 0xff;
+	snd_printdd("clock_list nid=%x, entries=%d\n", nid,
+		    chip->clock.items);
+	if (chip->clock.items > MAX_SAMPLE_CLOCK_COUNT) {
+		printk(KERN_ERR SFX "CLOCK_LIST too big: %d\n",
+		       chip->clock.items);
+		return -EINVAL;
+	}
+
+	nitems = chip->clock.items;
+	nb_verbs = (nitems + 3) / 4;
+	idx = 0;
+	idx_list = 0;
+	for (i = 0; i < nb_verbs; i++) {
+		unsigned int res_ex;
+		unsigned short items[4];
+
+		err = lola_codec_read(chip, nid, LOLA_VERB_GET_CLOCK_LIST,
+				      idx, 0, &val, &res_ex);
+		if (err < 0) {
+			printk(KERN_ERR SFX "Can't read CLOCK_LIST\n");
+			return -EINVAL;
+		}
+
+		items[0] = val & 0xfff;
+		items[1] = (val >> 16) & 0xfff;
+		items[2] = res_ex & 0xfff;
+		items[3] = (res_ex >> 16) & 0xfff;
+
+		for (j = 0; j < 4; j++) {
+			unsigned char type = items[j] >> 8;
+			unsigned int freq = items[j] & 0xff;
+			int format = LOLA_CLOCK_FORMAT_NONE;
+			bool add_clock = true;
+			if (type == LOLA_CLOCK_TYPE_INTERNAL) {
+				freq = lola_sample_rate_convert(freq);
+				if (freq < chip->sample_rate_min)
+					add_clock = false;
+				else if (freq == 48000) {
+					chip->clock.cur_index = idx_list;
+					chip->clock.cur_freq = 48000;
+					chip->clock.cur_valid = true;
+				}
+			} else if (type == LOLA_CLOCK_TYPE_VIDEO) {
+				freq = lola_sample_rate_convert(freq);
+				if (freq < chip->sample_rate_min)
+					add_clock = false;
+				/* video clock has a format (0:NTSC, 1:PAL)*/
+				if (items[j] & 0x80)
+					format = LOLA_CLOCK_FORMAT_NTSC;
+				else
+					format = LOLA_CLOCK_FORMAT_PAL;
+			}
+			if (add_clock) {
+				struct lola_sample_clock *sc;
+				sc = &chip->clock.sample_clock[idx_list];
+				sc->type = type;
+				sc->format = format;
+				sc->freq = freq;
+				/* keep the index used with the board */
+				chip->clock.idx_lookup[idx_list] = idx;
+				idx_list++;
+			} else {
+				chip->clock.items--;
+			}
+			if (++idx >= nitems)
+				break;
+		}
+	}
+	return 0;
+}
+
+/* enable unsolicited events of the clock widget */
+int lola_enable_clock_events(struct lola *chip)
+{
+	unsigned int res;
+	int err;
+
+	err = lola_codec_read(chip, chip->clock.nid,
+			      LOLA_VERB_SET_UNSOLICITED_ENABLE,
+			      LOLA_UNSOLICITED_ENABLE | LOLA_UNSOLICITED_TAG,
+			      0, &res, NULL);
+	if (err < 0)
+		return err;
+	if (res) {
+		printk(KERN_WARNING SFX "error in enable_clock_events %d\n",
+		       res);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int lola_set_clock_index(struct lola *chip, unsigned int idx)
+{
+	unsigned int res;
+	int err;
+
+	err = lola_codec_read(chip, chip->clock.nid,
+			      LOLA_VERB_SET_CLOCK_SELECT,
+			      chip->clock.idx_lookup[idx],
+			      0, &res, NULL);
+	if (err < 0)
+		return err;
+	if (res) {
+		printk(KERN_WARNING SFX "error in set_clock %d\n", res);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+bool lola_update_ext_clock_freq(struct lola *chip, unsigned int val)
+{
+	unsigned int tag;
+
+	/* the current EXTERNAL clock information gets updated by interrupt
+	 * with an unsolicited response
+	 */
+	if (!val)
+		return false;
+	tag = (val >> LOLA_UNSOL_RESP_TAG_OFFSET) & LOLA_UNSOLICITED_TAG_MASK;
+	if (tag != LOLA_UNSOLICITED_TAG)
+		return false;
+
+	/* only for current = external clocks */
+	if (chip->clock.sample_clock[chip->clock.cur_index].type !=
+	    LOLA_CLOCK_TYPE_INTERNAL) {
+		chip->clock.cur_freq = lola_sample_rate_convert(val & 0x7f);
+		chip->clock.cur_valid = (val & 0x100) != 0;
+	}
+	return true;
+}
+
+int lola_set_clock(struct lola *chip, int idx)
+{
+	int freq = 0;
+	bool valid = false;
+
+	if (idx == chip->clock.cur_index) {
+		/* current clock is allowed */
+		freq = chip->clock.cur_freq;
+		valid = chip->clock.cur_valid;
+	} else if (chip->clock.sample_clock[idx].type ==
+		   LOLA_CLOCK_TYPE_INTERNAL) {
+		/* internal clocks allowed */
+		freq = chip->clock.sample_clock[idx].freq;
+		valid = true;
+	}
+
+	if (!freq || !valid)
+		return -EINVAL;
+
+	if (!check_gran_clock_compatibility(chip, chip->granularity, freq))
+		return -EINVAL;
+
+	if (idx != chip->clock.cur_index) {
+		int err = lola_set_clock_index(chip, idx);
+		if (err < 0)
+			return err;
+		/* update new settings */
+		chip->clock.cur_index = idx;
+		chip->clock.cur_freq = freq;
+		chip->clock.cur_valid = true;
+	}
+	return 0;
+}
+
+int lola_set_sample_rate(struct lola *chip, int rate)
+{
+	int i;
+
+	if (chip->clock.cur_freq == rate && chip->clock.cur_valid)
+		return 0;
+	/* search for new dwClockIndex */
+	for (i = 0; i < chip->clock.items; i++) {
+		if (chip->clock.sample_clock[i].type == LOLA_CLOCK_TYPE_INTERNAL &&
+		    chip->clock.sample_clock[i].freq == rate)
+			break;
+	}
+	if (i >= chip->clock.items)
+		return -EINVAL;
+	return lola_set_clock(chip, i);
+}
+

+ 839 - 0
sound/pci/lola/lola_mixer.c

@@ -0,0 +1,839 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/tlv.h>
+#include "lola.h"
+
+static int __devinit lola_init_pin(struct lola *chip, struct lola_pin *pin,
+				   int dir, int nid)
+{
+	unsigned int val;
+	int err;
+
+	pin->nid = nid;
+	err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read wcaps for 0x%x\n", nid);
+		return err;
+	}
+	val &= 0x00f00fff; /* test TYPE and bits 0..11 */
+	if (val == 0x00400200)    /* Type = 4, Digital = 1 */
+		pin->is_analog = false;
+	else if (val == 0x0040000a && dir == CAPT) /* Dig=0, InAmp/ovrd */
+		pin->is_analog = true;
+	else if (val == 0x0040000c && dir == PLAY) /* Dig=0, OutAmp/ovrd */
+		pin->is_analog = true;
+	else {
+		printk(KERN_ERR SFX "Invalid wcaps 0x%x for 0x%x\n", val, nid);
+		return -EINVAL;
+	}
+
+	/* analog parameters only following, so continue in case of Digital pin
+	 */
+	if (!pin->is_analog)
+		return 0;
+
+	if (dir == PLAY)
+		err = lola_read_param(chip, nid, LOLA_PAR_AMP_OUT_CAP, &val);
+	else
+		err = lola_read_param(chip, nid, LOLA_PAR_AMP_IN_CAP, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read AMP-caps for 0x%x\n", nid);
+		return err;
+	}
+
+	pin->amp_mute = LOLA_AMP_MUTE_CAPABLE(val);
+	pin->amp_step_size = LOLA_AMP_STEP_SIZE(val);
+	pin->amp_num_steps = LOLA_AMP_NUM_STEPS(val);
+	if (pin->amp_num_steps) {
+		/* zero as mute state */
+		pin->amp_num_steps++;
+		pin->amp_step_size++;
+	}
+	pin->amp_offset = LOLA_AMP_OFFSET(val);
+
+	err = lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val,
+			      NULL);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't get MAX_LEVEL 0x%x\n", nid);
+		return err;
+	}
+	pin->max_level = val & 0x3ff;   /* 10 bits */
+
+	pin->config_default_reg = 0;
+	pin->fixed_gain_list_len = 0;
+	pin->cur_gain_step = 0;
+
+	return 0;
+}
+
+int __devinit lola_init_pins(struct lola *chip, int dir, int *nidp)
+{
+	int i, err, nid;
+	nid = *nidp;
+	for (i = 0; i < chip->pin[dir].num_pins; i++, nid++) {
+		err = lola_init_pin(chip, &chip->pin[dir].pins[i], dir, nid);
+		if (err < 0)
+			return err;
+		if (chip->pin[dir].pins[i].is_analog)
+			chip->pin[dir].num_analog_pins++;
+	}
+	*nidp = nid;
+	return 0;
+}
+
+void lola_free_mixer(struct lola *chip)
+{
+	if (chip->mixer.array_saved)
+		vfree(chip->mixer.array_saved);
+}
+
+int __devinit lola_init_mixer_widget(struct lola *chip, int nid)
+{
+	unsigned int val;
+	int err;
+
+	err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read wcaps for 0x%x\n", nid);
+		return err;
+	}
+
+	if ((val & 0xfff00000) != 0x02f00000) { /* test SubType and Type */
+		snd_printdd("No valid mixer widget\n");
+		return 0;
+	}
+
+	chip->mixer.nid = nid;
+	chip->mixer.caps = val;
+	chip->mixer.array = (struct lola_mixer_array __iomem *)
+		(chip->bar[BAR1].remap_addr + LOLA_BAR1_SOURCE_GAIN_ENABLE);
+
+	/* reserve memory to copy mixer data for sleep mode transitions */
+	chip->mixer.array_saved = vmalloc(sizeof(struct lola_mixer_array));
+
+	/* mixer matrix sources are physical input data and play streams */
+	chip->mixer.src_stream_outs = chip->pcm[PLAY].num_streams;
+	chip->mixer.src_phys_ins = chip->pin[CAPT].num_pins;
+
+	/* mixer matrix destinations are record streams and physical output */
+	chip->mixer.dest_stream_ins = chip->pcm[CAPT].num_streams;
+	chip->mixer.dest_phys_outs = chip->pin[PLAY].num_pins;
+
+	/* mixer matrix can have unused areas between PhysIn and
+	 * Play or Record and PhysOut zones
+	 */
+	chip->mixer.src_stream_out_ofs = chip->mixer.src_phys_ins +
+		LOLA_MIXER_SRC_INPUT_PLAY_SEPARATION(val);
+	chip->mixer.dest_phys_out_ofs = chip->mixer.dest_stream_ins +
+		LOLA_MIXER_DEST_REC_OUTPUT_SEPATATION(val);
+
+	/* example : MixerMatrix of LoLa881
+	 * 0-------8------16-------8------16
+	 * |       |       |       |       |
+	 * | INPUT |       | INPUT |       |
+	 * | ->    |unused | ->    |unused |
+	 * | RECORD|       | OUTPUT|       |
+	 * |       |       |       |       |
+	 * 8--------------------------------
+	 * |       |       |       |       |
+	 * |       |       |       |       |
+	 * |unused |unused |unused |unused |
+	 * |       |       |       |       |
+	 * |       |       |       |       |
+	 * 16-------------------------------
+	 * |       |       |       |       |
+	 * | PLAY  |       | PLAY  |       |
+	 * |  ->   |unused | ->    |unused |
+	 * | RECORD|       | OUTPUT|       |
+	 * |       |       |       |       |
+	 * 8--------------------------------
+	 * |       |       |       |       |
+	 * |       |       |       |       |
+	 * |unused |unused |unused |unused |
+	 * |       |       |       |       |
+	 * |       |       |       |       |
+	 * 16-------------------------------
+	 */
+	if (chip->mixer.src_stream_out_ofs > MAX_AUDIO_INOUT_COUNT ||
+	    chip->mixer.dest_phys_out_ofs > MAX_STREAM_IN_COUNT) {
+		printk(KERN_ERR SFX "Invalid mixer widget size\n");
+		return -EINVAL;
+	}
+
+	chip->mixer.src_mask = ((1U << chip->mixer.src_phys_ins) - 1) |
+		(((1U << chip->mixer.src_stream_outs) - 1)
+		 << chip->mixer.src_stream_out_ofs);
+	chip->mixer.dest_mask = ((1U << chip->mixer.dest_stream_ins) - 1) |
+		(((1U << chip->mixer.dest_phys_outs) - 1)
+		 << chip->mixer.dest_phys_out_ofs);
+
+	return 0;
+}
+
+static int lola_mixer_set_src_gain(struct lola *chip, unsigned int id,
+				   unsigned short gain, bool on)
+{
+	unsigned int oldval, val;
+
+	if (!(chip->mixer.src_mask & (1 << id)))
+		return -EINVAL;
+	writew(gain, &chip->mixer.array->src_gain[id]);
+	oldval = val = readl(&chip->mixer.array->src_gain_enable);
+	if (on)
+		val |= (1 << id);
+	else
+		val &= ~(1 << id);
+	writel(val, &chip->mixer.array->src_gain_enable);
+	lola_codec_flush(chip);
+	/* inform micro-controller about the new source gain */
+	return lola_codec_write(chip, chip->mixer.nid,
+				LOLA_VERB_SET_SOURCE_GAIN, id, 0);
+}
+
+#if 0 /* not used */
+static int lola_mixer_set_src_gains(struct lola *chip, unsigned int mask,
+				    unsigned short *gains)
+{
+	int i;
+
+	if ((chip->mixer.src_mask & mask) != mask)
+		return -EINVAL;
+	for (i = 0; i < LOLA_MIXER_DIM; i++) {
+		if (mask & (1 << i)) {
+			writew(*gains, &chip->mixer.array->src_gain[i]);
+			gains++;
+		}
+	}
+	writel(mask, &chip->mixer.array->src_gain_enable);
+	lola_codec_flush(chip);
+	if (chip->mixer.caps & LOLA_PEAK_METER_CAN_AGC_MASK) {
+		/* update for all srcs at once */
+		return lola_codec_write(chip, chip->mixer.nid,
+					LOLA_VERB_SET_SOURCE_GAIN, 0x80, 0);
+	}
+	/* update manually */
+	for (i = 0; i < LOLA_MIXER_DIM; i++) {
+		if (mask & (1 << i)) {
+			lola_codec_write(chip, chip->mixer.nid,
+					 LOLA_VERB_SET_SOURCE_GAIN, i, 0);
+		}
+	}
+	return 0;
+}
+#endif /* not used */
+
+static int lola_mixer_set_mapping_gain(struct lola *chip,
+				       unsigned int src, unsigned int dest,
+				       unsigned short gain, bool on)
+{
+	unsigned int val;
+
+	if (!(chip->mixer.src_mask & (1 << src)) ||
+	    !(chip->mixer.dest_mask & (1 << dest)))
+		return -EINVAL;
+	if (on)
+		writew(gain, &chip->mixer.array->dest_mix_gain[dest][src]);
+	val = readl(&chip->mixer.array->dest_mix_gain_enable[dest]);
+	if (on)
+		val |= (1 << src);
+	else
+		val &= ~(1 << src);
+	writel(val, &chip->mixer.array->dest_mix_gain_enable[dest]);
+	lola_codec_flush(chip);
+	return lola_codec_write(chip, chip->mixer.nid, LOLA_VERB_SET_MIX_GAIN,
+				src, dest);
+}
+
+static int lola_mixer_set_dest_gains(struct lola *chip, unsigned int id,
+				     unsigned int mask, unsigned short *gains)
+{
+	int i;
+
+	if (!(chip->mixer.dest_mask & (1 << id)) ||
+	    (chip->mixer.src_mask & mask) != mask)
+		return -EINVAL;
+	for (i = 0; i < LOLA_MIXER_DIM; i++) {
+		if (mask & (1 << i)) {
+			writew(*gains, &chip->mixer.array->dest_mix_gain[id][i]);
+			gains++;
+		}
+	}
+	writel(mask, &chip->mixer.array->dest_mix_gain_enable[id]);
+	lola_codec_flush(chip);
+	/* update for all dests at once */
+	return lola_codec_write(chip, chip->mixer.nid,
+				LOLA_VERB_SET_DESTINATION_GAIN, id, 0);
+}
+
+/*
+ */
+
+static int set_analog_volume(struct lola *chip, int dir,
+			     unsigned int idx, unsigned int val,
+			     bool external_call);
+
+int lola_setup_all_analog_gains(struct lola *chip, int dir, bool mute)
+{
+	struct lola_pin *pin;
+	int idx, max_idx;
+
+	pin = chip->pin[dir].pins;
+	max_idx = chip->pin[dir].num_pins;
+	for (idx = 0; idx < max_idx; idx++) {
+		if (pin[idx].is_analog) {
+			unsigned int val = mute ? 0 : pin[idx].cur_gain_step;
+			/* set volume and do not save the value */
+			set_analog_volume(chip, dir, idx, val, false);
+		}
+	}
+	return lola_codec_flush(chip);
+}
+
+void lola_save_mixer(struct lola *chip)
+{
+	/* mute analog output */
+	if (chip->mixer.array_saved) {
+		/* store contents of mixer array */
+		memcpy_fromio(chip->mixer.array_saved, chip->mixer.array,
+			      sizeof(*chip->mixer.array));
+	}
+	lola_setup_all_analog_gains(chip, PLAY, true); /* output mute */
+}
+
+void lola_restore_mixer(struct lola *chip)
+{
+	int i;
+
+	/*lola_reset_setups(chip);*/
+	if (chip->mixer.array_saved) {
+		/* restore contents of mixer array */
+		memcpy_toio(chip->mixer.array, chip->mixer.array_saved,
+			    sizeof(*chip->mixer.array));
+		/* inform micro-controller about all restored values
+		 * and ignore return values
+		 */
+		for (i = 0; i < chip->mixer.src_phys_ins; i++)
+			lola_codec_write(chip, chip->mixer.nid,
+					 LOLA_VERB_SET_SOURCE_GAIN,
+					 i, 0);
+		for (i = 0; i < chip->mixer.src_stream_outs; i++)
+			lola_codec_write(chip, chip->mixer.nid,
+					 LOLA_VERB_SET_SOURCE_GAIN,
+					 chip->mixer.src_stream_out_ofs + i, 0);
+		for (i = 0; i < chip->mixer.dest_stream_ins; i++)
+			lola_codec_write(chip, chip->mixer.nid,
+					 LOLA_VERB_SET_DESTINATION_GAIN,
+					 i, 0);
+		for (i = 0; i < chip->mixer.dest_phys_outs; i++)
+			lola_codec_write(chip, chip->mixer.nid,
+					 LOLA_VERB_SET_DESTINATION_GAIN,
+					 chip->mixer.dest_phys_out_ofs + i, 0);
+		lola_codec_flush(chip);
+	}
+}
+
+/*
+ */
+
+static int set_analog_volume(struct lola *chip, int dir,
+			     unsigned int idx, unsigned int val,
+			     bool external_call)
+{
+	struct lola_pin *pin;
+	int err;
+
+	if (idx >= chip->pin[dir].num_pins)
+		return -EINVAL;
+	pin = &chip->pin[dir].pins[idx];
+	if (!pin->is_analog || pin->amp_num_steps <= val)
+		return -EINVAL;
+	if (external_call && pin->cur_gain_step == val)
+		return 0;
+	if (external_call)
+		lola_codec_flush(chip);
+	err = lola_codec_write(chip, pin->nid,
+			       LOLA_VERB_SET_AMP_GAIN_MUTE, val, 0);
+	if (err < 0)
+		return err;
+	if (external_call)
+		pin->cur_gain_step = val;
+	return 0;
+}
+
+int lola_set_src_config(struct lola *chip, unsigned int src_mask, bool update)
+{
+	int ret = 0;
+	int success = 0;
+	int n, err;
+
+	/* SRC can be activated and the dwInputSRCMask is valid? */
+	if ((chip->input_src_caps_mask & src_mask) != src_mask)
+		return -EINVAL;
+	/* handle all even Inputs - SRC is a stereo setting !!! */
+	for (n = 0; n < chip->pin[CAPT].num_pins; n += 2) {
+		unsigned int mask = 3U << n; /* handle the stereo case */
+		unsigned int new_src, src_state;
+		if (!(chip->input_src_caps_mask & mask))
+			continue;
+		/* if one IO needs SRC, both stereo IO will get SRC */
+		new_src = (src_mask & mask) != 0;
+		if (update) {
+			src_state = (chip->input_src_mask & mask) != 0;
+			if (src_state == new_src)
+				continue;   /* nothing to change for this IO */
+		}
+		err = lola_codec_write(chip, chip->pcm[CAPT].streams[n].nid,
+				       LOLA_VERB_SET_SRC, new_src, 0);
+		if (!err)
+			success++;
+		else
+			ret = err;
+	}
+	if (success)
+		ret = lola_codec_flush(chip);
+	if (!ret)
+		chip->input_src_mask = src_mask;
+	return ret;
+}
+
+/*
+ */
+static int init_mixer_values(struct lola *chip)
+{
+	int i;
+
+	/* all src on */
+	lola_set_src_config(chip, (1 << chip->pin[CAPT].num_pins) - 1, false);
+
+	/* clear all matrix */
+	memset_io(chip->mixer.array, 0, sizeof(*chip->mixer.array));
+	/* set src gain to 0dB */
+	for (i = 0; i < chip->mixer.src_phys_ins; i++)
+		lola_mixer_set_src_gain(chip, i, 336, true); /* 0dB */
+	for (i = 0; i < chip->mixer.src_stream_outs; i++)
+		lola_mixer_set_src_gain(chip,
+					i + chip->mixer.src_stream_out_ofs,
+					336, true); /* 0dB */
+	/* set 1:1 dest gain */
+	for (i = 0; i < chip->mixer.dest_stream_ins; i++) {
+		int src = i % chip->mixer.src_phys_ins;
+		lola_mixer_set_mapping_gain(chip, src, i, 336, true);
+	}
+	for (i = 0; i < chip->mixer.src_stream_outs; i++) {
+		int src = chip->mixer.src_stream_out_ofs + i;
+		int dst = chip->mixer.dest_phys_out_ofs +
+			i % chip->mixer.dest_phys_outs;
+		lola_mixer_set_mapping_gain(chip, src, dst, 336, true);
+	}
+	return 0;
+}
+
+/*
+ * analog mixer control element
+ */
+static int lola_analog_vol_info(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int dir = kcontrol->private_value;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = chip->pin[dir].num_pins;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = chip->pin[dir].pins[0].amp_num_steps;
+	return 0;
+}
+
+static int lola_analog_vol_get(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int dir = kcontrol->private_value;
+	int i;
+
+	for (i = 0; i < chip->pin[dir].num_pins; i++)
+		ucontrol->value.integer.value[i] =
+			chip->pin[dir].pins[i].cur_gain_step;
+	return 0;
+}
+
+static int lola_analog_vol_put(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int dir = kcontrol->private_value;
+	int i, err;
+
+	for (i = 0; i < chip->pin[dir].num_pins; i++) {
+		err = set_analog_volume(chip, dir, i,
+					ucontrol->value.integer.value[i],
+					true);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static int lola_analog_vol_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+			       unsigned int size, unsigned int __user *tlv)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int dir = kcontrol->private_value;
+	unsigned int val1, val2;
+	struct lola_pin *pin;
+
+	if (size < 4 * sizeof(unsigned int))
+		return -ENOMEM;
+	pin = &chip->pin[dir].pins[0];
+
+	val2 = pin->amp_step_size * 25;
+	val1 = -1 * (int)pin->amp_offset * (int)val2;
+#ifdef TLV_DB_SCALE_MUTE
+	val2 |= TLV_DB_SCALE_MUTE;
+#endif
+	if (put_user(SNDRV_CTL_TLVT_DB_SCALE, tlv))
+		return -EFAULT;
+	if (put_user(2 * sizeof(unsigned int), tlv + 1))
+		return -EFAULT;
+	if (put_user(val1, tlv + 2))
+		return -EFAULT;
+	if (put_user(val2, tlv + 3))
+		return -EFAULT;
+	return 0;
+}
+
+static struct snd_kcontrol_new lola_analog_mixer __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK),
+	.info = lola_analog_vol_info,
+	.get = lola_analog_vol_get,
+	.put = lola_analog_vol_put,
+	.tlv.c = lola_analog_vol_tlv,
+};
+
+static int __devinit create_analog_mixer(struct lola *chip, int dir, char *name)
+{
+	if (!chip->pin[dir].num_pins)
+		return 0;
+	/* no analog volumes on digital only adapters */
+	if (chip->pin[dir].num_pins != chip->pin[dir].num_analog_pins)
+		return 0;
+	lola_analog_mixer.name = name;
+	lola_analog_mixer.private_value = dir;
+	return snd_ctl_add(chip->card,
+			   snd_ctl_new1(&lola_analog_mixer, chip));
+}
+
+/*
+ * Hardware sample rate converter on digital input
+ */
+static int lola_input_src_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = chip->pin[CAPT].num_pins;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int lola_input_src_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int i;
+
+	for (i = 0; i < chip->pin[CAPT].num_pins; i++)
+		ucontrol->value.integer.value[i] =
+			!!(chip->input_src_mask & (1 << i));
+	return 0;
+}
+
+static int lola_input_src_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	int i;
+	unsigned int mask;
+
+	mask = 0;
+	for (i = 0; i < chip->pin[CAPT].num_pins; i++)
+		if (ucontrol->value.integer.value[i])
+			mask |= 1 << i;
+	return lola_set_src_config(chip, mask, true);
+}
+
+static struct snd_kcontrol_new lola_input_src_mixer __devinitdata = {
+	.name = "Digital SRC Capture Switch",
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = lola_input_src_info,
+	.get = lola_input_src_get,
+	.put = lola_input_src_put,
+};
+
+/*
+ * Lola16161 or Lola881 can have Hardware sample rate converters
+ * on its digital input pins
+ */
+static int __devinit create_input_src_mixer(struct lola *chip)
+{
+	if (!chip->input_src_caps_mask)
+		return 0;
+
+	return snd_ctl_add(chip->card,
+			   snd_ctl_new1(&lola_input_src_mixer, chip));
+}
+
+/*
+ * src gain mixer
+ */
+static int lola_src_gain_info(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int count = (kcontrol->private_value >> 8) & 0xff;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = count;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 409;
+	return 0;
+}
+
+static int lola_src_gain_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int ofs = kcontrol->private_value & 0xff;
+	unsigned int count = (kcontrol->private_value >> 8) & 0xff;
+	unsigned int mask, i;
+
+	mask = readl(&chip->mixer.array->src_gain_enable);
+	for (i = 0; i < count; i++) {
+		unsigned int idx = ofs + i;
+		unsigned short val;
+		if (!(chip->mixer.src_mask & (1 << idx)))
+			return -EINVAL;
+		if (mask & (1 << idx))
+			val = readw(&chip->mixer.array->src_gain[idx]) + 1;
+		else
+			val = 0;
+		ucontrol->value.integer.value[i] = val;
+	}
+	return 0;
+}
+
+static int lola_src_gain_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int ofs = kcontrol->private_value & 0xff;
+	unsigned int count = (kcontrol->private_value >> 8) & 0xff;
+	int i, err;
+
+	for (i = 0; i < count; i++) {
+		unsigned int idx = ofs + i;
+		unsigned short val = ucontrol->value.integer.value[i];
+		if (val)
+			val--;
+		err = lola_mixer_set_src_gain(chip, idx, val, !!val);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+/* raw value: 0 = -84dB, 336 = 0dB, 408=18dB, incremented 1 for mute */
+static const DECLARE_TLV_DB_SCALE(lola_src_gain_tlv, -8425, 25, 1);
+
+static struct snd_kcontrol_new lola_src_gain_mixer __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.info = lola_src_gain_info,
+	.get = lola_src_gain_get,
+	.put = lola_src_gain_put,
+	.tlv.p = lola_src_gain_tlv,
+};
+
+static int __devinit create_src_gain_mixer(struct lola *chip,
+					   int num, int ofs, char *name)
+{
+	lola_src_gain_mixer.name = name;
+	lola_src_gain_mixer.private_value = ofs + (num << 8);
+	return snd_ctl_add(chip->card,
+			   snd_ctl_new1(&lola_src_gain_mixer, chip));
+}
+
+/*
+ * destination gain (matrix-like) mixer
+ */
+static int lola_dest_gain_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	unsigned int src_num = (kcontrol->private_value >> 8) & 0xff;
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = src_num;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 433;
+	return 0;
+}
+
+static int lola_dest_gain_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int src_ofs = kcontrol->private_value & 0xff;
+	unsigned int src_num = (kcontrol->private_value >> 8) & 0xff;
+	unsigned int dst_ofs = (kcontrol->private_value >> 16) & 0xff;
+	unsigned int dst, mask, i;
+
+	dst = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + dst_ofs;
+	mask = readl(&chip->mixer.array->dest_mix_gain_enable[dst]);
+	for (i = 0; i < src_num; i++) {
+		unsigned int src = src_ofs + i;
+		unsigned short val;
+		if (!(chip->mixer.src_mask & (1 << src)))
+			return -EINVAL;
+		if (mask & (1 << dst))
+			val = readw(&chip->mixer.array->dest_mix_gain[dst][src]) + 1;
+		else
+			val = 0;
+		ucontrol->value.integer.value[i] = val;
+	}
+	return 0;
+}
+
+static int lola_dest_gain_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct lola *chip = snd_kcontrol_chip(kcontrol);
+	unsigned int src_ofs = kcontrol->private_value & 0xff;
+	unsigned int src_num = (kcontrol->private_value >> 8) & 0xff;
+	unsigned int dst_ofs = (kcontrol->private_value >> 16) & 0xff;
+	unsigned int dst, mask;
+	unsigned short gains[MAX_STREAM_COUNT];
+	int i, num;
+
+	mask = 0;
+	num = 0;
+	for (i = 0; i < src_num; i++) {
+		unsigned short val = ucontrol->value.integer.value[i];
+		if (val) {
+			gains[num++] = val - 1;
+			mask |= 1 << i;
+		}
+	}
+	mask <<= src_ofs;
+	dst = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id) + dst_ofs;
+	return lola_mixer_set_dest_gains(chip, dst, mask, gains);
+}
+
+static const DECLARE_TLV_DB_SCALE(lola_dest_gain_tlv, -8425, 25, 1);
+
+static struct snd_kcontrol_new lola_dest_gain_mixer __devinitdata = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
+		   SNDRV_CTL_ELEM_ACCESS_TLV_READ),
+	.info = lola_dest_gain_info,
+	.get = lola_dest_gain_get,
+	.put = lola_dest_gain_put,
+	.tlv.p = lola_dest_gain_tlv,
+};
+
+static int __devinit create_dest_gain_mixer(struct lola *chip,
+					    int src_num, int src_ofs,
+					    int num, int ofs, char *name)
+{
+	lola_dest_gain_mixer.count = num;
+	lola_dest_gain_mixer.name = name;
+	lola_dest_gain_mixer.private_value =
+		src_ofs + (src_num << 8) + (ofs << 16) + (num << 24);
+	return snd_ctl_add(chip->card,
+			  snd_ctl_new1(&lola_dest_gain_mixer, chip));
+}
+
+/*
+ */
+int __devinit lola_create_mixer(struct lola *chip)
+{
+	int err;
+
+	err = create_analog_mixer(chip, PLAY, "Analog Playback Volume");
+	if (err < 0)
+		return err;
+	err = create_analog_mixer(chip, CAPT, "Analog Capture Volume");
+	if (err < 0)
+		return err;
+	err = create_input_src_mixer(chip);
+	if (err < 0)
+		return err;
+	err = create_src_gain_mixer(chip, chip->mixer.src_phys_ins, 0,
+				    "Line Source Gain Volume");
+	if (err < 0)
+		return err;
+	err = create_src_gain_mixer(chip, chip->mixer.src_stream_outs,
+				    chip->mixer.src_stream_out_ofs,
+				    "Stream Source Gain Volume");
+	if (err < 0)
+		return err;
+	err = create_dest_gain_mixer(chip,
+				     chip->mixer.src_phys_ins, 0,
+				     chip->mixer.dest_stream_ins, 0,
+				     "Line Capture Volume");
+	if (err < 0)
+		return err;
+	err = create_dest_gain_mixer(chip,
+				     chip->mixer.src_stream_outs,
+				     chip->mixer.src_stream_out_ofs,
+				     chip->mixer.dest_stream_ins, 0,
+				     "Stream-Loopback Capture Volume");
+	if (err < 0)
+		return err;
+	err = create_dest_gain_mixer(chip,
+				     chip->mixer.src_phys_ins, 0,
+				     chip->mixer.dest_phys_outs,
+				     chip->mixer.dest_phys_out_ofs,
+				     "Line-Loopback Playback Volume");
+	if (err < 0)
+		return err;
+	err = create_dest_gain_mixer(chip,
+				     chip->mixer.src_stream_outs,
+				     chip->mixer.src_stream_out_ofs,
+				     chip->mixer.dest_phys_outs,
+				     chip->mixer.dest_phys_out_ofs,
+				     "Stream Playback Volume");
+	if (err < 0)
+		return err;
+
+	return init_mixer_values(chip);
+}

+ 706 - 0
sound/pci/lola/lola_pcm.c

@@ -0,0 +1,706 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include "lola.h"
+
+#define LOLA_MAX_BDL_ENTRIES	8
+#define LOLA_MAX_BUF_SIZE	(1024*1024*1024)
+#define LOLA_BDL_ENTRY_SIZE	(16 * 16)
+
+static struct lola_pcm *lola_get_pcm(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	return &chip->pcm[substream->stream];
+}
+
+static struct lola_stream *lola_get_stream(struct snd_pcm_substream *substream)
+{
+	struct lola_pcm *pcm = lola_get_pcm(substream);
+	unsigned int idx = substream->number;
+	return &pcm->streams[idx];
+}
+
+static unsigned int lola_get_lrc(struct lola *chip)
+{
+	return lola_readl(chip, BAR1, LRC);
+}
+
+static unsigned int lola_get_tstamp(struct lola *chip, bool quick_no_sync)
+{
+	unsigned int tstamp = lola_get_lrc(chip) >> 8;
+	if (chip->granularity) {
+		unsigned int wait_banks = quick_no_sync ? 0 : 8;
+		tstamp += (wait_banks + 1) * chip->granularity - 1;
+		tstamp -= tstamp % chip->granularity;
+	}
+	return tstamp << 8;
+}
+
+/* clear any pending interrupt status */
+static void lola_stream_clear_pending_irq(struct lola *chip,
+					  struct lola_stream *str)
+{
+	unsigned int val = lola_dsd_read(chip, str->dsd, STS);
+	val &= LOLA_DSD_STS_DESE | LOLA_DSD_STS_BCIS;
+	if (val)
+		lola_dsd_write(chip, str->dsd, STS, val);
+}
+
+static void lola_stream_start(struct lola *chip, struct lola_stream *str,
+			      unsigned int tstamp)
+{
+	lola_stream_clear_pending_irq(chip, str);
+	lola_dsd_write(chip, str->dsd, CTL,
+		       LOLA_DSD_CTL_SRUN |
+		       LOLA_DSD_CTL_IOCE |
+		       LOLA_DSD_CTL_DEIE |
+		       LOLA_DSD_CTL_VLRCV |
+		       tstamp);
+}
+
+static void lola_stream_stop(struct lola *chip, struct lola_stream *str,
+			     unsigned int tstamp)
+{
+	lola_dsd_write(chip, str->dsd, CTL,
+		       LOLA_DSD_CTL_IOCE |
+		       LOLA_DSD_CTL_DEIE |
+		       LOLA_DSD_CTL_VLRCV |
+		       tstamp);
+	lola_stream_clear_pending_irq(chip, str);
+}
+
+static void wait_for_srst_clear(struct lola *chip, struct lola_stream *str)
+{
+	unsigned long end_time = jiffies + msecs_to_jiffies(200);
+	while (time_before(jiffies, end_time)) {
+		unsigned int val;
+		val = lola_dsd_read(chip, str->dsd, CTL);
+		if (!(val & LOLA_DSD_CTL_SRST))
+			return;
+		msleep(1);
+	}
+	printk(KERN_WARNING SFX "SRST not clear (stream %d)\n", str->dsd);
+}
+
+static int lola_stream_wait_for_fifo(struct lola *chip,
+				     struct lola_stream *str,
+				     bool ready)
+{
+	unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0;
+	unsigned long end_time = jiffies + msecs_to_jiffies(200);
+	while (time_before(jiffies, end_time)) {
+		unsigned int reg = lola_dsd_read(chip, str->dsd, STS);
+		if ((reg & LOLA_DSD_STS_FIFORDY) == val)
+			return 0;
+		msleep(1);
+	}
+	printk(KERN_WARNING SFX "FIFO not ready (stream %d)\n", str->dsd);
+	return -EIO;
+}
+
+/* sync for FIFO ready/empty for all linked streams;
+ * clear paused flag when FIFO gets ready again
+ */
+static int lola_sync_wait_for_fifo(struct lola *chip,
+				   struct snd_pcm_substream *substream,
+				   bool ready)
+{
+	unsigned int val = ready ? LOLA_DSD_STS_FIFORDY : 0;
+	unsigned long end_time = jiffies + msecs_to_jiffies(200);
+	struct snd_pcm_substream *s;
+	int pending = 0;
+
+	while (time_before(jiffies, end_time)) {
+		pending = 0;
+		snd_pcm_group_for_each_entry(s, substream) {
+			struct lola_stream *str;
+			if (s->pcm->card != substream->pcm->card)
+				continue;
+			str = lola_get_stream(s);
+			if (str->prepared && str->paused) {
+				unsigned int reg;
+				reg = lola_dsd_read(chip, str->dsd, STS);
+				if ((reg & LOLA_DSD_STS_FIFORDY) != val) {
+					pending = str->dsd + 1;
+					break;
+				}
+				if (ready)
+					str->paused = 0;
+			}
+		}
+		if (!pending)
+			return 0;
+		msleep(1);
+	}
+	printk(KERN_WARNING SFX "FIFO not ready (pending %d)\n", pending - 1);
+	return -EIO;
+}
+
+/* finish pause - prepare for a new resume */
+static void lola_sync_pause(struct lola *chip,
+			    struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_substream *s;
+
+	lola_sync_wait_for_fifo(chip, substream, false);
+	snd_pcm_group_for_each_entry(s, substream) {
+		struct lola_stream *str;
+		if (s->pcm->card != substream->pcm->card)
+			continue;
+		str = lola_get_stream(s);
+		if (str->paused && str->prepared)
+			lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRUN |
+				       LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE);
+	}
+	lola_sync_wait_for_fifo(chip, substream, true);
+}
+
+static void lola_stream_reset(struct lola *chip, struct lola_stream *str)
+{
+	if (str->prepared) {
+		if (str->paused)
+			lola_sync_pause(chip, str->substream);
+		str->prepared = 0;
+		lola_dsd_write(chip, str->dsd, CTL,
+			       LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE);
+		lola_stream_wait_for_fifo(chip, str, false);
+		lola_stream_clear_pending_irq(chip, str);
+		lola_dsd_write(chip, str->dsd, CTL, LOLA_DSD_CTL_SRST);
+		lola_dsd_write(chip, str->dsd, LVI, 0);
+		lola_dsd_write(chip, str->dsd, BDPU, 0);
+		lola_dsd_write(chip, str->dsd, BDPL, 0);
+		wait_for_srst_clear(chip, str);
+	}
+}
+
+static struct snd_pcm_hardware lola_pcm_hw = {
+	.info =			(SNDRV_PCM_INFO_MMAP |
+				 SNDRV_PCM_INFO_INTERLEAVED |
+				 SNDRV_PCM_INFO_BLOCK_TRANSFER |
+				 SNDRV_PCM_INFO_MMAP_VALID |
+				 SNDRV_PCM_INFO_PAUSE),
+	.formats =		(SNDRV_PCM_FMTBIT_S16_LE |
+				 SNDRV_PCM_FMTBIT_S24_LE |
+				 SNDRV_PCM_FMTBIT_S32_LE |
+				 SNDRV_PCM_FMTBIT_FLOAT_LE),
+	.rates =		SNDRV_PCM_RATE_8000_192000,
+	.rate_min =		8000,
+	.rate_max =		192000,
+	.channels_min =		1,
+	.channels_max =		2,
+	.buffer_bytes_max =	LOLA_MAX_BUF_SIZE,
+	.period_bytes_min =	128,
+	.period_bytes_max =	LOLA_MAX_BUF_SIZE / 2,
+	.periods_min =		2,
+	.periods_max =		LOLA_MAX_BDL_ENTRIES,
+	.fifo_size =		0,
+};
+
+static int lola_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_pcm *pcm = lola_get_pcm(substream);
+	struct lola_stream *str = lola_get_stream(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	mutex_lock(&chip->open_mutex);
+	if (str->opened) {
+		mutex_unlock(&chip->open_mutex);
+		return -EBUSY;
+	}
+	str->substream = substream;
+	str->master = NULL;
+	str->opened = 1;
+	runtime->hw = lola_pcm_hw;
+	runtime->hw.channels_max = pcm->num_streams - str->index;
+	if (chip->sample_rate) {
+		/* sample rate is locked */
+		runtime->hw.rate_min = chip->sample_rate;
+		runtime->hw.rate_max = chip->sample_rate;
+	} else {
+		runtime->hw.rate_min = chip->sample_rate_min;
+		runtime->hw.rate_max = chip->sample_rate_max;
+	}
+	chip->ref_count_rate++;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	/* period size = multiple of chip->granularity (8, 16 or 32 frames)*/
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
+				   chip->granularity);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
+				   chip->granularity);
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static void lola_cleanup_slave_streams(struct lola_pcm *pcm,
+				       struct lola_stream *str)
+{
+	int i;
+	for (i = str->index + 1; i < pcm->num_streams; i++) {
+		struct lola_stream *s = &pcm->streams[i];
+		if (s->master != str)
+			break;
+		s->master = NULL;
+		s->opened = 0;
+	}
+}
+
+static int lola_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_stream *str = lola_get_stream(substream);
+
+	mutex_lock(&chip->open_mutex);
+	if (str->substream == substream) {
+		str->substream = NULL;
+		str->opened = 0;
+	}
+	if (--chip->ref_count_rate == 0) {
+		/* release sample rate */
+		chip->sample_rate = 0;
+	}
+	mutex_unlock(&chip->open_mutex);
+	return 0;
+}
+
+static int lola_pcm_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	struct lola_stream *str = lola_get_stream(substream);
+
+	str->bufsize = 0;
+	str->period_bytes = 0;
+	str->format_verb = 0;
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int lola_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_pcm *pcm = lola_get_pcm(substream);
+	struct lola_stream *str = lola_get_stream(substream);
+
+	mutex_lock(&chip->open_mutex);
+	lola_stream_reset(chip, str);
+	lola_cleanup_slave_streams(pcm, str);
+	mutex_unlock(&chip->open_mutex);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * set up a BDL entry
+ */
+static int setup_bdle(struct snd_pcm_substream *substream,
+		      struct lola_stream *str, u32 **bdlp,
+		      int ofs, int size)
+{
+	u32 *bdl = *bdlp;
+
+	while (size > 0) {
+		dma_addr_t addr;
+		int chunk;
+
+		if (str->frags >= LOLA_MAX_BDL_ENTRIES)
+			return -EINVAL;
+
+		addr = snd_pcm_sgbuf_get_addr(substream, ofs);
+		/* program the address field of the BDL entry */
+		bdl[0] = cpu_to_le32((u32)addr);
+		bdl[1] = cpu_to_le32(upper_32_bits(addr));
+		/* program the size field of the BDL entry */
+		chunk = snd_pcm_sgbuf_get_chunk_size(substream, ofs, size);
+		bdl[2] = cpu_to_le32(chunk);
+		/* program the IOC to enable interrupt
+		 * only when the whole fragment is processed
+		 */
+		size -= chunk;
+		bdl[3] = size ? 0 : cpu_to_le32(0x01);
+		bdl += 4;
+		str->frags++;
+		ofs += chunk;
+	}
+	*bdlp = bdl;
+	return ofs;
+}
+
+/*
+ * set up BDL entries
+ */
+static int lola_setup_periods(struct lola *chip, struct lola_pcm *pcm,
+			      struct snd_pcm_substream *substream,
+			      struct lola_stream *str)
+{
+	u32 *bdl;
+	int i, ofs, periods, period_bytes;
+
+	period_bytes = str->period_bytes;
+	periods = str->bufsize / period_bytes;
+
+	/* program the initial BDL entries */
+	bdl = (u32 *)(pcm->bdl.area + LOLA_BDL_ENTRY_SIZE * str->index);
+	ofs = 0;
+	str->frags = 0;
+	for (i = 0; i < periods; i++) {
+		ofs = setup_bdle(substream, str, &bdl, ofs, period_bytes);
+		if (ofs < 0)
+			goto error;
+	}
+	return 0;
+
+ error:
+	snd_printk(KERN_ERR SFX "Too many BDL entries: buffer=%d, period=%d\n",
+		   str->bufsize, period_bytes);
+	return -EINVAL;
+}
+
+static unsigned int lola_get_format_verb(struct snd_pcm_substream *substream)
+{
+	unsigned int verb;
+
+	switch (substream->runtime->format) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		verb = 0x00000000;
+		break;
+	case SNDRV_PCM_FORMAT_S24_LE:
+		verb = 0x00000200;
+		break;
+	case SNDRV_PCM_FORMAT_S32_LE:
+		verb = 0x00000300;
+		break;
+	case SNDRV_PCM_FORMAT_FLOAT_LE:
+		verb = 0x00001300;
+		break;
+	default:
+		return 0;
+	}
+	verb |= substream->runtime->channels;
+	return verb;
+}
+
+static int lola_set_stream_config(struct lola *chip,
+				  struct lola_stream *str,
+				  int channels)
+{
+	int i, err;
+	unsigned int verb, val;
+
+	/* set format info for all channels
+	 * (with only one command for the first channel)
+	 */
+	err = lola_codec_read(chip, str->nid, LOLA_VERB_SET_STREAM_FORMAT,
+			      str->format_verb, 0, &val, NULL);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Cannot set stream format 0x%x\n",
+		       str->format_verb);
+		return err;
+	}
+
+	/* update stream - channel config */
+	for (i = 0; i < channels; i++) {
+		verb = (str->index << 6) | i;
+		err = lola_codec_read(chip, str[i].nid,
+				      LOLA_VERB_SET_CHANNEL_STREAMID, 0, verb,
+				      &val, NULL);
+		if (err < 0) {
+			printk(KERN_ERR SFX "Cannot set stream channel %d\n", i);
+			return err;
+		}
+	}
+	return 0;
+}
+
+/*
+ * set up the SD for streaming
+ */
+static int lola_setup_controller(struct lola *chip, struct lola_pcm *pcm,
+				 struct lola_stream *str)
+{
+	dma_addr_t bdl;
+
+	if (str->prepared)
+		return -EINVAL;
+
+	/* set up BDL */
+	bdl = pcm->bdl.addr + LOLA_BDL_ENTRY_SIZE * str->index;
+	lola_dsd_write(chip, str->dsd, BDPL, (u32)bdl);
+	lola_dsd_write(chip, str->dsd, BDPU, upper_32_bits(bdl));
+	/* program the stream LVI (last valid index) of the BDL */
+	lola_dsd_write(chip, str->dsd, LVI, str->frags - 1);
+	lola_stream_clear_pending_irq(chip, str);
+
+ 	lola_dsd_write(chip, str->dsd, CTL,
+		       LOLA_DSD_CTL_IOCE | LOLA_DSD_CTL_DEIE | LOLA_DSD_CTL_SRUN);
+
+	str->prepared = 1;
+
+	return lola_stream_wait_for_fifo(chip, str, true);
+}
+
+static int lola_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_pcm *pcm = lola_get_pcm(substream);
+	struct lola_stream *str = lola_get_stream(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int bufsize, period_bytes, format_verb;
+	int i, err;
+
+	mutex_lock(&chip->open_mutex);
+	lola_stream_reset(chip, str);
+	lola_cleanup_slave_streams(pcm, str);
+	if (str->index + runtime->channels > pcm->num_streams) {
+		mutex_unlock(&chip->open_mutex);
+		return -EINVAL;
+	}
+	for (i = 1; i < runtime->channels; i++) {
+		str[i].master = str;
+		str[i].opened = 1;
+	}
+	mutex_unlock(&chip->open_mutex);
+
+	bufsize = snd_pcm_lib_buffer_bytes(substream);
+	period_bytes = snd_pcm_lib_period_bytes(substream);
+	format_verb = lola_get_format_verb(substream);
+
+	str->bufsize = bufsize;
+	str->period_bytes = period_bytes;
+	str->format_verb = format_verb;
+
+	err = lola_setup_periods(chip, pcm, substream, str);
+	if (err < 0)
+		return err;
+
+	err = lola_set_sample_rate(chip, runtime->rate);
+	if (err < 0)
+		return err;
+	chip->sample_rate = runtime->rate;	/* sample rate gets locked */
+
+	err = lola_set_stream_config(chip, str, runtime->channels);
+	if (err < 0)
+		return err;
+
+	err = lola_setup_controller(chip, pcm, str);
+	if (err < 0) {
+		lola_stream_reset(chip, str);
+		return err;
+	}
+
+	return 0;
+}
+
+static int lola_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_stream *str;
+	struct snd_pcm_substream *s;
+	unsigned int start;
+	unsigned int tstamp;
+	bool sync_streams;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		start = 1;
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_STOP:
+		start = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * sample correct synchronization is only needed starting several
+	 * streams. On stop or if only one stream do as quick as possible
+	 */
+	sync_streams = (start && snd_pcm_stream_linked(substream));
+	tstamp = lola_get_tstamp(chip, !sync_streams);
+	spin_lock(&chip->reg_lock);
+	snd_pcm_group_for_each_entry(s, substream) {
+		if (s->pcm->card != substream->pcm->card)
+			continue;
+		str = lola_get_stream(s);
+		if (start)
+			lola_stream_start(chip, str, tstamp);
+		else
+			lola_stream_stop(chip, str, tstamp);
+		str->running = start;
+		str->paused = !start;
+		snd_pcm_trigger_done(s, substream);
+	}
+	spin_unlock(&chip->reg_lock);
+	return 0;
+}
+
+static snd_pcm_uframes_t lola_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct lola *chip = snd_pcm_substream_chip(substream);
+	struct lola_stream *str = lola_get_stream(substream);
+	unsigned int pos = lola_dsd_read(chip, str->dsd, LPIB);
+
+	if (pos >= str->bufsize)
+		pos = 0;
+	return bytes_to_frames(substream->runtime, pos);
+}
+
+void lola_pcm_update(struct lola *chip, struct lola_pcm *pcm, unsigned int bits)
+{
+	int i;
+
+	for (i = 0; bits && i < pcm->num_streams; i++) {
+		if (bits & (1 << i)) {
+			struct lola_stream *str = &pcm->streams[i];
+			if (str->substream && str->running)
+				snd_pcm_period_elapsed(str->substream);
+			bits &= ~(1 << i);
+		}
+	}
+}
+
+static struct snd_pcm_ops lola_pcm_ops = {
+	.open = lola_pcm_open,
+	.close = lola_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = lola_pcm_hw_params,
+	.hw_free = lola_pcm_hw_free,
+	.prepare = lola_pcm_prepare,
+	.trigger = lola_pcm_trigger,
+	.pointer = lola_pcm_pointer,
+	.page = snd_pcm_sgbuf_ops_page,
+};
+
+int __devinit lola_create_pcm(struct lola *chip)
+{
+	struct snd_pcm *pcm;
+	int i, err;
+
+	for (i = 0; i < 2; i++) {
+		err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
+					  snd_dma_pci_data(chip->pci),
+					  PAGE_SIZE, &chip->pcm[i].bdl);
+		if (err < 0)
+			return err;
+	}
+
+	err = snd_pcm_new(chip->card, "Digigram Lola", 0,
+			  chip->pcm[SNDRV_PCM_STREAM_PLAYBACK].num_streams,
+			  chip->pcm[SNDRV_PCM_STREAM_CAPTURE].num_streams,
+			  &pcm);
+	if (err < 0)
+		return err;
+	strlcpy(pcm->name, "Digigram Lola", sizeof(pcm->name));
+	pcm->private_data = chip;
+	for (i = 0; i < 2; i++) {
+		if (chip->pcm[i].num_streams)
+			snd_pcm_set_ops(pcm, i, &lola_pcm_ops);
+	}
+	/* buffer pre-allocation */
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+					      snd_dma_pci_data(chip->pci),
+					      1024 * 64, 32 * 1024 * 1024);
+	return 0;
+}
+
+void lola_free_pcm(struct lola *chip)
+{
+	snd_dma_free_pages(&chip->pcm[0].bdl);
+	snd_dma_free_pages(&chip->pcm[1].bdl);
+}
+
+/*
+ */
+
+static int lola_init_stream(struct lola *chip, struct lola_stream *str,
+			    int idx, int nid, int dir)
+{
+	unsigned int val;
+	int err;
+
+	str->nid = nid;
+	str->index = idx;
+	str->dsd = idx;
+	if (dir == PLAY)
+		str->dsd += MAX_STREAM_IN_COUNT;
+	err = lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read wcaps for 0x%x\n", nid);
+		return err;
+	}
+	if (dir == PLAY) {
+		/* test TYPE and bits 0..11 (no test bit9 : Digital = 0/1) */
+		if ((val & 0x00f00dff) != 0x00000010) {
+			printk(KERN_ERR SFX "Invalid wcaps 0x%x for 0x%x\n",
+			       val, nid);
+			return -EINVAL;
+		}
+	} else {
+		/* test TYPE and bits 0..11 (no test bit9 : Digital = 0/1)
+		 * (bug : ignore bit8: Conn list = 0/1)
+		 */
+		if ((val & 0x00f00cff) != 0x00100010) {
+			printk(KERN_ERR SFX "Invalid wcaps 0x%x for 0x%x\n",
+			       val, nid);
+			return -EINVAL;
+		}
+		/* test bit9:DIGITAL and bit12:SRC_PRESENT*/
+		if ((val & 0x00001200) == 0x00001200)
+			chip->input_src_caps_mask |= (1 << idx);
+	}
+
+	err = lola_read_param(chip, nid, LOLA_PAR_STREAM_FORMATS, &val);
+	if (err < 0) {
+		printk(KERN_ERR SFX "Can't read FORMATS 0x%x\n", nid);
+		return err;
+	}
+	val &= 3;
+	if (val == 3)
+		str->can_float = true;
+	if (!(val & 1)) {
+		printk(KERN_ERR SFX "Invalid formats 0x%x for 0x%x", val, nid);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+int __devinit lola_init_pcm(struct lola *chip, int dir, int *nidp)
+{
+	struct lola_pcm *pcm = &chip->pcm[dir];
+	int i, nid, err;
+
+	nid = *nidp;
+	for (i = 0; i < pcm->num_streams; i++, nid++) {
+		err = lola_init_stream(chip, &pcm->streams[i], i, nid, dir);
+		if (err < 0)
+			return err;
+	}
+	*nidp = nid;
+	return 0;
+}

+ 222 - 0
sound/pci/lola/lola_proc.c

@@ -0,0 +1,222 @@
+/*
+ *  Support for Digigram Lola PCI-e boards
+ *
+ *  Copyright (c) 2011 Takashi Iwai <tiwai@suse.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 <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <sound/core.h>
+#include <sound/info.h>
+#include <sound/pcm.h>
+#include "lola.h"
+
+static void print_audio_widget(struct snd_info_buffer *buffer,
+			       struct lola *chip, int nid, const char *name)
+{
+	unsigned int val;
+
+	lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val);
+	lola_read_param(chip, nid, LOLA_PAR_STREAM_FORMATS, &val);
+	snd_iprintf(buffer, "  Formats: 0x%x\n", val);
+}
+
+static void print_pin_widget(struct snd_info_buffer *buffer,
+			     struct lola *chip, int nid, unsigned int ampcap,
+			     const char *name)
+{
+	unsigned int val;
+
+	lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	snd_iprintf(buffer, "Node 0x%02x %s wcaps 0x%x\n", nid, name, val);
+	if (val == 0x00400200)
+		return;
+	lola_read_param(chip, nid, ampcap, &val);
+	snd_iprintf(buffer, "  Amp-Caps: 0x%x\n", val);
+	snd_iprintf(buffer, "    mute=%d, step-size=%d, steps=%d, ofs=%d\n",
+		    LOLA_AMP_MUTE_CAPABLE(val),
+		    LOLA_AMP_STEP_SIZE(val),
+		    LOLA_AMP_NUM_STEPS(val),
+		    LOLA_AMP_OFFSET(val));
+	lola_codec_read(chip, nid, LOLA_VERB_GET_MAX_LEVEL, 0, 0, &val, NULL);
+	snd_iprintf(buffer, "  Max-level: 0x%x\n", val);
+}
+
+static void print_clock_widget(struct snd_info_buffer *buffer,
+			       struct lola *chip, int nid)
+{
+	int i, j, num_clocks;
+	unsigned int val;
+
+	lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	snd_iprintf(buffer, "Node 0x%02x [Clock] wcaps 0x%x\n", nid, val);
+	num_clocks = val & 0xff;
+	for (i = 0; i < num_clocks; i += 4) {
+		unsigned int res_ex;
+		unsigned short items[4];
+		const char *name;
+
+		lola_codec_read(chip, nid, LOLA_VERB_GET_CLOCK_LIST,
+				i, 0, &val, &res_ex);
+		items[0] = val & 0xfff;
+		items[1] = (val >> 16) & 0xfff;
+		items[2] = res_ex & 0xfff;
+		items[3] = (res_ex >> 16) & 0xfff;
+		for (j = 0; j < 4; j++) {
+			unsigned char type = items[j] >> 8;
+			unsigned int freq = items[j] & 0xff;
+			if (i + j >= num_clocks)
+				break;
+			if (type == LOLA_CLOCK_TYPE_INTERNAL) {
+				name = "Internal";
+				freq = lola_sample_rate_convert(freq);
+			} else if (type == LOLA_CLOCK_TYPE_VIDEO) {
+				name = "Video";
+				freq = lola_sample_rate_convert(freq);
+			} else {
+				name = "Other";
+			}
+			snd_iprintf(buffer, "  Clock %d: Type %d:%s, freq=%d\n",
+				    i + j, type, name, freq);
+		}
+	}
+}
+
+static void print_mixer_widget(struct snd_info_buffer *buffer,
+			       struct lola *chip, int nid)
+{
+	unsigned int val;
+
+	lola_read_param(chip, nid, LOLA_PAR_AUDIO_WIDGET_CAP, &val);
+	snd_iprintf(buffer, "Node 0x%02x [Mixer] wcaps 0x%x\n", nid, val);
+}
+
+static void lola_proc_codec_read(struct snd_info_entry *entry,
+				 struct snd_info_buffer *buffer)
+{
+	struct lola *chip = entry->private_data;
+	unsigned int val;
+	int i, nid;
+
+	lola_read_param(chip, 0, LOLA_PAR_VENDOR_ID, &val);
+	snd_iprintf(buffer, "Vendor: 0x%08x\n", val);
+	lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val);
+	snd_iprintf(buffer, "Function Type: %d\n", val);
+	lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val);
+	snd_iprintf(buffer, "Specific-Caps: 0x%08x\n", val);
+	snd_iprintf(buffer, "  Pins-In %d, Pins-Out %d\n",
+		    chip->pin[CAPT].num_pins, chip->pin[PLAY].num_pins);
+	nid = 2;
+	for (i = 0; i < chip->pcm[CAPT].num_streams; i++, nid++)
+		print_audio_widget(buffer, chip, nid, "[Audio-In]");
+	for (i = 0; i < chip->pcm[PLAY].num_streams; i++, nid++)
+		print_audio_widget(buffer, chip, nid, "[Audio-Out]");
+	for (i = 0; i < chip->pin[CAPT].num_pins; i++, nid++)
+		print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_IN_CAP,
+				 "[Pin-In]");
+	for (i = 0; i < chip->pin[PLAY].num_pins; i++, nid++)
+		print_pin_widget(buffer, chip, nid, LOLA_PAR_AMP_OUT_CAP,
+				 "[Pin-Out]");
+	if (LOLA_AFG_CLOCK_WIDGET_PRESENT(chip->lola_caps)) {
+		print_clock_widget(buffer, chip, nid);
+		nid++;
+	}
+	if (LOLA_AFG_MIXER_WIDGET_PRESENT(chip->lola_caps)) {
+		print_mixer_widget(buffer, chip, nid);
+		nid++;
+	}
+}
+
+/* direct codec access for debugging */
+static void lola_proc_codec_rw_write(struct snd_info_entry *entry,
+				     struct snd_info_buffer *buffer)
+{
+	struct lola *chip = entry->private_data;
+	char line[64];
+	unsigned int id, verb, data, extdata;
+	while (!snd_info_get_line(buffer, line, sizeof(line))) {
+		if (sscanf(line, "%i %i %i %i", &id, &verb, &data, &extdata) != 4)
+			continue;
+		lola_codec_read(chip, id, verb, data, extdata,
+				&chip->debug_res,
+				&chip->debug_res_ex);
+	}
+}
+
+static void lola_proc_codec_rw_read(struct snd_info_entry *entry,
+				    struct snd_info_buffer *buffer)
+{
+	struct lola *chip = entry->private_data;
+	snd_iprintf(buffer, "0x%x 0x%x\n", chip->debug_res, chip->debug_res_ex);
+}
+
+/*
+ * dump some registers
+ */
+static void lola_proc_regs_read(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct lola *chip = entry->private_data;
+	int i;
+
+	for (i = 0; i < 0x40; i += 4) {
+		snd_iprintf(buffer, "BAR0 %02x: %08x\n", i,
+			    readl(chip->bar[BAR0].remap_addr + i));
+	}
+	snd_iprintf(buffer, "\n");
+	for (i = 0; i < 0x30; i += 4) {
+		snd_iprintf(buffer, "BAR1 %02x: %08x\n", i,
+			    readl(chip->bar[BAR1].remap_addr + i));
+	}
+	snd_iprintf(buffer, "\n");
+	for (i = 0x80; i < 0xa0; i += 4) {
+		snd_iprintf(buffer, "BAR1 %02x: %08x\n", i,
+			    readl(chip->bar[BAR1].remap_addr + i));
+	}
+	snd_iprintf(buffer, "\n");
+	for (i = 0; i < 32; i++) {
+		snd_iprintf(buffer, "DSD %02x STS  %08x\n", i,
+			    lola_dsd_read(chip, i, STS));
+		snd_iprintf(buffer, "DSD %02x LPIB %08x\n", i,
+			    lola_dsd_read(chip, i, LPIB));
+		snd_iprintf(buffer, "DSD %02x CTL  %08x\n", i,
+			    lola_dsd_read(chip, i, CTL));
+		snd_iprintf(buffer, "DSD %02x LVIL %08x\n", i,
+			    lola_dsd_read(chip, i, LVI));
+		snd_iprintf(buffer, "DSD %02x BDPL %08x\n", i,
+			    lola_dsd_read(chip, i, BDPL));
+		snd_iprintf(buffer, "DSD %02x BDPU %08x\n", i,
+			    lola_dsd_read(chip, i, BDPU));
+	}
+}
+
+void __devinit lola_proc_debug_new(struct lola *chip)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(chip->card, "codec", &entry))
+		snd_info_set_text_ops(entry, chip, lola_proc_codec_read);
+	if (!snd_card_proc_new(chip->card, "codec_rw", &entry)) {
+		snd_info_set_text_ops(entry, chip, lola_proc_codec_rw_read);
+		entry->mode |= S_IWUSR;
+		entry->c.text.write = lola_proc_codec_rw_write;
+	}
+	if (!snd_card_proc_new(chip->card, "regs", &entry))
+		snd_info_set_text_ops(entry, chip, lola_proc_regs_read);
+}