diff options
Diffstat (limited to '')
-rw-r--r-- | sound/pci/lola/lola.c | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/sound/pci/lola/lola.c b/sound/pci/lola/lola.c new file mode 100644 index 000000000..1aa30e90b --- /dev/null +++ b/sound/pci/lola/lola.c @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support for Digigram Lola PCI-e boards + * + * Copyright (c) 2011 Takashi Iwai <tiwai@suse.de> + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.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 bool 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_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) pr_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) { + dev_warn(chip->card->dev, "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(); + } + dev_warn(chip->card->dev, "RIRB response error\n"); + if (!chip->polling_mode) { + dev_warn(chip->card->dev, "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) { + dev_err(chip->card->dev, "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) +{ + unsigned char tmp; + unsigned long end_time; + + chip->rb = snd_devm_alloc_pages(&chip->pci->dev, SNDRV_DMA_TYPE_DEV, + PAGE_SIZE); + if (!chip->rb) + return -ENOMEM; + + chip->corb.addr = chip->rb->addr; + chip->corb.buf = (__le32 *)chip->rb->area; + chip->rirb.addr = chip->rb->addr + 2048; + chip->rirb.buf = (__le32 *)(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) { + dev_err(chip->card->dev, "Can't read VENDOR_ID\n"); + return err; + } + val >>= 16; + if (val != 0x1369) { + dev_err(chip->card->dev, "Unknown codec vendor 0x%x\n", val); + return -EINVAL; + } + + err = lola_read_param(chip, 1, LOLA_PAR_FUNCTION_TYPE, &val); + if (err < 0) { + dev_err(chip->card->dev, "Can't read FUNCTION_TYPE\n"); + return err; + } + if (val != 1) { + dev_err(chip->card->dev, "Unknown function type %d\n", val); + return -EINVAL; + } + + err = lola_read_param(chip, 1, LOLA_PAR_SPECIFIC_CAPS, &val); + if (err < 0) { + dev_err(chip->card->dev, "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); + dev_dbg(chip->card->dev, "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) { + dev_err(chip->card->dev, "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 snd_card *card) +{ + struct lola *chip = card->private_data; + + if (chip->initialized) + lola_stop_hw(chip); + lola_free_mixer(chip); +} + +static int lola_create(struct snd_card *card, struct pci_dev *pci, int dev) +{ + struct lola *chip = card->private_data; + int err; + unsigned int dever; + + err = pcim_enable_device(pci); + if (err < 0) + return err; + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + card->private_free = lola_free; + + 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: + dev_warn(chip->card->dev, + "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) { + dev_warn(chip->card->dev, + "Invalid sample_rate_min %d, reset to 16000\n", + chip->sample_rate_min); + chip->sample_rate_min = 16000; + } + + err = pcim_iomap_regions(pci, (1 << 0) | (1 << 2), DRVNAME); + if (err < 0) + return err; + + chip->bar[0].addr = pci_resource_start(pci, 0); + chip->bar[0].remap_addr = pcim_iomap_table(pci)[0]; + chip->bar[1].addr = pci_resource_start(pci, 2); + chip->bar[1].remap_addr = pcim_iomap_table(pci)[2]; + + pci_set_master(pci); + + err = reset_controller(chip); + if (err < 0) + return err; + + if (devm_request_irq(&pci->dev, pci->irq, lola_interrupt, IRQF_SHARED, + KBUILD_MODNAME, chip)) { + dev_err(chip->card->dev, "unable to grab IRQ %d\n", pci->irq); + return -EBUSY; + } + chip->irq = pci->irq; + card->sync_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; + dev_dbg(chip->card->dev, "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)) { + dev_err(chip->card->dev, "invalid DEVER = %x\n", dever); + return -EINVAL; + } + + err = setup_corb_rirb(chip); + if (err < 0) + return err; + + strcpy(card->driver, "Lola"); + strscpy(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; + return 0; +} + +static int __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_devm_card_new(&pci->dev, index[dev], id[dev], THIS_MODULE, + sizeof(*chip), &card); + if (err < 0) { + dev_err(&pci->dev, "Error creating card!\n"); + return err; + } + chip = card->private_data; + + err = lola_create(card, pci, dev); + if (err < 0) + return err; + + err = lola_parse_tree(chip); + if (err < 0) + return err; + + err = lola_create_pcm(chip); + if (err < 0) + return err; + + err = lola_create_mixer(chip); + if (err < 0) + return err; + + lola_proc_debug_new(chip); + + err = snd_card_register(card); + if (err < 0) + return err; + + pci_set_drvdata(pci, card); + dev++; + return 0; +} + +static int lola_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + return snd_card_free_on_error(&pci->dev, __lola_probe(pci, pci_id)); +} + +/* PCI IDs */ +static const struct pci_device_id lola_ids[] = { + { PCI_VDEVICE(DIGIGRAM, 0x0001) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, lola_ids); + +/* pci_driver definition */ +static struct pci_driver lola_driver = { + .name = KBUILD_MODNAME, + .id_table = lola_ids, + .probe = lola_probe, +}; + +module_pci_driver(lola_driver); |