[alsa-devel] [RFC v2 3/3] ALSA: hda: add hda controller to hda

Vinod Koul vinod.koul at intel.com
Wed Mar 25 07:01:39 CET 2015


From: Jeeja KP <jeeja.kp at intel.com>

This adds hdac_controller into hda core.

Signed-off-by: Jeeja KP <jeeja.kp at intel.com>
Signed-off-by: Vinod Koul <vinod.koul at intel.com>
---
 include/sound/hdaudio.h     |  215 ++++++++
 sound/hda/Makefile          |    2 +-
 sound/hda/hdac_controller.c | 1284 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1500 insertions(+), 1 deletion(-)
 create mode 100644 sound/hda/hdac_controller.c

diff --git a/include/sound/hdaudio.h b/include/sound/hdaudio.h
index 675614dc2b88..2843471efc7a 100644
--- a/include/sound/hdaudio.h
+++ b/include/sound/hdaudio.h
@@ -5,8 +5,11 @@
 #ifndef __SOUND_HDAUDIO_H
 #define __SOUND_HDAUDIO_H
 
+#include <linux/timecounter.h>
+#include <sound/core.h>
 #include <linux/device.h>
 #include <sound/hda_verbs.h>
+#include <sound/pcm.h>
 
 /* codec node id */
 typedef u16 hda_nid_t;
@@ -15,6 +18,7 @@ struct hdac_bus;
 struct hdac_device;
 struct hdac_driver;
 struct hdac_widget_tree;
+struct hda;
 
 /*
  * exported bus type
@@ -125,6 +129,8 @@ struct hdac_bus_ops {
 	/* get a response from the last command */
 	int (*get_response)(struct hdac_bus *bus, unsigned int addr,
 			    unsigned int *res);
+	/* reset bus for retry verb */
+	void (*bus_reset)(struct hdac_bus *bus);
 };
 
 #define HDA_UNSOL_QUEUE_SIZE	64
