[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