[alsa-devel] [PATCH v2 5/7] ASoC: hda - add Skylake HD audio driver
Vinod Koul
vinod.koul at intel.com
Fri Apr 17 14:02:42 CEST 2015
On Fri, Apr 17, 2015 at 12:06:50PM +0200, Takashi Iwai wrote:
> At Fri, 17 Apr 2015 14:43:18 +0530,
> Vinod Koul wrote:
> >
> > From: Jeeja KP <jeeja.kp at intel.com>
> >
> > This patch adds ASoC Skylake HD audio controller driver
> >
> > Signed-off-by: Jeeja KP <jeeja.kp at intel.com>
> > Signed-off-by: Subhransu S. Prusty <subhransu.s.prusty at intel.com>
> > Signed-off-by: Vinod Koul <vinod.koul at intel.com>
> > ---
> > include/sound/hdaudio.h | 1 +
> > sound/soc/hda/hda_skl.c | 748 +++++++++++++++++++++++++++++++++++++++++++++++
> > sound/soc/hda/hda_skl.h | 44 +++
> > 3 files changed, 793 insertions(+)
> > create mode 100644 sound/soc/hda/hda_skl.c
> > create mode 100644 sound/soc/hda/hda_skl.h
> >
> > diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
> > index 227e71956c35..a8feb761e00d 100644
> > --- a/include/sound/hdaudio.h
> > +++ b/include/sound/hdaudio.h
> > @@ -393,6 +393,7 @@ struct hdac_stream {
> > bool running:1;
> > bool no_period_wakeup:1;
> > bool locked:1;
> > + bool prepared:1;
> >
> > /* timestamp */
> > unsigned long start_wallclk; /* start + minimum wallclk */
>
> OK, this chunk should be in the earlier patch.
> Or I'll apply it now independently.
>
>
> > diff --git a/sound/soc/hda/hda_skl.c b/sound/soc/hda/hda_skl.c
> > new file mode 100644
> > index 000000000000..8a66ef5a4ed8
> > --- /dev/null
> > +++ b/sound/soc/hda/hda_skl.c
> > @@ -0,0 +1,748 @@
> > +/*
> > + * hda_skl.c - Implementation of primary ASoC Intel HD Audio driver
> > + *
> > + * Copyright (C) 2015 Intel Corp
> > + * Author: Jeeja KP <jeeja.kp at intel.com>
> > + *
> > + * Copyright (c) 2004 Takashi Iwai <tiwai at suse.de>
> > + * PeiSen Hou <pshou at realtek.com.tw>
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + *
> > + * 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; version 2 of the License.
> > + *
> > + * 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.
> > + *
> > + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > + */
> > +
> > +#include <linux/delay.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/moduleparam.h>
> > +#include <linux/pci.h>
> > +#include <linux/mutex.h>
> > +#include <linux/io.h>
> > +#include <linux/pm_runtime.h>
> > +
> > +#include <linux/platform_device.h>
> > +#include <sound/core.h>
> > +#include <sound/pcm.h>
> > +#include <sound/hda_register.h>
> > +#include <sound/hdaudio.h>
> > +#include "hda_skl.h"
> > +
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_SUPPORTED_DEVICE("{Intel, PCH},");
> > +MODULE_DESCRIPTION("Intel aDSP HDA driver");
> > +
> > +/*
> > + * initialize the PCI registers
> > + */
> > +/* update bits in a PCI register byte */
> > +static void update_pci_byte(struct pci_dev *pci, unsigned int reg,
> > + unsigned char mask, unsigned char val)
> > +{
> > + unsigned char data;
> > +
> > + pci_read_config_byte(pci, reg, &data);
> > + data &= ~mask;
> > + data |= (val & mask);
> > + pci_write_config_byte(pci, reg, data);
> > +}
> > +
> > +static void azx_init_pci(struct hdac_bus *chip)
> > +{
> > + struct hda_soc_bus *hda = container_of(chip, struct hda_soc_bus, chip);
> > +
> > + /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
> > + * TCSEL == Traffic Class Select Register, which sets PCI express QOS
> > + * Ensuring these bits are 0 clears playback static on some HD Audio
> > + * codecs.
> > + * The PCI register TCSEL is defined in the Intel manuals.
> > + */
> > + dev_dbg(chip->dev, "Clearing TCSEL\n");
> > + update_pci_byte(hda->pci, AZX_PCIREG_TCSEL, 0x07, 0);
> > +}
> > +
> > +irqreturn_t azx_interrupt(int irq, void *dev_id)
> > +{
> > + struct hdac_bus *chip = dev_id;
> > + u32 status;
> > +
> > +#ifdef CONFIG_PM
> > + if (!pm_runtime_active(chip->dev))
> > + return IRQ_NONE;
> > +#endif
> > +
> > + spin_lock(&chip->reg_lock);
> > +
> > + status = snd_hdac_chip_readl(chip, INTSTS);
> > + if (status == 0 || status == 0xffffffff) {
> > + spin_unlock(&chip->reg_lock);
> > + return IRQ_NONE;
> > + }
> > + spin_unlock(&chip->reg_lock);
> > +
> > + return IRQ_WAKE_THREAD;
> > +}
> > +
> > +irqreturn_t azx_threaded_handler(int irq, void *dev_id)
> > +{
> > + struct hdac_bus *chip = dev_id;
> > + u32 status;
> > + unsigned long cookie;
> > +
> > + status = snd_hdac_chip_readl(chip, INTSTS);
> > + spin_lock_irqsave(&chip->reg_lock, cookie);
> > +
> > + snd_hdac_bus_handle_stream_irq(chip, status, &azx_position_check);
> > +
> > + /* clear rirb int */
> > + status = snd_hdac_chip_readb(chip, RIRBSTS);
> > + if (status & RIRB_INT_MASK) {
> > + if (status & RIRB_INT_RESPONSE)
> > + snd_hdac_bus_update_rirb(chip);
> > + snd_hdac_chip_writeb(chip, RIRBSTS, RIRB_INT_MASK);
> > + }
> > +
> > + spin_unlock_irqrestore(&chip->reg_lock, cookie);
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +/* initialize SD streams, use seprate streeam tag for PB and CP */
> > +int azx_init_stream(struct hdac_bus *chip, int num_stream, int direction)
>
> Missing static.
>
> > +{
> > + int i, tag;
> > + int stream_tag = 0;
> > +
> > + /* initialize each stream (aka device)
> > + * assign the starting bdl address to each stream (device)
> > + * and initialize
> > + */
> > + for (i = 0; i < num_stream; i++) {
> > + struct hdac_stream *hdac_stream =
> > + devm_kzalloc(chip->dev, sizeof(*hdac_stream), GFP_KERNEL);
>
> hdac_bus_exit() has a check of list_empty() for the stream list.
> It means that it expects that the driver cleans up the stream list
> beforehand properly. That is, devm doesn't work well because it'll be
> performed after that... We can get rid of the check, too, if
> preferred, of course.
Okay, i think would prefer later as devm_ eases drivers job :)
Will send patch for that
>
>
> > + if (!hdac_stream) {
> > + dev_err(chip->dev, "kzalloc block failed");
> > + return -ENOMEM;
> > + }
> > + tag = ++stream_tag;
> > + snd_hdac_stream_init(chip, hdac_stream, i, direction, tag);
> > + list_add_tail(&hdac_stream->list, &chip->stream_list);
> > + }
> > + return 0;
> > +}
> > +
> > +/* calculate runtime delay from LPIB */
> > +int azx_get_delay_from_lpib(struct hdac_bus *chip,
> > + struct hdac_stream *azx_dev,
> > + unsigned int pos)
> > +{
> > + struct snd_pcm_substream *substream = azx_dev->substream;
> > + int stream = substream->stream;
> > + unsigned int lpib_pos = snd_hdac_stream_get_pos_lpib(azx_dev);
> > + int delay;
> > +
> > + if (stream == SNDRV_PCM_STREAM_PLAYBACK)
> > + delay = pos - lpib_pos;
> > + else
> > + delay = lpib_pos - pos;
> > + if (delay < 0) {
> > + if (delay >= azx_dev->delay_negative_threshold)
> > + delay = 0;
> > + else
> > + delay += azx_dev->bufsize;
> > + }
> > +
> > + if (delay >= azx_dev->period_bytes) {
> > + dev_info(chip->dev,
> > + "Unstable LPIB (%d >= %d); disabling LPIB delay counting\n",
> > + delay, azx_dev->period_bytes);
> > + delay = 0;
> > + }
> > +
> > + return bytes_to_frames(substream->runtime, delay);
> > +}
> > +
> > +/*
> > + * Check whether the current DMA position is acceptable for updating
> > + * periods. Returns non-zero if it's OK.
> > + *
> > + * Many HD-audio controllers appear pretty inaccurate about
> > + * the update-IRQ timing. The IRQ is issued before actually the
> > + * data is processed. So, we need to process it afterwords in a
> > + * workqueue.
> > + */
> > +static int azx_position_ok(struct hdac_bus *chip, struct hdac_stream *azx_dev)
> > +{
> > + u32 wallclk;
> > + unsigned int pos = 0;
> > +
> > + wallclk = snd_hdac_chip_readl(chip, WALLCLK) - azx_dev->start_wallclk;
> > + if (wallclk < (azx_dev->period_wallclk * 2) / 3)
> > + return -1; /* bogus (too early) interrupt */
> > +
> > + if (chip->use_posbuf) {
> > + /* use the position buffer as default */
> > + pos = snd_hdac_stream_get_pos_posbuf(azx_dev);
> > + if (!pos || pos == (u32)-1) {
> > + dev_info(chip->dev,
> > + "Invalid pos buffer, using LPIB read method instead.\n");
> > + pos = snd_hdac_stream_get_pos_lpib(azx_dev);
> > + }
> > + }
> > +
> > + if (pos >= azx_dev->bufsize)
> > + pos = 0;
> > +
> > + if (WARN_ONCE(!azx_dev->period_bytes,
> > + "hda-skl: zero azx_dev->period_bytes"))
> > + return -1; /* this shouldn't happen! */
> > + if (wallclk < (azx_dev->period_wallclk * 5) / 4 &&
> > + pos % azx_dev->period_bytes > azx_dev->period_bytes / 2)
> > + /* NG - it's below the first next period boundary */
> > + return chip->bdl_pos_adj ? 0 : -1;
> > + azx_dev->start_wallclk += wallclk;
> > + return 1; /* OK, it's fine */
> > +}
> > +
> > +/* called from IRQ */
> > +void azx_position_check(struct hdac_bus *chip, struct hdac_stream *azx_dev)
> > +{
> > + if (azx_position_ok(chip, azx_dev) > 0)
> > + snd_pcm_period_elapsed(azx_dev->substream);
> > +}
> > +
> > +static int azx_acquire_irq(struct hdac_bus *chip, int do_disconnect)
> > +{
> > + struct hda_soc_bus *hda = container_of(chip, struct hda_soc_bus, chip);
> > +
> > + if (request_threaded_irq(hda->pci->irq, azx_interrupt,
> > + azx_threaded_handler,
> > + hda->msi ? 0 : IRQF_SHARED,
> > + KBUILD_MODNAME, chip)) {
> > + dev_err(chip->dev,
> > + "unable to grab IRQ %d, disabling device\n",
> > + hda->pci->irq);
> > + return -1;
> > + }
> > + pci_intx(hda->pci, !hda->msi);
>
> You should keep the irq number tracking in bus->irq. Otherwise it may
> result in a wrong free_irq() in the error paths....
ah yes missed that, will update
>
> > + return 0;
> > +}
> > +
> > +#ifdef CONFIG_PM_SLEEP
> > +/*
> > + * power management
> > + */
> > +static int azx_suspend(struct device *dev)
> > +{
> > + struct pci_dev *pci = to_pci_dev(dev);
> > + struct hdac_bus *chip = pci_get_drvdata(pci);
> > + struct hda_soc_bus *hda = container_of(chip, struct hda_soc_bus, chip);
> > +
> > + snd_hdac_bus_stop_chip(chip);
> > + snd_hdac_bus_enter_link_reset(chip);
> > + if (pci->irq >= 0)
>
> .... here check bus->irq.
Yup
--
~Vinod
More information about the Alsa-devel
mailing list