@@ -144,6 +150,7 @@ struct hdac_bus {
 	u32 unsol_queue[HDA_UNSOL_QUEUE_SIZE * 2]; /* ring buffer */
 	unsigned int unsol_rp, unsol_wp;
 	struct work_struct unsol_work;
+	struct workqueue_struct *workq; /* common workqueue for codecs */
 
 	/* bit flags of powered codecs */
 	unsigned long codec_powered;
@@ -153,6 +160,17 @@ struct hdac_bus {
 
 	/* locks */
 	struct mutex cmd_mutex;
+
+	void *private_data;
+
+	/* msic op flags */
+	unsigned int allow_bus_reset:1; /* allow bus reset at fatal error */
+
+	/*status for controller */
+	unsigned int rirb_error:1;      /* error in codec communication */
+	unsigned int response_reset:1;  /* controller was reset */
+	unsigned int no_response_fallback:1; /* don't fallback at RIRB error */
+	unsigned int in_reset:1;        /* during reset operation */
 };
 
 int snd_hdac_bus_init(struct hdac_bus *bus, struct device *dev,
@@ -178,4 +196,201 @@ static inline void snd_hdac_codec_link_down(struct hdac_device *codec)
 	clear_bit(codec->addr, &codec->bus->codec_powered);
 }
 
+/*
+ * HD-audio contoller base device
+ */
+struct hda_device {
+	struct snd_dma_buffer bdl;	/* BDL buffer */
+	u32 *posbuf;			/* position buffer pointer */
+
+	unsigned int bufsize;		/* size of the play buffer in bytes */
+	unsigned int period_bytes;	 /* size of the period in bytes */
+	unsigned int frags;		/* number for period in the play buffer */
+	unsigned int fifo_size;		/* FIFO size */
+	unsigned long start_wallclk;	/* start + minimum wallclk */
+	unsigned long period_wallclk;	/* wallclk for period */
+
+	void __iomem *sd_addr;		/* stream descriptor pointer */
+
+	u32 sd_int_sta_mask;		/* stream int status mask */
+
+	/* pcm support */
+	struct snd_pcm_substream *substream;	/* assigned substream,
+						 * set in PCM open
+						 */
+	unsigned int format_val;	/* format value to be set in the
+					 * controller and the codec
+					 */
+	unsigned char stream_tag;	/* assigned stream */
+	unsigned char index;		/* stream index */
+	int assigned_key;		/* last device# key assigned to */
+
+	unsigned int opened:1;
+	unsigned int running:1;
+	unsigned int irq_pending:1;
+	unsigned int prepared:1;
+	unsigned int locked:1;
+	unsigned int wc_marked:1;
+	unsigned int no_period_wakeup:1;
+
+	struct timecounter  tc;
+	struct cyclecounter cc;
+
+	int delay_negative_threshold;
+
+	/* Allows dsp load to have sole access to the playback stream. */
+	struct mutex dsp_mutex;
+};
+
+/* CORB/RIRB */
+struct hda_rb {
+	u32 *buf;			/* CORB/RIRB buffer
+					 * Each CORB entry is 4byte, RIRB is 8byte
+					 */
+	dma_addr_t addr;		/* physical address of CORB/RIRB buffer */
+
+	/* for RIRB */
+	unsigned short rp, wp;		/* read/write pointers */
+	int cmds[HDA_MAX_CODECS];	/* number of pending requests */
+	u32 res[HDA_MAX_CODECS];	/* last read value */
+};
+
+/* Functions to read/write to hda registers. */
+/* FIXME: should we name this something else ??? */
+struct hda_ops {
+	void (*reg_writel)(u32 value, u32 __iomem *addr);
+	u32 (*reg_readl)(u32 __iomem *addr);
+	void (*reg_writew)(u16 value, u16 __iomem *addr);
+	u16 (*reg_readw)(u16 __iomem *addr);
+	void (*reg_writeb)(u8 value, u8 __iomem *addr);
+	u8 (*reg_readb)(u8 __iomem *addr);
+	/* Disable msi if supported, PCI only */
+	int (*disable_msi_reset_irq)(struct hda *);
+	/* Allocation ops */
+	int (*dma_alloc_pages)(struct hda *chip,
+			       int type,
+			       size_t size,
+			       struct snd_dma_buffer *buf);
+	void (*dma_free_pages)(struct hda *chip, struct snd_dma_buffer *buf);
+	int (*substream_alloc_pages)(struct hda *chip,
+				     struct snd_pcm_substream *substream,
+				     size_t size);
+	int (*substream_free_pages)(struct hda *chip,
+				    struct snd_pcm_substream *substream);
+	void (*pcm_mmap_prepare)(struct snd_pcm_substream *substream,
+				 struct vm_area_struct *area);
+	/* Check if current position is acceptable */
+	int (*position_check)(struct hda *chip, struct hda_device *hda_dev);
+};
+
+typedef unsigned int (*hda_get_pos_callback_t)(struct hda *, struct hda_device *);
+typedef int (*hda_get_delay_callback_t)(struct hda *, struct hda_device *,
+			 unsigned int pos);
+
+struct hda {
+	struct pci_dev *pci; /* FIXME: should we remove PCI assumption, right now its true for us always */
+	struct device *dev;
+	int dev_index;
+
+	/* chip type specific */
+	int driver_type;
+	unsigned int driver_caps;
+	int playback_streams;
+	int playback_index_offset;
+	int capture_streams;
+	int capture_index_offset;
+	int num_streams;
+
+	/* Register interaction. */
+	const struct hda_ops *ops;
+
+	/* position adjustment callbacks */
+	hda_get_pos_callback_t get_position[2];
+	hda_get_delay_callback_t get_delay[2];
+
+	/* pci resources */
+	unsigned long addr;
+	void __iomem *remap_addr;
+	int irq;
+
+	/* locks */
+	spinlock_t reg_lock;
+	struct mutex open_mutex; /* Prevents concurrent open/close operations */
+	struct completion probe_wait;
+
+	/* streams (x num_streams) */
+	struct hda_device *hda_dev;
+
+	/* HD codec */
+	unsigned short codec_mask;
+	int  codec_probe_mask; /* copied from probe_mask option */
+	struct hdac_bus *bus;
+	unsigned int beep_mode;
+
+	/* CORB/RIRB */
+	struct hda_rb corb;
+	struct hda_rb rirb;
+
+	/* CORB/RIRB and position buffers */
+	struct snd_dma_buffer rb;
+	struct snd_dma_buffer posbuf;
+
+	/* flags */
+	const int *bdl_pos_adj;
+	int poll_count;
+	unsigned int running:1;
+	unsigned int initialized:1;
+	unsigned int single_cmd:1;
+	unsigned int polling_mode:1;
+	unsigned int msi:1;
+	unsigned int probing:1; /* codec probing phase */
+	unsigned int snoop:1;
+	unsigned int align_buffer_size:1;
+	unsigned int region_requested:1;
+	unsigned int disabled:1; /* disabled by VGA-switcher */
+
+	/* for debugging */
+	unsigned int last_cmd[HDA_MAX_CODECS];
+
+	/* reboot notifier (for mysterious hangup problem at power-down) */
+	struct notifier_block reboot_notifier;
+
+	struct hda_device saved_hda_dev; /* FIXME: check if we need this */
+};
+
+/* Allocation functions. */
+int hda_alloc_stream_pages(struct hda *chip);
+void hda_free_stream_pages(struct hda *chip);
+
+/* pcm helper functions */
+int hda_setup_periods(struct hda *chip,
+			struct snd_pcm_substream *substream,
+			struct hda_device *dev);
+void hda_reset_device(struct hda *chip, struct hda_device *hda_dev);
+int hda_set_device_params(struct hda *chip, struct snd_pcm_substream *substream,
+				unsigned int format_val);
+void hda_set_pcm_constrains(struct hda *chip, struct snd_pcm_runtime *runtime);
+
+/* PCM setup */
+static inline struct hda_device *get_hda_dev(struct snd_pcm_substream *substream)
+{
+	return substream->runtime->private_data;
+}
+unsigned int hda_get_position(struct hda *chip, struct hda_device *hda_dev,
+		int codec_delay);
+unsigned int hda_get_pos_lpib(struct hda *chip, struct hda_device *hda_dev);
+unsigned int hda_get_pos_posbuf(struct hda *chip, struct hda_device *hda_dev);
+
+/* Stream control. */
+void hda_stream_stop(struct hda *chip, struct hda_device *hda_dev);
+
+/* Allocation functions. */
+int hda_alloc_stream_pages(struct hda *chip);
+void hda_free_stream_pages(struct hda *chip);
+
+/* Low level azx interface */
+void hda_init_chip(struct hda *chip, bool full_reset);
+void hda_stop_chip(struct hda *chip);
+void hda_enter_link_reset(struct hda *chip);
+
 #endif /* __SOUND_HDAUDIO_H */
diff --git a/sound/hda/Makefile b/sound/hda/Makefile
index eec5da03b41f..836c5f34b4dd 100644
--- a/sound/hda/Makefile
+++ b/sound/hda/Makefile
@@ -1,4 +1,4 @@
-snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o
+snd-hda-core-objs := hda_bus_type.o hdac_bus.o hdac_device.o hdac_sysfs.o hdac_controller.o
 
 snd-hda-core-objs += trace.o
 CFLAGS_trace.o := -I$(src)
diff --git a/sound/hda/hdac_controller.c b/sound/hda/hdac_controller.c
new file mode 100644
index 000000000000..3b14f7be43da
--- /dev/null
+++ b/sound/hda/hdac_controller.c
@@ -0,0 +1,1284 @@
+/*
+ *
+ *  Implementation of Common HDA driver funcitons for Intel HD Audio.
+ *
+ *  Copyright (c) 2014 Intel Corporation
+ *  Copyright(c) 2004 Intel Corporation. All rights reserved.
+ *
+ *  Copyright (c) 2004 Takashi Iwai <tiwai at suse.de>
+ *                     PeiSen Hou <pshou at realtek.com.tw>
+ *
+ *  Modified by: KP Jeeja <jeeja.kp at intel.com>
+ *
+ *  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.
+ *
+ *
+ */
+
+#include <linux/clocksource.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/reboot.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/hdaudio.h>
+#include <sound/hda_registers.h>
+
+/* DSP lock helpers */
+#ifdef CONFIG_SND_HDA_DSP_LOADER
+#define dsp_lock_init(dev)	mutex_init(&(dev)->dsp_mutex)
+#define dsp_lock(dev)		mutex_lock(&(dev)->dsp_mutex)
+#define dsp_unlock(dev)		mutex_unlock(&(dev)->dsp_mutex)
+#define dsp_is_locked(dev)	((dev)->locked)
+#else
+#define dsp_lock_init(dev)	do {} while (0)
+#define dsp_lock(dev)		do {} while (0)
+#define dsp_unlock(dev)		do {} while (0)
+#define dsp_is_locked(dev)	0
+#endif
+
+/*
+ * AZX stream operations.
+ */
+
+/* start a stream */
+void hda_stream_start(struct hda *chip, struct hda_device *azx_dev)
+{
+	/* enable SIE */
+	azx_writel(chip, INTCTL,
+		   azx_readl(chip, INTCTL) | (1 << azx_dev->index));
+	/* set DMA start and interrupt mask */
+	azx_sd_writeb(chip, azx_dev, SD_CTL,
+		      azx_sd_readb(chip, azx_dev, SD_CTL) |
+		      SD_CTL_DMA_START | SD_INT_MASK);
+}
+EXPORT_SYMBOL_GPL(hda_stream_start);
+/* stop DMA */
+static void hda_stream_clear(struct hda *chip, struct hda_device *azx_dev)
+{
+	azx_sd_writeb(chip, azx_dev, SD_CTL,
+		      azx_sd_readb(chip, azx_dev, SD_CTL) &
+		      ~(SD_CTL_DMA_START | SD_INT_MASK));
+	azx_sd_writeb(chip, azx_dev, SD_STS, SD_INT_MASK); /* to be sure */
+}
+
+/* stop a stream */
+void hda_stream_stop(struct hda *chip, struct hda_device *azx_dev)
+{
+	hda_stream_clear(chip, azx_dev);
+	/* disable SIE */
+	azx_writel(chip, INTCTL,
+		   azx_readl(chip, INTCTL) & ~(1 << azx_dev->index));
+}
+EXPORT_SYMBOL_GPL(hda_stream_stop);
+
+/* reset stream */
+void hda_stream_reset(struct hda *chip, struct hda_device *azx_dev)
+{
+	unsigned char val;
+	int timeout;
+
+	hda_stream_clear(chip, azx_dev);
+
+	azx_sd_writeb(chip, azx_dev, SD_CTL,
+		      azx_sd_readb(chip, azx_dev, SD_CTL) |
+		      SD_CTL_STREAM_RESET);
+	udelay(3);
+	timeout = 300;
+	while (!((val = azx_sd_readb(chip, azx_dev, SD_CTL)) &
+		 SD_CTL_STREAM_RESET) && --timeout)
+		;
+	val &= ~SD_CTL_STREAM_RESET;
+	azx_sd_writeb(chip, 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(chip, 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;
+}
+EXPORT_SYMBOL_GPL(hda_stream_reset);
+
+/*
+ * set up the SD for streaming
+ */
+int hda_setup_controller(struct hda *chip, struct hda_device *azx_dev)
+{
+	unsigned int val;
+	/* make sure the run bit is zero for SD */
+	hda_stream_clear(chip, azx_dev);
+	/* program the stream_tag */
+	val = azx_sd_readl(chip, azx_dev, SD_CTL);
+	val = (val & ~SD_CTL_STREAM_TAG_MASK) |
+		(azx_dev->stream_tag << SD_CTL_STREAM_TAG_SHIFT);
+	azx_sd_writel(chip, azx_dev, SD_CTL, val);
+
+	/* program the length of samples in cyclic buffer */
+	azx_sd_writel(chip, 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(chip, azx_dev, SD_FORMAT, azx_dev->format_val);
+
+	/* program the stream LVI (last valid index) of the BDL */
+	azx_sd_writew(chip, azx_dev, SD_LVI, azx_dev->frags - 1);
+
+	/* program the BDL address */
+	/* lower BDL address */
+	azx_sd_writel(chip, azx_dev, SD_BDLPL, (u32)azx_dev->bdl.addr);
+	/* upper BDL address */
+	azx_sd_writel(chip, azx_dev, SD_BDLPU,
+		      upper_32_bits(azx_dev->bdl.addr));
+
+	/* enable the position buffer */
+	if (chip->get_position[0] != hda_get_pos_lpib ||
+	    chip->get_position[1] != hda_get_pos_lpib) {
+		if (!(azx_readl(chip, DPLBASE) & AZX_DPLBASE_ENABLE))
+			azx_writel(chip, DPLBASE,
+				(u32)chip->posbuf.addr | AZX_DPLBASE_ENABLE);
+	}
+
+	/* set the interrupt enable bits in the descriptor control register */
+	azx_sd_writel(chip, azx_dev, SD_CTL,
+		      azx_sd_readl(chip, azx_dev, SD_CTL) | SD_INT_MASK);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hda_setup_controller);
+
+/* assign a stream for the PCM */
+struct hda_device *
+hda_assign_device(struct hda *chip, struct snd_pcm_substream *substream)
+{
+	int dev, i, nums;
+	struct hda_device *res = NULL;
+	/* make a non-zero unique key for the substream */
+	int key = (substream->pcm->device << 16) | (substream->number << 2) |
+		(substream->stream + 1);
+
+	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++) {
+		struct hda_device *azx_dev = &chip->hda_dev[dev];
+
+		dsp_lock(azx_dev);
+		if (!azx_dev->opened && !dsp_is_locked(azx_dev)) {
+			if (azx_dev->assigned_key == key) {
+				azx_dev->opened = 1;
+				azx_dev->assigned_key = key;
+				dsp_unlock(azx_dev);
+				return azx_dev;
+			}
+			if (!res ||
+			    (chip->driver_caps & AZX_DCAPS_REVERSE_ASSIGN))
+				res = azx_dev;
+		}
+		dsp_unlock(azx_dev);
+	}
+	if (res) {
+		dsp_lock(res);
+		res->opened = 1;
+		res->assigned_key = key;
+		dsp_unlock(res);
+	}
+	return res;
+}
+EXPORT_SYMBOL_GPL(hda_assign_device);
+
+/* release the assigned stream */
+void hda_release_device(struct hda_device *hda_dev)
+{
+	hda_dev->opened = 0;
+}
+EXPORT_SYMBOL_GPL(hda_release_device);
+
+/*
+ * set up a BDL entry
+ */
+int hda_setup_bdle(struct hda *chip,
+		      struct snd_dma_buffer *dmab,
+		      struct hda_device *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_sgbuf_get_addr(dmab, ofs);
+		dev_dbg(chip->dev, "buffer address=%#llx\n", (u64)addr);
+		/* 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_sgbuf_get_chunk_size(dmab, ofs, size);
+		/* one BDLE cannot cross 4K boundary on CTHDA chips */
+		if (chip->driver_caps & AZX_DCAPS_4K_BDLE_BOUNDARY) {
+			u32 remain = 0x1000 - (ofs & 0xfff);
+
+			if (chunk > remain)
+				chunk = remain;
+		}
+		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;
+	dev_dbg(chip->dev, "bdl: 0x%p, ofs:0x%x\n", bdl, ofs);
+	return ofs;
+}
+EXPORT_SYMBOL_GPL(hda_setup_bdle);
+
+/*
+ * set up BDL entries
+ */
+int hda_setup_periods(struct hda *chip,
+			     struct snd_pcm_substream *substream,
+			     struct hda_device *azx_dev)
+{
+	u32 *bdl;
+	int i, ofs, periods, period_bytes;
+	int pos_adj = 0;
+
+	/* reset BDL address */
+	azx_sd_writel(chip, azx_dev, SD_BDLPL, 0);
+	azx_sd_writel(chip, azx_dev, SD_BDLPU, 0);
+
+	period_bytes = azx_dev->period_bytes;
+	periods = azx_dev->bufsize / period_bytes;
+
+	/* program the initial BDL entries */
+	bdl = (u32 *)azx_dev->bdl.area;
+	ofs = 0;
+	azx_dev->frags = 0;
+
+	if (chip->bdl_pos_adj)
+		pos_adj = chip->bdl_pos_adj[chip->dev_index];
+	if (!azx_dev->no_period_wakeup && pos_adj > 0) {
+		struct snd_pcm_runtime *runtime = substream->runtime;
+		int pos_align = pos_adj;
+
+		pos_adj = (pos_adj * runtime->rate + 47999) / 48000;
+		if (!pos_adj)
+			pos_adj = pos_align;
+		else
+			pos_adj = ((pos_adj + pos_align - 1) / pos_align) *
+				pos_align;
+		pos_adj = frames_to_bytes(runtime, pos_adj);
+		if (pos_adj >= period_bytes) {
+			dev_warn(chip->dev, "Too big adjustment %d\n",
+				 pos_adj);
+			pos_adj = 0;
+		} else {
+			ofs = hda_setup_bdle(chip, snd_pcm_get_dma_buf(substream),
+					 azx_dev,
+					 &bdl, ofs, pos_adj, true);
+			if (ofs < 0)
+				goto error;
+		}
+	} else
+		pos_adj = 0;
+
+	for (i = 0; i < periods; i++) {
+		if (i == periods - 1 && pos_adj)
+			ofs = hda_setup_bdle(chip, snd_pcm_get_dma_buf(substream),
+					 azx_dev, &bdl, ofs,
+					 period_bytes - pos_adj, 0);
+		else
+			ofs = hda_setup_bdle(chip, snd_pcm_get_dma_buf(substream),
+					 azx_dev, &bdl, ofs,
+					 period_bytes,
+					 !azx_dev->no_period_wakeup);
+		if (ofs < 0)
+			goto error;
+	}
+	return 0;
+
+ error:
+	dev_err(chip->dev, "Too many BDL entries: buffer=%d, period=%d\n",
+		azx_dev->bufsize, period_bytes);
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(hda_setup_periods);
+
+unsigned int hda_get_pos_lpib(struct hda *chip, struct hda_device *azx_dev)
+{
+	return azx_sd_readl(chip, azx_dev, SD_LPIB);
+}
+EXPORT_SYMBOL_GPL(hda_get_pos_lpib);
+
+unsigned int hda_get_pos_posbuf(struct hda *chip, struct hda_device *azx_dev)
+{
+	return le32_to_cpu(*azx_dev->posbuf);
+}
+EXPORT_SYMBOL_GPL(hda_get_pos_posbuf);
+
+unsigned int hda_get_position(struct hda *chip,
+			      struct hda_device *azx_dev, int codec_delay)
+{
+	struct snd_pcm_substream *substream = azx_dev->substream;
+	unsigned int pos;
+	int stream = substream->stream;
+	int delay = 0;
+
+	if (chip->get_position[stream])
+		pos = chip->get_position[stream](chip, azx_dev);
+	else /* use the position buffer as default */
+		pos = hda_get_pos_posbuf(chip, azx_dev);
+
+	if (pos >= azx_dev->bufsize)
+		pos = 0;
+
+	if (substream->runtime) {
+		if (chip->get_delay[stream])
+			delay += chip->get_delay[stream](chip, azx_dev, pos);
+			delay += codec_delay;
+		substream->runtime->delay = delay;
+	}
+
+	return pos;
+}
+EXPORT_SYMBOL_GPL(hda_get_position);
+
+void hda_reset_device(struct hda *chip, struct hda_device *azx_dev)
+{
+	azx_sd_writel(chip, azx_dev, SD_BDLPL, 0);
+	azx_sd_writel(chip, azx_dev, SD_BDLPU, 0);
+	azx_sd_writel(chip, azx_dev, SD_CTL, 0);
+	azx_dev->bufsize = 0;
+	azx_dev->period_bytes = 0;
+	azx_dev->format_val = 0;
+}
+EXPORT_SYMBOL_GPL(hda_reset_device);
+
+int hda_set_device_params(struct hda *chip, struct snd_pcm_substream *substream,
+				unsigned int format_val)
+{
+
+	unsigned int bufsize, period_bytes, stream_tag;
+	struct hda_device *azx_dev = get_hda_dev(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	bufsize = snd_pcm_lib_buffer_bytes(substream);
+	period_bytes = snd_pcm_lib_period_bytes(substream);
+
+	dev_dbg(chip->dev, "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 ||
+	    runtime->no_period_wakeup != azx_dev->no_period_wakeup) {
+		azx_dev->bufsize = bufsize;
+		azx_dev->period_bytes = period_bytes;
+		azx_dev->format_val = format_val;
+		azx_dev->no_period_wakeup = runtime->no_period_wakeup;
+		err = hda_setup_periods(chip, substream, azx_dev);
+		if (err < 0)
+			return err;
+	}
+
+	/* when LPIB delay correction gives a small negative value,
+	 * we ignore it; currently set the threshold statically to
+	 * 64 frames
+	 */
+	if (runtime->period_size > 64)
+		azx_dev->delay_negative_threshold = -frames_to_bytes(runtime, 64);
+	else
+		azx_dev->delay_negative_threshold = 0;
+
+	/* wallclk has 24Mhz clock source */
+	azx_dev->period_wallclk = (((runtime->period_size * 24000) /
+						runtime->rate) * 1000);
+	hda_setup_controller(chip, azx_dev);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		azx_dev->fifo_size =
+			azx_sd_readw(chip, azx_dev, SD_FIFOSIZE) + 1;
+	else
+		azx_dev->fifo_size = 0;
+
+	stream_tag = azx_dev->stream_tag;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hda_set_device_params);
+
+void hda_set_pcm_constrains(struct hda *chip, struct snd_pcm_runtime *runtime)
+{
+	int buff_step;
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	/* avoid wrap-around with wall-clock */
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_TIME,
+				     20,
+				     178000000);
+
+	if (chip->align_buffer_size)
+		/* constrain buffer sizes to be multiple of 128
+		   bytes. This is more efficient in terms of memory
+		   access but isn't required by the HDA spec and
+		   prevents users from specifying exact period/buffer
+		   sizes. For example for 44.1kHz, a period size set
+		   to 20ms will be rounded to 19.59ms. */
+		buff_step = 128;
+	else
+		/* Don't enforce steps on buffer sizes, still need to
+		   be multiple of 4 bytes (HDA spec). Tested on Intel
+		   HDA controllers, may not work on all devices where
+		   option needs to be disabled */
+		buff_step = 4;
+
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+				   buff_step);
+	snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				   buff_step);
+
+}
+EXPORT_SYMBOL_GPL(hda_set_pcm_constrains);
+
+/*
+ * CORB / RIRB interface
+ */
+static int hda_alloc_cmd_io(struct hda *chip)
+{
+	int err;
+
+	/* single page (at least 4096 bytes) must suffice for both ringbuffes */
+	err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
+					 PAGE_SIZE, &chip->rb);
+	if (err < 0)
+		dev_err(chip->dev, "cannot allocate CORB/RIRB\n");
+	return err;
+}
+EXPORT_SYMBOL_GPL(hda_alloc_cmd_io);
+
+static void hda_init_cmd_io(struct hda *chip)
+{
+	int timeout;
+
+	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, AZX_CORBRP_RST);
+	if (!(chip->driver_caps & AZX_DCAPS_CORBRP_SELF_CLEAR)) {
+		for (timeout = 1000; timeout > 0; timeout--) {
+			if ((azx_readw(chip, CORBRP) & AZX_CORBRP_RST) == AZX_CORBRP_RST)
+				break;
+			udelay(1);
+		}
+		if (timeout <= 0)
+			dev_err(chip->dev, "CORB reset timeout#1, CORBRP = %d\n",
+				azx_readw(chip, CORBRP));
+
+		azx_writew(chip, CORBRP, 0);
+		for (timeout = 1000; timeout > 0; timeout--) {
+			if (azx_readw(chip, CORBRP) == 0)
+				break;
+			udelay(1);
+		}
+		if (timeout <= 0)
+			dev_err(chip->dev, "CORB reset timeout#2, CORBRP = %d\n",
+				azx_readw(chip, CORBRP));
+	}
+
+	/* enable corb dma */
+	azx_writeb(chip, CORBCTL, AZX_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, AZX_RIRBWP_RST);
+	/* set N=1, get RIRB response interrupt for new entry */
+	if (chip->driver_caps & AZX_DCAPS_CTX_WORKAROUND)
+		azx_writew(chip, RINTCNT, 0xc0);
+	else
+		azx_writew(chip, RINTCNT, 1);
+	/* enable rirb dma and response irq */
+	azx_writeb(chip, RIRBCTL, AZX_RBCTL_DMA_EN | AZX_RBCTL_IRQ_EN);
+	spin_unlock_irq(&chip->reg_lock);
+}
+EXPORT_SYMBOL_GPL(hda_init_cmd_io);
+
+static void hda_free_cmd_io(struct hda *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);
+}
+EXPORT_SYMBOL_GPL(hda_free_cmd_io);
+
+static unsigned int hda_command_addr(u32 cmd)
+{
+	unsigned int addr = cmd >> 28;
+
+	if (addr >= HDA_MAX_CODECS) {
+		snd_BUG();
+		addr = 0;
+	}
+
+	return addr;
+}
+
+/* send a command */
+static int hda_corb_send_cmd(struct hdac_bus *bus, u32 val)
+{
+	struct hda *chip = bus->private_data;
+	unsigned int addr = hda_command_addr(val);
+	unsigned int wp, rp;
+
+	spin_lock_irq(&chip->reg_lock);
+
+	/* add command to corb */
+	wp = azx_readw(chip, CORBWP);
+	if (wp == 0xffff) {
+		/* something wrong, controller likely turned to D3 */
+		spin_unlock_irq(&chip->reg_lock);
+		return -EIO;
+	}
+	wp++;
+	wp %= AZX_MAX_CORB_ENTRIES;
+
+	rp = azx_readw(chip, CORBRP);
+	if (wp == rp) {
+		/* oops, it's full */
+		spin_unlock_irq(&chip->reg_lock);
+		return -EAGAIN;
+	}
+
+	chip->rirb.cmds[addr]++;
+	chip->corb.buf[wp] = cpu_to_le32(val);
+	azx_writew(chip, CORBWP, wp);
+
+	spin_unlock_irq(&chip->reg_lock);
+
+	return 0;
+}
+
+#define AZX_RIRB_EX_UNSOL_EV	(1<<4)
+
+/**
+ * hda_queue_unsol_event - add an unsolicited event to queue
+ * @bus: the BUS
+ * @res: unsolicited event (lower 32bit of RIRB entry)
+ * @res_ex: codec addr and flags (upper 32bit or RIRB entry)
+ *
+ * Adds the given event to the queue.  The events are processed in
+ * the workqueue asynchronously.  Call this function in the interrupt
+ * hanlder when RIRB receives an unsolicited event.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int hda_queue_unsol_event(struct hdac_bus *bus, u32 res, u32 res_ex)
+{
+	unsigned int wp;
+
+	if (!bus)
+		return 0;
+
+	wp = (bus->unsol_wp + 1) % HDA_UNSOL_QUEUE_SIZE;
+
+	wp <<= 1;
+	bus->unsol_queue[wp] = res;
+	bus->unsol_queue[wp + 1] = res_ex;
+
+	queue_work(bus->workq, &bus->unsol_work);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hda_queue_unsol_event);
+
+/* retrieve RIRB entry - called from interrupt handler */
+static void hda_update_rirb(struct hda *chip)
+{
+	unsigned int rp, wp;
+	unsigned int addr;
+	u32 res, res_ex;
+
+	wp = azx_readw(chip, RIRBWP);
+	if (wp == 0xffff) {
+		/* something wrong, controller likely turned to D3 */
+		return;
+	}
+
+	if (wp == chip->rirb.wp)
+		return;
+	chip->rirb.wp = wp;
+
+	while (chip->rirb.rp != wp) {
+		chip->rirb.rp++;
+		chip->rirb.rp %= AZX_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 = res_ex & 0xf;
+		if ((addr >= HDA_MAX_CODECS) || !(chip->codec_mask & (1 << addr))) {
+			dev_err(chip->dev, "spurious response %#x:%#x, rp = %d, wp = %d",
+				res, res_ex,
+				chip->rirb.rp, wp);
+			snd_BUG();
+		} else if (res_ex & AZX_RIRB_EX_UNSOL_EV)
+			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 if (printk_ratelimit()) {
+			dev_err(chip->dev, "spurious response %#x:%#x, last cmd=%#08x\n",
+				res, res_ex,
+				chip->last_cmd[addr]);
+		}
+	}
+}
+
+/* receive a response */
+static unsigned int hda_rirb_get_response(struct hdac_bus *bus,
+					  unsigned int addr)
+{
+	struct hda *chip = bus->private_data;
+	unsigned long timeout;
+	unsigned long loopcounter;
+	int do_poll = 0;
+
+ again:
+	timeout = jiffies + msecs_to_jiffies(1000);
+
+	for (loopcounter = 0;; loopcounter++) {
+		if (chip->polling_mode || do_poll) {
+			spin_lock_irq(&chip->reg_lock);
+			hda_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;
+		else {
+			udelay(10);
+			cond_resched();
+		}
+	}
+
+	if (!bus->no_response_fallback)
+		return -1;
+
+	if (!chip->polling_mode && chip->poll_count < 2) {
+		dev_dbg(chip->dev,
+			"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) {
+		dev_warn(chip->dev,
+			 "azx_get_response timeout, switching to polling mode: last cmd=0x%08x\n",
+			 chip->last_cmd[addr]);
+		chip->polling_mode = 1;
+		goto again;
+	}
+
+	if (chip->msi) {
+		dev_warn(chip->dev,
+			 "No response from codec, disabling MSI: last cmd=0x%08x\n",
+			 chip->last_cmd[addr]);
+		if (chip->ops->disable_msi_reset_irq(chip) &&
+		    chip->ops->disable_msi_reset_irq(chip) < 0) {
+			bus->rirb_error = 1;
+			return -1;
+		}
+		goto again;
+	}
+
+	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 */
+	}
+
+	dev_err(chip->dev,
+		"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 */
+	hda_free_cmd_io(chip);
+	/* disable unsolicited responses */
+	azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~AZX_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 */
+static int hda_single_wait_for_response(struct hda *chip, unsigned int addr)
+{
+	int timeout = 50;
+
+	while (timeout--) {
+		/* check IRV busy bit */
+		if (azx_readw(chip, IRS) & AZX_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())
+		dev_dbg(chip->dev, "get_response timeout: IRS=0x%x\n",
+			azx_readw(chip, IRS));
+	chip->rirb.res[addr] = -1;
+	return -EIO;
+}
+
+/* send a command */
+static int hda_single_send_cmd(struct hdac_bus *bus, u32 val)
+{
+	struct hda *chip = bus->private_data;
+	unsigned int addr = hda_command_addr(val);
+	int timeout = 50;
+
+	bus->rirb_error = 0;
+	while (timeout--) {
+		/* check ICB busy bit */
+		if (!((azx_readw(chip, IRS) & AZX_IRS_BUSY))) {
+			/* Clear IRV valid bit */
+			azx_writew(chip, IRS, azx_readw(chip, IRS) |
+				   AZX_IRS_VALID);
+			azx_writel(chip, IC, val);
+			azx_writew(chip, IRS, azx_readw(chip, IRS) |
+				   AZX_IRS_BUSY);
+			return hda_single_wait_for_response(chip, addr);
+		}
+		udelay(1);
+	}
+	if (printk_ratelimit())
+		dev_dbg(chip->dev,
+			"send_cmd timeout: IRS=0x%x, val=0x%x\n",
+			azx_readw(chip, IRS), val);
+	return -EIO;
+}
+
+/* receive a response */
+static unsigned int hda_single_get_response(struct hdac_bus *bus,
+					    unsigned int addr)
+{
+	struct hda *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 hda_send_cmd(struct hdac_bus *bus, unsigned int val)
+{
+	struct hda *chip = bus->private_data;
+
+	if (chip->disabled)
+		return 0;
+	chip->last_cmd[hda_command_addr(val)] = val;
+	if (chip->single_cmd)
+		return hda_single_send_cmd(bus, val);
+	else
+		return hda_corb_send_cmd(bus, val);
+}
+EXPORT_SYMBOL_GPL(hda_send_cmd);
+
+/* get a response */
+unsigned int hda_get_response(struct hdac_bus *bus,
+				     unsigned int addr)
+{
+	struct hda *chip = bus->private_data;
+
+	if (chip->disabled)
+		return 0;
+	if (chip->single_cmd)
+		return hda_single_get_response(bus, addr);
+	else
+		return hda_rirb_get_response(bus, addr);
+}
+EXPORT_SYMBOL_GPL(hda_get_response);
+
+void hda_bus_reset(struct hdac_bus *bus)
+{
+	struct hda *chip = bus->private_data;
+
+	bus->in_reset = 1;
+	hda_stop_chip(chip);
+	hda_init_chip(chip, true);
+	bus->in_reset = 0;
+}
+EXPORT_SYMBOL_GPL(hda_bus_reset);
+
+int hda_alloc_stream_pages(struct hda *chip)
+{
+	int i, err;
+
+	for (i = 0; i < chip->num_streams; i++) {
+		dsp_lock_init(&chip->hda_dev[i]);
+		/* allocate memory for the BDL for each stream */
+		err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
+						 BDL_SIZE,
+						 &chip->hda_dev[i].bdl);
+		if (err < 0) {
+			dev_err(chip->dev, "cannot allocate BDL\n");
+			return -ENOMEM;
+		}
+	}
+	/* allocate memory for the position buffer */
+	err = chip->ops->dma_alloc_pages(chip, SNDRV_DMA_TYPE_DEV,
+					 chip->num_streams * 8, &chip->posbuf);
+	if (err < 0) {
+		dev_err(chip->dev, "cannot allocate posbuf\n");
+		return -ENOMEM;
+	}
+
+	/* allocate CORB/RIRB */
+	err = hda_alloc_cmd_io(chip);
+	if (err < 0)
+		return err;
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hda_alloc_stream_pages);
+
+void hda_free_stream_pages(struct hda *chip)
+{
+	int i;
+
+	if (chip->hda_dev) {
+		for (i = 0; i < chip->num_streams; i++)
+			if (chip->hda_dev[i].bdl.area)
+				chip->ops->dma_free_pages(
+					chip, &chip->hda_dev[i].bdl);
+	}
+	if (chip->rb.area)
+		chip->ops->dma_free_pages(chip, &chip->rb);
+	if (chip->posbuf.area)
+		chip->ops->dma_free_pages(chip, &chip->posbuf);
+}
+EXPORT_SYMBOL_GPL(hda_free_stream_pages);
+
+/*
+ * Lowlevel interface
+ */
+
+/* enter link reset */
+void hda_enter_link_reset(struct hda *chip)
+{
+	unsigned long timeout;
+
+	/* reset controller */
+	azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~AZX_GCTL_RESET);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+	while ((azx_readb(chip, GCTL) & AZX_GCTL_RESET) &&
+			time_before(jiffies, timeout))
+		usleep_range(500, 1000);
+}
+EXPORT_SYMBOL_GPL(hda_enter_link_reset);
+
+/* exit link reset */
+void hda_exit_link_reset(struct hda *chip)
+{
+	unsigned long timeout;
+
+	azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | AZX_GCTL_RESET);
+
+	timeout = jiffies + msecs_to_jiffies(100);
+	while (!azx_readb(chip, GCTL) &&
+			time_before(jiffies, timeout))
+		usleep_range(500, 1000);
+}
+EXPORT_SYMBOL_GPL(hda_exit_link_reset);
+
+/* reset codec link */
+static int hda_reset(struct hda *chip, bool full_reset)
+{
+	if (!full_reset)
+		goto __skip;
+
+	/* clear STATESTS */
+	azx_writew(chip, STATESTS, STATESTS_INT_MASK);
+
+	/* reset controller */
+	hda_enter_link_reset(chip);
+
+	/* delay for >= 100us for codec PLL to settle per spec
+	 * Rev 0.9 section 5.5.1
+	 */
+	usleep_range(500, 1000);
+
+	/* Bring controller out of reset */
+	hda_exit_link_reset(chip);
+
+	/* Brent Chartrand said to wait >= 540us for codecs to initialize */
+	usleep_range(1000, 1200);
+
+__skip:
+	/* check to see if controller is ready */
+	if (!azx_readb(chip, GCTL)) {
+		dev_dbg(chip->dev, "azx_reset: controller not ready!\n");
+		return -EBUSY;
+	}
+
+	/* Accept unsolicited responses */
+	if (!chip->single_cmd)
+		azx_writel(chip, GCTL, azx_readl(chip, GCTL) |
+			   AZX_GCTL_UNSOL);
+
+	/* detect codecs */
+	if (!chip->codec_mask) {
+		chip->codec_mask = azx_readw(chip, STATESTS);
+		dev_dbg(chip->dev, "codec_mask = 0x%x\n",
+			chip->codec_mask);
+	}
+
+	return 0;
+}
+
+/* enable interrupts */
+static void hda_int_enable(struct hda *chip)
+{
+	/* enable controller CIE and GIE */
+	azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) |
+		   AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN);
+}
+
+/* disable interrupts */
+static void hda_int_disable(struct hda *chip)
+{
+	int i;
+
+	/* disable interrupts in stream descriptor */
+	for (i = 0; i < chip->num_streams; i++) {
+		struct hda_device *azx_dev = &chip->hda_dev[i];
+
+		azx_sd_writeb(chip, azx_dev, SD_CTL,
+			      azx_sd_readb(chip, 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) &
+		   ~(AZX_INT_CTRL_EN | AZX_INT_GLOBAL_EN));
+}
+
+/* clear interrupts */
+static void hda_int_clear(struct hda *chip)
+{
+	int i;
+
+	/* clear stream status */
+	for (i = 0; i < chip->num_streams; i++) {
+		struct hda_device *azx_dev = &chip->hda_dev[i];
+
+		azx_sd_writeb(chip, azx_dev, SD_STS, SD_INT_MASK);
+	}
+
+	/* clear STATESTS */
+	azx_writew(chip, STATESTS, STATESTS_INT_MASK);
+
+	/* clear rirb status */
+	azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+
+	/* clear int status */
+	azx_writel(chip, INTSTS, AZX_INT_CTRL_EN | AZX_INT_ALL_STREAM);
+}
+
+/*
+ * reset and start the controller registers
+ */
+void hda_init_chip(struct hda *chip, bool full_reset)
+{
+	if (chip->initialized)
+		return;
+
+	/* reset controller */
+	hda_reset(chip, full_reset);
+
+	/* initialize interrupts */
+	hda_int_clear(chip);
+	hda_int_enable(chip);
+
+	/* initialize the codec command I/O */
+	if (!chip->single_cmd)
+		hda_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;
+}
+EXPORT_SYMBOL_GPL(hda_init_chip);
+
+void hda_stop_chip(struct hda *chip)
+{
+	if (!chip->initialized)
+		return;
+
+	/* disable interrupts */
+	hda_int_disable(chip);
+	hda_int_clear(chip);
+
+	/* disable CORB/RIRB */
+	hda_free_cmd_io(chip);
+
+	/* disable position buffer */
+	azx_writel(chip, DPLBASE, 0);
+	azx_writel(chip, DPUBASE, 0);
+
+	chip->initialized = 0;
+}
+EXPORT_SYMBOL_GPL(hda_stop_chip);
+
+/*
+ * interrupt handler
+ */
+irqreturn_t hda_interrupt(int irq, void *dev_id)
+{
+	struct hda *chip = dev_id;
+	u32 status;
+
+#ifdef CONFIG_PM_RUNTIME
+	if (chip->driver_caps & AZX_DCAPS_PM_RUNTIME)
+		if (!pm_runtime_active(chip->dev))
+			return IRQ_NONE;
+#endif
+
+	spin_lock(&chip->reg_lock);
+
+	if (chip->disabled) {
+		spin_unlock(&chip->reg_lock);
+		return IRQ_NONE;
+	}
+
+	status = azx_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;
+}
+EXPORT_SYMBOL_GPL(hda_interrupt);
+
+
+irqreturn_t hda_threaded_handler(int irq, void *dev_id)
+{
+	struct hda *chip = dev_id;
+	struct hda_device *azx_dev;
+	u32 status;
+	u8 sd_status;
+	int i;
+	unsigned long cookie;
+
+	status = azx_readl(chip, INTSTS);
+	spin_lock_irqsave(&chip->reg_lock, cookie);
+	for (i = 0; i < chip->num_streams; i++) {
+		azx_dev = &chip->hda_dev[i];
+		if (status & azx_dev->sd_int_sta_mask) {
+			sd_status = azx_sd_readb(chip, azx_dev, SD_STS);
+			azx_sd_writeb(chip, 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 */
+			if (!chip->ops->position_check ||
+			    chip->ops->position_check(chip, azx_dev)) {
+				spin_unlock_irqrestore(&chip->reg_lock, cookie);
+				snd_pcm_period_elapsed(azx_dev->substream);
+				spin_lock_irqsave(&chip->reg_lock, cookie);
+			}
+		}
+	}
+
+	/* clear rirb int */
+	status = azx_readb(chip, RIRBSTS);
+	if (status & RIRB_INT_MASK) {
+		if (status & RIRB_INT_RESPONSE) {
+			if (chip->driver_caps & AZX_DCAPS_RIRB_PRE_DELAY)
+				udelay(80);
+			hda_update_rirb(chip);
+		}
+		azx_writeb(chip, RIRBSTS, RIRB_INT_MASK);
+	}
+
+	spin_unlock_irqrestore(&chip->reg_lock, cookie);
+
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(hda_threaded_handler);
+
+static bool is_input_stream(struct hda *chip, unsigned char index)
+{
+	return (index >= chip->capture_index_offset &&
+		 index < chip->capture_index_offset + chip->capture_streams);
+}
+
+/* initialize SD streams */
+int hda_init_stream(struct hda *chip)
+{
+	int i;
+	int in_stream_tag = 0;
+	int out_stream_tag = 0;
+
+	/* 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 hda_device *azx_dev = &chip->hda_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;
+		azx_dev->index = i;
+
+		/* stream tag must be unique throughout
+		 * the stream direction group,
+		 * valid values 1...15
+		 * use separate stream tag if the flag
+		 * AZX_DCAPS_SEPARATE_STREAM_TAG is used
+		 */
+		if (chip->driver_caps & AZX_DCAPS_SEPARATE_STREAM_TAG)
+			azx_dev->stream_tag =
+				is_input_stream(chip, i) ?
+				++in_stream_tag :
+				++out_stream_tag;
+		else
+			azx_dev->stream_tag = i + 1;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(hda_init_stream);
+
+void hda_timecounter_init(struct snd_pcm_substream *substream,
+				bool force, cycle_t last, void *read_fn)
+{
+	struct hda_device *azx_dev = get_hda_dev(substream);
+	struct timecounter *tc = &azx_dev->tc;
+	struct cyclecounter *cc = &azx_dev->cc;
+	u64 nsec;
+
+	cc->read = read_fn;
+	cc->mask = CLOCKSOURCE_MASK(32);
+
+	/*
+	 * Converting from 24 MHz to ns means applying a 125/3 factor.
+	 * To avoid any saturation issues in intermediate operations,
+	 * the 125 factor is applied first. The division is applied
+	 * last after reading the timecounter value.
+	 * Applying the 1/3 factor as part of the multiplication
+	 * requires at least 20 bits for a decent precision, however
+	 * overflows occur after about 4 hours or less, not a option.
+	 */
+
+	cc->mult = 125; /* saturation after 195 years */
+	cc->shift = 0;
+
+	nsec = 0; /* audio time is elapsed time since trigger */
+	timecounter_init(tc, cc, nsec);
+	if (force)
+		/*
+		 * force timecounter to use predefined value,
+		 * used for synchronized starts
+		 */
+		tc->cycle_last = last;
+}
+EXPORT_SYMBOL_GPL(hda_timecounter_init);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Common HDA driver funcitons");
-- 
1.7.9.5



More information about the Alsa-devel mailing list