ALSA: Add the driver for Digigram Lola PCI-e boards

Added a new driver for supporting Digigram Lola PCI-e boards.

Lola has a similar h/w design like HD-audio but with extended verbs.
Thus the driver is written similarly like HD-audio driver in the bus
part.  The codec part is rather written in a fixed way specific to the
Lola board because of the verb incompatibility.

The driver provides basic PCM, supporting multi-streams and mixing.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Iwai 2011-05-03 16:14:46 +02:00
parent 521cb40b0c
commit d43f3010b8
10 changed files with 3112 additions and 0 deletions

View File

@ -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
------------------- -------------------

View File

@ -652,6 +652,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

View File

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

3
sound/pci/lola/Makefile Normal file
View File

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

731
sound/pci/lola/lola.c Normal file
View File

@ -0,0 +1,731 @@
/*
* 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"
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.");
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;
timeout = jiffies + msecs_to_jiffies(1000);
for (;;) {
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();
lola_update_rirb(chip);
}
printk(KERN_WARNING SFX "RIRB response error\n");
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);
lola_writel(chip, BAR0, CORBLBASE, (u32)chip->corb.addr);
lola_writel(chip, BAR0, CORBUBASE, upper_32_bits(chip->corb.addr));
/* 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_printd(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;
}
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,
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->sample_rate_min = 48000;
chip->granularity = LOLA_GRANULARITY_MIN;
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_printd(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, &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)

520
sound/pci/lola/lola.h Normal file
View File

@ -0,0 +1,520 @@
/*
* 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;
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;
struct snd_dma_buffer bdl; /* BDL buffer */
/* format + channel setup */
unsigned int format_verb;
/* flags */
unsigned int opened: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 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;
/* mixer */
struct lola_mixer_widget mixer;
/* hw info */
unsigned int version;
unsigned int lola_caps;
/* parameters */
unsigned int granularity;
unsigned int sample_rate_min;
/* flags */
unsigned int running :1;
unsigned int initialized :1;
unsigned int cold_reset :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);
/* 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 */

322
sound/pci/lola/lola_clock.c Normal file
View File

@ -0,0 +1,322 @@
/*
* 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 <sound/core.h>
#include <sound/pcm.h>
#include "lola.h"
static unsigned int 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 = 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 = 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 = 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);
}

826
sound/pci/lola/lola_mixer.c Normal file
View File

@ -0,0 +1,826 @@
/*
* 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;
}
*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;
lola_analog_mixer.name = name;
lola_analog_mixer.private_value = dir;
return snd_ctl_add(chip->card,
snd_ctl_new1(&lola_analog_mixer, chip));
}
/*
*/
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 = "Analog Capture Switch",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.info = lola_input_src_info,
.get = lola_input_src_get,
.put = lola_input_src_put,
};
static int __devinit create_input_src_mixer(struct lola *chip)
{
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);
}

607
sound/pci/lola/lola_pcm.c Normal file
View File

@ -0,0 +1,607 @@
/*
* 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 <sound/core.h>
#include <sound/pcm.h>
#include "lola.h"
#define BDL_SIZE 4096
#define LOLA_MAX_BDL_ENTRIES (BDL_SIZE / 16)
#define LOLA_MAX_FRAG 32
#define LOLA_MAX_BUF_SIZE (1024*1024*1024)
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 lola_stream_clear(struct lola *chip, struct lola_stream *str)
{
lola_dsd_write(chip, str->dsd, CTL, 0);
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(50);
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 void lola_stream_reset(struct lola *chip, struct lola_stream *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 int lola_stream_wait_for_fifo_ready(struct lola *chip,
struct lola_stream *str)
{
unsigned long end_time = jiffies + msecs_to_jiffies(50);
while (time_before(jiffies, end_time)) {
unsigned int val = lola_dsd_read(chip, str->dsd, STS);
if (val & LOLA_DSD_STS_FIFORDY)
return 0;
msleep(1);
}
printk(KERN_WARNING SFX "FIFO not ready (stream %d)\n", str->dsd);
return -EIO;
}
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_48000,
.rate_min = 48000,
.rate_max = 48000,
.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_FRAG,
.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;
int err;
mutex_lock(&chip->open_mutex);
if (str->opened) {
mutex_unlock(&chip->open_mutex);
return -EBUSY;
}
err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV,
snd_dma_pci_data(chip->pci),
PAGE_SIZE, &str->bdl);
if (err < 0) {
mutex_unlock(&chip->open_mutex);
printk(KERN_ERR SFX "Can't allocate BDL\n");
return err;
}
str->substream = substream;
str->master = NULL;
str->opened = 1;
runtime->hw = lola_pcm_hw;
runtime->hw.channels_max = pcm->num_streams - str->index;
snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
128);
snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
128);
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;
}
snd_dma_free_pages(&str->bdl);
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 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 *)str->bdl.area;
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_stream *str)
{
/* make sure the run bit is zero for SD */
lola_stream_clear(chip, str);
/* set up BDL */
lola_dsd_write(chip, str->dsd, BDPL, (u32)str->bdl.addr);
lola_dsd_write(chip, str->dsd, BDPU, upper_32_bits(str->bdl.addr));
/* program the stream LVI (last valid index) of the BDL */
lola_dsd_write(chip, str->dsd, LVI, str->frags - 1);
lola_stream_stop(chip, str, lola_get_tstamp(chip, false));
lola_stream_wait_for_fifo_ready(chip, str);
return 0;
}
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);
if (bufsize != str->bufsize ||
period_bytes != str->period_bytes ||
format_verb != str->format_verb) {
str->bufsize = bufsize;
str->period_bytes = period_bytes;
str->format_verb = format_verb;
err = lola_setup_periods(chip, substream, str);
if (err < 0)
return err;
}
err = lola_set_stream_config(chip, str, runtime->channels);
if (err < 0)
return err;
return lola_setup_controller(chip, str);
}
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;
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;
}
tstamp = lola_get_tstamp(chip, false);
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;
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;
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)
{
/* nothing to do */
}
/*
*/
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;
}

View File

@ -0,0 +1,86 @@
/*
* 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"
/* direct codec access for debugging */
static void lola_proc_codec_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_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));
}
for (i = 0; i < 0x30; i += 4) {
snd_iprintf(buffer, "BAR1 %02x: %08x\n", i,
readl(chip->bar[BAR1].remap_addr + i));
}
for (i = 0x80; i < 0xa0; i += 4) {
snd_iprintf(buffer, "BAR1 %02x: %08x\n", i,
readl(chip->bar[BAR1].remap_addr + i));
}
}
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);
entry->mode |= S_IWUSR;
entry->c.text.write = lola_proc_codec_write;
}
if (!snd_card_proc_new(chip->card, "regs", &entry))
snd_info_set_text_ops(entry, chip, lola_proc_regs_read);
}