summaryrefslogtreecommitdiff
path: root/sound/pci/hda/lib_hda_intel.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/hda/lib_hda_intel.c')
-rw-r--r--sound/pci/hda/lib_hda_intel.c1411
1 files changed, 1411 insertions, 0 deletions
diff --git a/sound/pci/hda/lib_hda_intel.c b/sound/pci/hda/lib_hda_intel.c
new file mode 100644
index 000000000000..293480b0308d
--- /dev/null
+++ b/sound/pci/hda/lib_hda_intel.c
@@ -0,0 +1,1411 @@
+/*
+ *
+ * lib_hda_intel.c - Common functions for Intel HD Audio drivers.
+ *
+ * Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ * PeiSen Hou <pshou@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; 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.
+ *
+ * CONTACTS:
+ *
+ * Matt Jared matt.jared@intel.com
+ * Andy Kopp andy.kopp@intel.com
+ * Dan Kogan dan.d.kogan@intel.com
+ *
+ * CHANGES:
+ *
+ * 2004.12.01 Major rewrite by tiwai, merged the work of pshou
+ *
+ */
+
+#include "lib_hda_intel.h"
+
+/*
+ * Interface for HD codec
+ */
+
+void azx_init_cmd_io(struct azx *chip)
+{
+ spin_lock_irq(&chip->reg_lock);
+ /* CORB set up */
+ chip->corb.addr = chip->rb.addr;
+ chip->corb.buf = (u32 *)chip->rb.area;
+ azx_writel(chip, CORBLBASE, (u32)chip->corb.addr);
+ azx_writel(chip, CORBUBASE, upper_32_bits(chip->corb.addr));
+
+ /* set the corb size to 256 entries (ULI requires explicitly) */
+ azx_writeb(chip, CORBSIZE, 0x02);
+ /* set the corb write pointer to 0 */
+ azx_writew(chip, CORBWP, 0);
+ /* reset the corb hw read pointer */
+ azx_writew(chip, CORBRP, ICH6_CORBRP_RST);
+ /* enable corb dma */
+ azx_writeb(chip, CORBCTL, ICH6_CORBCTL_RUN);
+
+ /* RIRB set up */
+ chip->rirb.addr = chip->rb.addr + 2048;
+ chip->rirb.buf = (u32 *)(chip->rb.area + 2048);
+ chip->rirb.wp = chip->rirb.rp = 0;
+ memset(chip->rirb.cmds, 0, sizeof(chip->rirb.cmds));
+ azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr);
+ azx_writel(chip, RIRBUBASE, upper_32_bits(chip->rirb.addr));
+
+ /* set the rirb size to 256 entries (ULI requires explicitly) */
+ azx_writeb(chip, RIRBSIZE, 0x02);
+ /* reset the rirb hw write pointer */
+ azx_writew(chip, RIRBWP, ICH6_RIRBWP_RST);
+ /* set N=1, get RIRB response interrupt for new entry */
+ if (chip->driver_type == AZX_DRIVER_CTX)
+ azx_writew(chip, RINTCNT, 0xc0);
+ else
+ azx_writew(chip, RINTCNT, 1);
+ /* enable rirb dma and response irq */
+ azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN);
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+void azx_free_cmd_io(struct azx *chip)
+{
+ spin_lock_irq(&chip->reg_lock);
+ /* disable ringbuffer DMAs */
+ azx_writeb(chip, RIRBCTL, 0);
+ azx_writeb(chip, CORBCTL, 0);
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+unsigned int azx_command_addr(u32 cmd)
+{
+ unsigned int addr = cmd >> 28;
+
+ if (addr >= AZX_MAX_CODECS) {
+ snd_BUG();
+ addr = 0;
+ }
+
+ return addr;
+}
+
+unsigned int azx_response_addr(u32 res)
+{
+ unsigned int addr = res & 0xf;
+
+ if (addr >= AZX_MAX_CODECS) {
+ snd_BUG();
+ addr = 0;
+ }
+
+ return addr;
+}
+
+/* send a command */
+int azx_corb_send_cmd(struct hda_bus *bus, u32 val)
+{
+ struct azx *chip = bus->private_data;
+ unsigned int addr = azx_command_addr(val);
+ unsigned int wp;
+
+ spin_lock_irq(&chip->reg_lock);
+
+ /* add command to corb */
+ wp = azx_readb(chip, CORBWP);
+ wp++;
+ wp %= ICH6_MAX_CORB_ENTRIES;
+
+ chip->rirb.cmds[addr]++;
+ chip->corb.buf[wp] = cpu_to_le32(val);
+ wmb();
+ azx_writel(chip, CORBWP, wp);
+
+ spin_unlock_irq(&chip->reg_lock);
+
+ return 0;
+}
+
+#define ICH6_RIRB_EX_UNSOL_EV (1<<4)
+
+/* retrieve RIRB entry - called from interrupt handler */
+void azx_update_rirb(struct azx *chip)
+{
+ unsigned int rp, wp;
+ unsigned int addr;
+ u32 res, res_ex;
+
+ wp = azx_readb(chip, RIRBWP);
+ if (wp == chip->rirb.wp)
+ return;
+ chip->rirb.wp = wp;
+
+ while (chip->rirb.rp != wp) {
+ chip->rirb.rp++;
+ chip->rirb.rp %= ICH6_MAX_RIRB_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]);
+ addr = azx_response_addr(res_ex);
+ if (res_ex & ICH6_RIRB_EX_UNSOL_EV)
+ snd_hda_queue_unsol_event(chip->bus, res, res_ex);
+ else if (chip->rirb.cmds[addr]) {
+ chip->rirb.res[addr] = res;
+ smp_wmb();
+ chip->rirb.cmds[addr]--;
+ } else
+ snd_printk(KERN_ERR SFX "spurious response %#x:%#x, "
+ "last cmd=%#08x\n",
+ res, res_ex,
+ chip->last_cmd[addr]);
+ }
+}
+
+/* receive a response */
+unsigned int azx_rirb_get_response(struct hda_bus *bus,
+ unsigned int addr)
+{
+ struct azx *chip = bus->private_data;
+ unsigned long timeout;
+ int do_poll = 0;
+
+ again:
+ timeout = jiffies + msecs_to_jiffies(1000);
+ for (;;) {
+ if (chip->polling_mode || do_poll) {
+ spin_lock_irq(&chip->reg_lock);
+ azx_update_rirb(chip);
+ spin_unlock_irq(&chip->reg_lock);
+ }
+ if (!chip->rirb.cmds[addr]) {
+ smp_rmb();
+ bus->rirb_error = 0;
+
+ if (!do_poll)
+ chip->poll_count = 0;
+ return chip->rirb.res[addr]; /* the last value */
+ }
+ if (time_after(jiffies, timeout))
+ break;
+ if (bus->needs_damn_long_delay)
+ msleep(2); /* temporary workaround */
+ else {
+ udelay(10);
+ cond_resched();
+ }
+ }
+
+ if (!chip->polling_mode && chip->poll_count < 2) {
+ snd_printdd(SFX "azx_get_response timeout, "
+ "polling the codec once: last cmd=0x%08x\n",
+ chip->last_cmd[addr]);
+ do_poll = 1;
+ chip->poll_count++;
+ goto again;
+ }
+
+
+ if (!chip->polling_mode) {
+ snd_printk(KERN_WARNING SFX "azx_get_response timeout, "
+ "switching to polling mode: last cmd=0x%08x\n",
+ chip->last_cmd[addr]);
+ chip->polling_mode = 1;
+ goto again;
+ }
+#if !defined(CONFIG_SND_HDA_TEGRA)
+ if (chip->msi) {
+ snd_printk(KERN_WARNING SFX "No response from codec, "
+ "disabling MSI: last cmd=0x%08x\n",
+ chip->last_cmd[addr]);
+ free_irq(chip->irq, chip);
+ chip->irq = -1;
+ pci_disable_msi(chip->pci);
+ chip->msi = 0;
+ if (azx_acquire_irq(chip, 1) < 0) {
+ bus->rirb_error = 1;
+ return -1;
+ }
+ goto again;
+ }
+#endif /* !CONFIG_SND_HDA_TEGRA */
+
+ if (chip->probing) {
+ /* If this critical timeout happens during the codec probing
+ * phase, this is likely an access to a non-existing codec
+ * slot. Better to return an error and reset the system.
+ */
+ return -1;
+ }
+
+ /* a fatal communication error; need either to reset or to fallback
+ * to the single_cmd mode
+ */
+ bus->rirb_error = 1;
+ if (bus->allow_bus_reset && !bus->response_reset && !bus->in_reset) {
+ bus->response_reset = 1;
+ return -1; /* give a chance to retry */
+ }
+
+ snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, "
+ "switching to single_cmd mode: last cmd=0x%08x\n",
+ chip->last_cmd[addr]);
+ chip->single_cmd = 1;
+ bus->response_reset = 0;
+ /* release CORB/RIRB */
+ azx_free_cmd_io(chip);
+ /* disable unsolicited responses */
+ azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_UNSOL);
+ return -1;
+}
+
+/*
+ * Use the single immediate command instead of CORB/RIRB for simplicity
+ *
+ * Note: according to Intel, this is not preferred use. The command was
+ * intended for the BIOS only, and may get confused with unsolicited
+ * responses. So, we shouldn't use it for normal operation from the
+ * driver.
+ * I left the codes, however, for debugging/testing purposes.
+ */
+
+/* receive a response */
+int azx_single_wait_for_response(struct azx *chip, unsigned int addr)
+{
+ int timeout = 50;
+
+ while (timeout--) {
+ /* check IRV busy bit */
+ if (azx_readw(chip, IRS) & ICH6_IRS_VALID) {
+ /* reuse rirb.res as the response return value */
+ chip->rirb.res[addr] = azx_readl(chip, IR);
+ return 0;
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ snd_printd(SFX "get_response timeout: IRS=0x%x\n",
+ azx_readw(chip, IRS));
+ chip->rirb.res[addr] = -1;
+ return -EIO;
+}
+
+/* send a command */
+int azx_single_send_cmd(struct hda_bus *bus, u32 val)
+{
+ struct azx *chip = bus->private_data;
+ unsigned int addr = azx_command_addr(val);
+ int timeout = 50;
+
+ bus->rirb_error = 0;
+ while (timeout--) {
+ /* check ICB busy bit */
+ if (!((azx_readw(chip, IRS) & ICH6_IRS_BUSY))) {
+ /* Clear IRV valid bit */
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ ICH6_IRS_VALID);
+ azx_writel(chip, IC, val);
+ azx_writew(chip, IRS, azx_readw(chip, IRS) |
+ ICH6_IRS_BUSY);
+ return azx_single_wait_for_response(chip, addr);
+ }
+ udelay(1);
+ }
+ if (printk_ratelimit())
+ snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n",
+ azx_readw(chip, IRS), val);
+ return -EIO;
+}
+
+/* receive a response */
+unsigned int azx_single_get_response(struct hda_bus *bus,
+ unsigned int addr)
+{
+ struct azx *chip = bus->private_data;
+ return chip->rirb.res[addr];
+}
+
+/*
+ * The below are the main callbacks from hda_codec.
+ *
+ * They are just the skeleton to call sub-callbacks according to the
+ * current setting of chip->single_cmd.
+ */
+
+/* send a command */
+int azx_send_cmd(struct hda_bus *bus, unsigned int val)
+{
+ struct azx *chip = bus->private_data;
+
+ chip->last_cmd[azx_command_addr(val)] = val;
+ if (chip->single_cmd)
+ return azx_single_send_cmd(bus, val);
+ else
+ return azx_corb_send_cmd(bus, val);
+}
+
+/* get a response */
+unsigned int azx_get_response(struct hda_bus *bus,
+ unsigned int addr)
+{
+ struct azx *chip = bus->private_data;
+ if (chip->single_cmd)
+ return azx_single_get_response(bus, addr);
+ else
+ return azx_rirb_get_response(bus, addr);
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+void azx_power_notify(struct hda_bus *bus);
+#endif
+
+/* reset codec link */
+ int azx_reset(struct azx *chip, int full_reset)
+{
+ int count;
+
+ if (!full_reset)
+ goto __skip;
+
+ /* clear STATESTS */
+ azx_writeb(chip, STATESTS, STATESTS_INT_MASK);
+
+ /* reset controller */
+ azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET);
+
+ count = 50;
+ while (azx_readb(chip, GCTL) && --count)
+ msleep(1);
+
+ /* delay for >= 100us for codec PLL to settle per spec
+ * Rev 0.9 section 5.5.1
+ */
+ msleep(1);
+
+ /* Bring controller out of reset */
+ azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET);
+
+ count = 50;
+ while (!azx_readb(chip, GCTL) && --count)
+ msleep(1);
+
+ /* Brent Chartrand said to wait >= 540us for codecs to initialize */
+ msleep(1);
+
+ __skip:
+ /* check to see if controller is ready */
+ if (!azx_readb(chip, GCTL)) {
+ snd_printd(SFX "azx_reset: controller not ready!\n");
+ return -EBUSY;
+ }
+
+ /* Accept unsolicited responses */
+ if (!chip->single_cmd)
+ azx_writel(chip, GCTL, azx_readl(chip, GCTL) |
+ ICH6_GCTL_UNSOL);
+
+ /* detect codecs */
+ if (!chip->codec_mask) {
+ chip->codec_mask = azx_readw(chip, STATESTS);
+ snd_printdd(SFX "codec_mask = 0x%x\n", chip->codec_mask);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Lowlevel interface
+ */
+
+/* enable interrupts */
+void azx_int_enable(struct azx *chip)
+{
+ /* enable controller CIE and GIE */
+ azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) |
+ ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN);
+}
+
+/* disable interrupts */
+void azx_int_disable(struct azx *chip)
+{
+ int i;
+
+ /* disable interrupts in stream descriptor */
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = &chip->azx_dev[i];
+ azx_sd_writeb(azx_dev, SD_CTL,
+ azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK);
+ }
+
+ /* disable SIE for all streams */
+ azx_writeb(chip, INTCTL, 0);
+
+ /* disable controller CIE and GIE */
+ azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) &
+ ~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN));
+}
+
+/* clear interrupts */
+void azx_int_clear(struct azx *chip)
+{
+ int i;
+
+ /* clear stream status */
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = &chip->azx_dev[i];
+ azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
+ }
+
+ /* clear STATESTS */
+ azx_writeb(chip, STATESTS, STATESTS_INT_MASK);
+
+ /* clear rirb status */
+ azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+
+ /* clear int status */
+ azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM);
+}
+
+/* start a stream */
+void azx_stream_start(struct azx *chip, struct azx_dev *azx_dev)
+{
+ /*
+ * Before stream start, initialize parameter
+ */
+ azx_dev->insufficient = 1;
+
+ /* enable SIE */
+ azx_writel(chip, INTCTL,
+ azx_readl(chip, INTCTL) | (1 << azx_dev->index));
+ /* set DMA start and interrupt mask */
+ azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) |
+ SD_CTL_DMA_START | SD_INT_MASK);
+}
+
+/* stop DMA */
+void azx_stream_clear(struct azx *chip, struct azx_dev *azx_dev)
+{
+ azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) &
+ ~(SD_CTL_DMA_START | SD_INT_MASK));
+ azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
+}
+
+/* stop a stream */
+void azx_stream_stop(struct azx *chip, struct azx_dev *azx_dev)
+{
+ azx_stream_clear(chip, azx_dev);
+ /* disable SIE */
+ azx_writel(chip, INTCTL,
+ azx_readl(chip, INTCTL) & ~(1 << azx_dev->index));
+}
+
+
+/*
+ * reset and start the controller registers
+ */
+ void azx_init_chip(struct azx *chip, int full_reset)
+{
+ if (chip->initialized)
+ return;
+
+ /* reset controller */
+ azx_reset(chip, full_reset);
+
+ /* initialize interrupts */
+ azx_int_clear(chip);
+ azx_int_enable(chip);
+
+ /* initialize the codec command I/O */
+ if (!chip->single_cmd)
+ azx_init_cmd_io(chip);
+
+ /* program the position buffer */
+ azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr);
+ azx_writel(chip, DPUBASE, upper_32_bits(chip->posbuf.addr));
+
+ chip->initialized = 1;
+}
+
+int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev);
+
+/*
+ * interrupt handler
+ */
+irqreturn_t azx_interrupt(int irq, void *dev_id)
+{
+ struct azx *chip = dev_id;
+ struct azx_dev *azx_dev;
+ u32 status;
+ u8 sd_status;
+ int i, ok;
+
+ spin_lock(&chip->reg_lock);
+
+ status = azx_readl(chip, INTSTS);
+ if (status == 0) {
+ spin_unlock(&chip->reg_lock);
+ return IRQ_NONE;
+ }
+
+ for (i = 0; i < chip->num_streams; i++) {
+ azx_dev = &chip->azx_dev[i];
+ if (status & azx_dev->sd_int_sta_mask) {
+ sd_status = azx_sd_readb(azx_dev, SD_STS);
+ azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
+ if (!azx_dev->substream || !azx_dev->running ||
+ !(sd_status & SD_INT_COMPLETE))
+ continue;
+ /* check whether this IRQ is really acceptable */
+ ok = azx_position_ok(chip, azx_dev);
+ if (ok == 1) {
+ azx_dev->irq_pending = 0;
+ spin_unlock(&chip->reg_lock);
+ snd_pcm_period_elapsed(azx_dev->substream);
+ spin_lock(&chip->reg_lock);
+ } else if (ok == 0 && chip->bus && chip->bus->workq) {
+ /* bogus IRQ, process it later */
+ azx_dev->irq_pending = 1;
+ queue_work(chip->bus->workq,
+ &chip->irq_pending_work);
+ }
+ }
+ }
+
+ /* clear rirb int */
+ status = azx_readb(chip, RIRBSTS);
+ if (status & RIRB_INT_MASK) {
+ if (status & RIRB_INT_RESPONSE) {
+ if (chip->driver_type == AZX_DRIVER_CTX)
+ udelay(80);
+ azx_update_rirb(chip);
+ }
+ azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+ }
+
+#if 0
+ /* clear state status int */
+ if (azx_readb(chip, STATESTS) & 0x04)
+ azx_writeb(chip, STATESTS, 0x04);
+#endif
+ spin_unlock(&chip->reg_lock);
+
+ return IRQ_HANDLED;
+}
+
+
+/*
+ * set up a BDL entry
+ */
+int setup_bdle(struct snd_pcm_substream *substream,
+ struct azx_dev *azx_dev, u32 **bdlp,
+ int ofs, int size, int with_ioc)
+{
+ u32 *bdl = *bdlp;
+
+ while (size > 0) {
+ dma_addr_t addr;
+ int chunk;
+
+ if (azx_dev->frags >= AZX_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 || !with_ioc) ? 0 : cpu_to_le32(0x01);
+ bdl += 4;
+ azx_dev->frags++;
+ ofs += chunk;
+ }
+ *bdlp = bdl;
+ return ofs;
+}
+
+/* reset stream */
+void azx_stream_reset(struct azx *chip, struct azx_dev *azx_dev)
+{
+ unsigned char val;
+ int timeout;
+
+ azx_stream_clear(chip, azx_dev);
+
+ azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) |
+ SD_CTL_STREAM_RESET);
+ udelay(3);
+ timeout = 300;
+ while (!((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
+ --timeout)
+ ;
+ val &= ~SD_CTL_STREAM_RESET;
+ azx_sd_writeb(azx_dev, SD_CTL, val);
+ udelay(3);
+
+ timeout = 300;
+ /* waiting for hardware to report that the stream is out of reset */
+ while (((val = azx_sd_readb(azx_dev, SD_CTL)) & SD_CTL_STREAM_RESET) &&
+ --timeout)
+ ;
+
+ /* reset first position - may not be synced with hw at this time */
+ *azx_dev->posbuf = 0;
+}
+
+/*
+ * set up the SD for streaming
+ */
+int azx_setup_controller(struct azx *chip, struct azx_dev *azx_dev)
+{
+ /* make sure the run bit is zero for SD */
+ azx_stream_clear(chip, azx_dev);
+ /* program the stream_tag */
+ azx_sd_writel(azx_dev, SD_CTL,
+ (azx_sd_readl(azx_dev, SD_CTL) & ~SD_CTL_STREAM_TAG_MASK)|
+ (azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT));
+
+ /* program the length of samples in cyclic buffer */
+ azx_sd_writel(azx_dev, SD_CBL, azx_dev->bufsize);
+
+ /* program the stream format */
+ /* this value needs to be the same as the one programmed */
+ azx_sd_writew(azx_dev, SD_FORMAT, azx_dev->format_val);
+
+ /* program the stream LVI (last valid index) of the BDL */
+ azx_sd_writew(azx_dev, SD_LVI, azx_dev->frags - 1);
+
+ /* program the BDL address */
+ /* lower BDL address */
+ azx_sd_writel(azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr);
+ /* upper BDL address */
+ azx_sd_writel(azx_dev, SD_BDLPU, upper_32_bits(azx_dev->bdl.addr));
+
+ /* enable the position buffer */
+ if (chip->position_fix[0] == POS_FIX_POSBUF ||
+ chip->position_fix[0] == POS_FIX_AUTO ||
+ chip->position_fix[1] == POS_FIX_POSBUF ||
+ chip->position_fix[1] == POS_FIX_AUTO ||
+ chip->via_dmapos_patch) {
+ if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
+ azx_writel(chip, DPLBASE,
+ (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE);
+ }
+
+ /* set the interrupt enable bits in the descriptor control register */
+ azx_sd_writel(azx_dev, SD_CTL,
+ azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK);
+
+ return 0;
+}
+
+/*
+ * Probe the given codec address
+ */
+int probe_codec(struct azx *chip, int addr)
+{
+ unsigned int cmd = (addr << 28) | (AC_NODE_ROOT << 20) |
+ (AC_VERB_PARAMETERS << 8) | AC_PAR_VENDOR_ID;
+ unsigned int res;
+
+ mutex_lock(&chip->bus->cmd_mutex);
+ chip->probing = 1;
+ azx_send_cmd(chip->bus, cmd);
+ res = azx_get_response(chip->bus, addr);
+ chip->probing = 0;
+ mutex_unlock(&chip->bus->cmd_mutex);
+ if (res == -1)
+ return -EIO;
+ snd_printdd(SFX "codec #%d probed OK\n", addr);
+ return 0;
+}
+
+void azx_stop_chip(struct azx *chip);
+
+void azx_bus_reset(struct hda_bus *bus)
+{
+ struct azx *chip = bus->private_data;
+
+ bus->in_reset = 1;
+ azx_stop_chip(chip);
+ azx_init_chip(chip, 1);
+#ifdef CONFIG_PM
+ if (chip->initialized) {
+ int i;
+
+ for (i = 0; i < HDA_MAX_PCMS; i++)
+ snd_pcm_suspend_all(chip->pcm[i]);
+ snd_hda_suspend(chip->bus);
+ snd_hda_resume(chip->bus);
+ }
+#endif
+ bus->in_reset = 0;
+}
+
+/* configure each codec instance */
+int __devinit azx_codec_configure(struct azx *chip)
+{
+ struct hda_codec *codec;
+ list_for_each_entry(codec, &chip->bus->codec_list, list) {
+ snd_hda_codec_configure(codec);
+ }
+ return 0;
+}
+
+
+/*
+ * PCM support
+ */
+
+/* assign a stream for the PCM */
+inline struct azx_dev *
+azx_assign_device(struct azx *chip, struct snd_pcm_substream *substream)
+{
+ int dev, i, nums;
+ struct azx_dev *res = NULL;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ dev = chip->playback_index_offset;
+ nums = chip->playback_streams;
+ } else {
+ dev = chip->capture_index_offset;
+ nums = chip->capture_streams;
+ }
+ for (i = 0; i < nums; i++, dev++)
+ if (!chip->azx_dev[dev].opened) {
+ res = &chip->azx_dev[dev];
+ if (res->device == substream->pcm->device)
+ break;
+ }
+ if (res) {
+ res->opened = 1;
+ res->device = substream->pcm->device;
+ }
+ return res;
+}
+
+/* release the assigned stream */
+inline void azx_release_device(struct azx_dev *azx_dev)
+{
+ azx_dev->opened = 0;
+}
+
+static struct snd_pcm_hardware azx_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ /* No full-resume yet implemented */
+ /* SNDRV_PCM_INFO_RESUME |*/
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_SYNC_START),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = SNDRV_PCM_RATE_48000,
+ .rate_min = 48000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = AZX_MAX_BUF_SIZE,
+ .period_bytes_min = 128,
+ .period_bytes_max = AZX_MAX_BUF_SIZE / 2,
+ .periods_min = 2,
+ .periods_max = AZX_MAX_FRAG,
+ .fifo_size = 0,
+};
+
+int azx_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned long flags;
+ int err;
+
+ mutex_lock(&chip->open_mutex);
+ azx_dev = azx_assign_device(chip, substream);
+ if (azx_dev == NULL) {
+ mutex_unlock(&chip->open_mutex);
+ return -EBUSY;
+ }
+ runtime->hw = azx_pcm_hw;
+ runtime->hw.channels_min = hinfo->channels_min;
+ runtime->hw.channels_max = hinfo->channels_max;
+ runtime->hw.formats = hinfo->formats;
+ runtime->hw.rates = hinfo->rates;
+ snd_pcm_limit_hw_rates(runtime);
+ 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);
+ snd_hda_power_up(apcm->codec);
+ err = hinfo->ops.open(hinfo, apcm->codec, substream);
+ if (err < 0) {
+ azx_release_device(azx_dev);
+ snd_hda_power_down(apcm->codec);
+ mutex_unlock(&chip->open_mutex);
+ return err;
+ }
+ snd_pcm_limit_hw_rates(runtime);
+ /* sanity check */
+ if (snd_BUG_ON(!runtime->hw.channels_min) ||
+ snd_BUG_ON(!runtime->hw.channels_max) ||
+ snd_BUG_ON(!runtime->hw.formats) ||
+ snd_BUG_ON(!runtime->hw.rates)) {
+ azx_release_device(azx_dev);
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ snd_hda_power_down(apcm->codec);
+ mutex_unlock(&chip->open_mutex);
+ return -EINVAL;
+ }
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ azx_dev->substream = substream;
+ azx_dev->running = 0;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+
+ runtime->private_data = azx_dev;
+ snd_pcm_set_sync(substream);
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+}
+
+int azx_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ unsigned long flags;
+
+ mutex_lock(&chip->open_mutex);
+ spin_lock_irqsave(&chip->reg_lock, flags);
+ azx_dev->substream = NULL;
+ azx_dev->running = 0;
+ spin_unlock_irqrestore(&chip->reg_lock, flags);
+ azx_release_device(azx_dev);
+ hinfo->ops.close(hinfo, apcm->codec, substream);
+ snd_hda_power_down(apcm->codec);
+ mutex_unlock(&chip->open_mutex);
+ return 0;
+}
+
+int azx_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+
+ azx_dev->bufsize = 0;
+ azx_dev->period_bytes = 0;
+ azx_dev->format_val = 0;
+ return snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+}
+
+int azx_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+
+ /* reset BDL address */
+ azx_sd_writel(azx_dev, SD_BDLPL, 0);
+ azx_sd_writel(azx_dev, SD_BDLPU, 0);
+ azx_sd_writel(azx_dev, SD_CTL, 0);
+ azx_dev->bufsize = 0;
+ azx_dev->period_bytes = 0;
+ azx_dev->format_val = 0;
+
+ snd_hda_codec_cleanup(apcm->codec, hinfo, substream);
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+int azx_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ unsigned int bufsize, period_bytes, format_val, stream_tag;
+ int err;
+
+ azx_stream_reset(chip, azx_dev);
+ format_val = snd_hda_calc_stream_format(runtime->rate,
+ runtime->channels,
+ runtime->format,
+ hinfo->maxbps,
+ apcm->codec->spdif_ctls);
+ if (!format_val) {
+ snd_printk(KERN_ERR SFX
+ "invalid format_val, rate=%d, ch=%d, format=%d\n",
+ runtime->rate, runtime->channels, runtime->format);
+ return -EINVAL;
+ }
+
+ bufsize = snd_pcm_lib_buffer_bytes(substream);
+ period_bytes = snd_pcm_lib_period_bytes(substream);
+
+ snd_printdd(SFX "azx_pcm_prepare: bufsize=0x%x, format=0x%x\n",
+ bufsize, format_val);
+
+ if (bufsize != azx_dev->bufsize ||
+ period_bytes != azx_dev->period_bytes ||
+ format_val != azx_dev->format_val) {
+ azx_dev->bufsize = bufsize;
+ azx_dev->period_bytes = period_bytes;
+ azx_dev->format_val = format_val;
+ err = azx_setup_periods(chip, substream, azx_dev);
+ if (err < 0)
+ return err;
+ }
+
+ /* wallclk has 24Mhz clock source */
+ azx_dev->period_wallclk = (((runtime->period_size * 24000) /
+ runtime->rate) * 1000);
+ azx_setup_controller(chip, azx_dev);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
+ else
+ azx_dev->fifo_size = 0;
+
+ stream_tag = azx_dev->stream_tag;
+ /* CA-IBG chips need the playback stream starting from 1 */
+ if (chip->driver_type == AZX_DRIVER_CTX &&
+ stream_tag > chip->capture_streams)
+ stream_tag -= chip->capture_streams;
+ return snd_hda_codec_prepare(apcm->codec, hinfo, stream_tag,
+ azx_dev->format_val, substream);
+}
+
+int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev;
+ struct snd_pcm_substream *s;
+ int rstart = 0, start, nsync = 0, sbits = 0;
+ int nwait, timeout;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ rstart = 1;
+ 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;
+ }
+
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ sbits |= 1 << azx_dev->index;
+ nsync++;
+ snd_pcm_trigger_done(s, substream);
+ }
+
+ spin_lock(&chip->reg_lock);
+ if (nsync > 1) {
+ /* first, set SYNC bits of corresponding streams */
+ azx_writel(chip, SYNC, azx_readl(chip, SYNC) | sbits);
+ }
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ if (start) {
+ azx_dev->start_wallclk = azx_readl(chip, WALLCLK);
+ if (!rstart)
+ azx_dev->start_wallclk -=
+ azx_dev->period_wallclk;
+ azx_stream_start(chip, azx_dev);
+ } else {
+ azx_stream_stop(chip, azx_dev);
+ }
+ azx_dev->running = start;
+ }
+ spin_unlock(&chip->reg_lock);
+ if (start) {
+ if (nsync == 1)
+ return 0;
+ /* wait until all FIFOs get ready */
+ for (timeout = 5000; timeout; timeout--) {
+ nwait = 0;
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ if (!(azx_sd_readb(azx_dev, SD_STS) &
+ SD_STS_FIFO_READY))
+ nwait++;
+ }
+ if (!nwait)
+ break;
+ cpu_relax();
+ }
+ } else {
+ /* wait until all RUN bits are cleared */
+ for (timeout = 5000; timeout; timeout--) {
+ nwait = 0;
+ snd_pcm_group_for_each_entry(s, substream) {
+ if (s->pcm->card != substream->pcm->card)
+ continue;
+ azx_dev = get_azx_dev(s);
+ if (azx_sd_readb(azx_dev, SD_CTL) &
+ SD_CTL_DMA_START)
+ nwait++;
+ }
+ if (!nwait)
+ break;
+ cpu_relax();
+ }
+ }
+ if (nsync > 1) {
+ spin_lock(&chip->reg_lock);
+ /* reset SYNC bits */
+ azx_writel(chip, SYNC, azx_readl(chip, SYNC) & ~sbits);
+ spin_unlock(&chip->reg_lock);
+ }
+ return 0;
+}
+
+/* get the current DMA position with correction on VIA chips */
+unsigned int azx_via_get_position(struct azx *chip,
+ struct azx_dev *azx_dev)
+{
+ unsigned int link_pos, mini_pos, bound_pos;
+ unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos;
+ unsigned int fifo_size;
+
+ link_pos = azx_sd_readl(azx_dev, SD_LPIB);
+ if (azx_dev->index >= 4) {
+ /* Playback, no problem using link position */
+ return link_pos;
+ }
+
+ /* Capture */
+ /* For new chipset,
+ * use mod to get the DMA position just like old chipset
+ */
+ mod_dma_pos = le32_to_cpu(*azx_dev->posbuf);
+ mod_dma_pos %= azx_dev->period_bytes;
+
+ /* azx_dev->fifo_size can't get FIFO size of in stream.
+ * Get from base address + offset.
+ */
+ fifo_size = readw(chip->remap_addr + VIA_IN_STREAM0_FIFO_SIZE_OFFSET);
+
+ if (azx_dev->insufficient) {
+ /* Link position never gather than FIFO size */
+ if (link_pos <= fifo_size)
+ return 0;
+
+ azx_dev->insufficient = 0;
+ }
+
+ if (link_pos <= fifo_size)
+ mini_pos = azx_dev->bufsize + link_pos - fifo_size;
+ else
+ mini_pos = link_pos - fifo_size;
+
+ /* Find nearest previous boudary */
+ mod_mini_pos = mini_pos % azx_dev->period_bytes;
+ mod_link_pos = link_pos % azx_dev->period_bytes;
+ if (mod_link_pos >= fifo_size)
+ bound_pos = link_pos - mod_link_pos;
+ else if (mod_dma_pos >= mod_mini_pos)
+ bound_pos = mini_pos - mod_mini_pos;
+ else {
+ bound_pos = mini_pos - mod_mini_pos + azx_dev->period_bytes;
+ if (bound_pos >= azx_dev->bufsize)
+ bound_pos = 0;
+ }
+
+ /* Calculate real DMA position we want */
+ return bound_pos + mod_dma_pos;
+}
+
+unsigned int azx_get_position(struct azx *chip,
+ struct azx_dev *azx_dev)
+{
+ unsigned int pos;
+
+ if (chip->via_dmapos_patch)
+ pos = azx_via_get_position(chip, azx_dev);
+ else {
+ int stream = azx_dev->substream->stream;
+ if (chip->position_fix[stream] == POS_FIX_POSBUF ||
+ chip->position_fix[stream] == POS_FIX_AUTO) {
+ /* use the position buffer */
+ pos = le32_to_cpu(*azx_dev->posbuf);
+ } else {
+ /* read LPIB */
+ pos = azx_sd_readl(azx_dev, SD_LPIB);
+ }
+ }
+ if (pos >= azx_dev->bufsize)
+ pos = 0;
+ return pos;
+}
+
+snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct azx *chip = apcm->chip;
+ struct azx_dev *azx_dev = get_azx_dev(substream);
+ return bytes_to_frames(substream->runtime,
+ azx_get_position(chip, azx_dev));
+}
+
+/*
+ * The work for pending PCM period updates.
+ */
+void azx_irq_pending_work(struct work_struct *work)
+{
+ struct azx *chip = container_of(work, struct azx, irq_pending_work);
+ int i, pending, ok;
+
+ if (!chip->irq_pending_warned) {
+ printk(KERN_WARNING
+ "hda-intel: IRQ timing workaround is activated "
+ "for card #%d. Suggest a bigger bdl_pos_adj.\n",
+ chip->card->number);
+ chip->irq_pending_warned = 1;
+ }
+
+ for (;;) {
+ pending = 0;
+ spin_lock_irq(&chip->reg_lock);
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = &chip->azx_dev[i];
+ if (!azx_dev->irq_pending ||
+ !azx_dev->substream ||
+ !azx_dev->running)
+ continue;
+ ok = azx_position_ok(chip, azx_dev);
+ if (ok > 0) {
+ azx_dev->irq_pending = 0;
+ spin_unlock(&chip->reg_lock);
+ snd_pcm_period_elapsed(azx_dev->substream);
+ spin_lock(&chip->reg_lock);
+ } else if (ok < 0) {
+ pending = 0; /* too early */
+ } else
+ pending++;
+ }
+ spin_unlock_irq(&chip->reg_lock);
+ if (!pending)
+ return;
+ msleep(1);
+ }
+}
+
+/* clear irq_pending flags and assure no on-going workq */
+void azx_clear_irq_pending(struct azx *chip)
+{
+ int i;
+
+ spin_lock_irq(&chip->reg_lock);
+ for (i = 0; i < chip->num_streams; i++)
+ chip->azx_dev[i].irq_pending = 0;
+ spin_unlock_irq(&chip->reg_lock);
+}
+
+void azx_pcm_free(struct snd_pcm *pcm)
+{
+ struct azx_pcm *apcm = pcm->private_data;
+ if (apcm) {
+ apcm->chip->pcm[pcm->device] = NULL;
+ kfree(apcm);
+ }
+}
+
+/*
+ * mixer creation - all stuff is implemented in hda module
+ */
+int __devinit azx_mixer_create(struct azx *chip)
+{
+ return snd_hda_build_controls(chip->bus);
+}
+
+
+/*
+ * initialize SD streams
+ */
+int __devinit azx_init_stream(struct azx *chip)
+{
+ int i;
+
+ /* initialize each stream (aka device)
+ * assign the starting bdl address to each stream (device)
+ * and initialize
+ */
+ for (i = 0; i < chip->num_streams; i++) {
+ struct azx_dev *azx_dev = &chip->azx_dev[i];
+ azx_dev->posbuf = (u32 __iomem *)(chip->posbuf.area + i * 8);
+ /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+ azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80);
+ /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */
+ azx_dev->sd_int_sta_mask = 1 << i;
+ /* stream tag: must be non-zero and unique */
+ azx_dev->index = i;
+ azx_dev->stream_tag = i + 1;
+ }
+
+ return 0;
+}
+
+#if !defined(CONFIG_SND_HDA_TEGRA)
+int azx_acquire_irq(struct azx *chip, int do_disconnect)
+{
+ if (request_irq(chip->pci->irq, azx_interrupt,
+ chip->msi ? 0 : IRQF_SHARED,
+ "hda_intel", chip)) {
+ printk(KERN_ERR "hda-intel: unable to grab IRQ %d, "
+ "disabling device\n", chip->pci->irq);
+ if (do_disconnect)
+ snd_card_disconnect(chip->card);
+ return -1;
+ }
+ chip->irq = chip->pci->irq;
+ pci_intx(chip->pci, !chip->msi);
+ return 0;
+}
+#else
+int azx_acquire_irq(struct azx *chip, int do_disconnect)
+{
+ chip->irq = platform_get_irq(chip->pdev, 0);
+ if (request_irq(chip->irq, azx_interrupt,
+ chip->msi ? 0 : IRQF_SHARED,
+ "tegra-hda", chip)) {
+ printk(KERN_ERR "hda-tegra: unable to grab IRQ %d, "
+ "disabling device\n", chip->irq);
+ if (do_disconnect)
+ snd_card_disconnect(chip->card);
+ return -1;
+ }
+ return 0;
+}
+#endif /* !CONFIG_SND_HDA_TEGRA */
+
+void azx_stop_chip(struct azx *chip)
+{
+ if (!chip->initialized)
+ return;
+
+ /* disable interrupts */
+ azx_int_disable(chip);
+ azx_int_clear(chip);
+
+ /* disable CORB/RIRB */
+ azx_free_cmd_io(chip);
+
+ /* disable position buffer */
+ azx_writel(chip, DPLBASE, 0);
+ azx_writel(chip, DPUBASE, 0);
+
+ chip->initialized = 0;
+}
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+/* power-up/down the controller */
+void azx_power_notify(struct hda_bus *bus)
+{
+ struct azx *chip = bus->private_data;
+ struct hda_codec *c;
+ int power_on = 0;
+
+ list_for_each_entry(c, &bus->codec_list, list) {
+ if (c->power_on) {
+ power_on = 1;
+ break;
+ }
+ }
+ if (power_on)
+ azx_init_chip(chip, 1);
+ else if (chip->running && power_save_controller &&
+ !bus->power_keep_link_on)
+ azx_stop_chip(chip);
+}
+#endif /* CONFIG_SND_HDA_POWER_SAVE */
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+
+int snd_hda_codecs_inuse(struct hda_bus *bus)
+{
+ struct hda_codec *codec;
+
+ list_for_each_entry(codec, &bus->codec_list, list) {
+ if (snd_hda_codec_needs_resume(codec))
+ return 1;
+ }
+ return 0;
+}
+
+#endif /* CONFIG_PM */
+
+
+/*
+ * reboot notifier for hang-up problem at power-down
+ */
+int azx_halt(struct notifier_block *nb, unsigned long event, void *buf)
+{
+ struct azx *chip = container_of(nb, struct azx, reboot_notifier);
+ snd_hda_bus_reboot_notify(chip->bus);
+ azx_stop_chip(chip);
+ return NOTIFY_OK;
+}
+
+void azx_notifier_register(struct azx *chip)
+{
+ chip->reboot_notifier.notifier_call = azx_halt;
+ register_reboot_notifier(&chip->reboot_notifier);
+}
+
+void azx_notifier_unregister(struct azx *chip)
+{
+ if (chip->reboot_notifier.notifier_call)
+ unregister_reboot_notifier(&chip->reboot_notifier);
+}
+
+void power_down_all_codecs(struct azx *chip)
+{
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+ /* The codecs were powered up in snd_hda_codec_new().
+ * Now all initialization done, so turn them down if possible
+ */
+ struct hda_codec *codec;
+ list_for_each_entry(codec, &chip->bus->codec_list, list) {
+ snd_hda_power_down(codec);
+ }
+#endif
+}