summaryrefslogtreecommitdiff
path: root/sound/pci/hda
diff options
context:
space:
mode:
Diffstat (limited to 'sound/pci/hda')
-rw-r--r--sound/pci/hda/Makefile7
-rw-r--r--sound/pci/hda/hda_codec.c1856
-rw-r--r--sound/pci/hda/hda_codec.h604
-rw-r--r--sound/pci/hda/hda_generic.c906
-rw-r--r--sound/pci/hda/hda_intel.c1449
-rw-r--r--sound/pci/hda/hda_local.h161
-rw-r--r--sound/pci/hda/hda_patch.h17
-rw-r--r--sound/pci/hda/hda_proc.c298
-rw-r--r--sound/pci/hda/patch_analog.c445
-rw-r--r--sound/pci/hda/patch_cmedia.c621
-rw-r--r--sound/pci/hda/patch_realtek.c1503
11 files changed, 7867 insertions, 0 deletions
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile
new file mode 100644
index 000000000000..570a59d33b41
--- /dev/null
+++ b/sound/pci/hda/Makefile
@@ -0,0 +1,7 @@
+snd-hda-intel-objs := hda_intel.o
+snd-hda-codec-objs := hda_codec.o hda_generic.o patch_realtek.o patch_cmedia.o patch_analog.o
+ifdef CONFIG_PROC_FS
+snd-hda-codec-objs += hda_proc.o
+endif
+
+obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o snd-hda-codec.o
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
new file mode 100644
index 000000000000..9ed117ac0c09
--- /dev/null
+++ b/sound/pci/hda/hda_codec.c
@@ -0,0 +1,1856 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/moduleparam.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include <sound/asoundef.h>
+#include <sound/initval.h>
+#include "hda_local.h"
+
+
+MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
+MODULE_DESCRIPTION("Universal interface for High Definition Audio Codec");
+MODULE_LICENSE("GPL");
+
+
+/*
+ * vendor / preset table
+ */
+
+struct hda_vendor_id {
+ unsigned int id;
+ const char *name;
+};
+
+/* codec vendor labels */
+static struct hda_vendor_id hda_vendor_ids[] = {
+ { 0x10ec, "Realtek" },
+ { 0x13f6, "C-Media" },
+ { 0x434d, "C-Media" },
+ {} /* terminator */
+};
+
+/* codec presets */
+#include "hda_patch.h"
+
+
+/**
+ * snd_hda_codec_read - send a command and get the response
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command and read the corresponding response.
+ *
+ * Returns the obtained response value, or -1 for an error.
+ */
+unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int parm)
+{
+ unsigned int res;
+ down(&codec->bus->cmd_mutex);
+ if (! codec->bus->ops.command(codec, nid, direct, verb, parm))
+ res = codec->bus->ops.get_response(codec);
+ else
+ res = (unsigned int)-1;
+ up(&codec->bus->cmd_mutex);
+ return res;
+}
+
+/**
+ * snd_hda_codec_write - send a single command without waiting for response
+ * @codec: the HDA codec
+ * @nid: NID to send the command
+ * @direct: direct flag
+ * @verb: the verb to send
+ * @parm: the parameter for the verb
+ *
+ * Send a single command without waiting for response.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int parm)
+{
+ int err;
+ down(&codec->bus->cmd_mutex);
+ err = codec->bus->ops.command(codec, nid, direct, verb, parm);
+ up(&codec->bus->cmd_mutex);
+ return err;
+}
+
+/**
+ * snd_hda_sequence_write - sequence writes
+ * @codec: the HDA codec
+ * @seq: VERB array to send
+ *
+ * Send the commands sequentially from the given array.
+ * The array must be terminated with NID=0.
+ */
+void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
+{
+ for (; seq->nid; seq++)
+ snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
+}
+
+/**
+ * snd_hda_get_sub_nodes - get the range of sub nodes
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @start_id: the pointer to store the start NID
+ *
+ * Parse the NID and store the start NID of its sub-nodes.
+ * Returns the number of sub-nodes.
+ */
+int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *start_id)
+{
+ unsigned int parm;
+
+ parm = snd_hda_param_read(codec, nid, AC_PAR_NODE_COUNT);
+ *start_id = (parm >> 16) & 0x7fff;
+ return (int)(parm & 0x7fff);
+}
+
+/**
+ * snd_hda_get_connections - get connection list
+ * @codec: the HDA codec
+ * @nid: NID to parse
+ * @conn_list: connection list array
+ * @max_conns: max. number of connections to store
+ *
+ * Parses the connection list of the given widget and stores the list
+ * of NIDs.
+ *
+ * Returns the number of connections, or a negative error code.
+ */
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
+ hda_nid_t *conn_list, int max_conns)
+{
+ unsigned int parm;
+ int i, j, conn_len, num_tupples, conns;
+ unsigned int shift, num_elems, mask;
+
+ snd_assert(conn_list && max_conns > 0, return -EINVAL);
+
+ parm = snd_hda_param_read(codec, nid, AC_PAR_CONNLIST_LEN);
+ if (parm & AC_CLIST_LONG) {
+ /* long form */
+ shift = 16;
+ num_elems = 2;
+ } else {
+ /* short form */
+ shift = 8;
+ num_elems = 4;
+ }
+ conn_len = parm & AC_CLIST_LENGTH;
+ num_tupples = num_elems / 2;
+ mask = (1 << (shift-1)) - 1;
+
+ if (! conn_len)
+ return 0; /* no connection */
+
+ if (conn_len == 1) {
+ /* single connection */
+ parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, 0);
+ conn_list[0] = parm & mask;
+ return 1;
+ }
+
+ /* multi connection */
+ conns = 0;
+ for (i = 0; i < conn_len; i += num_elems) {
+ parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, i);
+ for (j = 0; j < num_tupples; j++) {
+ int range_val;
+ hda_nid_t val1, val2, n;
+ range_val = parm & (1 << (shift-1)); /* ranges */
+ val1 = parm & mask;
+ parm >>= shift;
+ val2 = parm & mask;
+ parm >>= shift;
+ if (range_val) {
+ /* ranges between val1 and val2 */
+ if (val1 > val2) {
+ snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", val1, val2);
+ continue;
+ }
+ for (n = val1; n <= val2; n++) {
+ if (conns >= max_conns)
+ return -EINVAL;
+ conn_list[conns++] = n;
+ }
+ } else {
+ if (! val1)
+ break;
+ if (conns >= max_conns)
+ return -EINVAL;
+ conn_list[conns++] = val1;
+ if (! val2)
+ break;
+ if (conns >= max_conns)
+ return -EINVAL;
+ conn_list[conns++] = val2;
+ }
+ }
+ }
+ return conns;
+}
+
+
+/**
+ * snd_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 snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex)
+{
+ struct hda_bus_unsolicited *unsol;
+ unsigned int wp;
+
+ if ((unsol = bus->unsol) == NULL)
+ return 0;
+
+ wp = (unsol->wp + 1) % HDA_UNSOL_QUEUE_SIZE;
+ unsol->wp = wp;
+
+ wp <<= 1;
+ unsol->queue[wp] = res;
+ unsol->queue[wp + 1] = res_ex;
+
+ queue_work(unsol->workq, &unsol->work);
+
+ return 0;
+}
+
+/*
+ * process queueud unsolicited events
+ */
+static void process_unsol_events(void *data)
+{
+ struct hda_bus *bus = data;
+ struct hda_bus_unsolicited *unsol = bus->unsol;
+ struct hda_codec *codec;
+ unsigned int rp, caddr, res;
+
+ while (unsol->rp != unsol->wp) {
+ rp = (unsol->rp + 1) % HDA_UNSOL_QUEUE_SIZE;
+ unsol->rp = rp;
+ rp <<= 1;
+ res = unsol->queue[rp];
+ caddr = unsol->queue[rp + 1];
+ if (! (caddr & (1 << 4))) /* no unsolicited event? */
+ continue;
+ codec = bus->caddr_tbl[caddr & 0x0f];
+ if (codec && codec->patch_ops.unsol_event)
+ codec->patch_ops.unsol_event(codec, res);
+ }
+}
+
+/*
+ * initialize unsolicited queue
+ */
+static int init_unsol_queue(struct hda_bus *bus)
+{
+ struct hda_bus_unsolicited *unsol;
+
+ unsol = kcalloc(1, sizeof(*unsol), GFP_KERNEL);
+ if (! unsol) {
+ snd_printk(KERN_ERR "hda_codec: can't allocate unsolicited queue\n");
+ return -ENOMEM;
+ }
+ unsol->workq = create_workqueue("hda_codec");
+ if (! unsol->workq) {
+ snd_printk(KERN_ERR "hda_codec: can't create workqueue\n");
+ kfree(unsol);
+ return -ENOMEM;
+ }
+ INIT_WORK(&unsol->work, process_unsol_events, bus);
+ bus->unsol = unsol;
+ return 0;
+}
+
+/*
+ * destructor
+ */
+static void snd_hda_codec_free(struct hda_codec *codec);
+
+static int snd_hda_bus_free(struct hda_bus *bus)
+{
+ struct list_head *p, *n;
+
+ if (! bus)
+ return 0;
+ if (bus->unsol) {
+ destroy_workqueue(bus->unsol->workq);
+ kfree(bus->unsol);
+ }
+ list_for_each_safe(p, n, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ snd_hda_codec_free(codec);
+ }
+ if (bus->ops.private_free)
+ bus->ops.private_free(bus);
+ kfree(bus);
+ return 0;
+}
+
+static int snd_hda_bus_dev_free(snd_device_t *device)
+{
+ struct hda_bus *bus = device->device_data;
+ return snd_hda_bus_free(bus);
+}
+
+/**
+ * snd_hda_bus_new - create a HDA bus
+ * @card: the card entry
+ * @temp: the template for hda_bus information
+ * @busp: the pointer to store the created bus instance
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp,
+ struct hda_bus **busp)
+{
+ struct hda_bus *bus;
+ int err;
+ static snd_device_ops_t dev_ops = {
+ .dev_free = snd_hda_bus_dev_free,
+ };
+
+ snd_assert(temp, return -EINVAL);
+ snd_assert(temp->ops.command && temp->ops.get_response, return -EINVAL);
+
+ if (busp)
+ *busp = NULL;
+
+ bus = kcalloc(1, sizeof(*bus), GFP_KERNEL);
+ if (bus == NULL) {
+ snd_printk(KERN_ERR "can't allocate struct hda_bus\n");
+ return -ENOMEM;
+ }
+
+ bus->card = card;
+ bus->private_data = temp->private_data;
+ bus->pci = temp->pci;
+ bus->modelname = temp->modelname;
+ bus->ops = temp->ops;
+
+ init_MUTEX(&bus->cmd_mutex);
+ INIT_LIST_HEAD(&bus->codec_list);
+
+ init_unsol_queue(bus);
+
+ if ((err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops)) < 0) {
+ snd_hda_bus_free(bus);
+ return err;
+ }
+ if (busp)
+ *busp = bus;
+ return 0;
+}
+
+
+/*
+ * find a matching codec preset
+ */
+static const struct hda_codec_preset *find_codec_preset(struct hda_codec *codec)
+{
+ const struct hda_codec_preset **tbl, *preset;
+
+ for (tbl = hda_preset_tables; *tbl; tbl++) {
+ for (preset = *tbl; preset->id; preset++) {
+ u32 mask = preset->mask;
+ if (! mask)
+ mask = ~0;
+ if (preset->id == (codec->vendor_id & mask))
+ return preset;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * snd_hda_get_codec_name - store the codec name
+ */
+void snd_hda_get_codec_name(struct hda_codec *codec,
+ char *name, int namelen)
+{
+ const struct hda_vendor_id *c;
+ const char *vendor = NULL;
+ u16 vendor_id = codec->vendor_id >> 16;
+ char tmp[16];
+
+ for (c = hda_vendor_ids; c->id; c++) {
+ if (c->id == vendor_id) {
+ vendor = c->name;
+ break;
+ }
+ }
+ if (! vendor) {
+ sprintf(tmp, "Generic %04x", vendor_id);
+ vendor = tmp;
+ }
+ if (codec->preset && codec->preset->name)
+ snprintf(name, namelen, "%s %s", vendor, codec->preset->name);
+ else
+ snprintf(name, namelen, "%s ID %x", vendor, codec->vendor_id & 0xffff);
+}
+
+/*
+ * look for an AFG node
+ *
+ * return 0 if not found
+ */
+static int look_for_afg_node(struct hda_codec *codec)
+{
+ int i, total_nodes;
+ hda_nid_t nid;
+
+ total_nodes = snd_hda_get_sub_nodes(codec, AC_NODE_ROOT, &nid);
+ for (i = 0; i < total_nodes; i++, nid++) {
+ if ((snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE) & 0xff) ==
+ AC_GRP_AUDIO_FUNCTION)
+ return nid;
+ }
+ return 0;
+}
+
+/*
+ * codec destructor
+ */
+static void snd_hda_codec_free(struct hda_codec *codec)
+{
+ if (! codec)
+ return;
+ list_del(&codec->list);
+ codec->bus->caddr_tbl[codec->addr] = NULL;
+ if (codec->patch_ops.free)
+ codec->patch_ops.free(codec);
+ kfree(codec);
+}
+
+static void init_amp_hash(struct hda_codec *codec);
+
+/**
+ * snd_hda_codec_new - create a HDA codec
+ * @bus: the bus to assign
+ * @codec_addr: the codec address
+ * @codecp: the pointer to store the generated codec
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
+ struct hda_codec **codecp)
+{
+ struct hda_codec *codec;
+ char component[13];
+ int err;
+
+ snd_assert(bus, return -EINVAL);
+ snd_assert(codec_addr <= HDA_MAX_CODEC_ADDRESS, return -EINVAL);
+
+ if (bus->caddr_tbl[codec_addr]) {
+ snd_printk(KERN_ERR "hda_codec: address 0x%x is already occupied\n", codec_addr);
+ return -EBUSY;
+ }
+
+ codec = kcalloc(1, sizeof(*codec), GFP_KERNEL);
+ if (codec == NULL) {
+ snd_printk(KERN_ERR "can't allocate struct hda_codec\n");
+ return -ENOMEM;
+ }
+
+ codec->bus = bus;
+ codec->addr = codec_addr;
+ init_MUTEX(&codec->spdif_mutex);
+ init_amp_hash(codec);
+
+ list_add_tail(&codec->list, &bus->codec_list);
+ bus->caddr_tbl[codec_addr] = codec;
+
+ codec->vendor_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_VENDOR_ID);
+ codec->subsystem_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_SUBSYSTEM_ID);
+ codec->revision_id = snd_hda_param_read(codec, AC_NODE_ROOT, AC_PAR_REV_ID);
+
+ /* FIXME: support for multiple AFGs? */
+ codec->afg = look_for_afg_node(codec);
+ if (! codec->afg) {
+ snd_printk(KERN_ERR "hda_codec: no AFG node found\n");
+ snd_hda_codec_free(codec);
+ return -ENODEV;
+ }
+
+ codec->preset = find_codec_preset(codec);
+ if (! *bus->card->mixername)
+ snd_hda_get_codec_name(codec, bus->card->mixername,
+ sizeof(bus->card->mixername));
+
+ if (codec->preset && codec->preset->patch)
+ err = codec->preset->patch(codec);
+ else
+ err = snd_hda_parse_generic_codec(codec);
+ if (err < 0) {
+ snd_hda_codec_free(codec);
+ return err;
+ }
+
+ snd_hda_codec_proc_new(codec);
+
+ sprintf(component, "HDA:%08x", codec->vendor_id);
+ snd_component_add(codec->bus->card, component);
+
+ if (codecp)
+ *codecp = codec;
+ return 0;
+}
+
+/**
+ * snd_hda_codec_setup_stream - set up the codec for streaming
+ * @codec: the CODEC to set up
+ * @nid: the NID to set up
+ * @stream_tag: stream tag to pass, it's between 0x1 and 0xf.
+ * @channel_id: channel id to pass, zero based.
+ * @format: stream format.
+ */
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag,
+ int channel_id, int format)
+{
+ snd_printdd("hda_codec_setup_stream: NID=0x%x, stream=0x%x, channel=%d, format=0x%x\n",
+ nid, stream_tag, channel_id, format);
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID,
+ (stream_tag << 4) | channel_id);
+ msleep(1);
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format);
+}
+
+
+/*
+ * amp access functions
+ */
+
+#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + (idx) * 32 + (dir) * 64)
+#define INFO_AMP_CAPS (1<<0)
+#define INFO_AMP_VOL (1<<1)
+
+/* initialize the hash table */
+static void init_amp_hash(struct hda_codec *codec)
+{
+ memset(codec->amp_hash, 0xff, sizeof(codec->amp_hash));
+ codec->num_amp_entries = 0;
+}
+
+/* query the hash. allocate an entry if not found. */
+static struct hda_amp_info *get_alloc_amp_hash(struct hda_codec *codec, u32 key)
+{
+ u16 idx = key % (u16)ARRAY_SIZE(codec->amp_hash);
+ u16 cur = codec->amp_hash[idx];
+ struct hda_amp_info *info;
+
+ while (cur != 0xffff) {
+ info = &codec->amp_info[cur];
+ if (info->key == key)
+ return info;
+ cur = info->next;
+ }
+
+ /* add a new hash entry */
+ if (codec->num_amp_entries >= ARRAY_SIZE(codec->amp_info)) {
+ snd_printk(KERN_ERR "hda_codec: Tooooo many amps!\n");
+ return NULL;
+ }
+ cur = codec->num_amp_entries++;
+ info = &codec->amp_info[cur];
+ info->key = key;
+ info->status = 0; /* not initialized yet */
+ info->next = codec->amp_hash[idx];
+ codec->amp_hash[idx] = cur;
+
+ return info;
+}
+
+/*
+ * query AMP capabilities for the given widget and direction
+ */
+static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
+{
+ struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0));
+
+ if (! info)
+ return 0;
+ if (! (info->status & INFO_AMP_CAPS)) {
+ if (!(snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_AMP_OVRD))
+ nid = codec->afg;
+ info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ?
+ AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
+ info->status |= INFO_AMP_CAPS;
+ }
+ return info->amp_caps;
+}
+
+/*
+ * read the current volume to info
+ * if the cache exists, read from the cache.
+ */
+static void get_vol_mute(struct hda_codec *codec, struct hda_amp_info *info,
+ hda_nid_t nid, int ch, int direction, int index)
+{
+ u32 val, parm;
+
+ if (info->status & (INFO_AMP_VOL << ch))
+ return;
+
+ parm = ch ? AC_AMP_GET_RIGHT : AC_AMP_GET_LEFT;
+ parm |= direction == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
+ parm |= index;
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE, parm);
+ info->vol[ch] = val & 0xff;
+ info->status |= INFO_AMP_VOL << ch;
+}
+
+/*
+ * write the current volume in info to the h/w
+ */
+static void put_vol_mute(struct hda_codec *codec,
+ hda_nid_t nid, int ch, int direction, int index, int val)
+{
+ u32 parm;
+
+ parm = ch ? AC_AMP_SET_RIGHT : AC_AMP_SET_LEFT;
+ parm |= direction == HDA_OUTPUT ? AC_AMP_SET_OUTPUT : AC_AMP_SET_INPUT;
+ parm |= index << AC_AMP_SET_INDEX_SHIFT;
+ parm |= val;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, parm);
+}
+
+/*
+ * read/write AMP value. The volume is between 0 to 0x7f, 0x80 = mute bit.
+ */
+int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int index)
+{
+ struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index));
+ if (! info)
+ return 0;
+ get_vol_mute(codec, info, nid, ch, direction, index);
+ return info->vol[ch];
+}
+
+int snd_hda_codec_amp_write(struct hda_codec *codec, hda_nid_t nid, int ch, int direction, int idx, int val)
+{
+ struct hda_amp_info *info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx));
+ if (! info)
+ return 0;
+ get_vol_mute(codec, info, nid, ch, direction, idx);
+ if (info->vol[ch] == val && ! codec->in_resume)
+ return 0;
+ put_vol_mute(codec, nid, ch, direction, idx, val);
+ info->vol[ch] = val;
+ return 1;
+}
+
+
+/*
+ * AMP control callbacks
+ */
+/* retrieve parameters from private_value */
+#define get_amp_nid(kc) ((kc)->private_value & 0xffff)
+#define get_amp_channels(kc) (((kc)->private_value >> 16) & 0x3)
+#define get_amp_direction(kc) (((kc)->private_value >> 18) & 0x1)
+#define get_amp_index(kc) (((kc)->private_value >> 19) & 0xf)
+
+/* volume */
+int snd_hda_mixer_amp_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ u16 nid = get_amp_nid(kcontrol);
+ u8 chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ u32 caps;
+
+ caps = query_amp_caps(codec, nid, dir);
+ caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; /* num steps */
+ if (! caps) {
+ printk(KERN_WARNING "hda_codec: num_steps = 0 for NID=0x%x\n", nid);
+ return -EINVAL;
+ }
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = caps;
+ return 0;
+}
+
+int snd_hda_mixer_amp_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
+ if (chs & 2)
+ *valp = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
+ return 0;
+}
+
+int snd_hda_mixer_amp_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ int val;
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ if (chs & 1) {
+ val = *valp & 0x7f;
+ val |= snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80;
+ change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
+ valp++;
+ }
+ if (chs & 2) {
+ val = *valp & 0x7f;
+ val |= snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80;
+ change |= snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
+ }
+ return change;
+}
+
+/* switch */
+int snd_hda_mixer_amp_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ int chs = get_amp_channels(kcontrol);
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = chs == 3 ? 2 : 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+int snd_hda_mixer_amp_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ long *valp = ucontrol->value.integer.value;
+
+ if (chs & 1)
+ *valp++ = (snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x80) ? 0 : 1;
+ if (chs & 2)
+ *valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x80) ? 0 : 1;
+ return 0;
+}
+
+int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = get_amp_nid(kcontrol);
+ int chs = get_amp_channels(kcontrol);
+ int dir = get_amp_direction(kcontrol);
+ int idx = get_amp_index(kcontrol);
+ int val;
+ long *valp = ucontrol->value.integer.value;
+ int change = 0;
+
+ if (chs & 1) {
+ val = snd_hda_codec_amp_read(codec, nid, 0, dir, idx) & 0x7f;
+ val |= *valp ? 0 : 0x80;
+ change = snd_hda_codec_amp_write(codec, nid, 0, dir, idx, val);
+ valp++;
+ }
+ if (chs & 2) {
+ val = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & 0x7f;
+ val |= *valp ? 0 : 0x80;
+ change = snd_hda_codec_amp_write(codec, nid, 1, dir, idx, val);
+ }
+ return change;
+}
+
+/*
+ * SPDIF out controls
+ */
+
+static int snd_hda_spdif_mask_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
+ uinfo->count = 1;
+ return 0;
+}
+
+static int snd_hda_spdif_cmask_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_NONAUDIO |
+ IEC958_AES0_CON_EMPHASIS_5015 |
+ IEC958_AES0_CON_NOT_COPYRIGHT;
+ ucontrol->value.iec958.status[1] = IEC958_AES1_CON_CATEGORY |
+ IEC958_AES1_CON_ORIGINAL;
+ return 0;
+}
+
+static int snd_hda_spdif_pmask_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ ucontrol->value.iec958.status[0] = IEC958_AES0_PROFESSIONAL |
+ IEC958_AES0_NONAUDIO |
+ IEC958_AES0_PRO_EMPHASIS_5015;
+ return 0;
+}
+
+static int snd_hda_spdif_default_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.iec958.status[0] = codec->spdif_status & 0xff;
+ ucontrol->value.iec958.status[1] = (codec->spdif_status >> 8) & 0xff;
+ ucontrol->value.iec958.status[2] = (codec->spdif_status >> 16) & 0xff;
+ ucontrol->value.iec958.status[3] = (codec->spdif_status >> 24) & 0xff;
+
+ return 0;
+}
+
+/* convert from SPDIF status bits to HDA SPDIF bits
+ * bit 0 (DigEn) is always set zero (to be filled later)
+ */
+static unsigned short convert_from_spdif_status(unsigned int sbits)
+{
+ unsigned short val = 0;
+
+ if (sbits & IEC958_AES0_PROFESSIONAL)
+ val |= 1 << 6;
+ if (sbits & IEC958_AES0_NONAUDIO)
+ val |= 1 << 5;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if ((sbits & IEC958_AES0_PRO_EMPHASIS) == IEC958_AES0_PRO_EMPHASIS_5015)
+ val |= 1 << 3;
+ } else {
+ if ((sbits & IEC958_AES0_CON_EMPHASIS) == IEC958_AES0_CON_EMPHASIS_5015)
+ val |= 1 << 3;
+ if (! (sbits & IEC958_AES0_CON_NOT_COPYRIGHT))
+ val |= 1 << 4;
+ if (sbits & (IEC958_AES1_CON_ORIGINAL << 8))
+ val |= 1 << 7;
+ val |= sbits & (IEC958_AES1_CON_CATEGORY << 8);
+ }
+ return val;
+}
+
+/* convert to SPDIF status bits from HDA SPDIF bits
+ */
+static unsigned int convert_to_spdif_status(unsigned short val)
+{
+ unsigned int sbits = 0;
+
+ if (val & (1 << 5))
+ sbits |= IEC958_AES0_NONAUDIO;
+ if (val & (1 << 6))
+ sbits |= IEC958_AES0_PROFESSIONAL;
+ if (sbits & IEC958_AES0_PROFESSIONAL) {
+ if (sbits & (1 << 3))
+ sbits |= IEC958_AES0_PRO_EMPHASIS_5015;
+ } else {
+ if (val & (1 << 3))
+ sbits |= IEC958_AES0_CON_EMPHASIS_5015;
+ if (! (val & (1 << 4)))
+ sbits |= IEC958_AES0_CON_NOT_COPYRIGHT;
+ if (val & (1 << 7))
+ sbits |= (IEC958_AES1_CON_ORIGINAL << 8);
+ sbits |= val & (0x7f << 8);
+ }
+ return sbits;
+}
+
+static int snd_hda_spdif_default_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned short val;
+ int change;
+
+ down(&codec->spdif_mutex);
+ codec->spdif_status = ucontrol->value.iec958.status[0] |
+ ((unsigned int)ucontrol->value.iec958.status[1] << 8) |
+ ((unsigned int)ucontrol->value.iec958.status[2] << 16) |
+ ((unsigned int)ucontrol->value.iec958.status[3] << 24);
+ val = convert_from_spdif_status(codec->spdif_status);
+ val |= codec->spdif_ctls & 1;
+ change = codec->spdif_ctls != val;
+ codec->spdif_ctls = val;
+
+ if (change || codec->in_resume) {
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, val & 0xff);
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_2, val >> 8);
+ }
+
+ up(&codec->spdif_mutex);
+ return change;
+}
+
+static int snd_hda_spdif_out_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ uinfo->count = 1;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 1;
+ return 0;
+}
+
+static int snd_hda_spdif_out_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = codec->spdif_ctls & 1;
+ return 0;
+}
+
+static int snd_hda_spdif_out_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned short val;
+ int change;
+
+ down(&codec->spdif_mutex);
+ val = codec->spdif_ctls & ~1;
+ if (ucontrol->value.integer.value[0])
+ val |= 1;
+ change = codec->spdif_ctls != val;
+ if (change || codec->in_resume) {
+ codec->spdif_ctls = val;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, val & 0xff);
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+ AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT |
+ AC_AMP_SET_OUTPUT | ((val & 1) ? 0 : 0x80));
+ }
+ up(&codec->spdif_mutex);
+ return change;
+}
+
+static snd_kcontrol_new_t dig_mixes[] = {
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_cmask_get,
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,PRO_MASK),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_pmask_get,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_default_get,
+ .put = snd_hda_spdif_default_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,SWITCH),
+ .info = snd_hda_spdif_out_switch_info,
+ .get = snd_hda_spdif_out_switch_get,
+ .put = snd_hda_spdif_out_switch_put,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_out_ctls - create Output SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio out widget NID
+ *
+ * Creates controls related with the SPDIF output.
+ * Called from each patch supporting the SPDIF out.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+ int err;
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_new_t *dig_mix;
+
+ for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ kctl->private_value = nid;
+ if ((err = snd_ctl_add(codec->bus->card, kctl)) < 0)
+ return err;
+ }
+ codec->spdif_ctls = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0);
+ codec->spdif_status = convert_to_spdif_status(codec->spdif_ctls);
+ return 0;
+}
+
+/*
+ * SPDIF input
+ */
+
+#define snd_hda_spdif_in_switch_info snd_hda_spdif_out_switch_info
+
+static int snd_hda_spdif_in_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = codec->spdif_in_enable;
+ return 0;
+}
+
+static int snd_hda_spdif_in_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned int val = !!ucontrol->value.integer.value[0];
+ int change;
+
+ down(&codec->spdif_mutex);
+ change = codec->spdif_in_enable != val;
+ if (change || codec->in_resume) {
+ codec->spdif_in_enable = val;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, val);
+ }
+ up(&codec->spdif_mutex);
+ return change;
+}
+
+static int snd_hda_spdif_in_status_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ hda_nid_t nid = kcontrol->private_value;
+ unsigned short val;
+ unsigned int sbits;
+
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0);
+ sbits = convert_to_spdif_status(val);
+ ucontrol->value.iec958.status[0] = sbits;
+ ucontrol->value.iec958.status[1] = sbits >> 8;
+ ucontrol->value.iec958.status[2] = sbits >> 16;
+ ucontrol->value.iec958.status[3] = sbits >> 24;
+ return 0;
+}
+
+static snd_kcontrol_new_t dig_in_ctls[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,SWITCH),
+ .info = snd_hda_spdif_in_switch_info,
+ .get = snd_hda_spdif_in_switch_get,
+ .put = snd_hda_spdif_in_switch_put,
+ },
+ {
+ .access = SNDRV_CTL_ELEM_ACCESS_READ,
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = SNDRV_CTL_NAME_IEC958("",CAPTURE,DEFAULT),
+ .info = snd_hda_spdif_mask_info,
+ .get = snd_hda_spdif_in_status_get,
+ },
+ { } /* end */
+};
+
+/**
+ * snd_hda_create_spdif_in_ctls - create Input SPDIF-related controls
+ * @codec: the HDA codec
+ * @nid: audio in widget NID
+ *
+ * Creates controls related with the SPDIF input.
+ * Called from each patch supporting the SPDIF in.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
+{
+ int err;
+ snd_kcontrol_t *kctl;
+ snd_kcontrol_new_t *dig_mix;
+
+ for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) {
+ kctl = snd_ctl_new1(dig_mix, codec);
+ kctl->private_value = nid;
+ if ((err = snd_ctl_add(codec->bus->card, kctl)) < 0)
+ return err;
+ }
+ codec->spdif_in_enable = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0) & 1;
+ return 0;
+}
+
+
+/**
+ * snd_hda_build_controls - build mixer controls
+ * @bus: the BUS
+ *
+ * Creates mixer controls for each codec included in the bus.
+ *
+ * Returns 0 if successful, otherwise a negative error code.
+ */
+int snd_hda_build_controls(struct hda_bus *bus)
+{
+ struct list_head *p;
+
+ /* build controls */
+ list_for_each(p, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ int err;
+ if (! codec->patch_ops.build_controls)
+ continue;
+ err = codec->patch_ops.build_controls(codec);
+ if (err < 0)
+ return err;
+ }
+
+ /* initialize */
+ list_for_each(p, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ int err;
+ if (! codec->patch_ops.init)
+ continue;
+ err = codec->patch_ops.init(codec);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+
+/*
+ * stream formats
+ */
+static unsigned int rate_bits[][3] = {
+ /* rate in Hz, ALSA rate bitmask, HDA format value */
+ { 8000, SNDRV_PCM_RATE_8000, 0x0500 }, /* 1/6 x 48 */
+ { 11025, SNDRV_PCM_RATE_11025, 0x4300 }, /* 1/4 x 44 */
+ { 16000, SNDRV_PCM_RATE_16000, 0x0200 }, /* 1/3 x 48 */
+ { 22050, SNDRV_PCM_RATE_22050, 0x4100 }, /* 1/2 x 44 */
+ { 32000, SNDRV_PCM_RATE_32000, 0x0a00 }, /* 2/3 x 48 */
+ { 44100, SNDRV_PCM_RATE_44100, 0x4000 }, /* 44 */
+ { 48000, SNDRV_PCM_RATE_48000, 0x0000 }, /* 48 */
+ { 88200, SNDRV_PCM_RATE_88200, 0x4800 }, /* 2 x 44 */
+ { 96000, SNDRV_PCM_RATE_96000, 0x0800 }, /* 2 x 48 */
+ { 176400, SNDRV_PCM_RATE_176400, 0x5800 },/* 4 x 44 */
+ { 192000, SNDRV_PCM_RATE_192000, 0x1800 }, /* 4 x 48 */
+ { 0 }
+};
+
+/**
+ * snd_hda_calc_stream_format - calculate format bitset
+ * @rate: the sample rate
+ * @channels: the number of channels
+ * @format: the PCM format (SNDRV_PCM_FORMAT_XXX)
+ * @maxbps: the max. bps
+ *
+ * Calculate the format bitset from the given rate, channels and th PCM format.
+ *
+ * Return zero if invalid.
+ */
+unsigned int snd_hda_calc_stream_format(unsigned int rate,
+ unsigned int channels,
+ unsigned int format,
+ unsigned int maxbps)
+{
+ int i;
+ unsigned int val = 0;
+
+ for (i = 0; rate_bits[i][0]; i++)
+ if (rate_bits[i][0] == rate) {
+ val = rate_bits[i][2];
+ break;
+ }
+ if (! rate_bits[i][0]) {
+ snd_printdd("invalid rate %d\n", rate);
+ return 0;
+ }
+
+ if (channels == 0 || channels > 8) {
+ snd_printdd("invalid channels %d\n", channels);
+ return 0;
+ }
+ val |= channels - 1;
+
+ switch (snd_pcm_format_width(format)) {
+ case 8: val |= 0x00; break;
+ case 16: val |= 0x10; break;
+ case 20:
+ case 24:
+ case 32:
+ if (maxbps >= 32)
+ val |= 0x40;
+ else if (maxbps >= 24)
+ val |= 0x30;
+ else
+ val |= 0x20;
+ break;
+ default:
+ snd_printdd("invalid format width %d\n", snd_pcm_format_width(format));
+ return 0;
+ }
+
+ return val;
+}
+
+/**
+ * snd_hda_query_supported_pcm - query the supported PCM rates and formats
+ * @codec: the HDA codec
+ * @nid: NID to query
+ * @ratesp: the pointer to store the detected rate bitflags
+ * @formatsp: the pointer to store the detected formats
+ * @bpsp: the pointer to store the detected format widths
+ *
+ * Queries the supported PCM rates and formats. The NULL @ratesp, @formatsp
+ * or @bsps argument is ignored.
+ *
+ * Returns 0 if successful, otherwise a negative error code.
+ */
+int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+ u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
+{
+ int i;
+ unsigned int val, streams;
+
+ val = 0;
+ if (nid != codec->afg &&
+ snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
+ val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
+ if (val == -1)
+ return -EIO;
+ }
+ if (! val)
+ val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM);
+
+ if (ratesp) {
+ u32 rates = 0;
+ for (i = 0; rate_bits[i][0]; i++) {
+ if (val & (1 << i))
+ rates |= rate_bits[i][1];
+ }
+ *ratesp = rates;
+ }
+
+ if (formatsp || bpsp) {
+ u64 formats = 0;
+ unsigned int bps;
+ unsigned int wcaps;
+
+ wcaps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+ streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
+ if (streams == -1)
+ return -EIO;
+ if (! streams) {
+ streams = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM);
+ if (streams == -1)
+ return -EIO;
+ }
+
+ bps = 0;
+ if (streams & AC_SUPFMT_PCM) {
+ if (val & AC_SUPPCM_BITS_8) {
+ formats |= SNDRV_PCM_FMTBIT_U8;
+ bps = 8;
+ }
+ if (val & AC_SUPPCM_BITS_16) {
+ formats |= SNDRV_PCM_FMTBIT_S16_LE;
+ bps = 16;
+ }
+ if (wcaps & AC_WCAP_DIGITAL) {
+ if (val & AC_SUPPCM_BITS_32)
+ formats |= SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE;
+ if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24))
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (val & AC_SUPPCM_BITS_24)
+ bps = 24;
+ else if (val & AC_SUPPCM_BITS_20)
+ bps = 20;
+ } else if (val & (AC_SUPPCM_BITS_20|AC_SUPPCM_BITS_24|AC_SUPPCM_BITS_32)) {
+ formats |= SNDRV_PCM_FMTBIT_S32_LE;
+ if (val & AC_SUPPCM_BITS_32)
+ bps = 32;
+ else if (val & AC_SUPPCM_BITS_20)
+ bps = 20;
+ else if (val & AC_SUPPCM_BITS_24)
+ bps = 24;
+ }
+ }
+ else if (streams == AC_SUPFMT_FLOAT32) { /* should be exclusive */
+ formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
+ bps = 32;
+ } else if (streams == AC_SUPFMT_AC3) { /* should be exclusive */
+ /* temporary hack: we have still no proper support
+ * for the direct AC3 stream...
+ */
+ formats |= SNDRV_PCM_FMTBIT_U8;
+ bps = 8;
+ }
+ if (formatsp)
+ *formatsp = formats;
+ if (bpsp)
+ *bpsp = bps;
+ }
+
+ return 0;
+}
+
+/**
+ * snd_hda_is_supported_format - check whether the given node supports the format val
+ *
+ * Returns 1 if supported, 0 if not.
+ */
+int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int format)
+{
+ int i;
+ unsigned int val = 0, rate, stream;
+
+ if (nid != codec->afg &&
+ snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
+ val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
+ if (val == -1)
+ return 0;
+ }
+ if (! val) {
+ val = snd_hda_param_read(codec, codec->afg, AC_PAR_PCM);
+ if (val == -1)
+ return 0;
+ }
+
+ rate = format & 0xff00;
+ for (i = 0; rate_bits[i][0]; i++)
+ if (rate_bits[i][2] == rate) {
+ if (val & (1 << i))
+ break;
+ return 0;
+ }
+ if (! rate_bits[i][0])
+ return 0;
+
+ stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
+ if (stream == -1)
+ return 0;
+ if (! stream && nid != codec->afg)
+ stream = snd_hda_param_read(codec, codec->afg, AC_PAR_STREAM);
+ if (! stream || stream == -1)
+ return 0;
+
+ if (stream & AC_SUPFMT_PCM) {
+ switch (format & 0xf0) {
+ case 0x00:
+ if (! (val & AC_SUPPCM_BITS_8))
+ return 0;
+ break;
+ case 0x10:
+ if (! (val & AC_SUPPCM_BITS_16))
+ return 0;
+ break;
+ case 0x20:
+ if (! (val & AC_SUPPCM_BITS_20))
+ return 0;
+ break;
+ case 0x30:
+ if (! (val & AC_SUPPCM_BITS_24))
+ return 0;
+ break;
+ case 0x40:
+ if (! (val & AC_SUPPCM_BITS_32))
+ return 0;
+ break;
+ default:
+ return 0;
+ }
+ } else {
+ /* FIXME: check for float32 and AC3? */
+ }
+
+ return 1;
+}
+
+/*
+ * PCM stuff
+ */
+static int hda_pcm_default_open_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ return 0;
+}
+
+static int hda_pcm_default_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format);
+ return 0;
+}
+
+static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ snd_hda_codec_setup_stream(codec, hinfo->nid, 0, 0, 0);
+ return 0;
+}
+
+static int set_pcm_default_values(struct hda_codec *codec, struct hda_pcm_stream *info)
+{
+ if (info->nid) {
+ /* query support PCM information from the given NID */
+ if (! info->rates || ! info->formats)
+ snd_hda_query_supported_pcm(codec, info->nid,
+ info->rates ? NULL : &info->rates,
+ info->formats ? NULL : &info->formats,
+ info->maxbps ? NULL : &info->maxbps);
+ }
+ if (info->ops.open == NULL)
+ info->ops.open = hda_pcm_default_open_close;
+ if (info->ops.close == NULL)
+ info->ops.close = hda_pcm_default_open_close;
+ if (info->ops.prepare == NULL) {
+ snd_assert(info->nid, return -EINVAL);
+ info->ops.prepare = hda_pcm_default_prepare;
+ }
+ if (info->ops.prepare == NULL) {
+ snd_assert(info->nid, return -EINVAL);
+ info->ops.prepare = hda_pcm_default_prepare;
+ }
+ if (info->ops.cleanup == NULL) {
+ snd_assert(info->nid, return -EINVAL);
+ info->ops.cleanup = hda_pcm_default_cleanup;
+ }
+ return 0;
+}
+
+/**
+ * snd_hda_build_pcms - build PCM information
+ * @bus: the BUS
+ *
+ * Create PCM information for each codec included in the bus.
+ *
+ * The build_pcms codec patch is requested to set up codec->num_pcms and
+ * codec->pcm_info properly. The array is referred by the top-level driver
+ * to create its PCM instances.
+ * The allocated codec->pcm_info should be released in codec->patch_ops.free
+ * callback.
+ *
+ * At least, substreams, channels_min and channels_max must be filled for
+ * each stream. substreams = 0 indicates that the stream doesn't exist.
+ * When rates and/or formats are zero, the supported values are queried
+ * from the given nid. The nid is used also by the default ops.prepare
+ * and ops.cleanup callbacks.
+ *
+ * The driver needs to call ops.open in its open callback. Similarly,
+ * ops.close is supposed to be called in the close callback.
+ * ops.prepare should be called in the prepare or hw_params callback
+ * with the proper parameters for set up.
+ * ops.cleanup should be called in hw_free for clean up of streams.
+ *
+ * This function returns 0 if successfull, or a negative error code.
+ */
+int snd_hda_build_pcms(struct hda_bus *bus)
+{
+ struct list_head *p;
+
+ list_for_each(p, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ unsigned int pcm, s;
+ int err;
+ if (! codec->patch_ops.build_pcms)
+ continue;
+ err = codec->patch_ops.build_pcms(codec);
+ if (err < 0)
+ return err;
+ for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+ for (s = 0; s < 2; s++) {
+ struct hda_pcm_stream *info;
+ info = &codec->pcm_info[pcm].stream[s];
+ if (! info->substreams)
+ continue;
+ err = set_pcm_default_values(codec, info);
+ if (err < 0)
+ return err;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * snd_hda_check_board_config - compare the current codec with the config table
+ * @codec: the HDA codec
+ * @tbl: configuration table, terminated by null entries
+ *
+ * Compares the modelname or PCI subsystem id of the current codec with the
+ * given configuration table. If a matching entry is found, returns its
+ * config value (supposed to be 0 or positive).
+ *
+ * If no entries are matching, the function returns a negative value.
+ */
+int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl)
+{
+ struct hda_board_config *c;
+
+ if (codec->bus->modelname) {
+ for (c = tbl; c->modelname || c->pci_vendor; c++) {
+ if (c->modelname &&
+ ! strcmp(codec->bus->modelname, c->modelname)) {
+ snd_printd(KERN_INFO "hda_codec: model '%s' is selected\n", c->modelname);
+ return c->config;
+ }
+ }
+ }
+
+ if (codec->bus->pci) {
+ u16 subsystem_vendor, subsystem_device;
+ pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vendor);
+ pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_ID, &subsystem_device);
+ for (c = tbl; c->modelname || c->pci_vendor; c++) {
+ if (c->pci_vendor == subsystem_vendor &&
+ c->pci_device == subsystem_device)
+ return c->config;
+ }
+ }
+ return -1;
+}
+
+/**
+ * snd_hda_add_new_ctls - create controls from the array
+ * @codec: the HDA codec
+ * @knew: the array of snd_kcontrol_new_t
+ *
+ * This helper function creates and add new controls in the given array.
+ * The array must be terminated with an empty entry as terminator.
+ *
+ * Returns 0 if successful, or a negative error code.
+ */
+int snd_hda_add_new_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew)
+{
+ int err;
+
+ for (; knew->name; knew++) {
+ err = snd_ctl_add(codec->bus->card, snd_ctl_new1(knew, codec));
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+
+/*
+ * input MUX helper
+ */
+int snd_hda_input_mux_info(const struct hda_input_mux *imux, snd_ctl_elem_info_t *uinfo)
+{
+ unsigned int index;
+
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = imux->num_items;
+ index = uinfo->value.enumerated.item;
+ if (index >= imux->num_items)
+ index = imux->num_items - 1;
+ strcpy(uinfo->value.enumerated.name, imux->items[index].label);
+ return 0;
+}
+
+int snd_hda_input_mux_put(struct hda_codec *codec, const struct hda_input_mux *imux,
+ snd_ctl_elem_value_t *ucontrol, hda_nid_t nid,
+ unsigned int *cur_val)
+{
+ unsigned int idx;
+
+ idx = ucontrol->value.enumerated.item[0];
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (*cur_val == idx && ! codec->in_resume)
+ return 0;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL,
+ imux->items[idx].index);
+ *cur_val = idx;
+ return 1;
+}
+
+
+/*
+ * Multi-channel / digital-out PCM helper functions
+ */
+
+/*
+ * open the digital out in the exclusive mode
+ */
+int snd_hda_multi_out_dig_open(struct hda_codec *codec, struct hda_multi_out *mout)
+{
+ down(&codec->spdif_mutex);
+ if (mout->dig_out_used) {
+ up(&codec->spdif_mutex);
+ return -EBUSY; /* already being used */
+ }
+ mout->dig_out_used = HDA_DIG_EXCLUSIVE;
+ up(&codec->spdif_mutex);
+ return 0;
+}
+
+/*
+ * release the digital out
+ */
+int snd_hda_multi_out_dig_close(struct hda_codec *codec, struct hda_multi_out *mout)
+{
+ down(&codec->spdif_mutex);
+ mout->dig_out_used = 0;
+ up(&codec->spdif_mutex);
+ return 0;
+}
+
+/*
+ * set up more restrictions for analog out
+ */
+int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout,
+ snd_pcm_substream_t *substream)
+{
+ substream->runtime->hw.channels_max = mout->max_channels;
+ return snd_pcm_hw_constraint_step(substream->runtime, 0,
+ SNDRV_PCM_HW_PARAM_CHANNELS, 2);
+}
+
+/*
+ * set up the i/o for analog out
+ * when the digital out is available, copy the front out to digital out, too.
+ */
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ hda_nid_t *nids = mout->dac_nids;
+ int chs = substream->runtime->channels;
+ int i;
+
+ down(&codec->spdif_mutex);
+ if (mout->dig_out_nid && mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+ if (chs == 2 &&
+ snd_hda_is_supported_format(codec, mout->dig_out_nid, format) &&
+ ! (codec->spdif_status & IEC958_AES0_NONAUDIO)) {
+ mout->dig_out_used = HDA_DIG_ANALOG_DUP;
+ /* setup digital receiver */
+ snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
+ stream_tag, 0, format);
+ } else {
+ mout->dig_out_used = 0;
+ snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
+ }
+ }
+ up(&codec->spdif_mutex);
+
+ /* front */
+ snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag, 0, format);
+ if (mout->hp_nid)
+ /* headphone out will just decode front left/right (stereo) */
+ snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, 0, format);
+ /* surrounds */
+ for (i = 1; i < mout->num_dacs; i++) {
+ if (i == HDA_REAR && chs == 2) /* copy front to rear */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag, 0, format);
+ else if (chs >= (i + 1) * 2) /* independent out */
+ snd_hda_codec_setup_stream(codec, nids[i], stream_tag, i * 2,
+ format);
+ }
+ return 0;
+}
+
+/*
+ * clean up the setting for analog out
+ */
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_out *mout)
+{
+ hda_nid_t *nids = mout->dac_nids;
+ int i;
+
+ for (i = 0; i < mout->num_dacs; i++)
+ snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0);
+ if (mout->hp_nid)
+ snd_hda_codec_setup_stream(codec, mout->hp_nid, 0, 0, 0);
+ down(&codec->spdif_mutex);
+ if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
+ snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
+ mout->dig_out_used = 0;
+ }
+ up(&codec->spdif_mutex);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+
+/**
+ * snd_hda_suspend - suspend the codecs
+ * @bus: the HDA bus
+ * @state: suspsend state
+ *
+ * Returns 0 if successful.
+ */
+int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
+{
+ struct list_head *p;
+
+ /* FIXME: should handle power widget capabilities */
+ list_for_each(p, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ if (codec->patch_ops.suspend)
+ codec->patch_ops.suspend(codec, state);
+ }
+ return 0;
+}
+
+/**
+ * snd_hda_resume - resume the codecs
+ * @bus: the HDA bus
+ * @state: resume state
+ *
+ * Returns 0 if successful.
+ */
+int snd_hda_resume(struct hda_bus *bus)
+{
+ struct list_head *p;
+
+ list_for_each(p, &bus->codec_list) {
+ struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+ if (codec->patch_ops.resume)
+ codec->patch_ops.resume(codec);
+ }
+ return 0;
+}
+
+/**
+ * snd_hda_resume_ctls - resume controls in the new control list
+ * @codec: the HDA codec
+ * @knew: the array of snd_kcontrol_new_t
+ *
+ * This function resumes the mixer controls in the snd_kcontrol_new_t array,
+ * originally for snd_hda_add_new_ctls().
+ * The array must be terminated with an empty entry as terminator.
+ */
+int snd_hda_resume_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew)
+{
+ snd_ctl_elem_value_t *val;
+
+ val = kmalloc(sizeof(*val), GFP_KERNEL);
+ if (! val)
+ return -ENOMEM;
+ codec->in_resume = 1;
+ for (; knew->name; knew++) {
+ int i, count;
+ count = knew->count ? knew->count : 1;
+ for (i = 0; i < count; i++) {
+ memset(val, 0, sizeof(*val));
+ val->id.iface = knew->iface;
+ val->id.device = knew->device;
+ val->id.subdevice = knew->subdevice;
+ strcpy(val->id.name, knew->name);
+ val->id.index = knew->index ? knew->index : i;
+ /* Assume that get callback reads only from cache,
+ * not accessing to the real hardware
+ */
+ if (snd_ctl_elem_read(codec->bus->card, val) < 0)
+ continue;
+ snd_ctl_elem_write(codec->bus->card, NULL, val);
+ }
+ }
+ codec->in_resume = 0;
+ kfree(val);
+ return 0;
+}
+
+/**
+ * snd_hda_resume_spdif_out - resume the digital out
+ * @codec: the HDA codec
+ */
+int snd_hda_resume_spdif_out(struct hda_codec *codec)
+{
+ return snd_hda_resume_ctls(codec, dig_mixes);
+}
+
+/**
+ * snd_hda_resume_spdif_in - resume the digital in
+ * @codec: the HDA codec
+ */
+int snd_hda_resume_spdif_in(struct hda_codec *codec)
+{
+ return snd_hda_resume_ctls(codec, dig_in_ctls);
+}
+#endif
+
+/*
+ * symbols exported for controller modules
+ */
+EXPORT_SYMBOL(snd_hda_codec_read);
+EXPORT_SYMBOL(snd_hda_codec_write);
+EXPORT_SYMBOL(snd_hda_sequence_write);
+EXPORT_SYMBOL(snd_hda_get_sub_nodes);
+EXPORT_SYMBOL(snd_hda_queue_unsol_event);
+EXPORT_SYMBOL(snd_hda_bus_new);
+EXPORT_SYMBOL(snd_hda_codec_new);
+EXPORT_SYMBOL(snd_hda_codec_setup_stream);
+EXPORT_SYMBOL(snd_hda_calc_stream_format);
+EXPORT_SYMBOL(snd_hda_build_pcms);
+EXPORT_SYMBOL(snd_hda_build_controls);
+#ifdef CONFIG_PM
+EXPORT_SYMBOL(snd_hda_suspend);
+EXPORT_SYMBOL(snd_hda_resume);
+#endif
+
+/*
+ * INIT part
+ */
+
+static int __init alsa_hda_init(void)
+{
+ return 0;
+}
+
+static void __exit alsa_hda_exit(void)
+{
+}
+
+module_init(alsa_hda_init)
+module_exit(alsa_hda_exit)
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
new file mode 100644
index 000000000000..c9e9dc9c7c98
--- /dev/null
+++ b/sound/pci/hda/hda_codec.h
@@ -0,0 +1,604 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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.
+ */
+
+#ifndef __SOUND_HDA_CODEC_H
+#define __SOUND_HDA_CODEC_H
+
+#include <sound/info.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+
+/*
+ * nodes
+ */
+#define AC_NODE_ROOT 0x00
+
+/*
+ * function group types
+ */
+enum {
+ AC_GRP_AUDIO_FUNCTION = 0x01,
+ AC_GRP_MODEM_FUNCTION = 0x02,
+};
+
+/*
+ * widget types
+ */
+enum {
+ AC_WID_AUD_OUT, /* Audio Out */
+ AC_WID_AUD_IN, /* Audio In */
+ AC_WID_AUD_MIX, /* Audio Mixer */
+ AC_WID_AUD_SEL, /* Audio Selector */
+ AC_WID_PIN, /* Pin Complex */
+ AC_WID_POWER, /* Power */
+ AC_WID_VOL_KNB, /* Volume Knob */
+ AC_WID_BEEP, /* Beep Generator */
+ AC_WID_VENDOR = 0x0f /* Vendor specific */
+};
+
+/*
+ * GET verbs
+ */
+#define AC_VERB_GET_STREAM_FORMAT 0x0a00
+#define AC_VERB_GET_AMP_GAIN_MUTE 0x0b00
+#define AC_VERB_GET_PROC_COEF 0x0c00
+#define AC_VERB_GET_COEF_INDEX 0x0d00
+#define AC_VERB_PARAMETERS 0x0f00
+#define AC_VERB_GET_CONNECT_SEL 0x0f01
+#define AC_VERB_GET_CONNECT_LIST 0x0f02
+#define AC_VERB_GET_PROC_STATE 0x0f03
+#define AC_VERB_GET_SDI_SELECT 0x0f04
+#define AC_VERB_GET_POWER_STATE 0x0f05
+#define AC_VERB_GET_CONV 0x0f06
+#define AC_VERB_GET_PIN_WIDGET_CONTROL 0x0f07
+#define AC_VERB_GET_UNSOLICITED_RESPONSE 0x0f08
+#define AC_VERB_GET_PIN_SENSE 0x0f09
+#define AC_VERB_GET_BEEP_CONTROL 0x0f0a
+#define AC_VERB_GET_EAPD_BTLENABLE 0x0f0c
+#define AC_VERB_GET_DIGI_CONVERT 0x0f0d
+#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f
+/* f10-f1a: GPIO */
+#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c
+
+/*
+ * SET verbs
+ */
+#define AC_VERB_SET_STREAM_FORMAT 0x200
+#define AC_VERB_SET_AMP_GAIN_MUTE 0x300
+#define AC_VERB_SET_PROC_COEF 0x400
+#define AC_VERB_SET_COEF_INDEX 0x500
+#define AC_VERB_SET_CONNECT_SEL 0x701
+#define AC_VERB_SET_PROC_STATE 0x703
+#define AC_VERB_SET_SDI_SELECT 0x704
+#define AC_VERB_SET_POWER_STATE 0x705
+#define AC_VERB_SET_CHANNEL_STREAMID 0x706
+#define AC_VERB_SET_PIN_WIDGET_CONTROL 0x707
+#define AC_VERB_SET_UNSOLICITED_ENABLE 0x708
+#define AC_VERB_SET_PIN_SENSE 0x709
+#define AC_VERB_SET_BEEP_CONTROL 0x70a
+#define AC_VERB_SET_EAPD_BTLENALBE 0x70c
+#define AC_VERB_SET_DIGI_CONVERT_1 0x70d
+#define AC_VERB_SET_DIGI_CONVERT_2 0x70e
+#define AC_VERB_SET_VOLUME_KNOB_CONTROL 0x70f
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 0x71c
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_1 0x71d
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_2 0x71e
+#define AC_VERB_SET_CONFIG_DEFAULT_BYTES_3 0x71f
+#define AC_VERB_SET_CODEC_RESET 0x7ff
+
+/*
+ * Parameter IDs
+ */
+#define AC_PAR_VENDOR_ID 0x00
+#define AC_PAR_SUBSYSTEM_ID 0x01
+#define AC_PAR_REV_ID 0x02
+#define AC_PAR_NODE_COUNT 0x04
+#define AC_PAR_FUNCTION_TYPE 0x05
+#define AC_PAR_AUDIO_FG_CAP 0x08
+#define AC_PAR_AUDIO_WIDGET_CAP 0x09
+#define AC_PAR_PCM 0x0a
+#define AC_PAR_STREAM 0x0b
+#define AC_PAR_PIN_CAP 0x0c
+#define AC_PAR_AMP_IN_CAP 0x0d
+#define AC_PAR_CONNLIST_LEN 0x0e
+#define AC_PAR_POWER_STATE 0x0f
+#define AC_PAR_PROC_CAP 0x10
+#define AC_PAR_GPIO_CAP 0x11
+#define AC_PAR_AMP_OUT_CAP 0x12
+
+/*
+ * AC_VERB_PARAMETERS results (32bit)
+ */
+
+/* Function Group Type */
+#define AC_FGT_TYPE (0xff<<0)
+#define AC_FGT_TYPE_SHIFT 0
+#define AC_FGT_UNSOL_CAP (1<<8)
+
+/* Audio Function Group Capabilities */
+#define AC_AFG_OUT_DELAY (0xf<<0)
+#define AC_AFG_IN_DELAY (0xf<<8)
+#define AC_AFG_BEEP_GEN (1<<16)
+
+/* Audio Widget Capabilities */
+#define AC_WCAP_STEREO (1<<0) /* stereo I/O */
+#define AC_WCAP_IN_AMP (1<<1) /* AMP-in present */
+#define AC_WCAP_OUT_AMP (1<<2) /* AMP-out present */
+#define AC_WCAP_AMP_OVRD (1<<3) /* AMP-parameter override */
+#define AC_WCAP_FORMAT_OVRD (1<<4) /* format override */
+#define AC_WCAP_STRIPE (1<<5) /* stripe */
+#define AC_WCAP_PROC_WID (1<<6) /* Proc Widget */
+#define AC_WCAP_UNSOL_CAP (1<<7) /* Unsol capable */
+#define AC_WCAP_CONN_LIST (1<<8) /* connection list */
+#define AC_WCAP_DIGITAL (1<<9) /* digital I/O */
+#define AC_WCAP_POWER (1<<10) /* power control */
+#define AC_WCAP_LR_SWAP (1<<11) /* L/R swap */
+#define AC_WCAP_DELAY (0xf<<16)
+#define AC_WCAP_DELAY_SHIFT 16
+#define AC_WCAP_TYPE (0xf<<20)
+#define AC_WCAP_TYPE_SHIFT 20
+
+/* supported PCM rates and bits */
+#define AC_SUPPCM_RATES (0xfff << 0)
+#define AC_SUPPCM_BITS_8 (1<<16)
+#define AC_SUPPCM_BITS_16 (1<<17)
+#define AC_SUPPCM_BITS_20 (1<<18)
+#define AC_SUPPCM_BITS_24 (1<<19)
+#define AC_SUPPCM_BITS_32 (1<<20)
+
+/* supported PCM stream format */
+#define AC_SUPFMT_PCM (1<<0)
+#define AC_SUPFMT_FLOAT32 (1<<1)
+#define AC_SUPFMT_AC3 (1<<2)
+
+/* Pin widget capabilies */
+#define AC_PINCAP_IMP_SENSE (1<<0) /* impedance sense capable */
+#define AC_PINCAP_TRIG_REQ (1<<1) /* trigger required */
+#define AC_PINCAP_PRES_DETECT (1<<2) /* presence detect capable */
+#define AC_PINCAP_HP_DRV (1<<3) /* headphone drive capable */
+#define AC_PINCAP_OUT (1<<4) /* output capable */
+#define AC_PINCAP_IN (1<<5) /* input capable */
+#define AC_PINCAP_BALANCE (1<<6) /* balanced I/O capable */
+#define AC_PINCAP_VREF (7<<8)
+#define AC_PINCAP_VREF_SHIFT 8
+#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */
+/* Vref status (used in pin cap and pin ctl) */
+#define AC_PIN_VREF_HIZ (1<<0) /* Hi-Z */
+#define AC_PIN_VREF_50 (1<<1) /* 50% */
+#define AC_PIN_VREF_GRD (1<<2) /* ground */
+#define AC_PIN_VREF_80 (1<<4) /* 80% */
+#define AC_PIN_VREF_100 (1<<5) /* 100% */
+
+
+/* Amplifier capabilities */
+#define AC_AMPCAP_OFFSET (0x7f<<0) /* 0dB offset */
+#define AC_AMPCAP_OFFSET_SHIFT 0
+#define AC_AMPCAP_NUM_STEPS (0x7f<<8) /* number of steps */
+#define AC_AMPCAP_NUM_STEPS_SHIFT 8
+#define AC_AMPCAP_STEP_SIZE (0x7f<<16) /* step size 0-32dB in 0.25dB */
+#define AC_AMPCAP_STEP_SIZE_SHIFT 16
+#define AC_AMPCAP_MUTE (1<<31) /* mute capable */
+#define AC_AMPCAP_MUTE_SHIFT 31
+
+/* Connection list */
+#define AC_CLIST_LENGTH (0x7f<<0)
+#define AC_CLIST_LONG (1<<7)
+
+/* Supported power status */
+#define AC_PWRST_D0SUP (1<<0)
+#define AC_PWRST_D1SUP (1<<1)
+#define AC_PWRST_D2SUP (1<<2)
+#define AC_PWRST_D3SUP (1<<3)
+
+/* Processing capabilies */
+#define AC_PCAP_BENIGN (1<<0)
+#define AC_PCAP_NUM_COEF (0xff<<8)
+
+/* Volume knobs capabilities */
+#define AC_KNBCAP_NUM_STEPS (0x7f<<0)
+#define AC_KNBCAP_DELTA (1<<8)
+
+/*
+ * Control Parameters
+ */
+
+/* Amp gain/mute */
+#define AC_AMP_MUTE (1<<8)
+#define AC_AMP_GAIN (0x7f)
+#define AC_AMP_GET_INDEX (0xf<<0)
+
+#define AC_AMP_GET_LEFT (1<<13)
+#define AC_AMP_GET_RIGHT (0<<13)
+#define AC_AMP_GET_OUTPUT (1<<15)
+#define AC_AMP_GET_INPUT (0<<15)
+
+#define AC_AMP_SET_INDEX (0xf<<8)
+#define AC_AMP_SET_INDEX_SHIFT 8
+#define AC_AMP_SET_RIGHT (1<<12)
+#define AC_AMP_SET_LEFT (1<<13)
+#define AC_AMP_SET_INPUT (1<<14)
+#define AC_AMP_SET_OUTPUT (1<<15)
+
+/* DIGITAL1 bits */
+#define AC_DIG1_ENABLE (1<<0)
+#define AC_DIG1_V (1<<1)
+#define AC_DIG1_VCFG (1<<2)
+#define AC_DIG1_EMPHASIS (1<<3)
+#define AC_DIG1_COPYRIGHT (1<<4)
+#define AC_DIG1_NONAUDIO (1<<5)
+#define AC_DIG1_PROFESSIONAL (1<<6)
+#define AC_DIG1_LEVEL (1<<7)
+
+/* Pin widget control - 8bit */
+#define AC_PINCTL_VREFEN (0x7<<0)
+#define AC_PINCTL_IN_EN (1<<5)
+#define AC_PINCTL_OUT_EN (1<<6)
+#define AC_PINCTL_HP_EN (1<<7)
+
+/* configuration default - 32bit */
+#define AC_DEFCFG_SEQUENCE (0xf<<0)
+#define AC_DEFCFG_DEF_ASSOC (0xf<<4)
+#define AC_DEFCFG_MISC (0xf<<8)
+#define AC_DEFCFG_COLOR (0xf<<12)
+#define AC_DEFCFG_COLOR_SHIFT 12
+#define AC_DEFCFG_CONN_TYPE (0xf<<16)
+#define AC_DEFCFG_CONN_TYPE_SHIFT 16
+#define AC_DEFCFG_DEVICE (0xf<<20)
+#define AC_DEFCFG_DEVICE_SHIFT 20
+#define AC_DEFCFG_LOCATION (0x3f<<24)
+#define AC_DEFCFG_LOCATION_SHIFT 24
+#define AC_DEFCFG_PORT_CONN (0x3<<30)
+#define AC_DEFCFG_PORT_CONN_SHIFT 30
+
+/* device device types (0x0-0xf) */
+enum {
+ AC_JACK_LINE_OUT,
+ AC_JACK_SPEAKER,
+ AC_JACK_HP_OUT,
+ AC_JACK_CD,
+ AC_JACK_SPDIF_OUT,
+ AC_JACK_DIG_OTHER_OUT,
+ AC_JACK_MODEM_LINE_SIDE,
+ AC_JACK_MODEM_HAND_SIDE,
+ AC_JACK_LINE_IN,
+ AC_JACK_AUX,
+ AC_JACK_MIC_IN,
+ AC_JACK_TELEPHONY,
+ AC_JACK_SPDIF_IN,
+ AC_JACK_DIG_OTHER_IN,
+ AC_JACK_OTHER = 0xf,
+};
+
+/* jack connection types (0x0-0xf) */
+enum {
+ AC_JACK_CONN_UNKNOWN,
+ AC_JACK_CONN_1_8,
+ AC_JACK_CONN_1_4,
+ AC_JACK_CONN_ATAPI,
+ AC_JACK_CONN_RCA,
+ AC_JACK_CONN_OPTICAL,
+ AC_JACK_CONN_OTHER_DIGITAL,
+ AC_JACK_CONN_OTHER_ANALOG,
+ AC_JACK_CONN_DIN,
+ AC_JACK_CONN_XLR,
+ AC_JACK_CONN_RJ11,
+ AC_JACK_CONN_COMB,
+ AC_JACK_CONN_OTHER = 0xf,
+};
+
+/* jack colors (0x0-0xf) */
+enum {
+ AC_JACK_COLOR_UNKNOWN,
+ AC_JACK_COLOR_BLACK,
+ AC_JACK_COLOR_GREY,
+ AC_JACK_COLOR_BLUE,
+ AC_JACK_COLOR_GREEN,
+ AC_JACK_COLOR_RED,
+ AC_JACK_COLOR_ORANGE,
+ AC_JACK_COLOR_YELLOW,
+ AC_JACK_COLOR_PURPLE,
+ AC_JACK_COLOR_PINK,
+ AC_JACK_COLOR_WHITE = 0xe,
+ AC_JACK_COLOR_OTHER,
+};
+
+/* Jack location (0x0-0x3f) */
+/* common case */
+enum {
+ AC_JACK_LOC_NONE,
+ AC_JACK_LOC_REAR,
+ AC_JACK_LOC_FRONT,
+ AC_JACK_LOC_LEFT,
+ AC_JACK_LOC_RIGHT,
+ AC_JACK_LOC_TOP,
+ AC_JACK_LOC_BOTTOM,
+};
+/* bits 4-5 */
+enum {
+ AC_JACK_LOC_EXTERNAL = 0x00,
+ AC_JACK_LOC_INTERNAL = 0x10,
+ AC_JACK_LOC_SEPARATE = 0x20,
+ AC_JACK_LOC_OTHER = 0x30,
+};
+enum {
+ /* external on primary chasis */
+ AC_JACK_LOC_REAR_PANEL = 0x07,
+ AC_JACK_LOC_DRIVE_BAY,
+ /* internal */
+ AC_JACK_LOC_RISER = 0x17,
+ AC_JACK_LOC_HDMI,
+ AC_JACK_LOC_ATAPI,
+ /* others */
+ AC_JACK_LOC_MOBILE_IN = 0x37,
+ AC_JACK_LOC_MOBILE_OUT,
+};
+
+/* Port connectivity (0-3) */
+enum {
+ AC_JACK_PORT_COMPLEX,
+ AC_JACK_PORT_NONE,
+ AC_JACK_PORT_FIXED,
+ AC_JACK_PORT_BOTH,
+};
+
+/* max. connections to a widget */
+#define HDA_MAX_CONNECTIONS 16
+
+/* max. codec address */
+#define HDA_MAX_CODEC_ADDRESS 0x0f
+
+/*
+ * Structures
+ */
+
+struct hda_bus;
+struct hda_codec;
+struct hda_pcm;
+struct hda_pcm_stream;
+struct hda_bus_unsolicited;
+
+/* NID type */
+typedef u16 hda_nid_t;
+
+/* bus operators */
+struct hda_bus_ops {
+ /* send a single command */
+ int (*command)(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int parm);
+ /* get a response from the last command */
+ unsigned int (*get_response)(struct hda_codec *codec);
+ /* free the private data */
+ void (*private_free)(struct hda_bus *);
+};
+
+/* template to pass to the bus constructor */
+struct hda_bus_template {
+ void *private_data;
+ struct pci_dev *pci;
+ const char *modelname;
+ struct hda_bus_ops ops;
+};
+
+/*
+ * codec bus
+ *
+ * each controller needs to creata a hda_bus to assign the accessor.
+ * A hda_bus contains several codecs in the list codec_list.
+ */
+struct hda_bus {
+ snd_card_t *card;
+
+ /* copied from template */
+ void *private_data;
+ struct pci_dev *pci;
+ const char *modelname;
+ struct hda_bus_ops ops;
+
+ /* codec linked list */
+ struct list_head codec_list;
+ struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS]; /* caddr -> codec */
+
+ struct semaphore cmd_mutex;
+
+ /* unsolicited event queue */
+ struct hda_bus_unsolicited *unsol;
+
+ snd_info_entry_t *proc;
+};
+
+/*
+ * codec preset
+ *
+ * Known codecs have the patch to build and set up the controls/PCMs
+ * better than the generic parser.
+ */
+struct hda_codec_preset {
+ unsigned int id;
+ unsigned int mask;
+ unsigned int subs;
+ unsigned int subs_mask;
+ unsigned int rev;
+ const char *name;
+ int (*patch)(struct hda_codec *codec);
+};
+
+/* ops set by the preset patch */
+struct hda_codec_ops {
+ int (*build_controls)(struct hda_codec *codec);
+ int (*build_pcms)(struct hda_codec *codec);
+ int (*init)(struct hda_codec *codec);
+ void (*free)(struct hda_codec *codec);
+ void (*unsol_event)(struct hda_codec *codec, unsigned int res);
+#ifdef CONFIG_PM
+ int (*suspend)(struct hda_codec *codec, pm_message_t state);
+ int (*resume)(struct hda_codec *codec);
+#endif
+};
+
+/* record for amp information cache */
+struct hda_amp_info {
+ u32 key; /* hash key */
+ u32 amp_caps; /* amp capabilities */
+ u16 vol[2]; /* current volume & mute*/
+ u16 status; /* update flag */
+ u16 next; /* next link */
+};
+
+/* PCM callbacks */
+struct hda_pcm_ops {
+ int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec,
+ snd_pcm_substream_t *substream);
+ int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec,
+ snd_pcm_substream_t *substream);
+ int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec,
+ unsigned int stream_tag, unsigned int format,
+ snd_pcm_substream_t *substream);
+ int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec,
+ snd_pcm_substream_t *substream);
+};
+
+/* PCM information for each substream */
+struct hda_pcm_stream {
+ unsigned int substreams; /* number of substreams, 0 = not exist */
+ unsigned int channels_min; /* min. number of channels */
+ unsigned int channels_max; /* max. number of channels */
+ hda_nid_t nid; /* default NID to query rates/formats/bps, or set up */
+ u32 rates; /* supported rates */
+ u64 formats; /* supported formats (SNDRV_PCM_FMTBIT_) */
+ unsigned int maxbps; /* supported max. bit per sample */
+ struct hda_pcm_ops ops;
+};
+
+/* for PCM creation */
+struct hda_pcm {
+ char *name;
+ struct hda_pcm_stream stream[2];
+};
+
+/* codec information */
+struct hda_codec {
+ struct hda_bus *bus;
+ unsigned int addr; /* codec addr*/
+ struct list_head list; /* list point */
+
+ hda_nid_t afg; /* AFG node id */
+
+ /* ids */
+ u32 vendor_id;
+ u32 subsystem_id;
+ u32 revision_id;
+
+ /* detected preset */
+ const struct hda_codec_preset *preset;
+
+ /* set by patch */
+ struct hda_codec_ops patch_ops;
+
+ /* resume phase - all controls should update even if
+ * the values are not changed
+ */
+ unsigned int in_resume;
+
+ /* PCM to create, set by patch_ops.build_pcms callback */
+ unsigned int num_pcms;
+ struct hda_pcm *pcm_info;
+
+ /* codec specific info */
+ void *spec;
+
+ /* hash for amp access */
+ u16 amp_hash[32];
+ int num_amp_entries;
+ struct hda_amp_info amp_info[128]; /* big enough? */
+
+ struct semaphore spdif_mutex;
+ unsigned int spdif_status; /* IEC958 status bits */
+ unsigned short spdif_ctls; /* SPDIF control bits */
+ unsigned int spdif_in_enable; /* SPDIF input enable? */
+};
+
+/* direction */
+enum {
+ HDA_INPUT, HDA_OUTPUT
+};
+
+
+/*
+ * constructors
+ */
+int snd_hda_bus_new(snd_card_t *card, const struct hda_bus_template *temp,
+ struct hda_bus **busp);
+int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
+ struct hda_codec **codecp);
+
+/*
+ * low level functions
+ */
+unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int parm);
+int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int parm);
+#define snd_hda_param_read(codec, nid, param) snd_hda_codec_read(codec, nid, 0, AC_VERB_PARAMETERS, param)
+int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *start_id);
+int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid, hda_nid_t *conn_list, int max_conns);
+
+struct hda_verb {
+ hda_nid_t nid;
+ u32 verb;
+ u32 param;
+};
+
+void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq);
+
+/* unsolicited event */
+int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex);
+
+/*
+ * Mixer
+ */
+int snd_hda_build_controls(struct hda_bus *bus);
+
+/*
+ * PCM
+ */
+int snd_hda_build_pcms(struct hda_bus *bus);
+void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid, u32 stream_tag,
+ int channel_id, int format);
+unsigned int snd_hda_calc_stream_format(unsigned int rate, unsigned int channels,
+ unsigned int format, unsigned int maxbps);
+int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+ u32 *ratesp, u64 *formatsp, unsigned int *bpsp);
+int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
+ unsigned int format);
+
+/*
+ * Misc
+ */
+void snd_hda_get_codec_name(struct hda_codec *codec, char *name, int namelen);
+
+/*
+ * power management
+ */
+#ifdef CONFIG_PM
+int snd_hda_suspend(struct hda_bus *bus, pm_message_t state);
+int snd_hda_resume(struct hda_bus *bus);
+#endif
+
+#endif /* __SOUND_HDA_CODEC_H */
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c
new file mode 100644
index 000000000000..69f7b6c4cf83
--- /dev/null
+++ b/sound/pci/hda/hda_generic.c
@@ -0,0 +1,906 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic widget tree parser
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+/* widget node for parsing */
+struct hda_gnode {
+ hda_nid_t nid; /* NID of this widget */
+ unsigned short nconns; /* number of input connections */
+ hda_nid_t conn_list[HDA_MAX_CONNECTIONS]; /* input connections */
+ unsigned int wid_caps; /* widget capabilities */
+ unsigned char type; /* widget type */
+ unsigned char pin_ctl; /* pin controls */
+ unsigned char checked; /* the flag indicates that the node is already parsed */
+ unsigned int pin_caps; /* pin widget capabilities */
+ unsigned int def_cfg; /* default configuration */
+ unsigned int amp_out_caps; /* AMP out capabilities */
+ unsigned int amp_in_caps; /* AMP in capabilities */
+ struct list_head list;
+};
+
+/* pathc-specific record */
+struct hda_gspec {
+ struct hda_gnode *dac_node; /* DAC node */
+ struct hda_gnode *out_pin_node; /* Output pin (Line-Out) node */
+ struct hda_gnode *pcm_vol_node; /* Node for PCM volume */
+ unsigned int pcm_vol_index; /* connection of PCM volume */
+
+ struct hda_gnode *adc_node; /* ADC node */
+ struct hda_gnode *cap_vol_node; /* Node for capture volume */
+ unsigned int cur_cap_src; /* current capture source */
+ struct hda_input_mux input_mux;
+ char cap_labels[HDA_MAX_NUM_INPUTS][16];
+
+ unsigned int def_amp_in_caps;
+ unsigned int def_amp_out_caps;
+
+ struct hda_pcm pcm_rec; /* PCM information */
+
+ struct list_head nid_list; /* list of widgets */
+};
+
+/*
+ * retrieve the default device type from the default config value
+ */
+#define get_defcfg_type(node) (((node)->def_cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT)
+#define get_defcfg_location(node) (((node)->def_cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT)
+
+/*
+ * destructor
+ */
+static void snd_hda_generic_free(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct list_head *p, *n;
+
+ if (! spec)
+ return;
+ /* free all widgets */
+ list_for_each_safe(p, n, &spec->nid_list) {
+ struct hda_gnode *node = list_entry(p, struct hda_gnode, list);
+ kfree(node);
+ }
+ kfree(spec);
+}
+
+
+/*
+ * add a new widget node and read its attributes
+ */
+static int add_new_node(struct hda_codec *codec, struct hda_gspec *spec, hda_nid_t nid)
+{
+ struct hda_gnode *node;
+ int nconns;
+
+ node = kcalloc(1, sizeof(*node), GFP_KERNEL);
+ if (node == NULL)
+ return -ENOMEM;
+ node->nid = nid;
+ nconns = snd_hda_get_connections(codec, nid, node->conn_list, HDA_MAX_CONNECTIONS);
+ if (nconns < 0) {
+ kfree(node);
+ return nconns;
+ }
+ node->nconns = nconns;
+ node->wid_caps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+ node->type = (node->wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+
+ if (node->type == AC_WID_PIN) {
+ node->pin_caps = snd_hda_param_read(codec, node->nid, AC_PAR_PIN_CAP);
+ node->pin_ctl = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ node->def_cfg = snd_hda_codec_read(codec, node->nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+ }
+
+ if (node->wid_caps & AC_WCAP_OUT_AMP) {
+ if (node->wid_caps & AC_WCAP_AMP_OVRD)
+ node->amp_out_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_OUT_CAP);
+ if (! node->amp_out_caps)
+ node->amp_out_caps = spec->def_amp_out_caps;
+ }
+ if (node->wid_caps & AC_WCAP_IN_AMP) {
+ if (node->wid_caps & AC_WCAP_AMP_OVRD)
+ node->amp_in_caps = snd_hda_param_read(codec, node->nid, AC_PAR_AMP_IN_CAP);
+ if (! node->amp_in_caps)
+ node->amp_in_caps = spec->def_amp_in_caps;
+ }
+ list_add_tail(&node->list, &spec->nid_list);
+ return 0;
+}
+
+/*
+ * build the AFG subtree
+ */
+static int build_afg_tree(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ int i, nodes, err;
+ hda_nid_t nid;
+
+ snd_assert(spec, return -EINVAL);
+
+ spec->def_amp_out_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_OUT_CAP);
+ spec->def_amp_in_caps = snd_hda_param_read(codec, codec->afg, AC_PAR_AMP_IN_CAP);
+
+ nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
+ if (! nid || nodes < 0) {
+ printk(KERN_ERR "Invalid AFG subtree\n");
+ return -EINVAL;
+ }
+
+ /* parse all nodes belonging to the AFG */
+ for (i = 0; i < nodes; i++, nid++) {
+ if ((err = add_new_node(codec, spec, nid)) < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+
+/*
+ * look for the node record for the given NID
+ */
+/* FIXME: should avoid the braindead linear search */
+static struct hda_gnode *hda_get_node(struct hda_gspec *spec, hda_nid_t nid)
+{
+ struct list_head *p;
+ struct hda_gnode *node;
+
+ list_for_each(p, &spec->nid_list) {
+ node = list_entry(p, struct hda_gnode, list);
+ if (node->nid == nid)
+ return node;
+ }
+ return NULL;
+}
+
+/*
+ * unmute (and set max vol) the output amplifier
+ */
+static int unmute_output(struct hda_codec *codec, struct hda_gnode *node)
+{
+ unsigned int val, ofs;
+ snd_printdd("UNMUTE OUT: NID=0x%x\n", node->nid);
+ val = (node->amp_out_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ ofs = (node->amp_out_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
+ if (val >= ofs)
+ val -= ofs;
+ val |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
+ val |= AC_AMP_SET_OUTPUT;
+ return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, val);
+}
+
+/*
+ * unmute (and set max vol) the input amplifier
+ */
+static int unmute_input(struct hda_codec *codec, struct hda_gnode *node, unsigned int index)
+{
+ unsigned int val, ofs;
+ snd_printdd("UNMUTE IN: NID=0x%x IDX=0x%x\n", node->nid, index);
+ val = (node->amp_in_caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+ ofs = (node->amp_in_caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT;
+ if (val >= ofs)
+ val -= ofs;
+ val |= AC_AMP_SET_LEFT | AC_AMP_SET_RIGHT;
+ val |= AC_AMP_SET_INPUT;
+ // awk added - fixed to allow unmuting of indexed amps
+ val |= index << AC_AMP_SET_INDEX_SHIFT;
+ return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, val);
+}
+
+/*
+ * select the input connection of the given node.
+ */
+static int select_input_connection(struct hda_codec *codec, struct hda_gnode *node,
+ unsigned int index)
+{
+ snd_printdd("CONNECT: NID=0x%x IDX=0x%x\n", node->nid, index);
+ return snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_CONNECT_SEL, index);
+}
+
+/*
+ * clear checked flag of each node in the node list
+ */
+static void clear_check_flags(struct hda_gspec *spec)
+{
+ struct list_head *p;
+ struct hda_gnode *node;
+
+ list_for_each(p, &spec->nid_list) {
+ node = list_entry(p, struct hda_gnode, list);
+ node->checked = 0;
+ }
+}
+
+/*
+ * parse the output path recursively until reach to an audio output widget
+ *
+ * returns 0 if not found, 1 if found, or a negative error code.
+ */
+static int parse_output_path(struct hda_codec *codec, struct hda_gspec *spec,
+ struct hda_gnode *node)
+{
+ int i, err;
+ struct hda_gnode *child;
+
+ if (node->checked)
+ return 0;
+
+ node->checked = 1;
+ if (node->type == AC_WID_AUD_OUT) {
+ if (node->wid_caps & AC_WCAP_DIGITAL) {
+ snd_printdd("Skip Digital OUT node %x\n", node->nid);
+ return 0;
+ }
+ snd_printdd("AUD_OUT found %x\n", node->nid);
+ if (spec->dac_node) {
+ /* already DAC node is assigned, just unmute & connect */
+ return node == spec->dac_node;
+ }
+ spec->dac_node = node;
+ if (node->wid_caps & AC_WCAP_OUT_AMP) {
+ spec->pcm_vol_node = node;
+ spec->pcm_vol_index = 0;
+ }
+ return 1; /* found */
+ }
+
+ for (i = 0; i < node->nconns; i++) {
+ child = hda_get_node(spec, node->conn_list[i]);
+ if (! child)
+ continue;
+ err = parse_output_path(codec, spec, child);
+ if (err < 0)
+ return err;
+ else if (err > 0) {
+ /* found one,
+ * select the path, unmute both input and output
+ */
+ if (node->nconns > 1)
+ select_input_connection(codec, node, i);
+ unmute_input(codec, node, i);
+ unmute_output(codec, node);
+ if (! spec->pcm_vol_node) {
+ if (node->wid_caps & AC_WCAP_IN_AMP) {
+ spec->pcm_vol_node = node;
+ spec->pcm_vol_index = i;
+ } else if (node->wid_caps & AC_WCAP_OUT_AMP) {
+ spec->pcm_vol_node = node;
+ spec->pcm_vol_index = 0;
+ }
+ }
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Look for the output PIN widget with the given jack type
+ * and parse the output path to that PIN.
+ *
+ * Returns the PIN node when the path to DAC is established.
+ */
+static struct hda_gnode *parse_output_jack(struct hda_codec *codec,
+ struct hda_gspec *spec,
+ int jack_type)
+{
+ struct list_head *p;
+ struct hda_gnode *node;
+ int err;
+
+ list_for_each(p, &spec->nid_list) {
+ node = list_entry(p, struct hda_gnode, list);
+ if (node->type != AC_WID_PIN)
+ continue;
+ /* output capable? */
+ if (! (node->pin_caps & AC_PINCAP_OUT))
+ continue;
+ if (jack_type >= 0) {
+ if (jack_type != get_defcfg_type(node))
+ continue;
+ if (node->wid_caps & AC_WCAP_DIGITAL)
+ continue; /* skip SPDIF */
+ } else {
+ /* output as default? */
+ if (! (node->pin_ctl & AC_PINCTL_OUT_EN))
+ continue;
+ }
+ clear_check_flags(spec);
+ err = parse_output_path(codec, spec, node);
+ if (err < 0)
+ return NULL;
+ else if (err > 0) {
+ /* unmute the PIN output */
+ unmute_output(codec, node);
+ /* set PIN-Out enable */
+ snd_hda_codec_write(codec, node->nid, 0,
+ AC_VERB_SET_PIN_WIDGET_CONTROL,
+ AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN);
+ return node;
+ }
+ }
+ return NULL;
+}
+
+
+/*
+ * parse outputs
+ */
+static int parse_output(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct hda_gnode *node;
+
+ /*
+ * Look for the output PIN widget
+ */
+ /* first, look for the line-out pin */
+ node = parse_output_jack(codec, spec, AC_JACK_LINE_OUT);
+ if (node) /* found, remember the PIN node */
+ spec->out_pin_node = node;
+ /* look for the HP-out pin */
+ node = parse_output_jack(codec, spec, AC_JACK_HP_OUT);
+ if (node) {
+ if (! spec->out_pin_node)
+ spec->out_pin_node = node;
+ }
+
+ if (! spec->out_pin_node) {
+ /* no line-out or HP pins found,
+ * then choose for the first output pin
+ */
+ spec->out_pin_node = parse_output_jack(codec, spec, -1);
+ if (! spec->out_pin_node)
+ snd_printd("hda_generic: no proper output path found\n");
+ }
+
+ return 0;
+}
+
+/*
+ * input MUX
+ */
+
+/* control callbacks */
+static int capture_source_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gspec *spec = codec->spec;
+ return snd_hda_input_mux_info(&spec->input_mux, uinfo);
+}
+
+static int capture_source_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gspec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->cur_cap_src;
+ return 0;
+}
+
+static int capture_source_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct hda_gspec *spec = codec->spec;
+ return snd_hda_input_mux_put(codec, &spec->input_mux, ucontrol,
+ spec->adc_node->nid, &spec->cur_cap_src);
+}
+
+/*
+ * return the string name of the given input PIN widget
+ */
+static const char *get_input_type(struct hda_gnode *node, unsigned int *pinctl)
+{
+ unsigned int location = get_defcfg_location(node);
+ switch (get_defcfg_type(node)) {
+ case AC_JACK_LINE_IN:
+ if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+ return "Front Line";
+ return "Line";
+ case AC_JACK_CD:
+ if (pinctl)
+ *pinctl |= AC_PIN_VREF_GRD;
+ return "CD";
+ case AC_JACK_AUX:
+ if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+ return "Front Aux";
+ return "Aux";
+ case AC_JACK_MIC_IN:
+ if ((location & 0x0f) == AC_JACK_LOC_FRONT)
+ return "Front Mic";
+ return "Mic";
+ case AC_JACK_SPDIF_IN:
+ return "SPDIF";
+ case AC_JACK_DIG_OTHER_IN:
+ return "Digital";
+ }
+ return NULL;
+}
+
+/*
+ * parse the nodes recursively until reach to the input PIN
+ *
+ * returns 0 if not found, 1 if found, or a negative error code.
+ */
+static int parse_adc_sub_nodes(struct hda_codec *codec, struct hda_gspec *spec,
+ struct hda_gnode *node)
+{
+ int i, err;
+ unsigned int pinctl;
+ char *label;
+ const char *type;
+
+ if (node->checked)
+ return 0;
+
+ node->checked = 1;
+ if (node->type != AC_WID_PIN) {
+ for (i = 0; i < node->nconns; i++) {
+ struct hda_gnode *child;
+ child = hda_get_node(spec, node->conn_list[i]);
+ if (! child)
+ continue;
+ err = parse_adc_sub_nodes(codec, spec, child);
+ if (err < 0)
+ return err;
+ if (err > 0) {
+ /* found one,
+ * select the path, unmute both input and output
+ */
+ if (node->nconns > 1)
+ select_input_connection(codec, node, i);
+ unmute_input(codec, node, i);
+ unmute_output(codec, node);
+ return err;
+ }
+ }
+ return 0;
+ }
+
+ /* input capable? */
+ if (! (node->pin_caps & AC_PINCAP_IN))
+ return 0;
+
+ if (node->wid_caps & AC_WCAP_DIGITAL)
+ return 0; /* skip SPDIF */
+
+ if (spec->input_mux.num_items >= HDA_MAX_NUM_INPUTS) {
+ snd_printk(KERN_ERR "hda_generic: Too many items for capture\n");
+ return -EINVAL;
+ }
+
+ pinctl = AC_PINCTL_IN_EN;
+ /* create a proper capture source label */
+ type = get_input_type(node, &pinctl);
+ if (! type) {
+ /* input as default? */
+ if (! (node->pin_ctl & AC_PINCTL_IN_EN))
+ return 0;
+ type = "Input";
+ }
+ label = spec->cap_labels[spec->input_mux.num_items];
+ strcpy(label, type);
+ spec->input_mux.items[spec->input_mux.num_items].label = label;
+
+ /* unmute the PIN external input */
+ unmute_input(codec, node, 0); /* index = 0? */
+ /* set PIN-In enable */
+ snd_hda_codec_write(codec, node->nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl);
+
+ return 1; /* found */
+}
+
+/*
+ * parse input
+ */
+static int parse_input_path(struct hda_codec *codec, struct hda_gnode *adc_node)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct hda_gnode *node;
+ int i, err;
+
+ snd_printdd("AUD_IN = %x\n", adc_node->nid);
+ clear_check_flags(spec);
+
+ // awk added - fixed no recording due to muted widget
+ unmute_input(codec, adc_node, 0);
+
+ /*
+ * check each connection of the ADC
+ * if it reaches to a proper input PIN, add the path as the
+ * input path.
+ */
+ for (i = 0; i < adc_node->nconns; i++) {
+ node = hda_get_node(spec, adc_node->conn_list[i]);
+ if (! node)
+ continue;
+ err = parse_adc_sub_nodes(codec, spec, node);
+ if (err < 0)
+ return err;
+ else if (err > 0) {
+ struct hda_input_mux_item *csrc = &spec->input_mux.items[spec->input_mux.num_items];
+ char *buf = spec->cap_labels[spec->input_mux.num_items];
+ int ocap;
+ for (ocap = 0; ocap < spec->input_mux.num_items; ocap++) {
+ if (! strcmp(buf, spec->cap_labels[ocap])) {
+ /* same label already exists,
+ * put the index number to be unique
+ */
+ sprintf(buf, "%s %d", spec->cap_labels[ocap],
+ spec->input_mux.num_items);
+ }
+ }
+ csrc->index = i;
+ spec->input_mux.num_items++;
+ }
+ }
+
+ if (! spec->input_mux.num_items)
+ return 0; /* no input path found... */
+
+ snd_printdd("[Capture Source] NID=0x%x, #SRC=%d\n", adc_node->nid, spec->input_mux.num_items);
+ for (i = 0; i < spec->input_mux.num_items; i++)
+ snd_printdd(" [%s] IDX=0x%x\n", spec->input_mux.items[i].label,
+ spec->input_mux.items[i].index);
+
+ spec->adc_node = adc_node;
+ return 1;
+}
+
+/*
+ * parse input
+ */
+static int parse_input(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct list_head *p;
+ struct hda_gnode *node;
+ int err;
+
+ /*
+ * At first we look for an audio input widget.
+ * If it reaches to certain input PINs, we take it as the
+ * input path.
+ */
+ list_for_each(p, &spec->nid_list) {
+ node = list_entry(p, struct hda_gnode, list);
+ if (node->wid_caps & AC_WCAP_DIGITAL)
+ continue; /* skip SPDIF */
+ if (node->type == AC_WID_AUD_IN) {
+ err = parse_input_path(codec, node);
+ if (err < 0)
+ return err;
+ else if (err > 0)
+ return 0;
+ }
+ }
+ snd_printd("hda_generic: no proper input path found\n");
+ return 0;
+}
+
+/*
+ * create mixer controls if possible
+ */
+#define DIR_OUT 0x1
+#define DIR_IN 0x2
+
+static int create_mixer(struct hda_codec *codec, struct hda_gnode *node,
+ unsigned int index, const char *type, const char *dir_sfx)
+{
+ char name[32];
+ int err;
+ int created = 0;
+ snd_kcontrol_new_t knew;
+
+ if (type)
+ sprintf(name, "%s %s Switch", type, dir_sfx);
+ else
+ sprintf(name, "%s Switch", dir_sfx);
+ if ((node->wid_caps & AC_WCAP_IN_AMP) &&
+ (node->amp_in_caps & AC_AMPCAP_MUTE)) {
+ knew = (snd_kcontrol_new_t)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT);
+ snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
+ if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
+ return err;
+ created = 1;
+ } else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
+ (node->amp_out_caps & AC_AMPCAP_MUTE)) {
+ knew = (snd_kcontrol_new_t)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT);
+ snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
+ if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
+ return err;
+ created = 1;
+ }
+
+ if (type)
+ sprintf(name, "%s %s Volume", type, dir_sfx);
+ else
+ sprintf(name, "%s Volume", dir_sfx);
+ if ((node->wid_caps & AC_WCAP_IN_AMP) &&
+ (node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) {
+ knew = (snd_kcontrol_new_t)HDA_CODEC_VOLUME(name, node->nid, index, HDA_INPUT);
+ snd_printdd("[%s] NID=0x%x, DIR=IN, IDX=0x%x\n", name, node->nid, index);
+ if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
+ return err;
+ created = 1;
+ } else if ((node->wid_caps & AC_WCAP_OUT_AMP) &&
+ (node->amp_out_caps & AC_AMPCAP_NUM_STEPS)) {
+ knew = (snd_kcontrol_new_t)HDA_CODEC_VOLUME(name, node->nid, 0, HDA_OUTPUT);
+ snd_printdd("[%s] NID=0x%x, DIR=OUT\n", name, node->nid);
+ if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&knew, codec))) < 0)
+ return err;
+ created = 1;
+ }
+
+ return created;
+}
+
+/*
+ * check whether the controls with the given name and direction suffix already exist
+ */
+static int check_existing_control(struct hda_codec *codec, const char *type, const char *dir)
+{
+ snd_ctl_elem_id_t id;
+ memset(&id, 0, sizeof(id));
+ sprintf(id.name, "%s %s Volume", type, dir);
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ if (snd_ctl_find_id(codec->bus->card, &id))
+ return 1;
+ sprintf(id.name, "%s %s Switch", type, dir);
+ id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+ if (snd_ctl_find_id(codec->bus->card, &id))
+ return 1;
+ return 0;
+}
+
+/*
+ * build output mixer controls
+ */
+static int build_output_controls(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ int err;
+
+ err = create_mixer(codec, spec->pcm_vol_node, spec->pcm_vol_index,
+ "PCM", "Playback");
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/* create capture volume/switch */
+static int build_input_controls(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct hda_gnode *adc_node = spec->adc_node;
+ int err;
+
+ if (! adc_node)
+ return 0; /* not found */
+
+ /* create capture volume and switch controls if the ADC has an amp */
+ err = create_mixer(codec, adc_node, 0, NULL, "Capture");
+
+ /* create input MUX if multiple sources are available */
+ if (spec->input_mux.num_items > 1) {
+ static snd_kcontrol_new_t cap_sel = {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = capture_source_info,
+ .get = capture_source_get,
+ .put = capture_source_put,
+ };
+ if ((err = snd_ctl_add(codec->bus->card, snd_ctl_new1(&cap_sel, codec))) < 0)
+ return err;
+ spec->cur_cap_src = 0;
+ select_input_connection(codec, adc_node, spec->input_mux.items[0].index);
+ }
+ return 0;
+}
+
+
+/*
+ * parse the nodes recursively until reach to the output PIN.
+ *
+ * returns 0 - if not found,
+ * 1 - if found, but no mixer is created
+ * 2 - if found and mixer was already created, (just skip)
+ * a negative error code
+ */
+static int parse_loopback_path(struct hda_codec *codec, struct hda_gspec *spec,
+ struct hda_gnode *node, struct hda_gnode *dest_node,
+ const char *type)
+{
+ int i, err;
+
+ if (node->checked)
+ return 0;
+
+ node->checked = 1;
+ if (node == dest_node) {
+ /* loopback connection found */
+ return 1;
+ }
+
+ for (i = 0; i < node->nconns; i++) {
+ struct hda_gnode *child = hda_get_node(spec, node->conn_list[i]);
+ if (! child)
+ continue;
+ err = parse_loopback_path(codec, spec, child, dest_node, type);
+ if (err < 0)
+ return err;
+ else if (err >= 1) {
+ if (err == 1) {
+ err = create_mixer(codec, node, i, type, "Playback");
+ if (err < 0)
+ return err;
+ if (err > 0)
+ return 2; /* ok, created */
+ /* not created, maybe in the lower path */
+ err = 1;
+ }
+ /* connect and unmute */
+ if (node->nconns > 1)
+ select_input_connection(codec, node, i);
+ unmute_input(codec, node, i);
+ unmute_output(codec, node);
+ return err;
+ }
+ }
+ return 0;
+}
+
+/*
+ * parse the tree and build the loopback controls
+ */
+static int build_loopback_controls(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct list_head *p;
+ struct hda_gnode *node;
+ int err;
+ const char *type;
+
+ if (! spec->out_pin_node)
+ return 0;
+
+ list_for_each(p, &spec->nid_list) {
+ node = list_entry(p, struct hda_gnode, list);
+ if (node->type != AC_WID_PIN)
+ continue;
+ /* input capable? */
+ if (! (node->pin_caps & AC_PINCAP_IN))
+ return 0;
+ type = get_input_type(node, NULL);
+ if (type) {
+ if (check_existing_control(codec, type, "Playback"))
+ continue;
+ clear_check_flags(spec);
+ err = parse_loopback_path(codec, spec, spec->out_pin_node,
+ node, type);
+ if (err < 0)
+ return err;
+ if (! err)
+ continue;
+ }
+ }
+ return 0;
+}
+
+/*
+ * build mixer controls
+ */
+static int build_generic_controls(struct hda_codec *codec)
+{
+ int err;
+
+ if ((err = build_input_controls(codec)) < 0 ||
+ (err = build_output_controls(codec)) < 0 ||
+ (err = build_loopback_controls(codec)) < 0)
+ return err;
+
+ return 0;
+}
+
+/*
+ * PCM
+ */
+static struct hda_pcm_stream generic_pcm_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+};
+
+static int build_generic_pcms(struct hda_codec *codec)
+{
+ struct hda_gspec *spec = codec->spec;
+ struct hda_pcm *info = &spec->pcm_rec;
+
+ if (! spec->dac_node && ! spec->adc_node) {
+ snd_printd("hda_generic: no PCM found\n");
+ return 0;
+ }
+
+ codec->num_pcms = 1;
+ codec->pcm_info = info;
+
+ info->name = "HDA Generic";
+ if (spec->dac_node) {
+ info->stream[0] = generic_pcm_playback;
+ info->stream[0].nid = spec->dac_node->nid;
+ }
+ if (spec->adc_node) {
+ info->stream[1] = generic_pcm_playback;
+ info->stream[1].nid = spec->adc_node->nid;
+ }
+
+ return 0;
+}
+
+
+/*
+ */
+static struct hda_codec_ops generic_patch_ops = {
+ .build_controls = build_generic_controls,
+ .build_pcms = build_generic_pcms,
+ .free = snd_hda_generic_free,
+};
+
+/*
+ * the generic parser
+ */
+int snd_hda_parse_generic_codec(struct hda_codec *codec)
+{
+ struct hda_gspec *spec;
+ int err;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL) {
+ printk(KERN_ERR "hda_generic: can't allocate spec\n");
+ return -ENOMEM;
+ }
+ codec->spec = spec;
+ INIT_LIST_HEAD(&spec->nid_list);
+
+ if ((err = build_afg_tree(codec)) < 0)
+ goto error;
+
+ if ((err = parse_input(codec)) < 0 ||
+ (err = parse_output(codec)) < 0)
+ goto error;
+
+ codec->patch_ops = generic_patch_ops;
+
+ return 0;
+
+ error:
+ snd_hda_generic_free(codec);
+ return err;
+}
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
new file mode 100644
index 000000000000..d89647a3d449
--- /dev/null
+++ b/sound/pci/hda/hda_intel.c
@@ -0,0 +1,1449 @@
+/*
+ *
+ * hda_intel.c - Implementation of primary alsa driver code base for Intel HD Audio.
+ *
+ * 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 <sound/driver.h>
+#include <asm/io.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include "hda_codec.h"
+
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static char *model[SNDRV_CARDS];
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for Intel HD audio interface.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for Intel HD audio interface.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable Intel HD audio interface.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Use the given board model.");
+
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Intel, ICH6},"
+ "{Intel, ICH6M},"
+ "{Intel, ICH7}}");
+MODULE_DESCRIPTION("Intel HDA driver");
+
+#define SFX "hda-intel: "
+
+/*
+ * registers
+ */
+#define ICH6_REG_GCAP 0x00
+#define ICH6_REG_VMIN 0x02
+#define ICH6_REG_VMAJ 0x03
+#define ICH6_REG_OUTPAY 0x04
+#define ICH6_REG_INPAY 0x06
+#define ICH6_REG_GCTL 0x08
+#define ICH6_REG_WAKEEN 0x0c
+#define ICH6_REG_STATESTS 0x0e
+#define ICH6_REG_GSTS 0x10
+#define ICH6_REG_INTCTL 0x20
+#define ICH6_REG_INTSTS 0x24
+#define ICH6_REG_WALCLK 0x30
+#define ICH6_REG_SYNC 0x34
+#define ICH6_REG_CORBLBASE 0x40
+#define ICH6_REG_CORBUBASE 0x44
+#define ICH6_REG_CORBWP 0x48
+#define ICH6_REG_CORBRP 0x4A
+#define ICH6_REG_CORBCTL 0x4c
+#define ICH6_REG_CORBSTS 0x4d
+#define ICH6_REG_CORBSIZE 0x4e
+
+#define ICH6_REG_RIRBLBASE 0x50
+#define ICH6_REG_RIRBUBASE 0x54
+#define ICH6_REG_RIRBWP 0x58
+#define ICH6_REG_RINTCNT 0x5a
+#define ICH6_REG_RIRBCTL 0x5c
+#define ICH6_REG_RIRBSTS 0x5d
+#define ICH6_REG_RIRBSIZE 0x5e
+
+#define ICH6_REG_IC 0x60
+#define ICH6_REG_IR 0x64
+#define ICH6_REG_IRS 0x68
+#define ICH6_IRS_VALID (1<<1)
+#define ICH6_IRS_BUSY (1<<0)
+
+#define ICH6_REG_DPLBASE 0x70
+#define ICH6_REG_DPUBASE 0x74
+#define ICH6_DPLBASE_ENABLE 0x1 /* Enable position buffer */
+
+/* SD offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */
+enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 };
+
+/* stream register offsets from stream base */
+#define ICH6_REG_SD_CTL 0x00
+#define ICH6_REG_SD_STS 0x03
+#define ICH6_REG_SD_LPIB 0x04
+#define ICH6_REG_SD_CBL 0x08
+#define ICH6_REG_SD_LVI 0x0c
+#define ICH6_REG_SD_FIFOW 0x0e
+#define ICH6_REG_SD_FIFOSIZE 0x10
+#define ICH6_REG_SD_FORMAT 0x12
+#define ICH6_REG_SD_BDLPL 0x18
+#define ICH6_REG_SD_BDLPU 0x1c
+
+/* PCI space */
+#define ICH6_PCIREG_TCSEL 0x44
+
+/*
+ * other constants
+ */
+
+/* max number of SDs */
+#define MAX_ICH6_DEV 8
+/* max number of fragments - we may use more if allocating more pages for BDL */
+#define AZX_MAX_FRAG (PAGE_SIZE / (MAX_ICH6_DEV * 16))
+/* max buffer size - no h/w limit, you can increase as you like */
+#define AZX_MAX_BUF_SIZE (1024*1024*1024)
+/* max number of PCM devics per card */
+#define AZX_MAX_PCMS 8
+
+/* RIRB int mask: overrun[2], response[0] */
+#define RIRB_INT_RESPONSE 0x01
+#define RIRB_INT_OVERRUN 0x04
+#define RIRB_INT_MASK 0x05
+
+/* STATESTS int mask: SD2,SD1,SD0 */
+#define STATESTS_INT_MASK 0x07
+#define AZX_MAX_CODECS 3
+
+/* SD_CTL bits */
+#define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */
+#define SD_CTL_DMA_START 0x02 /* stream DMA start bit */
+#define SD_CTL_STREAM_TAG_MASK (0xf << 20)
+#define SD_CTL_STREAM_TAG_SHIFT 20
+
+/* SD_CTL and SD_STS */
+#define SD_INT_DESC_ERR 0x10 /* descriptor error interrupt */
+#define SD_INT_FIFO_ERR 0x08 /* FIFO error interrupt */
+#define SD_INT_COMPLETE 0x04 /* completion interrupt */
+#define SD_INT_MASK (SD_INT_DESC_ERR|SD_INT_FIFO_ERR|SD_INT_COMPLETE)
+
+/* SD_STS */
+#define SD_STS_FIFO_READY 0x20 /* FIFO ready */
+
+/* INTCTL and INTSTS */
+#define ICH6_INT_ALL_STREAM 0xff /* all stream interrupts */
+#define ICH6_INT_CTRL_EN 0x40000000 /* controller interrupt enable bit */
+#define ICH6_INT_GLOBAL_EN 0x80000000 /* global interrupt enable bit */
+
+/* GCTL reset bit */
+#define ICH6_GCTL_RESET (1<<0)
+
+/* CORB/RIRB control, read/write pointer */
+#define ICH6_RBCTL_DMA_EN 0x02 /* enable DMA */
+#define ICH6_RBCTL_IRQ_EN 0x01 /* enable IRQ */
+#define ICH6_RBRWP_CLR 0x8000 /* read/write pointer clear */
+/* below are so far hardcoded - should read registers in future */
+#define ICH6_MAX_CORB_ENTRIES 256
+#define ICH6_MAX_RIRB_ENTRIES 256
+
+
+/*
+ * Use CORB/RIRB for communication from/to codecs.
+ * This is the way recommended by Intel (see below).
+ */
+#define USE_CORB_RIRB
+
+/*
+ * Define this if use the position buffer instead of reading SD_LPIB
+ * It's not used as default since SD_LPIB seems to give more accurate position
+ */
+/* #define USE_POSBUF */
+
+/*
+ */
+
+typedef struct snd_azx azx_t;
+typedef struct snd_azx_rb azx_rb_t;
+typedef struct snd_azx_dev azx_dev_t;
+
+struct snd_azx_dev {
+ u32 *bdl; /* virtual address of the BDL */
+ dma_addr_t bdl_addr; /* physical address of the BDL */
+ volatile u32 *posbuf; /* position buffer pointer */
+
+ unsigned int bufsize; /* size of the play buffer in bytes */
+ unsigned int fragsize; /* size of each period in bytes */
+ unsigned int frags; /* number for period in the play buffer */
+ unsigned int fifo_size; /* FIFO size */
+
+ void __iomem *sd_addr; /* stream descriptor pointer */
+
+ u32 sd_int_sta_mask; /* stream int status mask */
+
+ /* pcm support */
+ snd_pcm_substream_t *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 */
+
+ unsigned int opened: 1;
+ unsigned int running: 1;
+};
+
+/* CORB/RIRB */
+struct snd_azx_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; /* number of pending requests */
+ u32 res; /* last read value */
+};
+
+struct snd_azx {
+ snd_card_t *card;
+ struct pci_dev *pci;
+
+ /* pci resources */
+ unsigned long addr;
+ void __iomem *remap_addr;
+ int irq;
+
+ /* locks */
+ spinlock_t reg_lock;
+ struct semaphore open_mutex;
+
+ /* streams */
+ azx_dev_t azx_dev[MAX_ICH6_DEV];
+
+ /* PCM */
+ unsigned int pcm_devs;
+ snd_pcm_t *pcm[AZX_MAX_PCMS];
+
+ /* HD codec */
+ unsigned short codec_mask;
+ struct hda_bus *bus;
+
+ /* CORB/RIRB */
+ azx_rb_t corb;
+ azx_rb_t rirb;
+
+ /* BDL, CORB/RIRB and position buffers */
+ struct snd_dma_buffer bdl;
+ struct snd_dma_buffer rb;
+ struct snd_dma_buffer posbuf;
+};
+
+/*
+ * macros for easy use
+ */
+#define azx_writel(chip,reg,value) \
+ writel(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readl(chip,reg) \
+ readl((chip)->remap_addr + ICH6_REG_##reg)
+#define azx_writew(chip,reg,value) \
+ writew(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readw(chip,reg) \
+ readw((chip)->remap_addr + ICH6_REG_##reg)
+#define azx_writeb(chip,reg,value) \
+ writeb(value, (chip)->remap_addr + ICH6_REG_##reg)
+#define azx_readb(chip,reg) \
+ readb((chip)->remap_addr + ICH6_REG_##reg)
+
+#define azx_sd_writel(dev,reg,value) \
+ writel(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readl(dev,reg) \
+ readl((dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_writew(dev,reg,value) \
+ writew(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readw(dev,reg) \
+ readw((dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_writeb(dev,reg,value) \
+ writeb(value, (dev)->sd_addr + ICH6_REG_##reg)
+#define azx_sd_readb(dev,reg) \
+ readb((dev)->sd_addr + ICH6_REG_##reg)
+
+/* for pcm support */
+#define get_azx_dev(substream) (azx_dev_t*)(substream->runtime->private_data)
+
+/* Get the upper 32bit of the given dma_addr_t
+ * Compiler should optimize and eliminate the code if dma_addr_t is 32bit
+ */
+#define upper_32bit(addr) (sizeof(addr) > 4 ? (u32)((addr) >> 32) : (u32)0)
+
+
+/*
+ * Interface for HD codec
+ */
+
+#ifdef USE_CORB_RIRB
+/*
+ * CORB / RIRB interface
+ */
+static int azx_alloc_cmd_io(azx_t *chip)
+{
+ int err;
+
+ /* single page (at least 4096 bytes) must suffice for both ringbuffes */
+ err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+ PAGE_SIZE, &chip->rb);
+ if (err < 0) {
+ snd_printk(KERN_ERR SFX "cannot allocate CORB/RIRB\n");
+ return err;
+ }
+ return 0;
+}
+
+static void azx_init_cmd_io(azx_t *chip)
+{
+ /* 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_32bit(chip->corb.addr));
+
+ /* set the corb write pointer to 0 */
+ azx_writew(chip, CORBWP, 0);
+ /* reset the corb hw read pointer */
+ azx_writew(chip, CORBRP, ICH6_RBRWP_CLR);
+ /* enable corb dma */
+ azx_writeb(chip, CORBCTL, ICH6_RBCTL_DMA_EN);
+
+ /* RIRB set up */
+ chip->rirb.addr = chip->rb.addr + 2048;
+ chip->rirb.buf = (u32 *)(chip->rb.area + 2048);
+ azx_writel(chip, RIRBLBASE, (u32)chip->rirb.addr);
+ azx_writel(chip, RIRBUBASE, upper_32bit(chip->rirb.addr));
+
+ /* reset the rirb hw write pointer */
+ azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR);
+ /* set N=1, get RIRB response interrupt for new entry */
+ azx_writew(chip, RINTCNT, 1);
+ /* enable rirb dma and response irq */
+#ifdef USE_CORB_RIRB
+ azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN);
+#else
+ azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN);
+#endif
+ chip->rirb.rp = chip->rirb.cmds = 0;
+}
+
+static void azx_free_cmd_io(azx_t *chip)
+{
+ /* disable ringbuffer DMAs */
+ azx_writeb(chip, RIRBCTL, 0);
+ azx_writeb(chip, CORBCTL, 0);
+}
+
+/* send a command */
+static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int para)
+{
+ azx_t *chip = codec->bus->private_data;
+ unsigned int wp;
+ u32 val;
+
+ val = (u32)(codec->addr & 0x0f) << 28;
+ val |= (u32)direct << 27;
+ val |= (u32)nid << 20;
+ val |= verb << 8;
+ val |= para;
+
+ /* add command to corb */
+ wp = azx_readb(chip, CORBWP);
+ wp++;
+ wp %= ICH6_MAX_CORB_ENTRIES;
+
+ spin_lock_irq(&chip->reg_lock);
+ chip->rirb.cmds++;
+ chip->corb.buf[wp] = cpu_to_le32(val);
+ 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 */
+static void azx_update_rirb(azx_t *chip)
+{
+ unsigned int rp, wp;
+ 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]);
+ if (res_ex & ICH6_RIRB_EX_UNSOL_EV)
+ snd_hda_queue_unsol_event(chip->bus, res, res_ex);
+ else if (chip->rirb.cmds) {
+ chip->rirb.cmds--;
+ chip->rirb.res = res;
+ }
+ }
+}
+
+/* receive a response */
+static unsigned int azx_get_response(struct hda_codec *codec)
+{
+ azx_t *chip = codec->bus->private_data;
+ int timeout = 50;
+
+ while (chip->rirb.cmds) {
+ if (! --timeout) {
+ snd_printk(KERN_ERR "azx_get_response timeout\n");
+ chip->rirb.rp = azx_readb(chip, RIRBWP);
+ chip->rirb.cmds = 0;
+ return -1;
+ }
+ msleep(1);
+ }
+ return chip->rirb.res; /* the last value */
+}
+
+#else
+/*
+ * 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.
+ */
+
+#define azx_alloc_cmd_io(chip) 0
+#define azx_init_cmd_io(chip)
+#define azx_free_cmd_io(chip)
+
+/* send a command */
+static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
+ unsigned int verb, unsigned int para)
+{
+ azx_t *chip = codec->bus->private_data;
+ u32 val;
+ int timeout = 50;
+
+ val = (u32)(codec->addr & 0x0f) << 28;
+ val |= (u32)direct << 27;
+ val |= (u32)nid << 20;
+ val |= verb << 8;
+ val |= para;
+
+ 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 0;
+ }
+ udelay(1);
+ }
+ snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n", azx_readw(chip, IRS), val);
+ return -EIO;
+}
+
+/* receive a response */
+static unsigned int azx_get_response(struct hda_codec *codec)
+{
+ azx_t *chip = codec->bus->private_data;
+ int timeout = 50;
+
+ while (timeout--) {
+ /* check IRV busy bit */
+ if (azx_readw(chip, IRS) & ICH6_IRS_VALID)
+ return azx_readl(chip, IR);
+ udelay(1);
+ }
+ snd_printd(SFX "get_response timeout: IRS=0x%x\n", azx_readw(chip, IRS));
+ return (unsigned int)-1;
+}
+
+#define azx_update_rirb(chip)
+
+#endif /* USE_CORB_RIRB */
+
+/* reset codec link */
+static int azx_reset(azx_t *chip)
+{
+ int count;
+
+ /* 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 intialize */
+ msleep(1);
+
+ /* check to see if controller is ready */
+ if (! azx_readb(chip, GCTL)) {
+ snd_printd("azx_reset: controller not ready!\n");
+ return -EBUSY;
+ }
+
+ /* detect codecs */
+ if (! chip->codec_mask) {
+ chip->codec_mask = azx_readw(chip, STATESTS);
+ snd_printdd("codec_mask = 0x%x\n", chip->codec_mask);
+ }
+
+ return 0;
+}
+
+
+/*
+ * Lowlevel interface
+ */
+
+/* enable interrupts */
+static void azx_int_enable(azx_t *chip)
+{
+ /* enable controller CIE and GIE */
+ azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) |
+ ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN);
+}
+
+/* disable interrupts */
+static void azx_int_disable(azx_t *chip)
+{
+ int i;
+
+ /* disable interrupts in stream descriptor */
+ for (i = 0; i < MAX_ICH6_DEV; i++) {
+ azx_dev_t *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 */
+static void azx_int_clear(azx_t *chip)
+{
+ int i;
+
+ /* clear stream status */
+ for (i = 0; i < MAX_ICH6_DEV; i++) {
+ azx_dev_t *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 */
+static void azx_stream_start(azx_t *chip, azx_dev_t *azx_dev)
+{
+ /* enable SIE */
+ azx_writeb(chip, INTCTL,
+ azx_readb(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 a stream */
+static void azx_stream_stop(azx_t *chip, azx_dev_t *azx_dev)
+{
+ /* stop DMA */
+ 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 */
+ /* disable SIE */
+ azx_writeb(chip, INTCTL,
+ azx_readb(chip, INTCTL) & ~(1 << azx_dev->index));
+}
+
+
+/*
+ * initialize the chip
+ */
+static void azx_init_chip(azx_t *chip)
+{
+ unsigned char tcsel_reg;
+
+ /* Clear bits 0-2 of PCI register TCSEL (at offset 0x44)
+ * TCSEL == Traffic Class Select Register, which sets PCI express QOS
+ * Ensuring these bits are 0 clears playback static on some HD Audio codecs
+ */
+ pci_read_config_byte (chip->pci, ICH6_PCIREG_TCSEL, &tcsel_reg);
+ pci_write_config_byte(chip->pci, ICH6_PCIREG_TCSEL, tcsel_reg & 0xf8);
+
+ /* reset controller */
+ azx_reset(chip);
+
+ /* initialize interrupts */
+ azx_int_clear(chip);
+ azx_int_enable(chip);
+
+ /* initialize the codec command I/O */
+ azx_init_cmd_io(chip);
+
+#ifdef USE_POSBUF
+ /* program the position buffer */
+ azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr);
+ azx_writel(chip, DPUBASE, upper_32bit(chip->posbuf.addr));
+#endif
+}
+
+
+/*
+ * interrupt handler
+ */
+static irqreturn_t azx_interrupt(int irq, void* dev_id, struct pt_regs *regs)
+{
+ azx_t *chip = dev_id;
+ azx_dev_t *azx_dev;
+ u32 status;
+ int i;
+
+ 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 < MAX_ICH6_DEV; i++) {
+ azx_dev = &chip->azx_dev[i];
+ if (status & azx_dev->sd_int_sta_mask) {
+ azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK);
+ if (azx_dev->substream && azx_dev->running) {
+ spin_unlock(&chip->reg_lock);
+ snd_pcm_period_elapsed(azx_dev->substream);
+ spin_lock(&chip->reg_lock);
+ }
+ }
+ }
+
+ /* clear rirb int */
+ status = azx_readb(chip, RIRBSTS);
+ if (status & RIRB_INT_MASK) {
+ if (status & RIRB_INT_RESPONSE)
+ 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 BDL entries
+ */
+static void azx_setup_periods(azx_dev_t *azx_dev)
+{
+ u32 *bdl = azx_dev->bdl;
+ dma_addr_t dma_addr = azx_dev->substream->runtime->dma_addr;
+ int idx;
+
+ /* reset BDL address */
+ azx_sd_writel(azx_dev, SD_BDLPL, 0);
+ azx_sd_writel(azx_dev, SD_BDLPU, 0);
+
+ /* program the initial BDL entries */
+ for (idx = 0; idx < azx_dev->frags; idx++) {
+ unsigned int off = idx << 2; /* 4 dword step */
+ dma_addr_t addr = dma_addr + idx * azx_dev->fragsize;
+ /* program the address field of the BDL entry */
+ bdl[off] = cpu_to_le32((u32)addr);
+ bdl[off+1] = cpu_to_le32(upper_32bit(addr));
+
+ /* program the size field of the BDL entry */
+ bdl[off+2] = cpu_to_le32(azx_dev->fragsize);
+
+ /* program the IOC to enable interrupt when buffer completes */
+ bdl[off+3] = cpu_to_le32(0x01);
+ }
+}
+
+/*
+ * set up the SD for streaming
+ */
+static int azx_setup_controller(azx_t *chip, azx_dev_t *azx_dev)
+{
+ unsigned char val;
+ int timeout;
+
+ /* make sure the run bit is zero for SD */
+ azx_sd_writeb(azx_dev, SD_CTL, azx_sd_readb(azx_dev, SD_CTL) & ~SD_CTL_DMA_START);
+ /* reset stream */
+ 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)
+ ;
+
+ /* 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_32bit(azx_dev->bdl_addr));
+
+#ifdef USE_POSBUF
+ /* enable the position buffer */
+ if (! (azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
+ azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE);
+#endif
+ /* 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;
+}
+
+
+/*
+ * Codec initialization
+ */
+
+static int __devinit azx_codec_create(azx_t *chip, const char *model)
+{
+ struct hda_bus_template bus_temp;
+ int c, codecs, err;
+
+ memset(&bus_temp, 0, sizeof(bus_temp));
+ bus_temp.private_data = chip;
+ bus_temp.modelname = model;
+ bus_temp.pci = chip->pci;
+ bus_temp.ops.command = azx_send_cmd;
+ bus_temp.ops.get_response = azx_get_response;
+
+ if ((err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus)) < 0)
+ return err;
+
+ codecs = 0;
+ for (c = 0; c < AZX_MAX_CODECS; c++) {
+ if (chip->codec_mask & (1 << c)) {
+ err = snd_hda_codec_new(chip->bus, c, NULL);
+ if (err < 0)
+ continue;
+ codecs++;
+ }
+ }
+ if (! codecs) {
+ snd_printk(KERN_ERR SFX "no codecs initialized\n");
+ return -ENXIO;
+ }
+
+ return 0;
+}
+
+
+/*
+ * PCM support
+ */
+
+/* assign a stream for the PCM */
+static inline azx_dev_t *azx_assign_device(azx_t *chip, int stream)
+{
+ int dev, i;
+ dev = stream == SNDRV_PCM_STREAM_PLAYBACK ? 4 : 0;
+ for (i = 0; i < 4; i++, dev++)
+ if (! chip->azx_dev[dev].opened) {
+ chip->azx_dev[dev].opened = 1;
+ return &chip->azx_dev[dev];
+ }
+ return NULL;
+}
+
+/* release the assigned stream */
+static inline void azx_release_device(azx_dev_t *azx_dev)
+{
+ azx_dev->opened = 0;
+}
+
+static snd_pcm_hardware_t azx_pcm_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_RESUME),
+ .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,
+};
+
+struct azx_pcm {
+ azx_t *chip;
+ struct hda_codec *codec;
+ struct hda_pcm_stream *hinfo[2];
+};
+
+static int azx_pcm_open(snd_pcm_substream_t *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ azx_t *chip = apcm->chip;
+ azx_dev_t *azx_dev;
+ snd_pcm_runtime_t *runtime = substream->runtime;
+ unsigned long flags;
+ int err;
+
+ down(&chip->open_mutex);
+ azx_dev = azx_assign_device(chip, substream->stream);
+ if (azx_dev == NULL) {
+ up(&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);
+ if ((err = hinfo->ops.open(hinfo, apcm->codec, substream)) < 0) {
+ azx_release_device(azx_dev);
+ up(&chip->open_mutex);
+ return err;
+ }
+ 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;
+ up(&chip->open_mutex);
+ return 0;
+}
+
+static int azx_pcm_close(snd_pcm_substream_t *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ azx_t *chip = apcm->chip;
+ azx_dev_t *azx_dev = get_azx_dev(substream);
+ unsigned long flags;
+
+ down(&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);
+ up(&chip->open_mutex);
+ return 0;
+}
+
+static int azx_pcm_hw_params(snd_pcm_substream_t *substream, snd_pcm_hw_params_t *hw_params)
+{
+ return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
+}
+
+static int azx_pcm_hw_free(snd_pcm_substream_t *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ azx_dev_t *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);
+
+ hinfo->ops.cleanup(hinfo, apcm->codec, substream);
+
+ return snd_pcm_lib_free_pages(substream);
+}
+
+static int azx_pcm_prepare(snd_pcm_substream_t *substream)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ azx_t *chip = apcm->chip;
+ azx_dev_t *azx_dev = get_azx_dev(substream);
+ struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream];
+ snd_pcm_runtime_t *runtime = substream->runtime;
+
+ azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream);
+ azx_dev->fragsize = snd_pcm_lib_period_bytes(substream);
+ azx_dev->frags = azx_dev->bufsize / azx_dev->fragsize;
+ azx_dev->format_val = snd_hda_calc_stream_format(runtime->rate,
+ runtime->channels,
+ runtime->format,
+ hinfo->maxbps);
+ if (! azx_dev->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;
+ }
+
+ snd_printdd("azx_pcm_prepare: bufsize=0x%x, fragsize=0x%x, format=0x%x\n",
+ azx_dev->bufsize, azx_dev->fragsize, azx_dev->format_val);
+ azx_setup_periods(azx_dev);
+ 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;
+
+ return hinfo->ops.prepare(hinfo, apcm->codec, azx_dev->stream_tag,
+ azx_dev->format_val, substream);
+}
+
+static int azx_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
+{
+ struct azx_pcm *apcm = snd_pcm_substream_chip(substream);
+ azx_dev_t *azx_dev = get_azx_dev(substream);
+ azx_t *chip = apcm->chip;
+ int err = 0;
+
+ spin_lock(&chip->reg_lock);
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_START:
+ azx_stream_start(chip, azx_dev);
+ azx_dev->running = 1;
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ azx_stream_stop(chip, azx_dev);
+ azx_dev->running = 0;
+ break;
+ default:
+ err = -EINVAL;
+ }
+ spin_unlock(&chip->reg_lock);
+ if (cmd == SNDRV_PCM_TRIGGER_PAUSE_PUSH ||
+ cmd == SNDRV_PCM_TRIGGER_STOP) {
+ int timeout = 5000;
+ while (azx_sd_readb(azx_dev, SD_CTL) & SD_CTL_DMA_START && --timeout)
+ ;
+ }
+ return err;
+}
+
+static snd_pcm_uframes_t azx_pcm_pointer(snd_pcm_substream_t *substream)
+{
+ azx_dev_t *azx_dev = get_azx_dev(substream);
+ unsigned int pos;
+
+#ifdef USE_POSBUF
+ /* use the position buffer */
+ pos = *azx_dev->posbuf;
+#else
+ /* read LPIB */
+ pos = azx_sd_readl(azx_dev, SD_LPIB) + azx_dev->fifo_size;
+#endif
+ if (pos >= azx_dev->bufsize)
+ pos = 0;
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static snd_pcm_ops_t azx_pcm_ops = {
+ .open = azx_pcm_open,
+ .close = azx_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = azx_pcm_hw_params,
+ .hw_free = azx_pcm_hw_free,
+ .prepare = azx_pcm_prepare,
+ .trigger = azx_pcm_trigger,
+ .pointer = azx_pcm_pointer,
+};
+
+static void azx_pcm_free(snd_pcm_t *pcm)
+{
+ kfree(pcm->private_data);
+}
+
+static int __devinit create_codec_pcm(azx_t *chip, struct hda_codec *codec,
+ struct hda_pcm *cpcm, int pcm_dev)
+{
+ int err;
+ snd_pcm_t *pcm;
+ struct azx_pcm *apcm;
+
+ snd_assert(cpcm->stream[0].substreams || cpcm->stream[1].substreams, return -EINVAL);
+ snd_assert(cpcm->name, return -EINVAL);
+
+ err = snd_pcm_new(chip->card, cpcm->name, pcm_dev,
+ cpcm->stream[0].substreams, cpcm->stream[1].substreams,
+ &pcm);
+ if (err < 0)
+ return err;
+ strcpy(pcm->name, cpcm->name);
+ apcm = kmalloc(sizeof(*apcm), GFP_KERNEL);
+ if (apcm == NULL)
+ return -ENOMEM;
+ apcm->chip = chip;
+ apcm->codec = codec;
+ apcm->hinfo[0] = &cpcm->stream[0];
+ apcm->hinfo[1] = &cpcm->stream[1];
+ pcm->private_data = apcm;
+ pcm->private_free = azx_pcm_free;
+ if (cpcm->stream[0].substreams)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &azx_pcm_ops);
+ if (cpcm->stream[1].substreams)
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops);
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+ snd_dma_pci_data(chip->pci),
+ 1024 * 64, 1024 * 128);
+ chip->pcm[pcm_dev] = pcm;
+
+ return 0;
+}
+
+static int __devinit azx_pcm_create(azx_t *chip)
+{
+ struct list_head *p;
+ struct hda_codec *codec;
+ int c, err;
+ int pcm_dev;
+
+ if ((err = snd_hda_build_pcms(chip->bus)) < 0)
+ return err;
+
+ pcm_dev = 0;
+ list_for_each(p, &chip->bus->codec_list) {
+ codec = list_entry(p, struct hda_codec, list);
+ for (c = 0; c < codec->num_pcms; c++) {
+ if (pcm_dev >= AZX_MAX_PCMS) {
+ snd_printk(KERN_ERR SFX "Too many PCMs\n");
+ return -EINVAL;
+ }
+ err = create_codec_pcm(chip, codec, &codec->pcm_info[c], pcm_dev);
+ if (err < 0)
+ return err;
+ pcm_dev++;
+ }
+ }
+ return 0;
+}
+
+/*
+ * mixer creation - all stuff is implemented in hda module
+ */
+static int __devinit azx_mixer_create(azx_t *chip)
+{
+ return snd_hda_build_controls(chip->bus);
+}
+
+
+/*
+ * initialize SD streams
+ */
+static int __devinit azx_init_stream(azx_t *chip)
+{
+ int i;
+
+ /* initialize each stream (aka device)
+ * assign the starting bdl address to each stream (device) and initialize
+ */
+ for (i = 0; i < MAX_ICH6_DEV; i++) {
+ unsigned int off = sizeof(u32) * (i * AZX_MAX_FRAG * 4);
+ azx_dev_t *azx_dev = &chip->azx_dev[i];
+ azx_dev->bdl = (u32 *)(chip->bdl.area + off);
+ azx_dev->bdl_addr = chip->bdl.addr + off;
+#ifdef USE_POSBUF
+ azx_dev->posbuf = (volatile u32 *)(chip->posbuf.area + i * 8);
+#endif
+ /* 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;
+}
+
+
+#ifdef CONFIG_PM
+/*
+ * power management
+ */
+static int azx_suspend(snd_card_t *card, pm_message_t state)
+{
+ azx_t *chip = card->pm_private_data;
+ int i;
+
+ for (i = 0; i < chip->pcm_devs; i++)
+ if (chip->pcm[i])
+ snd_pcm_suspend_all(chip->pcm[i]);
+ snd_hda_suspend(chip->bus, state);
+ azx_free_cmd_io(chip);
+ pci_disable_device(chip->pci);
+ return 0;
+}
+
+static int azx_resume(snd_card_t *card)
+{
+ azx_t *chip = card->pm_private_data;
+
+ pci_enable_device(chip->pci);
+ pci_set_master(chip->pci);
+ azx_init_chip(chip);
+ snd_hda_resume(chip->bus);
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+
+/*
+ * destructor
+ */
+static int azx_free(azx_t *chip)
+{
+ if (chip->remap_addr) {
+ int i;
+
+ for (i = 0; i < MAX_ICH6_DEV; i++)
+ azx_stream_stop(chip, &chip->azx_dev[i]);
+
+ /* 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);
+
+ /* wait a little for interrupts to finish */
+ msleep(1);
+
+ iounmap(chip->remap_addr);
+ }
+
+ if (chip->irq >= 0)
+ free_irq(chip->irq, (void*)chip);
+
+ if (chip->bdl.area)
+ snd_dma_free_pages(&chip->bdl);
+ if (chip->rb.area)
+ snd_dma_free_pages(&chip->rb);
+#ifdef USE_POSBUF
+ if (chip->posbuf.area)
+ snd_dma_free_pages(&chip->posbuf);
+#endif
+ pci_release_regions(chip->pci);
+ pci_disable_device(chip->pci);
+ kfree(chip);
+
+ return 0;
+}
+
+static int azx_dev_free(snd_device_t *device)
+{
+ return azx_free(device->device_data);
+}
+
+/*
+ * constructor
+ */
+static int __devinit azx_create(snd_card_t *card, struct pci_dev *pci, azx_t **rchip)
+{
+ azx_t *chip;
+ int err = 0;
+ static snd_device_ops_t ops = {
+ .dev_free = azx_dev_free,
+ };
+
+ *rchip = NULL;
+
+ if ((err = pci_enable_device(pci)) < 0)
+ return err;
+
+ chip = kcalloc(1, sizeof(*chip), GFP_KERNEL);
+
+ if (NULL == chip) {
+ snd_printk(KERN_ERR SFX "cannot allocate chip\n");
+ pci_disable_device(pci);
+ return -ENOMEM;
+ }
+
+ spin_lock_init(&chip->reg_lock);
+ init_MUTEX(&chip->open_mutex);
+ chip->card = card;
+ chip->pci = pci;
+ chip->irq = -1;
+
+ if ((err = pci_request_regions(pci, "ICH HD audio")) < 0) {
+ kfree(chip);
+ pci_disable_device(pci);
+ return err;
+ }
+
+ chip->addr = pci_resource_start(pci,0);
+ chip->remap_addr = ioremap_nocache(chip->addr, pci_resource_len(pci,0));
+ if (chip->remap_addr == NULL) {
+ snd_printk(KERN_ERR SFX "ioremap error\n");
+ err = -ENXIO;
+ goto errout;
+ }
+
+ if (request_irq(pci->irq, azx_interrupt, SA_INTERRUPT|SA_SHIRQ,
+ "HDA Intel", (void*)chip)) {
+ snd_printk(KERN_ERR SFX "unable to grab IRQ %d\n", pci->irq);
+ err = -EBUSY;
+ goto errout;
+ }
+ chip->irq = pci->irq;
+
+ pci_set_master(pci);
+ synchronize_irq(chip->irq);
+
+ /* allocate memory for the BDL for each stream */
+ if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+ PAGE_SIZE, &chip->bdl)) < 0) {
+ snd_printk(KERN_ERR SFX "cannot allocate BDL\n");
+ goto errout;
+ }
+#ifdef USE_POSBUF
+ /* allocate memory for the position buffer */
+ if ((err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci),
+ MAX_ICH6_DEV * 8, &chip->posbuf)) < 0) {
+ snd_printk(KERN_ERR SFX "cannot allocate posbuf\n");
+ goto errout;
+ }
+#endif
+ /* allocate CORB/RIRB */
+ if ((err = azx_alloc_cmd_io(chip)) < 0)
+ goto errout;
+
+ /* initialize streams */
+ azx_init_stream(chip);
+
+ /* initialize chip */
+ azx_init_chip(chip);
+
+ /* codec detection */
+ if (! chip->codec_mask) {
+ snd_printk(KERN_ERR SFX "no codecs found!\n");
+ err = -ENODEV;
+ goto errout;
+ }
+
+ if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) <0) {
+ snd_printk(KERN_ERR SFX "Error creating device [card]!\n");
+ goto errout;
+ }
+
+ *rchip = chip;
+ return 0;
+
+ errout:
+ azx_free(chip);
+ return err;
+}
+
+static int __devinit azx_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
+{
+ static int dev;
+ snd_card_t *card;
+ azx_t *chip;
+ int err = 0;
+
+ if (dev >= SNDRV_CARDS)
+ return -ENODEV;
+ if (! enable[dev]) {
+ dev++;
+ return -ENOENT;
+ }
+
+ card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0);
+ if (NULL == card) {
+ snd_printk(KERN_ERR SFX "Error creating card!\n");
+ return -ENOMEM;
+ }
+
+ if ((err = azx_create(card, pci, &chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ strcpy(card->driver, "HDA-Intel");
+ strcpy(card->shortname, "HDA Intel");
+ sprintf(card->longname, "%s at 0x%lx irq %i", card->shortname, chip->addr, chip->irq);
+
+ /* create codec instances */
+ if ((err = azx_codec_create(chip, model[dev])) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* create PCM streams */
+ if ((err = azx_pcm_create(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ /* create mixer controls */
+ if ((err = azx_mixer_create(chip)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ snd_card_set_pm_callback(card, azx_suspend, azx_resume, chip);
+ snd_card_set_dev(card, &pci->dev);
+
+ if ((err = snd_card_register(card)) < 0) {
+ snd_card_free(card);
+ return err;
+ }
+
+ pci_set_drvdata(pci, card);
+ dev++;
+
+ return err;
+}
+
+static void __devexit azx_remove(struct pci_dev *pci)
+{
+ snd_card_free(pci_get_drvdata(pci));
+ pci_set_drvdata(pci, NULL);
+}
+
+/* PCI IDs */
+static struct pci_device_id azx_ids[] = {
+ { 0x8086, 0x2668, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH6 */
+ { 0x8086, 0x27d8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* ICH7 */
+ { 0, }
+};
+MODULE_DEVICE_TABLE(pci, azx_ids);
+
+/* pci_driver definition */
+static struct pci_driver driver = {
+ .name = "HDA Intel",
+ .id_table = azx_ids,
+ .probe = azx_probe,
+ .remove = __devexit_p(azx_remove),
+ SND_PCI_PM_CALLBACKS
+};
+
+static int __init alsa_card_azx_init(void)
+{
+ return pci_module_init(&driver);
+}
+
+static void __exit alsa_card_azx_exit(void)
+{
+ pci_unregister_driver(&driver);
+}
+
+module_init(alsa_card_azx_init)
+module_exit(alsa_card_azx_exit)
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
new file mode 100644
index 000000000000..7c7b849875a0
--- /dev/null
+++ b/sound/pci/hda/hda_local.h
@@ -0,0 +1,161 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Local helper functions
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ * 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.
+ */
+
+#ifndef __SOUND_HDA_LOCAL_H
+#define __SOUND_HDA_LOCAL_H
+
+/*
+ * for mixer controls
+ */
+#define HDA_COMPOSE_AMP_VAL(nid,chs,idx,dir) ((nid) | ((chs)<<16) | ((dir)<<18) | ((idx)<<19))
+#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .info = snd_hda_mixer_amp_volume_info, \
+ .get = snd_hda_mixer_amp_volume_get, \
+ .put = snd_hda_mixer_amp_volume_put, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
+#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \
+ HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction)
+#define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
+ { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
+ .info = snd_hda_mixer_amp_switch_info, \
+ .get = snd_hda_mixer_amp_switch_get, \
+ .put = snd_hda_mixer_amp_switch_put, \
+ .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) }
+#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction)
+#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \
+ HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction)
+#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \
+ HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction)
+
+int snd_hda_mixer_amp_volume_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
+int snd_hda_mixer_amp_volume_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
+int snd_hda_mixer_amp_volume_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
+int snd_hda_mixer_amp_switch_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo);
+int snd_hda_mixer_amp_switch_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
+int snd_hda_mixer_amp_switch_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol);
+
+int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid);
+int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
+
+/*
+ * input MUX helper
+ */
+#define HDA_MAX_NUM_INPUTS 8
+struct hda_input_mux_item {
+ const char *label;
+ unsigned int index;
+};
+struct hda_input_mux {
+ unsigned int num_items;
+ struct hda_input_mux_item items[HDA_MAX_NUM_INPUTS];
+};
+
+int snd_hda_input_mux_info(const struct hda_input_mux *imux, snd_ctl_elem_info_t *uinfo);
+int snd_hda_input_mux_put(struct hda_codec *codec, const struct hda_input_mux *imux,
+ snd_ctl_elem_value_t *ucontrol, hda_nid_t nid,
+ unsigned int *cur_val);
+
+/*
+ * Multi-channel / digital-out PCM helper
+ */
+
+enum { HDA_FRONT, HDA_REAR, HDA_CLFE, HDA_SIDE }; /* index for dac_nidx */
+enum { HDA_DIG_NONE, HDA_DIG_EXCLUSIVE, HDA_DIG_ANALOG_DUP }; /* dig_out_used */
+
+struct hda_multi_out {
+ int num_dacs; /* # of DACs, must be more than 1 */
+ hda_nid_t *dac_nids; /* DAC list */
+ hda_nid_t hp_nid; /* optional DAC for HP, 0 when not exists */
+ hda_nid_t dig_out_nid; /* digital out audio widget */
+ int max_channels; /* currently supported analog channels */
+ int dig_out_used; /* current usage of digital out (HDA_DIG_XXX) */
+};
+
+int snd_hda_multi_out_dig_open(struct hda_codec *codec, struct hda_multi_out *mout);
+int snd_hda_multi_out_dig_close(struct hda_codec *codec, struct hda_multi_out *mout);
+int snd_hda_multi_out_analog_open(struct hda_codec *codec, struct hda_multi_out *mout,
+ snd_pcm_substream_t *substream);
+int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, struct hda_multi_out *mout,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream);
+int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, struct hda_multi_out *mout);
+
+/*
+ * generic codec parser
+ */
+int snd_hda_parse_generic_codec(struct hda_codec *codec);
+
+/*
+ * generic proc interface
+ */
+#ifdef CONFIG_PROC_FS
+int snd_hda_codec_proc_new(struct hda_codec *codec);
+#else
+static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; }
+#endif
+
+/*
+ * Misc
+ */
+struct hda_board_config {
+ const char *modelname;
+ int config;
+ unsigned short pci_vendor;
+ unsigned short pci_device;
+};
+
+int snd_hda_check_board_config(struct hda_codec *codec, struct hda_board_config *tbl);
+int snd_hda_add_new_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew);
+
+/*
+ * power management
+ */
+#ifdef CONFIG_PM
+int snd_hda_resume_ctls(struct hda_codec *codec, snd_kcontrol_new_t *knew);
+int snd_hda_resume_spdif_out(struct hda_codec *codec);
+int snd_hda_resume_spdif_in(struct hda_codec *codec);
+#endif
+
+/*
+ * unsolicited event handler
+ */
+
+#define HDA_UNSOL_QUEUE_SIZE 64
+
+struct hda_bus_unsolicited {
+ /* ring buffer */
+ u32 queue[HDA_UNSOL_QUEUE_SIZE * 2];
+ unsigned int rp, wp;
+
+ /* workqueue */
+ struct workqueue_struct *workq;
+ struct work_struct work;
+};
+
+#endif /* __SOUND_HDA_LOCAL_H */
diff --git a/sound/pci/hda/hda_patch.h b/sound/pci/hda/hda_patch.h
new file mode 100644
index 000000000000..cf6abce42bc9
--- /dev/null
+++ b/sound/pci/hda/hda_patch.h
@@ -0,0 +1,17 @@
+/*
+ * HDA Patches - included by hda_codec.c
+ */
+
+/* Realtek codecs */
+extern struct hda_codec_preset snd_hda_preset_realtek[];
+/* C-Media codecs */
+extern struct hda_codec_preset snd_hda_preset_cmedia[];
+/* Analog Devices codecs */
+extern struct hda_codec_preset snd_hda_preset_analog[];
+
+static const struct hda_codec_preset *hda_preset_tables[] = {
+ snd_hda_preset_realtek,
+ snd_hda_preset_cmedia,
+ snd_hda_preset_analog,
+ NULL
+};
diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c
new file mode 100644
index 000000000000..4d5db7faad8d
--- /dev/null
+++ b/sound/pci/hda/hda_proc.c
@@ -0,0 +1,298 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * Generic proc interface
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+
+static const char *get_wid_type_name(unsigned int wid_value)
+{
+ static char *names[16] = {
+ [AC_WID_AUD_OUT] = "Audio Output",
+ [AC_WID_AUD_IN] = "Audio Input",
+ [AC_WID_AUD_MIX] = "Audio Mixer",
+ [AC_WID_AUD_SEL] = "Audio Selector",
+ [AC_WID_PIN] = "Pin Complex",
+ [AC_WID_POWER] = "Power Widget",
+ [AC_WID_VOL_KNB] = "Volume Knob Widget",
+ [AC_WID_BEEP] = "Beep Generator Widget",
+ [AC_WID_VENDOR] = "Vendor Defined Widget",
+ };
+ wid_value &= 0xf;
+ if (names[wid_value])
+ return names[wid_value];
+ else
+ return "UNKOWN Widget";
+}
+
+static void print_amp_caps(snd_info_buffer_t *buffer,
+ struct hda_codec *codec, hda_nid_t nid, int dir)
+{
+ unsigned int caps;
+ if (dir == HDA_OUTPUT)
+ caps = snd_hda_param_read(codec, nid, AC_PAR_AMP_OUT_CAP);
+ else
+ caps = snd_hda_param_read(codec, nid, AC_PAR_AMP_IN_CAP);
+ if (caps == -1 || caps == 0) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, mute=%x\n",
+ caps & AC_AMPCAP_OFFSET,
+ (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
+ (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
+ (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
+}
+
+static void print_amp_vals(snd_info_buffer_t *buffer,
+ struct hda_codec *codec, hda_nid_t nid,
+ int dir, int stereo)
+{
+ unsigned int val;
+ if (stereo) {
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_LEFT |
+ (dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT :
+ AC_AMP_GET_INPUT));
+ snd_iprintf(buffer, "0x%02x ", val);
+ }
+ val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_AMP_GAIN_MUTE,
+ AC_AMP_GET_RIGHT |
+ (dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT :
+ AC_AMP_GET_INPUT));
+ snd_iprintf(buffer, "0x%02x\n", val);
+}
+
+static void print_pcm_caps(snd_info_buffer_t *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ unsigned int pcm = snd_hda_param_read(codec, nid, AC_PAR_PCM);
+ unsigned int stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
+ if (pcm == -1 || stream == -1) {
+ snd_iprintf(buffer, "N/A\n");
+ return;
+ }
+ snd_iprintf(buffer, "rates 0x%03x, bits 0x%02x, types 0x%x\n",
+ pcm & AC_SUPPCM_RATES, (pcm >> 16) & 0xff, stream & 0xf);
+}
+
+static const char *get_jack_location(u32 cfg)
+{
+ static char *bases[7] = {
+ "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
+ };
+ static unsigned char specials_idx[] = {
+ 0x07, 0x08,
+ 0x17, 0x18, 0x19,
+ 0x37, 0x38
+ };
+ static char *specials[] = {
+ "Rear Panel", "Drive Bar",
+ "Riser", "HDMI", "ATAPI",
+ "Mobile-In", "Mobile-Out"
+ };
+ int i;
+ cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+ if ((cfg & 0x0f) < 7)
+ return bases[cfg & 0x0f];
+ for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
+ if (cfg == specials_idx[i])
+ return specials[i];
+ }
+ return "UNKNOWN";
+}
+
+static const char *get_jack_connection(u32 cfg)
+{
+ static char *names[16] = {
+ "Unknown", "1/8", "1/4", "ATAPI",
+ "RCA", "Optical","Digital", "Analog",
+ "DIN", "XLR", "RJ11", "Comb",
+ NULL, NULL, NULL, "Other"
+ };
+ cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+static const char *get_jack_color(u32 cfg)
+{
+ static char *names[16] = {
+ "Unknown", "Black", "Grey", "Blue",
+ "Green", "Red", "Orange", "Yellow",
+ "Purple", "Pink", NULL, NULL,
+ NULL, NULL, "White", "Other",
+ };
+ cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
+ if (names[cfg])
+ return names[cfg];
+ else
+ return "UNKNOWN";
+}
+
+static void print_pin_caps(snd_info_buffer_t *buffer,
+ struct hda_codec *codec, hda_nid_t nid)
+{
+ static char *jack_types[16] = {
+ "Line Out", "Speaker", "HP Out", "CD",
+ "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
+ "Line In", "Aux", "Mic", "Telephony",
+ "SPDIF In", "Digitial In", "Reserved", "Other"
+ };
+ static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" };
+ unsigned int caps;
+
+ caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
+ snd_iprintf(buffer, " Pincap 0x08%x:", caps);
+ if (caps & AC_PINCAP_IN)
+ snd_iprintf(buffer, " IN");
+ if (caps & AC_PINCAP_OUT)
+ snd_iprintf(buffer, " OUT");
+ if (caps & AC_PINCAP_HP_DRV)
+ snd_iprintf(buffer, " HP");
+ snd_iprintf(buffer, "\n");
+ caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
+ snd_iprintf(buffer, " Pin Default 0x%08x: %s at %s %s\n", caps,
+ jack_types[(caps & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT],
+ jack_locations[(caps >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3],
+ get_jack_location(caps));
+ snd_iprintf(buffer, " Conn = %s, Color = %s\n",
+ get_jack_connection(caps),
+ get_jack_color(caps));
+}
+
+
+static void print_codec_info(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
+{
+ struct hda_codec *codec = entry->private_data;
+ char buf[32];
+ hda_nid_t nid;
+ int i, nodes;
+
+ snd_hda_get_codec_name(codec, buf, sizeof(buf));
+ snd_iprintf(buffer, "Codec: %s\n", buf);
+ snd_iprintf(buffer, "Address: %d\n", codec->addr);
+ snd_iprintf(buffer, "Vendor Id: 0x%x\n", codec->vendor_id);
+ snd_iprintf(buffer, "Subsystem Id: 0x%x\n", codec->subsystem_id);
+ snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
+ snd_iprintf(buffer, "Default PCM: ");
+ print_pcm_caps(buffer, codec, codec->afg);
+ snd_iprintf(buffer, "Default Amp-In caps: ");
+ print_amp_caps(buffer, codec, codec->afg, HDA_INPUT);
+ snd_iprintf(buffer, "Default Amp-Out caps: ");
+ print_amp_caps(buffer, codec, codec->afg, HDA_OUTPUT);
+
+ nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
+ if (! nid || nodes < 0) {
+ snd_iprintf(buffer, "Invalid AFG subtree\n");
+ return;
+ }
+ for (i = 0; i < nodes; i++, nid++) {
+ unsigned int wid_caps = snd_hda_param_read(codec, nid,
+ AC_PAR_AUDIO_WIDGET_CAP);
+ unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+ snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
+ get_wid_type_name(wid_type), wid_caps);
+ if (wid_caps & AC_WCAP_STEREO)
+ snd_iprintf(buffer, " Stereo");
+ else
+ snd_iprintf(buffer, " Mono");
+ if (wid_caps & AC_WCAP_DIGITAL)
+ snd_iprintf(buffer, " Digital");
+ if (wid_caps & AC_WCAP_IN_AMP)
+ snd_iprintf(buffer, " Amp-In");
+ if (wid_caps & AC_WCAP_OUT_AMP)
+ snd_iprintf(buffer, " Amp-Out");
+ snd_iprintf(buffer, "\n");
+
+ if (wid_caps & AC_WCAP_IN_AMP) {
+ snd_iprintf(buffer, " Amp-In caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_INPUT);
+ snd_iprintf(buffer, " Amp-In vals: ");
+ print_amp_vals(buffer, codec, nid, HDA_INPUT,
+ wid_caps & AC_WCAP_STEREO);
+ }
+ if (wid_caps & AC_WCAP_OUT_AMP) {
+ snd_iprintf(buffer, " Amp-Out caps: ");
+ print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
+ snd_iprintf(buffer, " Amp-Out vals: ");
+ print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
+ wid_caps & AC_WCAP_STEREO);
+ }
+
+ if (wid_type == AC_WID_PIN) {
+ unsigned int pinctls;
+ print_pin_caps(buffer, codec, nid);
+ pinctls = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0);
+ snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
+ if (pinctls & AC_PINCTL_IN_EN)
+ snd_iprintf(buffer, " IN");
+ if (pinctls & AC_PINCTL_OUT_EN)
+ snd_iprintf(buffer, " OUT");
+ if (pinctls & AC_PINCTL_HP_EN)
+ snd_iprintf(buffer, " HP");
+ snd_iprintf(buffer, "\n");
+ }
+
+ if ((wid_type == AC_WID_AUD_OUT || wid_type == AC_WID_AUD_IN) &&
+ (wid_caps & AC_WCAP_FORMAT_OVRD)) {
+ snd_iprintf(buffer, " PCM: ");
+ print_pcm_caps(buffer, codec, nid);
+ }
+
+ if (wid_caps & AC_WCAP_CONN_LIST) {
+ hda_nid_t conn[HDA_MAX_CONNECTIONS];
+ int c, conn_len;
+ conn_len = snd_hda_get_connections(codec, nid, conn,
+ HDA_MAX_CONNECTIONS);
+ snd_iprintf(buffer, " Connection: %d\n", conn_len);
+ snd_iprintf(buffer, " ");
+ for (c = 0; c < conn_len; c++)
+ snd_iprintf(buffer, " 0x%02x", conn[c]);
+ snd_iprintf(buffer, "\n");
+ }
+ }
+}
+
+/*
+ * create a proc read
+ */
+int snd_hda_codec_proc_new(struct hda_codec *codec)
+{
+ char name[32];
+ snd_info_entry_t *entry;
+ int err;
+
+ snprintf(name, sizeof(name), "codec#%d", codec->addr);
+ err = snd_card_proc_new(codec->bus->card, name, &entry);
+ if (err < 0)
+ return err;
+
+ snd_info_set_text_ops(entry, codec, 32 * 1024, print_codec_info);
+ return 0;
+}
+
diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c
new file mode 100644
index 000000000000..75d23849f71a
--- /dev/null
+++ b/sound/pci/hda/patch_analog.c
@@ -0,0 +1,445 @@
+/*
+ * HD audio interface patch for AD1986A
+ *
+ * Copyright (c) 2005 Takashi Iwai <tiwai@suse.de>
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+struct ad1986a_spec {
+ struct semaphore amp_mutex; /* PCM volume/mute control mutex */
+ struct hda_multi_out multiout; /* playback */
+ unsigned int cur_mux; /* capture source */
+ struct hda_pcm pcm_rec[2]; /* PCM information */
+};
+
+#define AD1986A_SPDIF_OUT 0x02
+#define AD1986A_FRONT_DAC 0x03
+#define AD1986A_SURR_DAC 0x04
+#define AD1986A_CLFE_DAC 0x05
+#define AD1986A_ADC 0x06
+
+static hda_nid_t ad1986a_dac_nids[3] = {
+ AD1986A_FRONT_DAC, AD1986A_SURR_DAC, AD1986A_CLFE_DAC
+};
+
+static struct hda_input_mux ad1986a_capture_source = {
+ .num_items = 7,
+ .items = {
+ { "Mic", 0x0 },
+ { "CD", 0x1 },
+ { "Aux", 0x3 },
+ { "Line", 0x4 },
+ { "Mix", 0x5 },
+ { "Mono", 0x6 },
+ { "Phone", 0x7 },
+ },
+};
+
+/*
+ * PCM control
+ *
+ * bind volumes/mutes of 3 DACs as a single PCM control for simplicity
+ */
+
+#define ad1986a_pcm_amp_vol_info snd_hda_mixer_amp_volume_info
+
+static int ad1986a_pcm_amp_vol_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *ad = codec->spec;
+
+ down(&ad->amp_mutex);
+ snd_hda_mixer_amp_volume_get(kcontrol, ucontrol);
+ up(&ad->amp_mutex);
+ return 0;
+}
+
+static int ad1986a_pcm_amp_vol_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *ad = codec->spec;
+ int i, change = 0;
+
+ down(&ad->amp_mutex);
+ for (i = 0; i < ARRAY_SIZE(ad1986a_dac_nids); i++) {
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(ad1986a_dac_nids[i], 3, 0, HDA_OUTPUT);
+ change |= snd_hda_mixer_amp_volume_put(kcontrol, ucontrol);
+ }
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT);
+ up(&ad->amp_mutex);
+ return change;
+}
+
+#define ad1986a_pcm_amp_sw_info snd_hda_mixer_amp_volume_info
+
+static int ad1986a_pcm_amp_sw_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *ad = codec->spec;
+
+ down(&ad->amp_mutex);
+ snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
+ up(&ad->amp_mutex);
+ return 0;
+}
+
+static int ad1986a_pcm_amp_sw_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *ad = codec->spec;
+ int i, change = 0;
+
+ down(&ad->amp_mutex);
+ for (i = 0; i < ARRAY_SIZE(ad1986a_dac_nids); i++) {
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(ad1986a_dac_nids[i], 3, 0, HDA_OUTPUT);
+ change |= snd_hda_mixer_amp_switch_put(kcontrol, ucontrol);
+ }
+ kcontrol->private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT);
+ up(&ad->amp_mutex);
+ return change;
+}
+
+/*
+ * input MUX handling
+ */
+static int ad1986a_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ return snd_hda_input_mux_info(&ad1986a_capture_source, uinfo);
+}
+
+static int ad1986a_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->cur_mux;
+ return 0;
+}
+
+static int ad1986a_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct ad1986a_spec *spec = codec->spec;
+
+ return snd_hda_input_mux_put(codec, &ad1986a_capture_source, ucontrol,
+ AD1986A_ADC, &spec->cur_mux);
+}
+
+/*
+ * mixers
+ */
+static snd_kcontrol_new_t ad1986a_mixers[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Volume",
+ .info = ad1986a_pcm_amp_vol_info,
+ .get = ad1986a_pcm_amp_vol_get,
+ .put = ad1986a_pcm_amp_vol_put,
+ .private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT)
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "PCM Playback Switch",
+ .info = ad1986a_pcm_amp_sw_info,
+ .get = ad1986a_pcm_amp_sw_get,
+ .put = ad1986a_pcm_amp_sw_put,
+ .private_value = HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT)
+ },
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x1b, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x1c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x1c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x1d, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x1d, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x1d, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x1d, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Headphone Playback Volume", 0x1a, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Line Playback Volume", 0x17, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Line Playback Switch", 0x17, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Aux Playback Volume", 0x16, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Aux Playback Switch", 0x16, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Mic Playback Volume", 0x13, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Mic Playback Switch", 0x13, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x18, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x18, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Mono Playback Volume", 0x1e, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Mono Playback Switch", 0x1e, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_OUTPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = ad1986a_mux_enum_info,
+ .get = ad1986a_mux_enum_get,
+ .put = ad1986a_mux_enum_put,
+ },
+ HDA_CODEC_MUTE("Stereo Downmix Switch", 0x09, 0x0, HDA_OUTPUT),
+ { } /* end */
+};
+
+/*
+ * initialization verbs
+ */
+static struct hda_verb ad1986a_init_verbs[] = {
+ /* Front, Surround, CLFE DAC; mute as default */
+ {0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* Downmix - off */
+ {0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* HP, Line-Out, Surround, CLFE selectors */
+ {0x0a, AC_VERB_SET_CONNECT_SEL, 0x0},
+ {0x0b, AC_VERB_SET_CONNECT_SEL, 0x0},
+ {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0},
+ {0x0d, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Mono selector */
+ {0x0e, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Mic selector: Mic 1/2 pin */
+ {0x0f, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Line-in selector: Line-in */
+ {0x10, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Mic 1/2 swap */
+ {0x11, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Record selector: mic */
+ {0x12, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* Mic, Phone, CD, Aux, Line-In amp; mute as default */
+ {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* PC beep */
+ {0x18, AC_VERB_SET_CONNECT_SEL, 0x0},
+ /* HP, Line-Out, Surround, CLFE, Mono pins; mute as default */
+ {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ { } /* end */
+};
+
+
+static int ad1986a_init(struct hda_codec *codec)
+{
+ snd_hda_sequence_write(codec, ad1986a_init_verbs);
+ return 0;
+}
+
+static int ad1986a_build_controls(struct hda_codec *codec)
+{
+ int err;
+
+ err = snd_hda_add_new_ctls(codec, ad1986a_mixers);
+ if (err < 0)
+ return err;
+ err = snd_hda_create_spdif_out_ctls(codec, AD1986A_SPDIF_OUT);
+ if (err < 0)
+ return err;
+ return 0;
+}
+
+/*
+ * Analog playback callbacks
+ */
+static int ad1986a_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
+}
+
+static int ad1986a_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+ format, substream);
+}
+
+static int ad1986a_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int ad1986a_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int ad1986a_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int ad1986a_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ snd_hda_codec_setup_stream(codec, AD1986A_ADC, stream_tag, 0, format);
+ return 0;
+}
+
+static int ad1986a_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ snd_hda_codec_setup_stream(codec, AD1986A_ADC, 0, 0, 0);
+ return 0;
+}
+
+
+/*
+ */
+static struct hda_pcm_stream ad1986a_pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 6,
+ .nid = AD1986A_FRONT_DAC, /* NID to query formats and rates */
+ .ops = {
+ .open = ad1986a_playback_pcm_open,
+ .prepare = ad1986a_playback_pcm_prepare,
+ .cleanup = ad1986a_playback_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream ad1986a_pcm_analog_capture = {
+ .substreams = 2,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = AD1986A_ADC, /* NID to query formats and rates */
+ .ops = {
+ .prepare = ad1986a_capture_pcm_prepare,
+ .cleanup = ad1986a_capture_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream ad1986a_pcm_digital_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = AD1986A_SPDIF_OUT,
+ .ops = {
+ .open = ad1986a_dig_playback_pcm_open,
+ .close = ad1986a_dig_playback_pcm_close
+ },
+};
+
+static int ad1986a_build_pcms(struct hda_codec *codec)
+{
+ struct ad1986a_spec *spec = codec->spec;
+ struct hda_pcm *info = spec->pcm_rec;
+
+ codec->num_pcms = 2;
+ codec->pcm_info = info;
+
+ info->name = "AD1986A Analog";
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad1986a_pcm_analog_playback;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad1986a_pcm_analog_capture;
+ info++;
+
+ info->name = "AD1986A Digital";
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad1986a_pcm_digital_playback;
+
+ return 0;
+}
+
+static void ad1986a_free(struct hda_codec *codec)
+{
+ kfree(codec->spec);
+}
+
+#ifdef CONFIG_PM
+static int ad1986a_resume(struct hda_codec *codec)
+{
+ ad1986a_init(codec);
+ snd_hda_resume_ctls(codec, ad1986a_mixers);
+ snd_hda_resume_spdif_out(codec);
+ return 0;
+}
+#endif
+
+static struct hda_codec_ops ad1986a_patch_ops = {
+ .build_controls = ad1986a_build_controls,
+ .build_pcms = ad1986a_build_pcms,
+ .init = ad1986a_init,
+ .free = ad1986a_free,
+#ifdef CONFIG_PM
+ .resume = ad1986a_resume,
+#endif
+};
+
+static int patch_ad1986a(struct hda_codec *codec)
+{
+ struct ad1986a_spec *spec;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ init_MUTEX(&spec->amp_mutex);
+ codec->spec = spec;
+
+ spec->multiout.max_channels = 6;
+ spec->multiout.num_dacs = ARRAY_SIZE(ad1986a_dac_nids);
+ spec->multiout.dac_nids = ad1986a_dac_nids;
+ spec->multiout.dig_out_nid = AD1986A_SPDIF_OUT;
+
+ codec->patch_ops = ad1986a_patch_ops;
+
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+struct hda_codec_preset snd_hda_preset_analog[] = {
+ { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a },
+ {} /* terminator */
+};
diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
new file mode 100644
index 000000000000..b7cc8e4bffb7
--- /dev/null
+++ b/sound/pci/hda/patch_cmedia.c
@@ -0,0 +1,621 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for C-Media CMI9880
+ *
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+
+/* board config type */
+enum {
+ CMI_MINIMAL, /* back 3-jack */
+ CMI_MIN_FP, /* back 3-jack + front-panel 2-jack */
+ CMI_FULL, /* back 6-jack + front-panel 2-jack */
+ CMI_FULL_DIG, /* back 6-jack + front-panel 2-jack + digital I/O */
+ CMI_ALLOUT, /* back 5-jack + front-panel 2-jack + digital out */
+};
+
+struct cmi_spec {
+ int board_config;
+ unsigned int surr_switch: 1; /* switchable line,mic */
+ unsigned int no_line_in: 1; /* no line-in (5-jack) */
+ unsigned int front_panel: 1; /* has front-panel 2-jack */
+
+ /* playback */
+ struct hda_multi_out multiout;
+
+ /* capture */
+ hda_nid_t *adc_nids;
+ hda_nid_t dig_in_nid;
+
+ /* capture source */
+ const struct hda_input_mux *input_mux;
+ unsigned int cur_mux[2];
+
+ /* channel mode */
+ unsigned int num_ch_modes;
+ unsigned int cur_ch_mode;
+ const struct cmi_channel_mode *channel_modes;
+
+ struct hda_pcm pcm_rec[2]; /* PCM information */
+};
+
+/*
+ * input MUX
+ */
+static int cmi_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int cmi_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+ return 0;
+}
+
+static int cmi_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+ spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]);
+}
+
+/*
+ * shared line-in, mic for surrounds
+ */
+
+/* 3-stack / 2 channel */
+static struct hda_verb cmi9880_ch2_init[] = {
+ /* set line-in PIN for input */
+ { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* set mic PIN for input, also enable vref */
+ { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* route front PCM (DAC1) to HP */
+ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+ {}
+};
+
+/* 3-stack / 6 channel */
+static struct hda_verb cmi9880_ch6_init[] = {
+ /* set line-in PIN for output */
+ { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* set mic PIN for output */
+ { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* route front PCM (DAC1) to HP */
+ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+ {}
+};
+
+/* 3-stack+front / 8 channel */
+static struct hda_verb cmi9880_ch8_init[] = {
+ /* set line-in PIN for output */
+ { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* set mic PIN for output */
+ { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* route rear-surround PCM (DAC4) to HP */
+ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x03 },
+ {}
+};
+
+struct cmi_channel_mode {
+ unsigned int channels;
+ const struct hda_verb *sequence;
+};
+
+static struct cmi_channel_mode cmi9880_channel_modes[3] = {
+ { 2, cmi9880_ch2_init },
+ { 6, cmi9880_ch6_init },
+ { 8, cmi9880_ch8_init },
+};
+
+static int cmi_ch_mode_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+
+ snd_assert(spec->channel_modes, return -EINVAL);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = spec->num_ch_modes;
+ if (uinfo->value.enumerated.item >= uinfo->value.enumerated.items)
+ uinfo->value.enumerated.item = uinfo->value.enumerated.items - 1;
+ sprintf(uinfo->value.enumerated.name, "%dch",
+ spec->channel_modes[uinfo->value.enumerated.item].channels);
+ return 0;
+}
+
+static int cmi_ch_mode_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+
+ ucontrol->value.enumerated.item[0] = spec->cur_ch_mode;
+ return 0;
+}
+
+static int cmi_ch_mode_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct cmi_spec *spec = codec->spec;
+
+ snd_assert(spec->channel_modes, return -EINVAL);
+ if (ucontrol->value.enumerated.item[0] >= spec->num_ch_modes)
+ ucontrol->value.enumerated.item[0] = spec->num_ch_modes;
+ if (ucontrol->value.enumerated.item[0] == spec->cur_ch_mode &&
+ ! codec->in_resume)
+ return 0;
+
+ spec->cur_ch_mode = ucontrol->value.enumerated.item[0];
+ snd_hda_sequence_write(codec, spec->channel_modes[spec->cur_ch_mode].sequence);
+ spec->multiout.max_channels = spec->channel_modes[spec->cur_ch_mode].channels;
+ return 1;
+}
+
+/*
+ */
+static snd_kcontrol_new_t cmi9880_basic_mixer[] = {
+ /* CMI9880 has no playback volumes! */
+ HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), /* front */
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x04, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x05, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x05, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Side Playback Switch", 0x06, 0x0, HDA_OUTPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* The multiple "Capture Source" controls confuse alsamixer
+ * So call somewhat different..
+ * FIXME: the controls appear in the "playback" view!
+ */
+ /* .name = "Capture Source", */
+ .name = "Input Source",
+ .count = 2,
+ .info = cmi_mux_enum_info,
+ .get = cmi_mux_enum_get,
+ .put = cmi_mux_enum_put,
+ },
+ HDA_CODEC_VOLUME("Capture Volume", 0x08, 0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x08, 0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0, HDA_INPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x23, 0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x23, 0, HDA_OUTPUT),
+ { } /* end */
+};
+
+/*
+ * shared I/O pins
+ */
+static snd_kcontrol_new_t cmi9880_ch_mode_mixer[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Channel Mode",
+ .info = cmi_ch_mode_info,
+ .get = cmi_ch_mode_get,
+ .put = cmi_ch_mode_put,
+ },
+ { } /* end */
+};
+
+/* AUD-in selections:
+ * 0x0b 0x0c 0x0d 0x0e 0x0f 0x10 0x11 0x1f 0x20
+ */
+static struct hda_input_mux cmi9880_basic_mux = {
+ .num_items = 4,
+ .items = {
+ { "Front Mic", 0x5 },
+ { "Rear Mic", 0x2 },
+ { "Line", 0x1 },
+ { "CD", 0x7 },
+ }
+};
+
+static struct hda_input_mux cmi9880_no_line_mux = {
+ .num_items = 3,
+ .items = {
+ { "Front Mic", 0x5 },
+ { "Rear Mic", 0x2 },
+ { "CD", 0x7 },
+ }
+};
+
+/* front, rear, clfe, rear_surr */
+static hda_nid_t cmi9880_dac_nids[4] = {
+ 0x03, 0x04, 0x05, 0x06
+};
+/* ADC0, ADC1 */
+static hda_nid_t cmi9880_adc_nids[2] = {
+ 0x08, 0x09
+};
+
+#define CMI_DIG_OUT_NID 0x07
+#define CMI_DIG_IN_NID 0x0a
+
+/*
+ */
+static struct hda_verb cmi9880_basic_init[] = {
+ /* port-D for line out (rear panel) */
+ { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-E for HP out (front panel) */
+ { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* route front PCM to HP */
+ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+ /* port-A for surround (rear panel) */
+ { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-G for CLFE (rear panel) */
+ { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-H for side (rear panel) */
+ { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-C for line-in (rear panel) */
+ { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* port-B for mic-in (rear panel) with vref */
+ { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* port-F for mic-in (front panel) with vref */
+ { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* CD-in */
+ { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* route front mic to ADC1/2 */
+ { 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
+ { 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
+ {} /* terminator */
+};
+
+static struct hda_verb cmi9880_allout_init[] = {
+ /* port-D for line out (rear panel) */
+ { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-E for HP out (front panel) */
+ { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* route front PCM to HP */
+ { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 },
+ /* port-A for side (rear panel) */
+ { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-G for CLFE (rear panel) */
+ { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-C for surround (rear panel) */
+ { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 },
+ /* port-B for mic-in (rear panel) with vref */
+ { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* port-F for mic-in (front panel) with vref */
+ { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* CD-in */
+ { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* route front mic to ADC1/2 */
+ { 0x08, AC_VERB_SET_CONNECT_SEL, 0x05 },
+ { 0x09, AC_VERB_SET_CONNECT_SEL, 0x05 },
+ {} /* terminator */
+};
+
+/*
+ */
+static int cmi9880_build_controls(struct hda_codec *codec)
+{
+ struct cmi_spec *spec = codec->spec;
+ int err;
+
+ err = snd_hda_add_new_ctls(codec, cmi9880_basic_mixer);
+ if (err < 0)
+ return err;
+ if (spec->surr_switch) {
+ err = snd_hda_add_new_ctls(codec, cmi9880_ch_mode_mixer);
+ if (err < 0)
+ return err;
+ }
+ if (spec->multiout.dig_out_nid) {
+ err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+ if (err < 0)
+ return err;
+ }
+ if (spec->dig_in_nid) {
+ err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+static int cmi9880_init(struct hda_codec *codec)
+{
+ struct cmi_spec *spec = codec->spec;
+ if (spec->board_config == CMI_ALLOUT)
+ snd_hda_sequence_write(codec, cmi9880_allout_init);
+ else
+ snd_hda_sequence_write(codec, cmi9880_basic_init);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * resume
+ */
+static int cmi9880_resume(struct hda_codec *codec)
+{
+ struct cmi_spec *spec = codec->spec;
+
+ cmi9880_init(codec);
+ snd_hda_resume_ctls(codec, cmi9880_basic_mixer);
+ if (spec->surr_switch)
+ snd_hda_resume_ctls(codec, cmi9880_ch_mode_mixer);
+ if (spec->multiout.dig_out_nid)
+ snd_hda_resume_spdif_out(codec);
+ if (spec->dig_in_nid)
+ snd_hda_resume_spdif_in(codec);
+
+ return 0;
+}
+#endif
+
+/*
+ * Analog playback callbacks
+ */
+static int cmi9880_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
+}
+
+static int cmi9880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+ format, substream);
+}
+
+static int cmi9880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int cmi9880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int cmi9880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int cmi9880_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+ stream_tag, 0, format);
+ return 0;
+}
+
+static int cmi9880_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct cmi_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
+ return 0;
+}
+
+
+/*
+ */
+static struct hda_pcm_stream cmi9880_pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 8,
+ .nid = 0x03, /* NID to query formats and rates */
+ .ops = {
+ .open = cmi9880_playback_pcm_open,
+ .prepare = cmi9880_playback_pcm_prepare,
+ .cleanup = cmi9880_playback_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream cmi9880_pcm_analog_capture = {
+ .substreams = 2,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = 0x08, /* NID to query formats and rates */
+ .ops = {
+ .prepare = cmi9880_capture_pcm_prepare,
+ .cleanup = cmi9880_capture_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream cmi9880_pcm_digital_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in cmi9880_build_pcms */
+ .ops = {
+ .open = cmi9880_dig_playback_pcm_open,
+ .close = cmi9880_dig_playback_pcm_close
+ },
+};
+
+static struct hda_pcm_stream cmi9880_pcm_digital_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in cmi9880_build_pcms */
+};
+
+static int cmi9880_build_pcms(struct hda_codec *codec)
+{
+ struct cmi_spec *spec = codec->spec;
+ struct hda_pcm *info = spec->pcm_rec;
+
+ codec->num_pcms = 1;
+ codec->pcm_info = info;
+
+ info->name = "CMI9880";
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_analog_playback;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_analog_capture;
+
+ if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+ codec->num_pcms++;
+ info++;
+ info->name = "CMI9880 Digital";
+ if (spec->multiout.dig_out_nid) {
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = cmi9880_pcm_digital_playback;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+ }
+ if (spec->dig_in_nid) {
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = cmi9880_pcm_digital_capture;
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+ }
+ }
+
+ return 0;
+}
+
+static void cmi9880_free(struct hda_codec *codec)
+{
+ kfree(codec->spec);
+}
+
+/*
+ */
+
+static struct hda_board_config cmi9880_cfg_tbl[] = {
+ { .modelname = "minimal", .config = CMI_MINIMAL },
+ { .modelname = "min_fp", .config = CMI_MIN_FP },
+ { .modelname = "full", .config = CMI_FULL },
+ { .modelname = "full_dig", .config = CMI_FULL_DIG },
+ { .modelname = "allout", .config = CMI_ALLOUT },
+ {} /* terminator */
+};
+
+static struct hda_codec_ops cmi9880_patch_ops = {
+ .build_controls = cmi9880_build_controls,
+ .build_pcms = cmi9880_build_pcms,
+ .init = cmi9880_init,
+ .free = cmi9880_free,
+#ifdef CONFIG_PM
+ .resume = cmi9880_resume,
+#endif
+};
+
+static int patch_cmi9880(struct hda_codec *codec)
+{
+ struct cmi_spec *spec;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ codec->spec = spec;
+ spec->board_config = snd_hda_check_board_config(codec, cmi9880_cfg_tbl);
+ if (spec->board_config < 0) {
+ snd_printd(KERN_INFO "hda_codec: Unknown model for CMI9880\n");
+ spec->board_config = CMI_FULL_DIG; /* try everything */
+ }
+
+ switch (spec->board_config) {
+ case CMI_MINIMAL:
+ case CMI_MIN_FP:
+ spec->surr_switch = 1;
+ if (spec->board_config == CMI_MINIMAL)
+ spec->num_ch_modes = 2;
+ else {
+ spec->front_panel = 1;
+ spec->num_ch_modes = 3;
+ }
+ spec->channel_modes = cmi9880_channel_modes;
+ spec->multiout.max_channels = cmi9880_channel_modes[0].channels;
+ spec->input_mux = &cmi9880_basic_mux;
+ break;
+ case CMI_FULL:
+ case CMI_FULL_DIG:
+ spec->front_panel = 1;
+ spec->multiout.max_channels = 8;
+ spec->input_mux = &cmi9880_basic_mux;
+ if (spec->board_config == CMI_FULL_DIG) {
+ spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+ spec->dig_in_nid = CMI_DIG_IN_NID;
+ }
+ break;
+ case CMI_ALLOUT:
+ spec->front_panel = 1;
+ spec->multiout.max_channels = 8;
+ spec->no_line_in = 1;
+ spec->input_mux = &cmi9880_no_line_mux;
+ spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+ break;
+ }
+
+ spec->multiout.num_dacs = 4;
+ spec->multiout.dac_nids = cmi9880_dac_nids;
+
+ spec->adc_nids = cmi9880_adc_nids;
+
+ codec->patch_ops = cmi9880_patch_ops;
+
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+struct hda_codec_preset snd_hda_preset_cmedia[] = {
+ { .id = 0x13f69880, .name = "CMI9880", .patch = patch_cmi9880 },
+ { .id = 0x434d4980, .name = "CMI9880", .patch = patch_cmi9880 },
+ {} /* terminator */
+};
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
new file mode 100644
index 000000000000..17c5062423ae
--- /dev/null
+++ b/sound/pci/hda/patch_realtek.c
@@ -0,0 +1,1503 @@
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for ALC 260/880/882 codecs
+ *
+ * Copyright (c) 2004 PeiSen Hou <pshou@realtek.com.tw>
+ * Takashi Iwai <tiwai@suse.de>
+ *
+ * This driver 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 driver 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
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+
+/* ALC880 board config type */
+enum {
+ ALC880_MINIMAL,
+ ALC880_3ST,
+ ALC880_3ST_DIG,
+ ALC880_5ST,
+ ALC880_5ST_DIG,
+ ALC880_W810,
+};
+
+struct alc_spec {
+ /* codec parameterization */
+ unsigned int front_panel: 1;
+
+ snd_kcontrol_new_t* mixers[2];
+ unsigned int num_mixers;
+
+ struct hda_verb *init_verbs;
+
+ char* stream_name_analog;
+ struct hda_pcm_stream *stream_analog_playback;
+ struct hda_pcm_stream *stream_analog_capture;
+
+ char* stream_name_digital;
+ struct hda_pcm_stream *stream_digital_playback;
+ struct hda_pcm_stream *stream_digital_capture;
+
+ /* playback */
+ struct hda_multi_out multiout;
+
+ /* capture */
+ unsigned int num_adc_nids;
+ hda_nid_t *adc_nids;
+ hda_nid_t dig_in_nid;
+
+ /* capture source */
+ const struct hda_input_mux *input_mux;
+ unsigned int cur_mux[3];
+
+ /* channel model */
+ const struct alc_channel_mode *channel_mode;
+ int num_channel_mode;
+
+ /* PCM information */
+ struct hda_pcm pcm_rec[2];
+};
+
+/* DAC/ADC assignment */
+
+static hda_nid_t alc880_dac_nids[4] = {
+ /* front, rear, clfe, rear_surr */
+ 0x02, 0x05, 0x04, 0x03
+};
+
+static hda_nid_t alc880_w810_dac_nids[3] = {
+ /* front, rear/surround, clfe */
+ 0x02, 0x03, 0x04
+};
+
+static hda_nid_t alc880_adc_nids[3] = {
+ /* ADC0-2 */
+ 0x07, 0x08, 0x09,
+};
+
+#define ALC880_DIGOUT_NID 0x06
+#define ALC880_DIGIN_NID 0x0a
+
+static hda_nid_t alc260_dac_nids[1] = {
+ /* front */
+ 0x02,
+};
+
+static hda_nid_t alc260_adc_nids[2] = {
+ /* ADC0-1 */
+ 0x04, 0x05,
+};
+
+#define ALC260_DIGOUT_NID 0x03
+#define ALC260_DIGIN_NID 0x06
+
+static struct hda_input_mux alc880_capture_source = {
+ .num_items = 4,
+ .items = {
+ { "Mic", 0x0 },
+ { "Front Mic", 0x3 },
+ { "Line", 0x2 },
+ { "CD", 0x4 },
+ },
+};
+
+static struct hda_input_mux alc260_capture_source = {
+ .num_items = 4,
+ .items = {
+ { "Mic", 0x0 },
+ { "Front Mic", 0x1 },
+ { "Line", 0x2 },
+ { "CD", 0x4 },
+ },
+};
+
+/*
+ * input MUX handling
+ */
+static int alc_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int alc_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+ ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+ return 0;
+}
+
+static int alc_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+ spec->adc_nids[adc_idx], &spec->cur_mux[adc_idx]);
+}
+
+/*
+ * channel mode setting
+ */
+struct alc_channel_mode {
+ int channels;
+ const struct hda_verb *sequence;
+};
+
+
+/*
+ * channel source setting (2/6 channel selection for 3-stack)
+ */
+
+/*
+ * set the path ways for 2 channel output
+ * need to set the codec line out and mic 1 pin widgets to inputs
+ */
+static struct hda_verb alc880_threestack_ch2_init[] = {
+ /* set pin widget 1Ah (line in) for input */
+ { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* set pin widget 18h (mic1) for input, for mic also enable the vref */
+ { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 },
+ /* mute the output for Line In PW */
+ { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+ /* mute for Mic1 PW */
+ { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+ { } /* end */
+};
+
+/*
+ * 6ch mode
+ * need to set the codec line out and mic 1 pin widgets to outputs
+ */
+static struct hda_verb alc880_threestack_ch6_init[] = {
+ /* set pin widget 1Ah (line in) for output */
+ { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* set pin widget 18h (mic1) for output */
+ { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* unmute the output for Line In PW */
+ { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+ /* unmute for Mic1 PW */
+ { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+ /* for rear channel output using Line In 1
+ * set select widget connection (nid = 0x12) - to summer node
+ * for rear NID = 0x0f...offset 3 in connection list
+ */
+ { 0x12, AC_VERB_SET_CONNECT_SEL, 0x3 },
+ /* for Mic1 - retask for center/lfe */
+ /* set select widget connection (nid = 0x10) - to summer node for
+ * front CLFE NID = 0x0e...offset 2 in connection list
+ */
+ { 0x10, AC_VERB_SET_CONNECT_SEL, 0x2 },
+ { } /* end */
+};
+
+static struct alc_channel_mode alc880_threestack_modes[2] = {
+ { 2, alc880_threestack_ch2_init },
+ { 6, alc880_threestack_ch6_init },
+};
+
+
+/*
+ * channel source setting (6/8 channel selection for 5-stack)
+ */
+
+/* set the path ways for 6 channel output
+ * need to set the codec line out and mic 1 pin widgets to inputs
+ */
+static struct hda_verb alc880_fivestack_ch6_init[] = {
+ /* set pin widget 1Ah (line in) for input */
+ { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 },
+ /* mute the output for Line In PW */
+ { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 },
+ { } /* end */
+};
+
+/* need to set the codec line out and mic 1 pin widgets to outputs */
+static struct hda_verb alc880_fivestack_ch8_init[] = {
+ /* set pin widget 1Ah (line in) for output */
+ { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 },
+ /* unmute the output for Line In PW */
+ { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000 },
+ /* output for surround channel output using Line In 1 */
+ /* set select widget connection (nid = 0x12) - to summer node
+ * for surr_rear NID = 0x0d...offset 1 in connection list
+ */
+ { 0x12, AC_VERB_SET_CONNECT_SEL, 0x1 },
+ { } /* end */
+};
+
+static struct alc_channel_mode alc880_fivestack_modes[2] = {
+ { 6, alc880_fivestack_ch6_init },
+ { 8, alc880_fivestack_ch8_init },
+};
+
+/*
+ * channel source setting for W810 system
+ *
+ * W810 has rear IO for:
+ * Front (DAC 02)
+ * Surround (DAC 03)
+ * Center/LFE (DAC 04)
+ * Digital out (06)
+ *
+ * The system also has a pair of internal speakers, and a headphone jack.
+ * These are both connected to Line2 on the codec, hence to DAC 02.
+ *
+ * There is a variable resistor to control the speaker or headphone
+ * volume. This is a hardware-only device without a software API.
+ *
+ * Plugging headphones in will disable the internal speakers. This is
+ * implemented in hardware, not via the driver using jack sense. In
+ * a similar fashion, plugging into the rear socket marked "front" will
+ * disable both the speakers and headphones.
+ *
+ * For input, there's a microphone jack, and an "audio in" jack.
+ * These may not do anything useful with this driver yet, because I
+ * haven't setup any initialization verbs for these yet...
+ */
+
+static struct alc_channel_mode alc880_w810_modes[1] = {
+ { 6, NULL }
+};
+
+/*
+ */
+static int alc880_ch_mode_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+
+ snd_assert(spec->channel_mode, return -ENXIO);
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+ uinfo->count = 1;
+ uinfo->value.enumerated.items = 2;
+ if (uinfo->value.enumerated.item >= 2)
+ uinfo->value.enumerated.item = 1;
+ sprintf(uinfo->value.enumerated.name, "%dch",
+ spec->channel_mode[uinfo->value.enumerated.item].channels);
+ return 0;
+}
+
+static int alc880_ch_mode_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+
+ snd_assert(spec->channel_mode, return -ENXIO);
+ ucontrol->value.enumerated.item[0] =
+ (spec->multiout.max_channels == spec->channel_mode[0].channels) ? 0 : 1;
+ return 0;
+}
+
+static int alc880_ch_mode_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+ int mode;
+
+ snd_assert(spec->channel_mode, return -ENXIO);
+ mode = ucontrol->value.enumerated.item[0] ? 1 : 0;
+ if (spec->multiout.max_channels == spec->channel_mode[mode].channels &&
+ ! codec->in_resume)
+ return 0;
+
+ /* change the current channel setting */
+ spec->multiout.max_channels = spec->channel_mode[mode].channels;
+ if (spec->channel_mode[mode].sequence)
+ snd_hda_sequence_write(codec, spec->channel_mode[mode].sequence);
+
+ return 1;
+}
+
+
+/*
+ */
+
+/* 3-stack mode
+ * Pin assignment: Front=0x14, Line-In/Rear=0x1a, Mic/CLFE=0x18, F-Mic=0x1b
+ * HP=0x19
+ */
+static snd_kcontrol_new_t alc880_base_mixer[] = {
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x18, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x18, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x3, HDA_INPUT),
+ HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x3, HDA_INPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x19, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* The multiple "Capture Source" controls confuse alsamixer
+ * So call somewhat different..
+ * FIXME: the controls appear in the "playback" view!
+ */
+ /* .name = "Capture Source", */
+ .name = "Input Source",
+ .count = 2,
+ .info = alc_mux_enum_info,
+ .get = alc_mux_enum_get,
+ .put = alc_mux_enum_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Channel Mode",
+ .info = alc880_ch_mode_info,
+ .get = alc880_ch_mode_get,
+ .put = alc880_ch_mode_put,
+ },
+ { } /* end */
+};
+
+/* 5-stack mode
+ * Pin assignment: Front=0x14, Rear=0x17, CLFE=0x16
+ * Line-In/Side=0x1a, Mic=0x18, F-Mic=0x1b, HP=0x19
+ */
+static snd_kcontrol_new_t alc880_five_stack_mixer[] = {
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x17, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x16, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Side Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Side Playback Switch", 0x1a, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x3, HDA_INPUT),
+ HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x3, HDA_INPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x19, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* The multiple "Capture Source" controls confuse alsamixer
+ * So call somewhat different..
+ * FIXME: the controls appear in the "playback" view!
+ */
+ /* .name = "Capture Source", */
+ .name = "Input Source",
+ .count = 2,
+ .info = alc_mux_enum_info,
+ .get = alc_mux_enum_get,
+ .put = alc_mux_enum_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Channel Mode",
+ .info = alc880_ch_mode_info,
+ .get = alc880_ch_mode_get,
+ .put = alc880_ch_mode_put,
+ },
+ { } /* end */
+};
+
+static snd_kcontrol_new_t alc880_w810_base_mixer[] = {
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x16, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x09, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x09, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* The multiple "Capture Source" controls confuse alsamixer
+ * So call somewhat different..
+ * FIXME: the controls appear in the "playback" view!
+ */
+ /* .name = "Capture Source", */
+ .name = "Input Source",
+ .count = 3,
+ .info = alc_mux_enum_info,
+ .get = alc_mux_enum_get,
+ .put = alc_mux_enum_put,
+ },
+ { } /* end */
+};
+
+/*
+ */
+static int alc_build_controls(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ int err;
+ int i;
+
+ for (i = 0; i < spec->num_mixers; i++) {
+ err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
+ if (err < 0)
+ return err;
+ }
+
+ if (spec->multiout.dig_out_nid) {
+ err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid);
+ if (err < 0)
+ return err;
+ }
+ if (spec->dig_in_nid) {
+ err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid);
+ if (err < 0)
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * initialize the codec volumes, etc
+ */
+
+static struct hda_verb alc880_init_verbs_three_stack[] = {
+ /* Line In pin widget for input */
+ {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* CD pin widget for input */
+ {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* Mic1 (rear panel) pin widget for input and vref at 80% */
+ {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* Mic2 (front panel) pin widget for input and vref at 80% */
+ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* unmute amp left and right */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+ /* set connection select to line in (default select for this ADC) */
+ {0x07, AC_VERB_SET_CONNECT_SEL, 0x02},
+ /* unmute front mixer amp left (volume = 0) */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* unmute rear mixer amp left and right (volume = 0) */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* unmute rear mixer amp left and right (volume = 0) */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+
+ /* using rear surround as the path for headphone output */
+ /* unmute rear surround mixer amp left and right (volume = 0) */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* PASD 3 stack boards use the Mic 2 as the headphone output */
+ /* need to program the selector associated with the Mic 2 pin widget to
+ * surround path (index 0x01) for headphone output */
+ {0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* need to retask the Mic 2 pin widget to output */
+ {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+ /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) for mixer widget(nid=0x0B)
+ * to support the input path of analog loopback
+ * Note: PASD motherboards uses the Line In 2 as the input for front panel
+ * mic (mic 2)
+ */
+ /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03 */
+ /* unmute CD */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
+ /* unmute Line In */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
+ /* unmute Mic 1 */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* unmute Line In 2 (for PASD boards Mic 2) */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
+
+ /* Unmute input amps for the line out paths to support the output path of
+ * analog loopback
+ * the mixers on the output path has 2 inputs, one from the DAC and one
+ * from the mixer
+ */
+ /* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+ /* Unmute Front out path */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute Surround (used as HP) out path */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute C/LFE out path */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))}, /* mute */
+ /* Unmute rear Surround out path */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+
+ { }
+};
+
+static struct hda_verb alc880_init_verbs_five_stack[] = {
+ /* Line In pin widget for input */
+ {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* CD pin widget for input */
+ {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* Mic1 (rear panel) pin widget for input and vref at 80% */
+ {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* Mic2 (front panel) pin widget for input and vref at 80% */
+ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* unmute amp left and right */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+ /* set connection select to line in (default select for this ADC) */
+ {0x07, AC_VERB_SET_CONNECT_SEL, 0x02},
+ /* unmute front mixer amp left and right (volume = 0) */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* five rear and clfe */
+ /* unmute rear mixer amp left and right (volume = 0) */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* unmute clfe mixer amp left and right (volume = 0) */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+
+ /* using rear surround as the path for headphone output */
+ /* unmute rear surround mixer amp left and right (volume = 0) */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* PASD 3 stack boards use the Mic 2 as the headphone output */
+ /* need to program the selector associated with the Mic 2 pin widget to
+ * surround path (index 0x01) for headphone output
+ */
+ {0x11, AC_VERB_SET_CONNECT_SEL, 0x01},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* need to retask the Mic 2 pin widget to output */
+ {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+ /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) for mixer
+ * widget(nid=0x0B) to support the input path of analog loopback
+ */
+ /* Note: PASD motherboards uses the Line In 2 as the input for front panel mic (mic 2) */
+ /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03*/
+ /* unmute CD */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
+ /* unmute Line In */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
+ /* unmute Mic 1 */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* unmute Line In 2 (for PASD boards Mic 2) */
+ {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))},
+
+ /* Unmute input amps for the line out paths to support the output path of
+ * analog loopback
+ * the mixers on the output path has 2 inputs, one from the DAC and
+ * one from the mixer
+ */
+ /* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+ /* Unmute Front out path */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute Surround (used as HP) out path */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute C/LFE out path */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))}, /* mute */
+ /* Unmute rear Surround out path */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+
+ { }
+};
+
+static struct hda_verb alc880_w810_init_verbs[] = {
+ /* front channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+
+ /* front channel selector/amp: input 1: capture mix: muted, (no volume selection) */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
+
+ /* front channel selector/amp: output 0: unmuted, max volume */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+
+ /* front out pin: muted, (no volume selection) */
+ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+
+ /* front out pin: NOT headphone enable, out enable, vref disabled */
+ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+
+ /* surround channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+
+ /* surround channel selector/amp: input 1: capture mix: muted, (no volume selection) */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
+
+ /* surround channel selector/amp: output 0: unmuted, max volume */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+
+ /* surround out pin: muted, (no volume selection) */
+ {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+
+ /* surround out pin: NOT headphone enable, out enable, vref disabled */
+ {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+
+ /* c/lfe channel selector/amp: input 0: DAC: unmuted, (no volume selection) */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+
+ /* c/lfe channel selector/amp: input 1: capture mix: muted, (no volume selection) */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0x7180},
+
+ /* c/lfe channel selector/amp: output 0: unmuted, max volume */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+
+ /* c/lfe out pin: muted, (no volume selection) */
+ {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+
+ /* c/lfe out pin: NOT headphone enable, out enable, vref disabled */
+ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+
+ /* hphone/speaker input selector: front DAC */
+ {0x13, AC_VERB_SET_CONNECT_SEL, 0x0},
+
+ /* hphone/speaker out pin: muted, (no volume selection) */
+ {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+
+ /* hphone/speaker out pin: NOT headphone enable, out enable, vref disabled */
+ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+
+
+ { }
+};
+
+static int alc_init(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ snd_hda_sequence_write(codec, spec->init_verbs);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * resume
+ */
+static int alc_resume(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ int i;
+
+ alc_init(codec);
+ for (i = 0; i < spec->num_mixers; i++) {
+ snd_hda_resume_ctls(codec, spec->mixers[i]);
+ }
+ if (spec->multiout.dig_out_nid)
+ snd_hda_resume_spdif_out(codec);
+ if (spec->dig_in_nid)
+ snd_hda_resume_spdif_in(codec);
+
+ return 0;
+}
+#endif
+
+/*
+ * Analog playback callbacks
+ */
+static int alc880_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
+}
+
+static int alc880_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+ format, substream);
+}
+
+static int alc880_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Digital out
+ */
+static int alc880_dig_playback_pcm_open(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int alc880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+ return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture
+ */
+static int alc880_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ unsigned int stream_tag,
+ unsigned int format,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+ stream_tag, 0, format);
+ return 0;
+}
+
+static int alc880_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+ struct hda_codec *codec,
+ snd_pcm_substream_t *substream)
+{
+ struct alc_spec *spec = codec->spec;
+
+ snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
+ return 0;
+}
+
+
+/*
+ */
+static struct hda_pcm_stream alc880_pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 8,
+ .nid = 0x02, /* NID to query formats and rates */
+ .ops = {
+ .open = alc880_playback_pcm_open,
+ .prepare = alc880_playback_pcm_prepare,
+ .cleanup = alc880_playback_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream alc880_pcm_analog_capture = {
+ .substreams = 2,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = 0x07, /* NID to query formats and rates */
+ .ops = {
+ .prepare = alc880_capture_pcm_prepare,
+ .cleanup = alc880_capture_pcm_cleanup
+ },
+};
+
+static struct hda_pcm_stream alc880_pcm_digital_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in alc_build_pcms */
+ .ops = {
+ .open = alc880_dig_playback_pcm_open,
+ .close = alc880_dig_playback_pcm_close
+ },
+};
+
+static struct hda_pcm_stream alc880_pcm_digital_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ /* NID is set in alc_build_pcms */
+};
+
+static int alc_build_pcms(struct hda_codec *codec)
+{
+ struct alc_spec *spec = codec->spec;
+ struct hda_pcm *info = spec->pcm_rec;
+ int i;
+
+ codec->num_pcms = 1;
+ codec->pcm_info = info;
+
+ info->name = spec->stream_name_analog;
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_analog_playback);
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture);
+
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = 0;
+ for (i = 0; i < spec->num_channel_mode; i++) {
+ if (spec->channel_mode[i].channels > info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max) {
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->channel_mode[i].channels;
+ }
+ }
+
+ if (spec->multiout.dig_out_nid || spec->dig_in_nid) {
+ codec->num_pcms++;
+ info++;
+ info->name = spec->stream_name_digital;
+ if (spec->multiout.dig_out_nid) {
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_digital_playback);
+ info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dig_out_nid;
+ }
+ if (spec->dig_in_nid) {
+ info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_digital_capture);
+ info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid;
+ }
+ }
+
+ return 0;
+}
+
+static void alc_free(struct hda_codec *codec)
+{
+ kfree(codec->spec);
+}
+
+/*
+ */
+static struct hda_codec_ops alc_patch_ops = {
+ .build_controls = alc_build_controls,
+ .build_pcms = alc_build_pcms,
+ .init = alc_init,
+ .free = alc_free,
+#ifdef CONFIG_PM
+ .resume = alc_resume,
+#endif
+};
+
+/*
+ */
+
+static struct hda_board_config alc880_cfg_tbl[] = {
+ /* Back 3 jack, front 2 jack */
+ { .modelname = "3stack", .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe200, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe201, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe202, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe203, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe204, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe205, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe206, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe207, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe208, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe209, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20a, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20b, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20c, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20d, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20e, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe20f, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe210, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe211, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe214, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe302, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe303, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe304, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe306, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe307, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xe404, .config = ALC880_3ST },
+ { .pci_vendor = 0x8086, .pci_device = 0xa101, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x3031, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4036, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4037, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4038, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4040, .config = ALC880_3ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4041, .config = ALC880_3ST },
+
+ /* Back 3 jack, front 2 jack (Internal add Aux-In) */
+ { .pci_vendor = 0x1025, .pci_device = 0xe310, .config = ALC880_3ST },
+
+ /* Back 3 jack plus 1 SPDIF out jack, front 2 jack */
+ { .modelname = "3stack-digout", .config = ALC880_3ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xe308, .config = ALC880_3ST_DIG },
+
+ /* Back 3 jack plus 1 SPDIF out jack, front 2 jack (Internal add Aux-In)*/
+ { .pci_vendor = 0x8086, .pci_device = 0xe305, .config = ALC880_3ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xd402, .config = ALC880_3ST_DIG },
+ { .pci_vendor = 0x1025, .pci_device = 0xe309, .config = ALC880_3ST_DIG },
+
+ /* Back 5 jack, front 2 jack */
+ { .modelname = "5stack", .config = ALC880_5ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x3033, .config = ALC880_5ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x4039, .config = ALC880_5ST },
+ { .pci_vendor = 0x107b, .pci_device = 0x3032, .config = ALC880_5ST },
+ { .pci_vendor = 0x103c, .pci_device = 0x2a09, .config = ALC880_5ST },
+
+ /* Back 5 jack plus 1 SPDIF out jack, front 2 jack */
+ { .modelname = "5stack-digout", .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xe224, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xe400, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xe401, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xe402, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xd400, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xd401, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x8086, .pci_device = 0xa100, .config = ALC880_5ST_DIG },
+ { .pci_vendor = 0x1565, .pci_device = 0x8202, .config = ALC880_5ST_DIG },
+
+ { .modelname = "w810", .config = ALC880_W810 },
+ { .pci_vendor = 0x161f, .pci_device = 0x203d, .config = ALC880_W810 },
+
+ {}
+};
+
+static int patch_alc880(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+ int board_config;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ codec->spec = spec;
+
+ board_config = snd_hda_check_board_config(codec, alc880_cfg_tbl);
+ if (board_config < 0) {
+ snd_printd(KERN_INFO "hda_codec: Unknown model for ALC880\n");
+ board_config = ALC880_MINIMAL;
+ }
+
+ switch (board_config) {
+ case ALC880_W810:
+ spec->mixers[spec->num_mixers] = alc880_w810_base_mixer;
+ spec->num_mixers++;
+ break;
+ case ALC880_5ST:
+ case ALC880_5ST_DIG:
+ spec->mixers[spec->num_mixers] = alc880_five_stack_mixer;
+ spec->num_mixers++;
+ break;
+ default:
+ spec->mixers[spec->num_mixers] = alc880_base_mixer;
+ spec->num_mixers++;
+ break;
+ }
+
+ switch (board_config) {
+ case ALC880_3ST_DIG:
+ case ALC880_5ST_DIG:
+ case ALC880_W810:
+ spec->multiout.dig_out_nid = ALC880_DIGOUT_NID;
+ break;
+ default:
+ break;
+ }
+
+ switch (board_config) {
+ case ALC880_3ST:
+ case ALC880_3ST_DIG:
+ case ALC880_5ST:
+ case ALC880_5ST_DIG:
+ case ALC880_W810:
+ spec->front_panel = 1;
+ break;
+ default:
+ break;
+ }
+
+ switch (board_config) {
+ case ALC880_5ST:
+ case ALC880_5ST_DIG:
+ spec->init_verbs = alc880_init_verbs_five_stack;
+ spec->channel_mode = alc880_fivestack_modes;
+ spec->num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes);
+ break;
+ case ALC880_W810:
+ spec->init_verbs = alc880_w810_init_verbs;
+ spec->channel_mode = alc880_w810_modes;
+ spec->num_channel_mode = ARRAY_SIZE(alc880_w810_modes);
+ break;
+ default:
+ spec->init_verbs = alc880_init_verbs_three_stack;
+ spec->channel_mode = alc880_threestack_modes;
+ spec->num_channel_mode = ARRAY_SIZE(alc880_threestack_modes);
+ break;
+ }
+
+ spec->stream_name_analog = "ALC880 Analog";
+ spec->stream_analog_playback = &alc880_pcm_analog_playback;
+ spec->stream_analog_capture = &alc880_pcm_analog_capture;
+
+ spec->stream_name_digital = "ALC880 Digital";
+ spec->stream_digital_playback = &alc880_pcm_digital_playback;
+ spec->stream_digital_capture = &alc880_pcm_digital_capture;
+
+ spec->multiout.max_channels = spec->channel_mode[0].channels;
+
+ switch (board_config) {
+ case ALC880_W810:
+ spec->multiout.num_dacs = ARRAY_SIZE(alc880_w810_dac_nids);
+ spec->multiout.dac_nids = alc880_w810_dac_nids;
+ // No dedicated headphone socket - it's shared with built-in speakers.
+ break;
+ default:
+ spec->multiout.num_dacs = ARRAY_SIZE(alc880_dac_nids);
+ spec->multiout.dac_nids = alc880_dac_nids;
+ spec->multiout.hp_nid = 0x03; /* rear-surround NID */
+ break;
+ }
+
+ spec->input_mux = &alc880_capture_source;
+ spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids);
+ spec->adc_nids = alc880_adc_nids;
+
+ codec->patch_ops = alc_patch_ops;
+
+ return 0;
+}
+
+/*
+ * ALC260 support
+ */
+
+/*
+ * This is just place-holder, so there's something for alc_build_pcms to look
+ * at when it calculates the maximum number of channels. ALC260 has no mixer
+ * element which allows changing the channel mode, so the verb list is
+ * never used.
+ */
+static struct alc_channel_mode alc260_modes[1] = {
+ { 2, NULL },
+};
+
+snd_kcontrol_new_t alc260_base_mixer[] = {
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x08, 0x0, HDA_OUTPUT),
+ /* use LINE2 for the output */
+ /* HDA_CODEC_MUTE("Front Playback Switch", 0x0f, 0x0, HDA_OUTPUT), */
+ HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT),
+ HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT),
+ HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT),
+ HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT),
+ HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x07, 0x01, HDA_INPUT),
+ HDA_CODEC_MUTE("Front Mic Playback Switch", 0x07, 0x01, HDA_INPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x07, 0x05, HDA_INPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x07, 0x05, HDA_INPUT),
+ HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x10, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x11, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x04, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x04, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Capture Source",
+ .info = alc_mux_enum_info,
+ .get = alc_mux_enum_get,
+ .put = alc_mux_enum_put,
+ },
+ { } /* end */
+};
+
+static struct hda_verb alc260_init_verbs[] = {
+ /* Line In pin widget for input */
+ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* CD pin widget for input */
+ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* Mic1 (rear panel) pin widget for input and vref at 80% */
+ {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* Mic2 (front panel) pin widget for input and vref at 80% */
+ {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* LINE-2 is used for line-out in rear */
+ {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* select line-out */
+ {0x15, AC_VERB_SET_CONNECT_SEL, 0x00},
+ /* LINE-OUT pin */
+ {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* enable HP */
+ {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* enable Mono */
+ {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* unmute amp left and right */
+ {0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+ /* set connection select to line in (default select for this ADC) */
+ {0x04, AC_VERB_SET_CONNECT_SEL, 0x02},
+ /* unmute Line-Out mixer amp left and right (volume = 0) */
+ {0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* unmute HP mixer amp left and right (volume = 0) */
+ {0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* unmute Mono mixer amp left and right (volume = 0) */
+ {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ /* mute pin widget amp left and right (no gain on this amp) */
+ {0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* mute LINE-2 out */
+ {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080},
+ /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & Line In 2 = 0x03 */
+ /* unmute CD */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))},
+ /* unmute Line In */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))},
+ /* unmute Mic */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* Amp Indexes: DAC = 0x01 & mixer = 0x00 */
+ /* Unmute Front out path */
+ {0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute Headphone out path */
+ {0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x09, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute Mono out path */
+ {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ { }
+};
+
+static struct hda_pcm_stream alc260_pcm_analog_playback = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = 0x2,
+};
+
+static struct hda_pcm_stream alc260_pcm_analog_capture = {
+ .substreams = 1,
+ .channels_min = 2,
+ .channels_max = 2,
+ .nid = 0x4,
+};
+
+static int patch_alc260(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ codec->spec = spec;
+
+ spec->mixers[spec->num_mixers] = alc260_base_mixer;
+ spec->num_mixers++;
+
+ spec->init_verbs = alc260_init_verbs;
+ spec->channel_mode = alc260_modes;
+ spec->num_channel_mode = ARRAY_SIZE(alc260_modes);
+
+ spec->stream_name_analog = "ALC260 Analog";
+ spec->stream_analog_playback = &alc260_pcm_analog_playback;
+ spec->stream_analog_capture = &alc260_pcm_analog_capture;
+
+ spec->multiout.max_channels = spec->channel_mode[0].channels;
+ spec->multiout.num_dacs = ARRAY_SIZE(alc260_dac_nids);
+ spec->multiout.dac_nids = alc260_dac_nids;
+
+ spec->input_mux = &alc260_capture_source;
+ spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids);
+ spec->adc_nids = alc260_adc_nids;
+
+ codec->patch_ops = alc_patch_ops;
+
+ return 0;
+}
+
+/*
+ * ALC882 support
+ *
+ * ALC882 is almost identical with ALC880 but has cleaner and more flexible
+ * configuration. Each pin widget can choose any input DACs and a mixer.
+ * Each ADC is connected from a mixer of all inputs. This makes possible
+ * 6-channel independent captures.
+ *
+ * In addition, an independent DAC for the multi-playback (not used in this
+ * driver yet).
+ */
+
+static struct alc_channel_mode alc882_ch_modes[1] = {
+ { 8, NULL }
+};
+
+static hda_nid_t alc882_dac_nids[4] = {
+ /* front, rear, clfe, rear_surr */
+ 0x02, 0x03, 0x04, 0x05
+};
+
+static hda_nid_t alc882_adc_nids[3] = {
+ /* ADC0-2 */
+ 0x07, 0x08, 0x09,
+};
+
+/* input MUX */
+/* FIXME: should be a matrix-type input source selection */
+
+static struct hda_input_mux alc882_capture_source = {
+ .num_items = 4,
+ .items = {
+ { "Mic", 0x0 },
+ { "Front Mic", 0x1 },
+ { "Line", 0x2 },
+ { "CD", 0x4 },
+ },
+};
+
+#define alc882_mux_enum_info alc_mux_enum_info
+#define alc882_mux_enum_get alc_mux_enum_get
+
+static int alc882_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+ struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct alc_spec *spec = codec->spec;
+ const struct hda_input_mux *imux = spec->input_mux;
+ unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+ static hda_nid_t capture_mixers[3] = { 0x24, 0x23, 0x22 };
+ hda_nid_t nid = capture_mixers[adc_idx];
+ unsigned int *cur_val = &spec->cur_mux[adc_idx];
+ unsigned int i, idx;
+
+ idx = ucontrol->value.enumerated.item[0];
+ if (idx >= imux->num_items)
+ idx = imux->num_items - 1;
+ if (*cur_val == idx && ! codec->in_resume)
+ return 0;
+ for (i = 0; i < imux->num_items; i++) {
+ unsigned int v = (i == idx) ? 0x7000 : 0x7080;
+ snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE,
+ v | (imux->items[i].index << 8));
+ }
+ *cur_val = idx;
+ return 1;
+}
+
+/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17
+ * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b
+ */
+static snd_kcontrol_new_t alc882_base_mixer[] = {
+ HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x16, 1, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Side Playback Switch", 0x17, 0x0, HDA_OUTPUT),
+ HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT),
+ HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
+ HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT),
+ HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x1, HDA_INPUT),
+ HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT),
+ HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT),
+ HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x08, 0x0, HDA_INPUT),
+ HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x09, 0x0, HDA_INPUT),
+ HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x09, 0x0, HDA_INPUT),
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ /* .name = "Capture Source", */
+ .name = "Input Source",
+ .count = 3,
+ .info = alc882_mux_enum_info,
+ .get = alc882_mux_enum_get,
+ .put = alc882_mux_enum_put,
+ },
+ { } /* end */
+};
+
+static struct hda_verb alc882_init_verbs[] = {
+ /* Front mixer: unmute input/output amp left and right (volume = 0) */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* Rear mixer */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* CLFE mixer */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ /* Side mixer */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000},
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+
+ /* Front Pin: to output mode */
+ {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* Front Pin: mute amp left and right (no volume) */
+ {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* select Front mixer (0x0c, index 0) */
+ {0x14, AC_VERB_SET_CONNECT_SEL, 0x00},
+ /* Rear Pin */
+ {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* Rear Pin: mute amp left and right (no volume) */
+ {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* select Rear mixer (0x0d, index 1) */
+ {0x15, AC_VERB_SET_CONNECT_SEL, 0x01},
+ /* CLFE Pin */
+ {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* CLFE Pin: mute amp left and right (no volume) */
+ {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* select CLFE mixer (0x0e, index 2) */
+ {0x16, AC_VERB_SET_CONNECT_SEL, 0x02},
+ /* Side Pin */
+ {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* Side Pin: mute amp left and right (no volume) */
+ {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* select Side mixer (0x0f, index 3) */
+ {0x17, AC_VERB_SET_CONNECT_SEL, 0x03},
+ /* Headphone Pin */
+ {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40},
+ /* Headphone Pin: mute amp left and right (no volume) */
+ {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000},
+ /* select Front mixer (0x0c, index 0) */
+ {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00},
+ /* Mic (rear) pin widget for input and vref at 80% */
+ {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* Front Mic pin widget for input and vref at 80% */
+ {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24},
+ /* Line In pin widget for input */
+ {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+ /* CD pin widget for input */
+ {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20},
+
+ /* FIXME: use matrix-type input source selection */
+ /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */
+ /* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */
+ {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+ {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+ {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+ /* Input mixer2 */
+ {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+ {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+ {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+ /* Input mixer3 */
+ {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))},
+ {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x03 << 8))},
+ {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8))},
+ {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x04 << 8))},
+ /* ADC1: unmute amp left and right */
+ {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+ /* ADC2: unmute amp left and right */
+ {0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+ /* ADC3: unmute amp left and right */
+ {0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000},
+
+ /* Unmute front loopback */
+ {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Unmute rear loopback */
+ {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+ /* Mute CLFE loopback */
+ {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8))},
+ /* Unmute side loopback */
+ {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))},
+
+ { }
+};
+
+static int patch_alc882(struct hda_codec *codec)
+{
+ struct alc_spec *spec;
+
+ spec = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+ if (spec == NULL)
+ return -ENOMEM;
+
+ codec->spec = spec;
+
+ spec->mixers[spec->num_mixers] = alc882_base_mixer;
+ spec->num_mixers++;
+
+ spec->multiout.dig_out_nid = ALC880_DIGOUT_NID;
+ spec->dig_in_nid = ALC880_DIGIN_NID;
+ spec->front_panel = 1;
+ spec->init_verbs = alc882_init_verbs;
+ spec->channel_mode = alc882_ch_modes;
+ spec->num_channel_mode = ARRAY_SIZE(alc882_ch_modes);
+
+ spec->stream_name_analog = "ALC882 Analog";
+ spec->stream_analog_playback = &alc880_pcm_analog_playback;
+ spec->stream_analog_capture = &alc880_pcm_analog_capture;
+
+ spec->stream_name_digital = "ALC882 Digital";
+ spec->stream_digital_playback = &alc880_pcm_digital_playback;
+ spec->stream_digital_capture = &alc880_pcm_digital_capture;
+
+ spec->multiout.max_channels = spec->channel_mode[0].channels;
+ spec->multiout.num_dacs = ARRAY_SIZE(alc882_dac_nids);
+ spec->multiout.dac_nids = alc882_dac_nids;
+
+ spec->input_mux = &alc882_capture_source;
+ spec->num_adc_nids = ARRAY_SIZE(alc882_adc_nids);
+ spec->adc_nids = alc882_adc_nids;
+
+ codec->patch_ops = alc_patch_ops;
+
+ return 0;
+}
+
+/*
+ * patch entries
+ */
+struct hda_codec_preset snd_hda_preset_realtek[] = {
+ { .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 },
+ { .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 },
+ { .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 },
+ {} /* terminator */
+};