diff options
Diffstat (limited to 'sound/pci/hda')
-rw-r--r-- | sound/pci/hda/Makefile | 20 | ||||
-rw-r--r-- | sound/pci/hda/hda_beep.c | 142 | ||||
-rw-r--r-- | sound/pci/hda/hda_beep.h | 45 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.c | 3141 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.h | 848 | ||||
-rw-r--r-- | sound/pci/hda/hda_generic.c | 1099 | ||||
-rw-r--r-- | sound/pci/hda/hda_hwdep.c | 121 | ||||
-rw-r--r-- | sound/pci/hda/hda_intel.c | 2496 | ||||
-rw-r--r-- | sound/pci/hda/hda_local.h | 433 | ||||
-rw-r--r-- | sound/pci/hda/hda_patch.h | 22 | ||||
-rw-r--r-- | sound/pci/hda/hda_proc.c | 676 | ||||
-rw-r--r-- | sound/pci/hda/patch_analog.c | 4331 | ||||
-rw-r--r-- | sound/pci/hda/patch_atihdmi.c | 199 | ||||
-rw-r--r-- | sound/pci/hda/patch_cmedia.c | 743 | ||||
-rw-r--r-- | sound/pci/hda/patch_conexant.c | 1794 | ||||
-rw-r--r-- | sound/pci/hda/patch_nvhdmi.c | 165 | ||||
-rw-r--r-- | sound/pci/hda/patch_realtek.c | 16493 | ||||
-rw-r--r-- | sound/pci/hda/patch_si3054.c | 303 | ||||
-rw-r--r-- | sound/pci/hda/patch_sigmatel.c | 5355 | ||||
-rw-r--r-- | sound/pci/hda/patch_via.c | 3335 |
20 files changed, 41761 insertions, 0 deletions
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile new file mode 100644 index 0000000..1980c6d --- /dev/null +++ b/sound/pci/hda/Makefile @@ -0,0 +1,20 @@ +snd-hda-intel-y := hda_intel.o +# since snd-hda-intel is the only driver using hda-codec, +# merge it into a single module although it was originally +# designed to be individual modules +snd-hda-intel-y += hda_codec.o +snd-hda-intel-$(CONFIG_PROC_FS) += hda_proc.o +snd-hda-intel-$(CONFIG_SND_HDA_HWDEP) += hda_hwdep.o +snd-hda-intel-$(CONFIG_SND_HDA_INPUT_BEEP) += hda_beep.o +snd-hda-intel-$(CONFIG_SND_HDA_GENERIC) += hda_generic.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_REALTEK) += patch_realtek.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_CMEDIA) += patch_cmedia.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_ANALOG) += patch_analog.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_SIGMATEL) += patch_sigmatel.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_SI3054) += patch_si3054.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_ATIHDMI) += patch_atihdmi.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_CONEXANT) += patch_conexant.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_VIA) += patch_via.o +snd-hda-intel-$(CONFIG_SND_HDA_CODEC_NVHDMI) += patch_nvhdmi.o + +obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o diff --git a/sound/pci/hda/hda_beep.c b/sound/pci/hda/hda_beep.c new file mode 100644 index 0000000..3ecd7e7 --- /dev/null +++ b/sound/pci/hda/hda_beep.c @@ -0,0 +1,142 @@ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matthew Ranostay <mranostay@embeddedalley.com> + * Copyright (c) 2008 Embedded Alley Solutions Inc + * + * 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 <linux/input.h> +#include <linux/pci.h> +#include <linux/workqueue.h> +#include <sound/core.h> +#include "hda_beep.h" + +enum { + DIGBEEP_HZ_STEP = 46875, /* 46.875 Hz */ + DIGBEEP_HZ_MIN = 93750, /* 93.750 Hz */ + DIGBEEP_HZ_MAX = 12000000, /* 12 KHz */ +}; + +static void snd_hda_generate_beep(struct work_struct *work) +{ + struct hda_beep *beep = + container_of(work, struct hda_beep, beep_work); + struct hda_codec *codec = beep->codec; + + if (!beep->enabled) + return; + + /* generate tone */ + snd_hda_codec_write_cache(codec, beep->nid, 0, + AC_VERB_SET_BEEP_CONTROL, beep->tone); +} + +static int snd_hda_beep_event(struct input_dev *dev, unsigned int type, + unsigned int code, int hz) +{ + struct hda_beep *beep = input_get_drvdata(dev); + + switch (code) { + case SND_BELL: + if (hz) + hz = 1000; + case SND_TONE: + hz *= 1000; /* fixed point */ + hz = hz - DIGBEEP_HZ_MIN; + if (hz < 0) + hz = 0; /* turn off PC beep*/ + else if (hz >= (DIGBEEP_HZ_MAX - DIGBEEP_HZ_MIN)) + hz = 0xff; + else { + hz /= DIGBEEP_HZ_STEP; + hz++; + } + break; + default: + return -1; + } + beep->tone = hz; + + /* schedule beep event */ + schedule_work(&beep->beep_work); + return 0; +} + +int snd_hda_attach_beep_device(struct hda_codec *codec, int nid) +{ + struct input_dev *input_dev; + struct hda_beep *beep; + int err; + + beep = kzalloc(sizeof(*beep), GFP_KERNEL); + if (beep == NULL) + return -ENOMEM; + snprintf(beep->phys, sizeof(beep->phys), + "card%d/codec#%d/beep0", codec->bus->card->number, codec->addr); + input_dev = input_allocate_device(); + if (!input_dev) { + kfree(beep); + return -ENOMEM; + } + + /* setup digital beep device */ + input_dev->name = "HDA Digital PCBeep"; + input_dev->phys = beep->phys; + input_dev->id.bustype = BUS_PCI; + + input_dev->id.vendor = codec->vendor_id >> 16; + input_dev->id.product = codec->vendor_id & 0xffff; + input_dev->id.version = 0x01; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = snd_hda_beep_event; + input_dev->dev.parent = &codec->bus->pci->dev; + input_set_drvdata(input_dev, beep); + + err = input_register_device(input_dev); + if (err < 0) { + input_free_device(input_dev); + kfree(beep); + return err; + } + + /* enable linear scale */ + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_DIGI_CONVERT_2, 0x01); + + beep->nid = nid; + beep->dev = input_dev; + beep->codec = codec; + beep->enabled = 1; + codec->beep = beep; + + INIT_WORK(&beep->beep_work, &snd_hda_generate_beep); + return 0; +} + +void snd_hda_detach_beep_device(struct hda_codec *codec) +{ + struct hda_beep *beep = codec->beep; + if (beep) { + cancel_work_sync(&beep->beep_work); + flush_scheduled_work(); + + input_unregister_device(beep->dev); + kfree(beep); + } +} diff --git a/sound/pci/hda/hda_beep.h b/sound/pci/hda/hda_beep.h new file mode 100644 index 0000000..b9679f0 --- /dev/null +++ b/sound/pci/hda/hda_beep.h @@ -0,0 +1,45 @@ +/* + * Digital Beep Input Interface for HD-audio codec + * + * Author: Matthew Ranostay <mranostay@embeddedalley.com> + * Copyright (c) 2008 Embedded Alley Solutions Inc + * + * 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 + */ + +#ifndef __SOUND_HDA_BEEP_H +#define __SOUND_HDA_BEEP_H + +#include "hda_codec.h" + +/* beep information */ +struct hda_beep { + struct input_dev *dev; + struct hda_codec *codec; + char phys[32]; + int tone; + int nid; + int enabled; + struct work_struct beep_work; /* scheduled task for beep event */ +}; + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +int snd_hda_attach_beep_device(struct hda_codec *codec, int nid); +void snd_hda_detach_beep_device(struct hda_codec *codec); +#else +#define snd_hda_attach_beep_device(...) +#define snd_hda_detach_beep_device(...) +#endif +#endif diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c new file mode 100644 index 0000000..eb91641 --- /dev/null +++ b/sound/pci/hda/hda_codec.c @@ -0,0 +1,3141 @@ +/* + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/mutex.h> +#include <sound/core.h> +#include "hda_codec.h" +#include <sound/asoundef.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include "hda_local.h" +#include <sound/hda_hwdep.h> +#include "hda_patch.h" /* codec presets */ + +#ifdef CONFIG_SND_HDA_POWER_SAVE +/* define this option here to hide as static */ +static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT; +module_param(power_save, int, 0644); +MODULE_PARM_DESC(power_save, "Automatic power-saving timeout " + "(in second, 0 = disable)."); +#endif + +/* + * vendor / preset table + */ + +struct hda_vendor_id { + unsigned int id; + const char *name; +}; + +/* codec vendor labels */ +static struct hda_vendor_id hda_vendor_ids[] = { + { 0x1002, "ATI" }, + { 0x1057, "Motorola" }, + { 0x1095, "Silicon Image" }, + { 0x10ec, "Realtek" }, + { 0x1106, "VIA" }, + { 0x111d, "IDT" }, + { 0x11c1, "LSI" }, + { 0x11d4, "Analog Devices" }, + { 0x13f6, "C-Media" }, + { 0x14f1, "Conexant" }, + { 0x17e8, "Chrontel" }, + { 0x1854, "LG" }, + { 0x1aec, "Wolfson Microelectronics" }, + { 0x434d, "C-Media" }, + { 0x8384, "SigmaTel" }, + {} /* terminator */ +}; + +static const struct hda_codec_preset *hda_preset_tables[] = { +#ifdef CONFIG_SND_HDA_CODEC_REALTEK + snd_hda_preset_realtek, +#endif +#ifdef CONFIG_SND_HDA_CODEC_CMEDIA + snd_hda_preset_cmedia, +#endif +#ifdef CONFIG_SND_HDA_CODEC_ANALOG + snd_hda_preset_analog, +#endif +#ifdef CONFIG_SND_HDA_CODEC_SIGMATEL + snd_hda_preset_sigmatel, +#endif +#ifdef CONFIG_SND_HDA_CODEC_SI3054 + snd_hda_preset_si3054, +#endif +#ifdef CONFIG_SND_HDA_CODEC_ATIHDMI + snd_hda_preset_atihdmi, +#endif +#ifdef CONFIG_SND_HDA_CODEC_CONEXANT + snd_hda_preset_conexant, +#endif +#ifdef CONFIG_SND_HDA_CODEC_VIA + snd_hda_preset_via, +#endif +#ifdef CONFIG_SND_HDA_CODEC_NVHDMI + snd_hda_preset_nvhdmi, +#endif + NULL +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static void hda_power_work(struct work_struct *work); +static void hda_keep_power_on(struct hda_codec *codec); +#else +static inline void hda_keep_power_on(struct hda_codec *codec) {} +#endif + +/** + * 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; + snd_hda_power_up(codec); + mutex_lock(&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; + mutex_unlock(&codec->bus->cmd_mutex); + snd_hda_power_down(codec); + 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; + snd_hda_power_up(codec); + mutex_lock(&codec->bus->cmd_mutex); + err = codec->bus->ops.command(codec, nid, direct, verb, parm); + mutex_unlock(&codec->bus->cmd_mutex); + snd_hda_power_down(codec); + 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); + if (parm == -1) + return 0; + *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, conn_len, conns; + unsigned int shift, num_elems, mask; + hda_nid_t prev_nid; + + if (snd_BUG_ON(!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; + 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; + prev_nid = 0; + for (i = 0; i < conn_len; i++) { + int range_val; + hda_nid_t val, n; + + if (i % num_elems == 0) + parm = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_LIST, i); + range_val = !!(parm & (1 << (shift-1))); /* ranges */ + val = parm & mask; + parm >>= shift; + if (range_val) { + /* ranges between the previous and this one */ + if (!prev_nid || prev_nid >= val) { + snd_printk(KERN_WARNING "hda_codec: " + "invalid dep_range_val %x:%x\n", + prev_nid, val); + continue; + } + for (n = prev_nid + 1; n <= val; n++) { + if (conns >= max_conns) { + snd_printk(KERN_ERR + "Too many connections\n"); + return -EINVAL; + } + conn_list[conns++] = n; + } + } else { + if (conns >= max_conns) { + snd_printk(KERN_ERR "Too many connections\n"); + return -EINVAL; + } + conn_list[conns++] = val; + } + prev_nid = val; + } + 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; + + unsol = bus->unsol; + if (!unsol) + 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; + + schedule_work(&unsol->work); + + return 0; +} + +/* + * process queued unsolicited events + */ +static void process_unsol_events(struct work_struct *work) +{ + struct hda_bus_unsolicited *unsol = + container_of(work, struct hda_bus_unsolicited, work); + struct hda_bus *bus = unsol->bus; + 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 __devinit init_unsol_queue(struct hda_bus *bus) +{ + struct hda_bus_unsolicited *unsol; + + if (bus->unsol) /* already initialized */ + return 0; + + unsol = kzalloc(sizeof(*unsol), GFP_KERNEL); + if (!unsol) { + snd_printk(KERN_ERR "hda_codec: " + "can't allocate unsolicited queue\n"); + return -ENOMEM; + } + INIT_WORK(&unsol->work, process_unsol_events); + unsol->bus = 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 hda_codec *codec, *n; + + if (!bus) + return 0; + if (bus->unsol) { + flush_scheduled_work(); + kfree(bus->unsol); + } + list_for_each_entry_safe(codec, n, &bus->codec_list, 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(struct snd_device *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 __devinit snd_hda_bus_new(struct snd_card *card, + const struct hda_bus_template *temp, + struct hda_bus **busp) +{ + struct hda_bus *bus; + int err; + static struct snd_device_ops dev_ops = { + .dev_free = snd_hda_bus_dev_free, + }; + + if (snd_BUG_ON(!temp)) + return -EINVAL; + if (snd_BUG_ON(!temp->ops.command || !temp->ops.get_response)) + return -EINVAL; + + if (busp) + *busp = NULL; + + bus = kzalloc(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; + + mutex_init(&bus->cmd_mutex); + INIT_LIST_HEAD(&bus->codec_list); + + err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops); + if (err < 0) { + snd_hda_bus_free(bus); + return err; + } + if (busp) + *busp = bus; + return 0; +} + +#ifdef CONFIG_SND_HDA_GENERIC +#define is_generic_config(codec) \ + (codec->bus->modelname && !strcmp(codec->bus->modelname, "generic")) +#else +#define is_generic_config(codec) 0 +#endif + +/* + * find a matching codec preset + */ +static const struct hda_codec_preset __devinit * +find_codec_preset(struct hda_codec *codec) +{ + const struct hda_codec_preset **tbl, *preset; + + if (is_generic_config(codec)) + return NULL; /* use the generic parser */ + + for (tbl = hda_preset_tables; *tbl; tbl++) { + for (preset = *tbl; preset->id; preset++) { + u32 mask = preset->mask; + if (preset->afg && preset->afg != codec->afg) + continue; + if (preset->mfg && preset->mfg != codec->mfg) + continue; + if (!mask) + mask = ~0; + if (preset->id == (codec->vendor_id & mask) && + (!preset->rev || + preset->rev == codec->revision_id)) + 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 and MFG nodes + */ +static void __devinit setup_fg_nodes(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++) { + unsigned int func; + func = snd_hda_param_read(codec, nid, AC_PAR_FUNCTION_TYPE); + switch (func & 0xff) { + case AC_GRP_AUDIO_FUNCTION: + codec->afg = nid; + break; + case AC_GRP_MODEM_FUNCTION: + codec->mfg = nid; + break; + default: + break; + } + } +} + +/* + * read widget caps for each widget and store in cache + */ +static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node) +{ + int i; + hda_nid_t nid; + + codec->num_nodes = snd_hda_get_sub_nodes(codec, fg_node, + &codec->start_nid); + codec->wcaps = kmalloc(codec->num_nodes * 4, GFP_KERNEL); + if (!codec->wcaps) + return -ENOMEM; + nid = codec->start_nid; + for (i = 0; i < codec->num_nodes; i++, nid++) + codec->wcaps[i] = snd_hda_param_read(codec, nid, + AC_PAR_AUDIO_WIDGET_CAP); + return 0; +} + + +static void init_hda_cache(struct hda_cache_rec *cache, + unsigned int record_size); +static void free_hda_cache(struct hda_cache_rec *cache); + +/* + * codec destructor + */ +static void snd_hda_codec_free(struct hda_codec *codec) +{ + if (!codec) + return; +#ifdef CONFIG_SND_HDA_POWER_SAVE + cancel_delayed_work(&codec->power_work); + flush_scheduled_work(); +#endif + list_del(&codec->list); + codec->bus->caddr_tbl[codec->addr] = NULL; + if (codec->patch_ops.free) + codec->patch_ops.free(codec); + free_hda_cache(&codec->amp_cache); + free_hda_cache(&codec->cmd_cache); + kfree(codec->wcaps); + kfree(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 __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr, + struct hda_codec **codecp) +{ + struct hda_codec *codec; + char component[31]; + int err; + + if (snd_BUG_ON(!bus)) + return -EINVAL; + if (snd_BUG_ON(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 = kzalloc(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; + mutex_init(&codec->spdif_mutex); + init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info)); + init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head)); + +#ifdef CONFIG_SND_HDA_POWER_SAVE + INIT_DELAYED_WORK(&codec->power_work, hda_power_work); + /* snd_hda_codec_new() marks the codec as power-up, and leave it as is. + * the caller has to power down appropriatley after initialization + * phase. + */ + hda_keep_power_on(codec); +#endif + + 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); + if (codec->vendor_id == -1) + /* read again, hopefully the access method was corrected + * in the last read... + */ + 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); + + setup_fg_nodes(codec); + if (!codec->afg && !codec->mfg) { + snd_printdd("hda_codec: no AFG or MFG node found\n"); + snd_hda_codec_free(codec); + return -ENODEV; + } + + if (read_widget_caps(codec, codec->afg ? codec->afg : codec->mfg) < 0) { + snd_printk(KERN_ERR "hda_codec: cannot malloc\n"); + snd_hda_codec_free(codec); + return -ENOMEM; + } + + if (!codec->subsystem_id) { + hda_nid_t nid = codec->afg ? codec->afg : codec->mfg; + codec->subsystem_id = + snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_SUBSYSTEM_ID, 0); + } + + codec->preset = find_codec_preset(codec); + /* audio codec should override the mixer name */ + if (codec->afg || !*bus->card->mixername) + snd_hda_get_codec_name(codec, bus->card->mixername, + sizeof(bus->card->mixername)); + + if (is_generic_config(codec)) { + err = snd_hda_parse_generic_codec(codec); + goto patched; + } + if (codec->preset && codec->preset->patch) { + err = codec->preset->patch(codec); + goto patched; + } + + /* call the default parser */ + err = snd_hda_parse_generic_codec(codec); + if (err < 0) + printk(KERN_ERR "hda-codec: No codec parser is available\n"); + + patched: + if (err < 0) { + snd_hda_codec_free(codec); + return err; + } + + if (codec->patch_ops.unsol_event) + init_unsol_queue(bus); + + snd_hda_codec_proc_new(codec); +#ifdef CONFIG_SND_HDA_HWDEP + snd_hda_create_hwdep(codec); +#endif + + sprintf(component, "HDA:%08x,%08x,%08x", codec->vendor_id, codec->subsystem_id, codec->revision_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) +{ + if (!nid) + return; + + 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); +} + +void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid) +{ + if (!nid) + return; + + snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0); +#if 0 /* keep the format */ + msleep(1); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0); +#endif +} + +/* + * amp access functions + */ + +/* FIXME: more better hash key? */ +#define HDA_HASH_KEY(nid,dir,idx) (u32)((nid) + ((idx) << 16) + ((dir) << 24)) +#define INFO_AMP_CAPS (1<<0) +#define INFO_AMP_VOL(ch) (1 << (1 + (ch))) + +/* initialize the hash table */ +static void __devinit init_hda_cache(struct hda_cache_rec *cache, + unsigned int record_size) +{ + memset(cache, 0, sizeof(*cache)); + memset(cache->hash, 0xff, sizeof(cache->hash)); + cache->record_size = record_size; +} + +static void free_hda_cache(struct hda_cache_rec *cache) +{ + kfree(cache->buffer); +} + +/* query the hash. allocate an entry if not found. */ +static struct hda_cache_head *get_alloc_hash(struct hda_cache_rec *cache, + u32 key) +{ + u16 idx = key % (u16)ARRAY_SIZE(cache->hash); + u16 cur = cache->hash[idx]; + struct hda_cache_head *info; + + while (cur != 0xffff) { + info = (struct hda_cache_head *)(cache->buffer + + cur * cache->record_size); + if (info->key == key) + return info; + cur = info->next; + } + + /* add a new hash entry */ + if (cache->num_entries >= cache->size) { + /* reallocate the array */ + unsigned int new_size = cache->size + 64; + void *new_buffer; + new_buffer = kcalloc(new_size, cache->record_size, GFP_KERNEL); + if (!new_buffer) { + snd_printk(KERN_ERR "hda_codec: " + "can't malloc amp_info\n"); + return NULL; + } + if (cache->buffer) { + memcpy(new_buffer, cache->buffer, + cache->size * cache->record_size); + kfree(cache->buffer); + } + cache->size = new_size; + cache->buffer = new_buffer; + } + cur = cache->num_entries++; + info = (struct hda_cache_head *)(cache->buffer + + cur * cache->record_size); + info->key = key; + info->val = 0; + info->next = cache->hash[idx]; + cache->hash[idx] = cur; + + return info; +} + +/* query and allocate an amp hash entry */ +static inline struct hda_amp_info * +get_alloc_amp_hash(struct hda_codec *codec, u32 key) +{ + return (struct hda_amp_info *)get_alloc_hash(&codec->amp_cache, key); +} + +/* + * query AMP capabilities for the given widget and direction + */ +u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction) +{ + struct hda_amp_info *info; + + info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, 0)); + if (!info) + return 0; + if (!(info->head.val & INFO_AMP_CAPS)) { + if (!(get_wcaps(codec, nid) & 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); + if (info->amp_caps) + info->head.val |= INFO_AMP_CAPS; + } + return info->amp_caps; +} + +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps) +{ + struct hda_amp_info *info; + + info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, dir, 0)); + if (!info) + return -EINVAL; + info->amp_caps = caps; + info->head.val |= INFO_AMP_CAPS; + return 0; +} + +/* + * read the current volume to info + * if the cache exists, read the cache value. + */ +static unsigned int 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->head.val & INFO_AMP_VOL(ch)) + return info->vol[ch]; + + 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->head.val |= INFO_AMP_VOL(ch); + return info->vol[ch]; +} + +/* + * write the current volume in info to the h/w and update the cache + */ +static void put_vol_mute(struct hda_codec *codec, struct hda_amp_info *info, + 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); + info->vol[ch] = val; +} + +/* + * read 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; + info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, index)); + if (!info) + return 0; + return get_vol_mute(codec, info, nid, ch, direction, index); +} + +/* + * update the AMP value, mask = bit mask to set, val = the value + */ +int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, + int direction, int idx, int mask, int val) +{ + struct hda_amp_info *info; + + info = get_alloc_amp_hash(codec, HDA_HASH_KEY(nid, direction, idx)); + if (!info) + return 0; + val &= mask; + val |= get_vol_mute(codec, info, nid, ch, direction, idx) & ~mask; + if (info->vol[ch] == val) + return 0; + put_vol_mute(codec, info, nid, ch, direction, idx, val); + return 1; +} + +/* + * update the AMP stereo with the same mask and value + */ +int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, + int direction, int idx, int mask, int val) +{ + int ch, ret = 0; + for (ch = 0; ch < 2; ch++) + ret |= snd_hda_codec_amp_update(codec, nid, ch, direction, + idx, mask, val); + return ret; +} + +#ifdef SND_HDA_NEEDS_RESUME +/* resume the all amp commands from the cache */ +void snd_hda_codec_resume_amp(struct hda_codec *codec) +{ + struct hda_amp_info *buffer = codec->amp_cache.buffer; + int i; + + for (i = 0; i < codec->amp_cache.size; i++, buffer++) { + u32 key = buffer->head.key; + hda_nid_t nid; + unsigned int idx, dir, ch; + if (!key) + continue; + nid = key & 0xff; + idx = (key >> 16) & 0xff; + dir = (key >> 24) & 0xff; + for (ch = 0; ch < 2; ch++) { + if (!(buffer->head.val & INFO_AMP_VOL(ch))) + continue; + put_vol_mute(codec, buffer, nid, ch, dir, idx, + buffer->vol[ch]); + } + } +} +#endif /* SND_HDA_NEEDS_RESUME */ + +/* volume */ +int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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); + /* num steps */ + caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + if (!caps) { + printk(KERN_WARNING "hda_codec: " + "num_steps = 0 for NID=0x%x (ctl = %s)\n", nid, + kcontrol->id.name); + 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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) + & HDA_AMP_VOLMASK; + if (chs & 2) + *valp = snd_hda_codec_amp_read(codec, nid, 1, dir, idx) + & HDA_AMP_VOLMASK; + return 0; +} + +int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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; + int change = 0; + + snd_hda_power_up(codec); + if (chs & 1) { + change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, + 0x7f, *valp); + valp++; + } + if (chs & 2) + change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, + 0x7f, *valp); + snd_hda_power_down(codec); + return change; +} + +int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + int dir = get_amp_direction(kcontrol); + u32 caps, val1, val2; + + if (size < 4 * sizeof(unsigned int)) + return -ENOMEM; + caps = query_amp_caps(codec, nid, dir); + val2 = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT; + val2 = (val2 + 1) * 25; + val1 = -((caps & AC_AMPCAP_OFFSET) >> AC_AMPCAP_OFFSET_SHIFT); + val1 = ((int)val1) * ((int)val2); + if (put_user(SNDRV_CTL_TLVT_DB_SCALE, _tlv)) + return -EFAULT; + if (put_user(2 * sizeof(unsigned int), _tlv + 1)) + return -EFAULT; + if (put_user(val1, _tlv + 2)) + return -EFAULT; + if (put_user(val2, _tlv + 3)) + return -EFAULT; + return 0; +} + +/* + * set (static) TLV for virtual master volume; recalculated as max 0dB + */ +void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int *tlv) +{ + u32 caps; + int nums, step; + + caps = query_amp_caps(codec, nid, dir); + nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT; + step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT; + step = (step + 1) * 25; + tlv[0] = SNDRV_CTL_TLVT_DB_SCALE; + tlv[1] = 2 * sizeof(unsigned int); + tlv[2] = -nums * step; + tlv[3] = step; +} + +/* find a mixer control element with the given name */ +static struct snd_kcontrol * +_snd_hda_find_mixer_ctl(struct hda_codec *codec, + const char *name, int idx) +{ + struct snd_ctl_elem_id id; + memset(&id, 0, sizeof(id)); + id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + id.index = idx; + strcpy(id.name, name); + return snd_ctl_find_id(codec->bus->card, &id); +} + +struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec, + const char *name) +{ + return _snd_hda_find_mixer_ctl(codec, name, 0); +} + +/* create a virtual master control and add slaves */ +int snd_hda_add_vmaster(struct hda_codec *codec, char *name, + unsigned int *tlv, const char **slaves) +{ + struct snd_kcontrol *kctl; + const char **s; + int err; + + for (s = slaves; *s && !snd_hda_find_mixer_ctl(codec, *s); s++) + ; + if (!*s) { + snd_printdd("No slave found for %s\n", name); + return 0; + } + kctl = snd_ctl_make_virtual_master(name, tlv); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(codec->bus->card, kctl); + if (err < 0) + return err; + + for (s = slaves; *s; s++) { + struct snd_kcontrol *sctl; + + sctl = snd_hda_find_mixer_ctl(codec, *s); + if (!sctl) { + snd_printdd("Cannot find slave %s, skipped\n", *s); + continue; + } + err = snd_ctl_add_slave(kctl, sctl); + if (err < 0) + return err; + } + return 0; +} + +/* switch */ +int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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) & + HDA_AMP_MUTE) ? 0 : 1; + if (chs & 2) + *valp = (snd_hda_codec_amp_read(codec, nid, 1, dir, idx) & + HDA_AMP_MUTE) ? 0 : 1; + return 0; +} + +int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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; + int change = 0; + + snd_hda_power_up(codec); + if (chs & 1) { + change = snd_hda_codec_amp_update(codec, nid, 0, dir, idx, + HDA_AMP_MUTE, + *valp ? 0 : HDA_AMP_MUTE); + valp++; + } + if (chs & 2) + change |= snd_hda_codec_amp_update(codec, nid, 1, dir, idx, + HDA_AMP_MUTE, + *valp ? 0 : HDA_AMP_MUTE); +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (codec->patch_ops.check_power_status) + codec->patch_ops.check_power_status(codec, nid); +#endif + snd_hda_power_down(codec); + return change; +} + +/* + * bound volume controls + * + * bind multiple volumes (# indices, from 0) + */ + +#define AMP_VAL_IDX_SHIFT 19 +#define AMP_VAL_IDX_MASK (0x0f<<19) + +int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long pval; + int err; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + pval = kcontrol->private_value; + kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */ + err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol); + kcontrol->private_value = pval; + mutex_unlock(&codec->spdif_mutex); + return err; +} + +int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned long pval; + int i, indices, err = 0, change = 0; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + pval = kcontrol->private_value; + indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT; + for (i = 0; i < indices; i++) { + kcontrol->private_value = (pval & ~AMP_VAL_IDX_MASK) | + (i << AMP_VAL_IDX_SHIFT); + err = snd_hda_mixer_amp_switch_put(kcontrol, ucontrol); + if (err < 0) + break; + change |= err; + } + kcontrol->private_value = pval; + mutex_unlock(&codec->spdif_mutex); + return err < 0 ? err : change; +} + +/* + * generic bound volume/swtich controls + */ +int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_bind_ctls *c; + int err; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + c = (struct hda_bind_ctls *)kcontrol->private_value; + kcontrol->private_value = *c->values; + err = c->ops->info(kcontrol, uinfo); + kcontrol->private_value = (long)c; + mutex_unlock(&codec->spdif_mutex); + return err; +} + +int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_bind_ctls *c; + int err; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + c = (struct hda_bind_ctls *)kcontrol->private_value; + kcontrol->private_value = *c->values; + err = c->ops->get(kcontrol, ucontrol); + kcontrol->private_value = (long)c; + mutex_unlock(&codec->spdif_mutex); + return err; +} + +int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_bind_ctls *c; + unsigned long *vals; + int err = 0, change = 0; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + c = (struct hda_bind_ctls *)kcontrol->private_value; + for (vals = c->values; *vals; vals++) { + kcontrol->private_value = *vals; + err = c->ops->put(kcontrol, ucontrol); + if (err < 0) + break; + change |= err; + } + kcontrol->private_value = (long)c; + mutex_unlock(&codec->spdif_mutex); + return err < 0 ? err : change; +} + +int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct hda_bind_ctls *c; + int err; + + mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */ + c = (struct hda_bind_ctls *)kcontrol->private_value; + kcontrol->private_value = *c->values; + err = c->ops->tlv(kcontrol, op_flag, size, tlv); + kcontrol->private_value = (long)c; + mutex_unlock(&codec->spdif_mutex); + return err; +} + +struct hda_ctl_ops snd_hda_bind_vol = { + .info = snd_hda_mixer_amp_volume_info, + .get = snd_hda_mixer_amp_volume_get, + .put = snd_hda_mixer_amp_volume_put, + .tlv = snd_hda_mixer_amp_tlv +}; + +struct hda_ctl_ops snd_hda_bind_sw = { + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = snd_hda_mixer_amp_switch_put, + .tlv = snd_hda_mixer_amp_tlv +}; + +/* + * SPDIF out controls + */ + +static int snd_hda_spdif_mask_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958; + uinfo->count = 1; + return 0; +} + +static int snd_hda_spdif_cmask_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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 |= AC_DIG1_PROFESSIONAL; + if (sbits & IEC958_AES0_NONAUDIO) + val |= AC_DIG1_NONAUDIO; + if (sbits & IEC958_AES0_PROFESSIONAL) { + if ((sbits & IEC958_AES0_PRO_EMPHASIS) == + IEC958_AES0_PRO_EMPHASIS_5015) + val |= AC_DIG1_EMPHASIS; + } else { + if ((sbits & IEC958_AES0_CON_EMPHASIS) == + IEC958_AES0_CON_EMPHASIS_5015) + val |= AC_DIG1_EMPHASIS; + if (!(sbits & IEC958_AES0_CON_NOT_COPYRIGHT)) + val |= AC_DIG1_COPYRIGHT; + if (sbits & (IEC958_AES1_CON_ORIGINAL << 8)) + val |= AC_DIG1_LEVEL; + 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 & AC_DIG1_NONAUDIO) + sbits |= IEC958_AES0_NONAUDIO; + if (val & AC_DIG1_PROFESSIONAL) + sbits |= IEC958_AES0_PROFESSIONAL; + if (sbits & IEC958_AES0_PROFESSIONAL) { + if (sbits & AC_DIG1_EMPHASIS) + sbits |= IEC958_AES0_PRO_EMPHASIS_5015; + } else { + if (val & AC_DIG1_EMPHASIS) + sbits |= IEC958_AES0_CON_EMPHASIS_5015; + if (!(val & AC_DIG1_COPYRIGHT)) + sbits |= IEC958_AES0_CON_NOT_COPYRIGHT; + if (val & AC_DIG1_LEVEL) + sbits |= (IEC958_AES1_CON_ORIGINAL << 8); + sbits |= val & (0x7f << 8); + } + return sbits; +} + +/* set digital convert verbs both for the given NID and its slaves */ +static void set_dig_out(struct hda_codec *codec, hda_nid_t nid, + int verb, int val) +{ + hda_nid_t *d; + + snd_hda_codec_write_cache(codec, nid, 0, verb, val); + d = codec->slave_dig_outs; + if (!d) + return; + for (; *d; d++) + snd_hda_codec_write_cache(codec, *d, 0, verb, val); +} + +static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid, + int dig1, int dig2) +{ + if (dig1 != -1) + set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_1, dig1); + if (dig2 != -1) + set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_2, dig2); +} + +static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned short val; + int change; + + mutex_lock(&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) + set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff); + + mutex_unlock(&codec->spdif_mutex); + return change; +} + +#define snd_hda_spdif_out_switch_info snd_ctl_boolean_mono_info + +static int snd_hda_spdif_out_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + + ucontrol->value.integer.value[0] = codec->spdif_ctls & AC_DIG1_ENABLE; + return 0; +} + +static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value; + unsigned short val; + int change; + + mutex_lock(&codec->spdif_mutex); + val = codec->spdif_ctls & ~AC_DIG1_ENABLE; + if (ucontrol->value.integer.value[0]) + val |= AC_DIG1_ENABLE; + change = codec->spdif_ctls != val; + if (change) { + codec->spdif_ctls = val; + set_dig_out_convert(codec, nid, val & 0xff, -1); + /* unmute amp switch (if any) */ + if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) && + (val & AC_DIG1_ENABLE)) + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, 0); + } + mutex_unlock(&codec->spdif_mutex); + return change; +} + +static struct snd_kcontrol_new 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 */ +}; + +#define SPDIF_MAX_IDX 4 /* 4 instances should be enough to probe */ + +/** + * 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; + struct snd_kcontrol *kctl; + struct snd_kcontrol_new *dig_mix; + int idx; + + for (idx = 0; idx < SPDIF_MAX_IDX; idx++) { + if (!_snd_hda_find_mixer_ctl(codec, "IEC958 Playback Switch", + idx)) + break; + } + if (idx >= SPDIF_MAX_IDX) { + printk(KERN_ERR "hda_codec: too many IEC958 outputs\n"); + return -EBUSY; + } + for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) { + kctl = snd_ctl_new1(dig_mix, codec); + kctl->id.index = idx; + kctl->private_value = nid; + err = snd_ctl_add(codec->bus->card, kctl); + if (err < 0) + return err; + } + codec->spdif_ctls = + snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0); + codec->spdif_status = convert_to_spdif_status(codec->spdif_ctls); + return 0; +} + +/* + * SPDIF sharing with analog output + */ +static int spdif_share_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = mout->share_spdif; + return 0; +} + +static int spdif_share_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol); + mout->share_spdif = !!ucontrol->value.integer.value[0]; + return 0; +} + +static struct snd_kcontrol_new spdif_share_sw = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Default PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = spdif_share_sw_get, + .put = spdif_share_sw_put, +}; + +int snd_hda_create_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + if (!mout->dig_out_nid) + return 0; + /* ATTENTION: here mout is passed as private_data, instead of codec */ + return snd_ctl_add(codec->bus->card, + snd_ctl_new1(&spdif_share_sw, mout)); +} + +/* + * SPDIF input + */ + +#define snd_hda_spdif_in_switch_info snd_hda_spdif_out_switch_info + +static int snd_hda_spdif_in_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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; + + mutex_lock(&codec->spdif_mutex); + change = codec->spdif_in_enable != val; + if (change) { + codec->spdif_in_enable = val; + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_DIGI_CONVERT_1, val); + } + mutex_unlock(&codec->spdif_mutex); + return change; +} + +static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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_1, 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 struct snd_kcontrol_new 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; + struct snd_kcontrol *kctl; + struct snd_kcontrol_new *dig_mix; + int idx; + + for (idx = 0; idx < SPDIF_MAX_IDX; idx++) { + if (!_snd_hda_find_mixer_ctl(codec, "IEC958 Capture Switch", + idx)) + break; + } + if (idx >= SPDIF_MAX_IDX) { + printk(KERN_ERR "hda_codec: too many IEC958 inputs\n"); + return -EBUSY; + } + for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) { + kctl = snd_ctl_new1(dig_mix, codec); + kctl->private_value = nid; + err = snd_ctl_add(codec->bus->card, kctl); + if (err < 0) + return err; + } + codec->spdif_in_enable = + snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0) & + AC_DIG1_ENABLE; + return 0; +} + +#ifdef SND_HDA_NEEDS_RESUME +/* + * command cache + */ + +/* build a 32bit cache key with the widget id and the command parameter */ +#define build_cmd_cache_key(nid, verb) ((verb << 8) | nid) +#define get_cmd_cache_nid(key) ((key) & 0xff) +#define get_cmd_cache_cmd(key) (((key) >> 8) & 0xffff) + +/** + * snd_hda_codec_write_cache - send a single command with caching + * @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_cache(struct hda_codec *codec, hda_nid_t nid, + int direct, unsigned int verb, unsigned int parm) +{ + int err; + snd_hda_power_up(codec); + mutex_lock(&codec->bus->cmd_mutex); + err = codec->bus->ops.command(codec, nid, direct, verb, parm); + if (!err) { + struct hda_cache_head *c; + u32 key = build_cmd_cache_key(nid, verb); + c = get_alloc_hash(&codec->cmd_cache, key); + if (c) + c->val = parm; + } + mutex_unlock(&codec->bus->cmd_mutex); + snd_hda_power_down(codec); + return err; +} + +/* resume the all commands from the cache */ +void snd_hda_codec_resume_cache(struct hda_codec *codec) +{ + struct hda_cache_head *buffer = codec->cmd_cache.buffer; + int i; + + for (i = 0; i < codec->cmd_cache.size; i++, buffer++) { + u32 key = buffer->key; + if (!key) + continue; + snd_hda_codec_write(codec, get_cmd_cache_nid(key), 0, + get_cmd_cache_cmd(key), buffer->val); + } +} + +/** + * snd_hda_sequence_write_cache - sequence writes with caching + * @codec: the HDA codec + * @seq: VERB array to send + * + * Send the commands sequentially from the given array. + * Thte commands are recorded on cache for power-save and resume. + * The array must be terminated with NID=0. + */ +void snd_hda_sequence_write_cache(struct hda_codec *codec, + const struct hda_verb *seq) +{ + for (; seq->nid; seq++) + snd_hda_codec_write_cache(codec, seq->nid, 0, seq->verb, + seq->param); +} +#endif /* SND_HDA_NEEDS_RESUME */ + +/* + * set power state of the codec + */ +static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state) +{ + hda_nid_t nid; + int i; + + snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE, + power_state); + msleep(10); /* partial workaround for "azx_get_response timeout" */ + + nid = codec->start_nid; + for (i = 0; i < codec->num_nodes; i++, nid++) { + unsigned int wcaps = get_wcaps(codec, nid); + if (wcaps & AC_WCAP_POWER) { + unsigned int wid_type = (wcaps & AC_WCAP_TYPE) >> + AC_WCAP_TYPE_SHIFT; + if (wid_type == AC_WID_PIN) { + unsigned int pincap; + /* + * don't power down the widget if it controls + * eapd and EAPD_BTLENABLE is set. + */ + pincap = snd_hda_param_read(codec, nid, + AC_PAR_PIN_CAP); + if (pincap & AC_PINCAP_EAPD) { + int eapd = snd_hda_codec_read(codec, + nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0); + eapd &= 0x02; + if (power_state == AC_PWRST_D3 && eapd) + continue; + } + } + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_POWER_STATE, + power_state); + } + } + + if (power_state == AC_PWRST_D0) { + unsigned long end_time; + int state; + msleep(10); + /* wait until the codec reachs to D0 */ + end_time = jiffies + msecs_to_jiffies(500); + do { + state = snd_hda_codec_read(codec, fg, 0, + AC_VERB_GET_POWER_STATE, 0); + if (state == power_state) + break; + msleep(1); + } while (time_after_eq(end_time, jiffies)); + } +} + +#ifdef SND_HDA_NEEDS_RESUME +/* + * call suspend and power-down; used both from PM and power-save + */ +static void hda_call_codec_suspend(struct hda_codec *codec) +{ + if (codec->patch_ops.suspend) + codec->patch_ops.suspend(codec, PMSG_SUSPEND); + hda_set_power_state(codec, + codec->afg ? codec->afg : codec->mfg, + AC_PWRST_D3); +#ifdef CONFIG_SND_HDA_POWER_SAVE + cancel_delayed_work(&codec->power_work); + codec->power_on = 0; + codec->power_transition = 0; +#endif +} + +/* + * kick up codec; used both from PM and power-save + */ +static void hda_call_codec_resume(struct hda_codec *codec) +{ + hda_set_power_state(codec, + codec->afg ? codec->afg : codec->mfg, + AC_PWRST_D0); + if (codec->patch_ops.resume) + codec->patch_ops.resume(codec); + else { + if (codec->patch_ops.init) + codec->patch_ops.init(codec); + snd_hda_codec_resume_amp(codec); + snd_hda_codec_resume_cache(codec); + } +} +#endif /* SND_HDA_NEEDS_RESUME */ + + +/** + * 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 __devinit snd_hda_build_controls(struct hda_bus *bus) +{ + struct hda_codec *codec; + + list_for_each_entry(codec, &bus->codec_list, list) { + int err = 0; + /* fake as if already powered-on */ + hda_keep_power_on(codec); + /* then fire up */ + hda_set_power_state(codec, + codec->afg ? codec->afg : codec->mfg, + AC_PWRST_D0); + /* continue to initialize... */ + if (codec->patch_ops.init) + err = codec->patch_ops.init(codec); + if (!err && codec->patch_ops.build_controls) + err = codec->patch_ops.build_controls(codec); + snd_hda_power_down(codec); + if (err < 0) + return err; + } + + return 0; +} + +/* + * stream formats + */ +struct hda_rate_tbl { + unsigned int hz; + unsigned int alsa_bits; + unsigned int hda_fmt; +}; + +static struct hda_rate_tbl rate_bits[] = { + /* rate in Hz, ALSA rate bitmask, HDA format value */ + + /* autodetected value used in snd_hda_query_supported_pcm */ + { 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 */ +#define AC_PAR_PCM_RATE_BITS 11 + /* up to bits 10, 384kHZ isn't supported properly */ + + /* not autodetected value */ + { 9600, SNDRV_PCM_RATE_KNOT, 0x0400 }, /* 1/5 x 48 */ + + { 0 } /* terminator */ +}; + +/** + * 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].hz; i++) + if (rate_bits[i].hz == rate) { + val = rate_bits[i].hda_fmt; + break; + } + if (!rate_bits[i].hz) { + 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 && + (get_wcaps(codec, nid) & 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; i < AC_PAR_PCM_RATE_BITS; i++) { + if (val & (1 << i)) + rates |= rate_bits[i].alsa_bits; + } + *ratesp = rates; + } + + if (formatsp || bpsp) { + u64 formats = 0; + unsigned int bps; + unsigned int wcaps; + + wcaps = get_wcaps(codec, nid); + 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_24) + bps = 24; + else if (val & AC_SUPPCM_BITS_20) + bps = 20; + } + } + 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 && + (get_wcaps(codec, nid) & 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; i < AC_PAR_PCM_RATE_BITS; i++) + if (rate_bits[i].hda_fmt == rate) { + if (val & (1 << i)) + break; + return 0; + } + if (i >= AC_PAR_PCM_RATE_BITS) + 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, + struct snd_pcm_substream *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, + struct snd_pcm_substream *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, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + return 0; +} + +static int __devinit set_pcm_default_values(struct hda_codec *codec, + struct hda_pcm_stream *info) +{ + /* query support PCM information from the given NID */ + if (info->nid && (!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) { + if (snd_BUG_ON(!info->nid)) + return -EINVAL; + info->ops.prepare = hda_pcm_default_prepare; + } + if (info->ops.cleanup == NULL) { + if (snd_BUG_ON(!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 __devinit snd_hda_build_pcms(struct hda_bus *bus) +{ + struct hda_codec *codec; + + list_for_each_entry(codec, &bus->codec_list, 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 + * @num_configs: number of config enums + * @models: array of model name strings + * @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, + int num_configs, const char **models, + const struct snd_pci_quirk *tbl) +{ + if (codec->bus->modelname && models) { + int i; + for (i = 0; i < num_configs; i++) { + if (models[i] && + !strcmp(codec->bus->modelname, models[i])) { + snd_printd(KERN_INFO "hda_codec: model '%s' is " + "selected\n", models[i]); + return i; + } + } + } + + if (!codec->bus->pci || !tbl) + return -1; + + tbl = snd_pci_quirk_lookup(codec->bus->pci, tbl); + if (!tbl) + return -1; + if (tbl->value >= 0 && tbl->value < num_configs) { +#ifdef CONFIG_SND_DEBUG_VERBOSE + char tmp[10]; + const char *model = NULL; + if (models) + model = models[tbl->value]; + if (!model) { + sprintf(tmp, "#%d", tbl->value); + model = tmp; + } + snd_printdd(KERN_INFO "hda_codec: model '%s' is selected " + "for config %x:%x (%s)\n", + model, tbl->subvendor, tbl->subdevice, + (tbl->name ? tbl->name : "Unknown device")); +#endif + return tbl->value; + } + return -1; +} + +/** + * snd_hda_add_new_ctls - create controls from the array + * @codec: the HDA codec + * @knew: the array of struct snd_kcontrol_new + * + * 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, struct snd_kcontrol_new *knew) +{ + int err; + + for (; knew->name; knew++) { + struct snd_kcontrol *kctl; + kctl = snd_ctl_new1(knew, codec); + if (!kctl) + return -ENOMEM; + err = snd_ctl_add(codec->bus->card, kctl); + if (err < 0) { + if (!codec->addr) + return err; + kctl = snd_ctl_new1(knew, codec); + if (!kctl) + return -ENOMEM; + kctl->id.device = codec->addr; + err = snd_ctl_add(codec->bus->card, kctl); + if (err < 0) + return err; + } + } + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg, + unsigned int power_state); + +static void hda_power_work(struct work_struct *work) +{ + struct hda_codec *codec = + container_of(work, struct hda_codec, power_work.work); + + if (!codec->power_on || codec->power_count) { + codec->power_transition = 0; + return; + } + + hda_call_codec_suspend(codec); + if (codec->bus->ops.pm_notify) + codec->bus->ops.pm_notify(codec); +} + +static void hda_keep_power_on(struct hda_codec *codec) +{ + codec->power_count++; + codec->power_on = 1; +} + +void snd_hda_power_up(struct hda_codec *codec) +{ + codec->power_count++; + if (codec->power_on || codec->power_transition) + return; + + codec->power_on = 1; + if (codec->bus->ops.pm_notify) + codec->bus->ops.pm_notify(codec); + hda_call_codec_resume(codec); + cancel_delayed_work(&codec->power_work); + codec->power_transition = 0; +} + +void snd_hda_power_down(struct hda_codec *codec) +{ + --codec->power_count; + if (!codec->power_on || codec->power_count || codec->power_transition) + return; + if (power_save) { + codec->power_transition = 1; /* avoid reentrance */ + schedule_delayed_work(&codec->power_work, + msecs_to_jiffies(power_save * 1000)); + } +} + +int snd_hda_check_amp_list_power(struct hda_codec *codec, + struct hda_loopback_check *check, + hda_nid_t nid) +{ + struct hda_amp_list *p; + int ch, v; + + if (!check->amplist) + return 0; + for (p = check->amplist; p->nid; p++) { + if (p->nid == nid) + break; + } + if (!p->nid) + return 0; /* nothing changed */ + + for (p = check->amplist; p->nid; p++) { + for (ch = 0; ch < 2; ch++) { + v = snd_hda_codec_amp_read(codec, p->nid, ch, p->dir, + p->idx); + if (!(v & HDA_AMP_MUTE) && v > 0) { + if (!check->power_on) { + check->power_on = 1; + snd_hda_power_up(codec); + } + return 1; + } + } + } + if (check->power_on) { + check->power_on = 0; + snd_hda_power_down(codec); + } + return 0; +} +#endif + +/* + * Channel mode helper + */ +int snd_hda_ch_mode_info(struct hda_codec *codec, + struct snd_ctl_elem_info *uinfo, + const struct hda_channel_mode *chmode, + int num_chmodes) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = num_chmodes; + if (uinfo->value.enumerated.item >= num_chmodes) + uinfo->value.enumerated.item = num_chmodes - 1; + sprintf(uinfo->value.enumerated.name, "%dch", + chmode[uinfo->value.enumerated.item].channels); + return 0; +} + +int snd_hda_ch_mode_get(struct hda_codec *codec, + struct snd_ctl_elem_value *ucontrol, + const struct hda_channel_mode *chmode, + int num_chmodes, + int max_channels) +{ + int i; + + for (i = 0; i < num_chmodes; i++) { + if (max_channels == chmode[i].channels) { + ucontrol->value.enumerated.item[0] = i; + break; + } + } + return 0; +} + +int snd_hda_ch_mode_put(struct hda_codec *codec, + struct snd_ctl_elem_value *ucontrol, + const struct hda_channel_mode *chmode, + int num_chmodes, + int *max_channelsp) +{ + unsigned int mode; + + mode = ucontrol->value.enumerated.item[0]; + if (mode >= num_chmodes) + return -EINVAL; + if (*max_channelsp == chmode[mode].channels) + return 0; + /* change the current channel setting */ + *max_channelsp = chmode[mode].channels; + if (chmode[mode].sequence) + snd_hda_sequence_write_cache(codec, chmode[mode].sequence); + return 1; +} + +/* + * input MUX helper + */ +int snd_hda_input_mux_info(const struct hda_input_mux *imux, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int index; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = imux->num_items; + if (!imux->num_items) + return 0; + 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, + struct snd_ctl_elem_value *ucontrol, + hda_nid_t nid, + unsigned int *cur_val) +{ + unsigned int idx; + + if (!imux->num_items) + return 0; + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (*cur_val == idx) + return 0; + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, + imux->items[idx].index); + *cur_val = idx; + return 1; +} + + +/* + * Multi-channel / digital-out PCM helper functions + */ + +/* setup SPDIF output stream */ +static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid, + unsigned int stream_tag, unsigned int format) +{ + /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */ + if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) + set_dig_out_convert(codec, nid, + codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff, + -1); + snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); + if (codec->slave_dig_outs) { + hda_nid_t *d; + for (d = codec->slave_dig_outs; *d; d++) + snd_hda_codec_setup_stream(codec, *d, stream_tag, 0, + format); + } + /* turn on again (if needed) */ + if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE)) + set_dig_out_convert(codec, nid, + codec->spdif_ctls & 0xff, -1); +} + +static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid) +{ + snd_hda_codec_cleanup_stream(codec, nid); + if (codec->slave_dig_outs) { + hda_nid_t *d; + for (d = codec->slave_dig_outs; *d; d++) + snd_hda_codec_cleanup_stream(codec, *d); + } +} + +/* + * open the digital out in the exclusive mode + */ +int snd_hda_multi_out_dig_open(struct hda_codec *codec, + struct hda_multi_out *mout) +{ + mutex_lock(&codec->spdif_mutex); + if (mout->dig_out_used == HDA_DIG_ANALOG_DUP) + /* already opened as analog dup; reset it once */ + cleanup_dig_out_stream(codec, mout->dig_out_nid); + mout->dig_out_used = HDA_DIG_EXCLUSIVE; + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +int snd_hda_multi_out_dig_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + mutex_lock(&codec->spdif_mutex); + setup_dig_out_stream(codec, mout->dig_out_nid, stream_tag, format); + mutex_unlock(&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) +{ + mutex_lock(&codec->spdif_mutex); + mout->dig_out_used = 0; + mutex_unlock(&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, + struct snd_pcm_substream *substream, + struct hda_pcm_stream *hinfo) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + runtime->hw.channels_max = mout->max_channels; + if (mout->dig_out_nid) { + if (!mout->analog_rates) { + mout->analog_rates = hinfo->rates; + mout->analog_formats = hinfo->formats; + mout->analog_maxbps = hinfo->maxbps; + } else { + runtime->hw.rates = mout->analog_rates; + runtime->hw.formats = mout->analog_formats; + hinfo->maxbps = mout->analog_maxbps; + } + if (!mout->spdif_rates) { + snd_hda_query_supported_pcm(codec, mout->dig_out_nid, + &mout->spdif_rates, + &mout->spdif_formats, + &mout->spdif_maxbps); + } + mutex_lock(&codec->spdif_mutex); + if (mout->share_spdif) { + runtime->hw.rates &= mout->spdif_rates; + runtime->hw.formats &= mout->spdif_formats; + if (mout->spdif_maxbps < hinfo->maxbps) + hinfo->maxbps = mout->spdif_maxbps; + } + mutex_unlock(&codec->spdif_mutex); + } + 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, + struct snd_pcm_substream *substream) +{ + hda_nid_t *nids = mout->dac_nids; + int chs = substream->runtime->channels; + int i; + + mutex_lock(&codec->spdif_mutex); + if (mout->dig_out_nid && mout->share_spdif && + 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_dig_out_stream(codec, mout->dig_out_nid, + stream_tag, format); + } else { + mout->dig_out_used = 0; + cleanup_dig_out_stream(codec, mout->dig_out_nid); + } + } + mutex_unlock(&codec->spdif_mutex); + + /* front */ + snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag, + 0, format); + if (!mout->no_share_stream && + mout->hp_nid && mout->hp_nid != nids[HDA_FRONT]) + /* headphone out will just decode front left/right (stereo) */ + snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, + 0, format); + /* extra outputs copied from front */ + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) + if (!mout->no_share_stream && mout->extra_out_nid[i]) + snd_hda_codec_setup_stream(codec, + mout->extra_out_nid[i], + stream_tag, 0, format); + + /* surrounds */ + for (i = 1; i < mout->num_dacs; i++) { + if (chs >= (i + 1) * 2) /* independent out */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + i * 2, format); + else if (!mout->no_share_stream) /* copy front */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + 0, 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_cleanup_stream(codec, nids[i]); + if (mout->hp_nid) + snd_hda_codec_cleanup_stream(codec, mout->hp_nid); + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) + if (mout->extra_out_nid[i]) + snd_hda_codec_cleanup_stream(codec, + mout->extra_out_nid[i]); + mutex_lock(&codec->spdif_mutex); + if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) { + cleanup_dig_out_stream(codec, mout->dig_out_nid); + mout->dig_out_used = 0; + } + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +/* + * Helper for automatic pin configuration + */ + +static int is_in_nid_list(hda_nid_t nid, hda_nid_t *list) +{ + for (; *list; list++) + if (*list == nid) + return 1; + return 0; +} + + +/* + * Sort an associated group of pins according to their sequence numbers. + */ +static void sort_pins_by_sequence(hda_nid_t * pins, short * sequences, + int num_pins) +{ + int i, j; + short seq; + hda_nid_t nid; + + for (i = 0; i < num_pins; i++) { + for (j = i + 1; j < num_pins; j++) { + if (sequences[i] > sequences[j]) { + seq = sequences[i]; + sequences[i] = sequences[j]; + sequences[j] = seq; + nid = pins[i]; + pins[i] = pins[j]; + pins[j] = nid; + } + } + } +} + + +/* + * Parse all pin widgets and store the useful pin nids to cfg + * + * The number of line-outs or any primary output is stored in line_outs, + * and the corresponding output pins are assigned to line_out_pins[], + * in the order of front, rear, CLFE, side, ... + * + * If more extra outputs (speaker and headphone) are found, the pins are + * assisnged to hp_pins[] and speaker_pins[], respectively. If no line-out jack + * is detected, one of speaker of HP pins is assigned as the primary + * output, i.e. to line_out_pins[0]. So, line_outs is always positive + * if any analog output exists. + * + * The analog input pins are assigned to input_pins array. + * The digital input/output pins are assigned to dig_in_pin and dig_out_pin, + * respectively. + */ +int snd_hda_parse_pin_def_config(struct hda_codec *codec, + struct auto_pin_cfg *cfg, + hda_nid_t *ignore_nids) +{ + hda_nid_t nid, end_nid; + short seq, assoc_line_out, assoc_speaker; + short sequences_line_out[ARRAY_SIZE(cfg->line_out_pins)]; + short sequences_speaker[ARRAY_SIZE(cfg->speaker_pins)]; + short sequences_hp[ARRAY_SIZE(cfg->hp_pins)]; + + memset(cfg, 0, sizeof(*cfg)); + + memset(sequences_line_out, 0, sizeof(sequences_line_out)); + memset(sequences_speaker, 0, sizeof(sequences_speaker)); + memset(sequences_hp, 0, sizeof(sequences_hp)); + assoc_line_out = assoc_speaker = 0; + + end_nid = codec->start_nid + codec->num_nodes; + for (nid = codec->start_nid; nid < end_nid; nid++) { + unsigned int wid_caps = get_wcaps(codec, nid); + unsigned int wid_type = + (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + unsigned int def_conf; + short assoc, loc; + + /* read all default configuration for pin complex */ + if (wid_type != AC_WID_PIN) + continue; + /* ignore the given nids (e.g. pc-beep returns error) */ + if (ignore_nids && is_in_nid_list(nid, ignore_nids)) + continue; + + def_conf = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + continue; + loc = get_defcfg_location(def_conf); + switch (get_defcfg_device(def_conf)) { + case AC_JACK_LINE_OUT: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + + if (!(wid_caps & AC_WCAP_STEREO)) + if (!cfg->mono_out_pin) + cfg->mono_out_pin = nid; + if (!assoc) + continue; + if (!assoc_line_out) + assoc_line_out = assoc; + else if (assoc_line_out != assoc) + continue; + if (cfg->line_outs >= ARRAY_SIZE(cfg->line_out_pins)) + continue; + cfg->line_out_pins[cfg->line_outs] = nid; + sequences_line_out[cfg->line_outs] = seq; + cfg->line_outs++; + break; + case AC_JACK_SPEAKER: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + if (! assoc) + continue; + if (! assoc_speaker) + assoc_speaker = assoc; + else if (assoc_speaker != assoc) + continue; + if (cfg->speaker_outs >= ARRAY_SIZE(cfg->speaker_pins)) + continue; + cfg->speaker_pins[cfg->speaker_outs] = nid; + sequences_speaker[cfg->speaker_outs] = seq; + cfg->speaker_outs++; + break; + case AC_JACK_HP_OUT: + seq = get_defcfg_sequence(def_conf); + assoc = get_defcfg_association(def_conf); + if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins)) + continue; + cfg->hp_pins[cfg->hp_outs] = nid; + sequences_hp[cfg->hp_outs] = (assoc << 4) | seq; + cfg->hp_outs++; + break; + case AC_JACK_MIC_IN: { + int preferred, alt; + if (loc == AC_JACK_LOC_FRONT) { + preferred = AUTO_PIN_FRONT_MIC; + alt = AUTO_PIN_MIC; + } else { + preferred = AUTO_PIN_MIC; + alt = AUTO_PIN_FRONT_MIC; + } + if (!cfg->input_pins[preferred]) + cfg->input_pins[preferred] = nid; + else if (!cfg->input_pins[alt]) + cfg->input_pins[alt] = nid; + break; + } + case AC_JACK_LINE_IN: + if (loc == AC_JACK_LOC_FRONT) + cfg->input_pins[AUTO_PIN_FRONT_LINE] = nid; + else + cfg->input_pins[AUTO_PIN_LINE] = nid; + break; + case AC_JACK_CD: + cfg->input_pins[AUTO_PIN_CD] = nid; + break; + case AC_JACK_AUX: + cfg->input_pins[AUTO_PIN_AUX] = nid; + break; + case AC_JACK_SPDIF_OUT: + cfg->dig_out_pin = nid; + break; + case AC_JACK_SPDIF_IN: + cfg->dig_in_pin = nid; + break; + } + } + + /* FIX-UP: + * If no line-out is defined but multiple HPs are found, + * some of them might be the real line-outs. + */ + if (!cfg->line_outs && cfg->hp_outs > 1) { + int i = 0; + while (i < cfg->hp_outs) { + /* The real HPs should have the sequence 0x0f */ + if ((sequences_hp[i] & 0x0f) == 0x0f) { + i++; + continue; + } + /* Move it to the line-out table */ + cfg->line_out_pins[cfg->line_outs] = cfg->hp_pins[i]; + sequences_line_out[cfg->line_outs] = sequences_hp[i]; + cfg->line_outs++; + cfg->hp_outs--; + memmove(cfg->hp_pins + i, cfg->hp_pins + i + 1, + sizeof(cfg->hp_pins[0]) * (cfg->hp_outs - i)); + memmove(sequences_hp + i - 1, sequences_hp + i, + sizeof(sequences_hp[0]) * (cfg->hp_outs - i)); + } + } + + /* sort by sequence */ + sort_pins_by_sequence(cfg->line_out_pins, sequences_line_out, + cfg->line_outs); + sort_pins_by_sequence(cfg->speaker_pins, sequences_speaker, + cfg->speaker_outs); + sort_pins_by_sequence(cfg->hp_pins, sequences_hp, + cfg->hp_outs); + + /* if we have only one mic, make it AUTO_PIN_MIC */ + if (!cfg->input_pins[AUTO_PIN_MIC] && + cfg->input_pins[AUTO_PIN_FRONT_MIC]) { + cfg->input_pins[AUTO_PIN_MIC] = + cfg->input_pins[AUTO_PIN_FRONT_MIC]; + cfg->input_pins[AUTO_PIN_FRONT_MIC] = 0; + } + /* ditto for line-in */ + if (!cfg->input_pins[AUTO_PIN_LINE] && + cfg->input_pins[AUTO_PIN_FRONT_LINE]) { + cfg->input_pins[AUTO_PIN_LINE] = + cfg->input_pins[AUTO_PIN_FRONT_LINE]; + cfg->input_pins[AUTO_PIN_FRONT_LINE] = 0; + } + + /* + * FIX-UP: if no line-outs are detected, try to use speaker or HP pin + * as a primary output + */ + if (!cfg->line_outs) { + if (cfg->speaker_outs) { + cfg->line_outs = cfg->speaker_outs; + memcpy(cfg->line_out_pins, cfg->speaker_pins, + sizeof(cfg->speaker_pins)); + cfg->speaker_outs = 0; + memset(cfg->speaker_pins, 0, sizeof(cfg->speaker_pins)); + cfg->line_out_type = AUTO_PIN_SPEAKER_OUT; + } else if (cfg->hp_outs) { + cfg->line_outs = cfg->hp_outs; + memcpy(cfg->line_out_pins, cfg->hp_pins, + sizeof(cfg->hp_pins)); + cfg->hp_outs = 0; + memset(cfg->hp_pins, 0, sizeof(cfg->hp_pins)); + cfg->line_out_type = AUTO_PIN_HP_OUT; + } + } + + /* Reorder the surround channels + * ALSA sequence is front/surr/clfe/side + * HDA sequence is: + * 4-ch: front/surr => OK as it is + * 6-ch: front/clfe/surr + * 8-ch: front/clfe/rear/side|fc + */ + switch (cfg->line_outs) { + case 3: + case 4: + nid = cfg->line_out_pins[1]; + cfg->line_out_pins[1] = cfg->line_out_pins[2]; + cfg->line_out_pins[2] = nid; + break; + } + + /* + * debug prints of the parsed results + */ + snd_printd("autoconfig: line_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + cfg->line_outs, cfg->line_out_pins[0], cfg->line_out_pins[1], + cfg->line_out_pins[2], cfg->line_out_pins[3], + cfg->line_out_pins[4]); + snd_printd(" speaker_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + cfg->speaker_outs, cfg->speaker_pins[0], + cfg->speaker_pins[1], cfg->speaker_pins[2], + cfg->speaker_pins[3], cfg->speaker_pins[4]); + snd_printd(" hp_outs=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + cfg->hp_outs, cfg->hp_pins[0], + cfg->hp_pins[1], cfg->hp_pins[2], + cfg->hp_pins[3], cfg->hp_pins[4]); + snd_printd(" mono: mono_out=0x%x\n", cfg->mono_out_pin); + snd_printd(" inputs: mic=0x%x, fmic=0x%x, line=0x%x, fline=0x%x," + " cd=0x%x, aux=0x%x\n", + cfg->input_pins[AUTO_PIN_MIC], + cfg->input_pins[AUTO_PIN_FRONT_MIC], + cfg->input_pins[AUTO_PIN_LINE], + cfg->input_pins[AUTO_PIN_FRONT_LINE], + cfg->input_pins[AUTO_PIN_CD], + cfg->input_pins[AUTO_PIN_AUX]); + + return 0; +} + +/* labels for input pins */ +const char *auto_pin_cfg_labels[AUTO_PIN_LAST] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux" +}; + + +#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 hda_codec *codec; + + list_for_each_entry(codec, &bus->codec_list, list) { +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!codec->power_on) + continue; +#endif + hda_call_codec_suspend(codec); + } + return 0; +} + +/** + * snd_hda_resume - resume the codecs + * @bus: the HDA bus + * @state: resume state + * + * Returns 0 if successful. + * + * This fucntion is defined only when POWER_SAVE isn't set. + * In the power-save mode, the codec is resumed dynamically. + */ +int snd_hda_resume(struct hda_bus *bus) +{ + struct hda_codec *codec; + + list_for_each_entry(codec, &bus->codec_list, list) { + if (snd_hda_codec_needs_resume(codec)) + hda_call_codec_resume(codec); + } + return 0; +} +#ifdef CONFIG_SND_HDA_POWER_SAVE +int snd_hda_codecs_inuse(struct hda_bus *bus) +{ + struct hda_codec *codec; + + list_for_each_entry(codec, &bus->codec_list, list) { + if (snd_hda_codec_needs_resume(codec)) + return 1; + } + return 0; +} +#endif +#endif diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h new file mode 100644 index 0000000..60468f5 --- /dev/null +++ b/sound/pci/hda/hda_codec.h @@ -0,0 +1,848 @@ +/* + * 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> +#include <sound/hwdep.h> + +#if defined(CONFIG_PM) || defined(CONFIG_SND_HDA_POWER_SAVE) +#define SND_HDA_NEEDS_RESUME /* resume control code is required */ +#endif + +/* + * 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_1 0x0f0d +#define AC_VERB_GET_DIGI_CONVERT_2 0x0f0e /* unused */ +#define AC_VERB_GET_VOLUME_KNOB_CONTROL 0x0f0f +/* f10-f1a: GPIO */ +#define AC_VERB_GET_GPIO_DATA 0x0f15 +#define AC_VERB_GET_GPIO_MASK 0x0f16 +#define AC_VERB_GET_GPIO_DIRECTION 0x0f17 +#define AC_VERB_GET_GPIO_WAKE_MASK 0x0f18 +#define AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK 0x0f19 +#define AC_VERB_GET_GPIO_STICKY_MASK 0x0f1a +#define AC_VERB_GET_CONFIG_DEFAULT 0x0f1c +/* f20: AFG/MFG */ +#define AC_VERB_GET_SUBSYSTEM_ID 0x0f20 +#define AC_VERB_GET_CVT_CHAN_COUNT 0x0f2d +#define AC_VERB_GET_HDMI_DIP_SIZE 0x0f2e +#define AC_VERB_GET_HDMI_ELDD 0x0f2f +#define AC_VERB_GET_HDMI_DIP_INDEX 0x0f30 +#define AC_VERB_GET_HDMI_DIP_DATA 0x0f31 +#define AC_VERB_GET_HDMI_DIP_XMIT 0x0f32 +#define AC_VERB_GET_HDMI_CP_CTRL 0x0f33 +#define AC_VERB_GET_HDMI_CHAN_SLOT 0x0f34 + +/* + * 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_BTLENABLE 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_GPIO_DATA 0x715 +#define AC_VERB_SET_GPIO_MASK 0x716 +#define AC_VERB_SET_GPIO_DIRECTION 0x717 +#define AC_VERB_SET_GPIO_WAKE_MASK 0x718 +#define AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK 0x719 +#define AC_VERB_SET_GPIO_STICKY_MASK 0x71a +#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_EAPD 0x788 +#define AC_VERB_SET_CODEC_RESET 0x7ff +#define AC_VERB_SET_CVT_CHAN_COUNT 0x72d +#define AC_VERB_SET_HDMI_DIP_INDEX 0x730 +#define AC_VERB_SET_HDMI_DIP_DATA 0x731 +#define AC_VERB_SET_HDMI_DIP_XMIT 0x732 +#define AC_VERB_SET_HDMI_CP_CTRL 0x733 +#define AC_VERB_SET_HDMI_CHAN_SLOT 0x734 + +/* + * 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 +#define AC_PAR_VOL_KNB_CAP 0x13 +#define AC_PAR_HDMI_LPCM_CAP 0x20 + +/* + * 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_CP_CAPS (1<<12) /* content protection */ +#define AC_WCAP_CHAN_CNT_EXT (7<<13) /* channel count ext */ +#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) + +/* GP I/O count */ +#define AC_GPIO_IO_COUNT (0xff<<0) +#define AC_GPIO_O_COUNT (0xff<<8) +#define AC_GPIO_O_COUNT_SHIFT 8 +#define AC_GPIO_I_COUNT (0xff<<16) +#define AC_GPIO_I_COUNT_SHIFT 16 +#define AC_GPIO_UNSOLICITED (1<<30) +#define AC_GPIO_WAKE (1<<31) + +/* Converter stream, channel */ +#define AC_CONV_CHANNEL (0xf<<0) +#define AC_CONV_STREAM (0xf<<4) +#define AC_CONV_STREAM_SHIFT 4 + +/* Input converter SDI select */ +#define AC_SDI_SELECT (0xf<<0) + +/* Unsolicited response control */ +#define AC_UNSOL_TAG (0x3f<<0) +#define AC_UNSOL_ENABLED (1<<7) +#define AC_USRSP_EN AC_UNSOL_ENABLED + +/* Unsolicited responses */ +#define AC_UNSOL_RES_TAG (0x3f<<26) +#define AC_UNSOL_RES_TAG_SHIFT 26 +#define AC_UNSOL_RES_SUBTAG (0x1f<<21) +#define AC_UNSOL_RES_SUBTAG_SHIFT 21 +#define AC_UNSOL_RES_ELDV (1<<1) /* ELD Data valid (for HDMI) */ +#define AC_UNSOL_RES_PD (1<<0) /* pinsense detect */ +#define AC_UNSOL_RES_CP_STATE (1<<1) /* content protection */ +#define AC_UNSOL_RES_CP_READY (1<<0) /* content protection */ + +/* 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 */ +/* Note: This LR_SWAP pincap is defined in the Realtek ALC883 specification, + * but is marked reserved in the Intel HDA specification. + */ +#define AC_PINCAP_LR_SWAP (1<<7) /* L/R swap */ +/* Note: The same bit as LR_SWAP is newly defined as HDMI capability + * in HD-audio specification + */ +#define AC_PINCAP_HDMI (1<<7) /* HDMI pin */ +#define AC_PINCAP_VREF (0x37<<8) +#define AC_PINCAP_VREF_SHIFT 8 +#define AC_PINCAP_EAPD (1<<16) /* EAPD capable */ +/* Vref status (used in pin cap) */ +#define AC_PINCAP_VREF_HIZ (1<<0) /* Hi-Z */ +#define AC_PINCAP_VREF_50 (1<<1) /* 50% */ +#define AC_PINCAP_VREF_GRD (1<<2) /* ground */ +#define AC_PINCAP_VREF_80 (1<<4) /* 80% */ +#define AC_PINCAP_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) + +/* Power state values */ +#define AC_PWRST_SETTING (0xf<<0) +#define AC_PWRST_ACTUAL (0xf<<4) +#define AC_PWRST_ACTUAL_SHIFT 4 +#define AC_PWRST_D0 0x00 +#define AC_PWRST_D1 0x01 +#define AC_PWRST_D2 0x02 +#define AC_PWRST_D3 0x03 + +/* Processing capabilies */ +#define AC_PCAP_BENIGN (1<<0) +#define AC_PCAP_NUM_COEF (0xff<<8) +#define AC_PCAP_NUM_COEF_SHIFT 8 + +/* Volume knobs capabilities */ +#define AC_KNBCAP_NUM_STEPS (0x7f<<0) +#define AC_KNBCAP_DELTA (1<<7) + +/* HDMI LPCM capabilities */ +#define AC_LPCMCAP_48K_CP_CHNS (0x0f<<0) /* max channels w/ CP-on */ +#define AC_LPCMCAP_48K_NO_CHNS (0x0f<<4) /* max channels w/o CP-on */ +#define AC_LPCMCAP_48K_20BIT (1<<8) /* 20b bitrate supported */ +#define AC_LPCMCAP_48K_24BIT (1<<9) /* 24b bitrate supported */ +#define AC_LPCMCAP_96K_CP_CHNS (0x0f<<10) /* max channels w/ CP-on */ +#define AC_LPCMCAP_96K_NO_CHNS (0x0f<<14) /* max channels w/o CP-on */ +#define AC_LPCMCAP_96K_20BIT (1<<18) /* 20b bitrate supported */ +#define AC_LPCMCAP_96K_24BIT (1<<19) /* 24b bitrate supported */ +#define AC_LPCMCAP_192K_CP_CHNS (0x0f<<20) /* max channels w/ CP-on */ +#define AC_LPCMCAP_192K_NO_CHNS (0x0f<<24) /* max channels w/o CP-on */ +#define AC_LPCMCAP_192K_20BIT (1<<28) /* 20b bitrate supported */ +#define AC_LPCMCAP_192K_24BIT (1<<29) /* 24b bitrate supported */ +#define AC_LPCMCAP_44K (1<<30) /* 44.1kHz support */ +#define AC_LPCMCAP_44K_MS (1<<31) /* 44.1kHz-multiplies support */ + +/* + * Control Parameters + */ + +/* Amp gain/mute */ +#define AC_AMP_MUTE (1<<7) +#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) + +/* DIGITAL2 bits */ +#define AC_DIG2_CC (0x7f<<0) + +/* Pin widget control - 8bit */ +#define AC_PINCTL_VREFEN (0x7<<0) +#define AC_PINCTL_VREF_HIZ 0 /* Hi-Z */ +#define AC_PINCTL_VREF_50 1 /* 50% */ +#define AC_PINCTL_VREF_GRD 2 /* ground */ +#define AC_PINCTL_VREF_80 4 /* 80% */ +#define AC_PINCTL_VREF_100 5 /* 100% */ +#define AC_PINCTL_IN_EN (1<<5) +#define AC_PINCTL_OUT_EN (1<<6) +#define AC_PINCTL_HP_EN (1<<7) + +/* Pin sense - 32bit */ +#define AC_PINSENSE_IMPEDANCE_MASK (0x7fffffff) +#define AC_PINSENSE_PRESENCE (1<<31) +#define AC_PINSENSE_ELDV (1<<30) /* ELD valid (HDMI) */ + +/* EAPD/BTL enable - 32bit */ +#define AC_EAPDBTL_BALANCED (1<<0) +#define AC_EAPDBTL_EAPD (1<<1) +#define AC_EAPDBTL_LR_SWAP (1<<2) + +/* HDMI ELD data */ +#define AC_ELDD_ELD_VALID (1<<31) +#define AC_ELDD_ELD_DATA 0xff + +/* HDMI DIP size */ +#define AC_DIPSIZE_ELD_BUF (1<<3) /* ELD buf size of packet size */ +#define AC_DIPSIZE_PACK_IDX (0x07<<0) /* packet index */ + +/* HDMI DIP index */ +#define AC_DIPIDX_PACK_IDX (0x07<<5) /* packet idnex */ +#define AC_DIPIDX_BYTE_IDX (0x1f<<0) /* byte index */ + +/* HDMI DIP xmit (transmit) control */ +#define AC_DIPXMIT_MASK (0x3<<6) +#define AC_DIPXMIT_DISABLE (0x0<<6) /* disable xmit */ +#define AC_DIPXMIT_ONCE (0x2<<6) /* xmit once then disable */ +#define AC_DIPXMIT_BEST (0x3<<6) /* best effort */ + +/* HDMI content protection (CP) control */ +#define AC_CPCTRL_CES (1<<9) /* current encryption state */ +#define AC_CPCTRL_READY (1<<8) /* ready bit */ +#define AC_CPCTRL_SUBTAG (0x1f<<3) /* subtag for unsol-resp */ +#define AC_CPCTRL_STATE (3<<0) /* current CP request state */ + +/* Converter channel <-> HDMI slot mapping */ +#define AC_CVTMAP_HDMI_SLOT (0xf<<0) /* HDMI slot number */ +#define AC_CVTMAP_CHAN (0xf<<4) /* converter channel number */ + +/* configuration default - 32bit */ +#define AC_DEFCFG_SEQUENCE (0xf<<0) +#define AC_DEFCFG_DEF_ASSOC (0xf<<4) +#define AC_DEFCFG_ASSOC_SHIFT 4 +#define AC_DEFCFG_MISC (0xf<<8) +#define AC_DEFCFG_MISC_SHIFT 8 +#define AC_DEFCFG_MISC_NO_PRESENCE (1<<0) +#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 32 + +/* max. codec address */ +#define HDA_MAX_CODEC_ADDRESS 0x0f + +/* + * Structures + */ + +struct hda_bus; +struct hda_beep; +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 *); +#ifdef CONFIG_SND_HDA_POWER_SAVE + /* notify power-up/down from codec to controller */ + void (*pm_notify)(struct hda_codec *codec); +#endif +}; + +/* 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 { + struct snd_card *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; + /* link caddr -> codec */ + struct hda_codec *caddr_tbl[HDA_MAX_CODEC_ADDRESS + 1]; + + struct mutex cmd_mutex; + + /* unsolicited event queue */ + struct hda_bus_unsolicited *unsol; + + struct snd_info_entry *proc; + + /* misc op flags */ + unsigned int needs_damn_long_delay :1; +}; + +/* + * 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; + hda_nid_t afg, mfg; + 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 SND_HDA_NEEDS_RESUME + int (*suspend)(struct hda_codec *codec, pm_message_t state); + int (*resume)(struct hda_codec *codec); +#endif +#ifdef CONFIG_SND_HDA_POWER_SAVE + int (*check_power_status)(struct hda_codec *codec, hda_nid_t nid); +#endif +}; + +/* record for amp information cache */ +struct hda_cache_head { + u32 key; /* hash key */ + u16 val; /* assigned value */ + u16 next; /* next link; -1 = terminal */ +}; + +struct hda_amp_info { + struct hda_cache_head head; + u32 amp_caps; /* amp capabilities */ + u16 vol[2]; /* current volume & mute */ +}; + +struct hda_cache_rec { + u16 hash[64]; /* hash table for index */ + unsigned int num_entries; /* number of assigned entries */ + unsigned int size; /* allocated size */ + unsigned int record_size; /* record size (including header) */ + void *buffer; /* hash table entries */ +}; + +/* PCM callbacks */ +struct hda_pcm_ops { + int (*open)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *substream); + int (*close)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *substream); + int (*prepare)(struct hda_pcm_stream *info, struct hda_codec *codec, + unsigned int stream_tag, unsigned int format, + struct snd_pcm_substream *substream); + int (*cleanup)(struct hda_pcm_stream *info, struct hda_codec *codec, + struct snd_pcm_substream *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; +}; + +/* PCM types */ +enum { + HDA_PCM_TYPE_AUDIO, + HDA_PCM_TYPE_SPDIF, + HDA_PCM_TYPE_HDMI, + HDA_PCM_TYPE_MODEM, + HDA_PCM_NTYPES +}; + +/* for PCM creation */ +struct hda_pcm { + char *name; + struct hda_pcm_stream stream[2]; + unsigned int pcm_type; /* HDA_PCM_TYPE_XXX */ + int device; /* assigned device number */ +}; + +/* 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 */ + hda_nid_t mfg; /* MFG 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; + + /* PCM to create, set by patch_ops.build_pcms callback */ + unsigned int num_pcms; + struct hda_pcm *pcm_info; + + /* codec specific info */ + void *spec; + + /* beep device */ + struct hda_beep *beep; + + /* widget capabilities cache */ + unsigned int num_nodes; + hda_nid_t start_nid; + u32 *wcaps; + + struct hda_cache_rec amp_cache; /* cache for amp access */ + struct hda_cache_rec cmd_cache; /* cache for other commands */ + + struct mutex spdif_mutex; + unsigned int spdif_status; /* IEC958 status bits */ + unsigned short spdif_ctls; /* SPDIF control bits */ + unsigned int spdif_in_enable; /* SPDIF input enable? */ + hda_nid_t *slave_dig_outs; /* optional digital out slave widgets */ + + struct snd_hwdep *hwdep; /* assigned hwdep device */ + + /* misc flags */ + unsigned int spdif_status_reset :1; /* needs to toggle SPDIF for each + * status change + * (e.g. Realtek codecs) + */ +#ifdef CONFIG_SND_HDA_POWER_SAVE + unsigned int power_on :1; /* current (global) power-state */ + unsigned int power_transition :1; /* power-state in transition */ + int power_count; /* current (global) power refcount */ + struct delayed_work power_work; /* delayed task for powerdown */ +#endif +}; + +/* direction */ +enum { + HDA_INPUT, HDA_OUTPUT +}; + + +/* + * constructors + */ +int snd_hda_bus_new(struct snd_card *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); + +/* cached write */ +#ifdef SND_HDA_NEEDS_RESUME +int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid, + int direct, unsigned int verb, unsigned int parm); +void snd_hda_sequence_write_cache(struct hda_codec *codec, + const struct hda_verb *seq); +void snd_hda_codec_resume_cache(struct hda_codec *codec); +#else +#define snd_hda_codec_write_cache snd_hda_codec_write +#define snd_hda_sequence_write_cache snd_hda_sequence_write +#endif + +/* + * 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); +void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid); +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 + +/* + * power saving + */ +#ifdef CONFIG_SND_HDA_POWER_SAVE +void snd_hda_power_up(struct hda_codec *codec); +void snd_hda_power_down(struct hda_codec *codec); +#define snd_hda_codec_needs_resume(codec) codec->power_count +int snd_hda_codecs_inuse(struct hda_bus *bus); +#else +static inline void snd_hda_power_up(struct hda_codec *codec) {} +static inline void snd_hda_power_down(struct hda_codec *codec) {} +#define snd_hda_codec_needs_resume(codec) 1 +#define snd_hda_codecs_inuse(bus) 1 +#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 0000000..0ca3089 --- /dev/null +++ b/sound/pci/hda/hda_generic.c @@ -0,0 +1,1099 @@ +/* + * 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 <linux/init.h> +#include <linux/slab.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_nid_t slist[2]; /* temporay list */ + 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; +}; + +/* patch-specific record */ + +#define MAX_PCM_VOLS 2 +struct pcm_vol { + struct hda_gnode *node; /* Node for PCM volume */ + unsigned int index; /* connection of PCM volume */ +}; + +struct hda_gspec { + struct hda_gnode *dac_node[2]; /* DAC node */ + struct hda_gnode *out_pin_node[2]; /* Output pin (Line-Out) node */ + struct pcm_vol pcm_vol[MAX_PCM_VOLS]; /* PCM volumes */ + unsigned int pcm_vol_nodes; /* number of PCM volumes */ + + 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 */ + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define MAX_LOOPBACK_AMPS 7 + struct hda_loopback_check loopback; + int num_loopbacks; + struct hda_amp_list loopback_list[MAX_LOOPBACK_AMPS + 1]; +#endif +}; + +/* + * retrieve the default device type from the default config value + */ +#define defcfg_type(node) (((node)->def_cfg & AC_DEFCFG_DEVICE) >> \ + AC_DEFCFG_DEVICE_SHIFT) +#define defcfg_location(node) (((node)->def_cfg & AC_DEFCFG_LOCATION) >> \ + AC_DEFCFG_LOCATION_SHIFT) +#define defcfg_port_conn(node) (((node)->def_cfg & AC_DEFCFG_PORT_CONN) >> \ + AC_DEFCFG_PORT_CONN_SHIFT) + +/* + * destructor + */ +static void snd_hda_generic_free(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + struct hda_gnode *node, *n; + + if (! spec) + return; + /* free all widgets */ + list_for_each_entry_safe(node, n, &spec->nid_list, list) { + if (node->conn_list != node->slist) + kfree(node->conn_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; + hda_nid_t conn_list[HDA_MAX_CONNECTIONS]; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (node == NULL) + return -ENOMEM; + node->nid = nid; + nconns = snd_hda_get_connections(codec, nid, conn_list, + HDA_MAX_CONNECTIONS); + if (nconns < 0) { + kfree(node); + return nconns; + } + if (nconns <= ARRAY_SIZE(node->slist)) + node->conn_list = node->slist; + else { + node->conn_list = kmalloc(sizeof(hda_nid_t) * nconns, + GFP_KERNEL); + if (! node->conn_list) { + snd_printk(KERN_ERR "hda-generic: cannot malloc\n"); + kfree(node); + return -ENOMEM; + } + } + memcpy(node->conn_list, conn_list, nconns * sizeof(hda_nid_t)); + node->nconns = nconns; + node->wid_caps = get_wcaps(codec, nid); + 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; + + if (snd_BUG_ON(!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 hda_gnode *node; + + list_for_each_entry(node, &spec->nid_list, 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; + snd_hda_codec_amp_stereo(codec, node->nid, HDA_OUTPUT, 0, 0xff, val); + return 0; +} + +/* + * 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; + snd_hda_codec_amp_stereo(codec, node->nid, HDA_INPUT, index, 0xff, val); + return 0; +} + +/* + * 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_cache(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 hda_gnode *node; + + list_for_each_entry(node, &spec->nid_list, 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 dac_idx) +{ + 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[dac_idx]) { + /* already DAC node is assigned, just unmute & connect */ + return node == spec->dac_node[dac_idx]; + } + spec->dac_node[dac_idx] = node; + if ((node->wid_caps & AC_WCAP_OUT_AMP) && + spec->pcm_vol_nodes < MAX_PCM_VOLS) { + spec->pcm_vol[spec->pcm_vol_nodes].node = node; + spec->pcm_vol[spec->pcm_vol_nodes].index = 0; + spec->pcm_vol_nodes++; + } + 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, dac_idx); + 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->dac_node[dac_idx] && + spec->pcm_vol_nodes < MAX_PCM_VOLS && + !(spec->dac_node[dac_idx]->wid_caps & + AC_WCAP_OUT_AMP)) { + if ((node->wid_caps & AC_WCAP_IN_AMP) || + (node->wid_caps & AC_WCAP_OUT_AMP)) { + int n = spec->pcm_vol_nodes; + spec->pcm_vol[n].node = node; + spec->pcm_vol[n].index = i; + spec->pcm_vol_nodes++; + } + } + 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 hda_gnode *node; + int err; + + list_for_each_entry(node, &spec->nid_list, list) { + if (node->type != AC_WID_PIN) + continue; + /* output capable? */ + if (! (node->pin_caps & AC_PINCAP_OUT)) + continue; + if (defcfg_port_conn(node) == AC_JACK_PORT_NONE) + continue; /* unconnected */ + if (jack_type >= 0) { + if (jack_type != 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, 0); + if (err < 0) + return NULL; + if (! err && spec->out_pin_node[0]) { + err = parse_output_path(codec, spec, node, 1); + if (err < 0) + return NULL; + } + if (err > 0) { + /* unmute the PIN output */ + unmute_output(codec, node); + /* set PIN-Out enable */ + snd_hda_codec_write_cache(codec, node->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + AC_PINCTL_OUT_EN | + ((node->pin_caps & AC_PINCAP_HP_DRV) ? + AC_PINCTL_HP_EN : 0)); + 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[0] = node; + else { + /* if no line-out is found, try speaker out */ + node = parse_output_jack(codec, spec, AC_JACK_SPEAKER); + if (node) + spec->out_pin_node[0] = node; + } + /* look for the HP-out pin */ + node = parse_output_jack(codec, spec, AC_JACK_HP_OUT); + if (node) { + if (! spec->out_pin_node[0]) + spec->out_pin_node[0] = node; + else + spec->out_pin_node[1] = node; + } + + if (! spec->out_pin_node[0]) { + /* no line-out or HP pins found, + * then choose for the first output pin + */ + spec->out_pin_node[0] = parse_output_jack(codec, spec, -1); + if (! spec->out_pin_node[0]) + snd_printd("hda_generic: no proper output path found\n"); + } + + return 0; +} + +/* + * input MUX + */ + +/* control callbacks */ +static int capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *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 = defcfg_location(node); + switch (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 0 + if (pinctl) + *pinctl |= AC_PINCTL_VREF_GRD; +#endif + return "CD"; + case AC_JACK_AUX: + if ((location & 0x0f) == AC_JACK_LOC_FRONT) + return "Front Aux"; + return "Aux"; + case AC_JACK_MIC_IN: + if (pinctl && + (node->pin_caps & + (AC_PINCAP_VREF_80 << AC_PINCAP_VREF_SHIFT))) + *pinctl |= AC_PINCTL_VREF_80; + 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 (defcfg_port_conn(node) == AC_JACK_PORT_NONE) + return 0; /* unconnected */ + + 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_cache(codec, node->nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pinctl); + + return 1; /* found */ +} + +/* add a capture source element */ +static void add_cap_src(struct hda_gspec *spec, int idx) +{ + struct hda_input_mux_item *csrc; + char *buf; + int num, ocap; + + num = spec->input_mux.num_items; + csrc = &spec->input_mux.items[num]; + buf = spec->cap_labels[num]; + for (ocap = 0; ocap < num; 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], num); + break; + } + } + csrc->index = idx; + spec->input_mux.num_items++; +} + +/* + * 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. + */ + /* first, check the direct connections to PIN widgets */ + for (i = 0; i < adc_node->nconns; i++) { + node = hda_get_node(spec, adc_node->conn_list[i]); + if (node && node->type == AC_WID_PIN) { + err = parse_adc_sub_nodes(codec, spec, node); + if (err < 0) + return err; + else if (err > 0) + add_cap_src(spec, i); + } + } + /* ... then check the rests, more complicated connections */ + for (i = 0; i < adc_node->nconns; i++) { + node = hda_get_node(spec, adc_node->conn_list[i]); + if (node && node->type != AC_WID_PIN) { + err = parse_adc_sub_nodes(codec, spec, node); + if (err < 0) + return err; + else if (err > 0) + add_cap_src(spec, i); + } + } + + 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 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_entry(node, &spec->nid_list, 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; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static void add_input_loopback(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx) +{ + struct hda_gspec *spec = codec->spec; + struct hda_amp_list *p; + + if (spec->num_loopbacks >= MAX_LOOPBACK_AMPS) { + snd_printk(KERN_ERR "hda_generic: Too many loopback ctls\n"); + return; + } + p = &spec->loopback_list[spec->num_loopbacks++]; + p->nid = nid; + p->dir = dir; + p->idx = idx; + spec->loopback.amplist = spec->loopback_list; +} +#else +#define add_input_loopback(codec,nid,dir,idx) +#endif + +/* + * create mixer controls if possible + */ +static int create_mixer(struct hda_codec *codec, struct hda_gnode *node, + unsigned int index, const char *type, + const char *dir_sfx, int is_loopback) +{ + char name[32]; + int err; + int created = 0; + struct snd_kcontrol_new 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 = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, index, HDA_INPUT); + if (is_loopback) + add_input_loopback(codec, node->nid, HDA_INPUT, index); + 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 = (struct snd_kcontrol_new)HDA_CODEC_MUTE(name, node->nid, 0, HDA_OUTPUT); + if (is_loopback) + add_input_loopback(codec, node->nid, HDA_OUTPUT, 0); + 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 = (struct snd_kcontrol_new)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 = (struct snd_kcontrol_new)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) +{ + struct snd_ctl_elem_id 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 create_output_mixers(struct hda_codec *codec, const char **names) +{ + struct hda_gspec *spec = codec->spec; + int i, err; + + for (i = 0; i < spec->pcm_vol_nodes; i++) { + err = create_mixer(codec, spec->pcm_vol[i].node, + spec->pcm_vol[i].index, + names[i], "Playback", 0); + if (err < 0) + return err; + } + return 0; +} + +static int build_output_controls(struct hda_codec *codec) +{ + struct hda_gspec *spec = codec->spec; + static const char *types_speaker[] = { "Speaker", "Headphone" }; + static const char *types_line[] = { "Front", "Headphone" }; + + switch (spec->pcm_vol_nodes) { + case 1: + return create_mixer(codec, spec->pcm_vol[0].node, + spec->pcm_vol[0].index, + "Master", "Playback", 0); + case 2: + if (defcfg_type(spec->out_pin_node[0]) == AC_JACK_SPEAKER) + return create_output_mixers(codec, types_speaker); + else + return create_output_mixers(codec, types_line); + } + 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 i, err; + static struct snd_kcontrol_new cap_sel = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = capture_source_info, + .get = capture_source_get, + .put = capture_source_put, + }; + + if (! adc_node || ! spec->input_mux.num_items) + return 0; /* not found */ + + spec->cur_cap_src = 0; + select_input_connection(codec, adc_node, + spec->input_mux.items[0].index); + + /* create capture volume and switch controls if the ADC has an amp */ + /* do we have only a single item? */ + if (spec->input_mux.num_items == 1) { + err = create_mixer(codec, adc_node, + spec->input_mux.items[0].index, + NULL, "Capture", 0); + if (err < 0) + return err; + return 0; + } + + /* create input MUX if multiple sources are available */ + if ((err = snd_ctl_add(codec->bus->card, + snd_ctl_new1(&cap_sel, codec))) < 0) + return err; + + /* no volume control? */ + if (! (adc_node->wid_caps & AC_WCAP_IN_AMP) || + ! (adc_node->amp_in_caps & AC_AMPCAP_NUM_STEPS)) + return 0; + + for (i = 0; i < spec->input_mux.num_items; i++) { + struct snd_kcontrol_new knew; + char name[32]; + sprintf(name, "%s Capture Volume", + spec->input_mux.items[i].label); + knew = (struct snd_kcontrol_new) + HDA_CODEC_VOLUME(name, adc_node->nid, + spec->input_mux.items[i].index, + HDA_INPUT); + if ((err = snd_ctl_add(codec->bus->card, + snd_ctl_new1(&knew, codec))) < 0) + return err; + } + + 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", 1); + 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 hda_gnode *node; + int err; + const char *type; + + if (! spec->out_pin_node[0]) + return 0; + + list_for_each_entry(node, &spec->nid_list, 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[0], + 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 generic_pcm2_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct hda_gspec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, hinfo->nid, stream_tag, 0, format); + snd_hda_codec_setup_stream(codec, spec->dac_node[1]->nid, + stream_tag, 0, format); + return 0; +} + +static int generic_pcm2_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct hda_gspec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, hinfo->nid); + snd_hda_codec_cleanup_stream(codec, spec->dac_node[1]->nid); + return 0; +} + +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[0] && ! 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[0]) { + info->stream[0] = generic_pcm_playback; + info->stream[0].nid = spec->dac_node[0]->nid; + if (spec->dac_node[1]) { + info->stream[0].ops.prepare = generic_pcm2_prepare; + info->stream[0].ops.cleanup = generic_pcm2_cleanup; + } + } + if (spec->adc_node) { + info->stream[1] = generic_pcm_playback; + info->stream[1].nid = spec->adc_node->nid; + } + + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int generic_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct hda_gspec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +#endif + + +/* + */ +static struct hda_codec_ops generic_patch_ops = { + .build_controls = build_generic_controls, + .build_pcms = build_generic_pcms, + .free = snd_hda_generic_free, +#ifdef CONFIG_SND_HDA_POWER_SAVE + .check_power_status = generic_check_power_status, +#endif +}; + +/* + * the generic parser + */ +int snd_hda_parse_generic_codec(struct hda_codec *codec) +{ + struct hda_gspec *spec; + int err; + + if(!codec->afg) + return 0; + + spec = kzalloc(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_hwdep.c b/sound/pci/hda/hda_hwdep.c new file mode 100644 index 0000000..6e18a42 --- /dev/null +++ b/sound/pci/hda/hda_hwdep.c @@ -0,0 +1,121 @@ +/* + * HWDEP Interface for HD-audio codec + * + * Copyright (c) 2007 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 <linux/init.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/compat.h> +#include <linux/mutex.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" +#include <sound/hda_hwdep.h> + +/* + * write/read an out-of-bound verb + */ +static int verb_write_ioctl(struct hda_codec *codec, + struct hda_verb_ioctl __user *arg) +{ + u32 verb, res; + + if (get_user(verb, &arg->verb)) + return -EFAULT; + res = snd_hda_codec_read(codec, verb >> 24, 0, + (verb >> 8) & 0xffff, verb & 0xff); + if (put_user(res, &arg->res)) + return -EFAULT; + return 0; +} + +static int get_wcap_ioctl(struct hda_codec *codec, + struct hda_verb_ioctl __user *arg) +{ + u32 verb, res; + + if (get_user(verb, &arg->verb)) + return -EFAULT; + res = get_wcaps(codec, verb >> 24); + if (put_user(res, &arg->res)) + return -EFAULT; + return 0; +} + + +/* + */ +static int hda_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hda_codec *codec = hw->private_data; + void __user *argp = (void __user *)arg; + + switch (cmd) { + case HDA_IOCTL_PVERSION: + return put_user(HDA_HWDEP_VERSION, (int __user *)argp); + case HDA_IOCTL_VERB_WRITE: + return verb_write_ioctl(codec, argp); + case HDA_IOCTL_GET_WCAP: + return get_wcap_ioctl(codec, argp); + } + return -ENOIOCTLCMD; +} + +#ifdef CONFIG_COMPAT +static int hda_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hda_hwdep_ioctl(hw, file, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ +#ifndef CONFIG_SND_DEBUG_VERBOSE + if (!capable(CAP_SYS_RAWIO)) + return -EACCES; +#endif + return 0; +} + +int __devinit snd_hda_create_hwdep(struct hda_codec *codec) +{ + char hwname[16]; + struct snd_hwdep *hwdep; + int err; + + sprintf(hwname, "HDA Codec %d", codec->addr); + err = snd_hwdep_new(codec->bus->card, hwname, codec->addr, &hwdep); + if (err < 0) + return err; + codec->hwdep = hwdep; + sprintf(hwdep->name, "HDA Codec %d", codec->addr); + hwdep->iface = SNDRV_HWDEP_IFACE_HDA; + hwdep->private_data = codec; + hwdep->exclusive = 1; + + hwdep->ops.open = hda_hwdep_open; + hwdep->ops.ioctl = hda_hwdep_ioctl; +#ifdef CONFIG_COMPAT + hwdep->ops.ioctl_compat = hda_hwdep_ioctl_compat; +#endif + + return 0; +} diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c new file mode 100644 index 0000000..34a5b69 --- /dev/null +++ b/sound/pci/hda/hda_intel.c @@ -0,0 +1,2496 @@ +/* + * + * 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 <asm/io.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/mutex.h> +#include <linux/reboot.h> +#include <sound/core.h> +#include <sound/initval.h> +#include "hda_codec.h" + + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static char *model[SNDRV_CARDS]; +static int position_fix[SNDRV_CARDS]; +static int bdl_pos_adj[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int probe_mask[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS-1)] = -1}; +static int single_cmd; +static int enable_msi; + +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_param_array(position_fix, int, NULL, 0444); +MODULE_PARM_DESC(position_fix, "Fix DMA pointer " + "(0 = auto, 1 = none, 2 = POSBUF)."); +module_param_array(bdl_pos_adj, int, NULL, 0644); +MODULE_PARM_DESC(bdl_pos_adj, "BDL position adjustment offset."); +module_param_array(probe_mask, int, NULL, 0444); +MODULE_PARM_DESC(probe_mask, "Bitmask to probe codecs (default = -1)."); +module_param(single_cmd, bool, 0444); +MODULE_PARM_DESC(single_cmd, "Use single command to communicate with codecs " + "(for debugging only)."); +module_param(enable_msi, int, 0444); +MODULE_PARM_DESC(enable_msi, "Enable Message Signaled Interrupt (MSI)"); + +#ifdef CONFIG_SND_HDA_POWER_SAVE +/* power_save option is defined in hda_codec.c */ + +/* reset the HD-audio controller in power save mode. + * this may give more power-saving, but will take longer time to + * wake up. + */ +static int power_save_controller = 1; +module_param(power_save_controller, bool, 0644); +MODULE_PARM_DESC(power_save_controller, "Reset controller in power save mode."); +#endif + +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Intel, ICH6}," + "{Intel, ICH6M}," + "{Intel, ICH7}," + "{Intel, ESB2}," + "{Intel, ICH8}," + "{Intel, ICH9}," + "{Intel, ICH10}," + "{Intel, PCH}," + "{Intel, SCH}," + "{ATI, SB450}," + "{ATI, SB600}," + "{ATI, RS600}," + "{ATI, RS690}," + "{ATI, RS780}," + "{ATI, R600}," + "{ATI, RV630}," + "{ATI, RV610}," + "{ATI, RV670}," + "{ATI, RV635}," + "{ATI, RV620}," + "{ATI, RV770}," + "{VIA, VT8251}," + "{VIA, VT8237A}," + "{SiS, SIS966}," + "{ULI, M5461}}"); +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 */ +/* ICH, ATI and VIA have 4 playback and 4 capture */ +#define ICH6_NUM_CAPTURE 4 +#define ICH6_NUM_PLAYBACK 4 + +/* ULI has 6 playback and 5 capture */ +#define ULI_NUM_CAPTURE 5 +#define ULI_NUM_PLAYBACK 6 + +/* ATI HDMI has 1 playback and 0 capture */ +#define ATIHDMI_NUM_CAPTURE 0 +#define ATIHDMI_NUM_PLAYBACK 1 + +/* TERA has 4 playback and 3 capture */ +#define TERA_NUM_CAPTURE 3 +#define TERA_NUM_PLAYBACK 4 + +/* this number is statically defined for simplicity */ +#define MAX_AZX_DEV 16 + +/* max number of fragments - we may use more if allocating more pages for BDL */ +#define BDL_SIZE 4096 +#define AZX_MAX_BDL_ENTRIES (BDL_SIZE / 16) +#define AZX_MAX_FRAG 32 +/* 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: S3,SD2,SD1,SD0 */ +#define AZX_MAX_CODECS 4 +#define STATESTS_INT_MASK 0x0f + +/* 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_STRIPE (3 << 16) /* stripe control */ +#define SD_CTL_TRAFFIC_PRIO (1 << 18) /* traffic priority */ +#define SD_CTL_DIR (1 << 19) /* bi-directional stream */ +#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 unsolicited response enable bit */ +#define ICH6_GCTL_UREN (1<<8) + +/* 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 + +/* position fix mode */ +enum { + POS_FIX_AUTO, + POS_FIX_LPIB, + POS_FIX_POSBUF, +}; + +/* Defines for ATI HD Audio support in SB450 south bridge */ +#define ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR 0x42 +#define ATI_SB450_HDAUDIO_ENABLE_SNOOP 0x02 + +/* Defines for Nvidia HDA support */ +#define NVIDIA_HDA_TRANSREG_ADDR 0x4e +#define NVIDIA_HDA_ENABLE_COHBITS 0x0f +#define NVIDIA_HDA_ISTRM_COH 0x4d +#define NVIDIA_HDA_OSTRM_COH 0x4c +#define NVIDIA_HDA_ENABLE_COHBIT 0x01 + +/* Defines for Intel SCH HDA snoop control */ +#define INTEL_SCH_HDA_DEVC 0x78 +#define INTEL_SCH_HDA_DEVC_NOSNOOP (0x1<<11) + +/* Define IN stream 0 FIFO size offset in VIA controller */ +#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET 0x90 +/* Define VIA HD Audio Device ID*/ +#define VIA_HDAC_DEVICE_ID 0x3288 + + +/* + */ + +struct azx_dev { + struct snd_dma_buffer bdl; /* BDL buffer */ + u32 *posbuf; /* position buffer pointer */ + + unsigned int bufsize; /* size of the play buffer in bytes */ + unsigned int period_bytes; /* size of the period in bytes */ + unsigned int frags; /* number for period in the play buffer */ + unsigned int fifo_size; /* FIFO size */ + + void __iomem *sd_addr; /* stream descriptor pointer */ + + u32 sd_int_sta_mask; /* stream int status mask */ + + /* pcm support */ + struct snd_pcm_substream *substream; /* assigned substream, + * set in PCM open + */ + unsigned int format_val; /* format value to be set in the + * controller and the codec + */ + unsigned char stream_tag; /* assigned stream */ + unsigned char index; /* stream index */ + + unsigned int opened :1; + unsigned int running :1; + unsigned int irq_pending :1; + unsigned int irq_ignore :1; + /* + * For VIA: + * A flag to ensure DMA position is 0 + * when link position is not greater than FIFO size + */ + unsigned int insufficient :1; +}; + +/* CORB/RIRB */ +struct 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 azx { + struct snd_card *card; + struct pci_dev *pci; + int dev_index; + + /* chip type specific */ + int driver_type; + int playback_streams; + int playback_index_offset; + int capture_streams; + int capture_index_offset; + int num_streams; + + /* pci resources */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + /* locks */ + spinlock_t reg_lock; + struct mutex open_mutex; + + /* streams (x num_streams) */ + struct azx_dev *azx_dev; + + /* PCM */ + struct snd_pcm *pcm[AZX_MAX_PCMS]; + + /* HD codec */ + unsigned short codec_mask; + struct hda_bus *bus; + + /* CORB/RIRB */ + struct azx_rb corb; + struct azx_rb rirb; + + /* CORB/RIRB and position buffers */ + struct snd_dma_buffer rb; + struct snd_dma_buffer posbuf; + + /* flags */ + int position_fix; + unsigned int running :1; + unsigned int initialized :1; + unsigned int single_cmd :1; + unsigned int polling_mode :1; + unsigned int msi :1; + unsigned int irq_pending_warned :1; + unsigned int via_dmapos_patch :1; /* enable DMA-position fix for VIA */ + + /* for debugging */ + unsigned int last_cmd; /* last issued command (to sync) */ + + /* for pending irqs */ + struct work_struct irq_pending_work; + + /* reboot notifier (for mysterious hangup problem at power-down) */ + struct notifier_block reboot_notifier; +}; + +/* driver types */ +enum { + AZX_DRIVER_ICH, + AZX_DRIVER_SCH, + AZX_DRIVER_ATI, + AZX_DRIVER_ATIHDMI, + AZX_DRIVER_VIA, + AZX_DRIVER_SIS, + AZX_DRIVER_ULI, + AZX_DRIVER_NVIDIA, + AZX_DRIVER_TERA, + AZX_NUM_DRIVERS, /* keep this as last entry */ +}; + +static char *driver_short_names[] __devinitdata = { + [AZX_DRIVER_ICH] = "HDA Intel", + [AZX_DRIVER_SCH] = "HDA Intel MID", + [AZX_DRIVER_ATI] = "HDA ATI SB", + [AZX_DRIVER_ATIHDMI] = "HDA ATI HDMI", + [AZX_DRIVER_VIA] = "HDA VIA VT82xx", + [AZX_DRIVER_SIS] = "HDA SIS966", + [AZX_DRIVER_ULI] = "HDA ULI M5461", + [AZX_DRIVER_NVIDIA] = "HDA NVidia", + [AZX_DRIVER_TERA] = "HDA Teradici", +}; + +/* + * 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) (substream->runtime->private_data) + +static int azx_acquire_irq(struct azx *chip, int do_disconnect); + +/* + * Interface for HD codec + */ + +/* + * CORB / RIRB interface + */ +static int azx_alloc_cmd_io(struct azx *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(struct azx *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_32_bits(chip->corb.addr)); + + /* set the corb size to 256 entries (ULI requires explicitly) */ + azx_writeb(chip, CORBSIZE, 0x02); + /* set the corb write pointer to 0 */ + azx_writew(chip, CORBWP, 0); + /* reset the corb hw read pointer */ + azx_writew(chip, CORBRP, ICH6_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_32_bits(chip->rirb.addr)); + + /* set the rirb size to 256 entries (ULI requires explicitly) */ + azx_writeb(chip, RIRBSIZE, 0x02); + /* reset the rirb hw write pointer */ + azx_writew(chip, RIRBWP, ICH6_RBRWP_CLR); + /* set N=1, get RIRB response interrupt for new entry */ + azx_writew(chip, RINTCNT, 1); + /* enable rirb dma and response irq */ + azx_writeb(chip, RIRBCTL, ICH6_RBCTL_DMA_EN | ICH6_RBCTL_IRQ_EN); + chip->rirb.rp = chip->rirb.cmds = 0; +} + +static void azx_free_cmd_io(struct azx *chip) +{ + /* disable ringbuffer DMAs */ + azx_writeb(chip, RIRBCTL, 0); + azx_writeb(chip, CORBCTL, 0); +} + +/* send a command */ +static int azx_corb_send_cmd(struct hda_codec *codec, u32 val) +{ + struct azx *chip = codec->bus->private_data; + unsigned int wp; + + /* 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(struct azx *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.res = res; + smp_wmb(); + chip->rirb.cmds--; + } + } +} + +/* receive a response */ +static unsigned int azx_rirb_get_response(struct hda_codec *codec) +{ + struct azx *chip = codec->bus->private_data; + unsigned long timeout; + + again: + timeout = jiffies + msecs_to_jiffies(1000); + for (;;) { + if (chip->polling_mode) { + spin_lock_irq(&chip->reg_lock); + azx_update_rirb(chip); + spin_unlock_irq(&chip->reg_lock); + } + if (!chip->rirb.cmds) { + smp_rmb(); + return chip->rirb.res; /* the last value */ + } + if (time_after(jiffies, timeout)) + break; + if (codec->bus->needs_damn_long_delay) + msleep(2); /* temporary workaround */ + else { + udelay(10); + cond_resched(); + } + } + + if (chip->msi) { + snd_printk(KERN_WARNING "hda_intel: No response from codec, " + "disabling MSI: last cmd=0x%08x\n", chip->last_cmd); + free_irq(chip->irq, chip); + chip->irq = -1; + pci_disable_msi(chip->pci); + chip->msi = 0; + if (azx_acquire_irq(chip, 1) < 0) + return -1; + goto again; + } + + if (!chip->polling_mode) { + snd_printk(KERN_WARNING "hda_intel: azx_get_response timeout, " + "switching to polling mode: last cmd=0x%08x\n", + chip->last_cmd); + chip->polling_mode = 1; + goto again; + } + + snd_printk(KERN_ERR "hda_intel: azx_get_response timeout, " + "switching to single_cmd mode: last cmd=0x%08x\n", + chip->last_cmd); + chip->rirb.rp = azx_readb(chip, RIRBWP); + chip->rirb.cmds = 0; + /* switch to single_cmd mode */ + chip->single_cmd = 1; + azx_free_cmd_io(chip); + return -1; +} + +/* + * Use the single immediate command instead of CORB/RIRB for simplicity + * + * Note: according to Intel, this is not preferred use. The command was + * intended for the BIOS only, and may get confused with unsolicited + * responses. So, we shouldn't use it for normal operation from the + * driver. + * I left the codes, however, for debugging/testing purposes. + */ + +/* send a command */ +static int azx_single_send_cmd(struct hda_codec *codec, u32 val) +{ + struct azx *chip = codec->bus->private_data; + int timeout = 50; + + 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); + } + if (printk_ratelimit()) + snd_printd(SFX "send_cmd timeout: IRS=0x%x, val=0x%x\n", + azx_readw(chip, IRS), val); + return -EIO; +} + +/* receive a response */ +static unsigned int azx_single_get_response(struct hda_codec *codec) +{ + struct azx *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); + } + if (printk_ratelimit()) + snd_printd(SFX "get_response timeout: IRS=0x%x\n", + azx_readw(chip, IRS)); + return (unsigned int)-1; +} + +/* + * The below are the main callbacks from hda_codec. + * + * They are just the skeleton to call sub-callbacks according to the + * current setting of chip->single_cmd. + */ + +/* send a command */ +static int azx_send_cmd(struct hda_codec *codec, hda_nid_t nid, + int direct, unsigned int verb, + unsigned int para) +{ + struct azx *chip = codec->bus->private_data; + u32 val; + + val = (u32)(codec->addr & 0x0f) << 28; + val |= (u32)direct << 27; + val |= (u32)nid << 20; + val |= verb << 8; + val |= para; + chip->last_cmd = val; + + if (chip->single_cmd) + return azx_single_send_cmd(codec, val); + else + return azx_corb_send_cmd(codec, val); +} + +/* get a response */ +static unsigned int azx_get_response(struct hda_codec *codec) +{ + struct azx *chip = codec->bus->private_data; + if (chip->single_cmd) + return azx_single_get_response(codec); + else + return azx_rirb_get_response(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static void azx_power_notify(struct hda_codec *codec); +#endif + +/* reset codec link */ +static int azx_reset(struct azx *chip) +{ + int count; + + /* clear STATESTS */ + azx_writeb(chip, STATESTS, STATESTS_INT_MASK); + + /* reset controller */ + azx_writel(chip, GCTL, azx_readl(chip, GCTL) & ~ICH6_GCTL_RESET); + + count = 50; + while (azx_readb(chip, GCTL) && --count) + msleep(1); + + /* delay for >= 100us for codec PLL to settle per spec + * Rev 0.9 section 5.5.1 + */ + msleep(1); + + /* Bring controller out of reset */ + azx_writeb(chip, GCTL, azx_readb(chip, GCTL) | ICH6_GCTL_RESET); + + count = 50; + while (!azx_readb(chip, GCTL) && --count) + msleep(1); + + /* Brent Chartrand said to wait >= 540us for codecs to initialize */ + msleep(1); + + /* check to see if controller is ready */ + if (!azx_readb(chip, GCTL)) { + snd_printd("azx_reset: controller not ready!\n"); + return -EBUSY; + } + + /* Accept unsolicited responses */ + azx_writel(chip, GCTL, azx_readl(chip, GCTL) | ICH6_GCTL_UREN); + + /* 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(struct azx *chip) +{ + /* enable controller CIE and GIE */ + azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) | + ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN); +} + +/* disable interrupts */ +static void azx_int_disable(struct azx *chip) +{ + int i; + + /* disable interrupts in stream descriptor */ + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = &chip->azx_dev[i]; + azx_sd_writeb(azx_dev, SD_CTL, + azx_sd_readb(azx_dev, SD_CTL) & ~SD_INT_MASK); + } + + /* disable SIE for all streams */ + azx_writeb(chip, INTCTL, 0); + + /* disable controller CIE and GIE */ + azx_writel(chip, INTCTL, azx_readl(chip, INTCTL) & + ~(ICH6_INT_CTRL_EN | ICH6_INT_GLOBAL_EN)); +} + +/* clear interrupts */ +static void azx_int_clear(struct azx *chip) +{ + int i; + + /* clear stream status */ + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = &chip->azx_dev[i]; + azx_sd_writeb(azx_dev, SD_STS, SD_INT_MASK); + } + + /* clear STATESTS */ + azx_writeb(chip, STATESTS, STATESTS_INT_MASK); + + /* clear rirb status */ + azx_writeb(chip, RIRBSTS, RIRB_INT_MASK); + + /* clear int status */ + azx_writel(chip, INTSTS, ICH6_INT_CTRL_EN | ICH6_INT_ALL_STREAM); +} + +/* start a stream */ +static void azx_stream_start(struct azx *chip, struct azx_dev *azx_dev) +{ + /* + * Before stream start, initialize parameter + */ + azx_dev->insufficient = 1; + + /* 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(struct azx *chip, struct azx_dev *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)); +} + + +/* + * reset and start the controller registers + */ +static void azx_init_chip(struct azx *chip) +{ + if (chip->initialized) + return; + + /* reset controller */ + azx_reset(chip); + + /* initialize interrupts */ + azx_int_clear(chip); + azx_int_enable(chip); + + /* initialize the codec command I/O */ + if (!chip->single_cmd) + azx_init_cmd_io(chip); + + /* program the position buffer */ + azx_writel(chip, DPLBASE, (u32)chip->posbuf.addr); + azx_writel(chip, DPUBASE, upper_32_bits(chip->posbuf.addr)); + + chip->initialized = 1; +} + +/* + * initialize the PCI registers + */ +/* update bits in a PCI register byte */ +static void update_pci_byte(struct pci_dev *pci, unsigned int reg, + unsigned char mask, unsigned char val) +{ + unsigned char data; + + pci_read_config_byte(pci, reg, &data); + data &= ~mask; + data |= (val & mask); + pci_write_config_byte(pci, reg, data); +} + +static void azx_init_pci(struct azx *chip) +{ + unsigned short snoop; + + /* 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 + */ + update_pci_byte(chip->pci, ICH6_PCIREG_TCSEL, 0x07, 0); + + switch (chip->driver_type) { + case AZX_DRIVER_ATI: + /* For ATI SB450 azalia HD audio, we need to enable snoop */ + update_pci_byte(chip->pci, + ATI_SB450_HDAUDIO_MISC_CNTR2_ADDR, + 0x07, ATI_SB450_HDAUDIO_ENABLE_SNOOP); + break; + case AZX_DRIVER_NVIDIA: + /* For NVIDIA HDA, enable snoop */ + update_pci_byte(chip->pci, + NVIDIA_HDA_TRANSREG_ADDR, + 0x0f, NVIDIA_HDA_ENABLE_COHBITS); + update_pci_byte(chip->pci, + NVIDIA_HDA_ISTRM_COH, + 0x01, NVIDIA_HDA_ENABLE_COHBIT); + update_pci_byte(chip->pci, + NVIDIA_HDA_OSTRM_COH, + 0x01, NVIDIA_HDA_ENABLE_COHBIT); + break; + case AZX_DRIVER_SCH: + pci_read_config_word(chip->pci, INTEL_SCH_HDA_DEVC, &snoop); + if (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) { + pci_write_config_word(chip->pci, INTEL_SCH_HDA_DEVC, \ + snoop & (~INTEL_SCH_HDA_DEVC_NOSNOOP)); + pci_read_config_word(chip->pci, + INTEL_SCH_HDA_DEVC, &snoop); + snd_printdd("HDA snoop disabled, enabling ... %s\n",\ + (snoop & INTEL_SCH_HDA_DEVC_NOSNOOP) \ + ? "Failed" : "OK"); + } + break; + + } +} + + +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev); + +/* + * interrupt handler + */ +static irqreturn_t azx_interrupt(int irq, void *dev_id) +{ + struct azx *chip = dev_id; + struct azx_dev *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 < chip->num_streams; 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) + continue; + /* ignore the first dummy IRQ (due to pos_adj) */ + if (azx_dev->irq_ignore) { + azx_dev->irq_ignore = 0; + continue; + } + /* check whether this IRQ is really acceptable */ + if (azx_position_ok(chip, azx_dev)) { + azx_dev->irq_pending = 0; + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(azx_dev->substream); + spin_lock(&chip->reg_lock); + } else { + /* bogus IRQ, process it later */ + azx_dev->irq_pending = 1; + schedule_work(&chip->irq_pending_work); + } + } + } + + /* clear rirb int */ + status = azx_readb(chip, RIRBSTS); + if (status & RIRB_INT_MASK) { + if (!chip->single_cmd && (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 a BDL entry + */ +static int setup_bdle(struct snd_pcm_substream *substream, + struct azx_dev *azx_dev, u32 **bdlp, + int ofs, int size, int with_ioc) +{ + u32 *bdl = *bdlp; + + while (size > 0) { + dma_addr_t addr; + int chunk; + + if (azx_dev->frags >= AZX_MAX_BDL_ENTRIES) + return -EINVAL; + + addr = snd_pcm_sgbuf_get_addr(substream, ofs); + /* program the address field of the BDL entry */ + bdl[0] = cpu_to_le32((u32)addr); + bdl[1] = cpu_to_le32(upper_32_bits(addr)); + /* program the size field of the BDL entry */ + chunk = snd_pcm_sgbuf_get_chunk_size(substream, ofs, size); + bdl[2] = cpu_to_le32(chunk); + /* program the IOC to enable interrupt + * only when the whole fragment is processed + */ + size -= chunk; + bdl[3] = (size || !with_ioc) ? 0 : cpu_to_le32(0x01); + bdl += 4; + azx_dev->frags++; + ofs += chunk; + } + *bdlp = bdl; + return ofs; +} + +/* + * set up BDL entries + */ +static int azx_setup_periods(struct azx *chip, + struct snd_pcm_substream *substream, + struct azx_dev *azx_dev) +{ + u32 *bdl; + int i, ofs, periods, period_bytes; + int pos_adj; + + /* reset BDL address */ + azx_sd_writel(azx_dev, SD_BDLPL, 0); + azx_sd_writel(azx_dev, SD_BDLPU, 0); + + period_bytes = snd_pcm_lib_period_bytes(substream); + azx_dev->period_bytes = period_bytes; + periods = azx_dev->bufsize / period_bytes; + + /* program the initial BDL entries */ + bdl = (u32 *)azx_dev->bdl.area; + ofs = 0; + azx_dev->frags = 0; + azx_dev->irq_ignore = 0; + pos_adj = bdl_pos_adj[chip->dev_index]; + if (pos_adj > 0) { + struct snd_pcm_runtime *runtime = substream->runtime; + int pos_align = pos_adj; + pos_adj = (pos_adj * runtime->rate + 47999) / 48000; + if (!pos_adj) + pos_adj = pos_align; + else + pos_adj = ((pos_adj + pos_align - 1) / pos_align) * + pos_align; + pos_adj = frames_to_bytes(runtime, pos_adj); + if (pos_adj >= period_bytes) { + snd_printk(KERN_WARNING "Too big adjustment %d\n", + bdl_pos_adj[chip->dev_index]); + pos_adj = 0; + } else { + ofs = setup_bdle(substream, azx_dev, + &bdl, ofs, pos_adj, 1); + if (ofs < 0) + goto error; + azx_dev->irq_ignore = 1; + } + } else + pos_adj = 0; + for (i = 0; i < periods; i++) { + if (i == periods - 1 && pos_adj) + ofs = setup_bdle(substream, azx_dev, &bdl, ofs, + period_bytes - pos_adj, 0); + else + ofs = setup_bdle(substream, azx_dev, &bdl, ofs, + period_bytes, 1); + if (ofs < 0) + goto error; + } + return 0; + + error: + snd_printk(KERN_ERR "Too many BDL entries: buffer=%d, period=%d\n", + azx_dev->bufsize, period_bytes); + /* reset */ + azx_sd_writel(azx_dev, SD_BDLPL, 0); + azx_sd_writel(azx_dev, SD_BDLPU, 0); + return -EINVAL; +} + +/* + * set up the SD for streaming + */ +static int azx_setup_controller(struct azx *chip, struct azx_dev *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_32_bits(azx_dev->bdl.addr)); + + /* enable the position buffer */ + if (chip->position_fix == POS_FIX_POSBUF || + chip->position_fix == POS_FIX_AUTO || + chip->via_dmapos_patch) { + if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE)) + azx_writel(chip, DPLBASE, + (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE); + } + + /* set the interrupt enable bits in the descriptor control register */ + azx_sd_writel(azx_dev, SD_CTL, + azx_sd_readl(azx_dev, SD_CTL) | SD_INT_MASK); + + return 0; +} + + +/* + * Codec initialization + */ + +/* number of codec slots for each chipset: 0 = default slots (i.e. 4) */ +static unsigned int azx_max_codecs[AZX_NUM_DRIVERS] __devinitdata = { + [AZX_DRIVER_TERA] = 1, +}; + +/* number of slots to probe as default + * this can be different from azx_max_codecs[] -- e.g. some boards + * report wrongly the non-existing 4th slot availability + */ +static unsigned int azx_default_codecs[AZX_NUM_DRIVERS] __devinitdata = { + [AZX_DRIVER_ICH] = 3, + [AZX_DRIVER_ATI] = 3, +}; + +static int __devinit azx_codec_create(struct azx *chip, const char *model, + unsigned int codec_probe_mask) +{ + struct hda_bus_template bus_temp; + int c, codecs, audio_codecs, err; + int def_slots, max_slots; + + 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; +#ifdef CONFIG_SND_HDA_POWER_SAVE + bus_temp.ops.pm_notify = azx_power_notify; +#endif + + err = snd_hda_bus_new(chip->card, &bus_temp, &chip->bus); + if (err < 0) + return err; + + if (chip->driver_type == AZX_DRIVER_NVIDIA) + chip->bus->needs_damn_long_delay = 1; + + codecs = audio_codecs = 0; + max_slots = azx_max_codecs[chip->driver_type]; + if (!max_slots) + max_slots = AZX_MAX_CODECS; + def_slots = azx_default_codecs[chip->driver_type]; + if (!def_slots) + def_slots = max_slots; + for (c = 0; c < def_slots; c++) { + if ((chip->codec_mask & (1 << c)) & codec_probe_mask) { + struct hda_codec *codec; + err = snd_hda_codec_new(chip->bus, c, &codec); + if (err < 0) + continue; + codecs++; + if (codec->afg) + audio_codecs++; + } + } + if (!audio_codecs) { + /* probe additional slots if no codec is found */ + for (; c < max_slots; c++) { + if ((chip->codec_mask & (1 << c)) & codec_probe_mask) { + 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 struct azx_dev *azx_assign_device(struct azx *chip, int stream) +{ + int dev, i, nums; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + dev = chip->playback_index_offset; + nums = chip->playback_streams; + } else { + dev = chip->capture_index_offset; + nums = chip->capture_streams; + } + for (i = 0; i < nums; i++, dev++) + if (!chip->azx_dev[dev].opened) { + chip->azx_dev[dev].opened = 1; + return &chip->azx_dev[dev]; + } + return NULL; +} + +/* release the assigned stream */ +static inline void azx_release_device(struct azx_dev *azx_dev) +{ + azx_dev->opened = 0; +} + +static struct snd_pcm_hardware azx_pcm_hw = { + .info = (SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + /* No full-resume yet implemented */ + /* SNDRV_PCM_INFO_RESUME |*/ + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_SYNC_START), + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = AZX_MAX_BUF_SIZE, + .period_bytes_min = 128, + .period_bytes_max = AZX_MAX_BUF_SIZE / 2, + .periods_min = 2, + .periods_max = AZX_MAX_FRAG, + .fifo_size = 0, +}; + +struct azx_pcm { + struct azx *chip; + struct hda_codec *codec; + struct hda_pcm_stream *hinfo[2]; +}; + +static int azx_pcm_open(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned long flags; + int err; + + mutex_lock(&chip->open_mutex); + azx_dev = azx_assign_device(chip, substream->stream); + if (azx_dev == NULL) { + mutex_unlock(&chip->open_mutex); + return -EBUSY; + } + runtime->hw = azx_pcm_hw; + runtime->hw.channels_min = hinfo->channels_min; + runtime->hw.channels_max = hinfo->channels_max; + runtime->hw.formats = hinfo->formats; + runtime->hw.rates = hinfo->rates; + snd_pcm_limit_hw_rates(runtime); + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + 128); + snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_BYTES, + 128); + snd_hda_power_up(apcm->codec); + err = hinfo->ops.open(hinfo, apcm->codec, substream); + if (err < 0) { + azx_release_device(azx_dev); + snd_hda_power_down(apcm->codec); + mutex_unlock(&chip->open_mutex); + return err; + } + spin_lock_irqsave(&chip->reg_lock, flags); + azx_dev->substream = substream; + azx_dev->running = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + + runtime->private_data = azx_dev; + snd_pcm_set_sync(substream); + mutex_unlock(&chip->open_mutex); + return 0; +} + +static int azx_pcm_close(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + unsigned long flags; + + mutex_lock(&chip->open_mutex); + spin_lock_irqsave(&chip->reg_lock, flags); + azx_dev->substream = NULL; + azx_dev->running = 0; + spin_unlock_irqrestore(&chip->reg_lock, flags); + azx_release_device(azx_dev); + hinfo->ops.close(hinfo, apcm->codec, substream); + snd_hda_power_down(apcm->codec); + mutex_unlock(&chip->open_mutex); + return 0; +} + +static int azx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); +} + +static int azx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx_dev *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + + /* reset BDL address */ + azx_sd_writel(azx_dev, SD_BDLPL, 0); + azx_sd_writel(azx_dev, SD_BDLPU, 0); + azx_sd_writel(azx_dev, SD_CTL, 0); + + hinfo->ops.cleanup(hinfo, apcm->codec, substream); + + return snd_pcm_lib_free_pages(substream); +} + +static int azx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + struct hda_pcm_stream *hinfo = apcm->hinfo[substream->stream]; + struct snd_pcm_runtime *runtime = substream->runtime; + + azx_dev->bufsize = snd_pcm_lib_buffer_bytes(substream); + 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, format=0x%x\n", + azx_dev->bufsize, azx_dev->format_val); + if (azx_setup_periods(chip, substream, azx_dev) < 0) + return -EINVAL; + 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(struct snd_pcm_substream *substream, int cmd) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev; + struct snd_pcm_substream *s; + int start, nsync = 0, sbits = 0; + int nwait, timeout; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + start = 1; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + start = 0; + break; + default: + return -EINVAL; + } + + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + sbits |= 1 << azx_dev->index; + nsync++; + snd_pcm_trigger_done(s, substream); + } + + spin_lock(&chip->reg_lock); + if (nsync > 1) { + /* first, set SYNC bits of corresponding streams */ + azx_writel(chip, SYNC, azx_readl(chip, SYNC) | sbits); + } + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + if (start) + azx_stream_start(chip, azx_dev); + else + azx_stream_stop(chip, azx_dev); + azx_dev->running = start; + } + spin_unlock(&chip->reg_lock); + if (start) { + if (nsync == 1) + return 0; + /* wait until all FIFOs get ready */ + for (timeout = 5000; timeout; timeout--) { + nwait = 0; + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + if (!(azx_sd_readb(azx_dev, SD_STS) & + SD_STS_FIFO_READY)) + nwait++; + } + if (!nwait) + break; + cpu_relax(); + } + } else { + /* wait until all RUN bits are cleared */ + for (timeout = 5000; timeout; timeout--) { + nwait = 0; + snd_pcm_group_for_each_entry(s, substream) { + if (s->pcm->card != substream->pcm->card) + continue; + azx_dev = get_azx_dev(s); + if (azx_sd_readb(azx_dev, SD_CTL) & + SD_CTL_DMA_START) + nwait++; + } + if (!nwait) + break; + cpu_relax(); + } + } + if (nsync > 1) { + spin_lock(&chip->reg_lock); + /* reset SYNC bits */ + azx_writel(chip, SYNC, azx_readl(chip, SYNC) & ~sbits); + spin_unlock(&chip->reg_lock); + } + return 0; +} + +/* get the current DMA position with correction on VIA chips */ +static unsigned int azx_via_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int link_pos, mini_pos, bound_pos; + unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos; + unsigned int fifo_size; + + link_pos = azx_sd_readl(azx_dev, SD_LPIB); + if (azx_dev->index >= 4) { + /* Playback, no problem using link position */ + return link_pos; + } + + /* Capture */ + /* For new chipset, + * use mod to get the DMA position just like old chipset + */ + mod_dma_pos = le32_to_cpu(*azx_dev->posbuf); + mod_dma_pos %= azx_dev->period_bytes; + + /* azx_dev->fifo_size can't get FIFO size of in stream. + * Get from base address + offset. + */ + fifo_size = readw(chip->remap_addr + VIA_IN_STREAM0_FIFO_SIZE_OFFSET); + + if (azx_dev->insufficient) { + /* Link position never gather than FIFO size */ + if (link_pos <= fifo_size) + return 0; + + azx_dev->insufficient = 0; + } + + if (link_pos <= fifo_size) + mini_pos = azx_dev->bufsize + link_pos - fifo_size; + else + mini_pos = link_pos - fifo_size; + + /* Find nearest previous boudary */ + mod_mini_pos = mini_pos % azx_dev->period_bytes; + mod_link_pos = link_pos % azx_dev->period_bytes; + if (mod_link_pos >= fifo_size) + bound_pos = link_pos - mod_link_pos; + else if (mod_dma_pos >= mod_mini_pos) + bound_pos = mini_pos - mod_mini_pos; + else { + bound_pos = mini_pos - mod_mini_pos + azx_dev->period_bytes; + if (bound_pos >= azx_dev->bufsize) + bound_pos = 0; + } + + /* Calculate real DMA position we want */ + return bound_pos + mod_dma_pos; +} + +static unsigned int azx_get_position(struct azx *chip, + struct azx_dev *azx_dev) +{ + unsigned int pos; + + if (chip->via_dmapos_patch) + pos = azx_via_get_position(chip, azx_dev); + else if (chip->position_fix == POS_FIX_POSBUF || + chip->position_fix == POS_FIX_AUTO) { + /* use the position buffer */ + pos = le32_to_cpu(*azx_dev->posbuf); + } else { + /* read LPIB */ + pos = azx_sd_readl(azx_dev, SD_LPIB); + } + if (pos >= azx_dev->bufsize) + pos = 0; + return pos; +} + +static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct azx_pcm *apcm = snd_pcm_substream_chip(substream); + struct azx *chip = apcm->chip; + struct azx_dev *azx_dev = get_azx_dev(substream); + return bytes_to_frames(substream->runtime, + azx_get_position(chip, azx_dev)); +} + +/* + * Check whether the current DMA position is acceptable for updating + * periods. Returns non-zero if it's OK. + * + * Many HD-audio controllers appear pretty inaccurate about + * the update-IRQ timing. The IRQ is issued before actually the + * data is processed. So, we need to process it afterwords in a + * workqueue. + */ +static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) +{ + unsigned int pos; + + pos = azx_get_position(chip, azx_dev); + if (chip->position_fix == POS_FIX_AUTO) { + if (!pos) { + printk(KERN_WARNING + "hda-intel: Invalid position buffer, " + "using LPIB read method instead.\n"); + chip->position_fix = POS_FIX_LPIB; + pos = azx_get_position(chip, azx_dev); + } else + chip->position_fix = POS_FIX_POSBUF; + } + + if (!bdl_pos_adj[chip->dev_index]) + return 1; /* no delayed ack */ + if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) + return 0; /* NG - it's below the period boundary */ + return 1; /* OK, it's fine */ +} + +/* + * The work for pending PCM period updates. + */ +static void azx_irq_pending_work(struct work_struct *work) +{ + struct azx *chip = container_of(work, struct azx, irq_pending_work); + int i, pending; + + if (!chip->irq_pending_warned) { + printk(KERN_WARNING + "hda-intel: IRQ timing workaround is activated " + "for card #%d. Suggest a bigger bdl_pos_adj.\n", + chip->card->number); + chip->irq_pending_warned = 1; + } + + for (;;) { + pending = 0; + spin_lock_irq(&chip->reg_lock); + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = &chip->azx_dev[i]; + if (!azx_dev->irq_pending || + !azx_dev->substream || + !azx_dev->running) + continue; + if (azx_position_ok(chip, azx_dev)) { + azx_dev->irq_pending = 0; + spin_unlock(&chip->reg_lock); + snd_pcm_period_elapsed(azx_dev->substream); + spin_lock(&chip->reg_lock); + } else + pending++; + } + spin_unlock_irq(&chip->reg_lock); + if (!pending) + return; + cond_resched(); + } +} + +/* clear irq_pending flags and assure no on-going workq */ +static void azx_clear_irq_pending(struct azx *chip) +{ + int i; + + spin_lock_irq(&chip->reg_lock); + for (i = 0; i < chip->num_streams; i++) + chip->azx_dev[i].irq_pending = 0; + spin_unlock_irq(&chip->reg_lock); + flush_scheduled_work(); +} + +static struct snd_pcm_ops 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, + .page = snd_pcm_sgbuf_ops_page, +}; + +static void azx_pcm_free(struct snd_pcm *pcm) +{ + kfree(pcm->private_data); +} + +static int __devinit create_codec_pcm(struct azx *chip, struct hda_codec *codec, + struct hda_pcm *cpcm) +{ + int err; + struct snd_pcm *pcm; + struct azx_pcm *apcm; + + /* if no substreams are defined for both playback and capture, + * it's just a placeholder. ignore it. + */ + if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams) + return 0; + + if (snd_BUG_ON(!cpcm->name)) + return -EINVAL; + + err = snd_pcm_new(chip->card, cpcm->name, cpcm->device, + 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_SG, + snd_dma_pci_data(chip->pci), + 1024 * 64, 32 * 1024 * 1024); + chip->pcm[cpcm->device] = pcm; + return 0; +} + +static int __devinit azx_pcm_create(struct azx *chip) +{ + static const char *dev_name[HDA_PCM_NTYPES] = { + "Audio", "SPDIF", "HDMI", "Modem" + }; + /* starting device index for each PCM type */ + static int dev_idx[HDA_PCM_NTYPES] = { + [HDA_PCM_TYPE_AUDIO] = 0, + [HDA_PCM_TYPE_SPDIF] = 1, + [HDA_PCM_TYPE_HDMI] = 3, + [HDA_PCM_TYPE_MODEM] = 6 + }; + /* normal audio device indices; not linear to keep compatibility */ + static int audio_idx[4] = { 0, 2, 4, 5 }; + struct hda_codec *codec; + int c, err; + int num_devs[HDA_PCM_NTYPES]; + + err = snd_hda_build_pcms(chip->bus); + if (err < 0) + return err; + + /* create audio PCMs */ + memset(num_devs, 0, sizeof(num_devs)); + list_for_each_entry(codec, &chip->bus->codec_list, list) { + for (c = 0; c < codec->num_pcms; c++) { + struct hda_pcm *cpcm = &codec->pcm_info[c]; + int type = cpcm->pcm_type; + switch (type) { + case HDA_PCM_TYPE_AUDIO: + if (num_devs[type] >= ARRAY_SIZE(audio_idx)) { + snd_printk(KERN_WARNING + "Too many audio devices\n"); + continue; + } + cpcm->device = audio_idx[num_devs[type]]; + break; + case HDA_PCM_TYPE_SPDIF: + case HDA_PCM_TYPE_HDMI: + case HDA_PCM_TYPE_MODEM: + if (num_devs[type]) { + snd_printk(KERN_WARNING + "%s already defined\n", + dev_name[type]); + continue; + } + cpcm->device = dev_idx[type]; + break; + default: + snd_printk(KERN_WARNING + "Invalid PCM type %d\n", type); + continue; + } + num_devs[type]++; + err = create_codec_pcm(chip, codec, cpcm); + if (err < 0) + return err; + } + } + return 0; +} + +/* + * mixer creation - all stuff is implemented in hda module + */ +static int __devinit azx_mixer_create(struct azx *chip) +{ + return snd_hda_build_controls(chip->bus); +} + + +/* + * initialize SD streams + */ +static int __devinit azx_init_stream(struct azx *chip) +{ + int i; + + /* initialize each stream (aka device) + * assign the starting bdl address to each stream (device) + * and initialize + */ + for (i = 0; i < chip->num_streams; i++) { + struct azx_dev *azx_dev = &chip->azx_dev[i]; + azx_dev->posbuf = (u32 __iomem *)(chip->posbuf.area + i * 8); + /* offset: SDI0=0x80, SDI1=0xa0, ... SDO3=0x160 */ + azx_dev->sd_addr = chip->remap_addr + (0x20 * i + 0x80); + /* int mask: SDI0=0x01, SDI1=0x02, ... SDO3=0x80 */ + azx_dev->sd_int_sta_mask = 1 << i; + /* stream tag: must be non-zero and unique */ + azx_dev->index = i; + azx_dev->stream_tag = i + 1; + } + + return 0; +} + +static int azx_acquire_irq(struct azx *chip, int do_disconnect) +{ + if (request_irq(chip->pci->irq, azx_interrupt, + chip->msi ? 0 : IRQF_SHARED, + "HDA Intel", chip)) { + printk(KERN_ERR "hda-intel: unable to grab IRQ %d, " + "disabling device\n", chip->pci->irq); + if (do_disconnect) + snd_card_disconnect(chip->card); + return -1; + } + chip->irq = chip->pci->irq; + pci_intx(chip->pci, !chip->msi); + return 0; +} + + +static void azx_stop_chip(struct azx *chip) +{ + if (!chip->initialized) + return; + + /* disable interrupts */ + azx_int_disable(chip); + azx_int_clear(chip); + + /* disable CORB/RIRB */ + azx_free_cmd_io(chip); + + /* disable position buffer */ + azx_writel(chip, DPLBASE, 0); + azx_writel(chip, DPUBASE, 0); + + chip->initialized = 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +/* power-up/down the controller */ +static void azx_power_notify(struct hda_codec *codec) +{ + struct azx *chip = codec->bus->private_data; + struct hda_codec *c; + int power_on = 0; + + list_for_each_entry(c, &codec->bus->codec_list, list) { + if (c->power_on) { + power_on = 1; + break; + } + } + if (power_on) + azx_init_chip(chip); + else if (chip->running && power_save_controller) + azx_stop_chip(chip); +} +#endif /* CONFIG_SND_HDA_POWER_SAVE */ + +#ifdef CONFIG_PM +/* + * power management + */ +static int azx_suspend(struct pci_dev *pci, pm_message_t state) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip = card->private_data; + int i; + + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + azx_clear_irq_pending(chip); + for (i = 0; i < AZX_MAX_PCMS; i++) + snd_pcm_suspend_all(chip->pcm[i]); + if (chip->initialized) + snd_hda_suspend(chip->bus, state); + azx_stop_chip(chip); + if (chip->irq >= 0) { + free_irq(chip->irq, chip); + chip->irq = -1; + } + if (chip->msi) + pci_disable_msi(chip->pci); + pci_disable_device(pci); + pci_save_state(pci); + pci_set_power_state(pci, pci_choose_state(pci, state)); + return 0; +} + +static int azx_resume(struct pci_dev *pci) +{ + struct snd_card *card = pci_get_drvdata(pci); + struct azx *chip = card->private_data; + + pci_set_power_state(pci, PCI_D0); + pci_restore_state(pci); + if (pci_enable_device(pci) < 0) { + printk(KERN_ERR "hda-intel: pci_enable_device failed, " + "disabling device\n"); + snd_card_disconnect(card); + return -EIO; + } + pci_set_master(pci); + if (chip->msi) + if (pci_enable_msi(pci) < 0) + chip->msi = 0; + if (azx_acquire_irq(chip, 1) < 0) + return -EIO; + azx_init_pci(chip); + + if (snd_hda_codecs_inuse(chip->bus)) + azx_init_chip(chip); + + snd_hda_resume(chip->bus); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return 0; +} +#endif /* CONFIG_PM */ + + +/* + * reboot notifier for hang-up problem at power-down + */ +static int azx_halt(struct notifier_block *nb, unsigned long event, void *buf) +{ + struct azx *chip = container_of(nb, struct azx, reboot_notifier); + azx_stop_chip(chip); + return NOTIFY_OK; +} + +static void azx_notifier_register(struct azx *chip) +{ + chip->reboot_notifier.notifier_call = azx_halt; + register_reboot_notifier(&chip->reboot_notifier); +} + +static void azx_notifier_unregister(struct azx *chip) +{ + if (chip->reboot_notifier.notifier_call) + unregister_reboot_notifier(&chip->reboot_notifier); +} + +/* + * destructor + */ +static int azx_free(struct azx *chip) +{ + int i; + + azx_notifier_unregister(chip); + + if (chip->initialized) { + azx_clear_irq_pending(chip); + for (i = 0; i < chip->num_streams; i++) + azx_stream_stop(chip, &chip->azx_dev[i]); + azx_stop_chip(chip); + } + + if (chip->irq >= 0) + free_irq(chip->irq, (void*)chip); + if (chip->msi) + pci_disable_msi(chip->pci); + if (chip->remap_addr) + iounmap(chip->remap_addr); + + if (chip->azx_dev) { + for (i = 0; i < chip->num_streams; i++) + if (chip->azx_dev[i].bdl.area) + snd_dma_free_pages(&chip->azx_dev[i].bdl); + } + if (chip->rb.area) + snd_dma_free_pages(&chip->rb); + if (chip->posbuf.area) + snd_dma_free_pages(&chip->posbuf); + pci_release_regions(chip->pci); + pci_disable_device(chip->pci); + kfree(chip->azx_dev); + kfree(chip); + + return 0; +} + +static int azx_dev_free(struct snd_device *device) +{ + return azx_free(device->device_data); +} + +/* + * white/black-listing for position_fix + */ +static struct snd_pci_quirk position_fix_list[] __devinitdata = { + SND_PCI_QUIRK(0x1028, 0x01cc, "Dell D820", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1028, 0x01de, "Dell Precision 390", POS_FIX_LPIB), + SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", POS_FIX_LPIB), + {} +}; + +static int __devinit check_position_fix(struct azx *chip, int fix) +{ + const struct snd_pci_quirk *q; + + switch (fix) { + case POS_FIX_LPIB: + case POS_FIX_POSBUF: + return fix; + } + + /* Check VIA/ATI HD Audio Controller exist */ + switch (chip->driver_type) { + case AZX_DRIVER_VIA: + case AZX_DRIVER_ATI: + chip->via_dmapos_patch = 1; + /* Use link position directly, avoid any transfer problem. */ + return POS_FIX_LPIB; + } + chip->via_dmapos_patch = 0; + + q = snd_pci_quirk_lookup(chip->pci, position_fix_list); + if (q) { + printk(KERN_INFO + "hda_intel: position_fix set to %d " + "for device %04x:%04x\n", + q->value, q->subvendor, q->subdevice); + return q->value; + } + return POS_FIX_AUTO; +} + +/* + * black-lists for probe_mask + */ +static struct snd_pci_quirk probe_mask_list[] __devinitdata = { + /* Thinkpad often breaks the controller communication when accessing + * to the non-working (or non-existing) modem codec slot. + */ + SND_PCI_QUIRK(0x1014, 0x05b7, "Thinkpad Z60", 0x01), + SND_PCI_QUIRK(0x17aa, 0x2010, "Thinkpad X/T/R60", 0x01), + SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X/T/R61", 0x01), + {} +}; + +static void __devinit check_probe_mask(struct azx *chip, int dev) +{ + const struct snd_pci_quirk *q; + + if (probe_mask[dev] == -1) { + q = snd_pci_quirk_lookup(chip->pci, probe_mask_list); + if (q) { + printk(KERN_INFO + "hda_intel: probe_mask set to 0x%x " + "for device %04x:%04x\n", + q->value, q->subvendor, q->subdevice); + probe_mask[dev] = q->value; + } + } +} + + +/* + * constructor + */ +static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, + int dev, int driver_type, + struct azx **rchip) +{ + struct azx *chip; + int i, err; + unsigned short gcap; + static struct snd_device_ops ops = { + .dev_free = azx_dev_free, + }; + + *rchip = NULL; + + err = pci_enable_device(pci); + if (err < 0) + return err; + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) { + snd_printk(KERN_ERR SFX "cannot allocate chip\n"); + pci_disable_device(pci); + return -ENOMEM; + } + + spin_lock_init(&chip->reg_lock); + mutex_init(&chip->open_mutex); + chip->card = card; + chip->pci = pci; + chip->irq = -1; + chip->driver_type = driver_type; + chip->msi = enable_msi; + chip->dev_index = dev; + INIT_WORK(&chip->irq_pending_work, azx_irq_pending_work); + + chip->position_fix = check_position_fix(chip, position_fix[dev]); + check_probe_mask(chip, dev); + + chip->single_cmd = single_cmd; + + if (bdl_pos_adj[dev] < 0) { + switch (chip->driver_type) { + case AZX_DRIVER_ICH: + bdl_pos_adj[dev] = 1; + break; + default: + bdl_pos_adj[dev] = 32; + break; + } + } + +#if BITS_PER_LONG != 64 + /* Fix up base address on ULI M5461 */ + if (chip->driver_type == AZX_DRIVER_ULI) { + u16 tmp3; + pci_read_config_word(pci, 0x40, &tmp3); + pci_write_config_word(pci, 0x40, tmp3 | 0x10); + pci_write_config_dword(pci, PCI_BASE_ADDRESS_1, 0); + } +#endif + + err = pci_request_regions(pci, "ICH HD audio"); + if (err < 0) { + kfree(chip); + pci_disable_device(pci); + return err; + } + + chip->addr = pci_resource_start(pci, 0); + chip->remap_addr = pci_ioremap_bar(pci, 0); + if (chip->remap_addr == NULL) { + snd_printk(KERN_ERR SFX "ioremap error\n"); + err = -ENXIO; + goto errout; + } + + if (chip->msi) + if (pci_enable_msi(pci) < 0) + chip->msi = 0; + + if (azx_acquire_irq(chip, 0) < 0) { + err = -EBUSY; + goto errout; + } + + pci_set_master(pci); + synchronize_irq(chip->irq); + + gcap = azx_readw(chip, GCAP); + snd_printdd("chipset global capabilities = 0x%x\n", gcap); + + /* ATI chips seems buggy about 64bit DMA addresses */ + if (chip->driver_type == AZX_DRIVER_ATI) + gcap &= ~0x01; + + /* allow 64bit DMA address if supported by H/W */ + if ((gcap & 0x01) && !pci_set_dma_mask(pci, DMA_64BIT_MASK)) + pci_set_consistent_dma_mask(pci, DMA_64BIT_MASK); + else { + pci_set_dma_mask(pci, DMA_32BIT_MASK); + pci_set_consistent_dma_mask(pci, DMA_32BIT_MASK); + } + + /* read number of streams from GCAP register instead of using + * hardcoded value + */ + chip->capture_streams = (gcap >> 8) & 0x0f; + chip->playback_streams = (gcap >> 12) & 0x0f; + if (!chip->playback_streams && !chip->capture_streams) { + /* gcap didn't give any info, switching to old method */ + + switch (chip->driver_type) { + case AZX_DRIVER_ULI: + chip->playback_streams = ULI_NUM_PLAYBACK; + chip->capture_streams = ULI_NUM_CAPTURE; + break; + case AZX_DRIVER_ATIHDMI: + chip->playback_streams = ATIHDMI_NUM_PLAYBACK; + chip->capture_streams = ATIHDMI_NUM_CAPTURE; + break; + default: + chip->playback_streams = ICH6_NUM_PLAYBACK; + chip->capture_streams = ICH6_NUM_CAPTURE; + break; + } + } + chip->capture_index_offset = 0; + chip->playback_index_offset = chip->capture_streams; + chip->num_streams = chip->playback_streams + chip->capture_streams; + chip->azx_dev = kcalloc(chip->num_streams, sizeof(*chip->azx_dev), + GFP_KERNEL); + if (!chip->azx_dev) { + snd_printk(KERN_ERR "cannot malloc azx_dev\n"); + goto errout; + } + + for (i = 0; i < chip->num_streams; i++) { + /* allocate memory for the BDL for each stream */ + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + BDL_SIZE, &chip->azx_dev[i].bdl); + if (err < 0) { + snd_printk(KERN_ERR SFX "cannot allocate BDL\n"); + goto errout; + } + } + /* allocate memory for the position buffer */ + err = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + chip->num_streams * 8, &chip->posbuf); + if (err < 0) { + snd_printk(KERN_ERR SFX "cannot allocate posbuf\n"); + goto errout; + } + /* allocate CORB/RIRB */ + if (!chip->single_cmd) { + err = azx_alloc_cmd_io(chip); + if (err < 0) + goto errout; + } + + /* initialize streams */ + azx_init_stream(chip); + + /* initialize chip */ + azx_init_pci(chip); + azx_init_chip(chip); + + /* codec detection */ + if (!chip->codec_mask) { + snd_printk(KERN_ERR SFX "no codecs found!\n"); + err = -ENODEV; + goto errout; + } + + err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops); + if (err <0) { + snd_printk(KERN_ERR SFX "Error creating device [card]!\n"); + goto errout; + } + + strcpy(card->driver, "HDA-Intel"); + strcpy(card->shortname, driver_short_names[chip->driver_type]); + sprintf(card->longname, "%s at 0x%lx irq %i", + card->shortname, chip->addr, chip->irq); + + *rchip = chip; + return 0; + + errout: + azx_free(chip); + return err; +} + +static void power_down_all_codecs(struct azx *chip) +{ +#ifdef CONFIG_SND_HDA_POWER_SAVE + /* The codecs were powered up in snd_hda_codec_new(). + * Now all initialization done, so turn them down if possible + */ + struct hda_codec *codec; + list_for_each_entry(codec, &chip->bus->codec_list, list) { + snd_hda_power_down(codec); + } +#endif +} + +static int __devinit azx_probe(struct pci_dev *pci, + const struct pci_device_id *pci_id) +{ + static int dev; + struct snd_card *card; + struct azx *chip; + int err; + + if (dev >= SNDRV_CARDS) + return -ENODEV; + if (!enable[dev]) { + dev++; + return -ENOENT; + } + + card = snd_card_new(index[dev], id[dev], THIS_MODULE, 0); + if (!card) { + snd_printk(KERN_ERR SFX "Error creating card!\n"); + return -ENOMEM; + } + + err = azx_create(card, pci, dev, pci_id->driver_data, &chip); + if (err < 0) { + snd_card_free(card); + return err; + } + card->private_data = chip; + + /* create codec instances */ + err = azx_codec_create(chip, model[dev], probe_mask[dev]); + if (err < 0) { + snd_card_free(card); + return err; + } + + /* create PCM streams */ + err = azx_pcm_create(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + /* create mixer controls */ + err = azx_mixer_create(chip); + if (err < 0) { + snd_card_free(card); + return err; + } + + snd_card_set_dev(card, &pci->dev); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + + pci_set_drvdata(pci, card); + chip->running = 1; + power_down_all_codecs(chip); + azx_notifier_register(chip); + + 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[] = { + /* ICH 6..10 */ + { PCI_DEVICE(0x8086, 0x2668), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x27d8), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x269a), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x284b), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x2911), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x293e), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x293f), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x3a3e), .driver_data = AZX_DRIVER_ICH }, + { PCI_DEVICE(0x8086, 0x3a6e), .driver_data = AZX_DRIVER_ICH }, + /* PCH */ + { PCI_DEVICE(0x8086, 0x3b56), .driver_data = AZX_DRIVER_ICH }, + /* SCH */ + { PCI_DEVICE(0x8086, 0x811b), .driver_data = AZX_DRIVER_SCH }, + /* ATI SB 450/600 */ + { PCI_DEVICE(0x1002, 0x437b), .driver_data = AZX_DRIVER_ATI }, + { PCI_DEVICE(0x1002, 0x4383), .driver_data = AZX_DRIVER_ATI }, + /* ATI HDMI */ + { PCI_DEVICE(0x1002, 0x793b), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0x7919), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0x960f), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0x970f), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa00), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa08), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa10), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa18), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa20), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa28), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa30), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa38), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa40), .driver_data = AZX_DRIVER_ATIHDMI }, + { PCI_DEVICE(0x1002, 0xaa48), .driver_data = AZX_DRIVER_ATIHDMI }, + /* VIA VT8251/VT8237A */ + { PCI_DEVICE(0x1106, 0x3288), .driver_data = AZX_DRIVER_VIA }, + /* SIS966 */ + { PCI_DEVICE(0x1039, 0x7502), .driver_data = AZX_DRIVER_SIS }, + /* ULI M5461 */ + { PCI_DEVICE(0x10b9, 0x5461), .driver_data = AZX_DRIVER_ULI }, + /* NVIDIA MCP */ + { PCI_DEVICE(0x10de, 0x026c), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0371), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x03e4), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x03f0), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x044a), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x044b), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x055c), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x055d), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0774), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0775), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0776), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0777), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x07fc), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x07fd), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0ac0), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0ac1), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0ac2), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0ac3), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0bd4), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0bd5), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0bd6), .driver_data = AZX_DRIVER_NVIDIA }, + { PCI_DEVICE(0x10de, 0x0bd7), .driver_data = AZX_DRIVER_NVIDIA }, + /* Teradici */ + { PCI_DEVICE(0x6549, 0x1200), .driver_data = AZX_DRIVER_TERA }, + { 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), +#ifdef CONFIG_PM + .suspend = azx_suspend, + .resume = azx_resume, +#endif +}; + +static int __init alsa_card_azx_init(void) +{ + return pci_register_driver(&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 0000000..7957fef --- /dev/null +++ b/sound/pci/hda/hda_local.h @@ -0,0 +1,433 @@ +/* + * 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)) +/* mono volume with index (index=0,1,...) (channel=1,2) */ +#define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = snd_hda_mixer_amp_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = snd_hda_mixer_amp_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, xindex, direction) } +/* stereo volume with index */ +#define HDA_CODEC_VOLUME_IDX(xname, xcidx, nid, xindex, direction) \ + HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, 3, xindex, direction) +/* mono volume */ +#define HDA_CODEC_VOLUME_MONO(xname, nid, channel, xindex, direction) \ + HDA_CODEC_VOLUME_MONO_IDX(xname, 0, nid, channel, xindex, direction) +/* stereo volume */ +#define HDA_CODEC_VOLUME(xname, nid, xindex, direction) \ + HDA_CODEC_VOLUME_MONO(xname, nid, 3, xindex, direction) +/* mono mute switch with index (index=0,1,...) (channel=1,2) */ +#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) } +/* stereo mute switch with index */ +#define HDA_CODEC_MUTE_IDX(xname, xcidx, nid, xindex, direction) \ + HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, 3, xindex, direction) +/* mono mute switch */ +#define HDA_CODEC_MUTE_MONO(xname, nid, channel, xindex, direction) \ + HDA_CODEC_MUTE_MONO_IDX(xname, 0, nid, channel, xindex, direction) +/* stereo mute switch */ +#define HDA_CODEC_MUTE(xname, nid, xindex, direction) \ + HDA_CODEC_MUTE_MONO(xname, nid, 3, xindex, direction) + +int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv); +int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +/* lowlevel accessor with caching; use carefully */ +int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch, + int direction, int index); +int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch, + int direction, int idx, int mask, int val); +int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid, + int dir, int idx, int mask, int val); +#ifdef SND_HDA_NEEDS_RESUME +void snd_hda_codec_resume_amp(struct hda_codec *codec); +#endif + +void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int *tlv); +struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec, + const char *name); +int snd_hda_add_vmaster(struct hda_codec *codec, char *name, + unsigned int *tlv, const char **slaves); + +/* amp value bits */ +#define HDA_AMP_MUTE 0x80 +#define HDA_AMP_UNMUTE 0x00 +#define HDA_AMP_VOLMASK 0x7f + +/* mono switch binding multiple inputs */ +#define HDA_BIND_MUTE_MONO(xname, nid, channel, indices, direction) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = snd_hda_mixer_amp_switch_info, \ + .get = snd_hda_mixer_bind_switch_get, \ + .put = snd_hda_mixer_bind_switch_put, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, channel, indices, direction) } + +/* stereo switch binding multiple inputs */ +#define HDA_BIND_MUTE(xname,nid,indices,dir) \ + HDA_BIND_MUTE_MONO(xname,nid,3,indices,dir) + +int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + +/* more generic bound controls */ +struct hda_ctl_ops { + snd_kcontrol_info_t *info; + snd_kcontrol_get_t *get; + snd_kcontrol_put_t *put; + snd_kcontrol_tlv_rw_t *tlv; +}; + +extern struct hda_ctl_ops snd_hda_bind_vol; /* for bind-volume with TLV */ +extern struct hda_ctl_ops snd_hda_bind_sw; /* for bind-switch */ + +struct hda_bind_ctls { + struct hda_ctl_ops *ops; + long values[]; +}; + +int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *tlv); + +#define HDA_BIND_VOL(xname, bindrec) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |\ + SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,\ + .info = snd_hda_mixer_bind_ctls_info,\ + .get = snd_hda_mixer_bind_ctls_get,\ + .put = snd_hda_mixer_bind_ctls_put,\ + .tlv = { .c = snd_hda_mixer_bind_tlv },\ + .private_value = (long) (bindrec) } +#define HDA_BIND_SW(xname, bindrec) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER,\ + .name = xname, \ + .info = snd_hda_mixer_bind_ctls_info,\ + .get = snd_hda_mixer_bind_ctls_get,\ + .put = snd_hda_mixer_bind_ctls_put,\ + .private_value = (long) (bindrec) } + +/* + * SPDIF I/O + */ +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 16 +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, + struct snd_ctl_elem_info *uinfo); +int snd_hda_input_mux_put(struct hda_codec *codec, + const struct hda_input_mux *imux, + struct snd_ctl_elem_value *ucontrol, hda_nid_t nid, + unsigned int *cur_val); + +/* + * Channel mode helper + */ +struct hda_channel_mode { + int channels; + const struct hda_verb *sequence; +}; + +int snd_hda_ch_mode_info(struct hda_codec *codec, + struct snd_ctl_elem_info *uinfo, + const struct hda_channel_mode *chmode, + int num_chmodes); +int snd_hda_ch_mode_get(struct hda_codec *codec, + struct snd_ctl_elem_value *ucontrol, + const struct hda_channel_mode *chmode, + int num_chmodes, + int max_channels); +int snd_hda_ch_mode_put(struct hda_codec *codec, + struct snd_ctl_elem_value *ucontrol, + const struct hda_channel_mode *chmode, + int num_chmodes, + int *max_channelsp); + +/* + * 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 extra_out_nid[3]; /* optional DACs, 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 no_share_stream; /* don't share a stream with multiple pins */ + int share_spdif; /* share SPDIF pin */ + /* PCM information for both analog and SPDIF DACs */ + unsigned int analog_rates; + unsigned int analog_maxbps; + u64 analog_formats; + unsigned int spdif_rates; + unsigned int spdif_maxbps; + u64 spdif_formats; +}; + +int snd_hda_create_spdif_share_sw(struct hda_codec *codec, + struct hda_multi_out *mout); +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_dig_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +int snd_hda_multi_out_analog_open(struct hda_codec *codec, + struct hda_multi_out *mout, + struct snd_pcm_substream *substream, + struct hda_pcm_stream *hinfo); +int snd_hda_multi_out_analog_prepare(struct hda_codec *codec, + struct hda_multi_out *mout, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream); +int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec, + struct hda_multi_out *mout); + +/* + * generic codec parser + */ +#ifdef CONFIG_SND_HDA_GENERIC +int snd_hda_parse_generic_codec(struct hda_codec *codec); +#else +static inline int snd_hda_parse_generic_codec(struct hda_codec *codec) +{ + return -ENODEV; +} +#endif + +/* + * 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 + */ +int snd_hda_check_board_config(struct hda_codec *codec, int num_configs, + const char **modelnames, + const struct snd_pci_quirk *pci_list); +int snd_hda_add_new_ctls(struct hda_codec *codec, + struct snd_kcontrol_new *knew); + +/* + * 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 work_struct work; + struct hda_bus *bus; +}; + +/* + * Helper for automatic ping configuration + */ + +enum { + AUTO_PIN_MIC, + AUTO_PIN_FRONT_MIC, + AUTO_PIN_LINE, + AUTO_PIN_FRONT_LINE, + AUTO_PIN_CD, + AUTO_PIN_AUX, + AUTO_PIN_LAST +}; + +enum { + AUTO_PIN_LINE_OUT, + AUTO_PIN_SPEAKER_OUT, + AUTO_PIN_HP_OUT +}; + +extern const char *auto_pin_cfg_labels[AUTO_PIN_LAST]; + +#define AUTO_CFG_MAX_OUTS 5 + +struct auto_pin_cfg { + int line_outs; + /* sorted in the order of Front/Surr/CLFE/Side */ + hda_nid_t line_out_pins[AUTO_CFG_MAX_OUTS]; + int speaker_outs; + hda_nid_t speaker_pins[AUTO_CFG_MAX_OUTS]; + int hp_outs; + int line_out_type; /* AUTO_PIN_XXX_OUT */ + hda_nid_t hp_pins[AUTO_CFG_MAX_OUTS]; + hda_nid_t input_pins[AUTO_PIN_LAST]; + hda_nid_t dig_out_pin; + hda_nid_t dig_in_pin; + hda_nid_t mono_out_pin; +}; + +#define get_defcfg_connect(cfg) \ + ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT) +#define get_defcfg_association(cfg) \ + ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT) +#define get_defcfg_location(cfg) \ + ((cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT) +#define get_defcfg_sequence(cfg) \ + (cfg & AC_DEFCFG_SEQUENCE) +#define get_defcfg_device(cfg) \ + ((cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT) + +int snd_hda_parse_pin_def_config(struct hda_codec *codec, + struct auto_pin_cfg *cfg, + hda_nid_t *ignore_nids); + +/* amp values */ +#define AMP_IN_MUTE(idx) (0x7080 | ((idx)<<8)) +#define AMP_IN_UNMUTE(idx) (0x7000 | ((idx)<<8)) +#define AMP_OUT_MUTE 0xb080 +#define AMP_OUT_UNMUTE 0xb000 +#define AMP_OUT_ZERO 0xb000 +/* pinctl values */ +#define PIN_IN (AC_PINCTL_IN_EN) +#define PIN_VREFHIZ (AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ) +#define PIN_VREF50 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_50) +#define PIN_VREFGRD (AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD) +#define PIN_VREF80 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_80) +#define PIN_VREF100 (AC_PINCTL_IN_EN | AC_PINCTL_VREF_100) +#define PIN_OUT (AC_PINCTL_OUT_EN) +#define PIN_HP (AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN) +#define PIN_HP_AMP (AC_PINCTL_HP_EN) + +/* + * get widget capabilities + */ +static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid) +{ + if (nid < codec->start_nid || + nid >= codec->start_nid + codec->num_nodes) + return 0; + return codec->wcaps[nid - codec->start_nid]; +} + +u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction); +int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir, + unsigned int caps); + +/* + * hwdep interface + */ +int snd_hda_create_hwdep(struct hda_codec *codec); + +/* + * power-management + */ + +#ifdef CONFIG_SND_HDA_POWER_SAVE +void snd_hda_schedule_power_save(struct hda_codec *codec); + +struct hda_amp_list { + hda_nid_t nid; + unsigned char dir; + unsigned char idx; +}; + +struct hda_loopback_check { + struct hda_amp_list *amplist; + int power_on; +}; + +int snd_hda_check_amp_list_power(struct hda_codec *codec, + struct hda_loopback_check *check, + hda_nid_t nid); +#endif /* CONFIG_SND_HDA_POWER_SAVE */ + +/* + * 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) + +#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 0000000..dfbcfa8 --- /dev/null +++ b/sound/pci/hda/hda_patch.h @@ -0,0 +1,22 @@ +/* + * 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[]; +/* SigmaTel codecs */ +extern struct hda_codec_preset snd_hda_preset_sigmatel[]; +/* SiLabs 3054/3055 modem codecs */ +extern struct hda_codec_preset snd_hda_preset_si3054[]; +/* ATI HDMI codecs */ +extern struct hda_codec_preset snd_hda_preset_atihdmi[]; +/* Conexant audio codec */ +extern struct hda_codec_preset snd_hda_preset_conexant[]; +/* VIA codecs */ +extern struct hda_codec_preset snd_hda_preset_via[]; +/* NVIDIA HDMI codecs */ +extern struct hda_codec_preset snd_hda_preset_nvhdmi[]; diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c new file mode 100644 index 0000000..c39af98 --- /dev/null +++ b/sound/pci/hda/hda_proc.c @@ -0,0 +1,676 @@ +/* + * 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 <linux/init.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.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 "UNKNOWN Widget"; +} + +static void print_amp_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, int dir) +{ + unsigned int caps; + caps = snd_hda_param_read(codec, nid, + dir == HDA_OUTPUT ? + AC_PAR_AMP_OUT_CAP : 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(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int dir, int stereo, int indices) +{ + unsigned int val; + int i; + + dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT; + for (i = 0; i < indices; i++) { + snd_iprintf(buffer, " ["); + if (stereo) { + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_LEFT | dir | i); + 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 | i); + snd_iprintf(buffer, "0x%02x]", val); + } + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm) +{ + static unsigned int rates[] = { + 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, + 96000, 176400, 192000, 384000 + }; + int i; + + pcm &= AC_SUPPCM_RATES; + snd_iprintf(buffer, " rates [0x%x]:", pcm); + for (i = 0; i < ARRAY_SIZE(rates); i++) + if (pcm & (1 << i)) + snd_iprintf(buffer, " %d", rates[i]); + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm) +{ + static unsigned int bits[] = { 8, 16, 20, 24, 32 }; + int i; + + pcm = (pcm >> 16) & 0xff; + snd_iprintf(buffer, " bits [0x%x]:", pcm); + for (i = 0; i < ARRAY_SIZE(bits); i++) + if (pcm & (1 << i)) + snd_iprintf(buffer, " %d", bits[i]); + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_formats(struct snd_info_buffer *buffer, + unsigned int streams) +{ + snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf); + if (streams & AC_SUPFMT_PCM) + snd_iprintf(buffer, " PCM"); + if (streams & AC_SUPFMT_FLOAT32) + snd_iprintf(buffer, " FLOAT"); + if (streams & AC_SUPFMT_AC3) + snd_iprintf(buffer, " AC3"); + snd_iprintf(buffer, "\n"); +} + +static void print_pcm_caps(struct snd_info_buffer *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; + } + print_pcm_rates(buffer, pcm); + print_pcm_bits(buffer, pcm); + print_pcm_formats(buffer, stream); +} + +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(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int *supports_vref) +{ + static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" }; + 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, val; + + caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); + snd_iprintf(buffer, " Pincap 0x%08x:", 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"); + if (caps & AC_PINCAP_EAPD) + snd_iprintf(buffer, " EAPD"); + if (caps & AC_PINCAP_PRES_DETECT) + snd_iprintf(buffer, " Detect"); + if (caps & AC_PINCAP_BALANCE) + snd_iprintf(buffer, " Balanced"); + if (caps & AC_PINCAP_HDMI) { + /* Realtek uses this bit as a different meaning */ + if ((codec->vendor_id >> 16) == 0x10ec) + snd_iprintf(buffer, " R/L"); + else + snd_iprintf(buffer, " HDMI"); + } + if (caps & AC_PINCAP_TRIG_REQ) + snd_iprintf(buffer, " Trigger"); + if (caps & AC_PINCAP_IMP_SENSE) + snd_iprintf(buffer, " ImpSense"); + snd_iprintf(buffer, "\n"); + if (caps & AC_PINCAP_VREF) { + unsigned int vref = + (caps & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + snd_iprintf(buffer, " Vref caps:"); + if (vref & AC_PINCAP_VREF_HIZ) + snd_iprintf(buffer, " HIZ"); + if (vref & AC_PINCAP_VREF_50) + snd_iprintf(buffer, " 50"); + if (vref & AC_PINCAP_VREF_GRD) + snd_iprintf(buffer, " GRD"); + if (vref & AC_PINCAP_VREF_80) + snd_iprintf(buffer, " 80"); + if (vref & AC_PINCAP_VREF_100) + snd_iprintf(buffer, " 100"); + snd_iprintf(buffer, "\n"); + *supports_vref = 1; + } else + *supports_vref = 0; + if (caps & AC_PINCAP_EAPD) { + val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0); + snd_iprintf(buffer, " EAPD 0x%x:", val); + if (val & AC_EAPDBTL_BALANCED) + snd_iprintf(buffer, " BALANCED"); + if (val & AC_EAPDBTL_EAPD) + snd_iprintf(buffer, " EAPD"); + if (val & AC_EAPDBTL_LR_SWAP) + snd_iprintf(buffer, " R/L"); + 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] %s at %s %s\n", caps, + jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT], + 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)); + /* Default association and sequence values refer to default grouping + * of pin complexes and their sequence within the group. This is used + * for priority and resource allocation. + */ + snd_iprintf(buffer, " DefAssociation = 0x%x, Sequence = 0x%x\n", + (caps & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT, + caps & AC_DEFCFG_SEQUENCE); + if (((caps & AC_DEFCFG_MISC) >> AC_DEFCFG_MISC_SHIFT) & + AC_DEFCFG_MISC_NO_PRESENCE) { + /* Miscellaneous bit indicates external hardware does not + * support presence detection even if the pin complex + * indicates it is supported. + */ + snd_iprintf(buffer, " Misc = NO_PRESENCE\n"); + } +} + +static void print_pin_ctls(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + int supports_vref) +{ + unsigned int pinctls; + + 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"); + if (supports_vref) { + int vref = pinctls & AC_PINCTL_VREFEN; + switch (vref) { + case AC_PINCTL_VREF_HIZ: + snd_iprintf(buffer, " VREF_HIZ"); + break; + case AC_PINCTL_VREF_50: + snd_iprintf(buffer, " VREF_50"); + break; + case AC_PINCTL_VREF_GRD: + snd_iprintf(buffer, " VREF_GRD"); + break; + case AC_PINCTL_VREF_80: + snd_iprintf(buffer, " VREF_80"); + break; + case AC_PINCTL_VREF_100: + snd_iprintf(buffer, " VREF_100"); + break; + } + } + snd_iprintf(buffer, "\n"); +} + +static void print_vol_knob(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int cap = snd_hda_param_read(codec, nid, + AC_PAR_VOL_KNB_CAP); + snd_iprintf(buffer, " Volume-Knob: delta=%d, steps=%d, ", + (cap >> 7) & 1, cap & 0x7f); + cap = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_VOLUME_KNOB_CONTROL, 0); + snd_iprintf(buffer, "direct=%d, val=%d\n", + (cap >> 7) & 1, cap & 0x7f); +} + +static void print_audio_io(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + unsigned int wid_type) +{ + int conv = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONV, 0); + snd_iprintf(buffer, + " Converter: stream=%d, channel=%d\n", + (conv & AC_CONV_STREAM) >> AC_CONV_STREAM_SHIFT, + conv & AC_CONV_CHANNEL); + + if (wid_type == AC_WID_AUD_IN && (conv & AC_CONV_CHANNEL) == 0) { + int sdi = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_SDI_SELECT, 0); + snd_iprintf(buffer, " SDI-Select: %d\n", + sdi & AC_SDI_SELECT); + } +} + +static void print_digital_conv(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int digi1 = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0); + snd_iprintf(buffer, " Digital:"); + if (digi1 & AC_DIG1_ENABLE) + snd_iprintf(buffer, " Enabled"); + if (digi1 & AC_DIG1_V) + snd_iprintf(buffer, " Validity"); + if (digi1 & AC_DIG1_VCFG) + snd_iprintf(buffer, " ValidityCfg"); + if (digi1 & AC_DIG1_EMPHASIS) + snd_iprintf(buffer, " Preemphasis"); + if (digi1 & AC_DIG1_COPYRIGHT) + snd_iprintf(buffer, " Copyright"); + if (digi1 & AC_DIG1_NONAUDIO) + snd_iprintf(buffer, " Non-Audio"); + if (digi1 & AC_DIG1_PROFESSIONAL) + snd_iprintf(buffer, " Pro"); + if (digi1 & AC_DIG1_LEVEL) + snd_iprintf(buffer, " GenLevel"); + snd_iprintf(buffer, "\n"); + snd_iprintf(buffer, " Digital category: 0x%x\n", + (digi1 >> 8) & AC_DIG2_CC); +} + +static const char *get_pwr_state(u32 state) +{ + static const char *buf[4] = { + "D0", "D1", "D2", "D3" + }; + if (state < 4) + return buf[state]; + return "UNKNOWN"; +} + +static void print_power_state(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int pwr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_POWER_STATE, 0); + snd_iprintf(buffer, " Power: setting=%s, actual=%s\n", + get_pwr_state(pwr & AC_PWRST_SETTING), + get_pwr_state((pwr & AC_PWRST_ACTUAL) >> + AC_PWRST_ACTUAL_SHIFT)); +} + +static void print_unsol_cap(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int unsol = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_UNSOLICITED_RESPONSE, 0); + snd_iprintf(buffer, + " Unsolicited: tag=%02x, enabled=%d\n", + unsol & AC_UNSOL_TAG, + (unsol & AC_UNSOL_ENABLED) ? 1 : 0); +} + +static void print_proc_caps(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int proc_caps = snd_hda_param_read(codec, nid, + AC_PAR_PROC_CAP); + snd_iprintf(buffer, " Processing caps: benign=%d, ncoeff=%d\n", + proc_caps & AC_PCAP_BENIGN, + (proc_caps & AC_PCAP_NUM_COEF) >> AC_PCAP_NUM_COEF_SHIFT); +} + +static void print_conn_list(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid, + unsigned int wid_type, hda_nid_t *conn, + int conn_len) +{ + int c, curr = -1; + + if (conn_len > 1 && wid_type != AC_WID_AUD_MIX) + curr = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_SEL, 0); + snd_iprintf(buffer, " Connection: %d\n", conn_len); + if (conn_len > 0) { + snd_iprintf(buffer, " "); + for (c = 0; c < conn_len; c++) { + snd_iprintf(buffer, " 0x%02x", conn[c]); + if (c == curr) + snd_iprintf(buffer, "*"); + } + snd_iprintf(buffer, "\n"); + } +} + +static void print_realtek_coef(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + int coeff = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PROC_COEF, 0); + snd_iprintf(buffer, " Processing Coefficient: 0x%02x\n", coeff); + coeff = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_COEF_INDEX, 0); + snd_iprintf(buffer, " Coefficient Index: 0x%02x\n", coeff); +} + +static void print_gpio(struct snd_info_buffer *buffer, + struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int gpio = + snd_hda_param_read(codec, codec->afg, AC_PAR_GPIO_CAP); + unsigned int enable, direction, wake, unsol, sticky, data; + int i, max; + snd_iprintf(buffer, "GPIO: io=%d, o=%d, i=%d, " + "unsolicited=%d, wake=%d\n", + gpio & AC_GPIO_IO_COUNT, + (gpio & AC_GPIO_O_COUNT) >> AC_GPIO_O_COUNT_SHIFT, + (gpio & AC_GPIO_I_COUNT) >> AC_GPIO_I_COUNT_SHIFT, + (gpio & AC_GPIO_UNSOLICITED) ? 1 : 0, + (gpio & AC_GPIO_WAKE) ? 1 : 0); + max = gpio & AC_GPIO_IO_COUNT; + if (!max || max > 8) + return; + enable = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_MASK, 0); + direction = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DIRECTION, 0); + wake = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_WAKE_MASK, 0); + unsol = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_UNSOLICITED_RSP_MASK, 0); + sticky = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_STICKY_MASK, 0); + data = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DATA, 0); + for (i = 0; i < max; ++i) + snd_iprintf(buffer, + " IO[%d]: enable=%d, dir=%d, wake=%d, " + "sticky=%d, data=%d\n", i, + (enable & (1<<i)) ? 1 : 0, + (direction & (1<<i)) ? 1 : 0, + (wake & (1<<i)) ? 1 : 0, + (sticky & (1<<i)) ? 1 : 0, + (data & (1<<i)) ? 1 : 0); + /* FIXME: add GPO and GPI pin information */ +} + +static void print_codec_info(struct snd_info_entry *entry, + struct snd_info_buffer *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); + + if (codec->mfg) + snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg); + else + snd_iprintf(buffer, "No Modem Function Group found\n"); + + if (! codec->afg) + return; + snd_hda_power_up(codec); + snd_iprintf(buffer, "Default PCM:\n"); + 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"); + snd_hda_power_down(codec); + return; + } + + print_gpio(buffer, codec, codec->afg); + + 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; + hda_nid_t conn[HDA_MAX_CONNECTIONS]; + int conn_len = 0; + + 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) { + unsigned int chans; + chans = (wid_caps & AC_WCAP_CHAN_CNT_EXT) >> 13; + chans = ((chans << 1) | 1) + 1; + if (chans == 2) + snd_iprintf(buffer, " Stereo"); + else + snd_iprintf(buffer, " %d-Channels", chans); + } 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"); + if (wid_caps & AC_WCAP_STRIPE) + snd_iprintf(buffer, " Stripe"); + if (wid_caps & AC_WCAP_LR_SWAP) + snd_iprintf(buffer, " R/L"); + if (wid_caps & AC_WCAP_CP_CAPS) + snd_iprintf(buffer, " CP"); + snd_iprintf(buffer, "\n"); + + /* volume knob is a special widget that always have connection + * list + */ + if (wid_type == AC_WID_VOL_KNB) + wid_caps |= AC_WCAP_CONN_LIST; + + if (wid_caps & AC_WCAP_CONN_LIST) + conn_len = snd_hda_get_connections(codec, nid, conn, + HDA_MAX_CONNECTIONS); + + 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, + wid_type == AC_WID_PIN ? 1 : conn_len); + } + 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, 1); + } + + switch (wid_type) { + case AC_WID_PIN: { + int supports_vref; + print_pin_caps(buffer, codec, nid, &supports_vref); + print_pin_ctls(buffer, codec, nid, supports_vref); + break; + } + case AC_WID_VOL_KNB: + print_vol_knob(buffer, codec, nid); + break; + case AC_WID_AUD_OUT: + case AC_WID_AUD_IN: + print_audio_io(buffer, codec, nid, wid_type); + if (wid_caps & AC_WCAP_DIGITAL) + print_digital_conv(buffer, codec, nid); + if (wid_caps & AC_WCAP_FORMAT_OVRD) { + snd_iprintf(buffer, " PCM:\n"); + print_pcm_caps(buffer, codec, nid); + } + break; + } + + if (wid_caps & AC_WCAP_UNSOL_CAP) + print_unsol_cap(buffer, codec, nid); + + if (wid_caps & AC_WCAP_POWER) + print_power_state(buffer, codec, nid); + + if (wid_caps & AC_WCAP_DELAY) + snd_iprintf(buffer, " Delay: %d samples\n", + (wid_caps & AC_WCAP_DELAY) >> + AC_WCAP_DELAY_SHIFT); + + if (wid_caps & AC_WCAP_CONN_LIST) + print_conn_list(buffer, codec, nid, wid_type, + conn, conn_len); + + if (wid_caps & AC_WCAP_PROC_WID) + print_proc_caps(buffer, codec, nid); + + /* NID 0x20 == Realtek Define Registers */ + if (codec->vendor_id == 0x10ec && nid == 0x20) + print_realtek_coef(buffer, codec, nid); + } + snd_hda_power_down(codec); +} + +/* + * create a proc read + */ +int snd_hda_codec_proc_new(struct hda_codec *codec) +{ + char name[32]; + struct snd_info_entry *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, 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 0000000..3495e83 --- /dev/null +++ b/sound/pci/hda/patch_analog.c @@ -0,0 +1,4331 @@ +/* + * HD audio interface patch for AD1882, AD1884, AD1981HD, AD1983, AD1984, + * AD1986A, AD1988 + * + * Copyright (c) 2005-2007 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 <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" +#include "hda_patch.h" + +struct ad198x_spec { + struct snd_kcontrol_new *mixers[5]; + int num_mixers; + + const struct hda_verb *init_verbs[5]; /* initialization verbs + * don't forget NULL termination! + */ + unsigned int num_init_verbs; + + /* playback */ + struct hda_multi_out multiout; /* playback set-up + * max_channels, dacs must be set + * dig_out_nid and hp_nid are optional + */ + unsigned int cur_eapd; + unsigned int need_dac_fix; + + /* capture */ + unsigned int num_adc_nids; + hda_nid_t *adc_nids; + hda_nid_t dig_in_nid; /* digital-in NID; optional */ + + /* capture source */ + const struct hda_input_mux *input_mux; + hda_nid_t *capsrc_nids; + unsigned int cur_mux[3]; + + /* channel model */ + const struct hda_channel_mode *channel_mode; + int num_channel_mode; + + /* PCM information */ + struct hda_pcm pcm_rec[3]; /* used in alc_build_pcms() */ + + unsigned int spdif_route; + + /* dynamic controls, init_verbs and input_mux */ + struct auto_pin_cfg autocfg; + unsigned int num_kctl_alloc, num_kctl_used; + struct snd_kcontrol_new *kctl_alloc; + struct hda_input_mux private_imux; + hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; + + unsigned int jack_present :1; + +#ifdef CONFIG_SND_HDA_POWER_SAVE + struct hda_loopback_check loopback; +#endif + /* for virtual master */ + hda_nid_t vmaster_nid; + const char **slave_vols; + const char **slave_sws; +}; + +/* + * input MUX handling (common part) + */ +static int ad198x_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + return snd_hda_input_mux_info(spec->input_mux, uinfo); +} + +static int ad198x_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_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 ad198x_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_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->capsrc_nids[adc_idx], + &spec->cur_mux[adc_idx]); +} + +/* + * initialization (common callbacks) + */ +static int ad198x_init(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->num_init_verbs; i++) + snd_hda_sequence_write(codec, spec->init_verbs[i]); + return 0; +} + +static const char *ad_slave_vols[] = { + "Front Playback Volume", + "Surround Playback Volume", + "Center Playback Volume", + "LFE Playback Volume", + "Side Playback Volume", + "Headphone Playback Volume", + "Mono Playback Volume", + "Speaker Playback Volume", + "IEC958 Playback Volume", + NULL +}; + +static const char *ad_slave_sws[] = { + "Front Playback Switch", + "Surround Playback Switch", + "Center Playback Switch", + "LFE Playback Switch", + "Side Playback Switch", + "Headphone Playback Switch", + "Mono Playback Switch", + "Speaker Playback Switch", + "IEC958 Playback Switch", + NULL +}; + +static int ad198x_build_controls(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + unsigned int i; + int err; + + 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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + if (spec->dig_in_nid) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + + /* if we have no master control, let's create it */ + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { + unsigned int vmaster_tlv[4]; + snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, + HDA_OUTPUT, vmaster_tlv); + err = snd_hda_add_vmaster(codec, "Master Playback Volume", + vmaster_tlv, + (spec->slave_vols ? + spec->slave_vols : ad_slave_vols)); + if (err < 0) + return err; + } + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { + err = snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, + (spec->slave_sws ? + spec->slave_sws : ad_slave_sws)); + if (err < 0) + return err; + } + + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int ad198x_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +#endif + +/* + * Analog playback callbacks + */ +static int ad198x_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int ad198x_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +static int ad198x_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + +/* + * Digital out + */ +static int ad198x_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int ad198x_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int ad198x_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +/* + * Analog capture + */ +static int ad198x_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], + stream_tag, 0, format); + return 0; +} + +static int ad198x_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct ad198x_spec *spec = codec->spec; + snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]); + return 0; +} + + +/* + */ +static struct hda_pcm_stream ad198x_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 6, /* changed later */ + .nid = 0, /* fill later */ + .ops = { + .open = ad198x_playback_pcm_open, + .prepare = ad198x_playback_pcm_prepare, + .cleanup = ad198x_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ad198x_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .prepare = ad198x_capture_pcm_prepare, + .cleanup = ad198x_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream ad198x_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .open = ad198x_dig_playback_pcm_open, + .close = ad198x_dig_playback_pcm_close, + .prepare = ad198x_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream ad198x_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ +}; + +static int ad198x_build_pcms(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "AD198x Analog"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = spec->multiout.max_channels; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0]; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad198x_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0]; + + if (spec->multiout.dig_out_nid) { + info++; + codec->num_pcms++; + info->name = "AD198x Digital"; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = ad198x_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] = ad198x_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid; + } + } + + return 0; +} + +static void ad198x_free(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + unsigned int i; + + if (spec->kctl_alloc) { + for (i = 0; i < spec->num_kctl_used; i++) + kfree(spec->kctl_alloc[i].name); + kfree(spec->kctl_alloc); + } + kfree(codec->spec); +} + +static struct hda_codec_ops ad198x_patch_ops = { + .build_controls = ad198x_build_controls, + .build_pcms = ad198x_build_pcms, + .init = ad198x_init, + .free = ad198x_free, +#ifdef CONFIG_SND_HDA_POWER_SAVE + .check_power_status = ad198x_check_power_status, +#endif +}; + + +/* + * EAPD control + * the private value = nid | (invert << 8) + */ +#define ad198x_eapd_info snd_ctl_boolean_mono_info + +static int ad198x_eapd_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + int invert = (kcontrol->private_value >> 8) & 1; + if (invert) + ucontrol->value.integer.value[0] = ! spec->cur_eapd; + else + ucontrol->value.integer.value[0] = spec->cur_eapd; + return 0; +} + +static int ad198x_eapd_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + int invert = (kcontrol->private_value >> 8) & 1; + hda_nid_t nid = kcontrol->private_value & 0xff; + unsigned int eapd; + eapd = !!ucontrol->value.integer.value[0]; + if (invert) + eapd = !eapd; + if (eapd == spec->cur_eapd) + return 0; + spec->cur_eapd = eapd; + snd_hda_codec_write_cache(codec, nid, + 0, AC_VERB_SET_EAPD_BTLENABLE, + eapd ? 0x02 : 0x00); + return 1; +} + +static int ad198x_ch_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo); +static int ad198x_ch_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); +static int ad198x_ch_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol); + + +/* + * AD1986A specific + */ + +#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 hda_nid_t ad1986a_adc_nids[1] = { AD1986A_ADC }; +static hda_nid_t ad1986a_capsrc_nids[1] = { 0x12 }; + +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 }, + }, +}; + + +static struct hda_bind_ctls ad1986a_bind_pcm_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(AD1986A_SURR_DAC, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(AD1986A_CLFE_DAC, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct hda_bind_ctls ad1986a_bind_pcm_sw = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(AD1986A_FRONT_DAC, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(AD1986A_SURR_DAC, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(AD1986A_CLFE_DAC, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* + * mixers + */ +static struct snd_kcontrol_new ad1986a_mixers[] = { + /* + * bind volumes/mutes of 3 DACs as a single PCM control for simplicity + */ + HDA_BIND_VOL("PCM Playback Volume", &ad1986a_bind_pcm_vol), + HDA_BIND_SW("PCM Playback Switch", &ad1986a_bind_pcm_sw), + 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("Mic Boost", 0x0f, 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 = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + HDA_CODEC_MUTE("Stereo Downmix Switch", 0x09, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* additional mixers for 3stack mode */ +static struct snd_kcontrol_new ad1986a_3st_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = ad198x_ch_mode_info, + .get = ad198x_ch_mode_get, + .put = ad198x_ch_mode_put, + }, + { } /* end */ +}; + +/* laptop model - 2ch only */ +static hda_nid_t ad1986a_laptop_dac_nids[1] = { AD1986A_FRONT_DAC }; + +/* master controls both pins 0x1a and 0x1b */ +static struct hda_bind_ctls ad1986a_laptop_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT), + 0, + }, +}; + +static struct hda_bind_ctls ad1986a_laptop_master_sw = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT), + 0, + }, +}; + +static struct snd_kcontrol_new ad1986a_laptop_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol), + HDA_BIND_SW("Master Playback Switch", &ad1986a_laptop_master_sw), + 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("Mic Boost", 0x0f, 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 = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +/* laptop-eapd model - 2ch only */ + +static struct hda_input_mux ad1986a_laptop_eapd_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x4 }, + { "Mix", 0x5 }, + }, +}; + +static struct hda_input_mux ad1986a_automic_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + { "Mix", 0x5 }, + }, +}; + +static struct snd_kcontrol_new ad1986a_laptop_eapd_mixers[] = { + HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol), + HDA_BIND_SW("Master Playback Switch", &ad1986a_laptop_master_sw), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x17, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x17, 0, 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("Mic Boost", 0x0f, 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 = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External Amplifier", + .info = ad198x_eapd_info, + .get = ad198x_eapd_get, + .put = ad198x_eapd_put, + .private_value = 0x1b | (1 << 8), /* port-D, inversed */ + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1986a_samsung_mixers[] = { + HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol), + HDA_BIND_SW("Master Playback Switch", &ad1986a_laptop_master_sw), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 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("Mic Boost", 0x0f, 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 = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External Amplifier", + .info = ad198x_eapd_info, + .get = ad198x_eapd_get, + .put = ad198x_eapd_put, + .private_value = 0x1b | (1 << 8), /* port-D, inversed */ + }, + { } /* end */ +}; + +/* re-connect the mic boost input according to the jack sensing */ +static void ad1986a_automic(struct hda_codec *codec) +{ + unsigned int present; + present = snd_hda_codec_read(codec, 0x1f, 0, AC_VERB_GET_PIN_SENSE, 0); + /* 0 = 0x1f, 2 = 0x1d, 4 = mixed */ + snd_hda_codec_write(codec, 0x0f, 0, AC_VERB_SET_CONNECT_SEL, + (present & AC_PINSENSE_PRESENCE) ? 0 : 2); +} + +#define AD1986A_MIC_EVENT 0x36 + +static void ad1986a_automic_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != AD1986A_MIC_EVENT) + return; + ad1986a_automic(codec); +} + +static int ad1986a_automic_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1986a_automic(codec); + return 0; +} + +/* laptop-automute - 2ch only */ + +static void ad1986a_update_hp(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + unsigned int mute; + + if (spec->jack_present) + mute = HDA_AMP_MUTE; /* mute internal speaker */ + else + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x1a, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); +} + +static void ad1986a_hp_automute(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x1a, 0, AC_VERB_GET_PIN_SENSE, 0); + /* Lenovo N100 seems to report the reversed bit for HP jack-sensing */ + spec->jack_present = !(present & 0x80000000); + ad1986a_update_hp(codec); +} + +#define AD1986A_HP_EVENT 0x37 + +static void ad1986a_hp_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) != AD1986A_HP_EVENT) + return; + ad1986a_hp_automute(codec); +} + +static int ad1986a_hp_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1986a_hp_automute(codec); + return 0; +} + +/* bind hp and internal speaker mute (with plug check) */ +static int ad1986a_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + change = snd_hda_codec_amp_update(codec, 0x1a, 0, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[0] ? 0 : HDA_AMP_MUTE); + change |= snd_hda_codec_amp_update(codec, 0x1a, 1, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[1] ? 0 : HDA_AMP_MUTE); + if (change) + ad1986a_update_hp(codec); + return change; +} + +static struct snd_kcontrol_new ad1986a_laptop_automute_mixers[] = { + HDA_BIND_VOL("Master Playback Volume", &ad1986a_laptop_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = ad1986a_hp_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("PCM Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x17, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x17, 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("Mic Boost", 0x0f, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x18, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x18, 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 = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External Amplifier", + .info = ad198x_eapd_info, + .get = ad198x_eapd_get, + .put = ad198x_eapd_put, + .private_value = 0x1b | (1 << 8), /* port-D, inversed */ + }, + { } /* 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}, + /* HP Pin */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Front, Surround, CLFE Pins */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mono Pin */ + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mic Pin */ + {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line, Aux, CD, Beep-In Pin */ + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { } /* end */ +}; + +static struct hda_verb ad1986a_ch2_init[] = { + /* Surround out -> Line In */ + { 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + /* Line-in selectors */ + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x1 }, + /* CLFE -> Mic in */ + { 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* Mic selector, mix C/LFE (backmic) and Mic (frontmic) */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x4 }, + { } /* end */ +}; + +static struct hda_verb ad1986a_ch4_init[] = { + /* Surround out -> Surround */ + { 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 }, + /* CLFE -> Mic in */ + { 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x4 }, + { } /* end */ +}; + +static struct hda_verb ad1986a_ch6_init[] = { + /* Surround out -> Surround out */ + { 0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 }, + /* CLFE -> CLFE */ + { 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x0 }, + { } /* end */ +}; + +static struct hda_channel_mode ad1986a_modes[3] = { + { 2, ad1986a_ch2_init }, + { 4, ad1986a_ch4_init }, + { 6, ad1986a_ch6_init }, +}; + +/* eapd initialization */ +static struct hda_verb ad1986a_eapd_init_verbs[] = { + {0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00 }, + {} +}; + +static struct hda_verb ad1986a_automic_verbs[] = { + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + /*{0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},*/ + {0x0f, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x1f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1986A_MIC_EVENT}, + {} +}; + +/* Ultra initialization */ +static struct hda_verb ad1986a_ultra_init[] = { + /* eapd initialization */ + { 0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00 }, + /* CLFE -> Mic in */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x2 }, + { 0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 }, + { } /* end */ +}; + +/* pin sensing on HP jack */ +static struct hda_verb ad1986a_hp_init_verbs[] = { + {0x1a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1986A_HP_EVENT}, + {} +}; + + +/* models */ +enum { + AD1986A_6STACK, + AD1986A_3STACK, + AD1986A_LAPTOP, + AD1986A_LAPTOP_EAPD, + AD1986A_LAPTOP_AUTOMUTE, + AD1986A_ULTRA, + AD1986A_SAMSUNG, + AD1986A_MODELS +}; + +static const char *ad1986a_models[AD1986A_MODELS] = { + [AD1986A_6STACK] = "6stack", + [AD1986A_3STACK] = "3stack", + [AD1986A_LAPTOP] = "laptop", + [AD1986A_LAPTOP_EAPD] = "laptop-eapd", + [AD1986A_LAPTOP_AUTOMUTE] = "laptop-automute", + [AD1986A_ULTRA] = "ultra", + [AD1986A_SAMSUNG] = "samsung", +}; + +static struct snd_pci_quirk ad1986a_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1153, "ASUS M9", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x11f7, "ASUS U5A", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1213, "ASUS A6J", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1263, "ASUS U5F", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1297, "ASUS Z62F", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x12b3, "ASUS V1j", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1302, "ASUS W3j", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1443, "ASUS VX1", AD1986A_LAPTOP), + SND_PCI_QUIRK(0x1043, 0x1447, "ASUS A8J", AD1986A_3STACK), + SND_PCI_QUIRK(0x1043, 0x817f, "ASUS P5", AD1986A_3STACK), + SND_PCI_QUIRK(0x1043, 0x818f, "ASUS P5", AD1986A_LAPTOP), + SND_PCI_QUIRK(0x1043, 0x81b3, "ASUS P5", AD1986A_3STACK), + SND_PCI_QUIRK(0x1043, 0x81cb, "ASUS M2N", AD1986A_3STACK), + SND_PCI_QUIRK(0x1043, 0x8234, "ASUS M2N", AD1986A_3STACK), + SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_3STACK), + SND_PCI_QUIRK(0x1179, 0xff40, "Toshiba", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x144d, 0xb03c, "Samsung R55", AD1986A_3STACK), + SND_PCI_QUIRK(0x144d, 0xc01e, "FSC V2060", AD1986A_LAPTOP), + SND_PCI_QUIRK(0x144d, 0xc023, "Samsung X60", AD1986A_SAMSUNG), + SND_PCI_QUIRK(0x144d, 0xc024, "Samsung R65", AD1986A_SAMSUNG), + SND_PCI_QUIRK(0x144d, 0xc026, "Samsung X11", AD1986A_SAMSUNG), + SND_PCI_QUIRK(0x144d, 0xc027, "Samsung Q1", AD1986A_ULTRA), + SND_PCI_QUIRK(0x144d, 0xc504, "Samsung Q35", AD1986A_3STACK), + SND_PCI_QUIRK(0x17aa, 0x1011, "Lenovo M55", AD1986A_LAPTOP), + SND_PCI_QUIRK(0x17aa, 0x1017, "Lenovo A60", AD1986A_3STACK), + SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo N100", AD1986A_LAPTOP_AUTOMUTE), + SND_PCI_QUIRK(0x17c0, 0x2017, "Samsung M50", AD1986A_LAPTOP), + {} +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1986a_loopbacks[] = { + { 0x13, HDA_OUTPUT, 0 }, /* Mic */ + { 0x14, HDA_OUTPUT, 0 }, /* Phone */ + { 0x15, HDA_OUTPUT, 0 }, /* CD */ + { 0x16, HDA_OUTPUT, 0 }, /* Aux */ + { 0x17, HDA_OUTPUT, 0 }, /* Line */ + { } /* end */ +}; +#endif + +static int is_jack_available(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int conf = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + return get_defcfg_connect(conf) != AC_JACK_PORT_NONE; +} + +static int patch_ad1986a(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + 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; + spec->num_adc_nids = 1; + spec->adc_nids = ad1986a_adc_nids; + spec->capsrc_nids = ad1986a_capsrc_nids; + spec->input_mux = &ad1986a_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1986a_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1986a_init_verbs; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1986a_loopbacks; +#endif + spec->vmaster_nid = 0x1b; + + codec->patch_ops = ad198x_patch_ops; + + /* override some parameters */ + board_config = snd_hda_check_board_config(codec, AD1986A_MODELS, + ad1986a_models, + ad1986a_cfg_tbl); + switch (board_config) { + case AD1986A_3STACK: + spec->num_mixers = 2; + spec->mixers[1] = ad1986a_3st_mixers; + spec->num_init_verbs = 2; + spec->init_verbs[1] = ad1986a_ch2_init; + spec->channel_mode = ad1986a_modes; + spec->num_channel_mode = ARRAY_SIZE(ad1986a_modes); + spec->need_dac_fix = 1; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + break; + case AD1986A_LAPTOP: + spec->mixers[0] = ad1986a_laptop_mixers; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1986a_laptop_dac_nids; + break; + case AD1986A_LAPTOP_EAPD: + spec->mixers[0] = ad1986a_laptop_eapd_mixers; + spec->num_init_verbs = 2; + spec->init_verbs[1] = ad1986a_eapd_init_verbs; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1986a_laptop_dac_nids; + if (!is_jack_available(codec, 0x25)) + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1986a_laptop_eapd_capture_source; + break; + case AD1986A_SAMSUNG: + spec->mixers[0] = ad1986a_samsung_mixers; + spec->num_init_verbs = 3; + spec->init_verbs[1] = ad1986a_eapd_init_verbs; + spec->init_verbs[2] = ad1986a_automic_verbs; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1986a_laptop_dac_nids; + if (!is_jack_available(codec, 0x25)) + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1986a_automic_capture_source; + codec->patch_ops.unsol_event = ad1986a_automic_unsol_event; + codec->patch_ops.init = ad1986a_automic_init; + break; + case AD1986A_LAPTOP_AUTOMUTE: + spec->mixers[0] = ad1986a_laptop_automute_mixers; + spec->num_init_verbs = 3; + spec->init_verbs[1] = ad1986a_eapd_init_verbs; + spec->init_verbs[2] = ad1986a_hp_init_verbs; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1986a_laptop_dac_nids; + if (!is_jack_available(codec, 0x25)) + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1986a_laptop_eapd_capture_source; + codec->patch_ops.unsol_event = ad1986a_hp_unsol_event; + codec->patch_ops.init = ad1986a_hp_init; + break; + case AD1986A_ULTRA: + spec->mixers[0] = ad1986a_laptop_eapd_mixers; + spec->num_init_verbs = 2; + spec->init_verbs[1] = ad1986a_ultra_init; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1986a_laptop_dac_nids; + spec->multiout.dig_out_nid = 0; + break; + } + + /* AD1986A has a hardware problem that it can't share a stream + * with multiple output pins. The copy of front to surrounds + * causes noisy or silent outputs at a certain timing, e.g. + * changing the volume. + * So, let's disable the shared stream. + */ + spec->multiout.no_share_stream = 1; + + return 0; +} + +/* + * AD1983 specific + */ + +#define AD1983_SPDIF_OUT 0x02 +#define AD1983_DAC 0x03 +#define AD1983_ADC 0x04 + +static hda_nid_t ad1983_dac_nids[1] = { AD1983_DAC }; +static hda_nid_t ad1983_adc_nids[1] = { AD1983_ADC }; +static hda_nid_t ad1983_capsrc_nids[1] = { 0x15 }; + +static struct hda_input_mux ad1983_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x1 }, + { "Mix", 0x2 }, + { "Mix Mono", 0x3 }, + }, +}; + +/* + * SPDIF playback route + */ +static int ad1983_spdif_route_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { "PCM", "ADC" }; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item > 1) + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int ad1983_spdif_route_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->spdif_route; + return 0; +} + +static int ad1983_spdif_route_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + if (ucontrol->value.enumerated.item[0] > 1) + return -EINVAL; + if (spec->spdif_route != ucontrol->value.enumerated.item[0]) { + spec->spdif_route = ucontrol->value.enumerated.item[0]; + snd_hda_codec_write_cache(codec, spec->multiout.dig_out_nid, 0, + AC_VERB_SET_CONNECT_SEL, + spec->spdif_route); + return 1; + } + return 0; +} + +static struct snd_kcontrol_new ad1983_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("PC Speaker Playback Volume", 0x10, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("PC Speaker Playback Switch", 0x10, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct hda_verb ad1983_init_verbs[] = { + /* Front, HP, Mono; mute as default */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Beep, PCM, Mic, Line-In: mute */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Front, HP selectors; from Mix */ + {0x05, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x06, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* Mono selector; from Mix */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic selector; Mic */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Line-in selector: Line-in */ + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Mic boost: 0dB */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* Record selector: mic */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* SPDIF route: PCM */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Front Pin */ + {0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* HP Pin */ + {0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Mono Pin */ + {0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Mic Pin */ + {0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line Pin */ + {0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1983_loopbacks[] = { + { 0x12, HDA_OUTPUT, 0 }, /* Mic */ + { 0x13, HDA_OUTPUT, 0 }, /* Line */ + { } /* end */ +}; +#endif + +static int patch_ad1983(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1983_dac_nids); + spec->multiout.dac_nids = ad1983_dac_nids; + spec->multiout.dig_out_nid = AD1983_SPDIF_OUT; + spec->num_adc_nids = 1; + spec->adc_nids = ad1983_adc_nids; + spec->capsrc_nids = ad1983_capsrc_nids; + spec->input_mux = &ad1983_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1983_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1983_init_verbs; + spec->spdif_route = 0; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1983_loopbacks; +#endif + spec->vmaster_nid = 0x05; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + + +/* + * AD1981 HD specific + */ + +#define AD1981_SPDIF_OUT 0x02 +#define AD1981_DAC 0x03 +#define AD1981_ADC 0x04 + +static hda_nid_t ad1981_dac_nids[1] = { AD1981_DAC }; +static hda_nid_t ad1981_adc_nids[1] = { AD1981_ADC }; +static hda_nid_t ad1981_capsrc_nids[1] = { 0x15 }; + +/* 0x0c, 0x09, 0x0e, 0x0f, 0x19, 0x05, 0x18, 0x17 */ +static struct hda_input_mux ad1981_capture_source = { + .num_items = 7, + .items = { + { "Front Mic", 0x0 }, + { "Line", 0x1 }, + { "Mix", 0x2 }, + { "Mix Mono", 0x3 }, + { "CD", 0x4 }, + { "Mic", 0x6 }, + { "Aux", 0x7 }, + }, +}; + +static struct snd_kcontrol_new ad1981_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x07, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Aux Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Aux Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("PC Speaker Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("PC Speaker Playback Switch", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* identical with AD1983 */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct hda_verb ad1981_init_verbs[] = { + /* Front, HP, Mono; mute as default */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Beep, PCM, Front Mic, Line, Rear Mic, Aux, CD-In: mute */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x13, 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}, + /* Front, HP selectors; from Mix */ + {0x05, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x06, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* Mono selector; from Mix */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic Mixer; select Front Mic */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* Mic boost: 0dB */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* Record selector: Front mic */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + /* SPDIF route: PCM */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Front Pin */ + {0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* HP Pin */ + {0x06, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, + /* Mono Pin */ + {0x07, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* Front & Rear Mic Pins */ + {0x08, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* Line Pin */ + {0x09, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* Digital Beep */ + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Line-Out as Input: disabled */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1981_loopbacks[] = { + { 0x12, HDA_OUTPUT, 0 }, /* Front Mic */ + { 0x13, HDA_OUTPUT, 0 }, /* Line */ + { 0x1b, HDA_OUTPUT, 0 }, /* Aux */ + { 0x1c, HDA_OUTPUT, 0 }, /* Mic */ + { 0x1d, HDA_OUTPUT, 0 }, /* CD */ + { } /* end */ +}; +#endif + +/* + * Patch for HP nx6320 + * + * nx6320 uses EAPD in the reverse way - EAPD-on means the internal + * speaker output enabled _and_ mute-LED off. + */ + +#define AD1981_HP_EVENT 0x37 +#define AD1981_MIC_EVENT 0x38 + +static struct hda_verb ad1981_hp_init_verbs[] = { + {0x05, AC_VERB_SET_EAPD_BTLENABLE, 0x00 }, /* default off */ + /* pin sensing on HP and Mic jacks */ + {0x06, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_HP_EVENT}, + {0x08, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_MIC_EVENT}, + {} +}; + +/* turn on/off EAPD (+ mute HP) as a master switch */ +static int ad1981_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + + if (! ad198x_eapd_put(kcontrol, ucontrol)) + return 0; + /* change speaker pin appropriately */ + snd_hda_codec_write(codec, 0x05, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + spec->cur_eapd ? PIN_OUT : 0); + /* toggle HP mute appropriately */ + snd_hda_codec_amp_stereo(codec, 0x06, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + spec->cur_eapd ? 0 : HDA_AMP_MUTE); + return 1; +} + +/* bind volumes of both NID 0x05 and 0x06 */ +static struct hda_bind_ctls ad1981_hp_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x05, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x06, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* mute internal speaker if HP is plugged */ +static void ad1981_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x06, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x05, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +/* toggle input of built-in and mic jack appropriately */ +static void ad1981_hp_automic(struct hda_codec *codec) +{ + static struct hda_verb mic_jack_on[] = { + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {} + }; + static struct hda_verb mic_jack_off[] = { + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {} + }; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x08, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + if (present) + snd_hda_sequence_write(codec, mic_jack_on); + else + snd_hda_sequence_write(codec, mic_jack_off); +} + +/* unsolicited event for HP jack sensing */ +static void ad1981_hp_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + switch (res) { + case AD1981_HP_EVENT: + ad1981_hp_automute(codec); + break; + case AD1981_MIC_EVENT: + ad1981_hp_automic(codec); + break; + } +} + +static struct hda_input_mux ad1981_hp_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Docking-Station", 0x1 }, + { "Mix", 0x2 }, + }, +}; + +static struct snd_kcontrol_new ad1981_hp_mixers[] = { + HDA_BIND_VOL("Master Playback Volume", &ad1981_hp_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = ad198x_eapd_info, + .get = ad198x_eapd_get, + .put = ad1981_hp_master_sw_put, + .private_value = 0x05, + }, + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), +#if 0 + /* FIXME: analog mic/line loopback doesn't work with my tests... + * (although recording is OK) + */ + HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Docking-Station Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Docking-Station Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x1c, 0x0, HDA_OUTPUT), + /* FIXME: does this laptop have analog CD connection? */ + HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT), +#endif + HDA_CODEC_VOLUME("Mic Boost", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x18, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +/* initialize jack-sensing, too */ +static int ad1981_hp_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1981_hp_automute(codec); + ad1981_hp_automic(codec); + return 0; +} + +/* configuration for Toshiba Laptops */ +static struct hda_verb ad1981_toshiba_init_verbs[] = { + {0x05, AC_VERB_SET_EAPD_BTLENABLE, 0x01 }, /* default on */ + /* pin sensing on HP and Mic jacks */ + {0x06, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_HP_EVENT}, + {0x08, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1981_MIC_EVENT}, + {} +}; + +static struct snd_kcontrol_new ad1981_toshiba_mixers[] = { + HDA_CODEC_VOLUME("Amp Volume", 0x1a, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Amp Switch", 0x1a, 0x0, HDA_OUTPUT), + { } +}; + +/* configuration for Lenovo Thinkpad T60 */ +static struct snd_kcontrol_new ad1981_thinkpad_mixers[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* identical with AD1983 */ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct hda_input_mux ad1981_thinkpad_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Mix", 0x2 }, + { "CD", 0x4 }, + }, +}; + +/* models */ +enum { + AD1981_BASIC, + AD1981_HP, + AD1981_THINKPAD, + AD1981_TOSHIBA, + AD1981_MODELS +}; + +static const char *ad1981_models[AD1981_MODELS] = { + [AD1981_HP] = "hp", + [AD1981_THINKPAD] = "thinkpad", + [AD1981_BASIC] = "basic", + [AD1981_TOSHIBA] = "toshiba" +}; + +static struct snd_pci_quirk ad1981_cfg_tbl[] = { + SND_PCI_QUIRK(0x1014, 0x0597, "Lenovo Z60", AD1981_THINKPAD), + SND_PCI_QUIRK(0x1014, 0x05b7, "Lenovo Z60m", AD1981_THINKPAD), + /* All HP models */ + SND_PCI_QUIRK(0x103c, 0, "HP nx", AD1981_HP), + SND_PCI_QUIRK(0x1179, 0x0001, "Toshiba U205", AD1981_TOSHIBA), + /* Lenovo Thinkpad T60/X60/Z6xx */ + SND_PCI_QUIRK(0x17aa, 0, "Lenovo Thinkpad", AD1981_THINKPAD), + /* HP nx6320 (reversed SSID, H/W bug) */ + SND_PCI_QUIRK(0x30b0, 0x103c, "HP nx6320", AD1981_HP), + {} +}; + +static int patch_ad1981(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1981_dac_nids); + spec->multiout.dac_nids = ad1981_dac_nids; + spec->multiout.dig_out_nid = AD1981_SPDIF_OUT; + spec->num_adc_nids = 1; + spec->adc_nids = ad1981_adc_nids; + spec->capsrc_nids = ad1981_capsrc_nids; + spec->input_mux = &ad1981_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1981_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1981_init_verbs; + spec->spdif_route = 0; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1981_loopbacks; +#endif + spec->vmaster_nid = 0x05; + + codec->patch_ops = ad198x_patch_ops; + + /* override some parameters */ + board_config = snd_hda_check_board_config(codec, AD1981_MODELS, + ad1981_models, + ad1981_cfg_tbl); + switch (board_config) { + case AD1981_HP: + spec->mixers[0] = ad1981_hp_mixers; + spec->num_init_verbs = 2; + spec->init_verbs[1] = ad1981_hp_init_verbs; + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1981_hp_capture_source; + + codec->patch_ops.init = ad1981_hp_init; + codec->patch_ops.unsol_event = ad1981_hp_unsol_event; + break; + case AD1981_THINKPAD: + spec->mixers[0] = ad1981_thinkpad_mixers; + spec->input_mux = &ad1981_thinkpad_capture_source; + break; + case AD1981_TOSHIBA: + spec->mixers[0] = ad1981_hp_mixers; + spec->mixers[1] = ad1981_toshiba_mixers; + spec->num_init_verbs = 2; + spec->init_verbs[1] = ad1981_toshiba_init_verbs; + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1981_hp_capture_source; + codec->patch_ops.init = ad1981_hp_init; + codec->patch_ops.unsol_event = ad1981_hp_unsol_event; + break; + } + return 0; +} + + +/* + * AD1988 + * + * Output pins and routes + * + * Pin Mix Sel DAC (*) + * port-A 0x11 (mute/hp) <- 0x22 <- 0x37 <- 03/04/06 + * port-B 0x14 (mute/hp) <- 0x2b <- 0x30 <- 03/04/06 + * port-C 0x15 (mute) <- 0x2c <- 0x31 <- 05/0a + * port-D 0x12 (mute/hp) <- 0x29 <- 04 + * port-E 0x17 (mute/hp) <- 0x26 <- 0x32 <- 05/0a + * port-F 0x16 (mute) <- 0x2a <- 06 + * port-G 0x24 (mute) <- 0x27 <- 05 + * port-H 0x25 (mute) <- 0x28 <- 0a + * mono 0x13 (mute/amp)<- 0x1e <- 0x36 <- 03/04/06 + * + * DAC0 = 03h, DAC1 = 04h, DAC2 = 05h, DAC3 = 06h, DAC4 = 0ah + * (*) DAC2/3/4 are swapped to DAC3/4/2 on AD198A rev.2 due to a h/w bug. + * + * Input pins and routes + * + * pin boost mix input # / adc input # + * port-A 0x11 -> 0x38 -> mix 2, ADC 0 + * port-B 0x14 -> 0x39 -> mix 0, ADC 1 + * port-C 0x15 -> 0x3a -> 33:0 - mix 1, ADC 2 + * port-D 0x12 -> 0x3d -> mix 3, ADC 8 + * port-E 0x17 -> 0x3c -> 34:0 - mix 4, ADC 4 + * port-F 0x16 -> 0x3b -> mix 5, ADC 3 + * port-G 0x24 -> N/A -> 33:1 - mix 1, 34:1 - mix 4, ADC 6 + * port-H 0x25 -> N/A -> 33:2 - mix 1, 34:2 - mix 4, ADC 7 + * + * + * DAC assignment + * 6stack - front/surr/CLFE/side/opt DACs - 04/06/05/0a/03 + * 3stack - front/surr/CLFE/opt DACs - 04/05/0a/03 + * + * Inputs of Analog Mix (0x20) + * 0:Port-B (front mic) + * 1:Port-C/G/H (line-in) + * 2:Port-A + * 3:Port-D (line-in/2) + * 4:Port-E/G/H (mic-in) + * 5:Port-F (mic2-in) + * 6:CD + * 7:Beep + * + * ADC selection + * 0:Port-A + * 1:Port-B (front mic-in) + * 2:Port-C (line-in) + * 3:Port-F (mic2-in) + * 4:Port-E (mic-in) + * 5:CD + * 6:Port-G + * 7:Port-H + * 8:Port-D (line-in/2) + * 9:Mix + * + * Proposed pin assignments by the datasheet + * + * 6-stack + * Port-A front headphone + * B front mic-in + * C rear line-in + * D rear front-out + * E rear mic-in + * F rear surround + * G rear CLFE + * H rear side + * + * 3-stack + * Port-A front headphone + * B front mic + * C rear line-in/surround + * D rear front-out + * E rear mic-in/CLFE + * + * laptop + * Port-A headphone + * B mic-in + * C docking station + * D internal speaker (with EAPD) + * E/F quad mic array + */ + + +/* models */ +enum { + AD1988_6STACK, + AD1988_6STACK_DIG, + AD1988_3STACK, + AD1988_3STACK_DIG, + AD1988_LAPTOP, + AD1988_LAPTOP_DIG, + AD1988_AUTO, + AD1988_MODEL_LAST, +}; + +/* reivision id to check workarounds */ +#define AD1988A_REV2 0x100200 + +#define is_rev2(codec) \ + ((codec)->vendor_id == 0x11d41988 && \ + (codec)->revision_id == AD1988A_REV2) + +/* + * mixers + */ + +static hda_nid_t ad1988_6stack_dac_nids[4] = { + 0x04, 0x06, 0x05, 0x0a +}; + +static hda_nid_t ad1988_3stack_dac_nids[3] = { + 0x04, 0x05, 0x0a +}; + +/* for AD1988A revision-2, DAC2-4 are swapped */ +static hda_nid_t ad1988_6stack_dac_nids_rev2[4] = { + 0x04, 0x05, 0x0a, 0x06 +}; + +static hda_nid_t ad1988_3stack_dac_nids_rev2[3] = { + 0x04, 0x0a, 0x06 +}; + +static hda_nid_t ad1988_adc_nids[3] = { + 0x08, 0x09, 0x0f +}; + +static hda_nid_t ad1988_capsrc_nids[3] = { + 0x0c, 0x0d, 0x0e +}; + +#define AD1988_SPDIF_OUT 0x02 +#define AD1988_SPDIF_OUT_HDMI 0x0b +#define AD1988_SPDIF_IN 0x07 + +static hda_nid_t ad1989b_slave_dig_outs[] = { + AD1988_SPDIF_OUT, AD1988_SPDIF_OUT_HDMI, 0 +}; + +static struct hda_input_mux ad1988_6stack_capture_source = { + .num_items = 5, + .items = { + { "Front Mic", 0x1 }, /* port-B */ + { "Line", 0x2 }, /* port-C */ + { "Mic", 0x4 }, /* port-E */ + { "CD", 0x5 }, + { "Mix", 0x9 }, + }, +}; + +static struct hda_input_mux ad1988_laptop_capture_source = { + .num_items = 3, + .items = { + { "Mic/Line", 0x1 }, /* port-B */ + { "CD", 0x5 }, + { "Mix", 0x9 }, + }, +}; + +/* + */ +static int ad198x_ch_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode, + spec->num_channel_mode); +} + +static int ad198x_ch_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, spec->multiout.max_channels); +} + +static int ad198x_ch_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct ad198x_spec *spec = codec->spec; + int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, + &spec->multiout.max_channels); + if (err >= 0 && spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; + return err; +} + +/* 6-stack mode */ +static struct snd_kcontrol_new ad1988_6stack_mixers1[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x06, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0a, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1988_6stack_mixers1_rev2[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0a, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x06, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1988_6stack_mixers2[] = { + HDA_BIND_MUTE("Front Playback Switch", 0x29, 2, HDA_INPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x2a, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x27, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x27, 2, 2, HDA_INPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x28, 2, HDA_INPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x22, 2, HDA_INPUT), + HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x4, HDA_INPUT), + + HDA_CODEC_VOLUME("Beep Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x10, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT), + + { } /* end */ +}; + +/* 3-stack mode */ +static struct snd_kcontrol_new ad1988_3stack_mixers1[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1988_3stack_mixers1_rev2[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x06, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x06, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1988_3stack_mixers2[] = { + HDA_BIND_MUTE("Front Playback Switch", 0x29, 2, HDA_INPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x2c, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x26, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x26, 2, 2, HDA_INPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x22, 2, HDA_INPUT), + HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x4, HDA_INPUT), + + HDA_CODEC_VOLUME("Beep Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x10, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = ad198x_ch_mode_info, + .get = ad198x_ch_mode_get, + .put = ad198x_ch_mode_put, + }, + + { } /* end */ +}; + +/* laptop mode */ +static struct snd_kcontrol_new ad1988_laptop_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x29, 0x0, HDA_INPUT), + HDA_BIND_MUTE("Mono Playback Switch", 0x1e, 2, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x6, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x1, HDA_INPUT), + + HDA_CODEC_VOLUME("Beep Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x10, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Analog Mix Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Analog Mix Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Boost", 0x39, 0x0, HDA_OUTPUT), + + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "External Amplifier", + .info = ad198x_eapd_info, + .get = ad198x_eapd_get, + .put = ad198x_eapd_put, + .private_value = 0x12 | (1 << 8), /* port-D, inversed */ + }, + + { } /* end */ +}; + +/* capture */ +static struct snd_kcontrol_new ad1988_capture_mixers[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x0e, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x0e, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 3, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +static int ad1988_spdif_playback_source_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { + "PCM", "ADC1", "ADC2", "ADC3" + }; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 4; + if (uinfo->value.enumerated.item >= 4) + uinfo->value.enumerated.item = 3; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int ad1988_spdif_playback_source_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int sel; + + sel = snd_hda_codec_read(codec, 0x1d, 0, AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_INPUT); + if (!(sel & 0x80)) + ucontrol->value.enumerated.item[0] = 0; + else { + sel = snd_hda_codec_read(codec, 0x0b, 0, + AC_VERB_GET_CONNECT_SEL, 0); + if (sel < 3) + sel++; + else + sel = 0; + ucontrol->value.enumerated.item[0] = sel; + } + return 0; +} + +static int ad1988_spdif_playback_source_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int val, sel; + int change; + + val = ucontrol->value.enumerated.item[0]; + if (val > 3) + return -EINVAL; + if (!val) { + sel = snd_hda_codec_read(codec, 0x1d, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_INPUT); + change = sel & 0x80; + if (change) { + snd_hda_codec_write_cache(codec, 0x1d, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(0)); + snd_hda_codec_write_cache(codec, 0x1d, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(1)); + } + } else { + sel = snd_hda_codec_read(codec, 0x1d, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_INPUT | 0x01); + change = sel & 0x80; + if (change) { + snd_hda_codec_write_cache(codec, 0x1d, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_MUTE(0)); + snd_hda_codec_write_cache(codec, 0x1d, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(1)); + } + sel = snd_hda_codec_read(codec, 0x0b, 0, + AC_VERB_GET_CONNECT_SEL, 0) + 1; + change |= sel != val; + if (change) + snd_hda_codec_write_cache(codec, 0x0b, 0, + AC_VERB_SET_CONNECT_SEL, + val - 1); + } + return change; +} + +static struct snd_kcontrol_new ad1988_spdif_out_mixers[] = { + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + .info = ad1988_spdif_playback_source_info, + .get = ad1988_spdif_playback_source_get, + .put = ad1988_spdif_playback_source_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1988_spdif_in_mixers[] = { + HDA_CODEC_VOLUME("IEC958 Capture Volume", 0x1c, 0x0, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1989_spdif_out_mixers[] = { + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("HDMI Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* + * initialization verbs + */ + +/* + * for 6-stack (+dig) + */ +static struct hda_verb ad1988_6stack_init_verbs[] = { + /* Front, Surround, CLFE, side DAC; unmute as default */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Port-A front headphon path */ + {0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Port-D line-out path */ + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Port-F surround path */ + {0x2a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x2a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Port-G CLFE path */ + {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Port-H side path */ + {0x28, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x28, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x25, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Mono out path */ + {0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */ + /* Port-B front mic-in path */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-C line-in path */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x33, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Port-E mic-in path */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x34, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Analog CD Input */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + + { } +}; + +static struct hda_verb ad1988_capture_init_verbs[] = { + /* mute analog mix */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* select ADCs - front-mic */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* ADCs; muted */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + { } +}; + +static struct hda_verb ad1988_spdif_init_verbs[] = { + /* SPDIF out sel */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x0}, /* ADC1 */ + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* SPDIF out pin */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + + { } +}; + +/* AD1989 has no ADC -> SPDIF route */ +static struct hda_verb ad1989_spdif_init_verbs[] = { + /* SPDIF-1 out pin */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + /* SPDIF-2/HDMI out pin */ + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } +}; + +/* + * verbs for 3stack (+dig) + */ +static struct hda_verb ad1988_3stack_ch2_init[] = { + /* set port-C to line-in */ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + /* set port-E to mic-in */ + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { } /* end */ +}; + +static struct hda_verb ad1988_3stack_ch6_init[] = { + /* set port-C to surround out */ + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + /* set port-E to CLFE out */ + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } /* end */ +}; + +static struct hda_channel_mode ad1988_3stack_modes[2] = { + { 2, ad1988_3stack_ch2_init }, + { 6, ad1988_3stack_ch6_init }, +}; + +static struct hda_verb ad1988_3stack_init_verbs[] = { + /* Front, Surround, CLFE, side DAC; unmute as default */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Port-A front headphon path */ + {0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Port-D line-out path */ + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Mono out path */ + {0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */ + /* Port-B front mic-in path */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-C line-in/surround path - 6ch mode as default */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x31, AC_VERB_SET_CONNECT_SEL, 0x0}, /* output sel: DAC 0x05 */ + {0x33, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Port-E mic-in/CLFE path - 6ch mode as default */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x32, AC_VERB_SET_CONNECT_SEL, 0x1}, /* output sel: DAC 0x0a */ + {0x34, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* mute analog mix */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* select ADCs - front-mic */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* ADCs; muted */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + { } +}; + +/* + * verbs for laptop mode (+dig) + */ +static struct hda_verb ad1988_laptop_hp_on[] = { + /* unmute port-A and mute port-D */ + { 0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; +static struct hda_verb ad1988_laptop_hp_off[] = { + /* mute port-A and unmute port-D */ + { 0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } /* end */ +}; + +#define AD1988_HP_EVENT 0x01 + +static struct hda_verb ad1988_laptop_init_verbs[] = { + /* Front, Surround, CLFE, side DAC; unmute as default */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Port-A front headphon path */ + {0x37, AC_VERB_SET_CONNECT_SEL, 0x01}, /* DAC1:04h */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* unsolicited event for pin-sense */ + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1988_HP_EVENT }, + /* Port-D line-out path + EAPD */ + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x00}, /* EAPD-off */ + /* Mono out path */ + {0x36, AC_VERB_SET_CONNECT_SEL, 0x1}, /* DAC1:04h */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, 0xb01f}, /* unmute, 0dB */ + /* Port-B mic-in path */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-C docking station - try to output */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x33, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* mute analog mix */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* select ADCs - mic */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* ADCs; muted */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + { } +}; + +static void ad1988_laptop_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) != AD1988_HP_EVENT) + return; + if (snd_hda_codec_read(codec, 0x11, 0, AC_VERB_GET_PIN_SENSE, 0) & (1 << 31)) + snd_hda_sequence_write(codec, ad1988_laptop_hp_on); + else + snd_hda_sequence_write(codec, ad1988_laptop_hp_off); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1988_loopbacks[] = { + { 0x20, HDA_INPUT, 0 }, /* Front Mic */ + { 0x20, HDA_INPUT, 1 }, /* Line */ + { 0x20, HDA_INPUT, 4 }, /* Mic */ + { 0x20, HDA_INPUT, 6 }, /* CD */ + { } /* end */ +}; +#endif + +/* + * Automatic parse of I/O pins from the BIOS configuration + */ + +#define NUM_CONTROL_ALLOC 32 +#define NUM_VERB_ALLOC 32 + +enum { + AD_CTL_WIDGET_VOL, + AD_CTL_WIDGET_MUTE, + AD_CTL_BIND_MUTE, +}; +static struct snd_kcontrol_new ad1988_control_templates[] = { + HDA_CODEC_VOLUME(NULL, 0, 0, 0), + HDA_CODEC_MUTE(NULL, 0, 0, 0), + HDA_BIND_MUTE(NULL, 0, 0, 0), +}; + +/* add dynamic controls */ +static int add_control(struct ad198x_spec *spec, int type, const char *name, + unsigned long val) +{ + struct snd_kcontrol_new *knew; + + if (spec->num_kctl_used >= spec->num_kctl_alloc) { + int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC; + + knew = kcalloc(num + 1, sizeof(*knew), GFP_KERNEL); /* array + terminator */ + if (! knew) + return -ENOMEM; + if (spec->kctl_alloc) { + memcpy(knew, spec->kctl_alloc, sizeof(*knew) * spec->num_kctl_alloc); + kfree(spec->kctl_alloc); + } + spec->kctl_alloc = knew; + spec->num_kctl_alloc = num; + } + + knew = &spec->kctl_alloc[spec->num_kctl_used]; + *knew = ad1988_control_templates[type]; + knew->name = kstrdup(name, GFP_KERNEL); + if (! knew->name) + return -ENOMEM; + knew->private_value = val; + spec->num_kctl_used++; + return 0; +} + +#define AD1988_PIN_CD_NID 0x18 +#define AD1988_PIN_BEEP_NID 0x10 + +static hda_nid_t ad1988_mixer_nids[8] = { + /* A B C D E F G H */ + 0x22, 0x2b, 0x2c, 0x29, 0x26, 0x2a, 0x27, 0x28 +}; + +static inline hda_nid_t ad1988_idx_to_dac(struct hda_codec *codec, int idx) +{ + static hda_nid_t idx_to_dac[8] = { + /* A B C D E F G H */ + 0x04, 0x06, 0x05, 0x04, 0x0a, 0x06, 0x05, 0x0a + }; + static hda_nid_t idx_to_dac_rev2[8] = { + /* A B C D E F G H */ + 0x04, 0x05, 0x0a, 0x04, 0x06, 0x05, 0x0a, 0x06 + }; + if (is_rev2(codec)) + return idx_to_dac_rev2[idx]; + else + return idx_to_dac[idx]; +} + +static hda_nid_t ad1988_boost_nids[8] = { + 0x38, 0x39, 0x3a, 0x3d, 0x3c, 0x3b, 0, 0 +}; + +static int ad1988_pin_idx(hda_nid_t nid) +{ + static hda_nid_t ad1988_io_pins[8] = { + 0x11, 0x14, 0x15, 0x12, 0x17, 0x16, 0x24, 0x25 + }; + int i; + for (i = 0; i < ARRAY_SIZE(ad1988_io_pins); i++) + if (ad1988_io_pins[i] == nid) + return i; + return 0; /* should be -1 */ +} + +static int ad1988_pin_to_loopback_idx(hda_nid_t nid) +{ + static int loopback_idx[8] = { + 2, 0, 1, 3, 4, 5, 1, 4 + }; + switch (nid) { + case AD1988_PIN_CD_NID: + return 6; + default: + return loopback_idx[ad1988_pin_idx(nid)]; + } +} + +static int ad1988_pin_to_adc_idx(hda_nid_t nid) +{ + static int adc_idx[8] = { + 0, 1, 2, 8, 4, 3, 6, 7 + }; + switch (nid) { + case AD1988_PIN_CD_NID: + return 5; + default: + return adc_idx[ad1988_pin_idx(nid)]; + } +} + +/* fill in the dac_nids table from the parsed pin configuration */ +static int ad1988_auto_fill_dac_nids(struct hda_codec *codec, + const struct auto_pin_cfg *cfg) +{ + struct ad198x_spec *spec = codec->spec; + int i, idx; + + spec->multiout.dac_nids = spec->private_dac_nids; + + /* check the pins hardwired to audio widget */ + for (i = 0; i < cfg->line_outs; i++) { + idx = ad1988_pin_idx(cfg->line_out_pins[i]); + spec->multiout.dac_nids[i] = ad1988_idx_to_dac(codec, idx); + } + spec->multiout.num_dacs = cfg->line_outs; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int ad1988_auto_create_multi_out_ctls(struct ad198x_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { "Front", "Surround", NULL /*CLFE*/, "Side" }; + hda_nid_t nid; + int i, err; + + for (i = 0; i < cfg->line_outs; i++) { + hda_nid_t dac = spec->multiout.dac_nids[i]; + if (! dac) + continue; + nid = ad1988_mixer_nids[ad1988_pin_idx(cfg->line_out_pins[i])]; + if (i == 2) { + /* Center/LFE */ + err = add_control(spec, AD_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(dac, 1, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, AD_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(dac, 2, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, AD_CTL_BIND_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 1, 2, HDA_INPUT)); + if (err < 0) + return err; + err = add_control(spec, AD_CTL_BIND_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 2, HDA_INPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = add_control(spec, AD_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(dac, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = add_control(spec, AD_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +/* add playback controls for speaker and HP outputs */ +static int ad1988_auto_create_extra_out(struct hda_codec *codec, hda_nid_t pin, + const char *pfx) +{ + struct ad198x_spec *spec = codec->spec; + hda_nid_t nid; + int i, idx, err; + char name[32]; + + if (! pin) + return 0; + + idx = ad1988_pin_idx(pin); + nid = ad1988_idx_to_dac(codec, idx); + /* check whether the corresponding DAC was already taken */ + for (i = 0; i < spec->autocfg.line_outs; i++) { + hda_nid_t pin = spec->autocfg.line_out_pins[i]; + hda_nid_t dac = ad1988_idx_to_dac(codec, ad1988_pin_idx(pin)); + if (dac == nid) + break; + } + if (i >= spec->autocfg.line_outs) { + /* specify the DAC as the extra output */ + if (!spec->multiout.hp_nid) + spec->multiout.hp_nid = nid; + else + spec->multiout.extra_out_nid[0] = nid; + /* control HP volume/switch on the output mixer amp */ + sprintf(name, "%s Playback Volume", pfx); + err = add_control(spec, AD_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + nid = ad1988_mixer_nids[idx]; + sprintf(name, "%s Playback Switch", pfx); + if ((err = add_control(spec, AD_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT))) < 0) + return err; + return 0; +} + +/* create input playback/capture controls for the given pin */ +static int new_analog_input(struct ad198x_spec *spec, hda_nid_t pin, + const char *ctlname, int boost) +{ + char name[32]; + int err, idx; + + sprintf(name, "%s Playback Volume", ctlname); + idx = ad1988_pin_to_loopback_idx(pin); + if ((err = add_control(spec, AD_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x20, 3, idx, HDA_INPUT))) < 0) + return err; + sprintf(name, "%s Playback Switch", ctlname); + if ((err = add_control(spec, AD_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(0x20, 3, idx, HDA_INPUT))) < 0) + return err; + if (boost) { + hda_nid_t bnid; + idx = ad1988_pin_idx(pin); + bnid = ad1988_boost_nids[idx]; + if (bnid) { + sprintf(name, "%s Boost", ctlname); + return add_control(spec, AD_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(bnid, 3, idx, HDA_OUTPUT)); + + } + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int ad1988_auto_create_analog_input_ctls(struct ad198x_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, err; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], + i <= AUTO_PIN_FRONT_MIC); + if (err < 0) + return err; + imux->items[imux->num_items].label = auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = ad1988_pin_to_adc_idx(cfg->input_pins[i]); + imux->num_items++; + } + imux->items[imux->num_items].label = "Mix"; + imux->items[imux->num_items].index = 9; + imux->num_items++; + + if ((err = add_control(spec, AD_CTL_WIDGET_VOL, + "Analog Mix Playback Volume", + HDA_COMPOSE_AMP_VAL(0x21, 3, 0x0, HDA_OUTPUT))) < 0) + return err; + if ((err = add_control(spec, AD_CTL_WIDGET_MUTE, + "Analog Mix Playback Switch", + HDA_COMPOSE_AMP_VAL(0x21, 3, 0x0, HDA_OUTPUT))) < 0) + return err; + + return 0; +} + +static void ad1988_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + /* set as output */ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, pin_type); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE); + switch (nid) { + case 0x11: /* port-A - DAC 04 */ + snd_hda_codec_write(codec, 0x37, 0, AC_VERB_SET_CONNECT_SEL, 0x01); + break; + case 0x14: /* port-B - DAC 06 */ + snd_hda_codec_write(codec, 0x30, 0, AC_VERB_SET_CONNECT_SEL, 0x02); + break; + case 0x15: /* port-C - DAC 05 */ + snd_hda_codec_write(codec, 0x31, 0, AC_VERB_SET_CONNECT_SEL, 0x00); + break; + case 0x17: /* port-E - DAC 0a */ + snd_hda_codec_write(codec, 0x32, 0, AC_VERB_SET_CONNECT_SEL, 0x01); + break; + case 0x13: /* mono - DAC 04 */ + snd_hda_codec_write(codec, 0x36, 0, AC_VERB_SET_CONNECT_SEL, 0x01); + break; + } +} + +static void ad1988_auto_init_multi_out(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->autocfg.line_outs; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + ad1988_auto_set_output_and_unmute(codec, nid, PIN_OUT, i); + } +} + +static void ad1988_auto_init_extra_out(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.speaker_pins[0]; + if (pin) /* connect to front */ + ad1988_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + ad1988_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); +} + +static void ad1988_auto_init_analog_input(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int i, idx; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (! nid) + continue; + switch (nid) { + case 0x15: /* port-C */ + snd_hda_codec_write(codec, 0x33, 0, AC_VERB_SET_CONNECT_SEL, 0x0); + break; + case 0x17: /* port-E */ + snd_hda_codec_write(codec, 0x34, 0, AC_VERB_SET_CONNECT_SEL, 0x0); + break; + } + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + i <= AUTO_PIN_FRONT_MIC ? PIN_VREF80 : PIN_IN); + if (nid != AD1988_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + idx = ad1988_pin_idx(nid); + if (ad1988_boost_nids[idx]) + snd_hda_codec_write(codec, ad1988_boost_nids[idx], 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_ZERO); + } +} + +/* parse the BIOS configuration and set up the alc_spec */ +/* return 1 if successful, 0 if the proper config is not found, or a negative error code */ +static int ad1988_parse_auto_config(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + int err; + + if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 0) + return err; + if ((err = ad1988_auto_fill_dac_nids(codec, &spec->autocfg)) < 0) + return err; + if (! spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + if ((err = ad1988_auto_create_multi_out_ctls(spec, &spec->autocfg)) < 0 || + (err = ad1988_auto_create_extra_out(codec, + spec->autocfg.speaker_pins[0], + "Speaker")) < 0 || + (err = ad1988_auto_create_extra_out(codec, spec->autocfg.hp_pins[0], + "Headphone")) < 0 || + (err = ad1988_auto_create_analog_input_ctls(spec, &spec->autocfg)) < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = AD1988_SPDIF_OUT; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = AD1988_SPDIF_IN; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = ad1988_6stack_init_verbs; + + spec->input_mux = &spec->private_imux; + + return 1; +} + +/* init callback for auto-configuration model -- overriding the default init */ +static int ad1988_auto_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1988_auto_init_multi_out(codec); + ad1988_auto_init_extra_out(codec); + ad1988_auto_init_analog_input(codec); + return 0; +} + + +/* + */ + +static const char *ad1988_models[AD1988_MODEL_LAST] = { + [AD1988_6STACK] = "6stack", + [AD1988_6STACK_DIG] = "6stack-dig", + [AD1988_3STACK] = "3stack", + [AD1988_3STACK_DIG] = "3stack-dig", + [AD1988_LAPTOP] = "laptop", + [AD1988_LAPTOP_DIG] = "laptop-dig", + [AD1988_AUTO] = "auto", +}; + +static struct snd_pci_quirk ad1988_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x81ec, "Asus P5B-DLX", AD1988_6STACK_DIG), + SND_PCI_QUIRK(0x1043, 0x81f6, "Asus M2N-SLI", AD1988_6STACK_DIG), + SND_PCI_QUIRK(0x1043, 0x8277, "Asus P5K-E/WIFI-AP", AD1988_6STACK_DIG), + SND_PCI_QUIRK(0x1043, 0x8311, "Asus P5Q-Premium/Pro", AD1988_6STACK_DIG), + {} +}; + +static int patch_ad1988(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + if (is_rev2(codec)) + snd_printk(KERN_INFO "patch_analog: AD1988A rev.2 is detected, enable workarounds\n"); + + board_config = snd_hda_check_board_config(codec, AD1988_MODEL_LAST, + ad1988_models, ad1988_cfg_tbl); + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for AD1988, trying auto-probe from BIOS...\n"); + board_config = AD1988_AUTO; + } + + if (board_config == AD1988_AUTO) { + /* automatic parse from the BIOS config */ + int err = ad1988_parse_auto_config(codec); + if (err < 0) { + ad198x_free(codec); + return err; + } else if (! err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration from BIOS. Using 6-stack mode...\n"); + board_config = AD1988_6STACK; + } + } + + switch (board_config) { + case AD1988_6STACK: + case AD1988_6STACK_DIG: + spec->multiout.max_channels = 8; + spec->multiout.num_dacs = 4; + if (is_rev2(codec)) + spec->multiout.dac_nids = ad1988_6stack_dac_nids_rev2; + else + spec->multiout.dac_nids = ad1988_6stack_dac_nids; + spec->input_mux = &ad1988_6stack_capture_source; + spec->num_mixers = 2; + if (is_rev2(codec)) + spec->mixers[0] = ad1988_6stack_mixers1_rev2; + else + spec->mixers[0] = ad1988_6stack_mixers1; + spec->mixers[1] = ad1988_6stack_mixers2; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1988_6stack_init_verbs; + if (board_config == AD1988_6STACK_DIG) { + spec->multiout.dig_out_nid = AD1988_SPDIF_OUT; + spec->dig_in_nid = AD1988_SPDIF_IN; + } + break; + case AD1988_3STACK: + case AD1988_3STACK_DIG: + spec->multiout.max_channels = 6; + spec->multiout.num_dacs = 3; + if (is_rev2(codec)) + spec->multiout.dac_nids = ad1988_3stack_dac_nids_rev2; + else + spec->multiout.dac_nids = ad1988_3stack_dac_nids; + spec->input_mux = &ad1988_6stack_capture_source; + spec->channel_mode = ad1988_3stack_modes; + spec->num_channel_mode = ARRAY_SIZE(ad1988_3stack_modes); + spec->num_mixers = 2; + if (is_rev2(codec)) + spec->mixers[0] = ad1988_3stack_mixers1_rev2; + else + spec->mixers[0] = ad1988_3stack_mixers1; + spec->mixers[1] = ad1988_3stack_mixers2; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1988_3stack_init_verbs; + if (board_config == AD1988_3STACK_DIG) + spec->multiout.dig_out_nid = AD1988_SPDIF_OUT; + break; + case AD1988_LAPTOP: + case AD1988_LAPTOP_DIG: + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = ad1988_3stack_dac_nids; + spec->input_mux = &ad1988_laptop_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1988_laptop_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1988_laptop_init_verbs; + if (board_config == AD1988_LAPTOP_DIG) + spec->multiout.dig_out_nid = AD1988_SPDIF_OUT; + break; + } + + spec->num_adc_nids = ARRAY_SIZE(ad1988_adc_nids); + spec->adc_nids = ad1988_adc_nids; + spec->capsrc_nids = ad1988_capsrc_nids; + spec->mixers[spec->num_mixers++] = ad1988_capture_mixers; + spec->init_verbs[spec->num_init_verbs++] = ad1988_capture_init_verbs; + if (spec->multiout.dig_out_nid) { + if (codec->vendor_id >= 0x11d4989a) { + spec->mixers[spec->num_mixers++] = + ad1989_spdif_out_mixers; + spec->init_verbs[spec->num_init_verbs++] = + ad1989_spdif_init_verbs; + codec->slave_dig_outs = ad1989b_slave_dig_outs; + } else { + spec->mixers[spec->num_mixers++] = + ad1988_spdif_out_mixers; + spec->init_verbs[spec->num_init_verbs++] = + ad1988_spdif_init_verbs; + } + } + if (spec->dig_in_nid && codec->vendor_id < 0x11d4989a) + spec->mixers[spec->num_mixers++] = ad1988_spdif_in_mixers; + + codec->patch_ops = ad198x_patch_ops; + switch (board_config) { + case AD1988_AUTO: + codec->patch_ops.init = ad1988_auto_init; + break; + case AD1988_LAPTOP: + case AD1988_LAPTOP_DIG: + codec->patch_ops.unsol_event = ad1988_laptop_unsol_event; + break; + } +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1988_loopbacks; +#endif + spec->vmaster_nid = 0x04; + + return 0; +} + + +/* + * AD1884 / AD1984 + * + * port-B - front line/mic-in + * port-E - aux in/out + * port-F - aux in/out + * port-C - rear line/mic-in + * port-D - rear line/hp-out + * port-A - front line/hp-out + * + * AD1984 = AD1884 + two digital mic-ins + * + * FIXME: + * For simplicity, we share the single DAC for both HP and line-outs + * right now. The inidividual playbacks could be easily implemented, + * but no build-up framework is given, so far. + */ + +static hda_nid_t ad1884_dac_nids[1] = { + 0x04, +}; + +static hda_nid_t ad1884_adc_nids[2] = { + 0x08, 0x09, +}; + +static hda_nid_t ad1884_capsrc_nids[2] = { + 0x0c, 0x0d, +}; + +#define AD1884_SPDIF_OUT 0x02 + +static struct hda_input_mux ad1884_capture_source = { + .num_items = 4, + .items = { + { "Front Mic", 0x0 }, + { "Mic", 0x1 }, + { "CD", 0x2 }, + { "Mix", 0x3 }, + }, +}; + +static struct snd_kcontrol_new ad1884_base_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + /* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */ + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x02, HDA_INPUT), + /* + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Digital Beep Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Digital Beep Playback Switch", 0x10, 0x0, HDA_OUTPUT), + */ + HDA_CODEC_VOLUME("Mic Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1984_dmic_mixers[] = { + HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x05, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Digital Mic Capture Switch", 0x05, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Digital Mic Capture Volume", 1, 0x06, 0x0, + HDA_INPUT), + HDA_CODEC_MUTE_IDX("Digital Mic Capture Switch", 1, 0x06, 0x0, + HDA_INPUT), + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1884_init_verbs[] = { + /* DACs; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-A (HP) mixer */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-A pin */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* HP selector - select DAC2 */ + {0x22, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-D (Line-out) mixer */ + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-D pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono-out mixer */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Mono-out pin */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono selector */ + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-B (front mic) pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-C (rear mic) pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer; mute as default */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + /* SPDIF output selector */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1884_loopbacks[] = { + { 0x20, HDA_INPUT, 0 }, /* Front Mic */ + { 0x20, HDA_INPUT, 1 }, /* Mic */ + { 0x20, HDA_INPUT, 2 }, /* CD */ + { 0x20, HDA_INPUT, 4 }, /* Docking */ + { } /* end */ +}; +#endif + +static const char *ad1884_slave_vols[] = { + "PCM Playback Volume", + "Mic Playback Volume", + "Mono Playback Volume", + "Front Mic Playback Volume", + "Mic Playback Volume", + "CD Playback Volume", + "Internal Mic Playback Volume", + "Docking Mic Playback Volume" + "Beep Playback Volume", + "IEC958 Playback Volume", + NULL +}; + +static int patch_ad1884(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1884_dac_nids); + spec->multiout.dac_nids = ad1884_dac_nids; + spec->multiout.dig_out_nid = AD1884_SPDIF_OUT; + spec->num_adc_nids = ARRAY_SIZE(ad1884_adc_nids); + spec->adc_nids = ad1884_adc_nids; + spec->capsrc_nids = ad1884_capsrc_nids; + spec->input_mux = &ad1884_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1884_base_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1884_init_verbs; + spec->spdif_route = 0; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1884_loopbacks; +#endif + spec->vmaster_nid = 0x04; + /* we need to cover all playback volumes */ + spec->slave_vols = ad1884_slave_vols; + + codec->patch_ops = ad198x_patch_ops; + + return 0; +} + +/* + * Lenovo Thinkpad T61/X61 + */ +static struct hda_input_mux ad1984_thinkpad_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x1 }, + { "Mix", 0x3 }, + { "Docking-Station", 0x4 }, + }, +}; + + +/* + * Dell Precision T3400 + */ +static struct hda_input_mux ad1984_dell_desktop_capture_source = { + .num_items = 3, + .items = { + { "Front Mic", 0x0 }, + { "Line-In", 0x1 }, + { "Mix", 0x3 }, + }, +}; + + +static struct snd_kcontrol_new ad1984_thinkpad_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + /* HDA_CODEC_VOLUME_IDX("PCM Playback Volume", 1, 0x03, 0x0, HDA_OUTPUT), */ + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Docking Mic Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Docking Mic Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Docking Mic Boost", 0x25, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +/* additional verbs */ +static struct hda_verb ad1984_thinkpad_init_verbs[] = { + /* Port-E (docking station mic) pin */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* docking mic boost */ + {0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer - docking mic; mute as default */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* enable EAPD bit */ + {0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + { } /* end */ +}; + +/* + * Dell Precision T3400 + */ +static struct snd_kcontrol_new ad1984_dell_desktop_mixers[] = { + HDA_CODEC_VOLUME("PCM Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Line-In Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line-In Playback Switch", 0x20, 0x01, HDA_INPUT), + /* + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x20, 0x03, HDA_INPUT), + */ + HDA_CODEC_VOLUME("Line-In Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +/* Digial MIC ADC NID 0x05 + 0x06 */ +static int ad1984_pcm_dmic_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_setup_stream(codec, 0x05 + substream->number, + stream_tag, 0, format); + return 0; +} + +static int ad1984_pcm_dmic_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + snd_hda_codec_cleanup_stream(codec, 0x05 + substream->number); + return 0; +} + +static struct hda_pcm_stream ad1984_pcm_dmic_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x05, + .ops = { + .prepare = ad1984_pcm_dmic_prepare, + .cleanup = ad1984_pcm_dmic_cleanup + }, +}; + +static int ad1984_build_pcms(struct hda_codec *codec) +{ + struct ad198x_spec *spec = codec->spec; + struct hda_pcm *info; + int err; + + err = ad198x_build_pcms(codec); + if (err < 0) + return err; + + info = spec->pcm_rec + codec->num_pcms; + codec->num_pcms++; + info->name = "AD1984 Digital Mic"; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = ad1984_pcm_dmic_capture; + return 0; +} + +/* models */ +enum { + AD1984_BASIC, + AD1984_THINKPAD, + AD1984_DELL_DESKTOP, + AD1984_MODELS +}; + +static const char *ad1984_models[AD1984_MODELS] = { + [AD1984_BASIC] = "basic", + [AD1984_THINKPAD] = "thinkpad", + [AD1984_DELL_DESKTOP] = "dell_desktop", +}; + +static struct snd_pci_quirk ad1984_cfg_tbl[] = { + /* Lenovo Thinkpad T61/X61 */ + SND_PCI_QUIRK(0x17aa, 0, "Lenovo Thinkpad", AD1984_THINKPAD), + SND_PCI_QUIRK(0x1028, 0x0214, "Dell T3400", AD1984_DELL_DESKTOP), + {} +}; + +static int patch_ad1984(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config, err; + + err = patch_ad1884(codec); + if (err < 0) + return err; + spec = codec->spec; + board_config = snd_hda_check_board_config(codec, AD1984_MODELS, + ad1984_models, ad1984_cfg_tbl); + switch (board_config) { + case AD1984_BASIC: + /* additional digital mics */ + spec->mixers[spec->num_mixers++] = ad1984_dmic_mixers; + codec->patch_ops.build_pcms = ad1984_build_pcms; + break; + case AD1984_THINKPAD: + spec->multiout.dig_out_nid = AD1884_SPDIF_OUT; + spec->input_mux = &ad1984_thinkpad_capture_source; + spec->mixers[0] = ad1984_thinkpad_mixers; + spec->init_verbs[spec->num_init_verbs++] = ad1984_thinkpad_init_verbs; + break; + case AD1984_DELL_DESKTOP: + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1984_dell_desktop_capture_source; + spec->mixers[0] = ad1984_dell_desktop_mixers; + break; + } + return 0; +} + + +/* + * AD1883 / AD1884A / AD1984A / AD1984B + * + * port-B (0x14) - front mic-in + * port-E (0x1c) - rear mic-in + * port-F (0x16) - CD / ext out + * port-C (0x15) - rear line-in + * port-D (0x12) - rear line-out + * port-A (0x11) - front hp-out + * + * AD1984A = AD1884A + digital-mic + * AD1883 = equivalent with AD1984A + * AD1984B = AD1984A + extra SPDIF-out + * + * FIXME: + * We share the single DAC for both HP and line-outs (see AD1884/1984). + */ + +static hda_nid_t ad1884a_dac_nids[1] = { + 0x03, +}; + +#define ad1884a_adc_nids ad1884_adc_nids +#define ad1884a_capsrc_nids ad1884_capsrc_nids + +#define AD1884A_SPDIF_OUT 0x02 + +static struct hda_input_mux ad1884a_capture_source = { + .num_items = 5, + .items = { + { "Front Mic", 0x0 }, + { "Mic", 0x4 }, + { "Line", 0x1 }, + { "CD", 0x2 }, + { "Mix", 0x3 }, + }, +}; + +static struct snd_kcontrol_new ad1884a_base_mixers[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x25, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1884a_init_verbs[] = { + /* DACs; unmute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, 0x27}, /* 0dB */ + /* Port-A (HP) mixer - route only from analog mixer */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-A pin */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-D (Line-out) mixer - route only from analog mixer */ + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-D pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono-out mixer - route only from analog mixer */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Mono-out pin */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-B (front mic) pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-C (rear line-in) pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-E (rear mic) pin */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x25, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* no boost */ + /* Port-F (CD) pin */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer; mute as default */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, /* aux */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* capture sources */ + {0x0c, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* SPDIF output amp */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1884a_loopbacks[] = { + { 0x20, HDA_INPUT, 0 }, /* Front Mic */ + { 0x20, HDA_INPUT, 1 }, /* Mic */ + { 0x20, HDA_INPUT, 2 }, /* CD */ + { 0x20, HDA_INPUT, 4 }, /* Docking */ + { } /* end */ +}; +#endif + +/* + * Laptop model + * + * Port A: Headphone jack + * Port B: MIC jack + * Port C: Internal MIC + * Port D: Dock Line Out (if enabled) + * Port E: Dock Line In (if enabled) + * Port F: Internal speakers + */ + +static struct hda_input_mux ad1884a_laptop_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, /* port-B */ + { "Internal Mic", 0x1 }, /* port-C */ + { "Dock Mic", 0x4 }, /* port-E */ + { "Mix", 0x3 }, + }, +}; + +static struct snd_kcontrol_new ad1884a_laptop_mixers[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Dock Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Dock Mic Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Dock Mic Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Dock Mic Boost", 0x25, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1884a_mobile_mixers[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Capture Volume", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Capture Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* mute internal speaker if HP is plugged */ +static void ad1884a_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x11, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_EAPD_BTLENABLE, + present ? 0x00 : 0x02); +} + +/* switch to external mic if plugged */ +static void ad1884a_hp_automic(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x0c, 0, AC_VERB_SET_CONNECT_SEL, + present ? 0 : 1); +} + +#define AD1884A_HP_EVENT 0x37 +#define AD1884A_MIC_EVENT 0x36 + +/* unsolicited event for HP jack sensing */ +static void ad1884a_hp_unsol_event(struct hda_codec *codec, unsigned int res) +{ + switch (res >> 26) { + case AD1884A_HP_EVENT: + ad1884a_hp_automute(codec); + break; + case AD1884A_MIC_EVENT: + ad1884a_hp_automic(codec); + break; + } +} + +/* initialize jack-sensing, too */ +static int ad1884a_hp_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1884a_hp_automute(codec); + ad1884a_hp_automic(codec); + return 0; +} + +/* additional verbs for laptop model */ +static struct hda_verb ad1884a_laptop_verbs[] = { + /* Port-A (HP) pin - always unmuted */ + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Port-F (int speaker) mixer - route only from analog mixer */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-F pin */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-C pin - internal mic-in */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x7002}, /* raise mic as default */ + /* analog mix */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* unsolicited event for pin-sense */ + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_MIC_EVENT}, + { } /* end */ +}; + +/* + * Thinkpad X300 + * 0x11 - HP + * 0x12 - speaker + * 0x14 - mic-in + * 0x17 - built-in mic + */ + +static struct hda_verb ad1984a_thinkpad_verbs[] = { + /* HP unmute */ + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* analog mix */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* turn on EAPD */ + {0x12, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + /* unsolicited event for pin-sense */ + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | AD1884A_HP_EVENT}, + /* internal mic - dmic */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* set magic COEFs for dmic */ + {0x01, AC_VERB_SET_COEF_INDEX, 0x13f7}, + {0x01, AC_VERB_SET_PROC_COEF, 0x08}, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1984a_thinkpad_mixers[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x20, 0x5, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_input_mux ad1984a_thinkpad_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x5 }, + { "Mix", 0x3 }, + }, +}; + +/* mute internal speaker if HP is plugged */ +static void ad1984a_thinkpad_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x11, 0, AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + snd_hda_codec_amp_stereo(codec, 0x12, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +/* unsolicited event for HP jack sensing */ +static void ad1984a_thinkpad_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != AD1884A_HP_EVENT) + return; + ad1984a_thinkpad_automute(codec); +} + +/* initialize jack-sensing, too */ +static int ad1984a_thinkpad_init(struct hda_codec *codec) +{ + ad198x_init(codec); + ad1984a_thinkpad_automute(codec); + return 0; +} + +/* + */ + +enum { + AD1884A_DESKTOP, + AD1884A_LAPTOP, + AD1884A_MOBILE, + AD1884A_THINKPAD, + AD1884A_MODELS +}; + +static const char *ad1884a_models[AD1884A_MODELS] = { + [AD1884A_DESKTOP] = "desktop", + [AD1884A_LAPTOP] = "laptop", + [AD1884A_MOBILE] = "mobile", + [AD1884A_THINKPAD] = "thinkpad", +}; + +static struct snd_pci_quirk ad1884a_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x3030, "HP", AD1884A_MOBILE), + SND_PCI_QUIRK(0x103c, 0x3056, "HP", AD1884A_MOBILE), + SND_PCI_QUIRK(0x103c, 0x30e6, "HP 6730b", AD1884A_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30e7, "HP EliteBook 8530p", AD1884A_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x3614, "HP 6730s", AD1884A_LAPTOP), + SND_PCI_QUIRK(0x17aa, 0x20ac, "Thinkpad X300", AD1884A_THINKPAD), + {} +}; + +static int patch_ad1884a(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(ad1884a_dac_nids); + spec->multiout.dac_nids = ad1884a_dac_nids; + spec->multiout.dig_out_nid = AD1884A_SPDIF_OUT; + spec->num_adc_nids = ARRAY_SIZE(ad1884a_adc_nids); + spec->adc_nids = ad1884a_adc_nids; + spec->capsrc_nids = ad1884a_capsrc_nids; + spec->input_mux = &ad1884a_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = ad1884a_base_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1884a_init_verbs; + spec->spdif_route = 0; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1884a_loopbacks; +#endif + codec->patch_ops = ad198x_patch_ops; + + /* override some parameters */ + board_config = snd_hda_check_board_config(codec, AD1884A_MODELS, + ad1884a_models, + ad1884a_cfg_tbl); + switch (board_config) { + case AD1884A_LAPTOP: + spec->mixers[0] = ad1884a_laptop_mixers; + spec->init_verbs[spec->num_init_verbs++] = ad1884a_laptop_verbs; + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1884a_laptop_capture_source; + codec->patch_ops.unsol_event = ad1884a_hp_unsol_event; + codec->patch_ops.init = ad1884a_hp_init; + break; + case AD1884A_MOBILE: + spec->mixers[0] = ad1884a_mobile_mixers; + spec->init_verbs[spec->num_init_verbs++] = ad1884a_laptop_verbs; + spec->multiout.dig_out_nid = 0; + codec->patch_ops.unsol_event = ad1884a_hp_unsol_event; + codec->patch_ops.init = ad1884a_hp_init; + break; + case AD1884A_THINKPAD: + spec->mixers[0] = ad1984a_thinkpad_mixers; + spec->init_verbs[spec->num_init_verbs++] = + ad1984a_thinkpad_verbs; + spec->multiout.dig_out_nid = 0; + spec->input_mux = &ad1984a_thinkpad_capture_source; + codec->patch_ops.unsol_event = ad1984a_thinkpad_unsol_event; + codec->patch_ops.init = ad1984a_thinkpad_init; + break; + } + + return 0; +} + + +/* + * AD1882 / AD1882A + * + * port-A - front hp-out + * port-B - front mic-in + * port-C - rear line-in, shared surr-out (3stack) + * port-D - rear line-out + * port-E - rear mic-in, shared clfe-out (3stack) + * port-F - rear surr-out (6stack) + * port-G - rear clfe-out (6stack) + */ + +static hda_nid_t ad1882_dac_nids[3] = { + 0x04, 0x03, 0x05 +}; + +static hda_nid_t ad1882_adc_nids[2] = { + 0x08, 0x09, +}; + +static hda_nid_t ad1882_capsrc_nids[2] = { + 0x0c, 0x0d, +}; + +#define AD1882_SPDIF_OUT 0x02 + +/* list: 0x11, 0x39, 0x3a, 0x18, 0x3c, 0x3b, 0x12, 0x20 */ +static struct hda_input_mux ad1882_capture_source = { + .num_items = 5, + .items = { + { "Front Mic", 0x1 }, + { "Mic", 0x4 }, + { "Line", 0x2 }, + { "CD", 0x3 }, + { "Mix", 0x7 }, + }, +}; + +/* list: 0x11, 0x39, 0x3a, 0x3c, 0x18, 0x1f, 0x12, 0x20 */ +static struct hda_input_mux ad1882a_capture_source = { + .num_items = 5, + .items = { + { "Front Mic", 0x1 }, + { "Mic", 0x4}, + { "Line", 0x2 }, + { "Digital Mic", 0x06 }, + { "Mix", 0x7 }, + }, +}; + +static struct snd_kcontrol_new ad1882_base_mixers[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x04, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x05, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x05, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x13, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x13, 1, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Boost", 0x3c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x39, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line-In Boost", 0x3a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x0d, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = ad198x_mux_enum_info, + .get = ad198x_mux_enum_get, + .put = ad198x_mux_enum_put, + }, + /* SPDIF controls */ + HDA_CODEC_VOLUME("IEC958 Playback Volume", 0x1b, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source", + /* identical with ad1983 */ + .info = ad1983_spdif_route_info, + .get = ad1983_spdif_route_get, + .put = ad1983_spdif_route_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882_loopback_mixers[] = { + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x07, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x07, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882a_loopback_mixers[] = { + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x20, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x20, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x20, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x20, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x20, 0x07, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x20, 0x07, HDA_INPUT), + HDA_CODEC_VOLUME("Digital Mic Boost", 0x1f, 0x0, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882_3stack_mixers[] = { + HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x17, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x17, 2, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = ad198x_ch_mode_info, + .get = ad198x_ch_mode_get, + .put = ad198x_ch_mode_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new ad1882_6stack_mixers[] = { + HDA_CODEC_MUTE("Surround Playback Switch", 0x16, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x24, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x24, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct hda_verb ad1882_ch2_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } /* end */ +}; + +static struct hda_verb ad1882_ch4_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } /* end */ +}; + +static struct hda_verb ad1882_ch6_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + { } /* end */ +}; + +static struct hda_channel_mode ad1882_modes[3] = { + { 2, ad1882_ch2_init }, + { 4, ad1882_ch4_init }, + { 6, ad1882_ch6_init }, +}; + +/* + * initialization verbs + */ +static struct hda_verb ad1882_init_verbs[] = { + /* DACs; mute as default */ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Port-A (HP) mixer */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-A pin */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* HP selector - select DAC2 */ + {0x37, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* Port-D (Line-out) mixer */ + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Port-D pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mono-out mixer */ + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Mono-out pin */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-B (front mic) pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x39, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-C (line-in) pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x3a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-C mixer - mute as input */ + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x2c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Port-E (mic-in) pin */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x3c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, /* boost */ + /* Port-E mixer - mute as input */ + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Port-F (surround) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Port-G (CLFE) */ + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Analog mixer; mute as default */ + /* list: 0x39, 0x3a, 0x11, 0x12, 0x3c, 0x3b, 0x18, 0x1a */ + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* Analog Mix output amp */ + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x1f}, /* 0dB */ + /* SPDIF output selector */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */ + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */ + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list ad1882_loopbacks[] = { + { 0x20, HDA_INPUT, 0 }, /* Front Mic */ + { 0x20, HDA_INPUT, 1 }, /* Mic */ + { 0x20, HDA_INPUT, 4 }, /* Line */ + { 0x20, HDA_INPUT, 6 }, /* CD */ + { } /* end */ +}; +#endif + +/* models */ +enum { + AD1882_3STACK, + AD1882_6STACK, + AD1882_MODELS +}; + +static const char *ad1882_models[AD1986A_MODELS] = { + [AD1882_3STACK] = "3stack", + [AD1882_6STACK] = "6stack", +}; + + +static int patch_ad1882(struct hda_codec *codec) +{ + struct ad198x_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.max_channels = 6; + spec->multiout.num_dacs = 3; + spec->multiout.dac_nids = ad1882_dac_nids; + spec->multiout.dig_out_nid = AD1882_SPDIF_OUT; + spec->num_adc_nids = ARRAY_SIZE(ad1882_adc_nids); + spec->adc_nids = ad1882_adc_nids; + spec->capsrc_nids = ad1882_capsrc_nids; + if (codec->vendor_id == 0x11d41882) + spec->input_mux = &ad1882_capture_source; + else + spec->input_mux = &ad1882a_capture_source; + spec->num_mixers = 2; + spec->mixers[0] = ad1882_base_mixers; + if (codec->vendor_id == 0x11d41882) + spec->mixers[1] = ad1882_loopback_mixers; + else + spec->mixers[1] = ad1882a_loopback_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = ad1882_init_verbs; + spec->spdif_route = 0; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = ad1882_loopbacks; +#endif + spec->vmaster_nid = 0x04; + + codec->patch_ops = ad198x_patch_ops; + + /* override some parameters */ + board_config = snd_hda_check_board_config(codec, AD1882_MODELS, + ad1882_models, NULL); + switch (board_config) { + default: + case AD1882_3STACK: + spec->num_mixers = 3; + spec->mixers[2] = ad1882_3stack_mixers; + spec->channel_mode = ad1882_modes; + spec->num_channel_mode = ARRAY_SIZE(ad1882_modes); + spec->need_dac_fix = 1; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + break; + case AD1882_6STACK: + spec->num_mixers = 3; + spec->mixers[2] = ad1882_6stack_mixers; + break; + } + return 0; +} + + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_analog[] = { + { .id = 0x11d4184a, .name = "AD1884A", .patch = patch_ad1884a }, + { .id = 0x11d41882, .name = "AD1882", .patch = patch_ad1882 }, + { .id = 0x11d41883, .name = "AD1883", .patch = patch_ad1884a }, + { .id = 0x11d41884, .name = "AD1884", .patch = patch_ad1884 }, + { .id = 0x11d4194a, .name = "AD1984A", .patch = patch_ad1884a }, + { .id = 0x11d4194b, .name = "AD1984B", .patch = patch_ad1884a }, + { .id = 0x11d41981, .name = "AD1981", .patch = patch_ad1981 }, + { .id = 0x11d41983, .name = "AD1983", .patch = patch_ad1983 }, + { .id = 0x11d41984, .name = "AD1984", .patch = patch_ad1984 }, + { .id = 0x11d41986, .name = "AD1986A", .patch = patch_ad1986a }, + { .id = 0x11d41988, .name = "AD1988", .patch = patch_ad1988 }, + { .id = 0x11d4198b, .name = "AD1988B", .patch = patch_ad1988 }, + { .id = 0x11d4882a, .name = "AD1882A", .patch = patch_ad1882 }, + { .id = 0x11d4989a, .name = "AD1989A", .patch = patch_ad1988 }, + { .id = 0x11d4989b, .name = "AD1989B", .patch = patch_ad1988 }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_atihdmi.c b/sound/pci/hda/patch_atihdmi.c new file mode 100644 index 0000000..ba61575 --- /dev/null +++ b/sound/pci/hda/patch_atihdmi.c @@ -0,0 +1,199 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for ATI HDMI codecs + * + * Copyright (c) 2006 ATI Technologies Inc. + * + * + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_patch.h" + +struct atihdmi_spec { + struct hda_multi_out multiout; + + struct hda_pcm pcm_rec; +}; + +#define CVT_NID 0x02 /* audio converter */ +#define PIN_NID 0x03 /* HDMI output pin */ + +static struct hda_verb atihdmi_basic_init[] = { + /* enable digital output on pin widget */ + { 0x03, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {} /* terminator */ +}; + +/* + * Controls + */ +static int atihdmi_build_controls(struct hda_codec *codec) +{ + struct atihdmi_spec *spec = codec->spec; + int err; + + err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); + if (err < 0) + return err; + + return 0; +} + +static int atihdmi_init(struct hda_codec *codec) +{ + snd_hda_sequence_write(codec, atihdmi_basic_init); + /* SI codec requires to unmute the pin */ + if (get_wcaps(codec, PIN_NID) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, PIN_NID, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); + return 0; +} + +/* + * Digital out + */ +static int atihdmi_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct atihdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int atihdmi_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct atihdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int atihdmi_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct atihdmi_spec *spec = codec->spec; + int chans = substream->runtime->channels; + int i, err; + + err = snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); + if (err < 0) + return err; + snd_hda_codec_write(codec, CVT_NID, 0, AC_VERB_SET_CVT_CHAN_COUNT, + chans - 1); + /* FIXME: XXX */ + for (i = 0; i < chans; i++) { + snd_hda_codec_write(codec, CVT_NID, 0, + AC_VERB_SET_HDMI_CHAN_SLOT, + (i << 4) | i); + } + return 0; +} + +static struct hda_pcm_stream atihdmi_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = CVT_NID, /* NID to query formats and rates and setup streams */ + .ops = { + .open = atihdmi_dig_playback_pcm_open, + .close = atihdmi_dig_playback_pcm_close, + .prepare = atihdmi_dig_playback_pcm_prepare + }, +}; + +static int atihdmi_build_pcms(struct hda_codec *codec) +{ + struct atihdmi_spec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm_rec; + unsigned int chans; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "ATI HDMI"; + info->pcm_type = HDA_PCM_TYPE_HDMI; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = atihdmi_pcm_digital_playback; + + /* FIXME: we must check ELD and change the PCM parameters dynamically + */ + chans = get_wcaps(codec, CVT_NID); + chans = (chans & AC_WCAP_CHAN_CNT_EXT) >> 13; + chans = ((chans << 1) | 1) + 1; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = chans; + + return 0; +} + +static void atihdmi_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + +static struct hda_codec_ops atihdmi_patch_ops = { + .build_controls = atihdmi_build_controls, + .build_pcms = atihdmi_build_pcms, + .init = atihdmi_init, + .free = atihdmi_free, +}; + +static int patch_atihdmi(struct hda_codec *codec) +{ + struct atihdmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 2; + /* NID for copying analog to digital, + * seems to be unused in pure-digital + * case. + */ + spec->multiout.dig_out_nid = CVT_NID; + + codec->patch_ops = atihdmi_patch_ops; + + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_atihdmi[] = { + { .id = 0x1002793c, .name = "ATI RS600 HDMI", .patch = patch_atihdmi }, + { .id = 0x10027919, .name = "ATI RS600 HDMI", .patch = patch_atihdmi }, + { .id = 0x1002791a, .name = "ATI RS690/780 HDMI", .patch = patch_atihdmi }, + { .id = 0x1002aa01, .name = "ATI R6xx HDMI", .patch = patch_atihdmi }, + { .id = 0x10951390, .name = "SiI1390 HDMI", .patch = patch_atihdmi }, + { .id = 0x10951392, .name = "SiI1392 HDMI", .patch = patch_atihdmi }, + { .id = 0x17e80047, .name = "Chrontel HDMI", .patch = patch_atihdmi }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c new file mode 100644 index 0000000..6ef57fb --- /dev/null +++ b/sound/pci/hda/patch_cmedia.c @@ -0,0 +1,743 @@ +/* + * 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 <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" +#include "hda_patch.h" +#define NUM_PINS 11 + + +/* 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 */ + CMI_AUTO, /* let driver guess it */ + CMI_MODELS +}; + +struct cmi_spec { + int board_config; + 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; + hda_nid_t dac_nids[AUTO_CFG_MAX_OUTS]; /* NID for each DAC */ + int num_dacs; + + /* 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 */ + int num_channel_modes; + const struct hda_channel_mode *channel_modes; + + struct hda_pcm pcm_rec[2]; /* PCM information */ + + /* pin deafault configuration */ + hda_nid_t pin_nid[NUM_PINS]; + unsigned int def_conf[NUM_PINS]; + unsigned int pin_def_confs; + + /* multichannel pins */ + struct hda_verb multi_init[9]; /* 2 verbs for each pin + terminator */ +}; + +/* + * input MUX + */ +static int cmi_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *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, PIN_IN }, + /* set mic PIN for input, also enable vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* 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, PIN_OUT }, + /* set mic PIN for output */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + /* 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, PIN_OUT }, + /* set mic PIN for output */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + /* route rear-surround PCM (DAC4) to HP */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x03 }, + {} +}; + +static struct hda_channel_mode cmi9880_channel_modes[3] = { + { 2, cmi9880_ch2_init }, + { 6, cmi9880_ch6_init }, + { 8, cmi9880_ch8_init }, +}; + +static int cmi_ch_mode_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cmi_spec *spec = codec->spec; + return snd_hda_ch_mode_info(codec, uinfo, spec->channel_modes, + spec->num_channel_modes); +} + +static int cmi_ch_mode_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cmi_spec *spec = codec->spec; + return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_modes, + spec->num_channel_modes, spec->multiout.max_channels); +} + +static int cmi_ch_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct cmi_spec *spec = codec->spec; + return snd_hda_ch_mode_put(codec, ucontrol, spec->channel_modes, + spec->num_channel_modes, &spec->multiout.max_channels); +} + +/* + */ +static struct snd_kcontrol_new 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.. + */ + /* .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 struct snd_kcontrol_new 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, PIN_HP }, + /* port-E for HP out (front panel) */ + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + /* 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, PIN_HP }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 }, + /* port-C for line-in (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* port-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + /* 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, PIN_HP }, + /* port-E for HP out (front panel) */ + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + /* 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, PIN_HP }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x1f, AC_VERB_SET_CONNECT_SEL, 0x02 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x01 }, + /* port-C for surround (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* port-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + /* 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->channel_modes) { + 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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + if (spec->dig_in_nid) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + return 0; +} + +/* fill in the multi_dac_nids table, which will decide + which audio widget to use for each channel */ +static int cmi9880_fill_multi_dac_nids(struct hda_codec *codec, const struct auto_pin_cfg *cfg) +{ + struct cmi_spec *spec = codec->spec; + hda_nid_t nid; + int assigned[4]; + int i, j; + + /* clear the table, only one c-media dac assumed here */ + memset(spec->dac_nids, 0, sizeof(spec->dac_nids)); + memset(assigned, 0, sizeof(assigned)); + /* check the pins we found */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + /* nid 0x0b~0x0e is hardwired to audio widget 0x3~0x6 */ + if (nid >= 0x0b && nid <= 0x0e) { + spec->dac_nids[i] = (nid - 0x0b) + 0x03; + assigned[nid - 0x0b] = 1; + } + } + /* left pin can be connect to any audio widget */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (nid <= 0x0e) + continue; + /* search for an empty channel */ + for (j = 0; j < cfg->line_outs; j++) { + if (! assigned[j]) { + spec->dac_nids[i] = j + 0x03; + assigned[j] = 1; + break; + } + } + } + spec->num_dacs = cfg->line_outs; + return 0; +} + +/* create multi_init table, which is used for multichannel initialization */ +static int cmi9880_fill_multi_init(struct hda_codec *codec, const struct auto_pin_cfg *cfg) +{ + struct cmi_spec *spec = codec->spec; + hda_nid_t nid; + int i, j, k, len; + + /* clear the table, only one c-media dac assumed here */ + memset(spec->multi_init, 0, sizeof(spec->multi_init)); + for (j = 0, i = 0; i < cfg->line_outs; i++) { + hda_nid_t conn[4]; + nid = cfg->line_out_pins[i]; + /* set as output */ + spec->multi_init[j].nid = nid; + spec->multi_init[j].verb = AC_VERB_SET_PIN_WIDGET_CONTROL; + spec->multi_init[j].param = PIN_OUT; + j++; + if (nid > 0x0e) { + /* set connection */ + spec->multi_init[j].nid = nid; + spec->multi_init[j].verb = AC_VERB_SET_CONNECT_SEL; + spec->multi_init[j].param = 0; + /* find the index in connect list */ + len = snd_hda_get_connections(codec, nid, conn, 4); + for (k = 0; k < len; k++) + if (conn[k] == spec->dac_nids[i]) { + spec->multi_init[j].param = k; + break; + } + j++; + } + } + 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); + if (spec->board_config == CMI_AUTO) + snd_hda_sequence_write(codec, spec->multi_init); + return 0; +} + +/* + * Analog playback callbacks + */ +static int cmi9880_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct cmi_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int cmi9880_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *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, + struct snd_pcm_substream *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, + struct snd_pcm_substream *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, + struct snd_pcm_substream *substream) +{ + struct cmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int cmi9880_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct cmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +/* + * Analog capture + */ +static int cmi9880_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *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, + struct snd_pcm_substream *substream) +{ + struct cmi_spec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]); + 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, + .prepare = cmi9880_dig_playback_pcm_prepare + }, +}; + +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"; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + 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 const char *cmi9880_models[CMI_MODELS] = { + [CMI_MINIMAL] = "minimal", + [CMI_MIN_FP] = "min_fp", + [CMI_FULL] = "full", + [CMI_FULL_DIG] = "full_dig", + [CMI_ALLOUT] = "allout", + [CMI_AUTO] = "auto", +}; + +static struct snd_pci_quirk cmi9880_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x813d, "ASUS P5AD2", CMI_FULL_DIG), + SND_PCI_QUIRK(0x1854, 0x002b, "LG LS75", CMI_MINIMAL), + SND_PCI_QUIRK(0x1854, 0x0032, "LG", CMI_FULL_DIG), + {} /* 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, +}; + +static int patch_cmi9880(struct hda_codec *codec) +{ + struct cmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->board_config = snd_hda_check_board_config(codec, CMI_MODELS, + cmi9880_models, + cmi9880_cfg_tbl); + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for CMI9880\n"); + spec->board_config = CMI_AUTO; /* try everything */ + } + + /* copy default DAC NIDs */ + memcpy(spec->dac_nids, cmi9880_dac_nids, sizeof(spec->dac_nids)); + spec->num_dacs = 4; + + switch (spec->board_config) { + case CMI_MINIMAL: + case CMI_MIN_FP: + spec->channel_modes = cmi9880_channel_modes; + if (spec->board_config == CMI_MINIMAL) + spec->num_channel_modes = 2; + else { + spec->front_panel = 1; + spec->num_channel_modes = 3; + } + 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; + case CMI_AUTO: + { + unsigned int port_e, port_f, port_g, port_h; + unsigned int port_spdifi, port_spdifo; + struct auto_pin_cfg cfg; + + /* collect pin default configuration */ + port_e = snd_hda_codec_read(codec, 0x0f, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_f = snd_hda_codec_read(codec, 0x10, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + spec->front_panel = 1; + if (get_defcfg_connect(port_e) == AC_JACK_PORT_NONE || + get_defcfg_connect(port_f) == AC_JACK_PORT_NONE) { + port_g = snd_hda_codec_read(codec, 0x1f, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_h = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + spec->channel_modes = cmi9880_channel_modes; + /* no front panel */ + if (get_defcfg_connect(port_g) == AC_JACK_PORT_NONE || + get_defcfg_connect(port_h) == AC_JACK_PORT_NONE) { + /* no optional rear panel */ + spec->board_config = CMI_MINIMAL; + spec->front_panel = 0; + spec->num_channel_modes = 2; + } else { + spec->board_config = CMI_MIN_FP; + spec->num_channel_modes = 3; + } + spec->input_mux = &cmi9880_basic_mux; + spec->multiout.max_channels = cmi9880_channel_modes[0].channels; + } else { + spec->input_mux = &cmi9880_basic_mux; + port_spdifi = snd_hda_codec_read(codec, 0x13, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + port_spdifo = snd_hda_codec_read(codec, 0x12, 0, AC_VERB_GET_CONFIG_DEFAULT, 0); + if (get_defcfg_connect(port_spdifo) != AC_JACK_PORT_NONE) + spec->multiout.dig_out_nid = CMI_DIG_OUT_NID; + if (get_defcfg_connect(port_spdifi) != AC_JACK_PORT_NONE) + spec->dig_in_nid = CMI_DIG_IN_NID; + spec->multiout.max_channels = 8; + } + snd_hda_parse_pin_def_config(codec, &cfg, NULL); + if (cfg.line_outs) { + spec->multiout.max_channels = cfg.line_outs * 2; + cmi9880_fill_multi_dac_nids(codec, &cfg); + cmi9880_fill_multi_init(codec, &cfg); + } else + snd_printd("patch_cmedia: cannot detect association in defcfg\n"); + break; + } + } + + spec->multiout.num_dacs = spec->num_dacs; + spec->multiout.dac_nids = spec->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_conexant.c b/sound/pci/hda/patch_conexant.c new file mode 100644 index 0000000..a50089f --- /dev/null +++ b/sound/pci/hda/patch_conexant.c @@ -0,0 +1,1794 @@ +/* + * HD audio interface patch for Conexant HDA audio codec + * + * Copyright (c) 2006 Pototskiy Akex <alex.pototskiy@gmail.com> + * Takashi Iwai <tiwai@suse.de> + * Tobin Davis <tdavis@dsl-only.net> + * + * 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 <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" +#include "hda_patch.h" + +#define CXT_PIN_DIR_IN 0x00 +#define CXT_PIN_DIR_OUT 0x01 +#define CXT_PIN_DIR_INOUT 0x02 +#define CXT_PIN_DIR_IN_NOMICBIAS 0x03 +#define CXT_PIN_DIR_INOUT_NOMICBIAS 0x04 + +#define CONEXANT_HP_EVENT 0x37 +#define CONEXANT_MIC_EVENT 0x38 + + + +struct conexant_spec { + + struct snd_kcontrol_new *mixers[5]; + int num_mixers; + + const struct hda_verb *init_verbs[5]; /* initialization verbs + * don't forget NULL + * termination! + */ + unsigned int num_init_verbs; + + /* playback */ + struct hda_multi_out multiout; /* playback set-up + * max_channels, dacs must be set + * dig_out_nid and hp_nid are optional + */ + unsigned int cur_eapd; + unsigned int hp_present; + unsigned int need_dac_fix; + + /* capture */ + unsigned int num_adc_nids; + hda_nid_t *adc_nids; + hda_nid_t dig_in_nid; /* digital-in NID; optional */ + + unsigned int cur_adc_idx; + hda_nid_t cur_adc; + unsigned int cur_adc_stream_tag; + unsigned int cur_adc_format; + + /* capture source */ + const struct hda_input_mux *input_mux; + hda_nid_t *capsrc_nids; + unsigned int cur_mux[3]; + + /* channel model */ + const struct hda_channel_mode *channel_mode; + int num_channel_mode; + + /* PCM information */ + struct hda_pcm pcm_rec[2]; /* used in build_pcms() */ + + unsigned int spdif_route; + + /* dynamic controls, init_verbs and input_mux */ + struct auto_pin_cfg autocfg; + unsigned int num_kctl_alloc, num_kctl_used; + struct snd_kcontrol_new *kctl_alloc; + struct hda_input_mux private_imux; + hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; + +}; + +static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int conexant_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, + format, substream); +} + +static int conexant_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + +/* + * Digital out + */ +static int conexant_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int conexant_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int conexant_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, + format, substream); +} + +/* + * Analog capture + */ +static int conexant_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], + stream_tag, 0, format); + return 0; +} + +static int conexant_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]); + return 0; +} + + + +static struct hda_pcm_stream conexant_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .open = conexant_playback_pcm_open, + .prepare = conexant_playback_pcm_prepare, + .cleanup = conexant_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream conexant_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .prepare = conexant_capture_pcm_prepare, + .cleanup = conexant_capture_pcm_cleanup + }, +}; + + +static struct hda_pcm_stream conexant_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .open = conexant_dig_playback_pcm_open, + .close = conexant_dig_playback_pcm_close, + .prepare = conexant_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream conexant_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ +}; + +static int cx5051_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + spec->cur_adc = spec->adc_nids[spec->cur_adc_idx]; + spec->cur_adc_stream_tag = stream_tag; + spec->cur_adc_format = format; + snd_hda_codec_setup_stream(codec, spec->cur_adc, stream_tag, 0, format); + return 0; +} + +static int cx5051_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct conexant_spec *spec = codec->spec; + snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + spec->cur_adc = 0; + return 0; +} + +static struct hda_pcm_stream cx5051_pcm_analog_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0, /* fill later */ + .ops = { + .prepare = cx5051_capture_pcm_prepare, + .cleanup = cx5051_capture_pcm_cleanup + }, +}; + +static int conexant_build_pcms(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "CONEXANT Analog"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = conexant_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = + spec->multiout.max_channels; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = + spec->multiout.dac_nids[0]; + if (codec->vendor_id == 0x14f15051) + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + cx5051_pcm_analog_capture; + else + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + conexant_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adc_nids; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0]; + + if (spec->multiout.dig_out_nid) { + info++; + codec->num_pcms++; + info->name = "Conexant Digital"; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + conexant_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] = + conexant_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = + spec->dig_in_nid; + } + } + + return 0; +} + +static int conexant_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + + return snd_hda_input_mux_info(spec->input_mux, uinfo); +} + +static int conexant_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_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 conexant_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_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->capsrc_nids[adc_idx], + &spec->cur_mux[adc_idx]); +} + +static int conexant_init(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->num_init_verbs; i++) + snd_hda_sequence_write(codec, spec->init_verbs[i]); + return 0; +} + +static void conexant_free(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int i; + + if (spec->kctl_alloc) { + for (i = 0; i < spec->num_kctl_used; i++) + kfree(spec->kctl_alloc[i].name); + kfree(spec->kctl_alloc); + } + + kfree(codec->spec); +} + +static int conexant_build_controls(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int i; + int err; + + 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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + 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 struct hda_codec_ops conexant_patch_ops = { + .build_controls = conexant_build_controls, + .build_pcms = conexant_build_pcms, + .init = conexant_init, + .free = conexant_free, +}; + +/* + * EAPD control + * the private value = nid | (invert << 8) + */ + +#define cxt_eapd_info snd_ctl_boolean_mono_info + +static int cxt_eapd_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int invert = (kcontrol->private_value >> 8) & 1; + if (invert) + ucontrol->value.integer.value[0] = !spec->cur_eapd; + else + ucontrol->value.integer.value[0] = spec->cur_eapd; + return 0; + +} + +static int cxt_eapd_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int invert = (kcontrol->private_value >> 8) & 1; + hda_nid_t nid = kcontrol->private_value & 0xff; + unsigned int eapd; + + eapd = !!ucontrol->value.integer.value[0]; + if (invert) + eapd = !eapd; + if (eapd == spec->cur_eapd) + return 0; + + spec->cur_eapd = eapd; + snd_hda_codec_write_cache(codec, nid, + 0, AC_VERB_SET_EAPD_BTLENABLE, + eapd ? 0x02 : 0x00); + return 1; +} + +/* controls for test mode */ +#ifdef CONFIG_SND_DEBUG + +#define CXT_EAPD_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = cxt_eapd_info, \ + .get = cxt_eapd_get, \ + .put = cxt_eapd_put, \ + .private_value = nid | (mask<<16) } + + + +static int conexant_ch_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode, + spec->num_channel_mode); +} + +static int conexant_ch_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, + spec->multiout.max_channels); +} + +static int conexant_ch_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, + &spec->multiout.max_channels); + if (err >= 0 && spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; + return err; +} + +#define CXT_PIN_MODE(xname, nid, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = conexant_ch_mode_info, \ + .get = conexant_ch_mode_get, \ + .put = conexant_ch_mode_put, \ + .private_value = nid | (dir<<16) } + +#endif /* CONFIG_SND_DEBUG */ + +/* Conexant 5045 specific */ + +static hda_nid_t cxt5045_dac_nids[1] = { 0x19 }; +static hda_nid_t cxt5045_adc_nids[1] = { 0x1a }; +static hda_nid_t cxt5045_capsrc_nids[1] = { 0x1a }; +#define CXT5045_SPDIF_OUT 0x18 + +static struct hda_channel_mode cxt5045_modes[1] = { + { 2, NULL }, +}; + +static struct hda_input_mux cxt5045_capture_source = { + .num_items = 2, + .items = { + { "IntMic", 0x1 }, + { "ExtMic", 0x2 }, + } +}; + +static struct hda_input_mux cxt5045_capture_source_benq = { + .num_items = 3, + .items = { + { "IntMic", 0x1 }, + { "ExtMic", 0x2 }, + { "LineIn", 0x3 }, + } +}; + +static struct hda_input_mux cxt5045_capture_source_hp530 = { + .num_items = 2, + .items = { + { "ExtMic", 0x1 }, + { "IntMic", 0x2 }, + } +}; + +/* turn on/off EAPD (+ mute HP) as a master switch */ +static int cxt5045_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + if (!cxt_eapd_put(kcontrol, ucontrol)) + return 0; + + /* toggle internal speakers mute depending of presence of + * the headphone jack + */ + bits = (!spec->hp_present && spec->cur_eapd) ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + + bits = spec->cur_eapd ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, 0x11, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + return 1; +} + +/* bind volumes of both NID 0x10 and 0x11 */ +static struct hda_bind_ctls cxt5045_hp_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x10, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x11, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* toggle input of built-in and mic jack appropriately */ +static void cxt5045_hp_automic(struct hda_codec *codec) +{ + static struct hda_verb mic_jack_on[] = { + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {} + }; + static struct hda_verb mic_jack_off[] = { + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {} + }; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x12, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + if (present) + snd_hda_sequence_write(codec, mic_jack_on); + else + snd_hda_sequence_write(codec, mic_jack_off); +} + + +/* mute internal speaker if HP is plugged */ +static void cxt5045_hp_automute(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + spec->hp_present = snd_hda_codec_read(codec, 0x11, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + + bits = (spec->hp_present || !spec->cur_eapd) ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x10, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +/* unsolicited event for HP jack sensing */ +static void cxt5045_hp_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + switch (res) { + case CONEXANT_HP_EVENT: + cxt5045_hp_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5045_hp_automic(codec); + break; + + } +} + +static struct snd_kcontrol_new cxt5045_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put + }, + HDA_CODEC_VOLUME("Int Mic Capture Volume", 0x1a, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Capture Switch", 0x1a, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Capture Volume", 0x1a, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Capture Switch", 0x1a, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x17, 0x2, HDA_INPUT), + HDA_BIND_VOL("Master Playback Volume", &cxt5045_hp_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5045_hp_master_sw_put, + .private_value = 0x10, + }, + + {} +}; + +static struct snd_kcontrol_new cxt5045_benq_mixers[] = { + HDA_CODEC_VOLUME("Line In Capture Volume", 0x1a, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Line In Capture Switch", 0x1a, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("Line In Playback Volume", 0x17, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Line In Playback Switch", 0x17, 0x3, HDA_INPUT), + + {} +}; + +static struct snd_kcontrol_new cxt5045_mixers_hp530[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put + }, + HDA_CODEC_VOLUME("Int Mic Capture Volume", 0x1a, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Capture Switch", 0x1a, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Capture Volume", 0x1a, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Capture Switch", 0x1a, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("PCM Playback Volume", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("PCM Playback Switch", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x17, 0x1, HDA_INPUT), + HDA_BIND_VOL("Master Playback Volume", &cxt5045_hp_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5045_hp_master_sw_put, + .private_value = 0x10, + }, + + {} +}; + +static struct hda_verb cxt5045_init_verbs[] = { + /* Line in, Mic */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 }, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 }, + /* HP, Amp */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x10, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Record selector: Int mic */ + {0x1a, AC_VERB_SET_CONNECT_SEL,0x1}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17}, + /* SPDIF route: PCM */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x13, AC_VERB_SET_CONNECT_SEL, 0x0 }, + /* EAPD */ + {0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x2 }, /* default on */ + { } /* end */ +}; + +static struct hda_verb cxt5045_benq_init_verbs[] = { + /* Int Mic, Mic */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 }, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_80 }, + /* Line In,HP, Amp */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x10, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x11, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Record selector: Int mic */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x1}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17}, + /* SPDIF route: PCM */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* EAPD */ + {0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ + { } /* end */ +}; + +static struct hda_verb cxt5045_hp_sense_init_verbs[] = { + /* pin sensing on HP jack */ + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + { } /* end */ +}; + +static struct hda_verb cxt5045_mic_sense_init_verbs[] = { + /* pin sensing on HP jack */ + {0x12, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + { } /* end */ +}; + +#ifdef CONFIG_SND_DEBUG +/* Test configuration for debugging, modelled after the ALC260 test + * configuration. + */ +static struct hda_input_mux cxt5045_test_capture_source = { + .num_items = 5, + .items = { + { "MIXER", 0x0 }, + { "MIC1 pin", 0x1 }, + { "LINE1 pin", 0x2 }, + { "HP-OUT pin", 0x3 }, + { "CD pin", 0x4 }, + }, +}; + +static struct snd_kcontrol_new cxt5045_test_mixer[] = { + + /* Output controls */ + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Node 11 Playback Volume", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Node 11 Playback Switch", 0x11, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Node 12 Playback Volume", 0x12, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Node 12 Playback Switch", 0x12, 0x0, HDA_OUTPUT), + + /* Modes for retasking pin widgets */ + CXT_PIN_MODE("HP-OUT pin mode", 0x11, CXT_PIN_DIR_INOUT), + CXT_PIN_MODE("LINE1 pin mode", 0x12, CXT_PIN_DIR_INOUT), + + /* EAPD Switch Control */ + CXT_EAPD_SWITCH("External Amplifier", 0x10, 0x0), + + /* Loopback mixer controls */ + + HDA_CODEC_VOLUME("Mixer-1 Volume", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mixer-1 Switch", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mixer-2 Volume", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mixer-2 Switch", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mixer-3 Volume", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Mixer-3 Switch", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Mixer-4 Volume", 0x17, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Mixer-4 Switch", 0x17, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("Mixer-5 Volume", 0x17, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Mixer-5 Switch", 0x17, 0x4, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put, + }, + /* Audio input controls */ + HDA_CODEC_VOLUME("Input-1 Volume", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Input-1 Switch", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Input-2 Volume", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Input-2 Switch", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Input-3 Volume", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Input-3 Switch", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Input-4 Volume", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Input-4 Switch", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("Input-5 Volume", 0x1a, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Input-5 Switch", 0x1a, 0x4, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb cxt5045_test_init_verbs[] = { + /* Set connections */ + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x0 }, + { 0x11, AC_VERB_SET_CONNECT_SEL, 0x0 }, + { 0x12, AC_VERB_SET_CONNECT_SEL, 0x0 }, + /* Enable retasking pins as output, initially without power amp */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* Disable digital (SPDIF) pins initially, but users can enable + * them via a mixer switch. In the case of SPDIF-out, this initverb + * payload also sets the generation to 0, output to be in "consumer" + * PCM format, copyright asserted, no pre-emphasis and no validity + * control. + */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_DIGI_CONVERT_1, 0}, + + /* Start with output sum widgets muted and their output gains at min */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + /* Unmute retasking pin widget output buffers since the default + * state appears to be output. As the pin mode is changed by the + * user the pin mode control will take care of enabling the pin's + * input/output buffers as needed. + */ + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mute capture amp left and right */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + + /* Set ADC connection select to match default mixer setting (mic1 + * pin) + */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mute all inputs to mixer widget (even unconnected ones) */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* Mixer pin */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* Mic1 pin */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* Line pin */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* HP pin */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */ + + { } +}; +#endif + + +/* initialize jack-sensing, too */ +static int cxt5045_init(struct hda_codec *codec) +{ + conexant_init(codec); + cxt5045_hp_automute(codec); + return 0; +} + + +enum { + CXT5045_LAPTOP_HPSENSE, + CXT5045_LAPTOP_MICSENSE, + CXT5045_LAPTOP_HPMICSENSE, + CXT5045_BENQ, + CXT5045_LAPTOP_HP530, +#ifdef CONFIG_SND_DEBUG + CXT5045_TEST, +#endif + CXT5045_MODELS +}; + +static const char *cxt5045_models[CXT5045_MODELS] = { + [CXT5045_LAPTOP_HPSENSE] = "laptop-hpsense", + [CXT5045_LAPTOP_MICSENSE] = "laptop-micsense", + [CXT5045_LAPTOP_HPMICSENSE] = "laptop-hpmicsense", + [CXT5045_BENQ] = "benq", + [CXT5045_LAPTOP_HP530] = "laptop-hp530", +#ifdef CONFIG_SND_DEBUG + [CXT5045_TEST] = "test", +#endif +}; + +static struct snd_pci_quirk cxt5045_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30a5, "HP", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30b2, "HP DV Series", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30b5, "HP DV2120", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30b7, "HP DV6000Z", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30bb, "HP DV8000", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30cd, "HP DV Series", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV9533EG", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x103c, 0x30d5, "HP 530", CXT5045_LAPTOP_HP530), + SND_PCI_QUIRK(0x103c, 0x30d9, "HP Spartan", CXT5045_LAPTOP_HPSENSE), + SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P105", CXT5045_LAPTOP_MICSENSE), + SND_PCI_QUIRK(0x152d, 0x0753, "Benq R55E", CXT5045_BENQ), + SND_PCI_QUIRK(0x1734, 0x10ad, "Fujitsu Si1520", CXT5045_LAPTOP_MICSENSE), + SND_PCI_QUIRK(0x1734, 0x10cb, "Fujitsu Si3515", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1734, 0x110e, "Fujitsu V5505", + CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1509, 0x1e40, "FIC", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1509, 0x2f05, "FIC", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1509, 0x2f06, "FIC", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1631, 0xc106, "Packard Bell", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x1631, 0xc107, "Packard Bell", CXT5045_LAPTOP_HPMICSENSE), + SND_PCI_QUIRK(0x8086, 0x2111, "Conexant Reference board", CXT5045_LAPTOP_HPSENSE), + {} +}; + +static int patch_cxt5045(struct hda_codec *codec) +{ + struct conexant_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(cxt5045_dac_nids); + spec->multiout.dac_nids = cxt5045_dac_nids; + spec->multiout.dig_out_nid = CXT5045_SPDIF_OUT; + spec->num_adc_nids = 1; + spec->adc_nids = cxt5045_adc_nids; + spec->capsrc_nids = cxt5045_capsrc_nids; + spec->input_mux = &cxt5045_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = cxt5045_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = cxt5045_init_verbs; + spec->spdif_route = 0; + spec->num_channel_mode = ARRAY_SIZE(cxt5045_modes), + spec->channel_mode = cxt5045_modes, + + + codec->patch_ops = conexant_patch_ops; + + board_config = snd_hda_check_board_config(codec, CXT5045_MODELS, + cxt5045_models, + cxt5045_cfg_tbl); + switch (board_config) { + case CXT5045_LAPTOP_HPSENSE: + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + spec->input_mux = &cxt5045_capture_source; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5045_hp_sense_init_verbs; + spec->mixers[0] = cxt5045_mixers; + codec->patch_ops.init = cxt5045_init; + break; + case CXT5045_LAPTOP_MICSENSE: + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + spec->input_mux = &cxt5045_capture_source; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5045_mic_sense_init_verbs; + spec->mixers[0] = cxt5045_mixers; + codec->patch_ops.init = cxt5045_init; + break; + default: + case CXT5045_LAPTOP_HPMICSENSE: + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + spec->input_mux = &cxt5045_capture_source; + spec->num_init_verbs = 3; + spec->init_verbs[1] = cxt5045_hp_sense_init_verbs; + spec->init_verbs[2] = cxt5045_mic_sense_init_verbs; + spec->mixers[0] = cxt5045_mixers; + codec->patch_ops.init = cxt5045_init; + break; + case CXT5045_BENQ: + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + spec->input_mux = &cxt5045_capture_source_benq; + spec->num_init_verbs = 1; + spec->init_verbs[0] = cxt5045_benq_init_verbs; + spec->mixers[0] = cxt5045_mixers; + spec->mixers[1] = cxt5045_benq_mixers; + spec->num_mixers = 2; + codec->patch_ops.init = cxt5045_init; + break; + case CXT5045_LAPTOP_HP530: + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + spec->input_mux = &cxt5045_capture_source_hp530; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5045_hp_sense_init_verbs; + spec->mixers[0] = cxt5045_mixers_hp530; + codec->patch_ops.init = cxt5045_init; + break; +#ifdef CONFIG_SND_DEBUG + case CXT5045_TEST: + spec->input_mux = &cxt5045_test_capture_source; + spec->mixers[0] = cxt5045_test_mixer; + spec->init_verbs[0] = cxt5045_test_init_verbs; + break; + +#endif + } + + switch (codec->subsystem_id >> 16) { + case 0x103c: + /* HP laptop has a really bad sound over 0dB on NID 0x17. + * Fix max PCM level to 0 dB + * (originall it has 0x2b steps with 0dB offset 0x14) + */ + snd_hda_override_amp_caps(codec, 0x17, HDA_INPUT, + (0x14 << AC_AMPCAP_OFFSET_SHIFT) | + (0x14 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x05 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (1 << AC_AMPCAP_MUTE_SHIFT)); + break; + } + + return 0; +} + + +/* Conexant 5047 specific */ +#define CXT5047_SPDIF_OUT 0x11 + +static hda_nid_t cxt5047_dac_nids[2] = { 0x10, 0x1c }; +static hda_nid_t cxt5047_adc_nids[1] = { 0x12 }; +static hda_nid_t cxt5047_capsrc_nids[1] = { 0x1a }; + +static struct hda_channel_mode cxt5047_modes[1] = { + { 2, NULL }, +}; + +static struct hda_input_mux cxt5047_capture_source = { + .num_items = 1, + .items = { + { "Mic", 0x2 }, + } +}; + +static struct hda_input_mux cxt5047_hp_capture_source = { + .num_items = 1, + .items = { + { "ExtMic", 0x2 }, + } +}; + +static struct hda_input_mux cxt5047_toshiba_capture_source = { + .num_items = 2, + .items = { + { "ExtMic", 0x2 }, + { "Line-In", 0x1 }, + } +}; + +/* turn on/off EAPD (+ mute HP) as a master switch */ +static int cxt5047_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + if (!cxt_eapd_put(kcontrol, ucontrol)) + return 0; + + /* toggle internal speakers mute depending of presence of + * the headphone jack + */ + bits = (!spec->hp_present && spec->cur_eapd) ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, 0x1d, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + bits = spec->cur_eapd ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, 0x13, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + return 1; +} + +/* bind volumes of both NID 0x13 (Headphones) and 0x1d (Speakers) */ +static struct hda_bind_ctls cxt5047_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x13, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x1d, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* mute internal speaker if HP is plugged */ +static void cxt5047_hp_automute(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + spec->hp_present = snd_hda_codec_read(codec, 0x13, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + + bits = (spec->hp_present || !spec->cur_eapd) ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x1d, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + /* Mute/Unmute PCM 2 for good measure - some systems need this */ + snd_hda_codec_amp_stereo(codec, 0x1c, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +/* mute internal speaker if HP is plugged */ +static void cxt5047_hp2_automute(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int bits; + + spec->hp_present = snd_hda_codec_read(codec, 0x13, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + + bits = spec->hp_present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x1d, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + /* Mute/Unmute PCM 2 for good measure - some systems need this */ + snd_hda_codec_amp_stereo(codec, 0x1c, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +/* toggle input of built-in and mic jack appropriately */ +static void cxt5047_hp_automic(struct hda_codec *codec) +{ + static struct hda_verb mic_jack_on[] = { + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {} + }; + static struct hda_verb mic_jack_off[] = { + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {} + }; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + if (present) + snd_hda_sequence_write(codec, mic_jack_on); + else + snd_hda_sequence_write(codec, mic_jack_off); +} + +/* unsolicited event for HP jack sensing */ +static void cxt5047_hp_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case CONEXANT_HP_EVENT: + cxt5047_hp_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5047_hp_automic(codec); + break; + } +} + +/* unsolicited event for HP jack sensing - non-EAPD systems */ +static void cxt5047_hp2_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + switch (res) { + case CONEXANT_HP_EVENT: + cxt5047_hp2_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5047_hp_automic(codec); + break; + } +} + +static struct snd_kcontrol_new cxt5047_mixers[] = { + HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Gain Volume", 0x1a, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mic Gain Switch", 0x1a, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM-2 Volume", 0x1c, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM-2 Switch", 0x1c, 0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x1d, 0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x13, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x13, 0x00, HDA_OUTPUT), + + {} +}; + +static struct snd_kcontrol_new cxt5047_toshiba_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put + }, + HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT), + HDA_BIND_VOL("Master Playback Volume", &cxt5047_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5047_hp_master_sw_put, + .private_value = 0x13, + }, + + {} +}; + +static struct snd_kcontrol_new cxt5047_hp_mixers[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put + }, + HDA_CODEC_VOLUME("Mic Bypass Capture Volume", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x19,0x02,HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x12, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("PCM Volume", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("PCM Switch", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x13, 0x00, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5047_hp_master_sw_put, + .private_value = 0x13, + }, + { } /* end */ +}; + +static struct hda_verb cxt5047_init_verbs[] = { + /* Line in, Mic, Built-in Mic */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 }, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 }, + /* HP, Speaker */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + {0x13, AC_VERB_SET_CONNECT_SEL,0x1}, + {0x1d, AC_VERB_SET_CONNECT_SEL,0x0}, + /* Record selector: Mic */ + {0x12, AC_VERB_SET_CONNECT_SEL,0x03}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17}, + {0x1A, AC_VERB_SET_CONNECT_SEL,0x02}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x00}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_OUTPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x03}, + /* SPDIF route: PCM */ + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x0 }, + /* Enable unsolicited events */ + {0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + { } /* end */ +}; + +/* configuration for Toshiba Laptops */ +static struct hda_verb cxt5047_toshiba_init_verbs[] = { + {0x13, AC_VERB_SET_EAPD_BTLENABLE, 0x0 }, /* default on */ + /* pin sensing on HP and Mic jacks */ + {0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + /* Speaker routing */ + {0x1d, AC_VERB_SET_CONNECT_SEL,0x1}, + {} +}; + +/* configuration for HP Laptops */ +static struct hda_verb cxt5047_hp_init_verbs[] = { + /* pin sensing on HP jack */ + {0x13, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + /* 0x13 is actually shared by both HP and speaker; + * setting the connection to 0 (=0x19) makes the master volume control + * working mysteriouslly... + */ + {0x13, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* Record selector: Ext Mic */ + {0x12, AC_VERB_SET_CONNECT_SEL,0x03}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_INPUT|AC_AMP_SET_RIGHT|AC_AMP_SET_LEFT|0x17}, + /* Speaker routing */ + {0x1d, AC_VERB_SET_CONNECT_SEL,0x1}, + {} +}; + +/* Test configuration for debugging, modelled after the ALC260 test + * configuration. + */ +#ifdef CONFIG_SND_DEBUG +static struct hda_input_mux cxt5047_test_capture_source = { + .num_items = 4, + .items = { + { "LINE1 pin", 0x0 }, + { "MIC1 pin", 0x1 }, + { "MIC2 pin", 0x2 }, + { "CD pin", 0x3 }, + }, +}; + +static struct snd_kcontrol_new cxt5047_test_mixer[] = { + + /* Output only controls */ + HDA_CODEC_VOLUME("OutAmp-1 Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("OutAmp-1 Switch", 0x10,0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("OutAmp-2 Volume", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("OutAmp-2 Switch", 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("HeadPhone Playback Volume", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("HeadPhone Playback Switch", 0x13, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line1-Out Playback Volume", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line1-Out Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line2-Out Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line2-Out Playback Switch", 0x15, 0x0, HDA_OUTPUT), + + /* Modes for retasking pin widgets */ + CXT_PIN_MODE("LINE1 pin mode", 0x14, CXT_PIN_DIR_INOUT), + CXT_PIN_MODE("MIC1 pin mode", 0x15, CXT_PIN_DIR_INOUT), + + /* EAPD Switch Control */ + CXT_EAPD_SWITCH("External Amplifier", 0x13, 0x0), + + /* Loopback mixer controls */ + HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x12, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("MIC1 Playback Switch", 0x12, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x12, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("MIC2 Playback Switch", 0x12, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("LINE Playback Volume", 0x12, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("LINE Playback Switch", 0x12, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x12, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x12, 0x04, HDA_INPUT), + + HDA_CODEC_VOLUME("Capture-1 Volume", 0x19, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture-1 Switch", 0x19, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture-2 Volume", 0x19, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Capture-2 Switch", 0x19, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture-3 Volume", 0x19, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Capture-3 Switch", 0x19, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Capture-4 Volume", 0x19, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Capture-4 Switch", 0x19, 0x3, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put, + }, + HDA_CODEC_VOLUME("Input-1 Volume", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Input-1 Switch", 0x1a, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Input-2 Volume", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Input-2 Switch", 0x1a, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Input-3 Volume", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Input-3 Switch", 0x1a, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Input-4 Volume", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Input-4 Switch", 0x1a, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("Input-5 Volume", 0x1a, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Input-5 Switch", 0x1a, 0x4, HDA_INPUT), + + { } /* end */ +}; + +static struct hda_verb cxt5047_test_init_verbs[] = { + /* Enable retasking pins as output, initially without power amp */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* Disable digital (SPDIF) pins initially, but users can enable + * them via a mixer switch. In the case of SPDIF-out, this initverb + * payload also sets the generation to 0, output to be in "consumer" + * PCM format, copyright asserted, no pre-emphasis and no validity + * control. + */ + {0x18, AC_VERB_SET_DIGI_CONVERT_1, 0}, + + /* Ensure mic1, mic2, line1 pin widgets take input from the + * OUT1 sum bus when acting as an output. + */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0}, + + /* Start with output sum widgets muted and their output gains at min */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + /* Unmute retasking pin widget output buffers since the default + * state appears to be output. As the pin mode is changed by the + * user the pin mode control will take care of enabling the pin's + * input/output buffers as needed. + */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mute capture amp left and right */ + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + + /* Set ADC connection select to match default mixer setting (mic1 + * pin) + */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mute all inputs to mixer widget (even unconnected ones) */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */ + + { } +}; +#endif + + +/* initialize jack-sensing, too */ +static int cxt5047_hp_init(struct hda_codec *codec) +{ + conexant_init(codec); + cxt5047_hp_automute(codec); + return 0; +} + + +enum { + CXT5047_LAPTOP, /* Laptops w/o EAPD support */ + CXT5047_LAPTOP_HP, /* Some HP laptops */ + CXT5047_LAPTOP_EAPD, /* Laptops with EAPD support */ +#ifdef CONFIG_SND_DEBUG + CXT5047_TEST, +#endif + CXT5047_MODELS +}; + +static const char *cxt5047_models[CXT5047_MODELS] = { + [CXT5047_LAPTOP] = "laptop", + [CXT5047_LAPTOP_HP] = "laptop-hp", + [CXT5047_LAPTOP_EAPD] = "laptop-eapd", +#ifdef CONFIG_SND_DEBUG + [CXT5047_TEST] = "test", +#endif +}; + +static struct snd_pci_quirk cxt5047_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30a0, "HP DV1000", CXT5047_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30a5, "HP DV5200T/DV8000T", CXT5047_LAPTOP_HP), + SND_PCI_QUIRK(0x103c, 0x30b2, "HP DV2000T/DV3000T", CXT5047_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30b5, "HP DV2000Z", CXT5047_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6700", CXT5047_LAPTOP), + SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba P100", CXT5047_LAPTOP_EAPD), + {} +}; + +static int patch_cxt5047(struct hda_codec *codec) +{ + struct conexant_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(cxt5047_dac_nids); + spec->multiout.dac_nids = cxt5047_dac_nids; + spec->multiout.dig_out_nid = CXT5047_SPDIF_OUT; + spec->num_adc_nids = 1; + spec->adc_nids = cxt5047_adc_nids; + spec->capsrc_nids = cxt5047_capsrc_nids; + spec->input_mux = &cxt5047_capture_source; + spec->num_mixers = 1; + spec->mixers[0] = cxt5047_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = cxt5047_init_verbs; + spec->spdif_route = 0; + spec->num_channel_mode = ARRAY_SIZE(cxt5047_modes), + spec->channel_mode = cxt5047_modes, + + codec->patch_ops = conexant_patch_ops; + + board_config = snd_hda_check_board_config(codec, CXT5047_MODELS, + cxt5047_models, + cxt5047_cfg_tbl); + switch (board_config) { + case CXT5047_LAPTOP: + codec->patch_ops.unsol_event = cxt5047_hp2_unsol_event; + break; + case CXT5047_LAPTOP_HP: + spec->input_mux = &cxt5047_hp_capture_source; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5047_hp_init_verbs; + spec->mixers[0] = cxt5047_hp_mixers; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; + codec->patch_ops.init = cxt5047_hp_init; + break; + case CXT5047_LAPTOP_EAPD: + spec->input_mux = &cxt5047_toshiba_capture_source; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5047_toshiba_init_verbs; + spec->mixers[0] = cxt5047_toshiba_mixers; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; + break; +#ifdef CONFIG_SND_DEBUG + case CXT5047_TEST: + spec->input_mux = &cxt5047_test_capture_source; + spec->mixers[0] = cxt5047_test_mixer; + spec->init_verbs[0] = cxt5047_test_init_verbs; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; +#endif + } + return 0; +} + +/* Conexant 5051 specific */ +static hda_nid_t cxt5051_dac_nids[1] = { 0x10 }; +static hda_nid_t cxt5051_adc_nids[2] = { 0x14, 0x15 }; +#define CXT5051_SPDIF_OUT 0x1C +#define CXT5051_PORTB_EVENT 0x38 +#define CXT5051_PORTC_EVENT 0x39 + +static struct hda_channel_mode cxt5051_modes[1] = { + { 2, NULL }, +}; + +static void cxt5051_update_speaker(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int pinctl; + pinctl = (!spec->hp_present && spec->cur_eapd) ? PIN_OUT : 0; + snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + pinctl); +} + +/* turn on/off EAPD (+ mute HP) as a master switch */ +static int cxt5051_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + + if (!cxt_eapd_put(kcontrol, ucontrol)) + return 0; + cxt5051_update_speaker(codec); + return 1; +} + +/* toggle input of built-in and mic jack appropriately */ +static void cxt5051_portb_automic(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x17, 0, + AC_VERB_GET_PIN_SENSE, 0) & + AC_PINSENSE_PRESENCE; + snd_hda_codec_write(codec, 0x14, 0, + AC_VERB_SET_CONNECT_SEL, + present ? 0x01 : 0x00); +} + +/* switch the current ADC according to the jack state */ +static void cxt5051_portc_automic(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + unsigned int present; + hda_nid_t new_adc; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & + AC_PINSENSE_PRESENCE; + if (present) + spec->cur_adc_idx = 1; + else + spec->cur_adc_idx = 0; + new_adc = spec->adc_nids[spec->cur_adc_idx]; + if (spec->cur_adc && spec->cur_adc != new_adc) { + /* stream is running, let's swap the current ADC */ + snd_hda_codec_cleanup_stream(codec, spec->cur_adc); + spec->cur_adc = new_adc; + snd_hda_codec_setup_stream(codec, new_adc, + spec->cur_adc_stream_tag, 0, + spec->cur_adc_format); + } +} + +/* mute internal speaker if HP is plugged */ +static void cxt5051_hp_automute(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + + spec->hp_present = snd_hda_codec_read(codec, 0x16, 0, + AC_VERB_GET_PIN_SENSE, 0) & + AC_PINSENSE_PRESENCE; + cxt5051_update_speaker(codec); +} + +/* unsolicited event for HP jack sensing */ +static void cxt5051_hp_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case CONEXANT_HP_EVENT: + cxt5051_hp_automute(codec); + break; + case CXT5051_PORTB_EVENT: + cxt5051_portb_automic(codec); + break; + case CXT5051_PORTC_EVENT: + cxt5051_portc_automic(codec); + break; + } +} + +static struct snd_kcontrol_new cxt5051_mixers[] = { + HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Docking Mic Volume", 0x15, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Docking Mic Switch", 0x15, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5051_hp_master_sw_put, + .private_value = 0x1a, + }, + + {} +}; + +static struct snd_kcontrol_new cxt5051_hp_mixers[] = { + HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("External Mic Volume", 0x15, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("External Mic Switch", 0x15, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5051_hp_master_sw_put, + .private_value = 0x1a, + }, + + {} +}; + +static struct hda_verb cxt5051_init_verbs[] = { + /* Line in, Mic */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03}, + /* SPK */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* HP, Amp */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* DAC1 */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Record selector: Int mic */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x44}, + /* SPDIF route: PCM */ + {0x1c, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* EAPD */ + {0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ + {0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT}, + {0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTC_EVENT}, + { } /* end */ +}; + +/* initialize jack-sensing, too */ +static int cxt5051_init(struct hda_codec *codec) +{ + conexant_init(codec); + if (codec->patch_ops.unsol_event) { + cxt5051_hp_automute(codec); + cxt5051_portb_automic(codec); + cxt5051_portc_automic(codec); + } + return 0; +} + + +enum { + CXT5051_LAPTOP, /* Laptops w/ EAPD support */ + CXT5051_HP, /* no docking */ + CXT5051_MODELS +}; + +static const char *cxt5051_models[CXT5051_MODELS] = { + [CXT5051_LAPTOP] = "laptop", + [CXT5051_HP] = "hp", +}; + +static struct snd_pci_quirk cxt5051_cfg_tbl[] = { + SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board", + CXT5051_LAPTOP), + SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP), + {} +}; + +static int patch_cxt5051(struct hda_codec *codec) +{ + struct conexant_spec *spec; + int board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + codec->spec = spec; + + codec->patch_ops = conexant_patch_ops; + codec->patch_ops.init = cxt5051_init; + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(cxt5051_dac_nids); + spec->multiout.dac_nids = cxt5051_dac_nids; + spec->multiout.dig_out_nid = CXT5051_SPDIF_OUT; + spec->num_adc_nids = 1; /* not 2; via auto-mic switch */ + spec->adc_nids = cxt5051_adc_nids; + spec->num_mixers = 1; + spec->mixers[0] = cxt5051_mixers; + spec->num_init_verbs = 1; + spec->init_verbs[0] = cxt5051_init_verbs; + spec->spdif_route = 0; + spec->num_channel_mode = ARRAY_SIZE(cxt5051_modes); + spec->channel_mode = cxt5051_modes; + spec->cur_adc = 0; + spec->cur_adc_idx = 0; + + board_config = snd_hda_check_board_config(codec, CXT5051_MODELS, + cxt5051_models, + cxt5051_cfg_tbl); + switch (board_config) { + case CXT5051_HP: + codec->patch_ops.unsol_event = cxt5051_hp_unsol_event; + spec->mixers[0] = cxt5051_hp_mixers; + break; + default: + case CXT5051_LAPTOP: + codec->patch_ops.unsol_event = cxt5051_hp_unsol_event; + break; + } + + return 0; +} + + +/* + */ + +struct hda_codec_preset snd_hda_preset_conexant[] = { + { .id = 0x14f15045, .name = "CX20549 (Venice)", + .patch = patch_cxt5045 }, + { .id = 0x14f15047, .name = "CX20551 (Waikiki)", + .patch = patch_cxt5047 }, + { .id = 0x14f15051, .name = "CX20561 (Hermosa)", + .patch = patch_cxt5051 }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_nvhdmi.c b/sound/pci/hda/patch_nvhdmi.c new file mode 100644 index 0000000..2eed2c8 --- /dev/null +++ b/sound/pci/hda/patch_nvhdmi.c @@ -0,0 +1,165 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for NVIDIA HDMI codecs + * + * Copyright (c) 2008 NVIDIA Corp. All rights reserved. + * Copyright (c) 2008 Wei Ni <wni@nvidia.com> + * + * + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + +struct nvhdmi_spec { + struct hda_multi_out multiout; + + struct hda_pcm pcm_rec; +}; + +static struct hda_verb nvhdmi_basic_init[] = { + /* enable digital output on pin widget */ + { 0x05, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {} /* terminator */ +}; + +/* + * Controls + */ +static int nvhdmi_build_controls(struct hda_codec *codec) +{ + struct nvhdmi_spec *spec = codec->spec; + int err; + + err = snd_hda_create_spdif_out_ctls(codec, spec->multiout.dig_out_nid); + if (err < 0) + return err; + + return 0; +} + +static int nvhdmi_init(struct hda_codec *codec) +{ + snd_hda_sequence_write(codec, nvhdmi_basic_init); + return 0; +} + +/* + * Digital out + */ +static int nvhdmi_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct nvhdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int nvhdmi_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct nvhdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int nvhdmi_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct nvhdmi_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, stream_tag, + format, substream); +} + +static struct hda_pcm_stream nvhdmi_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0x4, /* NID to query formats and rates and setup streams */ + .rates = SNDRV_PCM_RATE_48000, + .maxbps = 16, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .ops = { + .open = nvhdmi_dig_playback_pcm_open, + .close = nvhdmi_dig_playback_pcm_close, + .prepare = nvhdmi_dig_playback_pcm_prepare + }, +}; + +static int nvhdmi_build_pcms(struct hda_codec *codec) +{ + struct nvhdmi_spec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "NVIDIA HDMI"; + info->pcm_type = HDA_PCM_TYPE_HDMI; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = nvhdmi_pcm_digital_playback; + + return 0; +} + +static void nvhdmi_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + +static struct hda_codec_ops nvhdmi_patch_ops = { + .build_controls = nvhdmi_build_controls, + .build_pcms = nvhdmi_build_pcms, + .init = nvhdmi_init, + .free = nvhdmi_free, +}; + +static int patch_nvhdmi(struct hda_codec *codec) +{ + struct nvhdmi_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + spec->multiout.num_dacs = 0; /* no analog */ + spec->multiout.max_channels = 2; + spec->multiout.dig_out_nid = 0x4; /* NID for copying analog to digital, + * seems to be unused in pure-digital + * case. */ + + codec->patch_ops = nvhdmi_patch_ops; + + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_nvhdmi[] = { + { .id = 0x10de0002, .name = "NVIDIA MCP78 HDMI", .patch = patch_nvhdmi }, + { .id = 0x10de0007, .name = "NVIDIA MCP7A HDMI", .patch = patch_nvhdmi }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c new file mode 100644 index 0000000..7123a6e --- /dev/null +++ b/sound/pci/hda/patch_realtek.c @@ -0,0 +1,16493 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for ALC 260/880/882 codecs + * + * Copyright (c) 2004 Kailang Yang <kailang@realtek.com.tw> + * PeiSen Hou <pshou@realtek.com.tw> + * Takashi Iwai <tiwai@suse.de> + * Jonathan Woithe <jwoithe@physics.adelaide.edu.au> + * + * 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 <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" +#include "hda_patch.h" + +#define ALC880_FRONT_EVENT 0x01 +#define ALC880_DCVOL_EVENT 0x02 +#define ALC880_HP_EVENT 0x04 +#define ALC880_MIC_EVENT 0x08 + +/* ALC880 board config type */ +enum { + ALC880_3ST, + ALC880_3ST_DIG, + ALC880_5ST, + ALC880_5ST_DIG, + ALC880_W810, + ALC880_Z71V, + ALC880_6ST, + ALC880_6ST_DIG, + ALC880_F1734, + ALC880_ASUS, + ALC880_ASUS_DIG, + ALC880_ASUS_W1V, + ALC880_ASUS_DIG2, + ALC880_FUJITSU, + ALC880_UNIWILL_DIG, + ALC880_UNIWILL, + ALC880_UNIWILL_P53, + ALC880_CLEVO, + ALC880_TCL_S700, + ALC880_LG, + ALC880_LG_LW, + ALC880_MEDION_RIM, +#ifdef CONFIG_SND_DEBUG + ALC880_TEST, +#endif + ALC880_AUTO, + ALC880_MODEL_LAST /* last tag */ +}; + +/* ALC260 models */ +enum { + ALC260_BASIC, + ALC260_HP, + ALC260_HP_DC7600, + ALC260_HP_3013, + ALC260_FUJITSU_S702X, + ALC260_ACER, + ALC260_WILL, + ALC260_REPLACER_672V, +#ifdef CONFIG_SND_DEBUG + ALC260_TEST, +#endif + ALC260_AUTO, + ALC260_MODEL_LAST /* last tag */ +}; + +/* ALC262 models */ +enum { + ALC262_BASIC, + ALC262_HIPPO, + ALC262_HIPPO_1, + ALC262_FUJITSU, + ALC262_HP_BPC, + ALC262_HP_BPC_D7000_WL, + ALC262_HP_BPC_D7000_WF, + ALC262_HP_TC_T5735, + ALC262_HP_RP5700, + ALC262_BENQ_ED8, + ALC262_SONY_ASSAMD, + ALC262_BENQ_T31, + ALC262_ULTRA, + ALC262_LENOVO_3000, + ALC262_NEC, + ALC262_TOSHIBA_S06, + ALC262_TOSHIBA_RX1, + ALC262_AUTO, + ALC262_MODEL_LAST /* last tag */ +}; + +/* ALC268 models */ +enum { + ALC267_QUANTA_IL1, + ALC268_3ST, + ALC268_TOSHIBA, + ALC268_ACER, + ALC268_ACER_ASPIRE_ONE, + ALC268_DELL, + ALC268_ZEPTO, +#ifdef CONFIG_SND_DEBUG + ALC268_TEST, +#endif + ALC268_AUTO, + ALC268_MODEL_LAST /* last tag */ +}; + +/* ALC269 models */ +enum { + ALC269_BASIC, + ALC269_QUANTA_FL1, + ALC269_ASUS_EEEPC_P703, + ALC269_ASUS_EEEPC_P901, + ALC269_AUTO, + ALC269_MODEL_LAST /* last tag */ +}; + +/* ALC861 models */ +enum { + ALC861_3ST, + ALC660_3ST, + ALC861_3ST_DIG, + ALC861_6ST_DIG, + ALC861_UNIWILL_M31, + ALC861_TOSHIBA, + ALC861_ASUS, + ALC861_ASUS_LAPTOP, + ALC861_AUTO, + ALC861_MODEL_LAST, +}; + +/* ALC861-VD models */ +enum { + ALC660VD_3ST, + ALC660VD_3ST_DIG, + ALC861VD_3ST, + ALC861VD_3ST_DIG, + ALC861VD_6ST_DIG, + ALC861VD_LENOVO, + ALC861VD_DALLAS, + ALC861VD_HP, + ALC861VD_AUTO, + ALC861VD_MODEL_LAST, +}; + +/* ALC662 models */ +enum { + ALC662_3ST_2ch_DIG, + ALC662_3ST_6ch_DIG, + ALC662_3ST_6ch, + ALC662_5ST_DIG, + ALC662_LENOVO_101E, + ALC662_ASUS_EEEPC_P701, + ALC662_ASUS_EEEPC_EP20, + ALC663_ASUS_M51VA, + ALC663_ASUS_G71V, + ALC663_ASUS_H13, + ALC663_ASUS_G50V, + ALC662_ECS, + ALC663_ASUS_MODE1, + ALC662_ASUS_MODE2, + ALC663_ASUS_MODE3, + ALC663_ASUS_MODE4, + ALC663_ASUS_MODE5, + ALC663_ASUS_MODE6, + ALC662_AUTO, + ALC662_MODEL_LAST, +}; + +/* ALC882 models */ +enum { + ALC882_3ST_DIG, + ALC882_6ST_DIG, + ALC882_ARIMA, + ALC882_W2JC, + ALC882_TARGA, + ALC882_ASUS_A7J, + ALC882_ASUS_A7M, + ALC885_MACPRO, + ALC885_MBP3, + ALC885_IMAC24, + ALC882_AUTO, + ALC882_MODEL_LAST, +}; + +/* ALC883 models */ +enum { + ALC883_3ST_2ch_DIG, + ALC883_3ST_6ch_DIG, + ALC883_3ST_6ch, + ALC883_6ST_DIG, + ALC883_TARGA_DIG, + ALC883_TARGA_2ch_DIG, + ALC883_ACER, + ALC883_ACER_ASPIRE, + ALC883_MEDION, + ALC883_MEDION_MD2, + ALC883_LAPTOP_EAPD, + ALC883_LENOVO_101E_2ch, + ALC883_LENOVO_NB0763, + ALC888_LENOVO_MS7195_DIG, + ALC888_LENOVO_SKY, + ALC883_HAIER_W66, + ALC888_3ST_HP, + ALC888_6ST_DELL, + ALC883_MITAC, + ALC883_CLEVO_M720, + ALC883_FUJITSU_PI2515, + ALC883_3ST_6ch_INTEL, + ALC888_ASUS_M90V, + ALC888_ASUS_EEE1601, + ALC883_AUTO, + ALC883_MODEL_LAST, +}; + +/* for GPIO Poll */ +#define GPIO_MASK 0x03 + +struct alc_spec { + /* codec parameterization */ + struct snd_kcontrol_new *mixers[5]; /* mixer arrays */ + unsigned int num_mixers; + + const struct hda_verb *init_verbs[5]; /* initialization verbs + * don't forget NULL + * termination! + */ + unsigned int num_init_verbs; + + char *stream_name_analog; /* analog PCM stream */ + struct hda_pcm_stream *stream_analog_playback; + struct hda_pcm_stream *stream_analog_capture; + struct hda_pcm_stream *stream_analog_alt_playback; + struct hda_pcm_stream *stream_analog_alt_capture; + + char *stream_name_digital; /* digital PCM stream */ + struct hda_pcm_stream *stream_digital_playback; + struct hda_pcm_stream *stream_digital_capture; + + /* playback */ + struct hda_multi_out multiout; /* playback set-up + * max_channels, dacs must be set + * dig_out_nid and hp_nid are optional + */ + hda_nid_t alt_dac_nid; + + /* capture */ + unsigned int num_adc_nids; + hda_nid_t *adc_nids; + hda_nid_t *capsrc_nids; + hda_nid_t dig_in_nid; /* digital-in NID; optional */ + + /* capture source */ + unsigned int num_mux_defs; + const struct hda_input_mux *input_mux; + unsigned int cur_mux[3]; + + /* channel model */ + const struct hda_channel_mode *channel_mode; + int num_channel_mode; + int need_dac_fix; + + /* PCM information */ + struct hda_pcm pcm_rec[3]; /* used in alc_build_pcms() */ + + /* dynamic controls, init_verbs and input_mux */ + struct auto_pin_cfg autocfg; + unsigned int num_kctl_alloc, num_kctl_used; + struct snd_kcontrol_new *kctl_alloc; + struct hda_input_mux private_imux; + hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; + + /* hooks */ + void (*init_hook)(struct hda_codec *codec); + void (*unsol_event)(struct hda_codec *codec, unsigned int res); + + /* for pin sensing */ + unsigned int sense_updated: 1; + unsigned int jack_present: 1; + unsigned int master_sw: 1; + + /* for virtual master */ + hda_nid_t vmaster_nid; +#ifdef CONFIG_SND_HDA_POWER_SAVE + struct hda_loopback_check loopback; +#endif + + /* for PLL fix */ + hda_nid_t pll_nid; + unsigned int pll_coef_idx, pll_coef_bit; + +#ifdef SND_HDA_NEEDS_RESUME +#define ALC_MAX_PINS 16 + unsigned int num_pins; + hda_nid_t pin_nids[ALC_MAX_PINS]; + unsigned int pin_cfgs[ALC_MAX_PINS]; +#endif +}; + +/* + * configuration template - to be copied to the spec instance + */ +struct alc_config_preset { + struct snd_kcontrol_new *mixers[5]; /* should be identical size + * with spec + */ + const struct hda_verb *init_verbs[5]; + unsigned int num_dacs; + hda_nid_t *dac_nids; + hda_nid_t dig_out_nid; /* optional */ + hda_nid_t hp_nid; /* optional */ + unsigned int num_adc_nids; + hda_nid_t *adc_nids; + hda_nid_t *capsrc_nids; + hda_nid_t dig_in_nid; + unsigned int num_channel_mode; + const struct hda_channel_mode *channel_mode; + int need_dac_fix; + unsigned int num_mux_defs; + const struct hda_input_mux *input_mux; + void (*unsol_event)(struct hda_codec *, unsigned int); + void (*init_hook)(struct hda_codec *); +#ifdef CONFIG_SND_HDA_POWER_SAVE + struct hda_amp_list *loopbacks; +#endif +}; + + +/* + * input MUX handling + */ +static int alc_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + unsigned int mux_idx = snd_ctl_get_ioffidx(kcontrol, &uinfo->id); + if (mux_idx >= spec->num_mux_defs) + mux_idx = 0; + return snd_hda_input_mux_info(&spec->input_mux[mux_idx], uinfo); +} + +static int alc_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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); + unsigned int mux_idx = adc_idx >= spec->num_mux_defs ? 0 : adc_idx; + hda_nid_t nid = spec->capsrc_nids ? + spec->capsrc_nids[adc_idx] : spec->adc_nids[adc_idx]; + return snd_hda_input_mux_put(codec, &spec->input_mux[mux_idx], ucontrol, + nid, &spec->cur_mux[adc_idx]); +} + + +/* + * channel mode setting + */ +static int alc_ch_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + return snd_hda_ch_mode_info(codec, uinfo, spec->channel_mode, + spec->num_channel_mode); +} + +static int alc_ch_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + return snd_hda_ch_mode_get(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, + spec->multiout.max_channels); +} + +static int alc_ch_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + int err = snd_hda_ch_mode_put(codec, ucontrol, spec->channel_mode, + spec->num_channel_mode, + &spec->multiout.max_channels); + if (err >= 0 && spec->need_dac_fix) + spec->multiout.num_dacs = spec->multiout.max_channels / 2; + return err; +} + +/* + * Control the mode of pin widget settings via the mixer. "pc" is used + * instead of "%" to avoid consequences of accidently treating the % as + * being part of a format specifier. Maximum allowed length of a value is + * 63 characters plus NULL terminator. + * + * Note: some retasking pin complexes seem to ignore requests for input + * states other than HiZ (eg: PIN_VREFxx) and revert to HiZ if any of these + * are requested. Therefore order this list so that this behaviour will not + * cause problems when mixer clients move through the enum sequentially. + * NIDs 0x0f and 0x10 have been observed to have this behaviour as of + * March 2006. + */ +static char *alc_pin_mode_names[] = { + "Mic 50pc bias", "Mic 80pc bias", + "Line in", "Line out", "Headphone out", +}; +static unsigned char alc_pin_mode_values[] = { + PIN_VREF50, PIN_VREF80, PIN_IN, PIN_OUT, PIN_HP, +}; +/* The control can present all 5 options, or it can limit the options based + * in the pin being assumed to be exclusively an input or an output pin. In + * addition, "input" pins may or may not process the mic bias option + * depending on actual widget capability (NIDs 0x0f and 0x10 don't seem to + * accept requests for bias as of chip versions up to March 2006) and/or + * wiring in the computer. + */ +#define ALC_PIN_DIR_IN 0x00 +#define ALC_PIN_DIR_OUT 0x01 +#define ALC_PIN_DIR_INOUT 0x02 +#define ALC_PIN_DIR_IN_NOMICBIAS 0x03 +#define ALC_PIN_DIR_INOUT_NOMICBIAS 0x04 + +/* Info about the pin modes supported by the different pin direction modes. + * For each direction the minimum and maximum values are given. + */ +static signed char alc_pin_mode_dir_info[5][2] = { + { 0, 2 }, /* ALC_PIN_DIR_IN */ + { 3, 4 }, /* ALC_PIN_DIR_OUT */ + { 0, 4 }, /* ALC_PIN_DIR_INOUT */ + { 2, 2 }, /* ALC_PIN_DIR_IN_NOMICBIAS */ + { 2, 4 }, /* ALC_PIN_DIR_INOUT_NOMICBIAS */ +}; +#define alc_pin_mode_min(_dir) (alc_pin_mode_dir_info[_dir][0]) +#define alc_pin_mode_max(_dir) (alc_pin_mode_dir_info[_dir][1]) +#define alc_pin_mode_n_items(_dir) \ + (alc_pin_mode_max(_dir)-alc_pin_mode_min(_dir)+1) + +static int alc_pin_mode_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + unsigned int item_num = uinfo->value.enumerated.item; + unsigned char dir = (kcontrol->private_value >> 16) & 0xff; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = alc_pin_mode_n_items(dir); + + if (item_num<alc_pin_mode_min(dir) || item_num>alc_pin_mode_max(dir)) + item_num = alc_pin_mode_min(dir); + strcpy(uinfo->value.enumerated.name, alc_pin_mode_names[item_num]); + return 0; +} + +static int alc_pin_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int i; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char dir = (kcontrol->private_value >> 16) & 0xff; + long *valp = ucontrol->value.integer.value; + unsigned int pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, + 0x00); + + /* Find enumerated value for current pinctl setting */ + i = alc_pin_mode_min(dir); + while (alc_pin_mode_values[i] != pinctl && i <= alc_pin_mode_max(dir)) + i++; + *valp = i <= alc_pin_mode_max(dir) ? i: alc_pin_mode_min(dir); + return 0; +} + +static int alc_pin_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + signed int change; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char dir = (kcontrol->private_value >> 16) & 0xff; + long val = *ucontrol->value.integer.value; + unsigned int pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, + 0x00); + + if (val < alc_pin_mode_min(dir) || val > alc_pin_mode_max(dir)) + val = alc_pin_mode_min(dir); + + change = pinctl != alc_pin_mode_values[val]; + if (change) { + /* Set pin mode to that requested */ + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + alc_pin_mode_values[val]); + + /* Also enable the retasking pin's input/output as required + * for the requested pin mode. Enum values of 2 or less are + * input modes. + * + * Dynamically switching the input/output buffers probably + * reduces noise slightly (particularly on input) so we'll + * do it. However, having both input and output buffers + * enabled simultaneously doesn't seem to be problematic if + * this turns out to be necessary in the future. + */ + if (val <= 2) { + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT, 0, + HDA_AMP_MUTE, 0); + } else { + snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, 0); + } + } + return change; +} + +#define ALC_PIN_MODE(xname, nid, dir) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = alc_pin_mode_info, \ + .get = alc_pin_mode_get, \ + .put = alc_pin_mode_put, \ + .private_value = nid | (dir<<16) } + +/* A switch control for ALC260 GPIO pins. Multiple GPIOs can be ganged + * together using a mask with more than one bit set. This control is + * currently used only by the ALC260 test model. At this stage they are not + * needed for any "production" models. + */ +#ifdef CONFIG_SND_DEBUG +#define alc_gpio_data_info snd_ctl_boolean_mono_info + +static int alc_gpio_data_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long *valp = ucontrol->value.integer.value; + unsigned int val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DATA, 0x00); + + *valp = (val & mask) != 0; + return 0; +} +static int alc_gpio_data_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + signed int change; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long val = *ucontrol->value.integer.value; + unsigned int gpio_data = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_GPIO_DATA, + 0x00); + + /* Set/unset the masked GPIO bit(s) as needed */ + change = (val == 0 ? 0 : mask) != (gpio_data & mask); + if (val == 0) + gpio_data &= ~mask; + else + gpio_data |= mask; + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_GPIO_DATA, gpio_data); + + return change; +} +#define ALC_GPIO_DATA_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = alc_gpio_data_info, \ + .get = alc_gpio_data_get, \ + .put = alc_gpio_data_put, \ + .private_value = nid | (mask<<16) } +#endif /* CONFIG_SND_DEBUG */ + +/* A switch control to allow the enabling of the digital IO pins on the + * ALC260. This is incredibly simplistic; the intention of this control is + * to provide something in the test model allowing digital outputs to be + * identified if present. If models are found which can utilise these + * outputs a more complete mixer control can be devised for those models if + * necessary. + */ +#ifdef CONFIG_SND_DEBUG +#define alc_spdif_ctrl_info snd_ctl_boolean_mono_info + +static int alc_spdif_ctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long *valp = ucontrol->value.integer.value; + unsigned int val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, 0x00); + + *valp = (val & mask) != 0; + return 0; +} +static int alc_spdif_ctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + signed int change; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long val = *ucontrol->value.integer.value; + unsigned int ctrl_data = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_DIGI_CONVERT_1, + 0x00); + + /* Set/unset the masked control bit(s) as needed */ + change = (val == 0 ? 0 : mask) != (ctrl_data & mask); + if (val==0) + ctrl_data &= ~mask; + else + ctrl_data |= mask; + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, + ctrl_data); + + return change; +} +#define ALC_SPDIF_CTRL_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = alc_spdif_ctrl_info, \ + .get = alc_spdif_ctrl_get, \ + .put = alc_spdif_ctrl_put, \ + .private_value = nid | (mask<<16) } +#endif /* CONFIG_SND_DEBUG */ + +/* A switch control to allow the enabling EAPD digital outputs on the ALC26x. + * Again, this is only used in the ALC26x test models to help identify when + * the EAPD line must be asserted for features to work. + */ +#ifdef CONFIG_SND_DEBUG +#define alc_eapd_ctrl_info snd_ctl_boolean_mono_info + +static int alc_eapd_ctrl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long *valp = ucontrol->value.integer.value; + unsigned int val = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, 0x00); + + *valp = (val & mask) != 0; + return 0; +} + +static int alc_eapd_ctrl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int change; + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = kcontrol->private_value & 0xffff; + unsigned char mask = (kcontrol->private_value >> 16) & 0xff; + long val = *ucontrol->value.integer.value; + unsigned int ctrl_data = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_EAPD_BTLENABLE, + 0x00); + + /* Set/unset the masked control bit(s) as needed */ + change = (!val ? 0 : mask) != (ctrl_data & mask); + if (!val) + ctrl_data &= ~mask; + else + ctrl_data |= mask; + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE, + ctrl_data); + + return change; +} + +#define ALC_EAPD_CTRL_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = alc_eapd_ctrl_info, \ + .get = alc_eapd_ctrl_get, \ + .put = alc_eapd_ctrl_put, \ + .private_value = nid | (mask<<16) } +#endif /* CONFIG_SND_DEBUG */ + +/* + * set up from the preset table + */ +static void setup_preset(struct alc_spec *spec, + const struct alc_config_preset *preset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(preset->mixers) && preset->mixers[i]; i++) + spec->mixers[spec->num_mixers++] = preset->mixers[i]; + for (i = 0; i < ARRAY_SIZE(preset->init_verbs) && preset->init_verbs[i]; + i++) + spec->init_verbs[spec->num_init_verbs++] = + preset->init_verbs[i]; + + spec->channel_mode = preset->channel_mode; + spec->num_channel_mode = preset->num_channel_mode; + spec->need_dac_fix = preset->need_dac_fix; + + spec->multiout.max_channels = spec->channel_mode[0].channels; + + spec->multiout.num_dacs = preset->num_dacs; + spec->multiout.dac_nids = preset->dac_nids; + spec->multiout.dig_out_nid = preset->dig_out_nid; + spec->multiout.hp_nid = preset->hp_nid; + + spec->num_mux_defs = preset->num_mux_defs; + if (!spec->num_mux_defs) + spec->num_mux_defs = 1; + spec->input_mux = preset->input_mux; + + spec->num_adc_nids = preset->num_adc_nids; + spec->adc_nids = preset->adc_nids; + spec->capsrc_nids = preset->capsrc_nids; + spec->dig_in_nid = preset->dig_in_nid; + + spec->unsol_event = preset->unsol_event; + spec->init_hook = preset->init_hook; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = preset->loopbacks; +#endif +} + +/* Enable GPIO mask and set output */ +static struct hda_verb alc_gpio1_init_verbs[] = { + {0x01, AC_VERB_SET_GPIO_MASK, 0x01}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x01}, + { } +}; + +static struct hda_verb alc_gpio2_init_verbs[] = { + {0x01, AC_VERB_SET_GPIO_MASK, 0x02}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x02}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x02}, + { } +}; + +static struct hda_verb alc_gpio3_init_verbs[] = { + {0x01, AC_VERB_SET_GPIO_MASK, 0x03}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x03}, + { } +}; + +/* + * Fix hardware PLL issue + * On some codecs, the analog PLL gating control must be off while + * the default value is 1. + */ +static void alc_fix_pll(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int val; + + if (!spec->pll_nid) + return; + snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_COEF_INDEX, + spec->pll_coef_idx); + val = snd_hda_codec_read(codec, spec->pll_nid, 0, + AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_COEF_INDEX, + spec->pll_coef_idx); + snd_hda_codec_write(codec, spec->pll_nid, 0, AC_VERB_SET_PROC_COEF, + val & ~(1 << spec->pll_coef_bit)); +} + +static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid, + unsigned int coef_idx, unsigned int coef_bit) +{ + struct alc_spec *spec = codec->spec; + spec->pll_nid = nid; + spec->pll_coef_idx = coef_idx; + spec->pll_coef_bit = coef_bit; + alc_fix_pll(codec); +} + +static void alc_sku_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int present; + unsigned int hp_nid = spec->autocfg.hp_pins[0]; + unsigned int sp_nid = spec->autocfg.speaker_pins[0]; + + /* need to execute and sync at first */ + snd_hda_codec_read(codec, hp_nid, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, hp_nid, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & 0x80000000) != 0; + snd_hda_codec_write(codec, sp_nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + spec->jack_present ? 0 : PIN_OUT); +} + +#if 0 /* it's broken in some acses -- temporarily disabled */ +static void alc_mic_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int present; + unsigned int mic_nid = spec->autocfg.input_pins[AUTO_PIN_MIC]; + unsigned int fmic_nid = spec->autocfg.input_pins[AUTO_PIN_FRONT_MIC]; + unsigned int mix_nid = spec->capsrc_nids[0]; + unsigned int capsrc_idx_mic, capsrc_idx_fmic; + + capsrc_idx_mic = mic_nid - 0x18; + capsrc_idx_fmic = fmic_nid - 0x18; + present = snd_hda_codec_read(codec, mic_nid, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, mix_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (capsrc_idx_mic << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write(codec, mix_nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (capsrc_idx_fmic << 8) | (present ? 0x80 : 0)); + snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, capsrc_idx_fmic, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} +#else +#define alc_mic_automute(codec) /* NOP */ +#endif /* disabled */ + +/* unsolicited event for HP jack sensing */ +static void alc_sku_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if (codec->vendor_id == 0x10ec0880) + res >>= 28; + else + res >>= 26; + if (res == ALC880_HP_EVENT) + alc_sku_automute(codec); + + if (res == ALC880_MIC_EVENT) + alc_mic_automute(codec); +} + +static void alc_inithook(struct hda_codec *codec) +{ + alc_sku_automute(codec); + alc_mic_automute(codec); +} + +/* additional initialization for ALC888 variants */ +static void alc888_coef_init(struct hda_codec *codec) +{ + unsigned int tmp; + + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 0); + tmp = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, 0x20, 0, AC_VERB_SET_COEF_INDEX, 7); + if ((tmp & 0xf0) == 2) + /* alc888S-VC */ + snd_hda_codec_read(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, 0x830); + else + /* alc888-VB */ + snd_hda_codec_read(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, 0x3030); +} + +/* 32-bit subsystem ID for BIOS loading in HD Audio codec. + * 31 ~ 16 : Manufacture ID + * 15 ~ 8 : SKU ID + * 7 ~ 0 : Assembly ID + * port-A --> pin 39/41, port-E --> pin 14/15, port-D --> pin 35/36 + */ +static void alc_subsystem_id(struct hda_codec *codec, + unsigned int porta, unsigned int porte, + unsigned int portd) +{ + unsigned int ass, tmp, i; + unsigned nid; + struct alc_spec *spec = codec->spec; + + ass = codec->subsystem_id & 0xffff; + if ((ass != codec->bus->pci->subsystem_device) && (ass & 1)) + goto do_sku; + + /* + * 31~30 : port conetcivity + * 29~21 : reserve + * 20 : PCBEEP input + * 19~16 : Check sum (15:1) + * 15~1 : Custom + * 0 : override + */ + nid = 0x1d; + if (codec->vendor_id == 0x10ec0260) + nid = 0x17; + ass = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + if (!(ass & 1) && !(ass & 0x100000)) + return; + if ((ass >> 30) != 1) /* no physical connection */ + return; + + /* check sum */ + tmp = 0; + for (i = 1; i < 16; i++) { + if ((ass >> i) & 1) + tmp++; + } + if (((ass >> 16) & 0xf) != tmp) + return; +do_sku: + /* + * 0 : override + * 1 : Swap Jack + * 2 : 0 --> Desktop, 1 --> Laptop + * 3~5 : External Amplifier control + * 7~6 : Reserved + */ + tmp = (ass & 0x38) >> 3; /* external Amp control */ + switch (tmp) { + case 1: + snd_hda_sequence_write(codec, alc_gpio1_init_verbs); + break; + case 3: + snd_hda_sequence_write(codec, alc_gpio2_init_verbs); + break; + case 7: + snd_hda_sequence_write(codec, alc_gpio3_init_verbs); + break; + case 5: /* set EAPD output high */ + switch (codec->vendor_id) { + case 0x10ec0260: + snd_hda_codec_write(codec, 0x0f, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); + snd_hda_codec_write(codec, 0x10, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); + break; + case 0x10ec0262: + case 0x10ec0267: + case 0x10ec0268: + case 0x10ec0269: + case 0x10ec0272: + case 0x10ec0660: + case 0x10ec0662: + case 0x10ec0663: + case 0x10ec0862: + case 0x10ec0889: + snd_hda_codec_write(codec, 0x14, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); + snd_hda_codec_write(codec, 0x15, 0, + AC_VERB_SET_EAPD_BTLENABLE, 2); + break; + } + switch (codec->vendor_id) { + case 0x10ec0260: + snd_hda_codec_write(codec, 0x1a, 0, + AC_VERB_SET_COEF_INDEX, 7); + tmp = snd_hda_codec_read(codec, 0x1a, 0, + AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, 0x1a, 0, + AC_VERB_SET_COEF_INDEX, 7); + snd_hda_codec_write(codec, 0x1a, 0, + AC_VERB_SET_PROC_COEF, + tmp | 0x2010); + break; + case 0x10ec0262: + case 0x10ec0880: + case 0x10ec0882: + case 0x10ec0883: + case 0x10ec0885: + case 0x10ec0887: + case 0x10ec0889: + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 7); + tmp = snd_hda_codec_read(codec, 0x20, 0, + AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 7); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, + tmp | 0x2010); + break; + case 0x10ec0888: + /*alc888_coef_init(codec);*/ /* called in alc_init() */ + break; + case 0x10ec0267: + case 0x10ec0268: + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 7); + tmp = snd_hda_codec_read(codec, 0x20, 0, + AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 7); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, + tmp | 0x3000); + break; + } + default: + break; + } + + /* is laptop or Desktop and enable the function "Mute internal speaker + * when the external headphone out jack is plugged" + */ + if (!(ass & 0x8000)) + return; + /* + * 10~8 : Jack location + * 12~11: Headphone out -> 00: PortA, 01: PortE, 02: PortD, 03: Resvered + * 14~13: Resvered + * 15 : 1 --> enable the function "Mute internal speaker + * when the external headphone out jack is plugged" + */ + if (!spec->autocfg.speaker_pins[0]) { + if (spec->autocfg.line_out_pins[0]) + spec->autocfg.speaker_pins[0] = + spec->autocfg.line_out_pins[0]; + else + return; + } + + if (!spec->autocfg.hp_pins[0]) { + tmp = (ass >> 11) & 0x3; /* HP to chassis */ + if (tmp == 0) + spec->autocfg.hp_pins[0] = porta; + else if (tmp == 1) + spec->autocfg.hp_pins[0] = porte; + else if (tmp == 2) + spec->autocfg.hp_pins[0] = portd; + else + return; + } + if (spec->autocfg.hp_pins[0]) + snd_hda_codec_write(codec, spec->autocfg.hp_pins[0], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + AC_USRSP_EN | ALC880_HP_EVENT); + +#if 0 /* it's broken in some acses -- temporarily disabled */ + if (spec->autocfg.input_pins[AUTO_PIN_MIC] && + spec->autocfg.input_pins[AUTO_PIN_FRONT_MIC]) + snd_hda_codec_write(codec, + spec->autocfg.input_pins[AUTO_PIN_MIC], 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + AC_USRSP_EN | ALC880_MIC_EVENT); +#endif /* disabled */ + + spec->unsol_event = alc_sku_unsol_event; +} + +/* + * Fix-up pin default configurations + */ + +struct alc_pincfg { + hda_nid_t nid; + u32 val; +}; + +static void alc_fix_pincfg(struct hda_codec *codec, + const struct snd_pci_quirk *quirk, + const struct alc_pincfg **pinfix) +{ + const struct alc_pincfg *cfg; + + quirk = snd_pci_quirk_lookup(codec->bus->pci, quirk); + if (!quirk) + return; + + cfg = pinfix[quirk->value]; + for (; cfg->nid; cfg++) { + int i; + u32 val = cfg->val; + for (i = 0; i < 4; i++) { + snd_hda_codec_write(codec, cfg->nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_0 + i, + val & 0xff); + val >>= 8; + } + } +} + +/* + * ALC880 3-stack model + * + * DAC: Front = 0x02 (0x0c), Surr = 0x05 (0x0f), CLFE = 0x04 (0x0e) + * Pin assignment: Front = 0x14, Line-In/Surr = 0x1a, Mic/CLFE = 0x18, + * F-Mic = 0x1b, HP = 0x19 + */ + +static hda_nid_t alc880_dac_nids[4] = { + /* front, rear, clfe, rear_surr */ + 0x02, 0x05, 0x04, 0x03 +}; + +static hda_nid_t alc880_adc_nids[3] = { + /* ADC0-2 */ + 0x07, 0x08, 0x09, +}; + +/* The datasheet says the node 0x07 is connected from inputs, + * but it shows zero connection in the real implementation on some devices. + * Note: this is a 915GAV bug, fixed on 915GLV + */ +static hda_nid_t alc880_adc_nids_alt[2] = { + /* ADC1-2 */ + 0x08, 0x09, +}; + +#define ALC880_DIGOUT_NID 0x06 +#define ALC880_DIGIN_NID 0x0a + +static struct hda_input_mux alc880_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x3 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +/* channel source setting (2/6 channel selection for 3-stack) */ +/* 2ch mode */ +static struct hda_verb alc880_threestack_ch2_init[] = { + /* set line-in to input, mute it */ + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + /* set mic-in to input vref 80%, mute it */ + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* 6ch mode */ +static struct hda_verb alc880_threestack_ch6_init[] = { + /* set line-in to output, unmute it */ + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + /* set mic-in to output, unmute it */ + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } /* end */ +}; + +static struct hda_channel_mode alc880_threestack_modes[2] = { + { 2, alc880_threestack_ch2_init }, + { 6, alc880_threestack_ch6_init }, +}; + +static struct snd_kcontrol_new alc880_three_stack_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0f, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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_MUTE("Headphone Playback Switch", 0x19, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc880_capture_mixer[] = { + 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.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 3, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +/* capture mixer elements (in case NID 0x07 not available) */ +static struct snd_kcontrol_new alc880_capture_alt_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + + + +/* + * ALC880 5-stack model + * + * DAC: Front = 0x02 (0x0c), Surr = 0x05 (0x0f), CLFE = 0x04 (0x0d), + * Side = 0x02 (0xd) + * Pin assignment: Front = 0x14, Surr = 0x17, CLFE = 0x16 + * Line-In/Side = 0x1a, Mic = 0x18, F-Mic = 0x1b, HP = 0x19 + */ + +/* additional mixers to alc880_three_stack_mixer */ +static struct snd_kcontrol_new alc880_five_stack_mixer[] = { + HDA_CODEC_VOLUME("Side Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0d, 2, HDA_INPUT), + { } /* end */ +}; + +/* channel source setting (6/8 channel selection for 5-stack) */ +/* 6ch mode */ +static struct hda_verb alc880_fivestack_ch6_init[] = { + /* set line-in to input, mute it */ + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* 8ch mode */ +static struct hda_verb alc880_fivestack_ch8_init[] = { + /* set line-in to output, unmute it */ + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } /* end */ +}; + +static struct hda_channel_mode alc880_fivestack_modes[2] = { + { 6, alc880_fivestack_ch6_init }, + { 8, alc880_fivestack_ch8_init }, +}; + + +/* + * ALC880 6-stack model + * + * DAC: Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e), + * Side = 0x05 (0x0f) + * Pin assignment: Front = 0x14, Surr = 0x15, CLFE = 0x16, Side = 0x17, + * Mic = 0x18, F-Mic = 0x19, Line = 0x1a, HP = 0x1b + */ + +static hda_nid_t alc880_6st_dac_nids[4] = { + /* front, rear, clfe, rear_surr */ + 0x02, 0x03, 0x04, 0x05 +}; + +static struct hda_input_mux alc880_6stack_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +/* fixed 8-channels */ +static struct hda_channel_mode alc880_sixstack_modes[1] = { + { 8, NULL }, +}; + +static struct snd_kcontrol_new alc880_six_stack_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + 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), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + + +/* + * ALC880 W810 model + * + * 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 hda_nid_t alc880_w810_dac_nids[3] = { + /* front, rear/surround, clfe */ + 0x02, 0x03, 0x04 +}; + +/* fixed 6 channels */ +static struct hda_channel_mode alc880_w810_modes[1] = { + { 6, NULL } +}; + +/* Pin assignment: Front = 0x14, Surr = 0x15, CLFE = 0x16, HP = 0x1b */ +static struct snd_kcontrol_new alc880_w810_base_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + { } /* end */ +}; + + +/* + * Z710V model + * + * DAC: Front = 0x02 (0x0c), HP = 0x03 (0x0d) + * Pin assignment: Front = 0x14, HP = 0x15, Mic = 0x18, Mic2 = 0x19(?), + * Line = 0x1a + */ + +static hda_nid_t alc880_z71v_dac_nids[1] = { + 0x02 +}; +#define ALC880_Z71V_HP_DAC 0x03 + +/* fixed 2 channels */ +static struct hda_channel_mode alc880_2_jack_modes[1] = { + { 2, NULL } +}; + +static struct snd_kcontrol_new alc880_z71v_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + + +/* + * ALC880 F1734 model + * + * DAC: HP = 0x02 (0x0c), Front = 0x03 (0x0d) + * Pin assignment: HP = 0x14, Front = 0x15, Mic = 0x18 + */ + +static hda_nid_t alc880_f1734_dac_nids[1] = { + 0x03 +}; +#define ALC880_F1734_HP_DAC 0x02 + +static struct snd_kcontrol_new alc880_f1734_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct hda_input_mux alc880_f1734_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x1 }, + { "CD", 0x4 }, + }, +}; + + +/* + * ALC880 ASUS model + * + * DAC: HP/Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e) + * Pin assignment: HP/Front = 0x14, Surr = 0x15, CLFE = 0x16, + * Mic = 0x18, Line = 0x1a + */ + +#define alc880_asus_dac_nids alc880_w810_dac_nids /* identical with w810 */ +#define alc880_asus_modes alc880_threestack_modes /* 2/6 channel mode */ + +static struct snd_kcontrol_new alc880_asus_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +/* + * ALC880 ASUS W1V model + * + * DAC: HP/Front = 0x02 (0x0c), Surr = 0x03 (0x0d), CLFE = 0x04 (0x0e) + * Pin assignment: HP/Front = 0x14, Surr = 0x15, CLFE = 0x16, + * Mic = 0x18, Line = 0x1a, Line2 = 0x1b + */ + +/* additional mixers to alc880_asus_mixer */ +static struct snd_kcontrol_new alc880_asus_w1v_mixer[] = { + HDA_CODEC_VOLUME("Line2 Playback Volume", 0x0b, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Line2 Playback Switch", 0x0b, 0x03, HDA_INPUT), + { } /* end */ +}; + +/* additional mixers to alc880_asus_mixer */ +static struct snd_kcontrol_new alc880_pcbeep_mixer[] = { + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + { } /* end */ +}; + +/* TCL S700 */ +static struct snd_kcontrol_new alc880_tcl_s700_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 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("Mic Playback Volume", 0x0B, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0B, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +/* Uniwill */ +static struct snd_kcontrol_new alc880_uniwill_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc880_fujitsu_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc880_uniwill_p53_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + +/* + * virtual master controls + */ + +/* + * slave controls for virtual master + */ +static const char *alc_slave_vols[] = { + "Front Playback Volume", + "Surround Playback Volume", + "Center Playback Volume", + "LFE Playback Volume", + "Side Playback Volume", + "Headphone Playback Volume", + "Speaker Playback Volume", + "Mono Playback Volume", + "Line-Out Playback Volume", + NULL, +}; + +static const char *alc_slave_sws[] = { + "Front Playback Switch", + "Surround Playback Switch", + "Center Playback Switch", + "LFE Playback Switch", + "Side Playback Switch", + "Headphone Playback Switch", + "Speaker Playback Switch", + "Mono Playback Switch", + "IEC958 Playback Switch", + NULL, +}; + +/* + * build control elements + */ +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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + if (spec->dig_in_nid) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + + /* if we have no master control, let's create it */ + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { + unsigned int vmaster_tlv[4]; + snd_hda_set_vmaster_tlv(codec, spec->vmaster_nid, + HDA_OUTPUT, vmaster_tlv); + err = snd_hda_add_vmaster(codec, "Master Playback Volume", + vmaster_tlv, alc_slave_vols); + if (err < 0) + return err; + } + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { + err = snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, alc_slave_sws); + if (err < 0) + return err; + } + + return 0; +} + + +/* + * initialize the codec volumes, etc + */ + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc880_volume_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for front + * panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + + /* + * Set up output mixers (0x0c - 0x0f) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + { } +}; + +/* + * 3-stack pin configuration: + * front = 0x14, mic/clfe = 0x18, HP = 0x19, line/surr = 0x1a, f-mic = 0x1b + */ +static struct hda_verb alc880_pin_3stack_init_verbs[] = { + /* + * preset connection lists of input pins + * 0 = front, 1 = rear_surr, 2 = CLFE, 3 = surround + */ + {0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x03}, /* line/surround */ + + /* + * Set pin mode and muting + */ + /* set front pin widgets 0x14 for output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Mic1 (rear panel) pin widget for input and vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mic2 (as headphone out) for HP output */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Line In pin widget for input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line2 (as front mic) pin widget for input and vref at 80% */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* + * 5-stack pin configuration: + * front = 0x14, surround = 0x17, clfe = 0x16, mic = 0x18, HP = 0x19, + * line-in/side = 0x1a, f-mic = 0x1b + */ +static struct hda_verb alc880_pin_5stack_init_verbs[] = { + /* + * preset connection lists of input pins + * 0 = front, 1 = rear_surr, 2 = CLFE, 3 = surround + */ + {0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/side */ + + /* + * Set pin mode and muting + */ + /* set pin widgets 0x14-0x17 for output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* unmute pins for output (no gain on this amp) */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mic1 (rear panel) pin widget for input and vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mic2 (as headphone out) for HP output */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Line In pin widget for input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line2 (as front mic) pin widget for input and vref at 80% */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* + * W810 pin configuration: + * front = 0x14, surround = 0x15, clfe = 0x16, HP = 0x1b + */ +static struct hda_verb alc880_pin_w810_init_verbs[] = { + /* hphone/speaker input selector: front DAC */ + {0x13, AC_VERB_SET_CONNECT_SEL, 0x0}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + { } +}; + +/* + * Z71V pin configuration: + * Speaker-out = 0x14, HP = 0x15, Mic = 0x18, Line-in = 0x1a, Mic2 = 0x1b (?) + */ +static struct hda_verb alc880_pin_z71v_init_verbs[] = { + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* + * 6-stack pin configuration: + * front = 0x14, surr = 0x15, clfe = 0x16, side = 0x17, mic = 0x18, + * f-mic = 0x19, line = 0x1a, HP = 0x1b + */ +static struct hda_verb alc880_pin_6stack_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* + * Uniwill pin configuration: + * HP = 0x14, InternalSpeaker = 0x15, mic = 0x18, internal mic = 0x19, + * line = 0x1a + */ +static struct hda_verb alc880_uniwill_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, */ + /* {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + + { } +}; + +/* +* Uniwill P53 +* HP = 0x14, InternalSpeaker = 0x15, mic = 0x19, + */ +static struct hda_verb alc880_uniwill_p53_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_DCVOL_EVENT}, + + { } +}; + +static struct hda_verb alc880_beep_init_verbs[] = { + { 0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5) }, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc880_uniwill_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +/* auto-toggle front mic */ +static void alc880_uniwill_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, HDA_AMP_MUTE, bits); +} + +static void alc880_uniwill_automute(struct hda_codec *codec) +{ + alc880_uniwill_hp_automute(codec); + alc880_uniwill_mic_automute(codec); +} + +static void alc880_uniwill_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + switch (res >> 28) { + case ALC880_HP_EVENT: + alc880_uniwill_hp_automute(codec); + break; + case ALC880_MIC_EVENT: + alc880_uniwill_mic_automute(codec); + break; + } +} + +static void alc880_uniwill_p53_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, HDA_AMP_MUTE, bits); +} + +static void alc880_uniwill_p53_dcvol_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x21, 0, + AC_VERB_GET_VOLUME_KNOB_CONTROL, 0); + present &= HDA_AMP_VOLMASK; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK, present); + snd_hda_codec_amp_stereo(codec, 0x0d, HDA_OUTPUT, 0, + HDA_AMP_VOLMASK, present); +} + +static void alc880_uniwill_p53_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + if ((res >> 28) == ALC880_HP_EVENT) + alc880_uniwill_p53_hp_automute(codec); + if ((res >> 28) == ALC880_DCVOL_EVENT) + alc880_uniwill_p53_dcvol_automute(codec); +} + +/* + * F1734 pin configuration: + * HP = 0x14, speaker-out = 0x15, mic = 0x18 + */ +static struct hda_verb alc880_pin_f1734_init_verbs[] = { + {0x07, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, + {0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x12, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_DCVOL_EVENT}, + + { } +}; + +/* + * ASUS pin configuration: + * HP/front = 0x14, surr = 0x15, clfe = 0x16, mic = 0x18, line = 0x1a + */ +static struct hda_verb alc880_pin_asus_init_verbs[] = { + {0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, + {0x11, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x12, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* Enable GPIO mask and set output */ +#define alc880_gpio1_init_verbs alc_gpio1_init_verbs +#define alc880_gpio2_init_verbs alc_gpio2_init_verbs + +/* Clevo m520g init */ +static struct hda_verb alc880_pin_clevo_init_verbs[] = { + /* headphone output */ + {0x11, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* line-out */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Line-in */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* CD */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Mic1 (rear panel) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Mic2 (front panel) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* headphone */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* change to EAPD mode */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3060}, + + { } +}; + +static struct hda_verb alc880_pin_tcl_S700_init_verbs[] = { + /* change to EAPD mode */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3060}, + + /* Headphone output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Front output*/ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Line In pin widget for input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Mic1 (rear panel) pin widget for input and vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + + /* change to EAPD mode */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3070}, + + { } +}; + +/* + * LG m1 express dual + * + * Pin assignment: + * Rear Line-In/Out (blue): 0x14 + * Build-in Mic-In: 0x15 + * Speaker-out: 0x17 + * HP-Out (green): 0x1b + * Mic-In/Out (red): 0x19 + * SPDIF-Out: 0x1e + */ + +/* To make 5.1 output working (green=Front, blue=Surr, red=CLFE) */ +static hda_nid_t alc880_lg_dac_nids[3] = { + 0x05, 0x02, 0x03 +}; + +/* seems analog CD is not working */ +static struct hda_input_mux alc880_lg_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x1 }, + { "Line", 0x5 }, + { "Internal Mic", 0x6 }, + }, +}; + +/* 2,4,6 channel modes */ +static struct hda_verb alc880_lg_ch2_init[] = { + /* set line-in and mic-in to input */ + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { } +}; + +static struct hda_verb alc880_lg_ch4_init[] = { + /* set line-in to out and mic-in to input */ + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { } +}; + +static struct hda_verb alc880_lg_ch6_init[] = { + /* set line-in and mic-in to output */ + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, + { } +}; + +static struct hda_channel_mode alc880_lg_ch_modes[3] = { + { 2, alc880_lg_ch2_init }, + { 4, alc880_lg_ch4_init }, + { 6, alc880_lg_ch6_init }, +}; + +static struct snd_kcontrol_new alc880_lg_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x07, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x07, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct hda_verb alc880_lg_init_verbs[] = { + /* set capture source to mic-in */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* mute all amp mixer inputs */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* line-in to input */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* built-in mic */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* speaker-out */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* mic-in to input */ + {0x11, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* HP-out */ + {0x13, AC_VERB_SET_CONNECT_SEL, 0x03}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* jack sense */ + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | 0x1}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc880_lg_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc880_lg_unsol_event(struct hda_codec *codec, unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + if ((res >> 28) == 0x01) + alc880_lg_automute(codec); +} + +/* + * LG LW20 + * + * Pin assignment: + * Speaker-out: 0x14 + * Mic-In: 0x18 + * Built-in Mic-In: 0x19 + * Line-In: 0x1b + * HP-Out: 0x1a + * SPDIF-Out: 0x1e + */ + +static struct hda_input_mux alc880_lg_lw_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x1 }, + { "Line In", 0x2 }, + }, +}; + +#define alc880_lg_lw_modes alc880_threestack_modes + +static struct snd_kcontrol_new alc880_lg_lw_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0f, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, 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("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct hda_verb alc880_lg_lw_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x10, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x12, AC_VERB_SET_CONNECT_SEL, 0x03}, /* line/surround */ + + /* set capture source to mic-in */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* speaker-out */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* HP-out */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* mic-in to input */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* built-in mic */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* jack sense */ + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | 0x1}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc880_lg_lw_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc880_lg_lw_unsol_event(struct hda_codec *codec, unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + if ((res >> 28) == 0x01) + alc880_lg_lw_automute(codec); +} + +static struct snd_kcontrol_new alc880_medion_rim_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, 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("Internal Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Internal Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct hda_input_mux alc880_medion_rim_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x1 }, + }, +}; + +static struct hda_verb alc880_medion_rim_init_verbs[] = { + {0x13, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mic1 (rear panel) pin widget for input and vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Mic2 (as headphone out) for HP output */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Internal Speaker */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3060}, + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc880_medion_rim_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + if (present) + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 0); + else + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, 2); +} + +static void alc880_medion_rim_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 28 bit! + */ + if ((res >> 28) == ALC880_HP_EVENT) + alc880_medion_rim_automute(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list alc880_loopbacks[] = { + { 0x0b, HDA_INPUT, 0 }, + { 0x0b, HDA_INPUT, 1 }, + { 0x0b, HDA_INPUT, 2 }, + { 0x0b, HDA_INPUT, 3 }, + { 0x0b, HDA_INPUT, 4 }, + { } /* end */ +}; + +static struct hda_amp_list alc880_lg_loopbacks[] = { + { 0x0b, HDA_INPUT, 1 }, + { 0x0b, HDA_INPUT, 6 }, + { 0x0b, HDA_INPUT, 7 }, + { } /* end */ +}; +#endif + +/* + * Common callbacks + */ + +static int alc_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int i; + + alc_fix_pll(codec); + if (codec->vendor_id == 0x10ec0888) + alc888_coef_init(codec); + + for (i = 0; i < spec->num_init_verbs; i++) + snd_hda_sequence_write(codec, spec->init_verbs[i]); + + if (spec->init_hook) + spec->init_hook(codec); + + return 0; +} + +static void alc_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct alc_spec *spec = codec->spec; + + if (spec->unsol_event) + spec->unsol_event(codec, res); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int alc_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct alc_spec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +#endif + +/* + * Analog playback callbacks + */ +static int alc880_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int alc880_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *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, + struct snd_pcm_substream *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, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int alc880_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static int alc880_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +/* + * Analog capture + */ +static int alc880_alt_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number + 1], + stream_tag, 0, format); + return 0; +} + +static int alc880_alt_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct alc_spec *spec = codec->spec; + + snd_hda_codec_cleanup_stream(codec, + spec->adc_nids[substream->number + 1]); + return 0; +} + + +/* + */ +static struct hda_pcm_stream alc880_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + /* NID is set in alc_build_pcms */ + .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 = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ +}; + +static struct hda_pcm_stream alc880_pcm_analog_alt_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ +}; + +static struct hda_pcm_stream alc880_pcm_analog_alt_capture = { + .substreams = 2, /* can be overridden */ + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ + .ops = { + .prepare = alc880_alt_capture_pcm_prepare, + .cleanup = alc880_alt_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, + .prepare = alc880_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream alc880_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in alc_build_pcms */ +}; + +/* Used by alc_build_pcms to flag that a PCM has no playback stream */ +static struct hda_pcm_stream alc_pcm_null_stream = { + .substreams = 0, + .channels_min = 0, + .channels_max = 0, +}; + +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; + if (spec->stream_analog_playback) { + if (snd_BUG_ON(!spec->multiout.dac_nids)) + return -EINVAL; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = *(spec->stream_analog_playback); + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->multiout.dac_nids[0]; + } + if (spec->stream_analog_capture) { + if (snd_BUG_ON(!spec->adc_nids)) + return -EINVAL; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture); + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0]; + } + + if (spec->channel_mode) { + 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; + } + } + } + + /* SPDIF for stream index #1 */ + if (spec->multiout.dig_out_nid || spec->dig_in_nid) { + codec->num_pcms = 2; + info = spec->pcm_rec + 1; + info->name = spec->stream_name_digital; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->multiout.dig_out_nid && + spec->stream_digital_playback) { + 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 && + spec->stream_digital_capture) { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_digital_capture); + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid; + } + /* FIXME: do we need this for all Realtek codec models? */ + codec->spdif_status_reset = 1; + } + + /* If the use of more than one ADC is requested for the current + * model, configure a second analog capture-only PCM. + */ + /* Additional Analaog capture for index #2 */ + if ((spec->alt_dac_nid && spec->stream_analog_alt_playback) || + (spec->num_adc_nids > 1 && spec->stream_analog_alt_capture)) { + codec->num_pcms = 3; + info = spec->pcm_rec + 2; + info->name = spec->stream_name_analog; + if (spec->alt_dac_nid) { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + *spec->stream_analog_alt_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = + spec->alt_dac_nid; + } else { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = + alc_pcm_null_stream; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = 0; + } + if (spec->num_adc_nids > 1) { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + *spec->stream_analog_alt_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = + spec->adc_nids[1]; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = + spec->num_adc_nids - 1; + } else { + info->stream[SNDRV_PCM_STREAM_CAPTURE] = + alc_pcm_null_stream; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = 0; + } + } + + return 0; +} + +static void alc_free(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int i; + + if (!spec) + return; + + if (spec->kctl_alloc) { + for (i = 0; i < spec->num_kctl_used; i++) + kfree(spec->kctl_alloc[i].name); + kfree(spec->kctl_alloc); + } + kfree(spec); + codec->spec = NULL; /* to be sure */ +} + +#ifdef SND_HDA_NEEDS_RESUME +static void store_pin_configs(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t nid, end_nid; + + end_nid = codec->start_nid + codec->num_nodes; + for (nid = codec->start_nid; nid < end_nid; nid++) { + unsigned int wid_caps = get_wcaps(codec, nid); + unsigned int wid_type = + (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wid_type != AC_WID_PIN) + continue; + if (spec->num_pins >= ARRAY_SIZE(spec->pin_nids)) + break; + spec->pin_nids[spec->num_pins] = nid; + spec->pin_cfgs[spec->num_pins] = + snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + spec->num_pins++; + } +} + +static void resume_pin_configs(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->num_pins; i++) { + hda_nid_t pin_nid = spec->pin_nids[i]; + unsigned int pin_config = spec->pin_cfgs[i]; + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_0, + pin_config & 0x000000ff); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_1, + (pin_config & 0x0000ff00) >> 8); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_2, + (pin_config & 0x00ff0000) >> 16); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + pin_config >> 24); + } +} + +static int alc_resume(struct hda_codec *codec) +{ + resume_pin_configs(codec); + codec->patch_ops.init(codec); + snd_hda_codec_resume_amp(codec); + snd_hda_codec_resume_cache(codec); + return 0; +} +#else +#define store_pin_configs(codec) +#endif + +/* + */ +static struct hda_codec_ops alc_patch_ops = { + .build_controls = alc_build_controls, + .build_pcms = alc_build_pcms, + .init = alc_init, + .free = alc_free, + .unsol_event = alc_unsol_event, +#ifdef SND_HDA_NEEDS_RESUME + .resume = alc_resume, +#endif +#ifdef CONFIG_SND_HDA_POWER_SAVE + .check_power_status = alc_check_power_status, +#endif +}; + + +/* + * Test configuration for debugging + * + * Almost all inputs/outputs are enabled. I/O pins can be configured via + * enum controls. + */ +#ifdef CONFIG_SND_DEBUG +static hda_nid_t alc880_test_dac_nids[4] = { + 0x02, 0x03, 0x04, 0x05 +}; + +static struct hda_input_mux alc880_test_capture_source = { + .num_items = 7, + .items = { + { "In-1", 0x0 }, + { "In-2", 0x1 }, + { "In-3", 0x2 }, + { "In-4", 0x3 }, + { "CD", 0x4 }, + { "Front", 0x5 }, + { "Surround", 0x6 }, + }, +}; + +static struct hda_channel_mode alc880_test_modes[4] = { + { 2, NULL }, + { 4, NULL }, + { 6, NULL }, + { 8, NULL }, +}; + +static int alc_test_pin_ctl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { + "N/A", "Line Out", "HP Out", + "In Hi-Z", "In 50%", "In Grd", "In 80%", "In 100%" + }; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 8; + if (uinfo->value.enumerated.item >= 8) + uinfo->value.enumerated.item = 7; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int alc_test_pin_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = (hda_nid_t)kcontrol->private_value; + unsigned int pin_ctl, item = 0; + + pin_ctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + if (pin_ctl & AC_PINCTL_OUT_EN) { + if (pin_ctl & AC_PINCTL_HP_EN) + item = 2; + else + item = 1; + } else if (pin_ctl & AC_PINCTL_IN_EN) { + switch (pin_ctl & AC_PINCTL_VREFEN) { + case AC_PINCTL_VREF_HIZ: item = 3; break; + case AC_PINCTL_VREF_50: item = 4; break; + case AC_PINCTL_VREF_GRD: item = 5; break; + case AC_PINCTL_VREF_80: item = 6; break; + case AC_PINCTL_VREF_100: item = 7; break; + } + } + ucontrol->value.enumerated.item[0] = item; + return 0; +} + +static int alc_test_pin_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = (hda_nid_t)kcontrol->private_value; + static unsigned int ctls[] = { + 0, AC_PINCTL_OUT_EN, AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_HIZ, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_50, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_GRD, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_80, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_100, + }; + unsigned int old_ctl, new_ctl; + + old_ctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + new_ctl = ctls[ucontrol->value.enumerated.item[0]]; + if (old_ctl != new_ctl) { + int val; + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + new_ctl); + val = ucontrol->value.enumerated.item[0] >= 3 ? + HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, nid, HDA_OUTPUT, 0, + HDA_AMP_MUTE, val); + return 1; + } + return 0; +} + +static int alc_test_pin_src_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + static char *texts[] = { + "Front", "Surround", "CLFE", "Side" + }; + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 4; + if (uinfo->value.enumerated.item >= 4) + uinfo->value.enumerated.item = 3; + strcpy(uinfo->value.enumerated.name, texts[uinfo->value.enumerated.item]); + return 0; +} + +static int alc_test_pin_src_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = (hda_nid_t)kcontrol->private_value; + unsigned int sel; + + sel = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0); + ucontrol->value.enumerated.item[0] = sel & 3; + return 0; +} + +static int alc_test_pin_src_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = (hda_nid_t)kcontrol->private_value; + unsigned int sel; + + sel = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_SEL, 0) & 3; + if (ucontrol->value.enumerated.item[0] != sel) { + sel = ucontrol->value.enumerated.item[0] & 3; + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_CONNECT_SEL, sel); + return 1; + } + return 0; +} + +#define PIN_CTL_TEST(xname,nid) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = alc_test_pin_ctl_info, \ + .get = alc_test_pin_ctl_get, \ + .put = alc_test_pin_ctl_put, \ + .private_value = nid \ + } + +#define PIN_SRC_TEST(xname,nid) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = alc_test_pin_src_info, \ + .get = alc_test_pin_src_get, \ + .put = alc_test_pin_src_put, \ + .private_value = nid \ + } + +static struct snd_kcontrol_new alc880_test_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CLFE Playback Volume", 0x0e, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_BIND_MUTE("CLFE Playback Switch", 0x0e, 2, HDA_INPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + PIN_CTL_TEST("Front Pin Mode", 0x14), + PIN_CTL_TEST("Surround Pin Mode", 0x15), + PIN_CTL_TEST("CLFE Pin Mode", 0x16), + PIN_CTL_TEST("Side Pin Mode", 0x17), + PIN_CTL_TEST("In-1 Pin Mode", 0x18), + PIN_CTL_TEST("In-2 Pin Mode", 0x19), + PIN_CTL_TEST("In-3 Pin Mode", 0x1a), + PIN_CTL_TEST("In-4 Pin Mode", 0x1b), + PIN_SRC_TEST("In-1 Pin Source", 0x18), + PIN_SRC_TEST("In-2 Pin Source", 0x19), + PIN_SRC_TEST("In-3 Pin Source", 0x1a), + PIN_SRC_TEST("In-4 Pin Source", 0x1b), + HDA_CODEC_VOLUME("In-1 Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("In-1 Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("In-2 Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("In-2 Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("In-3 Playback Volume", 0x0b, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("In-3 Playback Switch", 0x0b, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("In-4 Playback Volume", 0x0b, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("In-4 Playback Switch", 0x0b, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x4, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct hda_verb alc880_test_init_verbs[] = { + /* Unmute inputs of 0x0c - 0x0f */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* Vol output for 0x0c-0x0f */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* Set output pins 0x14-0x17 */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Unmute output pins 0x14-0x17 */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Set input pins 0x18-0x1c */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Mute input pins 0x18-0x1b */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* ADC set up */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Analog input/passthru */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + { } +}; +#endif + +/* + */ + +static const char *alc880_models[ALC880_MODEL_LAST] = { + [ALC880_3ST] = "3stack", + [ALC880_TCL_S700] = "tcl", + [ALC880_3ST_DIG] = "3stack-digout", + [ALC880_CLEVO] = "clevo", + [ALC880_5ST] = "5stack", + [ALC880_5ST_DIG] = "5stack-digout", + [ALC880_W810] = "w810", + [ALC880_Z71V] = "z71v", + [ALC880_6ST] = "6stack", + [ALC880_6ST_DIG] = "6stack-digout", + [ALC880_ASUS] = "asus", + [ALC880_ASUS_W1V] = "asus-w1v", + [ALC880_ASUS_DIG] = "asus-dig", + [ALC880_ASUS_DIG2] = "asus-dig2", + [ALC880_UNIWILL_DIG] = "uniwill", + [ALC880_UNIWILL_P53] = "uniwill-p53", + [ALC880_FUJITSU] = "fujitsu", + [ALC880_F1734] = "F1734", + [ALC880_LG] = "lg", + [ALC880_LG_LW] = "lg-lw", + [ALC880_MEDION_RIM] = "medion", +#ifdef CONFIG_SND_DEBUG + [ALC880_TEST] = "test", +#endif + [ALC880_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc880_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x0f69, "Coeus G610P", ALC880_W810), + SND_PCI_QUIRK(0x1019, 0xa880, "ECS", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1019, 0xa884, "Acer APFV", ALC880_6ST), + SND_PCI_QUIRK(0x1025, 0x0070, "ULI", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0077, "ULI", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0078, "ULI", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0x0087, "ULI", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1025, 0xe309, "ULI", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x1025, 0xe310, "ULI", ALC880_3ST), + SND_PCI_QUIRK(0x1039, 0x1234, NULL, ALC880_6ST_DIG), + SND_PCI_QUIRK(0x103c, 0x2a09, "HP", ALC880_5ST), + SND_PCI_QUIRK(0x1043, 0x10b3, "ASUS W1V", ALC880_ASUS_W1V), + SND_PCI_QUIRK(0x1043, 0x10c2, "ASUS W6A", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x10c3, "ASUS Wxx", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x1113, "ASUS", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x1123, "ASUS", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x1173, "ASUS", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x1964, "ASUS Z71V", ALC880_Z71V), + /* SND_PCI_QUIRK(0x1043, 0x1964, "ASUS", ALC880_ASUS_DIG), */ + SND_PCI_QUIRK(0x1043, 0x1973, "ASUS", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x814e, "ASUS P5GD1 w/SPDIF", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1043, 0x8181, "ASUS P4GPL", ALC880_ASUS_DIG), + SND_PCI_QUIRK(0x1043, 0x8196, "ASUS P5GD1", ALC880_6ST), + SND_PCI_QUIRK(0x1043, 0x81b4, "ASUS", ALC880_6ST), + SND_PCI_QUIRK(0x1043, 0, "ASUS", ALC880_ASUS), /* default ASUS */ + SND_PCI_QUIRK(0x104d, 0x81a0, "Sony", ALC880_3ST), + SND_PCI_QUIRK(0x104d, 0x81d6, "Sony", ALC880_3ST), + SND_PCI_QUIRK(0x107b, 0x3032, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x107b, 0x3033, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x107b, 0x4039, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x1297, 0xc790, "Shuttle ST20G5", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1458, 0xa102, "Gigabyte K8", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x1150, "MSI", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1509, 0x925d, "FIC P4M", ALC880_6ST_DIG), + SND_PCI_QUIRK(0x1558, 0x0520, "Clevo m520G", ALC880_CLEVO), + SND_PCI_QUIRK(0x1558, 0x0660, "Clevo m655n", ALC880_CLEVO), + SND_PCI_QUIRK(0x1558, 0x5401, "ASUS", ALC880_ASUS_DIG2), + SND_PCI_QUIRK(0x1565, 0x8202, "Biostar", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_UNIWILL_DIG), + SND_PCI_QUIRK(0x1584, 0x9054, "Uniwlll", ALC880_F1734), + SND_PCI_QUIRK(0x1584, 0x9070, "Uniwill", ALC880_UNIWILL), + SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_UNIWILL_P53), + SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_W810), + SND_PCI_QUIRK(0x161f, 0x205d, "Medion Rim 2150", ALC880_MEDION_RIM), + SND_PCI_QUIRK(0x1695, 0x400d, "EPoX", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1695, 0x4012, "EPox EP-5LDA", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1734, 0x107c, "FSC F1734", ALC880_F1734), + SND_PCI_QUIRK(0x1734, 0x1094, "FSC Amilo M1451G", ALC880_FUJITSU), + SND_PCI_QUIRK(0x1734, 0x10ac, "FSC", ALC880_UNIWILL), + SND_PCI_QUIRK(0x1734, 0x10b0, "Fujitsu", ALC880_FUJITSU), + SND_PCI_QUIRK(0x1854, 0x0018, "LG LW20", ALC880_LG_LW), + SND_PCI_QUIRK(0x1854, 0x003b, "LG", ALC880_LG), + SND_PCI_QUIRK(0x1854, 0x0068, "LG w1", ALC880_LG), + SND_PCI_QUIRK(0x1854, 0x0077, "LG LW25", ALC880_LG_LW), + SND_PCI_QUIRK(0x19db, 0x4188, "TCL S700", ALC880_TCL_S700), + SND_PCI_QUIRK(0x2668, 0x8086, NULL, ALC880_6ST_DIG), /* broken BIOS */ + SND_PCI_QUIRK(0x8086, 0x2668, NULL, ALC880_6ST_DIG), + SND_PCI_QUIRK(0x8086, 0xa100, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd400, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd401, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd402, "Intel mobo", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe224, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe305, "Intel mobo", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe308, "Intel mobo", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe400, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe401, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe402, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0, "Intel mobo", ALC880_3ST), /* default Intel */ + SND_PCI_QUIRK(0xa0a0, 0x0560, "AOpen i915GMm-HFS", ALC880_5ST_DIG), + SND_PCI_QUIRK(0xe803, 0x1019, NULL, ALC880_6ST_DIG), + {} +}; + +/* + * ALC880 codec presets + */ +static struct alc_config_preset alc880_presets[] = { + [ALC880_3ST] = { + .mixers = { alc880_three_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_3ST_DIG] = { + .mixers = { alc880_three_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_TCL_S700] = { + .mixers = { alc880_tcl_s700_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_tcl_S700_init_verbs, + alc880_gpio2_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_capture_source, + }, + [ALC880_5ST] = { + .mixers = { alc880_three_stack_mixer, + alc880_five_stack_mixer}, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_5stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes), + .channel_mode = alc880_fivestack_modes, + .input_mux = &alc880_capture_source, + }, + [ALC880_5ST_DIG] = { + .mixers = { alc880_three_stack_mixer, + alc880_five_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_5stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_fivestack_modes), + .channel_mode = alc880_fivestack_modes, + .input_mux = &alc880_capture_source, + }, + [ALC880_6ST] = { + .mixers = { alc880_six_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_6stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_6st_dac_nids), + .dac_nids = alc880_6st_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_sixstack_modes), + .channel_mode = alc880_sixstack_modes, + .input_mux = &alc880_6stack_capture_source, + }, + [ALC880_6ST_DIG] = { + .mixers = { alc880_six_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_6stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_6st_dac_nids), + .dac_nids = alc880_6st_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_sixstack_modes), + .channel_mode = alc880_sixstack_modes, + .input_mux = &alc880_6stack_capture_source, + }, + [ALC880_W810] = { + .mixers = { alc880_w810_base_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_w810_init_verbs, + alc880_gpio2_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_w810_dac_nids), + .dac_nids = alc880_w810_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_w810_modes), + .channel_mode = alc880_w810_modes, + .input_mux = &alc880_capture_source, + }, + [ALC880_Z71V] = { + .mixers = { alc880_z71v_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_z71v_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_z71v_dac_nids), + .dac_nids = alc880_z71v_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_capture_source, + }, + [ALC880_F1734] = { + .mixers = { alc880_f1734_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_f1734_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_f1734_dac_nids), + .dac_nids = alc880_f1734_dac_nids, + .hp_nid = 0x02, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_f1734_capture_source, + .unsol_event = alc880_uniwill_p53_unsol_event, + .init_hook = alc880_uniwill_p53_hp_automute, + }, + [ALC880_ASUS] = { + .mixers = { alc880_asus_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_asus_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_asus_modes), + .channel_mode = alc880_asus_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_ASUS_DIG] = { + .mixers = { alc880_asus_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_asus_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_asus_modes), + .channel_mode = alc880_asus_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_ASUS_DIG2] = { + .mixers = { alc880_asus_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_asus_init_verbs, + alc880_gpio2_init_verbs }, /* use GPIO2 */ + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_asus_modes), + .channel_mode = alc880_asus_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_ASUS_W1V] = { + .mixers = { alc880_asus_mixer, alc880_asus_w1v_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_asus_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_asus_modes), + .channel_mode = alc880_asus_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_UNIWILL_DIG] = { + .mixers = { alc880_asus_mixer, alc880_pcbeep_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_asus_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_asus_modes), + .channel_mode = alc880_asus_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_UNIWILL] = { + .mixers = { alc880_uniwill_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_uniwill_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + .unsol_event = alc880_uniwill_unsol_event, + .init_hook = alc880_uniwill_automute, + }, + [ALC880_UNIWILL_P53] = { + .mixers = { alc880_uniwill_p53_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_uniwill_p53_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_asus_dac_nids), + .dac_nids = alc880_asus_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_w810_modes), + .channel_mode = alc880_threestack_modes, + .input_mux = &alc880_capture_source, + .unsol_event = alc880_uniwill_p53_unsol_event, + .init_hook = alc880_uniwill_p53_hp_automute, + }, + [ALC880_FUJITSU] = { + .mixers = { alc880_fujitsu_mixer, + alc880_pcbeep_mixer, }, + .init_verbs = { alc880_volume_init_verbs, + alc880_uniwill_p53_init_verbs, + alc880_beep_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_capture_source, + .unsol_event = alc880_uniwill_p53_unsol_event, + .init_hook = alc880_uniwill_p53_hp_automute, + }, + [ALC880_CLEVO] = { + .mixers = { alc880_three_stack_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_pin_clevo_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc880_capture_source, + }, + [ALC880_LG] = { + .mixers = { alc880_lg_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_lg_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_lg_dac_nids), + .dac_nids = alc880_lg_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_lg_ch_modes), + .channel_mode = alc880_lg_ch_modes, + .need_dac_fix = 1, + .input_mux = &alc880_lg_capture_source, + .unsol_event = alc880_lg_unsol_event, + .init_hook = alc880_lg_automute, +#ifdef CONFIG_SND_HDA_POWER_SAVE + .loopbacks = alc880_lg_loopbacks, +#endif + }, + [ALC880_LG_LW] = { + .mixers = { alc880_lg_lw_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_lg_lw_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_lg_lw_modes), + .channel_mode = alc880_lg_lw_modes, + .input_mux = &alc880_lg_lw_capture_source, + .unsol_event = alc880_lg_lw_unsol_event, + .init_hook = alc880_lg_lw_automute, + }, + [ALC880_MEDION_RIM] = { + .mixers = { alc880_medion_rim_mixer }, + .init_verbs = { alc880_volume_init_verbs, + alc880_medion_rim_init_verbs, + alc_gpio2_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_dac_nids), + .dac_nids = alc880_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_2_jack_modes), + .channel_mode = alc880_2_jack_modes, + .input_mux = &alc880_medion_rim_capture_source, + .unsol_event = alc880_medion_rim_unsol_event, + .init_hook = alc880_medion_rim_automute, + }, +#ifdef CONFIG_SND_DEBUG + [ALC880_TEST] = { + .mixers = { alc880_test_mixer }, + .init_verbs = { alc880_test_init_verbs }, + .num_dacs = ARRAY_SIZE(alc880_test_dac_nids), + .dac_nids = alc880_test_dac_nids, + .dig_out_nid = ALC880_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_test_modes), + .channel_mode = alc880_test_modes, + .input_mux = &alc880_test_capture_source, + }, +#endif +}; + +/* + * Automatic parse of I/O pins from the BIOS configuration + */ + +#define NUM_CONTROL_ALLOC 32 +#define NUM_VERB_ALLOC 32 + +enum { + ALC_CTL_WIDGET_VOL, + ALC_CTL_WIDGET_MUTE, + ALC_CTL_BIND_MUTE, +}; +static struct snd_kcontrol_new alc880_control_templates[] = { + HDA_CODEC_VOLUME(NULL, 0, 0, 0), + HDA_CODEC_MUTE(NULL, 0, 0, 0), + HDA_BIND_MUTE(NULL, 0, 0, 0), +}; + +/* add dynamic controls */ +static int add_control(struct alc_spec *spec, int type, const char *name, + unsigned long val) +{ + struct snd_kcontrol_new *knew; + + if (spec->num_kctl_used >= spec->num_kctl_alloc) { + int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC; + + /* array + terminator */ + knew = kcalloc(num + 1, sizeof(*knew), GFP_KERNEL); + if (!knew) + return -ENOMEM; + if (spec->kctl_alloc) { + memcpy(knew, spec->kctl_alloc, + sizeof(*knew) * spec->num_kctl_alloc); + kfree(spec->kctl_alloc); + } + spec->kctl_alloc = knew; + spec->num_kctl_alloc = num; + } + + knew = &spec->kctl_alloc[spec->num_kctl_used]; + *knew = alc880_control_templates[type]; + knew->name = kstrdup(name, GFP_KERNEL); + if (!knew->name) + return -ENOMEM; + knew->private_value = val; + spec->num_kctl_used++; + return 0; +} + +#define alc880_is_fixed_pin(nid) ((nid) >= 0x14 && (nid) <= 0x17) +#define alc880_fixed_pin_idx(nid) ((nid) - 0x14) +#define alc880_is_multi_pin(nid) ((nid) >= 0x18) +#define alc880_multi_pin_idx(nid) ((nid) - 0x18) +#define alc880_is_input_pin(nid) ((nid) >= 0x18) +#define alc880_input_pin_idx(nid) ((nid) - 0x18) +#define alc880_idx_to_dac(nid) ((nid) + 0x02) +#define alc880_dac_to_idx(nid) ((nid) - 0x02) +#define alc880_idx_to_mixer(nid) ((nid) + 0x0c) +#define alc880_idx_to_selector(nid) ((nid) + 0x10) +#define ALC880_PIN_CD_NID 0x1c + +/* fill in the dac_nids table from the parsed pin configuration */ +static int alc880_auto_fill_dac_nids(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int assigned[4]; + int i, j; + + memset(assigned, 0, sizeof(assigned)); + spec->multiout.dac_nids = spec->private_dac_nids; + + /* check the pins hardwired to audio widget */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (alc880_is_fixed_pin(nid)) { + int idx = alc880_fixed_pin_idx(nid); + spec->multiout.dac_nids[i] = alc880_idx_to_dac(idx); + assigned[idx] = 1; + } + } + /* left pins can be connect to any audio widget */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (alc880_is_fixed_pin(nid)) + continue; + /* search for an empty channel */ + for (j = 0; j < cfg->line_outs; j++) { + if (!assigned[j]) { + spec->multiout.dac_nids[i] = + alc880_idx_to_dac(j); + assigned[j] = 1; + break; + } + } + } + spec->multiout.num_dacs = cfg->line_outs; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int alc880_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { + "Front", "Surround", NULL /*CLFE*/, "Side" + }; + hda_nid_t nid; + int i, err; + + for (i = 0; i < cfg->line_outs; i++) { + if (!spec->multiout.dac_nids[i]) + continue; + nid = alc880_idx_to_mixer(alc880_dac_to_idx(spec->multiout.dac_nids[i])); + if (i == 2) { + /* Center/LFE */ + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_BIND_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 1, 2, + HDA_INPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_BIND_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 2, + HDA_INPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 2, + HDA_INPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +/* add playback controls for speaker and HP outputs */ +static int alc880_auto_create_extra_out(struct alc_spec *spec, hda_nid_t pin, + const char *pfx) +{ + hda_nid_t nid; + int err; + char name[32]; + + if (!pin) + return 0; + + if (alc880_is_fixed_pin(pin)) { + nid = alc880_idx_to_dac(alc880_fixed_pin_idx(pin)); + /* specify the DAC as the extra output */ + if (!spec->multiout.hp_nid) + spec->multiout.hp_nid = nid; + else + spec->multiout.extra_out_nid[0] = nid; + /* control HP volume/switch on the output mixer amp */ + nid = alc880_idx_to_mixer(alc880_fixed_pin_idx(pin)); + sprintf(name, "%s Playback Volume", pfx); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT)); + if (err < 0) + return err; + } else if (alc880_is_multi_pin(pin)) { + /* set manual connection */ + /* we have only a switch on HP-out PIN */ + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* create input playback/capture controls for the given pin */ +static int new_analog_input(struct alc_spec *spec, hda_nid_t pin, + const char *ctlname, + int idx, hda_nid_t mix_nid) +{ + char name[32]; + int err; + + sprintf(name, "%s Playback Volume", ctlname); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", ctlname); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT)); + if (err < 0) + return err; + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc880_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, err, idx; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (alc880_is_input_pin(cfg->input_pins[i])) { + idx = alc880_input_pin_idx(cfg->input_pins[i]); + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], + idx, 0x0b); + if (err < 0) + return err; + imux->items[imux->num_items].label = + auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = + alc880_input_pin_idx(cfg->input_pins[i]); + imux->num_items++; + } + } + return 0; +} + +static void alc_set_pin_output(struct hda_codec *codec, hda_nid_t nid, + unsigned int pin_type) +{ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_type); + /* unmute pin */ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); +} + +static void alc880_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + alc_set_pin_output(codec, nid, pin_type); + /* need the manual connection? */ + if (alc880_is_multi_pin(nid)) { + struct alc_spec *spec = codec->spec; + int idx = alc880_multi_pin_idx(nid); + snd_hda_codec_write(codec, alc880_idx_to_selector(idx), 0, + AC_VERB_SET_CONNECT_SEL, + alc880_dac_to_idx(spec->multiout.dac_nids[dac_idx])); + } +} + +static int get_pin_type(int line_out_type) +{ + if (line_out_type == AUTO_PIN_HP_OUT) + return PIN_HP; + else + return PIN_OUT; +} + +static void alc880_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x15, 0x1b, 0x14); + for (i = 0; i < spec->autocfg.line_outs; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + alc880_auto_set_output_and_unmute(codec, nid, pin_type, i); + } +} + +static void alc880_auto_init_extra_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.speaker_pins[0]; + if (pin) /* connect to front */ + alc880_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + alc880_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); +} + +static void alc880_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (alc880_is_input_pin(nid)) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN); + if (nid != ALC880_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } + } +} + +/* parse the BIOS configuration and set up the alc_spec */ +/* return 1 if successful, 0 if the proper config is not found, + * or a negative error code + */ +static int alc880_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc880_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc880_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc880_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc880_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc880_auto_create_extra_out(spec, + spec->autocfg.speaker_pins[0], + "Speaker"); + if (err < 0) + return err; + err = alc880_auto_create_extra_out(spec, spec->autocfg.hp_pins[0], + "Headphone"); + if (err < 0) + return err; + err = alc880_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC880_DIGOUT_NID; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = ALC880_DIGIN_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = alc880_volume_init_verbs; + + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + store_pin_configs(codec); + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc880_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc880_auto_init_multi_out(codec); + alc880_auto_init_extra_out(codec); + alc880_auto_init_analog_input(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +/* + * OK, here we have finally the patch for ALC880 + */ + +static int patch_alc880(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC880_MODEL_LAST, + alc880_models, + alc880_cfg_tbl); + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC880, " + "trying auto-probe from BIOS...\n"); + board_config = ALC880_AUTO; + } + + if (board_config == ALC880_AUTO) { + /* automatic parse from the BIOS config */ + err = alc880_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using 3-stack mode...\n"); + board_config = ALC880_3ST; + } + } + + if (board_config != ALC880_AUTO) + setup_preset(spec, &alc880_presets[board_config]); + + spec->stream_name_analog = "ALC880 Analog"; + spec->stream_analog_playback = &alc880_pcm_analog_playback; + spec->stream_analog_capture = &alc880_pcm_analog_capture; + spec->stream_analog_alt_capture = &alc880_pcm_analog_alt_capture; + + spec->stream_name_digital = "ALC880 Digital"; + spec->stream_digital_playback = &alc880_pcm_digital_playback; + spec->stream_digital_capture = &alc880_pcm_digital_capture; + + if (!spec->adc_nids && spec->input_mux) { + /* check whether NID 0x07 is valid */ + unsigned int wcap = get_wcaps(codec, alc880_adc_nids[0]); + /* get type */ + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wcap != AC_WID_AUD_IN) { + spec->adc_nids = alc880_adc_nids_alt; + spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids_alt); + spec->mixers[spec->num_mixers] = + alc880_capture_alt_mixer; + spec->num_mixers++; + } else { + spec->adc_nids = alc880_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc880_adc_nids); + spec->mixers[spec->num_mixers] = alc880_capture_mixer; + spec->num_mixers++; + } + } + + spec->vmaster_nid = 0x0c; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC880_AUTO) + spec->init_hook = alc880_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc880_loopbacks; +#endif + + return 0; +} + + +/* + * ALC260 support + */ + +static hda_nid_t alc260_dac_nids[1] = { + /* front */ + 0x02, +}; + +static hda_nid_t alc260_adc_nids[1] = { + /* ADC0 */ + 0x04, +}; + +static hda_nid_t alc260_adc_nids_alt[1] = { + /* ADC1 */ + 0x05, +}; + +static hda_nid_t alc260_hp_adc_nids[2] = { + /* ADC1, 0 */ + 0x05, 0x04 +}; + +/* NIDs used when simultaneous access to both ADCs makes sense. Note that + * alc260_capture_mixer assumes ADC0 (nid 0x04) is the first ADC. + */ +static hda_nid_t alc260_dual_adc_nids[2] = { + /* ADC0, ADC1 */ + 0x04, 0x05 +}; + +#define ALC260_DIGOUT_NID 0x03 +#define ALC260_DIGIN_NID 0x06 + +static struct hda_input_mux alc260_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +/* On Fujitsu S702x laptops capture only makes sense from Mic/LineIn jack, + * headphone jack and the internal CD lines since these are the only pins at + * which audio can appear. For flexibility, also allow the option of + * recording the mixer output on the second ADC (ADC0 doesn't have a + * connection to the mixer output). + */ +static struct hda_input_mux alc260_fujitsu_capture_sources[2] = { + { + .num_items = 3, + .items = { + { "Mic/Line", 0x0 }, + { "CD", 0x4 }, + { "Headphone", 0x2 }, + }, + }, + { + .num_items = 4, + .items = { + { "Mic/Line", 0x0 }, + { "CD", 0x4 }, + { "Headphone", 0x2 }, + { "Mixer", 0x5 }, + }, + }, + +}; + +/* Acer TravelMate(/Extensa/Aspire) notebooks have similar configuration to + * the Fujitsu S702x, but jacks are marked differently. + */ +static struct hda_input_mux alc260_acer_capture_sources[2] = { + { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Headphone", 0x5 }, + }, + }, + { + .num_items = 5, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "Headphone", 0x6 }, + { "Mixer", 0x5 }, + }, + }, +}; +/* + * 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 hda_channel_mode alc260_modes[1] = { + { 2, NULL }, +}; + + +/* Mixer combinations + * + * basic: base_output + input + pc_beep + capture + * HP: base_output + input + capture_alt + * HP_3013: hp_3013 + input + capture + * fujitsu: fujitsu + capture + * acer: acer + capture + */ + +static struct snd_kcontrol_new alc260_base_output_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x08, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x09, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Mono Playback Switch", 0x0a, 1, 2, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc260_input_mixer[] = { + 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), + { } /* end */ +}; + +static struct snd_kcontrol_new alc260_pc_beep_mixer[] = { + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x07, 0x05, HDA_INPUT), + { } /* end */ +}; + +/* update HP, line and mono out pins according to the master switch */ +static void alc260_hp_master_update(struct hda_codec *codec, + hda_nid_t hp, hda_nid_t line, + hda_nid_t mono) +{ + struct alc_spec *spec = codec->spec; + unsigned int val = spec->master_sw ? PIN_HP : 0; + /* change HP and line-out pins */ + snd_hda_codec_write(codec, 0x0f, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + val); + snd_hda_codec_write(codec, 0x10, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + val); + /* mono (speaker) depending on the HP jack sense */ + val = (val && !spec->jack_present) ? PIN_OUT : 0; + snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + val); +} + +static int alc260_hp_master_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + *ucontrol->value.integer.value = spec->master_sw; + return 0; +} + +static int alc260_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + int val = !!*ucontrol->value.integer.value; + hda_nid_t hp, line, mono; + + if (val == spec->master_sw) + return 0; + spec->master_sw = val; + hp = (kcontrol->private_value >> 16) & 0xff; + line = (kcontrol->private_value >> 8) & 0xff; + mono = kcontrol->private_value & 0xff; + alc260_hp_master_update(codec, hp, line, mono); + return 1; +} + +static struct snd_kcontrol_new alc260_hp_output_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = alc260_hp_master_sw_get, + .put = alc260_hp_master_sw_put, + .private_value = (0x0f << 16) | (0x10 << 8) | 0x11 + }, + HDA_CODEC_VOLUME("Front Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x08, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x09, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x09, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0, + HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Speaker Playback Switch", 0x0a, 1, 2, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc260_hp_unsol_verbs[] = { + {0x10, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {}, +}; + +static void alc260_hp_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x10, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; + alc260_hp_master_update(codec, 0x0f, 0x10, 0x11); +} + +static void alc260_hp_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc260_hp_automute(codec); +} + +static struct snd_kcontrol_new alc260_hp_3013_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = alc260_hp_master_sw_get, + .put = alc260_hp_master_sw_put, + .private_value = (0x10 << 16) | (0x15 << 8) | 0x11 + }, + HDA_CODEC_VOLUME("Front Playback Volume", 0x09, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Aux-In Playback Volume", 0x07, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("Aux-In Playback Switch", 0x07, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x11, 1, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct hda_bind_ctls alc260_dc7600_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x0a, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct hda_bind_ctls alc260_dc7600_bind_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x11, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc260_hp_dc7600_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc260_dc7600_bind_master_vol), + HDA_BIND_SW("LineOut Playback Switch", &alc260_dc7600_bind_switch), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x0f, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x10, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct hda_verb alc260_hp_3013_unsol_verbs[] = { + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {}, +}; + +static void alc260_hp_3013_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; + alc260_hp_master_update(codec, 0x10, 0x15, 0x11); +} + +static void alc260_hp_3013_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc260_hp_3013_automute(codec); +} + +static void alc260_hp_3012_automute(struct hda_codec *codec) +{ + unsigned int present, bits; + + present = snd_hda_codec_read(codec, 0x10, 0, + AC_VERB_GET_PIN_SENSE, 0) & AC_PINSENSE_PRESENCE; + + bits = present ? 0 : PIN_OUT; + snd_hda_codec_write(codec, 0x0f, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); + snd_hda_codec_write(codec, 0x11, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); + snd_hda_codec_write(codec, 0x15, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); +} + +static void alc260_hp_3012_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc260_hp_3012_automute(codec); +} + +/* Fujitsu S702x series laptops. ALC260 pin usage: Mic/Line jack = 0x12, + * HP jack = 0x14, CD audio = 0x16, internal speaker = 0x10. + */ +static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x08, 2, HDA_INPUT), + ALC_PIN_MODE("Headphone Jack Mode", 0x14, ALC_PIN_DIR_INOUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic/Line Playback Volume", 0x07, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic/Line Playback Switch", 0x07, 0x0, HDA_INPUT), + ALC_PIN_MODE("Mic/Line Jack Mode", 0x12, ALC_PIN_DIR_IN), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x09, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x09, 2, HDA_INPUT), + { } /* end */ +}; + +/* Mixer for Acer TravelMate(/Extensa/Aspire) notebooks. Note that current + * versions of the ALC260 don't act on requests to enable mic bias from NID + * 0x0f (used to drive the headphone jack in these laptops). The ALC260 + * datasheet doesn't mention this restriction. At this stage it's not clear + * whether this behaviour is intentional or is a hardware bug in chip + * revisions available in early 2006. Therefore for now allow the + * "Headphone Jack Mode" control to span all choices, but if it turns out + * that the lack of mic bias for this NID is intentional we could change the + * mode from ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS. + * + * In addition, Acer TravelMate(/Extensa/Aspire) notebooks in early 2006 + * don't appear to make the mic bias available from the "line" jack, even + * though the NID used for this jack (0x14) can supply it. The theory is + * that perhaps Acer have included blocking capacitors between the ALC260 + * and the output jack. If this turns out to be the case for all such + * models the "Line Jack Mode" mode could be changed from ALC_PIN_DIR_INOUT + * to ALC_PIN_DIR_INOUT_NOMICBIAS. + * + * The C20x Tablet series have a mono internal speaker which is controlled + * via the chip's Mono sum widget and pin complex, so include the necessary + * controls for such models. On models without a "mono speaker" the control + * won't do anything. + */ +static struct snd_kcontrol_new alc260_acer_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x08, 2, HDA_INPUT), + ALC_PIN_MODE("Headphone Jack Mode", 0x0f, ALC_PIN_DIR_INOUT), + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0a, 1, 0x0, + HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Speaker Playback Switch", 0x0a, 1, 2, + HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT), + ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN), + HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT), + ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), + { } /* end */ +}; + +/* Packard bell V7900 ALC260 pin usage: HP = 0x0f, Mic jack = 0x12, + * Line In jack = 0x14, CD audio = 0x16, pc beep = 0x17. + */ +static struct snd_kcontrol_new alc260_will_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x08, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT), + ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN), + HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT), + ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), + { } /* end */ +}; + +/* Replacer 672V ALC260 pin usage: Mic jack = 0x12, + * Line In jack = 0x14, ATAPI Mic = 0x13, speaker = 0x0f. + */ +static struct snd_kcontrol_new alc260_replacer_672v_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x08, 0x2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x07, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x07, 0x0, HDA_INPUT), + ALC_PIN_MODE("Mic Jack Mode", 0x12, ALC_PIN_DIR_IN), + HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x07, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("ATATI Mic Playback Switch", 0x07, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x07, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x07, 0x02, HDA_INPUT), + ALC_PIN_MODE("Line Jack Mode", 0x14, ALC_PIN_DIR_INOUT), + { } /* end */ +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc260_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x04, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x04, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x05, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x05, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc260_capture_alt_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x05, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x05, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +/* + * initialization verbs + */ +static struct hda_verb alc260_init_verbs[] = { + /* Line In pin widget for input */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* CD pin widget for input */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Mic1 (rear panel) pin widget for input and vref at 80% */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + /* Mic2 (front panel) pin widget for input and vref at 80% */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + /* LINE-2 is used for line-out in rear */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* select line-out */ + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* LINE-OUT pin */ + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* enable HP */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* enable Mono */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* mute capture amp left and right */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* set connection select to line in (default select for this ADC) */ + {0x04, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* mute capture amp left and right */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* set connection select to line in (default select for this ADC) */ + {0x05, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* set vol=0 Line-Out mixer amp left and right */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* unmute pin widget amp left and right (no gain on this amp) */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* set vol=0 HP mixer amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* unmute pin widget amp left and right (no gain on this amp) */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* set vol=0 Mono mixer amp left and right */ + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* unmute pin widget amp left and right (no gain on this amp) */ + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* unmute LINE-2 out pin */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & + * Line In 2 = 0x03 + */ + /* mute analog inputs */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Amp Indexes: DAC = 0x01 & mixer = 0x00 */ + /* mute Front out path */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* mute Headphone out path */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* mute Mono out path */ + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } +}; + +#if 0 /* should be identical with alc260_init_verbs? */ +static struct hda_verb alc260_hp_init_verbs[] = { + /* Headphone and output */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + /* mono output */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* 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 In pin widget for input */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + /* Line-2 pin widget for output */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* CD pin widget for input */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + /* 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) */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + /* 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, 0x0000}, + /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & + * Line In 2 = 0x03 + */ + /* mute analog inputs */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* 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))}, + { } +}; +#endif + +static struct hda_verb alc260_hp_3013_init_verbs[] = { + /* Line out and output */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* mono output */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* 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 In pin widget for input */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + /* Headphone pin widget for output */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + /* CD pin widget for input */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + /* 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) */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + /* 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, 0x0000}, + /* Amp Indexes: CD = 0x04, Line In 1 = 0x02, Mic 1 = 0x00 & + * Line In 2 = 0x03 + */ + /* mute analog inputs */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* 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))}, + { } +}; + +/* Initialisation sequence for ALC260 as configured in Fujitsu S702x + * laptops. ALC260 pin usage: Mic/Line jack = 0x12, HP jack = 0x14, CD + * audio = 0x16, internal speaker = 0x10. + */ +static struct hda_verb alc260_fujitsu_init_verbs[] = { + /* Disable all GPIOs */ + {0x01, AC_VERB_SET_GPIO_MASK, 0}, + /* Internal speaker is connected to headphone pin */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Headphone/Line-out jack connects to Line1 pin; make it an output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* Mic/Line-in jack is connected to mic1 pin, so make it an input */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Ensure all other unused pins are disabled and muted. */ + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + + /* Disable digital (SPDIF) pins */ + {0x03, AC_VERB_SET_DIGI_CONVERT_1, 0}, + {0x06, AC_VERB_SET_DIGI_CONVERT_1, 0}, + + /* Ensure Line1 pin widget takes its input from the OUT1 sum bus + * when acting as an output. + */ + {0x0d, AC_VERB_SET_CONNECT_SEL, 0}, + + /* Start with output sum widgets muted and their output gains at min */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Unmute HP pin widget amp left and right (no equiv mixer ctrl) */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Unmute Line1 pin widget output buffer since it starts as an output. + * If the pin mode is changed by the user the pin mode control will + * take care of enabling the pin's input/output buffers as needed. + * Therefore there's no need to enable the input buffer at this + * stage. + */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Unmute input buffer of pin widget used for Line-in (no equiv + * mixer ctrl) + */ + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute capture amp left and right */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + /* Set ADC connection select to match default mixer setting - line + * in (on mic1 pin) + */ + {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Do the same for the second ADC: mute capture input amp and + * set ADC connection to line in (on mic1 pin) + */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mute all inputs to mixer widget (even unconnected ones) */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */ + + { } +}; + +/* Initialisation sequence for ALC260 as configured in Acer TravelMate and + * similar laptops (adapted from Fujitsu init verbs). + */ +static struct hda_verb alc260_acer_init_verbs[] = { + /* On TravelMate laptops, GPIO 0 enables the internal speaker and + * the headphone jack. Turn this on and rely on the standard mute + * methods whenever the user wants to turn these outputs off. + */ + {0x01, AC_VERB_SET_GPIO_MASK, 0x01}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x01}, + /* Internal speaker/Headphone jack is connected to Line-out pin */ + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Internal microphone/Mic jack is connected to Mic1 pin */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50}, + /* Line In jack is connected to Line1 pin */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + /* Some Acers (eg: C20x Tablets) use Mono pin for internal speaker */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Ensure all other unused pins are disabled and muted. */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + /* Disable digital (SPDIF) pins */ + {0x03, AC_VERB_SET_DIGI_CONVERT_1, 0}, + {0x06, AC_VERB_SET_DIGI_CONVERT_1, 0}, + + /* Ensure Mic1 and Line1 pin widgets take input from the OUT1 sum + * bus when acting as outputs. + */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0}, + + /* Start with output sum widgets muted and their output gains at min */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Unmute Line-out pin widget amp left and right + * (no equiv mixer ctrl) + */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Unmute mono pin widget amp output (no equiv mixer ctrl) */ + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Unmute Mic1 and Line1 pin widget input buffers since they start as + * inputs. If the pin mode is changed by the user the pin mode control + * will take care of enabling the pin's input/output buffers as needed. + * Therefore there's no need to enable the input buffer at this + * stage. + */ + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute capture amp left and right */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + /* Set ADC connection select to match default mixer setting - mic + * (on mic1 pin) + */ + {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Do similar with the second ADC: mute capture input amp and + * set ADC connection to mic to match ALSA's default state. + */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mute all inputs to mixer widget (even unconnected ones) */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */ + + { } +}; + +static struct hda_verb alc260_will_verbs[] = { + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x0b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x0f, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {0x1a, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x1a, AC_VERB_SET_PROC_COEF, 0x3040}, + {} +}; + +static struct hda_verb alc260_replacer_672v_verbs[] = { + {0x0f, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {0x1a, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x1a, AC_VERB_SET_PROC_COEF, 0x3050}, + + {0x01, AC_VERB_SET_GPIO_MASK, 0x01}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x01}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x00}, + + {0x0f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc260_replacer_672v_automute(struct hda_codec *codec) +{ + unsigned int present; + + /* speaker --> GPIO Data 0, hp or spdif --> GPIO data 1 */ + present = snd_hda_codec_read(codec, 0x0f, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + if (present) { + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 1); + snd_hda_codec_write_cache(codec, 0x0f, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_HP); + } else { + snd_hda_codec_write_cache(codec, 0x01, 0, + AC_VERB_SET_GPIO_DATA, 0); + snd_hda_codec_write_cache(codec, 0x0f, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_OUT); + } +} + +static void alc260_replacer_672v_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc260_replacer_672v_automute(codec); +} + +static struct hda_verb alc260_hp_dc7600_verbs[] = { + {0x05, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x10, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* Test configuration for debugging, modelled after the ALC880 test + * configuration. + */ +#ifdef CONFIG_SND_DEBUG +static hda_nid_t alc260_test_dac_nids[1] = { + 0x02, +}; +static hda_nid_t alc260_test_adc_nids[2] = { + 0x04, 0x05, +}; +/* For testing the ALC260, each input MUX needs its own definition since + * the signal assignments are different. This assumes that the first ADC + * is NID 0x04. + */ +static struct hda_input_mux alc260_test_capture_sources[2] = { + { + .num_items = 7, + .items = { + { "MIC1 pin", 0x0 }, + { "MIC2 pin", 0x1 }, + { "LINE1 pin", 0x2 }, + { "LINE2 pin", 0x3 }, + { "CD pin", 0x4 }, + { "LINE-OUT pin", 0x5 }, + { "HP-OUT pin", 0x6 }, + }, + }, + { + .num_items = 8, + .items = { + { "MIC1 pin", 0x0 }, + { "MIC2 pin", 0x1 }, + { "LINE1 pin", 0x2 }, + { "LINE2 pin", 0x3 }, + { "CD pin", 0x4 }, + { "Mixer", 0x5 }, + { "LINE-OUT pin", 0x6 }, + { "HP-OUT pin", 0x7 }, + }, + }, +}; +static struct snd_kcontrol_new alc260_test_mixer[] = { + /* Output driver widgets */ + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0a, 1, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Mono Playback Switch", 0x0a, 1, 2, HDA_INPUT), + HDA_CODEC_VOLUME("LOUT2 Playback Volume", 0x09, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("LOUT2 Playback Switch", 0x09, 2, HDA_INPUT), + HDA_CODEC_VOLUME("LOUT1 Playback Volume", 0x08, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("LOUT1 Playback Switch", 0x08, 2, HDA_INPUT), + + /* Modes for retasking pin widgets + * Note: the ALC260 doesn't seem to act on requests to enable mic + * bias from NIDs 0x0f and 0x10. The ALC260 datasheet doesn't + * mention this restriction. At this stage it's not clear whether + * this behaviour is intentional or is a hardware bug in chip + * revisions available at least up until early 2006. Therefore for + * now allow the "HP-OUT" and "LINE-OUT" Mode controls to span all + * choices, but if it turns out that the lack of mic bias for these + * NIDs is intentional we could change their modes from + * ALC_PIN_DIR_INOUT to ALC_PIN_DIR_INOUT_NOMICBIAS. + */ + ALC_PIN_MODE("HP-OUT pin mode", 0x10, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("LINE-OUT pin mode", 0x0f, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("LINE2 pin mode", 0x15, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("LINE1 pin mode", 0x14, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("MIC2 pin mode", 0x13, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("MIC1 pin mode", 0x12, ALC_PIN_DIR_INOUT), + + /* Loopback mixer controls */ + HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x07, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("MIC1 Playback Switch", 0x07, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x07, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("MIC2 Playback Switch", 0x07, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("LINE1 Playback Volume", 0x07, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("LINE1 Playback Switch", 0x07, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("LINE2 Playback Volume", 0x07, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("LINE2 Playback Switch", 0x07, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x07, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("Beep Playback Volume", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x07, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("LINE-OUT loopback Playback Volume", 0x07, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("LINE-OUT loopback Playback Switch", 0x07, 0x06, HDA_INPUT), + HDA_CODEC_VOLUME("HP-OUT loopback Playback Volume", 0x07, 0x7, HDA_INPUT), + HDA_CODEC_MUTE("HP-OUT loopback Playback Switch", 0x07, 0x7, HDA_INPUT), + + /* Controls for GPIO pins, assuming they are configured as outputs */ + ALC_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01), + ALC_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02), + ALC_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04), + ALC_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08), + + /* Switches to allow the digital IO pins to be enabled. The datasheet + * is ambigious as to which NID is which; testing on laptops which + * make this output available should provide clarification. + */ + ALC_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x03, 0x01), + ALC_SPDIF_CTRL_SWITCH("SPDIF Capture Switch", 0x06, 0x01), + + /* A switch allowing EAPD to be enabled. Some laptops seem to use + * this output to turn on an external amplifier. + */ + ALC_EAPD_CTRL_SWITCH("LINE-OUT EAPD Enable Switch", 0x0f, 0x02), + ALC_EAPD_CTRL_SWITCH("HP-OUT EAPD Enable Switch", 0x10, 0x02), + + { } /* end */ +}; +static struct hda_verb alc260_test_init_verbs[] = { + /* Enable all GPIOs as outputs with an initial value of 0 */ + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x0f}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x00}, + {0x01, AC_VERB_SET_GPIO_MASK, 0x0f}, + + /* Enable retasking pins as output, initially without power amp */ + {0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* Disable digital (SPDIF) pins initially, but users can enable + * them via a mixer switch. In the case of SPDIF-out, this initverb + * payload also sets the generation to 0, output to be in "consumer" + * PCM format, copyright asserted, no pre-emphasis and no validity + * control. + */ + {0x03, AC_VERB_SET_DIGI_CONVERT_1, 0}, + {0x06, AC_VERB_SET_DIGI_CONVERT_1, 0}, + + /* Ensure mic1, mic2, line1 and line2 pin widgets take input from the + * OUT1 sum bus when acting as an output. + */ + {0x0b, AC_VERB_SET_CONNECT_SEL, 0}, + {0x0c, AC_VERB_SET_CONNECT_SEL, 0}, + {0x0d, AC_VERB_SET_CONNECT_SEL, 0}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0}, + + /* Start with output sum widgets muted and their output gains at min */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Unmute retasking pin widget output buffers since the default + * state appears to be output. As the pin mode is changed by the + * user the pin mode control will take care of enabling the pin's + * input/output buffers as needed. + */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Also unmute the mono-out pin widget */ + {0x11, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mute capture amp left and right */ + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + /* Set ADC connection select to match default mixer setting (mic1 + * pin) + */ + {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Do the same for the second ADC: mute capture input amp and + * set ADC connection to mic1 pin + */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mute all inputs to mixer widget (even unconnected ones) */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* mic1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, /* mic2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, /* line1 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, /* line2 pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, /* CD pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, /* Beep-gen pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, /* Line-out pin */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, /* HP-pin pin */ + + { } +}; +#endif + +#define alc260_pcm_analog_playback alc880_pcm_analog_alt_playback +#define alc260_pcm_analog_capture alc880_pcm_analog_capture + +#define alc260_pcm_digital_playback alc880_pcm_digital_playback +#define alc260_pcm_digital_capture alc880_pcm_digital_capture + +/* + * for BIOS auto-configuration + */ + +static int alc260_add_playback_controls(struct alc_spec *spec, hda_nid_t nid, + const char *pfx, int *vol_bits) +{ + hda_nid_t nid_vol; + unsigned long vol_val, sw_val; + char name[32]; + int err; + + if (nid >= 0x0f && nid < 0x11) { + nid_vol = nid - 0x7; + vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT); + sw_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + } else if (nid == 0x11) { + nid_vol = nid - 0x7; + vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, HDA_OUTPUT); + sw_val = HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_OUTPUT); + } else if (nid >= 0x12 && nid <= 0x15) { + nid_vol = 0x08; + vol_val = HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, HDA_OUTPUT); + sw_val = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT); + } else + return 0; /* N/A */ + + if (!(*vol_bits & (1 << nid_vol))) { + /* first control for the volume widget */ + snprintf(name, sizeof(name), "%s Playback Volume", pfx); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, vol_val); + if (err < 0) + return err; + *vol_bits |= (1 << nid_vol); + } + snprintf(name, sizeof(name), "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, sw_val); + if (err < 0) + return err; + return 1; +} + +/* add playback controls from the parsed DAC table */ +static int alc260_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int err; + int vols = 0; + + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = spec->private_dac_nids; + spec->multiout.dac_nids[0] = 0x02; + + nid = cfg->line_out_pins[0]; + if (nid) { + err = alc260_add_playback_controls(spec, nid, "Front", &vols); + if (err < 0) + return err; + } + + nid = cfg->speaker_pins[0]; + if (nid) { + err = alc260_add_playback_controls(spec, nid, "Speaker", &vols); + if (err < 0) + return err; + } + + nid = cfg->hp_pins[0]; + if (nid) { + err = alc260_add_playback_controls(spec, nid, "Headphone", + &vols); + if (err < 0) + return err; + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc260_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, err, idx; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (cfg->input_pins[i] >= 0x12) { + idx = cfg->input_pins[i] - 0x12; + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], idx, + 0x07); + if (err < 0) + return err; + imux->items[imux->num_items].label = + auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = idx; + imux->num_items++; + } + if (cfg->input_pins[i] >= 0x0f && cfg->input_pins[i] <= 0x10){ + idx = cfg->input_pins[i] - 0x09; + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], idx, + 0x07); + if (err < 0) + return err; + imux->items[imux->num_items].label = + auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = idx; + imux->num_items++; + } + } + return 0; +} + +static void alc260_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int sel_idx) +{ + alc_set_pin_output(codec, nid, pin_type); + /* need the manual connection? */ + if (nid >= 0x12) { + int idx = nid - 0x12; + snd_hda_codec_write(codec, idx + 0x0b, 0, + AC_VERB_SET_CONNECT_SEL, sel_idx); + } +} + +static void alc260_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t nid; + + alc_subsystem_id(codec, 0x10, 0x15, 0x0f); + nid = spec->autocfg.line_out_pins[0]; + if (nid) { + int pin_type = get_pin_type(spec->autocfg.line_out_type); + alc260_auto_set_output_and_unmute(codec, nid, pin_type, 0); + } + + nid = spec->autocfg.speaker_pins[0]; + if (nid) + alc260_auto_set_output_and_unmute(codec, nid, PIN_OUT, 0); + + nid = spec->autocfg.hp_pins[0]; + if (nid) + alc260_auto_set_output_and_unmute(codec, nid, PIN_HP, 0); +} + +#define ALC260_PIN_CD_NID 0x16 +static void alc260_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (nid >= 0x12) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN); + if (nid != ALC260_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } + } +} + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc260_volume_init_verbs[] = { + /* + * Unmute ADC0-1 and set the default input to mic-in + */ + {0x04, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x05, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + /* mute analog inputs */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x08 - 0x0a) + */ + /* set vol=0 to output mixers */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + { } +}; + +static int alc260_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int wcap; + int err; + static hda_nid_t alc260_ignore[] = { 0x17, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc260_ignore); + if (err < 0) + return err; + err = alc260_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->kctl_alloc) + return 0; /* can't find valid BIOS pin config */ + err = alc260_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC260_DIGOUT_NID; + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = alc260_volume_init_verbs; + + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + /* check whether NID 0x04 is valid */ + wcap = get_wcaps(codec, 0x04); + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; /* get type */ + if (wcap != AC_WID_AUD_IN || spec->input_mux->num_items == 1) { + spec->adc_nids = alc260_adc_nids_alt; + spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids_alt); + spec->mixers[spec->num_mixers] = alc260_capture_alt_mixer; + } else { + spec->adc_nids = alc260_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc260_adc_nids); + spec->mixers[spec->num_mixers] = alc260_capture_mixer; + } + spec->num_mixers++; + + store_pin_configs(codec); + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc260_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc260_auto_init_multi_out(codec); + alc260_auto_init_analog_input(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list alc260_loopbacks[] = { + { 0x07, HDA_INPUT, 0 }, + { 0x07, HDA_INPUT, 1 }, + { 0x07, HDA_INPUT, 2 }, + { 0x07, HDA_INPUT, 3 }, + { 0x07, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +/* + * ALC260 configurations + */ +static const char *alc260_models[ALC260_MODEL_LAST] = { + [ALC260_BASIC] = "basic", + [ALC260_HP] = "hp", + [ALC260_HP_3013] = "hp-3013", + [ALC260_HP_DC7600] = "hp-dc7600", + [ALC260_FUJITSU_S702X] = "fujitsu", + [ALC260_ACER] = "acer", + [ALC260_WILL] = "will", + [ALC260_REPLACER_672V] = "replacer", +#ifdef CONFIG_SND_DEBUG + [ALC260_TEST] = "test", +#endif + [ALC260_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc260_cfg_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x007b, "Acer C20x", ALC260_ACER), + SND_PCI_QUIRK(0x1025, 0x008f, "Acer", ALC260_ACER), + SND_PCI_QUIRK(0x103c, 0x2808, "HP d5700", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x280a, "HP d5750", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x3010, "HP", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x3011, "HP", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x3012, "HP", ALC260_HP_DC7600), + SND_PCI_QUIRK(0x103c, 0x3013, "HP", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x3014, "HP", ALC260_HP), + SND_PCI_QUIRK(0x103c, 0x3015, "HP", ALC260_HP), + SND_PCI_QUIRK(0x103c, 0x3016, "HP", ALC260_HP), + SND_PCI_QUIRK(0x104d, 0x81bb, "Sony VAIO", ALC260_BASIC), + SND_PCI_QUIRK(0x104d, 0x81cc, "Sony VAIO", ALC260_BASIC), + SND_PCI_QUIRK(0x104d, 0x81cd, "Sony VAIO", ALC260_BASIC), + SND_PCI_QUIRK(0x10cf, 0x1326, "Fujitsu S702X", ALC260_FUJITSU_S702X), + SND_PCI_QUIRK(0x152d, 0x0729, "CTL U553W", ALC260_BASIC), + SND_PCI_QUIRK(0x161f, 0x2057, "Replacer 672V", ALC260_REPLACER_672V), + SND_PCI_QUIRK(0x1631, 0xc017, "PB V7900", ALC260_WILL), + {} +}; + +static struct alc_config_preset alc260_presets[] = { + [ALC260_BASIC] = { + .mixers = { alc260_base_output_mixer, + alc260_input_mixer, + alc260_pc_beep_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_init_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_adc_nids), + .adc_nids = alc260_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + }, + [ALC260_HP] = { + .mixers = { alc260_hp_output_mixer, + alc260_input_mixer, + alc260_capture_alt_mixer }, + .init_verbs = { alc260_init_verbs, + alc260_hp_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_hp_adc_nids), + .adc_nids = alc260_hp_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + .unsol_event = alc260_hp_unsol_event, + .init_hook = alc260_hp_automute, + }, + [ALC260_HP_DC7600] = { + .mixers = { alc260_hp_dc7600_mixer, + alc260_input_mixer, + alc260_capture_alt_mixer }, + .init_verbs = { alc260_init_verbs, + alc260_hp_dc7600_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_hp_adc_nids), + .adc_nids = alc260_hp_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + .unsol_event = alc260_hp_3012_unsol_event, + .init_hook = alc260_hp_3012_automute, + }, + [ALC260_HP_3013] = { + .mixers = { alc260_hp_3013_mixer, + alc260_input_mixer, + alc260_capture_alt_mixer }, + .init_verbs = { alc260_hp_3013_init_verbs, + alc260_hp_3013_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_hp_adc_nids), + .adc_nids = alc260_hp_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + .unsol_event = alc260_hp_3013_unsol_event, + .init_hook = alc260_hp_3013_automute, + }, + [ALC260_FUJITSU_S702X] = { + .mixers = { alc260_fujitsu_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_fujitsu_init_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids), + .adc_nids = alc260_dual_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .num_mux_defs = ARRAY_SIZE(alc260_fujitsu_capture_sources), + .input_mux = alc260_fujitsu_capture_sources, + }, + [ALC260_ACER] = { + .mixers = { alc260_acer_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_acer_init_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_dual_adc_nids), + .adc_nids = alc260_dual_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .num_mux_defs = ARRAY_SIZE(alc260_acer_capture_sources), + .input_mux = alc260_acer_capture_sources, + }, + [ALC260_WILL] = { + .mixers = { alc260_will_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_init_verbs, alc260_will_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_adc_nids), + .adc_nids = alc260_adc_nids, + .dig_out_nid = ALC260_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + }, + [ALC260_REPLACER_672V] = { + .mixers = { alc260_replacer_672v_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_init_verbs, alc260_replacer_672v_verbs }, + .num_dacs = ARRAY_SIZE(alc260_dac_nids), + .dac_nids = alc260_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_adc_nids), + .adc_nids = alc260_adc_nids, + .dig_out_nid = ALC260_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .input_mux = &alc260_capture_source, + .unsol_event = alc260_replacer_672v_unsol_event, + .init_hook = alc260_replacer_672v_automute, + }, +#ifdef CONFIG_SND_DEBUG + [ALC260_TEST] = { + .mixers = { alc260_test_mixer, + alc260_capture_mixer }, + .init_verbs = { alc260_test_init_verbs }, + .num_dacs = ARRAY_SIZE(alc260_test_dac_nids), + .dac_nids = alc260_test_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc260_test_adc_nids), + .adc_nids = alc260_test_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc260_modes), + .channel_mode = alc260_modes, + .num_mux_defs = ARRAY_SIZE(alc260_test_capture_sources), + .input_mux = alc260_test_capture_sources, + }, +#endif +}; + +static int patch_alc260(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err, board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC260_MODEL_LAST, + alc260_models, + alc260_cfg_tbl); + if (board_config < 0) { + snd_printd(KERN_INFO "hda_codec: Unknown model for ALC260, " + "trying auto-probe from BIOS...\n"); + board_config = ALC260_AUTO; + } + + if (board_config == ALC260_AUTO) { + /* automatic parse from the BIOS config */ + err = alc260_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC260_BASIC; + } + } + + if (board_config != ALC260_AUTO) + setup_preset(spec, &alc260_presets[board_config]); + + spec->stream_name_analog = "ALC260 Analog"; + spec->stream_analog_playback = &alc260_pcm_analog_playback; + spec->stream_analog_capture = &alc260_pcm_analog_capture; + + spec->stream_name_digital = "ALC260 Digital"; + spec->stream_digital_playback = &alc260_pcm_digital_playback; + spec->stream_digital_capture = &alc260_pcm_digital_capture; + + spec->vmaster_nid = 0x08; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC260_AUTO) + spec->init_hook = alc260_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc260_loopbacks; +#endif + + 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). + */ +#define ALC882_DIGOUT_NID 0x06 +#define ALC882_DIGIN_NID 0x0a + +static struct hda_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 +}; + +/* identical with ALC880 */ +#define alc882_adc_nids alc880_adc_nids +#define alc882_adc_nids_alt alc880_adc_nids_alt + +static hda_nid_t alc882_capsrc_nids[3] = { 0x24, 0x23, 0x22 }; +static hda_nid_t alc882_capsrc_nids_alt[2] = { 0x23, 0x22 }; + +/* 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(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *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); + hda_nid_t nid = spec->capsrc_nids ? + spec->capsrc_nids[adc_idx] : spec->adc_nids[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) + return 0; + for (i = 0; i < imux->num_items; i++) { + unsigned int v = (i == idx) ? 0 : HDA_AMP_MUTE; + snd_hda_codec_amp_stereo(codec, nid, HDA_INPUT, + imux->items[i].index, + HDA_AMP_MUTE, v); + } + *cur_val = idx; + return 1; +} + +/* + * 2ch mode + */ +static struct hda_verb alc882_3ST_ch2_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc882_3ST_ch6_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +static struct hda_channel_mode alc882_3ST_6ch_modes[2] = { + { 2, alc882_3ST_ch2_init }, + { 6, alc882_3ST_ch6_init }, +}; + +/* + * 6ch mode + */ +static struct hda_verb alc882_sixstack_ch6_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +/* + * 8ch mode + */ +static struct hda_verb alc882_sixstack_ch8_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +static struct hda_channel_mode alc882_sixstack_modes[2] = { + { 6, alc882_sixstack_ch6_init }, + { 8, alc882_sixstack_ch8_init }, +}; + +/* + * macbook pro ALC885 can switch LineIn to LineOut without loosing Mic + */ + +/* + * 2ch mode + */ +static struct hda_verb alc885_mbp_ch2_init[] = { + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc885_mbp_ch6_init[] = { + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + { } /* end */ +}; + +static struct hda_channel_mode alc885_mbp_6ch_modes[2] = { + { 2, alc885_mbp_ch2_init }, + { 6, alc885_mbp_ch6_init }, +}; + + +/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17 + * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b + */ +static struct snd_kcontrol_new alc882_base_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, 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), + { } /* end */ +}; + +static struct snd_kcontrol_new alc885_mbp3_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT), + HDA_CODEC_MUTE ("Speaker Playback Switch", 0x14, 0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x0d, 0x00, HDA_OUTPUT), + 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, 0x00, HDA_INPUT), + HDA_CODEC_MUTE ("Mic Playback Switch", 0x0b, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Line Boost", 0x1a, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0x00, HDA_INPUT), + { } /* end */ +}; +static struct snd_kcontrol_new alc882_w2jc_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc882_targa_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + 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("Mic Boost", 0x18, 0, 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("Front Mic Boost", 0x19, 0, HDA_INPUT), + { } /* end */ +}; + +/* Pin assignment: Front=0x14, HP = 0x15, Front = 0x16, ??? + * Front Mic=0x18, Line In = 0x1a, Line In = 0x1b, CD = 0x1c + */ +static struct snd_kcontrol_new alc882_asus_a7j_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Mobile Front Playback Switch", 0x16, 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("Mobile Line Playback Volume", 0x0b, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("Mobile Line Playback Switch", 0x0b, 0x03, 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("Mic Boost", 0x18, 0, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc882_asus_a7m_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 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_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc882_chmode_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_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, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Rear mixer */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* CLFE mixer */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Side mixer */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + /* Front Pin: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Rear Pin: output 1 (0x0d) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* CLFE Pin: output 2 (0x0e) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* Side Pin: output 3 (0x0f) */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line-2 In: Headphone output (output 0 - 0x0c) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + /* 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, AMP_IN_UNMUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* ADC1: mute amp left and right */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC2: mute amp left and right */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC3: mute amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + + { } +}; + +static struct hda_verb alc882_eapd_verbs[] = { + /* change to EAPD mode */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3060}, + { } +}; + +/* Mac Pro test */ +static struct snd_kcontrol_new alc882_macpro_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x18, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x02, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc882_macpro_init_verbs[] = { + /* Front mixer: unmute input/output amp left and right (volume = 0) */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Front Pin: output 0 (0x0c) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Speaker: output */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x04}, + /* Headphone output (output 0 - 0x0c) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* 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, AMP_IN_UNMUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* ADC1: mute amp left and right */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC2: mute amp left and right */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC3: mute amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + + { } +}; + +/* Macbook Pro rev3 */ +static struct hda_verb alc885_mbp3_init_verbs[] = { + /* Front mixer: unmute input/output amp left and right (volume = 0) */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Rear mixer */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Front Pin: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* HP Pin: output 0 (0x0d) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc4}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: use output 1 when in LineOut mode */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, + + /* 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, AMP_IN_UNMUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* ADC1: mute amp left and right */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC2: mute amp left and right */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC3: mute amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + + { } +}; + +/* iMac 24 mixer. */ +static struct snd_kcontrol_new alc885_imac24_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0x0c, 0x00, HDA_INPUT), + { } /* end */ +}; + +/* iMac 24 init verbs. */ +static struct hda_verb alc885_imac24_init_verbs[] = { + /* Internal speakers: output 0 (0x0c) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Internal speakers: output 0 (0x0c) */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Headphone: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* Front Mic: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + { } +}; + +/* Toggle speaker-output according to the hp-jack state */ +static void alc885_imac24_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x18, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +/* Processes unsolicited events. */ +static void alc885_imac24_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Headphone insertion or removal. */ + if ((res >> 26) == ALC880_HP_EVENT) + alc885_imac24_automute(codec); +} + +static void alc885_mbp3_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? 0 : HDA_AMP_MUTE); + +} +static void alc885_mbp3_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Headphone insertion or removal. */ + if ((res >> 26) == ALC880_HP_EVENT) + alc885_mbp3_automute(codec); +} + + +static struct hda_verb alc882_targa_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */ + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x01, AC_VERB_SET_GPIO_MASK, 0x03}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x03}, + { } /* end */ +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc882_targa_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_write_cache(codec, 1, 0, AC_VERB_SET_GPIO_DATA, + present ? 1 : 3); +} + +static void alc882_targa_unsol_event(struct hda_codec *codec, unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 4bit tag is placed at 26 bit! + */ + if (((res >> 26) == ALC880_HP_EVENT)) { + alc882_targa_automute(codec); + } +} + +static struct hda_verb alc882_asus_a7j_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */ + + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */ + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + { } /* end */ +}; + +static struct hda_verb alc882_asus_a7m_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + {0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front */ + + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */ + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + { } /* end */ +}; + +static void alc882_gpio_mute(struct hda_codec *codec, int pin, int muted) +{ + unsigned int gpiostate, gpiomask, gpiodir; + + gpiostate = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DATA, 0); + + if (!muted) + gpiostate |= (1 << pin); + else + gpiostate &= ~(1 << pin); + + gpiomask = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_MASK, 0); + gpiomask |= (1 << pin); + + gpiodir = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DIRECTION, 0); + gpiodir |= (1 << pin); + + + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_MASK, gpiomask); + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DIRECTION, gpiodir); + + msleep(1); + + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DATA, gpiostate); +} + +/* set up GPIO at initialization */ +static void alc885_macpro_init_hook(struct hda_codec *codec) +{ + alc882_gpio_mute(codec, 0, 0); + alc882_gpio_mute(codec, 1, 0); +} + +/* set up GPIO and update auto-muting at initialization */ +static void alc885_imac24_init_hook(struct hda_codec *codec) +{ + alc885_macpro_init_hook(codec); + alc885_imac24_automute(codec); +} + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc882_auto_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0f) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* 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))}, + + { } +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc882_capture_alt_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc882_mux_enum_info, + .get = alc882_mux_enum_get, + .put = alc882_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc882_capture_mixer[] = { + 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.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 3, + .info = alc882_mux_enum_info, + .get = alc882_mux_enum_get, + .put = alc882_mux_enum_put, + }, + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc882_loopbacks alc880_loopbacks +#endif + +/* pcm configuration: identiacal with ALC880 */ +#define alc882_pcm_analog_playback alc880_pcm_analog_playback +#define alc882_pcm_analog_capture alc880_pcm_analog_capture +#define alc882_pcm_digital_playback alc880_pcm_digital_playback +#define alc882_pcm_digital_capture alc880_pcm_digital_capture + +/* + * configuration and preset + */ +static const char *alc882_models[ALC882_MODEL_LAST] = { + [ALC882_3ST_DIG] = "3stack-dig", + [ALC882_6ST_DIG] = "6stack-dig", + [ALC882_ARIMA] = "arima", + [ALC882_W2JC] = "w2jc", + [ALC882_TARGA] = "targa", + [ALC882_ASUS_A7J] = "asus-a7j", + [ALC882_ASUS_A7M] = "asus-a7m", + [ALC885_MACPRO] = "macpro", + [ALC885_MBP3] = "mbp3", + [ALC885_IMAC24] = "imac24", + [ALC882_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc882_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x1043, 0x060d, "Asus A7J", ALC882_ASUS_A7J), + SND_PCI_QUIRK(0x1043, 0x1243, "Asus A7J", ALC882_ASUS_A7J), + SND_PCI_QUIRK(0x1043, 0x13c2, "Asus A7M", ALC882_ASUS_A7M), + SND_PCI_QUIRK(0x1043, 0x1971, "Asus W2JC", ALC882_W2JC), + SND_PCI_QUIRK(0x1043, 0x817f, "Asus P5LD2", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x1043, 0x81d8, "Asus P5WD", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte P35 DS3R", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x28fb, "Targa T8", ALC882_TARGA), /* MSI-1049 T8 */ + SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x161f, 0x2054, "Arima W820", ALC882_ARIMA), + {} +}; + +static struct alc_config_preset alc882_presets[] = { + [ALC882_3ST_DIG] = { + .mixers = { alc882_base_mixer }, + .init_verbs = { alc882_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc882_ch_modes), + .channel_mode = alc882_ch_modes, + .need_dac_fix = 1, + .input_mux = &alc882_capture_source, + }, + [ALC882_6ST_DIG] = { + .mixers = { alc882_base_mixer, alc882_chmode_mixer }, + .init_verbs = { alc882_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc882_sixstack_modes), + .channel_mode = alc882_sixstack_modes, + .input_mux = &alc882_capture_source, + }, + [ALC882_ARIMA] = { + .mixers = { alc882_base_mixer, alc882_chmode_mixer }, + .init_verbs = { alc882_init_verbs, alc882_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc882_sixstack_modes), + .channel_mode = alc882_sixstack_modes, + .input_mux = &alc882_capture_source, + }, + [ALC882_W2JC] = { + .mixers = { alc882_w2jc_mixer, alc882_chmode_mixer }, + .init_verbs = { alc882_init_verbs, alc882_eapd_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc882_capture_source, + .dig_out_nid = ALC882_DIGOUT_NID, + }, + [ALC885_MBP3] = { + .mixers = { alc885_mbp3_mixer, alc882_chmode_mixer }, + .init_verbs = { alc885_mbp3_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .channel_mode = alc885_mbp_6ch_modes, + .num_channel_mode = ARRAY_SIZE(alc885_mbp_6ch_modes), + .input_mux = &alc882_capture_source, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .unsol_event = alc885_mbp3_unsol_event, + .init_hook = alc885_mbp3_automute, + }, + [ALC885_MACPRO] = { + .mixers = { alc882_macpro_mixer }, + .init_verbs = { alc882_macpro_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc882_ch_modes), + .channel_mode = alc882_ch_modes, + .input_mux = &alc882_capture_source, + .init_hook = alc885_macpro_init_hook, + }, + [ALC885_IMAC24] = { + .mixers = { alc885_imac24_mixer }, + .init_verbs = { alc885_imac24_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc882_ch_modes), + .channel_mode = alc882_ch_modes, + .input_mux = &alc882_capture_source, + .unsol_event = alc885_imac24_unsol_event, + .init_hook = alc885_imac24_init_hook, + }, + [ALC882_TARGA] = { + .mixers = { alc882_targa_mixer, alc882_chmode_mixer, + alc882_capture_mixer }, + .init_verbs = { alc882_init_verbs, alc882_targa_verbs}, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .num_adc_nids = ARRAY_SIZE(alc882_adc_nids), + .adc_nids = alc882_adc_nids, + .capsrc_nids = alc882_capsrc_nids, + .num_channel_mode = ARRAY_SIZE(alc882_3ST_6ch_modes), + .channel_mode = alc882_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc882_capture_source, + .unsol_event = alc882_targa_unsol_event, + .init_hook = alc882_targa_automute, + }, + [ALC882_ASUS_A7J] = { + .mixers = { alc882_asus_a7j_mixer, alc882_chmode_mixer, + alc882_capture_mixer }, + .init_verbs = { alc882_init_verbs, alc882_asus_a7j_verbs}, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .num_adc_nids = ARRAY_SIZE(alc882_adc_nids), + .adc_nids = alc882_adc_nids, + .capsrc_nids = alc882_capsrc_nids, + .num_channel_mode = ARRAY_SIZE(alc882_3ST_6ch_modes), + .channel_mode = alc882_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc882_capture_source, + }, + [ALC882_ASUS_A7M] = { + .mixers = { alc882_asus_a7m_mixer, alc882_chmode_mixer }, + .init_verbs = { alc882_init_verbs, alc882_eapd_verbs, + alc880_gpio1_init_verbs, + alc882_asus_a7m_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .dig_out_nid = ALC882_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc880_threestack_modes), + .channel_mode = alc880_threestack_modes, + .need_dac_fix = 1, + .input_mux = &alc882_capture_source, + }, +}; + + +/* + * Pin config fixes + */ +enum { + PINFIX_ABIT_AW9D_MAX +}; + +static struct alc_pincfg alc882_abit_aw9d_pinfix[] = { + { 0x15, 0x01080104 }, /* side */ + { 0x16, 0x01011012 }, /* rear */ + { 0x17, 0x01016011 }, /* clfe */ + { } +}; + +static const struct alc_pincfg *alc882_pin_fixes[] = { + [PINFIX_ABIT_AW9D_MAX] = alc882_abit_aw9d_pinfix, +}; + +static struct snd_pci_quirk alc882_pinfix_tbl[] = { + SND_PCI_QUIRK(0x147b, 0x107a, "Abit AW9D-MAX", PINFIX_ABIT_AW9D_MAX), + {} +}; + +/* + * BIOS auto configuration + */ +static void alc882_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + /* set as output */ + struct alc_spec *spec = codec->spec; + int idx; + + alc_set_pin_output(codec, nid, pin_type); + if (spec->multiout.dac_nids[dac_idx] == 0x25) + idx = 4; + else + idx = spec->multiout.dac_nids[dac_idx] - 2; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, idx); + +} + +static void alc882_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x15, 0x1b, 0x14); + for (i = 0; i <= HDA_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + if (nid) + alc882_auto_set_output_and_unmute(codec, nid, pin_type, + i); + } +} + +static void alc882_auto_init_hp_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + /* use dac 0 */ + alc882_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); + pin = spec->autocfg.speaker_pins[0]; + if (pin) + alc882_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); +} + +#define alc882_is_input_pin(nid) alc880_is_input_pin(nid) +#define ALC882_PIN_CD_NID ALC880_PIN_CD_NID + +static void alc882_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + unsigned int vref; + if (!nid) + continue; + vref = PIN_IN; + if (1 /*i <= AUTO_PIN_FRONT_MIC*/) { + unsigned int pincap; + pincap = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP); + if ((pincap >> AC_PINCAP_VREF_SHIFT) & + AC_PINCAP_VREF_80) + vref = PIN_VREF80; + } + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, vref); + if (get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } +} + +static void alc882_auto_init_input_src(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + const struct hda_input_mux *imux = spec->input_mux; + int c; + + for (c = 0; c < spec->num_adc_nids; c++) { + hda_nid_t conn_list[HDA_MAX_NUM_INPUTS]; + hda_nid_t nid = spec->capsrc_nids[c]; + int conns, mute, idx, item; + + conns = snd_hda_get_connections(codec, nid, conn_list, + ARRAY_SIZE(conn_list)); + if (conns < 0) + continue; + for (idx = 0; idx < conns; idx++) { + /* if the current connection is the selected one, + * unmute it as default - otherwise mute it + */ + mute = AMP_IN_MUTE(idx); + for (item = 0; item < imux->num_items; item++) { + if (imux->items[item].index == idx) { + if (spec->cur_mux[c] == item) + mute = AMP_IN_UNMUTE(idx); + break; + } + } + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, mute); + } + } +} + +/* add mic boosts if needed */ +static int alc_auto_add_mic_boost(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + hda_nid_t nid; + + nid = spec->autocfg.input_pins[AUTO_PIN_MIC]; + if (nid && (get_wcaps(codec, nid) & AC_WCAP_IN_AMP)) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Mic Boost", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + } + nid = spec->autocfg.input_pins[AUTO_PIN_FRONT_MIC]; + if (nid && (get_wcaps(codec, nid) & AC_WCAP_IN_AMP)) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Front Mic Boost", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* almost identical with ALC880 parser... */ +static int alc882_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err = alc880_parse_auto_config(codec); + + if (err < 0) + return err; + else if (!err) + return 0; /* no config found */ + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + /* hack - override the init verbs */ + spec->init_verbs[0] = alc882_auto_init_verbs; + + return 1; /* config found */ +} + +/* additional initialization for auto-configuration model */ +static void alc882_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc882_auto_init_multi_out(codec); + alc882_auto_init_hp_out(codec); + alc882_auto_init_analog_input(codec); + alc882_auto_init_input_src(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +static int patch_alc883(struct hda_codec *codec); /* called in patch_alc882() */ + +static int patch_alc882(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err, board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC882_MODEL_LAST, + alc882_models, + alc882_cfg_tbl); + + if (board_config < 0 || board_config >= ALC882_MODEL_LAST) { + /* Pick up systems that don't supply PCI SSID */ + switch (codec->subsystem_id) { + case 0x106b0c00: /* Mac Pro */ + board_config = ALC885_MACPRO; + break; + case 0x106b1000: /* iMac 24 */ + case 0x106b2800: /* AppleTV */ + board_config = ALC885_IMAC24; + break; + case 0x106b00a0: /* MacBookPro3,1 - Another revision */ + case 0x106b00a1: /* Macbook (might be wrong - PCI SSID?) */ + case 0x106b00a4: /* MacbookPro4,1 */ + case 0x106b2c00: /* Macbook Pro rev3 */ + case 0x106b3600: /* Macbook 3.1 */ + case 0x106b3800: /* MacbookPro4,1 - latter revision */ + board_config = ALC885_MBP3; + break; + default: + /* ALC889A is handled better as ALC888-compatible */ + if (codec->revision_id == 0x100101 || + codec->revision_id == 0x100103) { + alc_free(codec); + return patch_alc883(codec); + } + printk(KERN_INFO "hda_codec: Unknown model for ALC882, " + "trying auto-probe from BIOS...\n"); + board_config = ALC882_AUTO; + } + } + + alc_fix_pincfg(codec, alc882_pinfix_tbl, alc882_pin_fixes); + + if (board_config == ALC882_AUTO) { + /* automatic parse from the BIOS config */ + err = alc882_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC882_3ST_DIG; + } + } + + if (board_config != ALC882_AUTO) + setup_preset(spec, &alc882_presets[board_config]); + + if (codec->vendor_id == 0x10ec0885) { + spec->stream_name_analog = "ALC885 Analog"; + spec->stream_name_digital = "ALC885 Digital"; + } else { + spec->stream_name_analog = "ALC882 Analog"; + spec->stream_name_digital = "ALC882 Digital"; + } + + spec->stream_analog_playback = &alc882_pcm_analog_playback; + spec->stream_analog_capture = &alc882_pcm_analog_capture; + /* FIXME: setup DAC5 */ + /*spec->stream_analog_alt_playback = &alc880_pcm_analog_alt_playback;*/ + spec->stream_analog_alt_capture = &alc880_pcm_analog_alt_capture; + + spec->stream_digital_playback = &alc882_pcm_digital_playback; + spec->stream_digital_capture = &alc882_pcm_digital_capture; + + if (!spec->adc_nids && spec->input_mux) { + /* check whether NID 0x07 is valid */ + unsigned int wcap = get_wcaps(codec, 0x07); + /* get type */ + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wcap != AC_WID_AUD_IN) { + spec->adc_nids = alc882_adc_nids_alt; + spec->num_adc_nids = ARRAY_SIZE(alc882_adc_nids_alt); + spec->capsrc_nids = alc882_capsrc_nids_alt; + spec->mixers[spec->num_mixers] = + alc882_capture_alt_mixer; + spec->num_mixers++; + } else { + spec->adc_nids = alc882_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc882_adc_nids); + spec->capsrc_nids = alc882_capsrc_nids; + spec->mixers[spec->num_mixers] = alc882_capture_mixer; + spec->num_mixers++; + } + } + + spec->vmaster_nid = 0x0c; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC882_AUTO) + spec->init_hook = alc882_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc882_loopbacks; +#endif + + return 0; +} + +/* + * ALC883 support + * + * ALC883 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). + */ +#define ALC883_DIGOUT_NID 0x06 +#define ALC883_DIGIN_NID 0x0a + +static hda_nid_t alc883_dac_nids[4] = { + /* front, rear, clfe, rear_surr */ + 0x02, 0x03, 0x04, 0x05 +}; + +static hda_nid_t alc883_adc_nids[2] = { + /* ADC1-2 */ + 0x08, 0x09, +}; + +static hda_nid_t alc883_capsrc_nids[2] = { 0x23, 0x22 }; + +/* input MUX */ +/* FIXME: should be a matrix-type input source selection */ + +static struct hda_input_mux alc883_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc883_3stack_6ch_intel = { + .num_items = 4, + .items = { + { "Mic", 0x1 }, + { "Front Mic", 0x0 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc883_lenovo_101e_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x1 }, + { "Line", 0x2 }, + }, +}; + +static struct hda_input_mux alc883_lenovo_nb0763_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "iMic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc883_fujitsu_pi2515_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + { "Int Mic", 0x1 }, + }, +}; + +static struct hda_input_mux alc883_lenovo_sky_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x4 }, + }, +}; + +static struct hda_input_mux alc883_asus_eee1601_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x0 }, + { "Line", 0x2 }, + }, +}; + +#define alc883_mux_enum_info alc_mux_enum_info +#define alc883_mux_enum_get alc_mux_enum_get +/* ALC883 has the ALC882-type input selection */ +#define alc883_mux_enum_put alc882_mux_enum_put + +/* + * 2ch mode + */ +static struct hda_channel_mode alc883_3ST_2ch_modes[1] = { + { 2, NULL } +}; + +/* + * 2ch mode + */ +static struct hda_verb alc883_3ST_ch2_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* + * 4ch mode + */ +static struct hda_verb alc883_3ST_ch4_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc883_3ST_ch6_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +static struct hda_channel_mode alc883_3ST_6ch_modes[3] = { + { 2, alc883_3ST_ch2_init }, + { 4, alc883_3ST_ch4_init }, + { 6, alc883_3ST_ch6_init }, +}; + +/* + * 2ch mode + */ +static struct hda_verb alc883_3ST_ch2_intel_init[] = { + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* + * 4ch mode + */ +static struct hda_verb alc883_3ST_ch4_intel_init[] = { + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc883_3ST_ch6_intel_init[] = { + { 0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x19, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +static struct hda_channel_mode alc883_3ST_6ch_intel_modes[3] = { + { 2, alc883_3ST_ch2_intel_init }, + { 4, alc883_3ST_ch4_intel_init }, + { 6, alc883_3ST_ch6_intel_init }, +}; + +/* + * 6ch mode + */ +static struct hda_verb alc883_sixstack_ch6_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +/* + * 8ch mode + */ +static struct hda_verb alc883_sixstack_ch8_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +static struct hda_channel_mode alc883_sixstack_modes[2] = { + { 6, alc883_sixstack_ch6_init }, + { 8, alc883_sixstack_ch8_init }, +}; + +static struct hda_verb alc883_medion_eapd_verbs[] = { + /* eanable EAPD on medion laptop */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3070}, + { } +}; + +/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17 + * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b + */ + +static struct snd_kcontrol_new alc883_base_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, 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", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_mitac_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_clevo_m720_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_2ch_fujitsu_pi2515_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_3ST_2ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, 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", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_3ST_6ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, 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", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_3ST_6ch_intel_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 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, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x0, 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", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_fivestack_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, 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", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_tagra_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + 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_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + 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_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_tagra_2ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front 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("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_lenovo_101e_2ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_lenovo_nb0763_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 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("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("iMic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("iMic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_medion_md2_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x15, 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("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, 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("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_acer_aspire_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), + 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_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc888_lenovo_sky_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0e, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0e, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", + 0x0d, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0d, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0d, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0d, 2, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Side Playback Volume", 0x0f, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("iSpeaker 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_VOLUME("Mic Boost", 0x18, 0, 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_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_bind_ctls alc883_bind_cap_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT), + HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT), + 0 + }, +}; + +static struct hda_bind_ctls alc883_bind_cap_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x08, 3, 0, HDA_INPUT), + HDA_COMPOSE_AMP_VAL(0x09, 3, 0, HDA_INPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc883_asus_eee1601_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x14, 0x0, HDA_OUTPUT), + 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_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_BIND_VOL("Capture Volume", &alc883_bind_cap_vol), + HDA_BIND_SW("Capture Switch", &alc883_bind_cap_switch), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc883_mux_enum_info, + .get = alc883_mux_enum_get, + .put = alc883_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc883_chmode_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct hda_verb alc883_init_verbs[] = { + /* ADC1: mute amp left and right */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* ADC2: mute amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Front mixer: unmute input/output amp left and right (volume = 0) */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Rear mixer */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* CLFE mixer */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Side mixer */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + /* mute analog input loopbacks */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* Front Pin: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Rear Pin: output 1 (0x0d) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* CLFE Pin: output 2 (0x0e) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* Side Pin: output 3 (0x0f) */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line-2 In: Headphone output (output 0 - 0x0c) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */ + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_mitac_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +/* auto-toggle front mic */ +/* +static void alc883_mitac_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, HDA_AMP_MUTE, bits); +} +*/ + +static void alc883_mitac_automute(struct hda_codec *codec) +{ + alc883_mitac_hp_automute(codec); + /* alc883_mitac_mic_automute(codec); */ +} + +static void alc883_mitac_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc883_mitac_hp_automute(codec); + break; + case ALC880_MIC_EVENT: + /* alc883_mitac_mic_automute(codec); */ + break; + } +} + +static struct hda_verb alc883_mitac_verbs[] = { + /* HP */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Subwoofer */ + {0x17, AC_VERB_SET_CONNECT_SEL, 0x02}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* enable unsolicited event */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN}, */ + + { } /* end */ +}; + +static struct hda_verb alc883_clevo_m720_verbs[] = { + /* HP */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Int speaker */ + {0x14, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* enable unsolicited event */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN}, + + { } /* end */ +}; + +static struct hda_verb alc883_2ch_fujitsu_pi2515_verbs[] = { + /* HP */ + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* Subwoofer */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* enable unsolicited event */ + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + + { } /* end */ +}; + +static struct hda_verb alc883_tagra_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* mic/clfe */ + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x01}, /* line/surround */ + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* HP */ + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x01, AC_VERB_SET_GPIO_MASK, 0x03}, + {0x01, AC_VERB_SET_GPIO_DIRECTION, 0x03}, + {0x01, AC_VERB_SET_GPIO_DATA, 0x03}, + + { } /* end */ +}; + +static struct hda_verb alc883_lenovo_101e_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_FRONT_EVENT|AC_USRSP_EN}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT|AC_USRSP_EN}, + { } /* end */ +}; + +static struct hda_verb alc883_lenovo_nb0763_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + { } /* end */ +}; + +static struct hda_verb alc888_lenovo_ms7195_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_FRONT_EVENT | AC_USRSP_EN}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +static struct hda_verb alc883_haier_w66_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + { } /* end */ +}; + +static struct hda_verb alc888_lenovo_sky_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +static struct hda_verb alc888_3st_hp_verbs[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Front: output 0 (0x0c) */ + {0x16, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Rear : output 1 (0x0d) */ + {0x18, AC_VERB_SET_CONNECT_SEL, 0x02}, /* CLFE : output 2 (0x0e) */ + { } +}; + +static struct hda_verb alc888_6st_dell_verbs[] = { + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } +}; + +static struct hda_verb alc888_3st_hp_2ch_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } +}; + +static struct hda_verb alc888_3st_hp_6ch_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { } +}; + +static struct hda_channel_mode alc888_3st_hp_modes[2] = { + { 2, alc888_3st_hp_2ch_init }, + { 6, alc888_3st_hp_6ch_init }, +}; + +/* toggle front-jack and RCA according to the hp-jack state */ +static void alc888_lenovo_ms7195_front_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +/* toggle RCA according to the front-jack state */ +static void alc888_lenovo_ms7195_rca_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc883_lenovo_ms7195_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc888_lenovo_ms7195_front_automute(codec); + if ((res >> 26) == ALC880_FRONT_EVENT) + alc888_lenovo_ms7195_rca_automute(codec); +} + +static struct hda_verb alc883_medion_md2_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_medion_md2_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc883_medion_md2_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_medion_md2_automute(codec); +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_tagra_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + snd_hda_codec_write_cache(codec, 1, 0, AC_VERB_SET_GPIO_DATA, + present ? 1 : 3); +} + +static void alc883_tagra_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_tagra_automute(codec); +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_clevo_m720_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc883_clevo_m720_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc883_clevo_m720_automute(struct hda_codec *codec) +{ + alc883_clevo_m720_hp_automute(codec); + alc883_clevo_m720_mic_automute(codec); +} + +static void alc883_clevo_m720_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc883_clevo_m720_hp_automute(codec); + break; + case ALC880_MIC_EVENT: + alc883_clevo_m720_mic_automute(codec); + break; + } +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_2ch_fujitsu_pi2515_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc883_2ch_fujitsu_pi2515_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_2ch_fujitsu_pi2515_automute(codec); +} + +static void alc883_haier_w66_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? 0x80 : 0; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + 0x80, bits); +} + +static void alc883_haier_w66_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_haier_w66_automute(codec); +} + +static void alc883_lenovo_101e_ispeaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc883_lenovo_101e_all_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc883_lenovo_101e_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_lenovo_101e_all_automute(codec); + if ((res >> 26) == ALC880_FRONT_EVENT) + alc883_lenovo_101e_ispeaker_automute(codec); +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_acer_aspire_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc883_acer_aspire_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc883_acer_aspire_automute(codec); +} + +static struct hda_verb alc883_acer_eapd_verbs[] = { + /* HP Pin: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Front Pin: output 0 (0x0c) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* eanable EAPD on medion laptop */ + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3050}, + /* enable unsolicited event */ + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } +}; + +static void alc888_6st_dell_front_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc888_6st_dell_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + printk("hp_event\n"); + alc888_6st_dell_front_automute(codec); + break; + } +} + +static void alc888_lenovo_sky_front_automute(struct hda_codec *codec) +{ + unsigned int mute; + unsigned int present; + + snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + present = (present & 0x80000000) != 0; + if (present) { + /* mute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + snd_hda_codec_amp_stereo(codec, 0x17, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } +} + +static void alc883_lenovo_sky_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc888_lenovo_sky_front_automute(codec); +} + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc883_auto_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0f) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */ + /* Input mixer1 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + /* {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + /* Input mixer2 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + /* {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + { } +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc883_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc882_mux_enum_info, + .get = alc882_mux_enum_get, + .put = alc882_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_verb alc888_asus_m90v_verbs[] = { + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* enable unsolicited event */ + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +static void alc883_nb_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); +} + +static void alc883_M90V_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? 0 : PIN_OUT; + snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); + snd_hda_codec_write(codec, 0x15, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); + snd_hda_codec_write(codec, 0x16, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); +} + +static void alc883_mode2_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc883_M90V_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc883_nb_mic_automute(codec); + break; + } +} + +static void alc883_mode2_inithook(struct hda_codec *codec) +{ + alc883_M90V_speaker_automute(codec); + alc883_nb_mic_automute(codec); +} + +static struct hda_verb alc888_asus_eee1601_verbs[] = { + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x20, AC_VERB_SET_COEF_INDEX, 0x0b}, + {0x20, AC_VERB_SET_PROC_COEF, 0x0838}, + /* enable unsolicited event */ + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +static void alc883_eee1601_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? 0 : PIN_OUT; + snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + bits); +} + +static void alc883_eee1601_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc883_eee1601_speaker_automute(codec); + break; + } +} + +static void alc883_eee1601_inithook(struct hda_codec *codec) +{ + alc883_eee1601_speaker_automute(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc883_loopbacks alc880_loopbacks +#endif + +/* pcm configuration: identiacal with ALC880 */ +#define alc883_pcm_analog_playback alc880_pcm_analog_playback +#define alc883_pcm_analog_capture alc880_pcm_analog_capture +#define alc883_pcm_analog_alt_capture alc880_pcm_analog_alt_capture +#define alc883_pcm_digital_playback alc880_pcm_digital_playback +#define alc883_pcm_digital_capture alc880_pcm_digital_capture + +/* + * configuration and preset + */ +static const char *alc883_models[ALC883_MODEL_LAST] = { + [ALC883_3ST_2ch_DIG] = "3stack-dig", + [ALC883_3ST_6ch_DIG] = "3stack-6ch-dig", + [ALC883_3ST_6ch] = "3stack-6ch", + [ALC883_6ST_DIG] = "6stack-dig", + [ALC883_TARGA_DIG] = "targa-dig", + [ALC883_TARGA_2ch_DIG] = "targa-2ch-dig", + [ALC883_ACER] = "acer", + [ALC883_ACER_ASPIRE] = "acer-aspire", + [ALC883_MEDION] = "medion", + [ALC883_MEDION_MD2] = "medion-md2", + [ALC883_LAPTOP_EAPD] = "laptop-eapd", + [ALC883_LENOVO_101E_2ch] = "lenovo-101e", + [ALC883_LENOVO_NB0763] = "lenovo-nb0763", + [ALC888_LENOVO_MS7195_DIG] = "lenovo-ms7195-dig", + [ALC888_LENOVO_SKY] = "lenovo-sky", + [ALC883_HAIER_W66] = "haier-w66", + [ALC888_3ST_HP] = "3stack-hp", + [ALC888_6ST_DELL] = "6stack-dell", + [ALC883_MITAC] = "mitac", + [ALC883_CLEVO_M720] = "clevo-m720", + [ALC883_FUJITSU_PI2515] = "fujitsu-pi2515", + [ALC883_3ST_6ch_INTEL] = "3stack-6ch-intel", + [ALC883_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc883_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC883_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1025, 0x006c, "Acer Aspire 9810", ALC883_ACER_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x0090, "Acer Aspire", ALC883_ACER_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x0110, "Acer Aspire", ALC883_ACER_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x0112, "Acer Aspire 9303", ALC883_ACER_ASPIRE), + SND_PCI_QUIRK(0x1025, 0x0121, "Acer Aspire 5920G", ALC883_ACER_ASPIRE), + SND_PCI_QUIRK(0x1025, 0, "Acer laptop", ALC883_ACER), /* default Acer */ + SND_PCI_QUIRK(0x1028, 0x020d, "Dell Inspiron 530", ALC888_6ST_DELL), + SND_PCI_QUIRK(0x103c, 0x2a3d, "HP Pavillion", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x103c, 0x2a4f, "HP Samba", ALC888_3ST_HP), + SND_PCI_QUIRK(0x103c, 0x2a60, "HP Lucknow", ALC888_3ST_HP), + SND_PCI_QUIRK(0x103c, 0x2a61, "HP Nettle", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1043, 0x1873, "Asus M90V", ALC888_ASUS_M90V), + SND_PCI_QUIRK(0x1043, 0x8249, "Asus M2A-VM HDMI", ALC883_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1043, 0x835f, "Asus Eee 1601", ALC888_ASUS_EEE1601), + SND_PCI_QUIRK(0x105b, 0x0ce8, "Foxconn P35AX-S", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1071, 0x8253, "Mitac 8252d", ALC883_MITAC), + SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC883_LAPTOP_EAPD), + SND_PCI_QUIRK(0x10f1, 0x2350, "TYAN-S2350", ALC888_6ST_DELL), + SND_PCI_QUIRK(0x108e, 0x534d, NULL, ALC883_3ST_6ch), + SND_PCI_QUIRK(0x1458, 0xa002, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x0349, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x040d, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x0579, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x2fb3, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x3729, "MSI S420", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3783, "NEC S970", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3b7f, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x3ef9, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3fc1, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3fc3, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3fcc, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3fdf, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x4314, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x4319, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x4324, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7187, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7250, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7267, "MSI", ALC883_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1462, 0x7280, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7327, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0xa422, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x147b, 0x1083, "Abit IP35-PRO", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1558, 0x0721, "Clevo laptop M720R", ALC883_CLEVO_M720), + SND_PCI_QUIRK(0x1558, 0x0722, "Clevo laptop M720SR", ALC883_CLEVO_M720), + SND_PCI_QUIRK(0x1558, 0, "Clevo laptop", ALC883_LAPTOP_EAPD), + SND_PCI_QUIRK(0x15d9, 0x8780, "Supermicro PDSBA", ALC883_3ST_6ch), + SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_MEDION), + SND_PCI_QUIRK(0x1734, 0x1107, "FSC AMILO Xi2550", + ALC883_FUJITSU_PI2515), + SND_PCI_QUIRK(0x1734, 0x1108, "Fujitsu AMILO Pi2515", ALC883_FUJITSU_PI2515), + SND_PCI_QUIRK(0x17aa, 0x101e, "Lenovo 101e", ALC883_LENOVO_101E_2ch), + SND_PCI_QUIRK(0x17aa, 0x2085, "Lenovo NB0763", ALC883_LENOVO_NB0763), + SND_PCI_QUIRK(0x17aa, 0x3bfc, "Lenovo NB0763", ALC883_LENOVO_NB0763), + SND_PCI_QUIRK(0x17aa, 0x3bfd, "Lenovo NB0763", ALC883_LENOVO_NB0763), + SND_PCI_QUIRK(0x17aa, 0x101d, "Lenovo Sky", ALC888_LENOVO_SKY), + SND_PCI_QUIRK(0x17c0, 0x4071, "MEDION MD2", ALC883_MEDION_MD2), + SND_PCI_QUIRK(0x17c0, 0x4085, "MEDION MD96630", ALC888_LENOVO_MS7195_DIG), + SND_PCI_QUIRK(0x17f2, 0x5000, "Albatron KI690-AM2", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1991, 0x5625, "Haier W66", ALC883_HAIER_W66), + SND_PCI_QUIRK(0x8086, 0x0001, "DG33BUC", ALC883_3ST_6ch_INTEL), + SND_PCI_QUIRK(0x8086, 0x0002, "DG33FBC", ALC883_3ST_6ch_INTEL), + SND_PCI_QUIRK(0x8086, 0xd601, "D102GGC", ALC883_3ST_6ch), + {} +}; + +static struct alc_config_preset alc883_presets[] = { + [ALC883_3ST_2ch_DIG] = { + .mixers = { alc883_3ST_2ch_mixer }, + .init_verbs = { alc883_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + }, + [ALC883_3ST_6ch_DIG] = { + .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + }, + [ALC883_3ST_6ch] = { + .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + }, + [ALC883_3ST_6ch_INTEL] = { + .mixers = { alc883_3ST_6ch_intel_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_intel_modes), + .channel_mode = alc883_3ST_6ch_intel_modes, + .need_dac_fix = 1, + .input_mux = &alc883_3stack_6ch_intel, + }, + [ALC883_6ST_DIG] = { + .mixers = { alc883_base_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), + .channel_mode = alc883_sixstack_modes, + .input_mux = &alc883_capture_source, + }, + [ALC883_TARGA_DIG] = { + .mixers = { alc883_tagra_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc883_tagra_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_tagra_unsol_event, + .init_hook = alc883_tagra_automute, + }, + [ALC883_TARGA_2ch_DIG] = { + .mixers = { alc883_tagra_2ch_mixer}, + .init_verbs = { alc883_init_verbs, alc883_tagra_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_tagra_unsol_event, + .init_hook = alc883_tagra_automute, + }, + [ALC883_ACER] = { + .mixers = { alc883_base_mixer }, + /* On TravelMate laptops, GPIO 0 enables the internal speaker + * and the headphone jack. Turn this on and rely on the + * standard mute methods whenever the user wants to turn + * these outputs off. + */ + .init_verbs = { alc883_init_verbs, alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + }, + [ALC883_ACER_ASPIRE] = { + .mixers = { alc883_acer_aspire_mixer }, + .init_verbs = { alc883_init_verbs, alc883_acer_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_acer_aspire_unsol_event, + .init_hook = alc883_acer_aspire_automute, + }, + [ALC883_MEDION] = { + .mixers = { alc883_fivestack_mixer, + alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, + alc883_medion_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), + .channel_mode = alc883_sixstack_modes, + .input_mux = &alc883_capture_source, + }, + [ALC883_MEDION_MD2] = { + .mixers = { alc883_medion_md2_mixer}, + .init_verbs = { alc883_init_verbs, alc883_medion_md2_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_medion_md2_unsol_event, + .init_hook = alc883_medion_md2_automute, + }, + [ALC883_LAPTOP_EAPD] = { + .mixers = { alc883_base_mixer }, + .init_verbs = { alc883_init_verbs, alc882_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + }, + [ALC883_CLEVO_M720] = { + .mixers = { alc883_clevo_m720_mixer }, + .init_verbs = { alc883_init_verbs, alc883_clevo_m720_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_clevo_m720_unsol_event, + .init_hook = alc883_clevo_m720_automute, + }, + [ALC883_LENOVO_101E_2ch] = { + .mixers = { alc883_lenovo_101e_2ch_mixer}, + .init_verbs = { alc883_init_verbs, alc883_lenovo_101e_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_lenovo_101e_capture_source, + .unsol_event = alc883_lenovo_101e_unsol_event, + .init_hook = alc883_lenovo_101e_all_automute, + }, + [ALC883_LENOVO_NB0763] = { + .mixers = { alc883_lenovo_nb0763_mixer }, + .init_verbs = { alc883_init_verbs, alc883_lenovo_nb0763_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_lenovo_nb0763_capture_source, + .unsol_event = alc883_medion_md2_unsol_event, + .init_hook = alc883_medion_md2_automute, + }, + [ALC888_LENOVO_MS7195_DIG] = { + .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_lenovo_ms7195_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_lenovo_ms7195_unsol_event, + .init_hook = alc888_lenovo_ms7195_front_automute, + }, + [ALC883_HAIER_W66] = { + .mixers = { alc883_tagra_2ch_mixer}, + .init_verbs = { alc883_init_verbs, alc883_haier_w66_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_haier_w66_unsol_event, + .init_hook = alc883_haier_w66_automute, + }, + [ALC888_3ST_HP] = { + .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_3st_hp_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc888_3st_hp_modes), + .channel_mode = alc888_3st_hp_modes, + .need_dac_fix = 1, + .input_mux = &alc883_capture_source, + }, + [ALC888_6ST_DELL] = { + .mixers = { alc883_base_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_6st_dell_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), + .channel_mode = alc883_sixstack_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc888_6st_dell_unsol_event, + .init_hook = alc888_6st_dell_front_automute, + }, + [ALC883_MITAC] = { + .mixers = { alc883_mitac_mixer }, + .init_verbs = { alc883_init_verbs, alc883_mitac_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + .unsol_event = alc883_mitac_unsol_event, + .init_hook = alc883_mitac_automute, + }, + [ALC883_FUJITSU_PI2515] = { + .mixers = { alc883_2ch_fujitsu_pi2515_mixer }, + .init_verbs = { alc883_init_verbs, + alc883_2ch_fujitsu_pi2515_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_fujitsu_pi2515_capture_source, + .unsol_event = alc883_2ch_fujitsu_pi2515_unsol_event, + .init_hook = alc883_2ch_fujitsu_pi2515_automute, + }, + [ALC888_LENOVO_SKY] = { + .mixers = { alc888_lenovo_sky_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_lenovo_sky_verbs}, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc883_sixstack_modes), + .channel_mode = alc883_sixstack_modes, + .need_dac_fix = 1, + .input_mux = &alc883_lenovo_sky_capture_source, + .unsol_event = alc883_lenovo_sky_unsol_event, + .init_hook = alc888_lenovo_sky_front_automute, + }, + [ALC888_ASUS_M90V] = { + .mixers = { alc883_3ST_6ch_mixer, alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc888_asus_m90v_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_6ch_modes), + .channel_mode = alc883_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_fujitsu_pi2515_capture_source, + .unsol_event = alc883_mode2_unsol_event, + .init_hook = alc883_mode2_inithook, + }, + [ALC888_ASUS_EEE1601] = { + .mixers = { alc883_asus_eee1601_mixer }, + .init_verbs = { alc883_init_verbs, alc888_asus_eee1601_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .dig_out_nid = ALC883_DIGOUT_NID, + .dig_in_nid = ALC883_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .need_dac_fix = 1, + .input_mux = &alc883_asus_eee1601_capture_source, + .unsol_event = alc883_eee1601_unsol_event, + .init_hook = alc883_eee1601_inithook, + }, +}; + + +/* + * BIOS auto configuration + */ +static void alc883_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + /* set as output */ + struct alc_spec *spec = codec->spec; + int idx; + + alc_set_pin_output(codec, nid, pin_type); + if (spec->multiout.dac_nids[dac_idx] == 0x25) + idx = 4; + else + idx = spec->multiout.dac_nids[dac_idx] - 2; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, idx); + +} + +static void alc883_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x15, 0x1b, 0x14); + for (i = 0; i <= HDA_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + if (nid) + alc883_auto_set_output_and_unmute(codec, nid, pin_type, + i); + } +} + +static void alc883_auto_init_hp_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + /* use dac 0 */ + alc883_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); + pin = spec->autocfg.speaker_pins[0]; + if (pin) + alc883_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); +} + +#define alc883_is_input_pin(nid) alc880_is_input_pin(nid) +#define ALC883_PIN_CD_NID ALC880_PIN_CD_NID + +static void alc883_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (alc883_is_input_pin(nid)) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + (i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN)); + if (nid != ALC883_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } + } +} + +#define alc883_auto_init_input_src alc882_auto_init_input_src + +/* almost identical with ALC880 parser... */ +static int alc883_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err = alc880_parse_auto_config(codec); + + if (err < 0) + return err; + else if (!err) + return 0; /* no config found */ + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + /* hack - override the init verbs */ + spec->init_verbs[0] = alc883_auto_init_verbs; + spec->mixers[spec->num_mixers] = alc883_capture_mixer; + spec->num_mixers++; + + return 1; /* config found */ +} + +/* additional initialization for auto-configuration model */ +static void alc883_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc883_auto_init_multi_out(codec); + alc883_auto_init_hp_out(codec); + alc883_auto_init_analog_input(codec); + alc883_auto_init_input_src(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +static int patch_alc883(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err, board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + alc_fix_pll_init(codec, 0x20, 0x0a, 10); + + board_config = snd_hda_check_board_config(codec, ALC883_MODEL_LAST, + alc883_models, + alc883_cfg_tbl); + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC883, " + "trying auto-probe from BIOS...\n"); + board_config = ALC883_AUTO; + } + + if (board_config == ALC883_AUTO) { + /* automatic parse from the BIOS config */ + err = alc883_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC883_3ST_2ch_DIG; + } + } + + if (board_config != ALC883_AUTO) + setup_preset(spec, &alc883_presets[board_config]); + + switch (codec->vendor_id) { + case 0x10ec0888: + if (codec->revision_id == 0x100101) { + spec->stream_name_analog = "ALC1200 Analog"; + spec->stream_name_digital = "ALC1200 Digital"; + } else { + spec->stream_name_analog = "ALC888 Analog"; + spec->stream_name_digital = "ALC888 Digital"; + } + break; + case 0x10ec0889: + spec->stream_name_analog = "ALC889 Analog"; + spec->stream_name_digital = "ALC889 Digital"; + break; + default: + spec->stream_name_analog = "ALC883 Analog"; + spec->stream_name_digital = "ALC883 Digital"; + break; + } + + spec->stream_analog_playback = &alc883_pcm_analog_playback; + spec->stream_analog_capture = &alc883_pcm_analog_capture; + spec->stream_analog_alt_capture = &alc883_pcm_analog_alt_capture; + + spec->stream_digital_playback = &alc883_pcm_digital_playback; + spec->stream_digital_capture = &alc883_pcm_digital_capture; + + spec->num_adc_nids = ARRAY_SIZE(alc883_adc_nids); + spec->adc_nids = alc883_adc_nids; + spec->capsrc_nids = alc883_capsrc_nids; + + spec->vmaster_nid = 0x0c; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC883_AUTO) + spec->init_hook = alc883_auto_init; + +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc883_loopbacks; +#endif + + return 0; +} + +/* + * ALC262 support + */ + +#define ALC262_DIGOUT_NID ALC880_DIGOUT_NID +#define ALC262_DIGIN_NID ALC880_DIGIN_NID + +#define alc262_dac_nids alc260_dac_nids +#define alc262_adc_nids alc882_adc_nids +#define alc262_adc_nids_alt alc882_adc_nids_alt +#define alc262_capsrc_nids alc882_capsrc_nids +#define alc262_capsrc_nids_alt alc882_capsrc_nids_alt + +#define alc262_modes alc260_modes +#define alc262_capture_source alc882_capture_source + +static hda_nid_t alc262_dmic_adc_nids[1] = { + /* ADC0 */ + 0x09 +}; + +static hda_nid_t alc262_dmic_capsrc_nids[1] = { 0x22 }; + +static struct snd_kcontrol_new alc262_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("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("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + /* HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Playback Switch", 0x0b, 0x05, HDA_INPUT), */ + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0D, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Mono Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_hippo1_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 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("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + /* HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Playback Switch", 0x0b, 0x05, HDA_INPUT), */ + /*HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0D, 0x0, HDA_OUTPUT),*/ + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* update HP, line and mono-out pins according to the master switch */ +static void alc262_hp_master_update(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int val = spec->master_sw; + + /* HP & line-out */ + snd_hda_codec_write_cache(codec, 0x1b, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + val ? PIN_HP : 0); + snd_hda_codec_write_cache(codec, 0x15, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + val ? PIN_HP : 0); + /* mono (speaker) depending on the HP jack sense */ + val = val && !spec->jack_present; + snd_hda_codec_write_cache(codec, 0x16, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + val ? PIN_OUT : 0); +} + +static void alc262_hp_bpc_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int presence; + presence = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = !!(presence & AC_PINSENSE_PRESENCE); + alc262_hp_master_update(codec); +} + +static void alc262_hp_bpc_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hp_bpc_automute(codec); +} + +static void alc262_hp_wildwest_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int presence; + presence = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = !!(presence & AC_PINSENSE_PRESENCE); + alc262_hp_master_update(codec); +} + +static void alc262_hp_wildwest_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hp_wildwest_automute(codec); +} + +static int alc262_hp_master_sw_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + *ucontrol->value.integer.value = spec->master_sw; + return 0; +} + +static int alc262_hp_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + int val = !!*ucontrol->value.integer.value; + + if (val == spec->master_sw) + return 0; + spec->master_sw = val; + alc262_hp_master_update(codec); + return 1; +} + +static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = alc262_hp_master_sw_get, + .put = alc262_hp_master_sw_put, + }, + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 2, 0x0, + HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 2, 0x0, + HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, 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("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Playback Switch", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("AUX IN Playback Volume", 0x0b, 0x06, HDA_INPUT), + HDA_CODEC_MUTE("AUX IN Playback Switch", 0x0b, 0x06, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_HP_BPC_WildWest_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = alc262_hp_master_sw_get, + .put = alc262_hp_master_sw_put, + }, + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 2, 0x0, + HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 2, 0x0, + HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x1a, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Playback Switch", 0x0b, 0x05, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_HP_BPC_WildWest_option_mixer[] = { + HDA_CODEC_VOLUME("Rear Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Rear Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Rear Mic Boost", 0x18, 0, HDA_INPUT), + { } /* end */ +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_hp_t5735_automute(struct hda_codec *codec, int force) +{ + struct alc_spec *spec = codec->spec; + + if (force || !spec->sense_updated) { + unsigned int present; + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; + spec->sense_updated = 1; + } + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_OUTPUT, 0, HDA_AMP_MUTE, + spec->jack_present ? HDA_AMP_MUTE : 0); +} + +static void alc262_hp_t5735_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hp_t5735_automute(codec, 1); +} + +static void alc262_hp_t5735_init_hook(struct hda_codec *codec) +{ + alc262_hp_t5735_automute(codec, 1); +} + +static struct snd_kcontrol_new alc262_hp_t5735_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc262_hp_t5735_verbs[] = { + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } +}; + +static struct snd_kcontrol_new alc262_hp_rp5700_mixer[] = { + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0e, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x16, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x01, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc262_hp_rp5700_verbs[] = { + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x00 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x00 << 8))}, + {} +}; + +static struct hda_input_mux alc262_hp_rp5700_capture_source = { + .num_items = 1, + .items = { + { "Line", 0x1 }, + }, +}; + +/* bind hp and internal speaker mute (with plug check) */ +static int alc262_sony_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + /* change hp mute */ + change = snd_hda_codec_amp_update(codec, 0x15, 0, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[0] ? 0 : HDA_AMP_MUTE); + change |= snd_hda_codec_amp_update(codec, 0x15, 1, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[1] ? 0 : HDA_AMP_MUTE); + if (change) { + /* change speaker according to HP jack state */ + struct alc_spec *spec = codec->spec; + unsigned int mute; + if (spec->jack_present) + mute = HDA_AMP_MUTE; + else + mute = snd_hda_codec_amp_read(codec, 0x15, 0, + HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } + return change; +} + +static struct snd_kcontrol_new alc262_sony_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc262_sony_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_benq_t31_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + { } /* end */ +}; + +#define alc262_capture_mixer alc882_capture_mixer +#define alc262_capture_alt_mixer alc882_capture_alt_mixer + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc262_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + + /* 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))}, + + { } +}; + +static struct hda_verb alc262_eapd_verbs[] = { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 2}, + { } +}; + +static struct hda_verb alc262_hippo_unsol_verbs[] = { + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {} +}; + +static struct hda_verb alc262_hippo1_unsol_verbs[] = { + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0x0000}, + + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {} +}; + +static struct hda_verb alc262_sony_unsol_verbs[] = { + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, // Front Mic + + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {} +}; + +static struct hda_input_mux alc262_dmic_capture_source = { + .num_items = 2, + .items = { + { "Int DMic", 0x9 }, + { "Mic", 0x0 }, + }, +}; + +static struct snd_kcontrol_new alc262_toshiba_s06_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_verb alc262_toshiba_s06_verbs[] = { + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x22, AC_VERB_SET_CONNECT_SEL, 0x09}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static void alc262_dmic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x22, 0, + AC_VERB_SET_CONNECT_SEL, present ? 0x0 : 0x09); +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc262_toshiba_s06_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? 0 : PIN_OUT; + snd_hda_codec_write(codec, 0x14, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, bits); +} + + + +/* unsolicited event for HP jack sensing */ +static void alc262_toshiba_s06_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc262_toshiba_s06_speaker_automute(codec); + if ((res >> 26) == ALC880_MIC_EVENT) + alc262_dmic_automute(codec); + +} + +static void alc262_toshiba_s06_init_hook(struct hda_codec *codec) +{ + alc262_toshiba_s06_speaker_automute(codec); + alc262_dmic_automute(codec); +} + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_hippo_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int mute; + unsigned int present; + + /* need to execute and sync at first */ + snd_hda_codec_read(codec, 0x15, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & 0x80000000) != 0; + if (spec->jack_present) { + /* mute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x15, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } +} + +/* unsolicited event for HP jack sensing */ +static void alc262_hippo_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hippo_automute(codec); +} + +static void alc262_hippo1_automute(struct hda_codec *codec) +{ + unsigned int mute; + unsigned int present; + + snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + present = (present & 0x80000000) != 0; + if (present) { + /* mute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } +} + +/* unsolicited event for HP jack sensing */ +static void alc262_hippo1_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_hippo1_automute(codec); +} + +/* + * nec model + * 0x15 = headphone + * 0x16 = internal speaker + * 0x18 = external mic + */ + +static struct snd_kcontrol_new alc262_nec_mixer[] = { + HDA_CODEC_VOLUME_MONO("Speaker Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Speaker Playback Switch", 0x16, 0, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct hda_verb alc262_nec_verbs[] = { + /* Unmute Speaker */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Headphone */ + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* External mic to headphone */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* External mic to speaker */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {} +}; + +/* + * fujitsu model + * 0x14 = headphone/spdif-out, 0x15 = internal speaker, + * 0x1b = port replicator headphone out + */ + +#define ALC_HP_EVENT 0x37 + +static struct hda_verb alc262_fujitsu_unsol_verbs[] = { + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {} +}; + +static struct hda_verb alc262_lenovo_3000_unsol_verbs[] = { + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC_HP_EVENT}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {} +}; + +static struct hda_input_mux alc262_fujitsu_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Int Mic", 0x1 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc262_HP_capture_source = { + .num_items = 5, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + { "AUX IN", 0x6 }, + }, +}; + +static struct hda_input_mux alc262_HP_D7000_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x2 }, + { "Line", 0x1 }, + { "CD", 0x4 }, + }, +}; + +/* mute/unmute internal speaker according to the hp jacks and mute state */ +static void alc262_fujitsu_automute(struct hda_codec *codec, int force) +{ + struct alc_spec *spec = codec->spec; + unsigned int mute; + + if (force || !spec->sense_updated) { + unsigned int present; + /* need to execute and sync at first */ + snd_hda_codec_read(codec, 0x14, 0, AC_VERB_SET_PIN_SENSE, 0); + /* check laptop HP jack */ + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0); + /* need to execute and sync at first */ + snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); + /* check docking HP jack */ + present |= snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + if (present & AC_PINSENSE_PRESENCE) + spec->jack_present = 1; + else + spec->jack_present = 0; + spec->sense_updated = 1; + } + /* unmute internal speaker only if both HPs are unplugged and + * master switch is on + */ + if (spec->jack_present) + mute = HDA_AMP_MUTE; + else + mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); +} + +/* unsolicited event for HP jack sensing */ +static void alc262_fujitsu_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC_HP_EVENT) + return; + alc262_fujitsu_automute(codec, 1); +} + +static void alc262_fujitsu_init_hook(struct hda_codec *codec) +{ + alc262_fujitsu_automute(codec, 1); +} + +/* bind volumes of both NID 0x0c and 0x0d */ +static struct hda_bind_ctls alc262_fujitsu_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x0c, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x0d, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_lenovo_3000_automute(struct hda_codec *codec, int force) +{ + struct alc_spec *spec = codec->spec; + unsigned int mute; + + if (force || !spec->sense_updated) { + unsigned int present_int_hp; + /* need to execute and sync at first */ + snd_hda_codec_read(codec, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); + present_int_hp = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present_int_hp & 0x80000000) != 0; + spec->sense_updated = 1; + } + if (spec->jack_present) { + /* mute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + snd_hda_codec_amp_stereo(codec, 0x16, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } +} + +/* unsolicited event for HP jack sensing */ +static void alc262_lenovo_3000_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC_HP_EVENT) + return; + alc262_lenovo_3000_automute(codec, 1); +} + +/* bind hp and internal speaker mute (with plug check) */ +static int alc262_fujitsu_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + change = snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp ? 0 : HDA_AMP_MUTE); + change |= snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp ? 0 : HDA_AMP_MUTE); + + if (change) + alc262_fujitsu_automute(codec, 0); + return change; +} + +static struct snd_kcontrol_new alc262_fujitsu_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc262_fujitsu_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, 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("PC Speaker Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Switch", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, 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("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +/* bind hp and internal speaker mute (with plug check) */ +static int alc262_lenovo_3000_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + change = snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp ? 0 : HDA_AMP_MUTE); + + if (change) + alc262_lenovo_3000_automute(codec, 0); + return change; +} + +static struct snd_kcontrol_new alc262_lenovo_3000_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc262_lenovo_3000_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, 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("Mic Boost", 0x18, 0, 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("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc262_toshiba_rx1_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc262_fujitsu_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc262_sony_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + { } /* end */ +}; + +/* additional init verbs for Benq laptops */ +static struct hda_verb alc262_EAPD_verbs[] = { + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3070}, + {} +}; + +static struct hda_verb alc262_benq_t31_EAPD_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + + {0x20, AC_VERB_SET_COEF_INDEX, 0x07}, + {0x20, AC_VERB_SET_PROC_COEF, 0x3050}, + {} +}; + +/* Samsung Q1 Ultra Vista model setup */ +static struct snd_kcontrol_new alc262_ultra_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Master Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Mic Boost", 0x15, 0, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc262_ultra_verbs[] = { + /* output mixer */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + /* speaker */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* HP */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + /* internal mic */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* ADC, choose mic */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(8)}, + {} +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_ultra_automute(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + unsigned int mute; + + mute = 0; + /* auto-mute only when HP is used as HP */ + if (!spec->cur_mux[0]) { + unsigned int present; + /* need to execute and sync at first */ + snd_hda_codec_read(codec, 0x15, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & AC_PINSENSE_PRESENCE) != 0; + if (spec->jack_present) + mute = HDA_AMP_MUTE; + } + /* mute/unmute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + /* mute/unmute HP */ + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute ? 0 : HDA_AMP_MUTE); +} + +/* unsolicited event for HP jack sensing */ +static void alc262_ultra_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc262_ultra_automute(codec); +} + +static struct hda_input_mux alc262_ultra_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x1 }, + { "Headphone", 0x7 }, + }, +}; + +static int alc262_ultra_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct alc_spec *spec = codec->spec; + int ret; + + ret = alc882_mux_enum_put(kcontrol, ucontrol); + if (!ret) + return 0; + /* reprogram the HP pin as mic or HP according to the input source */ + snd_hda_codec_write_cache(codec, 0x15, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + spec->cur_mux[0] ? PIN_VREF80 : PIN_HP); + alc262_ultra_automute(codec); /* mute/unmute HP */ + return ret; +} + +static struct snd_kcontrol_new alc262_ultra_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x07, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x07, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .info = alc882_mux_enum_info, + .get = alc882_mux_enum_get, + .put = alc262_ultra_mux_enum_put, + }, + { } /* end */ +}; + +/* add playback controls from the parsed DAC table */ +static int alc262_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int err; + + spec->multiout.num_dacs = 1; /* only use one dac */ + spec->multiout.dac_nids = spec->private_dac_nids; + spec->multiout.dac_nids[0] = 2; + + nid = cfg->line_out_pins[0]; + if (nid) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x0c, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Front Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + + nid = cfg->speaker_pins[0]; + if (nid) { + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Speaker Playback Volume", + HDA_COMPOSE_AMP_VAL(0x0e, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Speaker Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Speaker Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + nid = cfg->hp_pins[0]; + if (nid) { + /* spec->multiout.hp_nid = 2; */ + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(0x0e, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +/* identical with ALC880 */ +#define alc262_auto_create_analog_input_ctls \ + alc880_auto_create_analog_input_ctls + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc262_volume_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0f) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* 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))}, + + { } +}; + +static struct hda_verb alc262_HP_BPC_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + + + /* 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, (0x7000 | (0x03 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + + { } +}; + +static struct hda_verb alc262_HP_BPC_WildWest_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for front + * panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(6)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(7)}, + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Mono */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* rear MIC */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* Line in */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Front MIC */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Line out */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD in */ + + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + + /* {0x14, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, 0x7023 }, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, 0x7000 }, + + /* 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))}, /*rear MIC*/ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, /*Line in*/ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, /*F MIC*/ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, /*Front*/ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, /*CD*/ + /* {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))}, */ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))}, /*HP*/ + /* Input mixer2 */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + /* {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))}, */ + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))}, + /* Input mixer3 */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x00 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x03 << 8))}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x04 << 8))}, + /* {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x06 << 8))}, */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x07 << 8))}, + + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + + { } +}; + +static struct hda_verb alc262_toshiba_rx1_unsol_verbs[] = { + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Front Speaker */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x01}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* MIC jack */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Front MIC */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) }, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) }, + + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP jack */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc262_loopbacks alc880_loopbacks +#endif + +/* pcm configuration: identiacal with ALC880 */ +#define alc262_pcm_analog_playback alc880_pcm_analog_playback +#define alc262_pcm_analog_capture alc880_pcm_analog_capture +#define alc262_pcm_digital_playback alc880_pcm_digital_playback +#define alc262_pcm_digital_capture alc880_pcm_digital_capture + +/* + * BIOS auto configuration + */ +static int alc262_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc262_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc262_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + err = alc262_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc262_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC262_DIGOUT_NID; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = ALC262_DIGIN_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = alc262_volume_init_verbs; + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + store_pin_configs(codec); + return 1; +} + +#define alc262_auto_init_multi_out alc882_auto_init_multi_out +#define alc262_auto_init_hp_out alc882_auto_init_hp_out +#define alc262_auto_init_analog_input alc882_auto_init_analog_input +#define alc262_auto_init_input_src alc882_auto_init_input_src + + +/* init callback for auto-configuration model -- overriding the default init */ +static void alc262_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc262_auto_init_multi_out(codec); + alc262_auto_init_hp_out(codec); + alc262_auto_init_analog_input(codec); + alc262_auto_init_input_src(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +/* + * configuration and preset + */ +static const char *alc262_models[ALC262_MODEL_LAST] = { + [ALC262_BASIC] = "basic", + [ALC262_HIPPO] = "hippo", + [ALC262_HIPPO_1] = "hippo_1", + [ALC262_FUJITSU] = "fujitsu", + [ALC262_HP_BPC] = "hp-bpc", + [ALC262_HP_BPC_D7000_WL]= "hp-bpc-d7000", + [ALC262_HP_TC_T5735] = "hp-tc-t5735", + [ALC262_HP_RP5700] = "hp-rp5700", + [ALC262_BENQ_ED8] = "benq", + [ALC262_BENQ_T31] = "benq-t31", + [ALC262_SONY_ASSAMD] = "sony-assamd", + [ALC262_TOSHIBA_S06] = "toshiba-s06", + [ALC262_TOSHIBA_RX1] = "toshiba-rx1", + [ALC262_ULTRA] = "ultra", + [ALC262_LENOVO_3000] = "lenovo-3000", + [ALC262_NEC] = "nec", + [ALC262_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc262_cfg_tbl[] = { + SND_PCI_QUIRK(0x1002, 0x437b, "Hippo", ALC262_HIPPO), + SND_PCI_QUIRK(0x1033, 0x8895, "NEC Versa S9100", ALC262_NEC), + SND_PCI_QUIRK(0x103c, 0x12fe, "HP xw9400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x12ff, "HP xw4550", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1306, "HP xw8600", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1307, "HP xw6600", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1308, "HP xw4600", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x1309, "HP xw4*00", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x130a, "HP xw6*00", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x130b, "HP xw8*00", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x2800, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2801, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2802, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2803, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2804, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2805, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2806, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2807, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x280c, "HP xw4400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x3014, "HP xw6400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x3015, "HP xw8400", ALC262_HP_BPC), + SND_PCI_QUIRK(0x103c, 0x302f, "HP Thin Client T5735", + ALC262_HP_TC_T5735), + SND_PCI_QUIRK(0x103c, 0x2817, "HP RP5700", ALC262_HP_RP5700), + SND_PCI_QUIRK(0x104d, 0x1f00, "Sony ASSAMD", ALC262_SONY_ASSAMD), + SND_PCI_QUIRK(0x104d, 0x8203, "Sony UX-90", ALC262_HIPPO), + SND_PCI_QUIRK(0x104d, 0x820f, "Sony ASSAMD", ALC262_SONY_ASSAMD), + SND_PCI_QUIRK(0x104d, 0x900e, "Sony ASSAMD", ALC262_SONY_ASSAMD), + SND_PCI_QUIRK(0x104d, 0x9015, "Sony 0x9015", ALC262_SONY_ASSAMD), + SND_PCI_QUIRK(0x1179, 0x0001, "Toshiba dynabook SS RX1", + ALC262_TOSHIBA_RX1), + SND_PCI_QUIRK(0x1179, 0xff7b, "Toshiba S06", ALC262_TOSHIBA_S06), + SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu", ALC262_FUJITSU), + SND_PCI_QUIRK(0x10cf, 0x142d, "Fujitsu Lifebook E8410", ALC262_FUJITSU), + SND_PCI_QUIRK(0x144d, 0xc032, "Samsung Q1 Ultra", ALC262_ULTRA), + SND_PCI_QUIRK(0x144d, 0xc039, "Samsung Q1U EL", ALC262_ULTRA), + SND_PCI_QUIRK(0x144d, 0xc510, "Samsung Q45", ALC262_HIPPO), + SND_PCI_QUIRK(0x17aa, 0x384e, "Lenovo 3000 y410", ALC262_LENOVO_3000), + SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_BENQ_ED8), + SND_PCI_QUIRK(0x17ff, 0x058d, "Benq T31-16", ALC262_BENQ_T31), + SND_PCI_QUIRK(0x17ff, 0x058f, "Benq Hippo", ALC262_HIPPO_1), + {} +}; + +static struct alc_config_preset alc262_presets[] = { + [ALC262_BASIC] = { + .mixers = { alc262_base_mixer }, + .init_verbs = { alc262_init_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + }, + [ALC262_HIPPO] = { + .mixers = { alc262_base_mixer }, + .init_verbs = { alc262_init_verbs, alc262_hippo_unsol_verbs}, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC262_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo_unsol_event, + .init_hook = alc262_hippo_automute, + }, + [ALC262_HIPPO_1] = { + .mixers = { alc262_hippo1_mixer }, + .init_verbs = { alc262_init_verbs, alc262_hippo1_unsol_verbs}, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x02, + .dig_out_nid = ALC262_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo1_unsol_event, + .init_hook = alc262_hippo1_automute, + }, + [ALC262_FUJITSU] = { + .mixers = { alc262_fujitsu_mixer }, + .init_verbs = { alc262_init_verbs, alc262_EAPD_verbs, + alc262_fujitsu_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC262_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_fujitsu_capture_source, + .unsol_event = alc262_fujitsu_unsol_event, + .init_hook = alc262_fujitsu_init_hook, + }, + [ALC262_HP_BPC] = { + .mixers = { alc262_HP_BPC_mixer }, + .init_verbs = { alc262_HP_BPC_init_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_HP_capture_source, + .unsol_event = alc262_hp_bpc_unsol_event, + .init_hook = alc262_hp_bpc_automute, + }, + [ALC262_HP_BPC_D7000_WF] = { + .mixers = { alc262_HP_BPC_WildWest_mixer }, + .init_verbs = { alc262_HP_BPC_WildWest_init_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_HP_D7000_capture_source, + .unsol_event = alc262_hp_wildwest_unsol_event, + .init_hook = alc262_hp_wildwest_automute, + }, + [ALC262_HP_BPC_D7000_WL] = { + .mixers = { alc262_HP_BPC_WildWest_mixer, + alc262_HP_BPC_WildWest_option_mixer }, + .init_verbs = { alc262_HP_BPC_WildWest_init_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_HP_D7000_capture_source, + .unsol_event = alc262_hp_wildwest_unsol_event, + .init_hook = alc262_hp_wildwest_automute, + }, + [ALC262_HP_TC_T5735] = { + .mixers = { alc262_hp_t5735_mixer }, + .init_verbs = { alc262_init_verbs, alc262_hp_t5735_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hp_t5735_unsol_event, + .init_hook = alc262_hp_t5735_init_hook, + }, + [ALC262_HP_RP5700] = { + .mixers = { alc262_hp_rp5700_mixer }, + .init_verbs = { alc262_init_verbs, alc262_hp_rp5700_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_hp_rp5700_capture_source, + }, + [ALC262_BENQ_ED8] = { + .mixers = { alc262_base_mixer }, + .init_verbs = { alc262_init_verbs, alc262_EAPD_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + }, + [ALC262_SONY_ASSAMD] = { + .mixers = { alc262_sony_mixer }, + .init_verbs = { alc262_init_verbs, alc262_sony_unsol_verbs}, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x02, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo_unsol_event, + .init_hook = alc262_hippo_automute, + }, + [ALC262_BENQ_T31] = { + .mixers = { alc262_benq_t31_mixer }, + .init_verbs = { alc262_init_verbs, alc262_benq_t31_EAPD_verbs, alc262_hippo_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo_unsol_event, + .init_hook = alc262_hippo_automute, + }, + [ALC262_ULTRA] = { + .mixers = { alc262_ultra_mixer, alc262_ultra_capture_mixer }, + .init_verbs = { alc262_ultra_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_ultra_capture_source, + .adc_nids = alc262_adc_nids, /* ADC0 */ + .capsrc_nids = alc262_capsrc_nids, + .num_adc_nids = 1, /* single ADC */ + .unsol_event = alc262_ultra_unsol_event, + .init_hook = alc262_ultra_automute, + }, + [ALC262_LENOVO_3000] = { + .mixers = { alc262_lenovo_3000_mixer }, + .init_verbs = { alc262_init_verbs, alc262_EAPD_verbs, + alc262_lenovo_3000_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC262_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_fujitsu_capture_source, + .unsol_event = alc262_lenovo_3000_unsol_event, + }, + [ALC262_NEC] = { + .mixers = { alc262_nec_mixer }, + .init_verbs = { alc262_nec_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + }, + [ALC262_TOSHIBA_S06] = { + .mixers = { alc262_toshiba_s06_mixer }, + .init_verbs = { alc262_init_verbs, alc262_toshiba_s06_verbs, + alc262_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .capsrc_nids = alc262_dmic_capsrc_nids, + .dac_nids = alc262_dac_nids, + .adc_nids = alc262_dmic_adc_nids, /* ADC0 */ + .dig_out_nid = ALC262_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_dmic_capture_source, + .unsol_event = alc262_toshiba_s06_unsol_event, + .init_hook = alc262_toshiba_s06_init_hook, + }, + [ALC262_TOSHIBA_RX1] = { + .mixers = { alc262_toshiba_rx1_mixer }, + .init_verbs = { alc262_init_verbs, alc262_toshiba_rx1_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc262_dac_nids), + .dac_nids = alc262_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc262_modes), + .channel_mode = alc262_modes, + .input_mux = &alc262_capture_source, + .unsol_event = alc262_hippo_unsol_event, + .init_hook = alc262_hippo_automute, + }, +}; + +static int patch_alc262(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; +#if 0 + /* pshou 07/11/05 set a zero PCM sample to DAC when FIFO is + * under-run + */ + { + int tmp; + snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_COEF_INDEX, 7); + tmp = snd_hda_codec_read(codec, 0x20, 0, AC_VERB_GET_PROC_COEF, 0); + snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_COEF_INDEX, 7); + snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PROC_COEF, tmp | 0x80); + } +#endif + + alc_fix_pll_init(codec, 0x20, 0x0a, 10); + + board_config = snd_hda_check_board_config(codec, ALC262_MODEL_LAST, + alc262_models, + alc262_cfg_tbl); + + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC262, " + "trying auto-probe from BIOS...\n"); + board_config = ALC262_AUTO; + } + + if (board_config == ALC262_AUTO) { + /* automatic parse from the BIOS config */ + err = alc262_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC262_BASIC; + } + } + + if (board_config != ALC262_AUTO) + setup_preset(spec, &alc262_presets[board_config]); + + spec->stream_name_analog = "ALC262 Analog"; + spec->stream_analog_playback = &alc262_pcm_analog_playback; + spec->stream_analog_capture = &alc262_pcm_analog_capture; + + spec->stream_name_digital = "ALC262 Digital"; + spec->stream_digital_playback = &alc262_pcm_digital_playback; + spec->stream_digital_capture = &alc262_pcm_digital_capture; + + if (!spec->adc_nids && spec->input_mux) { + /* check whether NID 0x07 is valid */ + unsigned int wcap = get_wcaps(codec, 0x07); + + /* get type */ + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wcap != AC_WID_AUD_IN) { + spec->adc_nids = alc262_adc_nids_alt; + spec->num_adc_nids = ARRAY_SIZE(alc262_adc_nids_alt); + spec->capsrc_nids = alc262_capsrc_nids_alt; + spec->mixers[spec->num_mixers] = + alc262_capture_alt_mixer; + spec->num_mixers++; + } else { + spec->adc_nids = alc262_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc262_adc_nids); + spec->capsrc_nids = alc262_capsrc_nids; + spec->mixers[spec->num_mixers] = alc262_capture_mixer; + spec->num_mixers++; + } + } + + spec->vmaster_nid = 0x0c; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC262_AUTO) + spec->init_hook = alc262_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc262_loopbacks; +#endif + + return 0; +} + +/* + * ALC268 channel source setting (2 channel) + */ +#define ALC268_DIGOUT_NID ALC880_DIGOUT_NID +#define alc268_modes alc260_modes + +static hda_nid_t alc268_dac_nids[2] = { + /* front, hp */ + 0x02, 0x03 +}; + +static hda_nid_t alc268_adc_nids[2] = { + /* ADC0-1 */ + 0x08, 0x07 +}; + +static hda_nid_t alc268_adc_nids_alt[1] = { + /* ADC0 */ + 0x08 +}; + +static hda_nid_t alc268_capsrc_nids[2] = { 0x23, 0x24 }; + +static struct snd_kcontrol_new alc268_base_mixer[] = { + /* output mixer control */ + HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT), + { } +}; + +/* bind Beep switches of both NID 0x0f and 0x10 */ +static struct hda_bind_ctls alc268_bind_beep_sw = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x0f, 3, 1, HDA_INPUT), + HDA_COMPOSE_AMP_VAL(0x10, 3, 1, HDA_INPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc268_beep_mixer[] = { + HDA_CODEC_VOLUME("Beep Playback Volume", 0x1d, 0x0, HDA_INPUT), + HDA_BIND_SW("Beep Playback Switch", &alc268_bind_beep_sw), + { } +}; + +static struct hda_verb alc268_eapd_verbs[] = { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 2}, + { } +}; + +/* Toshiba specific */ +#define alc268_toshiba_automute alc262_hippo_automute + +static struct hda_verb alc268_toshiba_verbs[] = { + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } /* end */ +}; + +static struct hda_input_mux alc268_acer_lc_capture_source = { + .num_items = 2, + .items = { + { "i-Mic", 0x6 }, + { "E-Mic", 0x0 }, + }, +}; + +/* Acer specific */ +/* bind volumes of both NID 0x02 and 0x03 */ +static struct hda_bind_ctls alc268_acer_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x03, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc268_acer_automute(struct hda_codec *codec, int force) +{ + struct alc_spec *spec = codec->spec; + unsigned int mute; + + if (force || !spec->sense_updated) { + unsigned int present; + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & 0x80000000) != 0; + spec->sense_updated = 1; + } + if (spec->jack_present) + mute = HDA_AMP_MUTE; /* mute internal speaker */ + else /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); +} + + +/* bind hp and internal speaker mute (with plug check) */ +static int alc268_acer_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + change = snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[0] ? 0 : HDA_AMP_MUTE); + change |= snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[1] ? 0 : HDA_AMP_MUTE); + if (change) + alc268_acer_automute(codec, 0); + return change; +} + +static struct snd_kcontrol_new alc268_acer_aspire_one_mixer[] = { + /* output mixer control */ + HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc268_acer_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("Mic Boost Capture Volume", 0x18, 0, HDA_INPUT), + { } +}; + +static struct snd_kcontrol_new alc268_acer_mixer[] = { + /* output mixer control */ + HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc268_acer_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Line In Boost", 0x1a, 0, HDA_INPUT), + { } +}; + +static struct hda_verb alc268_acer_aspire_one_verbs[] = { + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x23, AC_VERB_SET_CONNECT_SEL, 0x06}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, 0xa017}, + { } +}; + +static struct hda_verb alc268_acer_verbs[] = { + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* internal dmic? */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } +}; + +/* unsolicited event for HP jack sensing */ +static void alc268_toshiba_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc268_toshiba_automute(codec); +} + +static void alc268_acer_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc268_acer_automute(codec, 1); +} + +static void alc268_acer_init_hook(struct hda_codec *codec) +{ + alc268_acer_automute(codec, 1); +} + +/* toggle speaker-output according to the hp-jack state */ +static void alc268_aspire_one_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? AMP_IN_MUTE(0) : 0; + snd_hda_codec_amp_stereo(codec, 0x0f, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0f, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); +} + + +static void alc268_acer_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x23, 0, AC_VERB_SET_CONNECT_SEL, + present ? 0x0 : 0x6); +} + +static void alc268_acer_lc_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc268_aspire_one_speaker_automute(codec); + if ((res >> 26) == ALC880_MIC_EVENT) + alc268_acer_mic_automute(codec); +} + +static void alc268_acer_lc_init_hook(struct hda_codec *codec) +{ + alc268_aspire_one_speaker_automute(codec); + alc268_acer_mic_automute(codec); +} + +static struct snd_kcontrol_new alc268_dell_mixer[] = { + /* output mixer control */ + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT), + { } +}; + +static struct hda_verb alc268_dell_verbs[] = { + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + { } +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc268_dell_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned int mute; + + present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0); + if (present & 0x80000000) + mute = HDA_AMP_MUTE; + else + mute = snd_hda_codec_amp_read(codec, 0x15, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); +} + +static void alc268_dell_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) != ALC880_HP_EVENT) + return; + alc268_dell_automute(codec); +} + +#define alc268_dell_init_hook alc268_dell_automute + +static struct snd_kcontrol_new alc267_quanta_il1_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x3, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Mic Capture Switch", 0x23, 2, HDA_OUTPUT), + HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + { } +}; + +static struct hda_verb alc267_quanta_il1_verbs[] = { + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_MIC_EVENT | AC_USRSP_EN}, + { } +}; + +static void alc267_quanta_il1_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + snd_hda_codec_write(codec, 0x14, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + present ? 0 : PIN_OUT); +} + +static void alc267_quanta_il1_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x23, 0, + AC_VERB_SET_CONNECT_SEL, + present ? 0x00 : 0x01); +} + +static void alc267_quanta_il1_automute(struct hda_codec *codec) +{ + alc267_quanta_il1_hp_automute(codec); + alc267_quanta_il1_mic_automute(codec); +} + +static void alc267_quanta_il1_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc267_quanta_il1_hp_automute(codec); + break; + case ALC880_MIC_EVENT: + alc267_quanta_il1_mic_automute(codec); + break; + } +} + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc268_base_init_verbs[] = { + /* Unmute DAC0-1 and set vol = 0 */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_CONNECT_SEL, 0x00}, + + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + /* set PCBEEP vol = 0, mute connections */ + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + /* Unmute Selector 23h,24h and set the default input to mic-in */ + + {0x23, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x24, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc268_volume_init_verbs[] = { + /* set output DAC */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + /* set PCBEEP vol = 0, mute connections */ + {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + { } +}; + +#define alc268_mux_enum_info alc_mux_enum_info +#define alc268_mux_enum_get alc_mux_enum_get +#define alc268_mux_enum_put alc_mux_enum_put + +static struct snd_kcontrol_new alc268_capture_alt_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc268_mux_enum_info, + .get = alc268_mux_enum_get, + .put = alc268_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc268_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x24, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x24, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 2, + .info = alc268_mux_enum_info, + .get = alc268_mux_enum_get, + .put = alc268_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_input_mux alc268_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x3 }, + }, +}; + +static struct hda_input_mux alc268_acer_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Internal Mic", 0x6 }, + { "Line", 0x2 }, + }, +}; + +#ifdef CONFIG_SND_DEBUG +static struct snd_kcontrol_new alc268_test_mixer[] = { + /* Volume widgets */ + HDA_CODEC_VOLUME("LOUT1 Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("LOUT2 Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Mono sum Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE("LINE-OUT sum Playback Switch", 0x0f, 2, HDA_INPUT), + HDA_BIND_MUTE("HP-OUT sum Playback Switch", 0x10, 2, HDA_INPUT), + HDA_BIND_MUTE("LINE-OUT Playback Switch", 0x14, 2, HDA_OUTPUT), + HDA_BIND_MUTE("HP-OUT Playback Switch", 0x15, 2, HDA_OUTPUT), + HDA_BIND_MUTE("Mono Playback Switch", 0x16, 2, HDA_OUTPUT), + HDA_CODEC_VOLUME("MIC1 Capture Volume", 0x18, 0x0, HDA_INPUT), + HDA_BIND_MUTE("MIC1 Capture Switch", 0x18, 2, HDA_OUTPUT), + HDA_CODEC_VOLUME("MIC2 Capture Volume", 0x19, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("LINE1 Capture Volume", 0x1a, 0x0, HDA_INPUT), + HDA_BIND_MUTE("LINE1 Capture Switch", 0x1a, 2, HDA_OUTPUT), + /* The below appears problematic on some hardwares */ + /*HDA_CODEC_VOLUME("PCBEEP Playback Volume", 0x1d, 0x0, HDA_INPUT),*/ + HDA_CODEC_VOLUME("PCM-IN1 Capture Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("PCM-IN1 Capture Switch", 0x23, 2, HDA_OUTPUT), + HDA_CODEC_VOLUME("PCM-IN2 Capture Volume", 0x24, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("PCM-IN2 Capture Switch", 0x24, 2, HDA_OUTPUT), + + /* Modes for retasking pin widgets */ + ALC_PIN_MODE("LINE-OUT pin mode", 0x14, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("HP-OUT pin mode", 0x15, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("MIC1 pin mode", 0x18, ALC_PIN_DIR_INOUT), + ALC_PIN_MODE("LINE1 pin mode", 0x1a, ALC_PIN_DIR_INOUT), + + /* Controls for GPIO pins, assuming they are configured as outputs */ + ALC_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01), + ALC_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02), + ALC_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04), + ALC_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08), + + /* Switches to allow the digital SPDIF output pin to be enabled. + * The ALC268 does not have an SPDIF input. + */ + ALC_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x06, 0x01), + + /* A switch allowing EAPD to be enabled. Some laptops seem to use + * this output to turn on an external amplifier. + */ + ALC_EAPD_CTRL_SWITCH("LINE-OUT EAPD Enable Switch", 0x0f, 0x02), + ALC_EAPD_CTRL_SWITCH("HP-OUT EAPD Enable Switch", 0x10, 0x02), + + { } /* end */ +}; +#endif + +/* create input playback/capture controls for the given pin */ +static int alc268_new_analog_output(struct alc_spec *spec, hda_nid_t nid, + const char *ctlname, int idx) +{ + char name[32]; + int err; + + sprintf(name, "%s Playback Volume", ctlname); + if (nid == 0x14) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x02, 3, idx, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (nid == 0x15) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x03, 3, idx, + HDA_OUTPUT)); + if (err < 0) + return err; + } else + return -1; + sprintf(name, "%s Playback Switch", ctlname); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, idx, HDA_OUTPUT)); + if (err < 0) + return err; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int alc268_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int err; + + spec->multiout.num_dacs = 2; /* only use one dac */ + spec->multiout.dac_nids = spec->private_dac_nids; + spec->multiout.dac_nids[0] = 2; + spec->multiout.dac_nids[1] = 3; + + nid = cfg->line_out_pins[0]; + if (nid) + alc268_new_analog_output(spec, nid, "Front", 0); + + nid = cfg->speaker_pins[0]; + if (nid == 0x1d) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Speaker Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + } + nid = cfg->hp_pins[0]; + if (nid) + alc268_new_analog_output(spec, nid, "Headphone", 0); + + nid = cfg->line_out_pins[1] | cfg->line_out_pins[2]; + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Mono Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, HDA_INPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc268_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, idx1; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + switch(cfg->input_pins[i]) { + case 0x18: + idx1 = 0; /* Mic 1 */ + break; + case 0x19: + idx1 = 1; /* Mic 2 */ + break; + case 0x1a: + idx1 = 2; /* Line In */ + break; + case 0x1c: + idx1 = 3; /* CD */ + break; + case 0x12: + case 0x13: + idx1 = 6; /* digital mics */ + break; + default: + continue; + } + imux->items[imux->num_items].label = auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = idx1; + imux->num_items++; + } + return 0; +} + +static void alc268_auto_init_mono_speaker_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t speaker_nid = spec->autocfg.speaker_pins[0]; + hda_nid_t hp_nid = spec->autocfg.hp_pins[0]; + hda_nid_t line_nid = spec->autocfg.line_out_pins[0]; + unsigned int dac_vol1, dac_vol2; + + if (speaker_nid) { + snd_hda_codec_write(codec, speaker_nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + snd_hda_codec_write(codec, 0x0f, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(1)); + snd_hda_codec_write(codec, 0x10, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_IN_UNMUTE(1)); + } else { + snd_hda_codec_write(codec, 0x0f, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)); + snd_hda_codec_write(codec, 0x10, 0, + AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)); + } + + dac_vol1 = dac_vol2 = 0xb000 | 0x40; /* set max volume */ + if (line_nid == 0x14) + dac_vol2 = AMP_OUT_ZERO; + else if (line_nid == 0x15) + dac_vol1 = AMP_OUT_ZERO; + if (hp_nid == 0x14) + dac_vol2 = AMP_OUT_ZERO; + else if (hp_nid == 0x15) + dac_vol1 = AMP_OUT_ZERO; + if (line_nid != 0x16 || hp_nid != 0x16 || + spec->autocfg.line_out_pins[1] != 0x16 || + spec->autocfg.line_out_pins[2] != 0x16) + dac_vol1 = dac_vol2 = AMP_OUT_ZERO; + + snd_hda_codec_write(codec, 0x02, 0, + AC_VERB_SET_AMP_GAIN_MUTE, dac_vol1); + snd_hda_codec_write(codec, 0x03, 0, + AC_VERB_SET_AMP_GAIN_MUTE, dac_vol2); +} + +/* pcm configuration: identiacal with ALC880 */ +#define alc268_pcm_analog_playback alc880_pcm_analog_playback +#define alc268_pcm_analog_capture alc880_pcm_analog_capture +#define alc268_pcm_analog_alt_capture alc880_pcm_analog_alt_capture +#define alc268_pcm_digital_playback alc880_pcm_digital_playback + +/* + * BIOS auto configuration + */ +static int alc268_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc268_ignore[] = { 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc268_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc268_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc268_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = 2; + + /* digital only support output */ + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC268_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + if (spec->autocfg.speaker_pins[0] != 0x1d) + spec->mixers[spec->num_mixers++] = alc268_beep_mixer; + + spec->init_verbs[spec->num_init_verbs++] = alc268_volume_init_verbs; + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + store_pin_configs(codec); + return 1; +} + +#define alc268_auto_init_multi_out alc882_auto_init_multi_out +#define alc268_auto_init_hp_out alc882_auto_init_hp_out +#define alc268_auto_init_analog_input alc882_auto_init_analog_input + +/* init callback for auto-configuration model -- overriding the default init */ +static void alc268_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc268_auto_init_multi_out(codec); + alc268_auto_init_hp_out(codec); + alc268_auto_init_mono_speaker_out(codec); + alc268_auto_init_analog_input(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +/* + * configuration and preset + */ +static const char *alc268_models[ALC268_MODEL_LAST] = { + [ALC267_QUANTA_IL1] = "quanta-il1", + [ALC268_3ST] = "3stack", + [ALC268_TOSHIBA] = "toshiba", + [ALC268_ACER] = "acer", + [ALC268_ACER_ASPIRE_ONE] = "acer-aspire", + [ALC268_DELL] = "dell", + [ALC268_ZEPTO] = "zepto", +#ifdef CONFIG_SND_DEBUG + [ALC268_TEST] = "test", +#endif + [ALC268_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc268_cfg_tbl[] = { + SND_PCI_QUIRK(0x1025, 0x011e, "Acer Aspire 5720z", ALC268_ACER), + SND_PCI_QUIRK(0x1025, 0x0126, "Acer", ALC268_ACER), + SND_PCI_QUIRK(0x1025, 0x012e, "Acer Aspire 5310", ALC268_ACER), + SND_PCI_QUIRK(0x1025, 0x0130, "Acer Extensa 5210", ALC268_ACER), + SND_PCI_QUIRK(0x1025, 0x0136, "Acer Aspire 5315", ALC268_ACER), + SND_PCI_QUIRK(0x1025, 0x015b, "Acer Aspire One", + ALC268_ACER_ASPIRE_ONE), + SND_PCI_QUIRK(0x1028, 0x0253, "Dell OEM", ALC268_DELL), + SND_PCI_QUIRK(0x103c, 0x30cc, "TOSHIBA", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC268_3ST), + SND_PCI_QUIRK(0x1179, 0xff10, "TOSHIBA A205", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x1179, 0xff50, "TOSHIBA A305", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x1179, 0xff64, "TOSHIBA L305", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x14c0, 0x0025, "COMPAL IFL90/JFL-92", ALC268_TOSHIBA), + SND_PCI_QUIRK(0x152d, 0x0763, "Diverse (CPR2000)", ALC268_ACER), + SND_PCI_QUIRK(0x152d, 0x0771, "Quanta IL1", ALC267_QUANTA_IL1), + SND_PCI_QUIRK(0x1170, 0x0040, "ZEPTO", ALC268_ZEPTO), + {} +}; + +static struct alc_config_preset alc268_presets[] = { + [ALC267_QUANTA_IL1] = { + .mixers = { alc267_quanta_il1_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc267_quanta_il1_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + .unsol_event = alc267_quanta_il1_unsol_event, + .init_hook = alc267_quanta_il1_automute, + }, + [ALC268_3ST] = { + .mixers = { alc268_base_mixer, alc268_capture_alt_mixer, + alc268_beep_mixer }, + .init_verbs = { alc268_base_init_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC268_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + }, + [ALC268_TOSHIBA] = { + .mixers = { alc268_base_mixer, alc268_capture_alt_mixer, + alc268_beep_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_toshiba_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + .unsol_event = alc268_toshiba_unsol_event, + .init_hook = alc268_toshiba_automute, + }, + [ALC268_ACER] = { + .mixers = { alc268_acer_mixer, alc268_capture_alt_mixer, + alc268_beep_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_acer_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x02, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_acer_capture_source, + .unsol_event = alc268_acer_unsol_event, + .init_hook = alc268_acer_init_hook, + }, + [ALC268_ACER_ASPIRE_ONE] = { + .mixers = { alc268_acer_aspire_one_mixer, + alc268_capture_alt_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_acer_aspire_one_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_acer_lc_capture_source, + .unsol_event = alc268_acer_lc_unsol_event, + .init_hook = alc268_acer_lc_init_hook, + }, + [ALC268_DELL] = { + .mixers = { alc268_dell_mixer, alc268_beep_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_dell_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .hp_nid = 0x02, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .unsol_event = alc268_dell_unsol_event, + .init_hook = alc268_dell_init_hook, + .input_mux = &alc268_capture_source, + }, + [ALC268_ZEPTO] = { + .mixers = { alc268_base_mixer, alc268_capture_alt_mixer, + alc268_beep_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_toshiba_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC268_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + .unsol_event = alc268_toshiba_unsol_event, + .init_hook = alc268_toshiba_automute + }, +#ifdef CONFIG_SND_DEBUG + [ALC268_TEST] = { + .mixers = { alc268_test_mixer, alc268_capture_mixer }, + .init_verbs = { alc268_base_init_verbs, alc268_eapd_verbs, + alc268_volume_init_verbs }, + .num_dacs = ARRAY_SIZE(alc268_dac_nids), + .dac_nids = alc268_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt), + .adc_nids = alc268_adc_nids_alt, + .capsrc_nids = alc268_capsrc_nids, + .hp_nid = 0x03, + .dig_out_nid = ALC268_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc268_modes), + .channel_mode = alc268_modes, + .input_mux = &alc268_capture_source, + }, +#endif +}; + +static int patch_alc268(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC268_MODEL_LAST, + alc268_models, + alc268_cfg_tbl); + + if (board_config < 0 || board_config >= ALC268_MODEL_LAST) { + printk(KERN_INFO "hda_codec: Unknown model for ALC268, " + "trying auto-probe from BIOS...\n"); + board_config = ALC268_AUTO; + } + + if (board_config == ALC268_AUTO) { + /* automatic parse from the BIOS config */ + err = alc268_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC268_3ST; + } + } + + if (board_config != ALC268_AUTO) + setup_preset(spec, &alc268_presets[board_config]); + + if (codec->vendor_id == 0x10ec0267) { + spec->stream_name_analog = "ALC267 Analog"; + spec->stream_name_digital = "ALC267 Digital"; + } else { + spec->stream_name_analog = "ALC268 Analog"; + spec->stream_name_digital = "ALC268 Digital"; + } + + spec->stream_analog_playback = &alc268_pcm_analog_playback; + spec->stream_analog_capture = &alc268_pcm_analog_capture; + spec->stream_analog_alt_capture = &alc268_pcm_analog_alt_capture; + + spec->stream_digital_playback = &alc268_pcm_digital_playback; + + if (!query_amp_caps(codec, 0x1d, HDA_INPUT)) + /* override the amp caps for beep generator */ + snd_hda_override_amp_caps(codec, 0x1d, HDA_INPUT, + (0x0c << AC_AMPCAP_OFFSET_SHIFT) | + (0x0c << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x07 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + + if (!spec->adc_nids && spec->input_mux) { + /* check whether NID 0x07 is valid */ + unsigned int wcap = get_wcaps(codec, 0x07); + int i; + + /* get type */ + wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wcap != AC_WID_AUD_IN || spec->input_mux->num_items == 1) { + spec->adc_nids = alc268_adc_nids_alt; + spec->num_adc_nids = ARRAY_SIZE(alc268_adc_nids_alt); + spec->mixers[spec->num_mixers] = + alc268_capture_alt_mixer; + spec->num_mixers++; + } else { + spec->adc_nids = alc268_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc268_adc_nids); + spec->mixers[spec->num_mixers] = + alc268_capture_mixer; + spec->num_mixers++; + } + spec->capsrc_nids = alc268_capsrc_nids; + /* set default input source */ + for (i = 0; i < spec->num_adc_nids; i++) + snd_hda_codec_write_cache(codec, alc268_capsrc_nids[i], + 0, AC_VERB_SET_CONNECT_SEL, + spec->input_mux->items[0].index); + } + + spec->vmaster_nid = 0x02; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC268_AUTO) + spec->init_hook = alc268_auto_init; + + return 0; +} + +/* + * ALC269 channel source setting (2 channel) + */ +#define ALC269_DIGOUT_NID ALC880_DIGOUT_NID + +#define alc269_dac_nids alc260_dac_nids + +static hda_nid_t alc269_adc_nids[1] = { + /* ADC1 */ + 0x08, +}; + +static hda_nid_t alc269_capsrc_nids[1] = { + 0x23, +}; + +/* NOTE: ADC2 (0x07) is connected from a recording *MIXER* (0x24), + * not a mux! + */ + +static struct hda_input_mux alc269_eeepc_dmic_capture_source = { + .num_items = 2, + .items = { + { "i-Mic", 0x5 }, + { "e-Mic", 0x0 }, + }, +}; + +static struct hda_input_mux alc269_eeepc_amic_capture_source = { + .num_items = 2, + .items = { + { "i-Mic", 0x1 }, + { "e-Mic", 0x0 }, + }, +}; + +#define alc269_modes alc260_modes +#define alc269_capture_source alc880_lg_lw_capture_source + +static struct snd_kcontrol_new alc269_base_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + 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("Beep Playback Volume", 0x0b, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x0b, 0x4, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc269_quanta_fl1_mixer[] = { + /* output mixer control */ + HDA_BIND_VOL("Master Playback Volume", &alc268_acer_bind_master_vol), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc268_acer_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + }, + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Internal Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x04, HDA_INPUT), + { } +}; + +/* bind volumes of both NID 0x0c and 0x0d */ +static struct hda_bind_ctls alc269_epc_bind_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x03, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc269_eeepc_mixer[] = { + HDA_CODEC_MUTE("iSpeaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_BIND_VOL("LineOut Playback Volume", &alc269_epc_bind_vol), + HDA_CODEC_MUTE("LineOut Playback Switch", 0x15, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc269_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc269_epc_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { } /* end */ +}; + +/* beep control */ +static struct snd_kcontrol_new alc269_beep_mixer[] = { + HDA_CODEC_VOLUME("Beep Playback Volume", 0x0b, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Beep Playback Switch", 0x0b, 0x4, HDA_INPUT), + { } /* end */ +}; + +static struct hda_verb alc269_quanta_fl1_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc269_quanta_fl1_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? AMP_IN_MUTE(0) : 0; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); + + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 0x0c); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, 0x680); + + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_COEF_INDEX, 0x0c); + snd_hda_codec_write(codec, 0x20, 0, + AC_VERB_SET_PROC_COEF, 0x480); +} + +static void alc269_quanta_fl1_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x23, 0, + AC_VERB_SET_CONNECT_SEL, present ? 0x0 : 0x1); +} + +static void alc269_quanta_fl1_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc269_quanta_fl1_speaker_automute(codec); + if ((res >> 26) == ALC880_MIC_EVENT) + alc269_quanta_fl1_mic_automute(codec); +} + +static void alc269_quanta_fl1_init_hook(struct hda_codec *codec) +{ + alc269_quanta_fl1_speaker_automute(codec); + alc269_quanta_fl1_mic_automute(codec); +} + +static struct hda_verb alc269_eeepc_dmic_init_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x23, AC_VERB_SET_CONNECT_SEL, 0x05}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 }, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x7019 | (0x00 << 8))}, + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc269_eeepc_amic_init_verbs[] = { + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x23, AC_VERB_SET_CONNECT_SEL, 0x01}, + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, 0xb026 }, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, (0x701b | (0x00 << 8))}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc269_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? AMP_IN_MUTE(0) : 0; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); +} + +static void alc269_eeepc_dmic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x23, 0, + AC_VERB_SET_CONNECT_SEL, (present ? 0 : 5)); +} + +static void alc269_eeepc_amic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x24, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write(codec, 0x24, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); +} + +/* unsolicited event for HP jack sensing */ +static void alc269_eeepc_dmic_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc269_speaker_automute(codec); + + if ((res >> 26) == ALC880_MIC_EVENT) + alc269_eeepc_dmic_automute(codec); +} + +static void alc269_eeepc_dmic_inithook(struct hda_codec *codec) +{ + alc269_speaker_automute(codec); + alc269_eeepc_dmic_automute(codec); +} + +/* unsolicited event for HP jack sensing */ +static void alc269_eeepc_amic_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc269_speaker_automute(codec); + + if ((res >> 26) == ALC880_MIC_EVENT) + alc269_eeepc_amic_automute(codec); +} + +static void alc269_eeepc_amic_inithook(struct hda_codec *codec) +{ + alc269_speaker_automute(codec); + alc269_eeepc_amic_automute(codec); +} + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc269_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Mute input amps (PCBeep, Line In, Mic 1 & Mic 2) of the + * analog-loopback mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for + * front panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0e) + */ + /* set vol=0 to output mixers */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1d, 0b */ + /* Input mixer1: unmute Mic, F-Mic, Line, CD inputs */ + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + + /* set EAPD */ + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 2}, + { } +}; + +/* add playback controls from the parsed DAC table */ +static int alc269_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + hda_nid_t nid; + int err; + + spec->multiout.num_dacs = 1; /* only use one dac */ + spec->multiout.dac_nids = spec->private_dac_nids; + spec->multiout.dac_nids[0] = 2; + + nid = cfg->line_out_pins[0]; + if (nid) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Front Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + + nid = cfg->speaker_pins[0]; + if (nid) { + if (!cfg->line_out_pins[0]) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Speaker Playback Volume", + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Speaker Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Speaker Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + nid = cfg->hp_pins[0]; + if (nid) { + /* spec->multiout.hp_nid = 2; */ + if (!cfg->line_out_pins[0] && !cfg->speaker_pins[0]) { + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + if (nid == 0x16) { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +static int alc269_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int err; + + err = alc880_auto_create_analog_input_ctls(spec, cfg); + if (err < 0) + return err; + /* digital-mic input pin is excluded in alc880_auto_create..() + * because it's under 0x18 + */ + if (cfg->input_pins[AUTO_PIN_MIC] == 0x12 || + cfg->input_pins[AUTO_PIN_FRONT_MIC] == 0x12) { + struct hda_input_mux *imux = &spec->private_imux; + imux->items[imux->num_items].label = "Int Mic"; + imux->items[imux->num_items].index = 0x05; + imux->num_items++; + } + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc269_loopbacks alc880_loopbacks +#endif + +/* pcm configuration: identiacal with ALC880 */ +#define alc269_pcm_analog_playback alc880_pcm_analog_playback +#define alc269_pcm_analog_capture alc880_pcm_analog_capture +#define alc269_pcm_digital_playback alc880_pcm_digital_playback +#define alc269_pcm_digital_capture alc880_pcm_digital_capture + +/* + * BIOS auto configuration + */ +static int alc269_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i, err; + static hda_nid_t alc269_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc269_ignore); + if (err < 0) + return err; + + err = alc269_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc269_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC269_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + /* create a beep mixer control if the pin 0x1d isn't assigned */ + for (i = 0; i < ARRAY_SIZE(spec->autocfg.input_pins); i++) + if (spec->autocfg.input_pins[i] == 0x1d) + break; + if (i >= ARRAY_SIZE(spec->autocfg.input_pins)) + spec->mixers[spec->num_mixers++] = alc269_beep_mixer; + + spec->init_verbs[spec->num_init_verbs++] = alc269_init_verbs; + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + /* set default input source */ + snd_hda_codec_write_cache(codec, alc269_capsrc_nids[0], + 0, AC_VERB_SET_CONNECT_SEL, + spec->input_mux->items[0].index); + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + spec->mixers[spec->num_mixers] = alc269_capture_mixer; + spec->num_mixers++; + + store_pin_configs(codec); + return 1; +} + +#define alc269_auto_init_multi_out alc882_auto_init_multi_out +#define alc269_auto_init_hp_out alc882_auto_init_hp_out +#define alc269_auto_init_analog_input alc882_auto_init_analog_input + + +/* init callback for auto-configuration model -- overriding the default init */ +static void alc269_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc269_auto_init_multi_out(codec); + alc269_auto_init_hp_out(codec); + alc269_auto_init_analog_input(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +/* + * configuration and preset + */ +static const char *alc269_models[ALC269_MODEL_LAST] = { + [ALC269_BASIC] = "basic", + [ALC269_QUANTA_FL1] = "quanta", + [ALC269_ASUS_EEEPC_P703] = "eeepc-p703", + [ALC269_ASUS_EEEPC_P901] = "eeepc-p901" +}; + +static struct snd_pci_quirk alc269_cfg_tbl[] = { + SND_PCI_QUIRK(0x17aa, 0x3bf8, "Quanta FL1", ALC269_QUANTA_FL1), + SND_PCI_QUIRK(0x1043, 0x8330, "ASUS Eeepc P703 P900A", + ALC269_ASUS_EEEPC_P703), + SND_PCI_QUIRK(0x1043, 0x831a, "ASUS Eeepc P901", + ALC269_ASUS_EEEPC_P901), + SND_PCI_QUIRK(0x1043, 0x834a, "ASUS Eeepc S101", + ALC269_ASUS_EEEPC_P901), + {} +}; + +static struct alc_config_preset alc269_presets[] = { + [ALC269_BASIC] = { + .mixers = { alc269_base_mixer, alc269_capture_mixer }, + .init_verbs = { alc269_init_verbs }, + .num_dacs = ARRAY_SIZE(alc269_dac_nids), + .dac_nids = alc269_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc269_modes), + .channel_mode = alc269_modes, + .input_mux = &alc269_capture_source, + }, + [ALC269_QUANTA_FL1] = { + .mixers = { alc269_quanta_fl1_mixer }, + .init_verbs = { alc269_init_verbs, alc269_quanta_fl1_verbs }, + .num_dacs = ARRAY_SIZE(alc269_dac_nids), + .dac_nids = alc269_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc269_modes), + .channel_mode = alc269_modes, + .input_mux = &alc269_capture_source, + .unsol_event = alc269_quanta_fl1_unsol_event, + .init_hook = alc269_quanta_fl1_init_hook, + }, + [ALC269_ASUS_EEEPC_P703] = { + .mixers = { alc269_eeepc_mixer, alc269_epc_capture_mixer }, + .init_verbs = { alc269_init_verbs, + alc269_eeepc_amic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc269_dac_nids), + .dac_nids = alc269_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc269_modes), + .channel_mode = alc269_modes, + .input_mux = &alc269_eeepc_amic_capture_source, + .unsol_event = alc269_eeepc_amic_unsol_event, + .init_hook = alc269_eeepc_amic_inithook, + }, + [ALC269_ASUS_EEEPC_P901] = { + .mixers = { alc269_eeepc_mixer, alc269_epc_capture_mixer}, + .init_verbs = { alc269_init_verbs, + alc269_eeepc_dmic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc269_dac_nids), + .dac_nids = alc269_dac_nids, + .hp_nid = 0x03, + .num_channel_mode = ARRAY_SIZE(alc269_modes), + .channel_mode = alc269_modes, + .input_mux = &alc269_eeepc_dmic_capture_source, + .unsol_event = alc269_eeepc_dmic_unsol_event, + .init_hook = alc269_eeepc_dmic_inithook, + }, +}; + +static int patch_alc269(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + alc_fix_pll_init(codec, 0x20, 0x04, 15); + + board_config = snd_hda_check_board_config(codec, ALC269_MODEL_LAST, + alc269_models, + alc269_cfg_tbl); + + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC269, " + "trying auto-probe from BIOS...\n"); + board_config = ALC269_AUTO; + } + + if (board_config == ALC269_AUTO) { + /* automatic parse from the BIOS config */ + err = alc269_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC269_BASIC; + } + } + + if (board_config != ALC269_AUTO) + setup_preset(spec, &alc269_presets[board_config]); + + spec->stream_name_analog = "ALC269 Analog"; + spec->stream_analog_playback = &alc269_pcm_analog_playback; + spec->stream_analog_capture = &alc269_pcm_analog_capture; + + spec->stream_name_digital = "ALC269 Digital"; + spec->stream_digital_playback = &alc269_pcm_digital_playback; + spec->stream_digital_capture = &alc269_pcm_digital_capture; + + spec->adc_nids = alc269_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc269_adc_nids); + spec->capsrc_nids = alc269_capsrc_nids; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC269_AUTO) + spec->init_hook = alc269_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc269_loopbacks; +#endif + + return 0; +} + +/* + * ALC861 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 alc861_threestack_ch2_init[] = { + /* set pin widget 1Ah (line in) for input */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* set pin widget 18h (mic1/2) for input, for mic also enable + * the vref + */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c }, +#if 0 + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8)) }, /*line-in*/ +#endif + { } /* end */ +}; +/* + * 6ch mode + * need to set the codec line out and mic 1 pin widgets to outputs + */ +static struct hda_verb alc861_threestack_ch6_init[] = { + /* set pin widget 1Ah (line in) for output (Back Surround)*/ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* set pin widget 18h (mic1) for output (CLFE)*/ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + + { 0x0c, AC_VERB_SET_CONNECT_SEL, 0x00 }, + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00 }, + + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 }, +#if 0 + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8)) }, /*line in*/ +#endif + { } /* end */ +}; + +static struct hda_channel_mode alc861_threestack_modes[2] = { + { 2, alc861_threestack_ch2_init }, + { 6, alc861_threestack_ch6_init }, +}; +/* Set mic1 as input and unmute the mixer */ +static struct hda_verb alc861_uniwill_m31_ch2_init[] = { + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/ + { } /* end */ +}; +/* Set mic1 as output and mute mixer */ +static struct hda_verb alc861_uniwill_m31_ch4_init[] = { + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/ + { } /* end */ +}; + +static struct hda_channel_mode alc861_uniwill_m31_modes[2] = { + { 2, alc861_uniwill_m31_ch2_init }, + { 4, alc861_uniwill_m31_ch4_init }, +}; + +/* Set mic1 and line-in as input and unmute the mixer */ +static struct hda_verb alc861_asus_ch2_init[] = { + /* set pin widget 1Ah (line in) for input */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* set pin widget 18h (mic1/2) for input, for mic also enable + * the vref + */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c }, +#if 0 + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x01 << 8)) }, /*mic*/ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7000 | (0x02 << 8)) }, /*line-in*/ +#endif + { } /* end */ +}; +/* Set mic1 nad line-in as output and mute mixer */ +static struct hda_verb alc861_asus_ch6_init[] = { + /* set pin widget 1Ah (line in) for output (Back Surround)*/ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* { 0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, */ + /* set pin widget 18h (mic1) for output (CLFE)*/ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, */ + { 0x0c, AC_VERB_SET_CONNECT_SEL, 0x00 }, + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00 }, + + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080 }, +#if 0 + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x01 << 8)) }, /*mic*/ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, (0x7080 | (0x02 << 8)) }, /*line in*/ +#endif + { } /* end */ +}; + +static struct hda_channel_mode alc861_asus_modes[2] = { + { 2, alc861_asus_ch2_init }, + { 6, alc861_asus_ch6_init }, +}; + +/* patch-ALC861 */ + +static struct snd_kcontrol_new alc861_base_mixer[] = { + /* output mixer control */ + HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 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", 0x04, 0x0, HDA_OUTPUT), + + /*Input mixer control */ + /* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */ + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT), + + /* Capture mixer control */ + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc861_3ST_mixer[] = { + /* output mixer control */ + HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 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", 0x04, 0x0, HDA_OUTPUT), */ + + /* Input mixer control */ + /* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */ + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT), + + /* Capture mixer control */ + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .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 = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + .private_value = ARRAY_SIZE(alc861_threestack_modes), + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc861_toshiba_mixer[] = { + /* output mixer control */ + HDA_CODEC_MUTE("Master Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT), + + /*Capture mixer control */ + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + + { } /* end */ +}; + +static struct snd_kcontrol_new alc861_uniwill_m31_mixer[] = { + /* output mixer control */ + HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 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", 0x04, 0x0, HDA_OUTPUT), */ + + /* Input mixer control */ + /* HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), */ + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_INPUT), + + /* Capture mixer control */ + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .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 = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + .private_value = ARRAY_SIZE(alc861_uniwill_m31_modes), + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc861_asus_mixer[] = { + /* output mixer control */ + HDA_CODEC_MUTE("Front Playback Switch", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x06, 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", 0x04, 0x0, HDA_OUTPUT), + + /* Input mixer control */ + HDA_CODEC_VOLUME("Input Playback Volume", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Input Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x15, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x15, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x10, 0x01, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1a, 0x03, HDA_OUTPUT), + + /* Capture mixer control */ + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .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 = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + .private_value = ARRAY_SIZE(alc861_asus_modes), + }, + { } +}; + +/* additional mixer */ +static struct snd_kcontrol_new alc861_asus_laptop_mixer[] = { + HDA_CODEC_VOLUME("CD Playback Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x23, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("PC Beep Playback Switch", 0x23, 0x0, HDA_OUTPUT), + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc861_base_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + /* port-A for surround (rear panel) */ + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x0e, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-C for line-in (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* port-D for Front */ + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* 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-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x1f, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* route front mic to ADC1*/ + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute DAC0~3 & spdif out*/ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Unmute Mixer 14 (mic) 1c (Line in)*/ + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Unmute Stereo Mixer 15 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */ + + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* hp used DAC 3 (Front) */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + + { } +}; + +static struct hda_verb alc861_threestack_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + /* port-A for surround (rear panel) */ + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-C for line-in (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* port-D for Front */ + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* 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-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* route front mic to ADC1*/ + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* Unmute DAC0~3 & spdif out*/ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Unmute Mixer 14 (mic) 1c (Line in)*/ + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Unmute Stereo Mixer 15 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */ + + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* hp used DAC 3 (Front) */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + { } +}; + +static struct hda_verb alc861_uniwill_m31_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + /* port-A for surround (rear panel) */ + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-C for line-in (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* port-D for Front */ + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-E for HP out (front panel) */ + /* this has to be set to VREF80 */ + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* route front PCM to HP */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* route front mic to ADC1*/ + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* Unmute DAC0~3 & spdif out*/ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Unmute Mixer 14 (mic) 1c (Line in)*/ + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Unmute Stereo Mixer 15 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */ + + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* hp used DAC 3 (Front) */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + { } +}; + +static struct hda_verb alc861_asus_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + /* port-A for surround (rear panel) + * according to codec#0 this is the HP jack + */ + { 0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0xc0 }, /* was 0x00 */ + /* route front PCM to HP */ + { 0x0e, AC_VERB_SET_CONNECT_SEL, 0x01 }, + /* port-B for mic-in (rear panel) with vref */ + { 0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-C for line-in (rear panel) */ + { 0x0c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* port-D for Front */ + { 0x0b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-E for HP out (front panel) */ + /* this has to be set to VREF80 */ + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* route front PCM to HP */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, + /* port-F for mic-in (front panel) with vref */ + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, + /* port-G for CLFE (rear panel) */ + { 0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* port-H for side (rear panel) */ + { 0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40 }, + /* CD-in */ + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20 }, + /* route front mic to ADC1*/ + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* Unmute DAC0~3 & spdif out*/ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Unmute Mixer 14 (mic) 1c (Line in)*/ + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Unmute Stereo Mixer 15 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, /* Output 0~12 step */ + + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + /* hp used DAC 3 (Front) */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + { } +}; + +/* additional init verbs for ASUS laptops */ +static struct hda_verb alc861_asus_laptop_init_verbs[] = { + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x45 }, /* HP-out */ + { 0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2) }, /* mute line-in */ + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc861_auto_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + /* {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, */ + {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute DAC0~3 & spdif out*/ + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x06, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Unmute Mixer 14 (mic) 1c (Line in)*/ + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x014, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x01c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Unmute Stereo Mixer 15 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, 0xb00c}, + + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + + {0x08, AC_VERB_SET_CONNECT_SEL, 0x00}, /* set Mic 1 */ + + { } +}; + +static struct hda_verb alc861_toshiba_init_verbs[] = { + {0x0f, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + + { } +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc861_toshiba_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x0f, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x16, HDA_INPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x1a, HDA_INPUT, 3, + HDA_AMP_MUTE, present ? 0 : HDA_AMP_MUTE); +} + +static void alc861_toshiba_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc861_toshiba_automute(codec); +} + +/* pcm configuration: identiacal with ALC880 */ +#define alc861_pcm_analog_playback alc880_pcm_analog_playback +#define alc861_pcm_analog_capture alc880_pcm_analog_capture +#define alc861_pcm_digital_playback alc880_pcm_digital_playback +#define alc861_pcm_digital_capture alc880_pcm_digital_capture + + +#define ALC861_DIGOUT_NID 0x07 + +static struct hda_channel_mode alc861_8ch_modes[1] = { + { 8, NULL } +}; + +static hda_nid_t alc861_dac_nids[4] = { + /* front, surround, clfe, side */ + 0x03, 0x06, 0x05, 0x04 +}; + +static hda_nid_t alc660_dac_nids[3] = { + /* front, clfe, surround */ + 0x03, 0x05, 0x06 +}; + +static hda_nid_t alc861_adc_nids[1] = { + /* ADC0-2 */ + 0x08, +}; + +static struct hda_input_mux alc861_capture_source = { + .num_items = 5, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x3 }, + { "Line", 0x1 }, + { "CD", 0x4 }, + { "Mixer", 0x5 }, + }, +}; + +/* fill in the dac_nids table from the parsed pin configuration */ +static int alc861_auto_fill_dac_nids(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int i; + hda_nid_t nid; + + spec->multiout.dac_nids = spec->private_dac_nids; + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + if (i >= ARRAY_SIZE(alc861_dac_nids)) + continue; + spec->multiout.dac_nids[i] = alc861_dac_nids[i]; + } + } + spec->multiout.num_dacs = cfg->line_outs; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int alc861_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { + "Front", "Surround", NULL /*CLFE*/, "Side" + }; + hda_nid_t nid; + int i, idx, err; + + for (i = 0; i < cfg->line_outs; i++) { + nid = spec->multiout.dac_nids[i]; + if (!nid) + continue; + if (nid == 0x05) { + /* Center/LFE */ + err = add_control(spec, ALC_CTL_BIND_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_BIND_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + for (idx = 0; idx < ARRAY_SIZE(alc861_dac_nids) - 1; + idx++) + if (nid == alc861_dac_nids[idx]) + break; + sprintf(name, "%s Playback Switch", chname[idx]); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +static int alc861_auto_create_hp_ctls(struct alc_spec *spec, hda_nid_t pin) +{ + int err; + hda_nid_t nid; + + if (!pin) + return 0; + + if ((pin >= 0x0b && pin <= 0x10) || pin == 0x1f || pin == 0x20) { + nid = 0x03; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + spec->multiout.hp_nid = nid; + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc861_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, err, idx, idx1; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + switch (cfg->input_pins[i]) { + case 0x0c: + idx1 = 1; + idx = 2; /* Line In */ + break; + case 0x0f: + idx1 = 2; + idx = 2; /* Line In */ + break; + case 0x0d: + idx1 = 0; + idx = 1; /* Mic In */ + break; + case 0x10: + idx1 = 3; + idx = 1; /* Mic In */ + break; + case 0x11: + idx1 = 4; + idx = 0; /* CD */ + break; + default: + continue; + } + + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], idx, 0x15); + if (err < 0) + return err; + + imux->items[imux->num_items].label = auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = idx1; + imux->num_items++; + } + return 0; +} + +static struct snd_kcontrol_new alc861_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x08, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x08, 0x0, HDA_INPUT), + + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc_mux_enum_info, + .get = alc_mux_enum_get, + .put = alc_mux_enum_put, + }, + { } /* end */ +}; + +static void alc861_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, + int pin_type, int dac_idx) +{ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_type); + snd_hda_codec_write(codec, dac_idx, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); +} + +static void alc861_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x0e, 0x0f, 0x0b); + for (i = 0; i < spec->autocfg.line_outs; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + if (nid) + alc861_auto_set_output_and_unmute(codec, nid, pin_type, + spec->multiout.dac_nids[i]); + } +} + +static void alc861_auto_init_hp_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + alc861_auto_set_output_and_unmute(codec, pin, PIN_HP, + spec->multiout.dac_nids[0]); + pin = spec->autocfg.speaker_pins[0]; + if (pin) + alc861_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); +} + +static void alc861_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (nid >= 0x0c && nid <= 0x11) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN); + } + } +} + +/* parse the BIOS configuration and set up the alc_spec */ +/* return 1 if successful, 0 if the proper config is not found, + * or a negative error code + */ +static int alc861_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc861_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc861_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc861_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc861_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc861_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = alc861_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC861_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] = alc861_auto_init_verbs; + + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + spec->adc_nids = alc861_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc861_adc_nids); + spec->mixers[spec->num_mixers] = alc861_capture_mixer; + spec->num_mixers++; + + store_pin_configs(codec); + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc861_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc861_auto_init_multi_out(codec); + alc861_auto_init_hp_out(codec); + alc861_auto_init_analog_input(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list alc861_loopbacks[] = { + { 0x15, HDA_INPUT, 0 }, + { 0x15, HDA_INPUT, 1 }, + { 0x15, HDA_INPUT, 2 }, + { 0x15, HDA_INPUT, 3 }, + { } /* end */ +}; +#endif + + +/* + * configuration and preset + */ +static const char *alc861_models[ALC861_MODEL_LAST] = { + [ALC861_3ST] = "3stack", + [ALC660_3ST] = "3stack-660", + [ALC861_3ST_DIG] = "3stack-dig", + [ALC861_6ST_DIG] = "6stack-dig", + [ALC861_UNIWILL_M31] = "uniwill-m31", + [ALC861_TOSHIBA] = "toshiba", + [ALC861_ASUS] = "asus", + [ALC861_ASUS_LAPTOP] = "asus-laptop", + [ALC861_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc861_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1205, "ASUS W7J", ALC861_3ST), + SND_PCI_QUIRK(0x1043, 0x1335, "ASUS F2/3", ALC861_ASUS_LAPTOP), + SND_PCI_QUIRK(0x1043, 0x1338, "ASUS F2/3", ALC861_ASUS_LAPTOP), + SND_PCI_QUIRK(0x1043, 0x1393, "ASUS", ALC861_ASUS), + SND_PCI_QUIRK(0x1043, 0x13d7, "ASUS A9rp", ALC861_ASUS_LAPTOP), + SND_PCI_QUIRK(0x1043, 0x81cb, "ASUS P1-AH2", ALC861_3ST_DIG), + SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba", ALC861_TOSHIBA), + /* FIXME: the entry below breaks Toshiba A100 (model=auto works!) + * Any other models that need this preset? + */ + /* SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), */ + SND_PCI_QUIRK(0x1462, 0x7254, "HP dx2200 (MSI MS-7254)", ALC861_3ST), + SND_PCI_QUIRK(0x1462, 0x7297, "HP dx2250 (MSI MS-7297)", ALC861_3ST), + SND_PCI_QUIRK(0x1584, 0x2b01, "Uniwill X40AIx", ALC861_UNIWILL_M31), + SND_PCI_QUIRK(0x1584, 0x9072, "Uniwill m31", ALC861_UNIWILL_M31), + SND_PCI_QUIRK(0x1584, 0x9075, "Airis Praxis N1212", ALC861_ASUS_LAPTOP), + /* FIXME: the below seems conflict */ + /* SND_PCI_QUIRK(0x1584, 0x9075, "Uniwill", ALC861_UNIWILL_M31), */ + SND_PCI_QUIRK(0x1849, 0x0660, "Asrock 939SLI32", ALC660_3ST), + SND_PCI_QUIRK(0x8086, 0xd600, "Intel", ALC861_3ST), + {} +}; + +static struct alc_config_preset alc861_presets[] = { + [ALC861_3ST] = { + .mixers = { alc861_3ST_mixer }, + .init_verbs = { alc861_threestack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861_threestack_modes), + .channel_mode = alc861_threestack_modes, + .need_dac_fix = 1, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC861_3ST_DIG] = { + .mixers = { alc861_base_mixer }, + .init_verbs = { alc861_threestack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .dig_out_nid = ALC861_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861_threestack_modes), + .channel_mode = alc861_threestack_modes, + .need_dac_fix = 1, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC861_6ST_DIG] = { + .mixers = { alc861_base_mixer }, + .init_verbs = { alc861_base_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .dig_out_nid = ALC861_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861_8ch_modes), + .channel_mode = alc861_8ch_modes, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC660_3ST] = { + .mixers = { alc861_3ST_mixer }, + .init_verbs = { alc861_threestack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc660_dac_nids), + .dac_nids = alc660_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861_threestack_modes), + .channel_mode = alc861_threestack_modes, + .need_dac_fix = 1, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC861_UNIWILL_M31] = { + .mixers = { alc861_uniwill_m31_mixer }, + .init_verbs = { alc861_uniwill_m31_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .dig_out_nid = ALC861_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861_uniwill_m31_modes), + .channel_mode = alc861_uniwill_m31_modes, + .need_dac_fix = 1, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC861_TOSHIBA] = { + .mixers = { alc861_toshiba_mixer }, + .init_verbs = { alc861_base_init_verbs, + alc861_toshiba_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + .unsol_event = alc861_toshiba_unsol_event, + .init_hook = alc861_toshiba_automute, + }, + [ALC861_ASUS] = { + .mixers = { alc861_asus_mixer }, + .init_verbs = { alc861_asus_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .dig_out_nid = ALC861_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861_asus_modes), + .channel_mode = alc861_asus_modes, + .need_dac_fix = 1, + .hp_nid = 0x06, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, + [ALC861_ASUS_LAPTOP] = { + .mixers = { alc861_toshiba_mixer, alc861_asus_laptop_mixer }, + .init_verbs = { alc861_asus_init_verbs, + alc861_asus_laptop_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861_dac_nids), + .dac_nids = alc861_dac_nids, + .dig_out_nid = ALC861_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .need_dac_fix = 1, + .num_adc_nids = ARRAY_SIZE(alc861_adc_nids), + .adc_nids = alc861_adc_nids, + .input_mux = &alc861_capture_source, + }, +}; + + +static int patch_alc861(struct hda_codec *codec) +{ + struct alc_spec *spec; + int board_config; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC861_MODEL_LAST, + alc861_models, + alc861_cfg_tbl); + + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC861, " + "trying auto-probe from BIOS...\n"); + board_config = ALC861_AUTO; + } + + if (board_config == ALC861_AUTO) { + /* automatic parse from the BIOS config */ + err = alc861_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC861_3ST_DIG; + } + } + + if (board_config != ALC861_AUTO) + setup_preset(spec, &alc861_presets[board_config]); + + spec->stream_name_analog = "ALC861 Analog"; + spec->stream_analog_playback = &alc861_pcm_analog_playback; + spec->stream_analog_capture = &alc861_pcm_analog_capture; + + spec->stream_name_digital = "ALC861 Digital"; + spec->stream_digital_playback = &alc861_pcm_digital_playback; + spec->stream_digital_capture = &alc861_pcm_digital_capture; + + spec->vmaster_nid = 0x03; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC861_AUTO) + spec->init_hook = alc861_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc861_loopbacks; +#endif + + return 0; +} + +/* + * ALC861-VD support + * + * Based on ALC882 + * + * In addition, an independent DAC + */ +#define ALC861VD_DIGOUT_NID 0x06 + +static hda_nid_t alc861vd_dac_nids[4] = { + /* front, surr, clfe, side surr */ + 0x02, 0x03, 0x04, 0x05 +}; + +/* dac_nids for ALC660vd are in a different order - according to + * Realtek's driver. + * This should probably tesult in a different mixer for 6stack models + * of ALC660vd codecs, but for now there is only 3stack mixer + * - and it is the same as in 861vd. + * adc_nids in ALC660vd are (is) the same as in 861vd + */ +static hda_nid_t alc660vd_dac_nids[3] = { + /* front, rear, clfe, rear_surr */ + 0x02, 0x04, 0x03 +}; + +static hda_nid_t alc861vd_adc_nids[1] = { + /* ADC0 */ + 0x09, +}; + +static hda_nid_t alc861vd_capsrc_nids[1] = { 0x22 }; + +/* input MUX */ +/* FIXME: should be a matrix-type input source selection */ +static struct hda_input_mux alc861vd_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc861vd_dallas_capture_source = { + .num_items = 2, + .items = { + { "Ext Mic", 0x0 }, + { "Int Mic", 0x1 }, + }, +}; + +static struct hda_input_mux alc861vd_hp_capture_source = { + .num_items = 2, + .items = { + { "Front Mic", 0x0 }, + { "ATAPI Mic", 0x1 }, + }, +}; + +#define alc861vd_mux_enum_info alc_mux_enum_info +#define alc861vd_mux_enum_get alc_mux_enum_get +/* ALC861VD has the ALC882-type input selection (but has only one ADC) */ +#define alc861vd_mux_enum_put alc882_mux_enum_put + +/* + * 2ch mode + */ +static struct hda_channel_mode alc861vd_3stack_2ch_modes[1] = { + { 2, NULL } +}; + +/* + * 6ch mode + */ +static struct hda_verb alc861vd_6stack_ch6_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +/* + * 8ch mode + */ +static struct hda_verb alc861vd_6stack_ch8_init[] = { + { 0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +static struct hda_channel_mode alc861vd_6stack_modes[2] = { + { 6, alc861vd_6stack_ch6_init }, + { 8, alc861vd_6stack_ch8_init }, +}; + +static struct snd_kcontrol_new alc861vd_chmode_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc861vd_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT), + + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc861vd_mux_enum_info, + .get = alc861vd_mux_enum_get, + .put = alc861vd_mux_enum_put, + }, + { } /* end */ +}; + +/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17 + * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b + */ +static struct snd_kcontrol_new alc861vd_6st_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + + HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x0d, 2, HDA_INPUT), + + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, + HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, + HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x0e, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 2, HDA_INPUT), + + HDA_CODEC_VOLUME("Side Playback Volume", 0x05, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Side Playback Switch", 0x0f, 2, HDA_INPUT), + + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, 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 Boost", 0x19, 0, 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("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + + { } /* end */ +}; + +static struct snd_kcontrol_new alc861vd_3st_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, 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 Boost", 0x19, 0, 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("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + + HDA_CODEC_VOLUME("PC Speaker Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Speaker Playback Switch", 0x0b, 0x05, HDA_INPUT), + + { } /* end */ +}; + +static struct snd_kcontrol_new alc861vd_lenovo_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + /*HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT),*/ + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, 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 Boost", 0x19, 0, 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("CD Playback Volume", 0x0b, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT), + + { } /* end */ +}; + +/* Pin assignment: Speaker=0x14, HP = 0x15, + * Ext Mic=0x18, Int Mic = 0x19, CD = 0x1c, PC Beep = 0x1d + */ +static struct snd_kcontrol_new alc861vd_dallas_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Ext Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Ext Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("Int Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Int Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("PC Beep Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Switch", 0x0b, 0x05, HDA_INPUT), + { } /* end */ +}; + +/* Pin assignment: Speaker=0x14, Line-out = 0x15, + * Front Mic=0x18, ATAPI Mic = 0x19, + */ +static struct snd_kcontrol_new alc861vd_hp_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Headphone Playback Switch", 0x0d, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("ATAPI Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("ATAPI Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + + { } /* end */ +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc861vd_volume_init_verbs[] = { + /* + * Unmute ADC0 and set the default input to mic-in + */ + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of + * the analog-loopback mixer widget + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* Capture mixer: unmute Mic, F-Mic, Line, CD inputs */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output mixers (0x02 - 0x05) + */ + /* set vol=0 to output mixers */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + { } +}; + +/* + * 3-stack pin configuration: + * front = 0x14, mic/clfe = 0x18, HP = 0x19, line/surr = 0x1a, f-mic = 0x1b + */ +static struct hda_verb alc861vd_3stack_init_verbs[] = { + /* + * Set pin mode and muting + */ + /* set front pin widgets 0x14 for output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line-2 In: Headphone output (output 0 - 0x0c) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +/* + * 6-stack pin configuration: + */ +static struct hda_verb alc861vd_6stack_init_verbs[] = { + /* + * Set pin mode and muting + */ + /* set front pin widgets 0x14 for output */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x00}, + + /* Rear Pin: output 1 (0x0d) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* CLFE Pin: output 2 (0x0e) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* Side Pin: output 3 (0x0f) */ + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_CONNECT_SEL, 0x03}, + + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line-2 In: Headphone output (output 0 - 0x0c) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + { } +}; + +static struct hda_verb alc861vd_eapd_verbs[] = { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + { } +}; + +static struct hda_verb alc660vd_eapd_verbs[] = { + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 2}, + { } +}; + +static struct hda_verb alc861vd_lenovo_unsol_verbs[] = { + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(5)}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {} +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc861vd_lenovo_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc861vd_lenovo_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0b, HDA_INPUT, 1, + HDA_AMP_MUTE, bits); +} + +static void alc861vd_lenovo_automute(struct hda_codec *codec) +{ + alc861vd_lenovo_hp_automute(codec); + alc861vd_lenovo_mic_automute(codec); +} + +static void alc861vd_lenovo_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc861vd_lenovo_hp_automute(codec); + break; + case ALC880_MIC_EVENT: + alc861vd_lenovo_mic_automute(codec); + break; + } +} + +static struct hda_verb alc861vd_dallas_verbs[] = { + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF50}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + + { } /* end */ +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc861vd_dallas_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + +static void alc861vd_dallas_unsol_event(struct hda_codec *codec, unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc861vd_dallas_automute(codec); +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc861vd_loopbacks alc880_loopbacks +#endif + +/* pcm configuration: identiacal with ALC880 */ +#define alc861vd_pcm_analog_playback alc880_pcm_analog_playback +#define alc861vd_pcm_analog_capture alc880_pcm_analog_capture +#define alc861vd_pcm_digital_playback alc880_pcm_digital_playback +#define alc861vd_pcm_digital_capture alc880_pcm_digital_capture + +/* + * configuration and preset + */ +static const char *alc861vd_models[ALC861VD_MODEL_LAST] = { + [ALC660VD_3ST] = "3stack-660", + [ALC660VD_3ST_DIG] = "3stack-660-digout", + [ALC861VD_3ST] = "3stack", + [ALC861VD_3ST_DIG] = "3stack-digout", + [ALC861VD_6ST_DIG] = "6stack-digout", + [ALC861VD_LENOVO] = "lenovo", + [ALC861VD_DALLAS] = "dallas", + [ALC861VD_HP] = "hp", + [ALC861VD_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc861vd_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0xa88d, "Realtek ALC660 demo", ALC660VD_3ST), + SND_PCI_QUIRK(0x103c, 0x30bf, "HP TX1000", ALC861VD_HP), + SND_PCI_QUIRK(0x1043, 0x12e2, "Asus z35m", ALC660VD_3ST), + SND_PCI_QUIRK(0x1043, 0x1339, "Asus G1", ALC660VD_3ST), + SND_PCI_QUIRK(0x1043, 0x1633, "Asus V1Sn", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x1043, 0x81e7, "ASUS", ALC660VD_3ST_DIG), + SND_PCI_QUIRK(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST), + SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba A135", ALC861VD_LENOVO), + /*SND_PCI_QUIRK(0x1179, 0xff00, "DALLAS", ALC861VD_DALLAS),*/ /*lenovo*/ + SND_PCI_QUIRK(0x1179, 0xff01, "DALLAS", ALC861VD_DALLAS), + SND_PCI_QUIRK(0x1179, 0xff03, "Toshiba P205", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x1179, 0xff31, "Toshiba L30-149", ALC861VD_DALLAS), + SND_PCI_QUIRK(0x1565, 0x820d, "Biostar NF61S SE", ALC861VD_6ST_DIG), + SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo 3000 C200", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x17aa, 0x384e, "Lenovo 3000 N200", ALC861VD_LENOVO), + SND_PCI_QUIRK(0x1849, 0x0862, "ASRock K8NF6G-VSTA", ALC861VD_6ST_DIG), + {} +}; + +static struct alc_config_preset alc861vd_presets[] = { + [ALC660VD_3ST] = { + .mixers = { alc861vd_3st_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc660vd_dac_nids), + .dac_nids = alc660vd_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + }, + [ALC660VD_3ST_DIG] = { + .mixers = { alc861vd_3st_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc660vd_dac_nids), + .dac_nids = alc660vd_dac_nids, + .dig_out_nid = ALC861VD_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + }, + [ALC861VD_3ST] = { + .mixers = { alc861vd_3st_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861vd_dac_nids), + .dac_nids = alc861vd_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + }, + [ALC861VD_3ST_DIG] = { + .mixers = { alc861vd_3st_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861vd_dac_nids), + .dac_nids = alc861vd_dac_nids, + .dig_out_nid = ALC861VD_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + }, + [ALC861VD_6ST_DIG] = { + .mixers = { alc861vd_6st_mixer, alc861vd_chmode_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_6stack_init_verbs }, + .num_dacs = ARRAY_SIZE(alc861vd_dac_nids), + .dac_nids = alc861vd_dac_nids, + .dig_out_nid = ALC861VD_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861vd_6stack_modes), + .channel_mode = alc861vd_6stack_modes, + .input_mux = &alc861vd_capture_source, + }, + [ALC861VD_LENOVO] = { + .mixers = { alc861vd_lenovo_mixer }, + .init_verbs = { alc861vd_volume_init_verbs, + alc861vd_3stack_init_verbs, + alc861vd_eapd_verbs, + alc861vd_lenovo_unsol_verbs }, + .num_dacs = ARRAY_SIZE(alc660vd_dac_nids), + .dac_nids = alc660vd_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_capture_source, + .unsol_event = alc861vd_lenovo_unsol_event, + .init_hook = alc861vd_lenovo_automute, + }, + [ALC861VD_DALLAS] = { + .mixers = { alc861vd_dallas_mixer }, + .init_verbs = { alc861vd_dallas_verbs }, + .num_dacs = ARRAY_SIZE(alc861vd_dac_nids), + .dac_nids = alc861vd_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_dallas_capture_source, + .unsol_event = alc861vd_dallas_unsol_event, + .init_hook = alc861vd_dallas_automute, + }, + [ALC861VD_HP] = { + .mixers = { alc861vd_hp_mixer }, + .init_verbs = { alc861vd_dallas_verbs, alc861vd_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc861vd_dac_nids), + .dac_nids = alc861vd_dac_nids, + .dig_out_nid = ALC861VD_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc861vd_3stack_2ch_modes), + .channel_mode = alc861vd_3stack_2ch_modes, + .input_mux = &alc861vd_hp_capture_source, + .unsol_event = alc861vd_dallas_unsol_event, + .init_hook = alc861vd_dallas_automute, + }, +}; + +/* + * BIOS auto configuration + */ +static void alc861vd_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, int dac_idx) +{ + alc_set_pin_output(codec, nid, pin_type); +} + +static void alc861vd_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x15, 0x1b, 0x14); + for (i = 0; i <= HDA_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + if (nid) + alc861vd_auto_set_output_and_unmute(codec, nid, + pin_type, i); + } +} + + +static void alc861vd_auto_init_hp_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front and use dac 0 */ + alc861vd_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); + pin = spec->autocfg.speaker_pins[0]; + if (pin) + alc861vd_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); +} + +#define alc861vd_is_input_pin(nid) alc880_is_input_pin(nid) +#define ALC861VD_PIN_CD_NID ALC880_PIN_CD_NID + +static void alc861vd_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (alc861vd_is_input_pin(nid)) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN); + if (nid != ALC861VD_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } + } +} + +#define alc861vd_auto_init_input_src alc882_auto_init_input_src + +#define alc861vd_idx_to_mixer_vol(nid) ((nid) + 0x02) +#define alc861vd_idx_to_mixer_switch(nid) ((nid) + 0x0c) + +/* add playback controls from the parsed DAC table */ +/* Based on ALC880 version. But ALC861VD has separate, + * different NIDs for mute/unmute switch and volume control */ +static int alc861vd_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = {"Front", "Surround", "CLFE", "Side"}; + hda_nid_t nid_v, nid_s; + int i, err; + + for (i = 0; i < cfg->line_outs; i++) { + if (!spec->multiout.dac_nids[i]) + continue; + nid_v = alc861vd_idx_to_mixer_vol( + alc880_dac_to_idx( + spec->multiout.dac_nids[i])); + nid_s = alc861vd_idx_to_mixer_switch( + alc880_dac_to_idx( + spec->multiout.dac_nids[i])); + + if (i == 2) { + /* Center/LFE */ + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_v, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_v, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_BIND_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_s, 1, 2, + HDA_INPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_BIND_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_s, 2, 2, + HDA_INPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_v, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_s, 3, 2, + HDA_INPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +/* add playback controls for speaker and HP outputs */ +/* Based on ALC880 version. But ALC861VD has separate, + * different NIDs for mute/unmute switch and volume control */ +static int alc861vd_auto_create_extra_out(struct alc_spec *spec, + hda_nid_t pin, const char *pfx) +{ + hda_nid_t nid_v, nid_s; + int err; + char name[32]; + + if (!pin) + return 0; + + if (alc880_is_fixed_pin(pin)) { + nid_v = alc880_idx_to_dac(alc880_fixed_pin_idx(pin)); + /* specify the DAC as the extra output */ + if (!spec->multiout.hp_nid) + spec->multiout.hp_nid = nid_v; + else + spec->multiout.extra_out_nid[0] = nid_v; + /* control HP volume/switch on the output mixer amp */ + nid_v = alc861vd_idx_to_mixer_vol( + alc880_fixed_pin_idx(pin)); + nid_s = alc861vd_idx_to_mixer_switch( + alc880_fixed_pin_idx(pin)); + + sprintf(name, "%s Playback Volume", pfx); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_v, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_s, 3, 2, HDA_INPUT)); + if (err < 0) + return err; + } else if (alc880_is_multi_pin(pin)) { + /* set manual connection */ + /* we have only a switch on HP-out PIN */ + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* parse the BIOS configuration and set up the alc_spec + * return 1 if successful, 0 if the proper config is not found, + * or a negative error code + * Based on ALC880 version - had to change it to override + * alc880_auto_create_extra_out and alc880_auto_create_multi_out_ctls */ +static int alc861vd_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc861vd_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc861vd_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc880_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc861vd_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc861vd_auto_create_extra_out(spec, + spec->autocfg.speaker_pins[0], + "Speaker"); + if (err < 0) + return err; + err = alc861vd_auto_create_extra_out(spec, + spec->autocfg.hp_pins[0], + "Headphone"); + if (err < 0) + return err; + err = alc880_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC861VD_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_init_verbs++] + = alc861vd_volume_init_verbs; + + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + store_pin_configs(codec); + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc861vd_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc861vd_auto_init_multi_out(codec); + alc861vd_auto_init_hp_out(codec); + alc861vd_auto_init_analog_input(codec); + alc861vd_auto_init_input_src(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +static int patch_alc861vd(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err, board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + board_config = snd_hda_check_board_config(codec, ALC861VD_MODEL_LAST, + alc861vd_models, + alc861vd_cfg_tbl); + + if (board_config < 0 || board_config >= ALC861VD_MODEL_LAST) { + printk(KERN_INFO "hda_codec: Unknown model for ALC660VD/" + "ALC861VD, trying auto-probe from BIOS...\n"); + board_config = ALC861VD_AUTO; + } + + if (board_config == ALC861VD_AUTO) { + /* automatic parse from the BIOS config */ + err = alc861vd_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC861VD_3ST; + } + } + + if (board_config != ALC861VD_AUTO) + setup_preset(spec, &alc861vd_presets[board_config]); + + if (codec->vendor_id == 0x10ec0660) { + spec->stream_name_analog = "ALC660-VD Analog"; + spec->stream_name_digital = "ALC660-VD Digital"; + /* always turn on EAPD */ + spec->init_verbs[spec->num_init_verbs++] = alc660vd_eapd_verbs; + } else { + spec->stream_name_analog = "ALC861VD Analog"; + spec->stream_name_digital = "ALC861VD Digital"; + } + + spec->stream_analog_playback = &alc861vd_pcm_analog_playback; + spec->stream_analog_capture = &alc861vd_pcm_analog_capture; + + spec->stream_digital_playback = &alc861vd_pcm_digital_playback; + spec->stream_digital_capture = &alc861vd_pcm_digital_capture; + + spec->adc_nids = alc861vd_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc861vd_adc_nids); + spec->capsrc_nids = alc861vd_capsrc_nids; + + spec->mixers[spec->num_mixers] = alc861vd_capture_mixer; + spec->num_mixers++; + + spec->vmaster_nid = 0x02; + + codec->patch_ops = alc_patch_ops; + + if (board_config == ALC861VD_AUTO) + spec->init_hook = alc861vd_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc861vd_loopbacks; +#endif + + return 0; +} + +/* + * ALC662 support + * + * ALC662 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). + */ +#define ALC662_DIGOUT_NID 0x06 +#define ALC662_DIGIN_NID 0x0a + +static hda_nid_t alc662_dac_nids[4] = { + /* front, rear, clfe, rear_surr */ + 0x02, 0x03, 0x04 +}; + +static hda_nid_t alc662_adc_nids[1] = { + /* ADC1-2 */ + 0x09, +}; + +static hda_nid_t alc662_capsrc_nids[1] = { 0x22 }; + +/* input MUX */ +/* FIXME: should be a matrix-type input source selection */ +static struct hda_input_mux alc662_capture_source = { + .num_items = 4, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + +static struct hda_input_mux alc662_lenovo_101e_capture_source = { + .num_items = 2, + .items = { + { "Mic", 0x1 }, + { "Line", 0x2 }, + }, +}; + +static struct hda_input_mux alc662_eeepc_capture_source = { + .num_items = 2, + .items = { + { "i-Mic", 0x1 }, + { "e-Mic", 0x0 }, + }, +}; + +static struct hda_input_mux alc663_capture_source = { + .num_items = 3, + .items = { + { "Mic", 0x0 }, + { "Front Mic", 0x1 }, + { "Line", 0x2 }, + }, +}; + +static struct hda_input_mux alc663_m51va_capture_source = { + .num_items = 2, + .items = { + { "Ext-Mic", 0x0 }, + { "D-Mic", 0x9 }, + }, +}; + +#define alc662_mux_enum_info alc_mux_enum_info +#define alc662_mux_enum_get alc_mux_enum_get +#define alc662_mux_enum_put alc882_mux_enum_put + +/* + * 2ch mode + */ +static struct hda_channel_mode alc662_3ST_2ch_modes[1] = { + { 2, NULL } +}; + +/* + * 2ch mode + */ +static struct hda_verb alc662_3ST_ch2_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc662_3ST_ch6_init[] = { + { 0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x02 }, + { 0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE }, + { 0x1a, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { } /* end */ +}; + +static struct hda_channel_mode alc662_3ST_6ch_modes[2] = { + { 2, alc662_3ST_ch2_init }, + { 6, alc662_3ST_ch6_init }, +}; + +/* + * 2ch mode + */ +static struct hda_verb alc662_sixstack_ch6_init[] = { + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x00 }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +/* + * 6ch mode + */ +static struct hda_verb alc662_sixstack_ch8_init[] = { + { 0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { 0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + { } /* end */ +}; + +static struct hda_channel_mode alc662_5stack_modes[2] = { + { 2, alc662_sixstack_ch6_init }, + { 6, alc662_sixstack_ch8_init }, +}; + +/* Pin assignment: Front=0x14, Rear=0x15, CLFE=0x16, Side=0x17 + * Mic=0x18, Front Mic=0x19, Line-In=0x1a, HP=0x1b + */ + +static struct snd_kcontrol_new alc662_base_mixer[] = { + /* output mixer control */ + HDA_CODEC_VOLUME("Front Playback Volume", 0x2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x3, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x0d, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + + /*Input mixer control */ + HDA_CODEC_VOLUME("CD Playback Volume", 0xb, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0xb, 0x4, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0xb, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0xb, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0xb, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0xb, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Front Mic Playback Volume", 0xb, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0xb, 0x01, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_3ST_2ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT), + 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), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_3ST_6ch_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x0c, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x0d, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x0e, 1, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x0e, 2, 0x0, HDA_INPUT), + 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), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_lenovo_101e_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Front Playback Switch", 0x02, 2, HDA_INPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Speaker Playback Switch", 0x03, 2, HDA_INPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + 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, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_eeepc_p701_mixer[] = { + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line-Out Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("e-Mic Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("e-Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("e-Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + + HDA_CODEC_VOLUME("i-Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_eeepc_ep20_mixer[] = { + HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Line-Out Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("Surround Playback Switch", 0x03, 2, HDA_INPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x04, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x04, 2, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE_MONO("Center Playback Switch", 0x04, 1, 2, HDA_INPUT), + HDA_BIND_MUTE_MONO("LFE Playback Switch", 0x04, 2, 2, HDA_INPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("MuteCtrl Playback Switch", 0x0c, 2, 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), + { } /* end */ +}; + +static struct hda_bind_ctls alc663_asus_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x03, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct hda_bind_ctls alc663_asus_one_bind_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc663_m51va_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &alc663_asus_one_bind_switch), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + +static struct hda_bind_ctls alc663_asus_tree_bind_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x21, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc663_two_hp_m1_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &alc663_asus_tree_bind_switch), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + + { } /* end */ +}; + +static struct hda_bind_ctls alc663_asus_four_bind_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x15, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc663_two_hp_m2_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &alc663_asus_four_bind_switch), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_1bjd_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("F-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("F-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct hda_bind_ctls alc663_asus_two_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x04, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct hda_bind_ctls alc663_asus_two_bind_switch = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +static struct snd_kcontrol_new alc663_asus_21jd_clfe_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", + &alc663_asus_two_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &alc663_asus_two_bind_switch), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc663_asus_15jd_clfe_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &alc663_asus_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &alc663_asus_two_bind_switch), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc663_g71v_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Front Playback Volume", 0x03, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc663_g50v_mixer[] = { + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x02, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Mic Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Mic Playback Switch", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Line Playback Switch", 0x0b, 0x02, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_chmode_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Channel Mode", + .info = alc_ch_mode_info, + .get = alc_ch_mode_get, + .put = alc_ch_mode_put, + }, + { } /* end */ +}; + +static struct hda_verb alc662_init_verbs[] = { + /* ADC: mute amp left and right */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* Front mixer: unmute input/output amp left and right (volume = 0) */ + + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + /* Front Pin: output 0 (0x0c) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Rear Pin: output 1 (0x0d) */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* CLFE Pin: output 2 (0x0e) */ + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Mic (rear) pin: input vref at 80% */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Front Mic pin: input vref at 80% */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line In pin: input */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + /* Line-2 In: Headphone output (output 0 - 0x0c) */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* CD pin widget for input */ + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */ + /* Input mixer */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* always trun on EAPD */ + {0x14, AC_VERB_SET_EAPD_BTLENABLE, 2}, + {0x15, AC_VERB_SET_EAPD_BTLENABLE, 2}, + + { } +}; + +static struct hda_verb alc662_sue_init_verbs[] = { + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_FRONT_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc662_eeepc_sue_init_verbs[] = { + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* Set Unsolicited Event*/ +static struct hda_verb alc662_eeepc_ep20_sue_init_verbs[] = { + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb alc662_auto_init_verbs[] = { + /* + * Unmute ADC and set the default input to mic-in + */ + {0x09, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + * Note: PASD motherboards uses the Line In 2 as the input for front + * panel mic (mic 2) + */ + /* Amp Indices: Mic1 = 0, Mic2 = 1, Line1 = 2, Line2 = 3, CD = 4 */ + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* + * Set up output mixers (0x0c - 0x0f) + */ + /* set vol=0 to output mixers */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* set up input amps for analog loopback */ + /* Amp Indices: DAC = 0, mixer = 1 */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + + + /* FIXME: use matrix-type input source selection */ + /* Mixer elements: 0x18, 19, 1a, 1b, 1c, 1d, 14, 15, 16, 17, 0b */ + /* Input mixer */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x23, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + { } +}; + +/* additional verbs for ALC663 */ +static struct hda_verb alc663_auto_init_verbs[] = { + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + { } +}; + +static struct hda_verb alc663_m51va_init_verbs[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x21, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(9)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_21jd_amic_init_verbs[] = { + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x21, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc662_1bjd_amic_init_verbs[] = { + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_15jd_amic_init_verbs[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_two_hp_amic_m1_init_verbs[] = { + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x21, AC_VERB_SET_CONNECT_SEL, 0x0}, /* Headphone */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x0}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_two_hp_amic_m2_init_verbs[] = { + {0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1b, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Headphone */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, /* Headphone */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_g71v_init_verbs[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + /* {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, */ + /* {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, */ /* Headphone */ + + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x21, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Headphone */ + + {0x15, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_FRONT_EVENT}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_MIC_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc663_g50v_init_verbs[] = { + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x21, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x21, AC_VERB_SET_CONNECT_SEL, 0x00}, /* Headphone */ + + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x21, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +static struct hda_verb alc662_ecs_init_verbs[] = { + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, 0x701f}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x18, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_MIC_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | ALC880_HP_EVENT}, + {} +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new alc662_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = alc662_mux_enum_info, + .get = alc662_mux_enum_get, + .put = alc662_mux_enum_put, + }, + { } /* end */ +}; + +static struct snd_kcontrol_new alc662_auto_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0x0, HDA_INPUT), + { } /* end */ +}; + +static void alc662_lenovo_101e_ispeaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc662_lenovo_101e_all_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc662_lenovo_101e_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc662_lenovo_101e_all_automute(codec); + if ((res >> 26) == ALC880_FRONT_EVENT) + alc662_lenovo_101e_ispeaker_automute(codec); +} + +static void alc662_eeepc_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x22, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write(codec, 0x22, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); + snd_hda_codec_write(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); +} + +/* unsolicited event for HP jack sensing */ +static void alc662_eeepc_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc262_hippo1_automute( codec ); + + if ((res >> 26) == ALC880_MIC_EVENT) + alc662_eeepc_mic_automute(codec); +} + +static void alc662_eeepc_inithook(struct hda_codec *codec) +{ + alc262_hippo1_automute( codec ); + alc662_eeepc_mic_automute(codec); +} + +static void alc662_eeepc_ep20_automute(struct hda_codec *codec) +{ + unsigned int mute; + unsigned int present; + + snd_hda_codec_read(codec, 0x14, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0); + present = (present & 0x80000000) != 0; + if (present) { + /* mute internal speaker */ + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, HDA_AMP_MUTE); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x14, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_stereo(codec, 0x1b, HDA_OUTPUT, 0, + HDA_AMP_MUTE, mute); + } +} + +/* unsolicited event for HP jack sensing */ +static void alc662_eeepc_ep20_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + if ((res >> 26) == ALC880_HP_EVENT) + alc662_eeepc_ep20_automute(codec); +} + +static void alc662_eeepc_ep20_inithook(struct hda_codec *codec) +{ + alc662_eeepc_ep20_automute(codec); +} + +static void alc663_m51va_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x21, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); +} + +static void alc663_21jd_two_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x21, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); +} + +static void alc663_15jd_two_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 0, + AMP_IN_MUTE(0), bits); + snd_hda_codec_amp_stereo(codec, 0x0e, HDA_INPUT, 1, + AMP_IN_MUTE(0), bits); +} + +static void alc662_f5z_speaker_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? 0 : PIN_OUT; + snd_hda_codec_write(codec, 0x14, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, bits); +} + +static void alc663_two_hp_m1_speaker_automute(struct hda_codec *codec) +{ + unsigned int present1, present2; + + present1 = snd_hda_codec_read(codec, 0x21, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + present2 = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + + if (present1 || present2) { + snd_hda_codec_write_cache(codec, 0x14, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, 0); + } else { + snd_hda_codec_write_cache(codec, 0x14, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT); + } +} + +static void alc663_two_hp_m2_speaker_automute(struct hda_codec *codec) +{ + unsigned int present1, present2; + + present1 = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + present2 = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + + if (present1 || present2) { + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), AMP_IN_MUTE(0)); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), AMP_IN_MUTE(0)); + } else { + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 0, + AMP_IN_MUTE(0), 0); + snd_hda_codec_amp_stereo(codec, 0x0c, HDA_INPUT, 1, + AMP_IN_MUTE(0), 0); + } +} + +static void alc663_m51va_mic_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + snd_hda_codec_write_cache(codec, 0x22, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write_cache(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x00 << 8) | (present ? 0 : 0x80)); + snd_hda_codec_write_cache(codec, 0x22, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x09 << 8) | (present ? 0x80 : 0)); + snd_hda_codec_write_cache(codec, 0x23, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x09 << 8) | (present ? 0x80 : 0)); +} + +static void alc663_m51va_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_m51va_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc663_m51va_mic_automute(codec); + break; + } +} + +static void alc663_m51va_inithook(struct hda_codec *codec) +{ + alc663_m51va_speaker_automute(codec); + alc663_m51va_mic_automute(codec); +} + +/* ***************** Mode1 ******************************/ +static void alc663_mode1_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_m51va_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_mode1_inithook(struct hda_codec *codec) +{ + alc663_m51va_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} +/* ***************** Mode2 ******************************/ +static void alc662_mode2_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc662_f5z_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc662_mode2_inithook(struct hda_codec *codec) +{ + alc662_f5z_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} +/* ***************** Mode3 ******************************/ +static void alc663_mode3_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_two_hp_m1_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_mode3_inithook(struct hda_codec *codec) +{ + alc663_two_hp_m1_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} +/* ***************** Mode4 ******************************/ +static void alc663_mode4_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_21jd_two_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_mode4_inithook(struct hda_codec *codec) +{ + alc663_21jd_two_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} +/* ***************** Mode5 ******************************/ +static void alc663_mode5_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_15jd_two_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_mode5_inithook(struct hda_codec *codec) +{ + alc663_15jd_two_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} +/* ***************** Mode6 ******************************/ +static void alc663_mode6_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_two_hp_m2_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_mode6_inithook(struct hda_codec *codec) +{ + alc663_two_hp_m2_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} + +static void alc663_g71v_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x21, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x15, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc663_g71v_front_automute(struct hda_codec *codec) +{ + unsigned int present; + unsigned char bits; + + present = snd_hda_codec_read(codec, 0x15, 0, + AC_VERB_GET_PIN_SENSE, 0) + & AC_PINSENSE_PRESENCE; + bits = present ? HDA_AMP_MUTE : 0; + snd_hda_codec_amp_stereo(codec, 0x14, HDA_OUTPUT, 0, + HDA_AMP_MUTE, bits); +} + +static void alc663_g71v_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_g71v_hp_automute(codec); + break; + case ALC880_FRONT_EVENT: + alc663_g71v_front_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_g71v_inithook(struct hda_codec *codec) +{ + alc663_g71v_front_automute(codec); + alc663_g71v_hp_automute(codec); + alc662_eeepc_mic_automute(codec); +} + +static void alc663_g50v_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + switch (res >> 26) { + case ALC880_HP_EVENT: + alc663_m51va_speaker_automute(codec); + break; + case ALC880_MIC_EVENT: + alc662_eeepc_mic_automute(codec); + break; + } +} + +static void alc663_g50v_inithook(struct hda_codec *codec) +{ + alc663_m51va_speaker_automute(codec); + alc662_eeepc_mic_automute(codec); +} + +/* bind hp and internal speaker mute (with plug check) */ +static int alc662_ecs_master_sw_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + long *valp = ucontrol->value.integer.value; + int change; + + change = snd_hda_codec_amp_update(codec, 0x1b, 0, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[0] ? 0 : HDA_AMP_MUTE); + change |= snd_hda_codec_amp_update(codec, 0x1b, 1, HDA_OUTPUT, 0, + HDA_AMP_MUTE, + valp[1] ? 0 : HDA_AMP_MUTE); + if (change) + alc262_hippo1_automute(codec); + return change; +} + +static struct snd_kcontrol_new alc662_ecs_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0x02, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = snd_hda_mixer_amp_switch_info, + .get = snd_hda_mixer_amp_switch_get, + .put = alc662_ecs_master_sw_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x1b, 3, 0, HDA_OUTPUT), + }, + + HDA_CODEC_VOLUME("e-Mic/LineIn Boost", 0x18, 0, HDA_INPUT), + HDA_CODEC_VOLUME("e-Mic/LineIn Playback Volume", 0x0b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("e-Mic/LineIn Playback Switch", 0x0b, 0x0, HDA_INPUT), + + HDA_CODEC_VOLUME("i-Mic Boost", 0x19, 0, HDA_INPUT), + HDA_CODEC_VOLUME("i-Mic Playback Volume", 0x0b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("i-Mic Playback Switch", 0x0b, 0x1, HDA_INPUT), + { } /* end */ +}; + +#ifdef CONFIG_SND_HDA_POWER_SAVE +#define alc662_loopbacks alc880_loopbacks +#endif + + +/* pcm configuration: identiacal with ALC880 */ +#define alc662_pcm_analog_playback alc880_pcm_analog_playback +#define alc662_pcm_analog_capture alc880_pcm_analog_capture +#define alc662_pcm_digital_playback alc880_pcm_digital_playback +#define alc662_pcm_digital_capture alc880_pcm_digital_capture + +/* + * configuration and preset + */ +static const char *alc662_models[ALC662_MODEL_LAST] = { + [ALC662_3ST_2ch_DIG] = "3stack-dig", + [ALC662_3ST_6ch_DIG] = "3stack-6ch-dig", + [ALC662_3ST_6ch] = "3stack-6ch", + [ALC662_5ST_DIG] = "6stack-dig", + [ALC662_LENOVO_101E] = "lenovo-101e", + [ALC662_ASUS_EEEPC_P701] = "eeepc-p701", + [ALC662_ASUS_EEEPC_EP20] = "eeepc-ep20", + [ALC662_ECS] = "ecs", + [ALC663_ASUS_M51VA] = "m51va", + [ALC663_ASUS_G71V] = "g71v", + [ALC663_ASUS_H13] = "h13", + [ALC663_ASUS_G50V] = "g50v", + [ALC663_ASUS_MODE1] = "asus-mode1", + [ALC662_ASUS_MODE2] = "asus-mode2", + [ALC663_ASUS_MODE3] = "asus-mode3", + [ALC663_ASUS_MODE4] = "asus-mode4", + [ALC663_ASUS_MODE5] = "asus-mode5", + [ALC663_ASUS_MODE6] = "asus-mode6", + [ALC662_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc662_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1878, "ASUS M51VA", ALC663_ASUS_M51VA), + SND_PCI_QUIRK(0x1043, 0x19a3, "ASUS G50V", ALC663_ASUS_G50V), + SND_PCI_QUIRK(0x1043, 0x8290, "ASUS P5GC-MX", ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1043, 0x82a1, "ASUS Eeepc", ALC662_ASUS_EEEPC_P701), + SND_PCI_QUIRK(0x1043, 0x82d1, "ASUS Eeepc EP20", ALC662_ASUS_EEEPC_EP20), + SND_PCI_QUIRK(0x1043, 0x1903, "ASUS F5GL", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1878, "ASUS M50Vr", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1000, "ASUS N50Vm", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19b3, "ASUS F7Z", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1953, "ASUS NB", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19a3, "ASUS NB", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x11d3, "ASUS NB", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1203, "ASUS NB", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19e3, "ASUS NB", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x1993, "ASUS N20", ALC663_ASUS_MODE1), + SND_PCI_QUIRK(0x1043, 0x19c3, "ASUS F5Z/F6x", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1339, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1913, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1843, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1813, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x11f3, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1876, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1864, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1783, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1753, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x16c3, "ASUS NB", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1933, "ASUS F80Q", ALC662_ASUS_MODE2), + SND_PCI_QUIRK(0x1043, 0x1893, "ASUS M50Vm", ALC663_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x11c3, "ASUS M70V", ALC663_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1963, "ASUS X71C", ALC663_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1894, "ASUS X55", ALC663_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x1092, "ASUS NB", ALC663_ASUS_MODE3), + SND_PCI_QUIRK(0x1043, 0x19f3, "ASUS NB", ALC663_ASUS_MODE4), + SND_PCI_QUIRK(0x1043, 0x1823, "ASUS NB", ALC663_ASUS_MODE5), + SND_PCI_QUIRK(0x1043, 0x1833, "ASUS NB", ALC663_ASUS_MODE6), + SND_PCI_QUIRK(0x1043, 0x1763, "ASUS NB", ALC663_ASUS_MODE6), + SND_PCI_QUIRK(0x1043, 0x1765, "ASUS NB", ALC663_ASUS_MODE6), + SND_PCI_QUIRK(0x105b, 0x0d47, "Foxconn 45CMX/45GMX/45CMX-K", + ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x17aa, 0x101e, "Lenovo", ALC662_LENOVO_101E), + SND_PCI_QUIRK(0x1019, 0x9087, "ECS", ALC662_ECS), + SND_PCI_QUIRK(0x105b, 0x0cd6, "Foxconn", ALC662_ECS), + SND_PCI_QUIRK(0x1458, 0xa002, "Gigabyte 945GCM-S2L", + ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1565, 0x820f, "Biostar TA780G M2+", ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1849, 0x3662, "ASROCK K10N78FullHD-hSLI R3.0", + ALC662_3ST_6ch_DIG), + SND_PCI_QUIRK(0x1854, 0x2000, "ASUS H13-2000", ALC663_ASUS_H13), + SND_PCI_QUIRK(0x1854, 0x2001, "ASUS H13-2001", ALC663_ASUS_H13), + SND_PCI_QUIRK(0x1854, 0x2002, "ASUS H13-2002", ALC663_ASUS_H13), + {} +}; + +static struct alc_config_preset alc662_presets[] = { + [ALC662_3ST_2ch_DIG] = { + .mixers = { alc662_3ST_2ch_mixer, alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .dig_in_nid = ALC662_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_capture_source, + }, + [ALC662_3ST_6ch_DIG] = { + .mixers = { alc662_3ST_6ch_mixer, alc662_chmode_mixer, + alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .dig_in_nid = ALC662_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes), + .channel_mode = alc662_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc662_capture_source, + }, + [ALC662_3ST_6ch] = { + .mixers = { alc662_3ST_6ch_mixer, alc662_chmode_mixer, + alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes), + .channel_mode = alc662_3ST_6ch_modes, + .need_dac_fix = 1, + .input_mux = &alc662_capture_source, + }, + [ALC662_5ST_DIG] = { + .mixers = { alc662_base_mixer, alc662_chmode_mixer, + alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .dig_in_nid = ALC662_DIGIN_NID, + .num_channel_mode = ARRAY_SIZE(alc662_5stack_modes), + .channel_mode = alc662_5stack_modes, + .input_mux = &alc662_capture_source, + }, + [ALC662_LENOVO_101E] = { + .mixers = { alc662_lenovo_101e_mixer, alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs, alc662_sue_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_lenovo_101e_capture_source, + .unsol_event = alc662_lenovo_101e_unsol_event, + .init_hook = alc662_lenovo_101e_all_automute, + }, + [ALC662_ASUS_EEEPC_P701] = { + .mixers = { alc662_eeepc_p701_mixer, alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc662_eeepc_sue_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc662_eeepc_unsol_event, + .init_hook = alc662_eeepc_inithook, + }, + [ALC662_ASUS_EEEPC_EP20] = { + .mixers = { alc662_eeepc_ep20_mixer, alc662_capture_mixer, + alc662_chmode_mixer }, + .init_verbs = { alc662_init_verbs, + alc662_eeepc_ep20_sue_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes), + .channel_mode = alc662_3ST_6ch_modes, + .input_mux = &alc662_lenovo_101e_capture_source, + .unsol_event = alc662_eeepc_ep20_unsol_event, + .init_hook = alc662_eeepc_ep20_inithook, + }, + [ALC662_ECS] = { + .mixers = { alc662_ecs_mixer, alc662_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc662_ecs_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc662_eeepc_unsol_event, + .init_hook = alc662_eeepc_inithook, + }, + [ALC663_ASUS_M51VA] = { + .mixers = { alc663_m51va_mixer, alc662_capture_mixer}, + .init_verbs = { alc662_init_verbs, alc663_m51va_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc663_m51va_capture_source, + .unsol_event = alc663_m51va_unsol_event, + .init_hook = alc663_m51va_inithook, + }, + [ALC663_ASUS_G71V] = { + .mixers = { alc663_g71v_mixer, alc662_capture_mixer}, + .init_verbs = { alc662_init_verbs, alc663_g71v_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_g71v_unsol_event, + .init_hook = alc663_g71v_inithook, + }, + [ALC663_ASUS_H13] = { + .mixers = { alc663_m51va_mixer, alc662_capture_mixer}, + .init_verbs = { alc662_init_verbs, alc663_m51va_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc663_m51va_capture_source, + .unsol_event = alc663_m51va_unsol_event, + .init_hook = alc663_m51va_inithook, + }, + [ALC663_ASUS_G50V] = { + .mixers = { alc663_g50v_mixer, alc662_capture_mixer}, + .init_verbs = { alc662_init_verbs, alc663_g50v_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_6ch_modes), + .channel_mode = alc662_3ST_6ch_modes, + .input_mux = &alc663_capture_source, + .unsol_event = alc663_g50v_unsol_event, + .init_hook = alc663_g50v_inithook, + }, + [ALC663_ASUS_MODE1] = { + .mixers = { alc663_m51va_mixer, alc662_auto_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc663_21jd_amic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .hp_nid = 0x03, + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_mode1_unsol_event, + .init_hook = alc663_mode1_inithook, + }, + [ALC662_ASUS_MODE2] = { + .mixers = { alc662_1bjd_mixer, alc662_auto_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc662_1bjd_amic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc662_mode2_unsol_event, + .init_hook = alc662_mode2_inithook, + }, + [ALC663_ASUS_MODE3] = { + .mixers = { alc663_two_hp_m1_mixer, alc662_auto_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc663_two_hp_amic_m1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .hp_nid = 0x03, + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_mode3_unsol_event, + .init_hook = alc663_mode3_inithook, + }, + [ALC663_ASUS_MODE4] = { + .mixers = { alc663_asus_21jd_clfe_mixer, + alc662_auto_capture_mixer}, + .init_verbs = { alc662_init_verbs, + alc663_21jd_amic_init_verbs}, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .hp_nid = 0x03, + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_mode4_unsol_event, + .init_hook = alc663_mode4_inithook, + }, + [ALC663_ASUS_MODE5] = { + .mixers = { alc663_asus_15jd_clfe_mixer, + alc662_auto_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc663_15jd_amic_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .hp_nid = 0x03, + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_mode5_unsol_event, + .init_hook = alc663_mode5_inithook, + }, + [ALC663_ASUS_MODE6] = { + .mixers = { alc663_two_hp_m2_mixer, alc662_auto_capture_mixer }, + .init_verbs = { alc662_init_verbs, + alc663_two_hp_amic_m2_init_verbs }, + .num_dacs = ARRAY_SIZE(alc662_dac_nids), + .hp_nid = 0x03, + .dac_nids = alc662_dac_nids, + .dig_out_nid = ALC662_DIGOUT_NID, + .num_channel_mode = ARRAY_SIZE(alc662_3ST_2ch_modes), + .channel_mode = alc662_3ST_2ch_modes, + .input_mux = &alc662_eeepc_capture_source, + .unsol_event = alc663_mode6_unsol_event, + .init_hook = alc663_mode6_inithook, + }, +}; + + +/* + * BIOS auto configuration + */ + +/* add playback controls from the parsed DAC table */ +static int alc662_auto_create_multi_out_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { + "Front", "Surround", NULL /*CLFE*/, "Side" + }; + hda_nid_t nid; + int i, err; + + for (i = 0; i < cfg->line_outs; i++) { + if (!spec->multiout.dac_nids[i]) + continue; + nid = alc880_idx_to_dac(i); + if (i == 2) { + /* Center/LFE */ + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(0x0e, 1, 0, + HDA_INPUT)); + if (err < 0) + return err; + err = add_control(spec, ALC_CTL_WIDGET_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(0x0e, 2, 0, + HDA_INPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(alc880_idx_to_mixer(i), + 3, 0, HDA_INPUT)); + if (err < 0) + return err; + } + } + return 0; +} + +/* add playback controls for speaker and HP outputs */ +static int alc662_auto_create_extra_out(struct alc_spec *spec, hda_nid_t pin, + const char *pfx) +{ + hda_nid_t nid; + int err; + char name[32]; + + if (!pin) + return 0; + + if (pin == 0x17) { + /* ALC663 has a mono output pin on 0x17 */ + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(pin, 2, 0, HDA_OUTPUT)); + return err; + } + + if (alc880_is_fixed_pin(pin)) { + nid = alc880_idx_to_dac(alc880_fixed_pin_idx(pin)); + /* printk("DAC nid=%x\n",nid); */ + /* specify the DAC as the extra output */ + if (!spec->multiout.hp_nid) + spec->multiout.hp_nid = nid; + else + spec->multiout.extra_out_nid[0] = nid; + /* control HP volume/switch on the output mixer amp */ + nid = alc880_idx_to_dac(alc880_fixed_pin_idx(pin)); + sprintf(name, "%s Playback Volume", pfx); + err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 2, HDA_INPUT)); + if (err < 0) + return err; + } else if (alc880_is_multi_pin(pin)) { + /* set manual connection */ + /* we have only a switch on HP-out PIN */ + sprintf(name, "%s Playback Switch", pfx); + err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + return 0; +} + +/* create playback/capture controls for input pins */ +static int alc662_auto_create_analog_input_ctls(struct alc_spec *spec, + const struct auto_pin_cfg *cfg) +{ + struct hda_input_mux *imux = &spec->private_imux; + int i, err, idx; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (alc880_is_input_pin(cfg->input_pins[i])) { + idx = alc880_input_pin_idx(cfg->input_pins[i]); + err = new_analog_input(spec, cfg->input_pins[i], + auto_pin_cfg_labels[i], + idx, 0x0b); + if (err < 0) + return err; + imux->items[imux->num_items].label = + auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = + alc880_input_pin_idx(cfg->input_pins[i]); + imux->num_items++; + } + } + return 0; +} + +static void alc662_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + alc_set_pin_output(codec, nid, pin_type); + /* need the manual connection? */ + if (alc880_is_multi_pin(nid)) { + struct alc_spec *spec = codec->spec; + int idx = alc880_multi_pin_idx(nid); + snd_hda_codec_write(codec, alc880_idx_to_selector(idx), 0, + AC_VERB_SET_CONNECT_SEL, + alc880_dac_to_idx(spec->multiout.dac_nids[dac_idx])); + } +} + +static void alc662_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + alc_subsystem_id(codec, 0x15, 0x1b, 0x14); + for (i = 0; i <= HDA_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + int pin_type = get_pin_type(spec->autocfg.line_out_type); + if (nid) + alc662_auto_set_output_and_unmute(codec, nid, pin_type, + i); + } +} + +static void alc662_auto_init_hp_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + /* use dac 0 */ + alc662_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); + pin = spec->autocfg.speaker_pins[0]; + if (pin) + alc662_auto_set_output_and_unmute(codec, pin, PIN_OUT, 0); +} + +#define alc662_is_input_pin(nid) alc880_is_input_pin(nid) +#define ALC662_PIN_CD_NID ALC880_PIN_CD_NID + +static void alc662_auto_init_analog_input(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + if (alc662_is_input_pin(nid)) { + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + (i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF80 : PIN_IN)); + if (nid != ALC662_PIN_CD_NID) + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_MUTE); + } + } +} + +#define alc662_auto_init_input_src alc882_auto_init_input_src + +static int alc662_parse_auto_config(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int err; + static hda_nid_t alc662_ignore[] = { 0x1d, 0 }; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc662_ignore); + if (err < 0) + return err; + if (!spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + err = alc880_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc662_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = alc662_auto_create_extra_out(spec, + spec->autocfg.speaker_pins[0], + "Speaker"); + if (err < 0) + return err; + err = alc662_auto_create_extra_out(spec, spec->autocfg.hp_pins[0], + "Headphone"); + if (err < 0) + return err; + err = alc662_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = ALC880_DIGOUT_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->num_mux_defs = 1; + spec->input_mux = &spec->private_imux; + + spec->init_verbs[spec->num_init_verbs++] = alc662_auto_init_verbs; + if (codec->vendor_id == 0x10ec0663) + spec->init_verbs[spec->num_init_verbs++] = + alc663_auto_init_verbs; + + err = alc_auto_add_mic_boost(codec); + if (err < 0) + return err; + + spec->mixers[spec->num_mixers] = alc662_capture_mixer; + spec->num_mixers++; + + store_pin_configs(codec); + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc662_auto_init(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + alc662_auto_init_multi_out(codec); + alc662_auto_init_hp_out(codec); + alc662_auto_init_analog_input(codec); + alc662_auto_init_input_src(codec); + if (spec->unsol_event) + alc_inithook(codec); +} + +static int patch_alc662(struct hda_codec *codec) +{ + struct alc_spec *spec; + int err, board_config; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return -ENOMEM; + + codec->spec = spec; + + alc_fix_pll_init(codec, 0x20, 0x04, 15); + + board_config = snd_hda_check_board_config(codec, ALC662_MODEL_LAST, + alc662_models, + alc662_cfg_tbl); + if (board_config < 0) { + printk(KERN_INFO "hda_codec: Unknown model for ALC662, " + "trying auto-probe from BIOS...\n"); + board_config = ALC662_AUTO; + } + + if (board_config == ALC662_AUTO) { + /* automatic parse from the BIOS config */ + err = alc662_parse_auto_config(codec); + if (err < 0) { + alc_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO + "hda_codec: Cannot set up configuration " + "from BIOS. Using base mode...\n"); + board_config = ALC662_3ST_2ch_DIG; + } + } + + if (board_config != ALC662_AUTO) + setup_preset(spec, &alc662_presets[board_config]); + + if (codec->vendor_id == 0x10ec0663) { + spec->stream_name_analog = "ALC663 Analog"; + spec->stream_name_digital = "ALC663 Digital"; + } else if (codec->vendor_id == 0x10ec0272) { + spec->stream_name_analog = "ALC272 Analog"; + spec->stream_name_digital = "ALC272 Digital"; + } else { + spec->stream_name_analog = "ALC662 Analog"; + spec->stream_name_digital = "ALC662 Digital"; + } + + spec->stream_analog_playback = &alc662_pcm_analog_playback; + spec->stream_analog_capture = &alc662_pcm_analog_capture; + + spec->stream_digital_playback = &alc662_pcm_digital_playback; + spec->stream_digital_capture = &alc662_pcm_digital_capture; + + spec->adc_nids = alc662_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(alc662_adc_nids); + spec->capsrc_nids = alc662_capsrc_nids; + + spec->vmaster_nid = 0x02; + + codec->patch_ops = alc_patch_ops; + if (board_config == ALC662_AUTO) + spec->init_hook = alc662_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + if (!spec->loopback.amplist) + spec->loopback.amplist = alc662_loopbacks; +#endif + + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_realtek[] = { + { .id = 0x10ec0260, .name = "ALC260", .patch = patch_alc260 }, + { .id = 0x10ec0262, .name = "ALC262", .patch = patch_alc262 }, + { .id = 0x10ec0267, .name = "ALC267", .patch = patch_alc268 }, + { .id = 0x10ec0268, .name = "ALC268", .patch = patch_alc268 }, + { .id = 0x10ec0269, .name = "ALC269", .patch = patch_alc269 }, + { .id = 0x10ec0272, .name = "ALC272", .patch = patch_alc662 }, + { .id = 0x10ec0861, .rev = 0x100340, .name = "ALC660", + .patch = patch_alc861 }, + { .id = 0x10ec0660, .name = "ALC660-VD", .patch = patch_alc861vd }, + { .id = 0x10ec0861, .name = "ALC861", .patch = patch_alc861 }, + { .id = 0x10ec0862, .name = "ALC861-VD", .patch = patch_alc861vd }, + { .id = 0x10ec0662, .rev = 0x100002, .name = "ALC662 rev2", + .patch = patch_alc883 }, + { .id = 0x10ec0662, .rev = 0x100101, .name = "ALC662 rev1", + .patch = patch_alc662 }, + { .id = 0x10ec0663, .name = "ALC663", .patch = patch_alc662 }, + { .id = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 }, + { .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 }, + { .id = 0x10ec0883, .name = "ALC883", .patch = patch_alc883 }, + { .id = 0x10ec0885, .rev = 0x100101, .name = "ALC889A", + .patch = patch_alc882 }, /* should be patch_alc883() in future */ + { .id = 0x10ec0885, .rev = 0x100103, .name = "ALC889A", + .patch = patch_alc882 }, /* should be patch_alc883() in future */ + { .id = 0x10ec0885, .name = "ALC885", .patch = patch_alc882 }, + { .id = 0x10ec0887, .name = "ALC887", .patch = patch_alc883 }, + { .id = 0x10ec0888, .name = "ALC888", .patch = patch_alc883 }, + { .id = 0x10ec0888, .rev = 0x100101, .name = "ALC1200", + .patch = patch_alc883 }, + { .id = 0x10ec0889, .name = "ALC889", .patch = patch_alc883 }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c new file mode 100644 index 0000000..9332b63 --- /dev/null +++ b/sound/pci/hda/patch_si3054.c @@ -0,0 +1,303 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for Silicon Labs 3054/5 modem codec + * + * Copyright (c) 2005 Sasha Khapyorsky <sashak@alsa-project.org> + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_patch.h" + +/* si3054 verbs */ +#define SI3054_VERB_READ_NODE 0x900 +#define SI3054_VERB_WRITE_NODE 0x100 + +/* si3054 nodes (registers) */ +#define SI3054_EXTENDED_MID 2 +#define SI3054_LINE_RATE 3 +#define SI3054_LINE_LEVEL 4 +#define SI3054_GPIO_CFG 5 +#define SI3054_GPIO_POLARITY 6 +#define SI3054_GPIO_STICKY 7 +#define SI3054_GPIO_WAKEUP 8 +#define SI3054_GPIO_STATUS 9 +#define SI3054_GPIO_CONTROL 10 +#define SI3054_MISC_AFE 11 +#define SI3054_CHIPID 12 +#define SI3054_LINE_CFG1 13 +#define SI3054_LINE_STATUS 14 +#define SI3054_DC_TERMINATION 15 +#define SI3054_LINE_CONFIG 16 +#define SI3054_CALLPROG_ATT 17 +#define SI3054_SQ_CONTROL 18 +#define SI3054_MISC_CONTROL 19 +#define SI3054_RING_CTRL1 20 +#define SI3054_RING_CTRL2 21 + +/* extended MID */ +#define SI3054_MEI_READY 0xf + +/* line level */ +#define SI3054_ATAG_MASK 0x00f0 +#define SI3054_DTAG_MASK 0xf000 + +/* GPIO bits */ +#define SI3054_GPIO_OH 0x0001 +#define SI3054_GPIO_CID 0x0002 + +/* chipid and revisions */ +#define SI3054_CHIPID_CODEC_REV_MASK 0x000f +#define SI3054_CHIPID_DAA_REV_MASK 0x00f0 +#define SI3054_CHIPID_INTERNATIONAL 0x0100 +#define SI3054_CHIPID_DAA_ID 0x0f00 +#define SI3054_CHIPID_CODEC_ID (1<<12) + +/* si3054 codec registers (nodes) access macros */ +#define GET_REG(codec,reg) (snd_hda_codec_read(codec,reg,0,SI3054_VERB_READ_NODE,0)) +#define SET_REG(codec,reg,val) (snd_hda_codec_write(codec,reg,0,SI3054_VERB_WRITE_NODE,val)) +#define SET_REG_CACHE(codec,reg,val) \ + snd_hda_codec_write_cache(codec,reg,0,SI3054_VERB_WRITE_NODE,val) + + +struct si3054_spec { + unsigned international; + struct hda_pcm pcm; +}; + + +/* + * Modem mixer + */ + +#define PRIVATE_VALUE(reg,mask) ((reg<<16)|(mask&0xffff)) +#define PRIVATE_REG(val) ((val>>16)&0xffff) +#define PRIVATE_MASK(val) (val&0xffff) + +#define si3054_switch_info snd_ctl_boolean_mono_info + +static int si3054_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg = PRIVATE_REG(kcontrol->private_value); + u16 mask = PRIVATE_MASK(kcontrol->private_value); + uvalue->value.integer.value[0] = (GET_REG(codec, reg)) & mask ? 1 : 0 ; + return 0; +} + +static int si3054_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *uvalue) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg = PRIVATE_REG(kcontrol->private_value); + u16 mask = PRIVATE_MASK(kcontrol->private_value); + if (uvalue->value.integer.value[0]) + SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) | mask); + else + SET_REG_CACHE(codec, reg, (GET_REG(codec, reg)) & ~mask); + return 0; +} + +#define SI3054_KCONTROL(kname,reg,mask) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = kname, \ + .info = si3054_switch_info, \ + .get = si3054_switch_get, \ + .put = si3054_switch_put, \ + .private_value = PRIVATE_VALUE(reg,mask), \ +} + + +static struct snd_kcontrol_new si3054_modem_mixer[] = { + SI3054_KCONTROL("Off-hook Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_OH), + SI3054_KCONTROL("Caller ID Switch", SI3054_GPIO_CONTROL, SI3054_GPIO_CID), + {} +}; + +static int si3054_build_controls(struct hda_codec *codec) +{ + return snd_hda_add_new_ctls(codec, si3054_modem_mixer); +} + + +/* + * PCM callbacks + */ + +static int si3054_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + u16 val; + + SET_REG(codec, SI3054_LINE_RATE, substream->runtime->rate); + val = GET_REG(codec, SI3054_LINE_LEVEL); + val &= 0xff << (8 * (substream->stream != SNDRV_PCM_STREAM_PLAYBACK)); + val |= ((stream_tag & 0xf) << 4) << (8 * (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)); + SET_REG(codec, SI3054_LINE_LEVEL, val); + + snd_hda_codec_setup_stream(codec, hinfo->nid, + stream_tag, 0, format); + return 0; +} + +static int si3054_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + static unsigned int rates[] = { 8000, 9600, 16000 }; + static struct snd_pcm_hw_constraint_list hw_constraints_rates = { + .count = ARRAY_SIZE(rates), + .list = rates, + .mask = 0, + }; + substream->runtime->hw.period_bytes_min = 80; + return snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &hw_constraints_rates); +} + + +static struct hda_pcm_stream si3054_pcm = { + .substreams = 1, + .channels_min = 1, + .channels_max = 1, + .nid = 0x1, + .rates = SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|SNDRV_PCM_RATE_KNOT, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .maxbps = 16, + .ops = { + .open = si3054_pcm_open, + .prepare = si3054_pcm_prepare, + }, +}; + + +static int si3054_build_pcms(struct hda_codec *codec) +{ + struct si3054_spec *spec = codec->spec; + struct hda_pcm *info = &spec->pcm; + si3054_pcm.nid = codec->mfg; + codec->num_pcms = 1; + codec->pcm_info = info; + info->name = "Si3054 Modem"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = si3054_pcm; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = si3054_pcm; + info->pcm_type = HDA_PCM_TYPE_MODEM; + return 0; +} + + +/* + * Init part + */ + +static int si3054_init(struct hda_codec *codec) +{ + struct si3054_spec *spec = codec->spec; + unsigned wait_count; + u16 val; + + snd_hda_codec_write(codec, AC_NODE_ROOT, 0, AC_VERB_SET_CODEC_RESET, 0); + snd_hda_codec_write(codec, codec->mfg, 0, AC_VERB_SET_STREAM_FORMAT, 0); + SET_REG(codec, SI3054_LINE_RATE, 9600); + SET_REG(codec, SI3054_LINE_LEVEL, SI3054_DTAG_MASK|SI3054_ATAG_MASK); + SET_REG(codec, SI3054_EXTENDED_MID, 0); + + wait_count = 10; + do { + msleep(2); + val = GET_REG(codec, SI3054_EXTENDED_MID); + } while ((val & SI3054_MEI_READY) != SI3054_MEI_READY && wait_count--); + + if((val&SI3054_MEI_READY) != SI3054_MEI_READY) { + snd_printk(KERN_ERR "si3054: cannot initialize. EXT MID = %04x\n", val); + /* let's pray that this is no fatal error */ + /* return -EACCES; */ + } + + SET_REG(codec, SI3054_GPIO_POLARITY, 0xffff); + SET_REG(codec, SI3054_GPIO_CFG, 0x0); + SET_REG(codec, SI3054_MISC_AFE, 0); + SET_REG(codec, SI3054_LINE_CFG1,0x200); + + if((GET_REG(codec,SI3054_LINE_STATUS) & (1<<6)) == 0) { + snd_printd("Link Frame Detect(FDT) is not ready (line status: %04x)\n", + GET_REG(codec,SI3054_LINE_STATUS)); + } + + spec->international = GET_REG(codec, SI3054_CHIPID) & SI3054_CHIPID_INTERNATIONAL; + + return 0; +} + +static void si3054_free(struct hda_codec *codec) +{ + kfree(codec->spec); +} + + +/* + */ + +static struct hda_codec_ops si3054_patch_ops = { + .build_controls = si3054_build_controls, + .build_pcms = si3054_build_pcms, + .init = si3054_init, + .free = si3054_free, +}; + +static int patch_si3054(struct hda_codec *codec) +{ + struct si3054_spec *spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + codec->spec = spec; + codec->patch_ops = si3054_patch_ops; + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_si3054[] = { + { .id = 0x163c3055, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x163c3155, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x11c13026, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x11c13055, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x11c13155, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x10573055, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x10573057, .name = "Si3054", .patch = patch_si3054 }, + { .id = 0x10573155, .name = "Si3054", .patch = patch_si3054 }, + /* VIA HDA on Clevo m540 */ + { .id = 0x11063288, .name = "Si3054", .patch = patch_si3054 }, + /* Asus A8J Modem (SM56) */ + { .id = 0x15433155, .name = "Si3054", .patch = patch_si3054 }, + /* LG LW20 modem */ + { .id = 0x18540018, .name = "Si3054", .patch = patch_si3054 }, + {} +}; + diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c new file mode 100644 index 0000000..96f4c8d --- /dev/null +++ b/sound/pci/hda/patch_sigmatel.c @@ -0,0 +1,5355 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for SigmaTel STAC92xx + * + * Copyright (c) 2005 Embedded Alley Solutions, Inc. + * Matt Porter <mporter@embeddedalley.com> + * + * Based on patch_cmedia.c and patch_realtek.c + * 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 <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <sound/core.h> +#include <sound/asoundef.h> +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_patch.h" +#include "hda_beep.h" + +#define NUM_CONTROL_ALLOC 32 + +#define STAC_VREF_EVENT 0x00 +#define STAC_INSERT_EVENT 0x10 +#define STAC_PWR_EVENT 0x20 +#define STAC_HP_EVENT 0x30 + +enum { + STAC_REF, + STAC_9200_OQO, + STAC_9200_DELL_D21, + STAC_9200_DELL_D22, + STAC_9200_DELL_D23, + STAC_9200_DELL_M21, + STAC_9200_DELL_M22, + STAC_9200_DELL_M23, + STAC_9200_DELL_M24, + STAC_9200_DELL_M25, + STAC_9200_DELL_M26, + STAC_9200_DELL_M27, + STAC_9200_GATEWAY, + STAC_9200_PANASONIC, + STAC_9200_MODELS +}; + +enum { + STAC_9205_REF, + STAC_9205_DELL_M42, + STAC_9205_DELL_M43, + STAC_9205_DELL_M44, + STAC_9205_MODELS +}; + +enum { + STAC_92HD73XX_NO_JD, /* no jack-detection */ + STAC_92HD73XX_REF, + STAC_DELL_M6_AMIC, + STAC_DELL_M6_DMIC, + STAC_DELL_M6_BOTH, + STAC_DELL_EQ, + STAC_92HD73XX_MODELS +}; + +enum { + STAC_92HD83XXX_REF, + STAC_92HD83XXX_MODELS +}; + +enum { + STAC_92HD71BXX_REF, + STAC_DELL_M4_1, + STAC_DELL_M4_2, + STAC_DELL_M4_3, + STAC_HP_M4, + STAC_HP_DV5, + STAC_92HD71BXX_MODELS +}; + +enum { + STAC_925x_REF, + STAC_M2_2, + STAC_MA6, + STAC_PA6, + STAC_925x_MODELS +}; + +enum { + STAC_D945_REF, + STAC_D945GTP3, + STAC_D945GTP5, + STAC_INTEL_MAC_V1, + STAC_INTEL_MAC_V2, + STAC_INTEL_MAC_V3, + STAC_INTEL_MAC_V4, + STAC_INTEL_MAC_V5, + STAC_INTEL_MAC_AUTO, /* This model is selected if no module parameter + * is given, one of the above models will be + * chosen according to the subsystem id. */ + /* for backward compatibility */ + STAC_MACMINI, + STAC_MACBOOK, + STAC_MACBOOK_PRO_V1, + STAC_MACBOOK_PRO_V2, + STAC_IMAC_INTEL, + STAC_IMAC_INTEL_20, + STAC_ECS_202, + STAC_922X_DELL_D81, + STAC_922X_DELL_D82, + STAC_922X_DELL_M81, + STAC_922X_DELL_M82, + STAC_922X_MODELS +}; + +enum { + STAC_D965_REF_NO_JD, /* no jack-detection */ + STAC_D965_REF, + STAC_D965_3ST, + STAC_D965_5ST, + STAC_DELL_3ST, + STAC_DELL_BIOS, + STAC_927X_MODELS +}; + +struct sigmatel_spec { + struct snd_kcontrol_new *mixers[4]; + unsigned int num_mixers; + + int board_config; + unsigned int eapd_switch: 1; + unsigned int surr_switch: 1; + unsigned int line_switch: 1; + unsigned int mic_switch: 1; + unsigned int alt_switch: 1; + unsigned int hp_detect: 1; + unsigned int spdif_mute: 1; + + /* gpio lines */ + unsigned int eapd_mask; + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + unsigned int gpio_mute; + + /* stream */ + unsigned int stream_delay; + + /* analog loopback */ + unsigned char aloopback_mask; + unsigned char aloopback_shift; + + /* power management */ + unsigned int num_pwrs; + unsigned int *pwr_mapping; + hda_nid_t *pwr_nids; + hda_nid_t *dac_list; + + /* playback */ + struct hda_input_mux *mono_mux; + struct hda_input_mux *amp_mux; + unsigned int cur_mmux; + struct hda_multi_out multiout; + hda_nid_t dac_nids[5]; + + /* capture */ + hda_nid_t *adc_nids; + unsigned int num_adcs; + hda_nid_t *mux_nids; + unsigned int num_muxes; + hda_nid_t *dmic_nids; + unsigned int num_dmics; + hda_nid_t *dmux_nids; + unsigned int num_dmuxes; + hda_nid_t *smux_nids; + unsigned int num_smuxes; + const char **spdif_labels; + + hda_nid_t dig_in_nid; + hda_nid_t mono_nid; + hda_nid_t anabeep_nid; + hda_nid_t digbeep_nid; + + /* pin widgets */ + hda_nid_t *pin_nids; + unsigned int num_pins; + unsigned int *pin_configs; + unsigned int *bios_pin_configs; + + /* codec specific stuff */ + struct hda_verb *init; + struct snd_kcontrol_new *mixer; + + /* capture source */ + struct hda_input_mux *dinput_mux; + unsigned int cur_dmux[2]; + struct hda_input_mux *input_mux; + unsigned int cur_mux[3]; + struct hda_input_mux *sinput_mux; + unsigned int cur_smux[2]; + unsigned int cur_amux; + hda_nid_t *amp_nids; + unsigned int num_amps; + unsigned int powerdown_adcs; + + /* i/o switches */ + unsigned int io_switch[2]; + unsigned int clfe_swap; + unsigned int hp_switch; /* NID of HP as line-out */ + unsigned int aloopback; + + struct hda_pcm pcm_rec[2]; /* PCM information */ + + /* dynamic controls and input_mux */ + struct auto_pin_cfg autocfg; + unsigned int num_kctl_alloc, num_kctl_used; + struct snd_kcontrol_new *kctl_alloc; + struct hda_input_mux private_dimux; + struct hda_input_mux private_imux; + struct hda_input_mux private_smux; + struct hda_input_mux private_amp_mux; + struct hda_input_mux private_mono_mux; +}; + +static hda_nid_t stac9200_adc_nids[1] = { + 0x03, +}; + +static hda_nid_t stac9200_mux_nids[1] = { + 0x0c, +}; + +static hda_nid_t stac9200_dac_nids[1] = { + 0x02, +}; + +static hda_nid_t stac92hd73xx_pwr_nids[8] = { + 0x0a, 0x0b, 0x0c, 0xd, 0x0e, + 0x0f, 0x10, 0x11 +}; + +static hda_nid_t stac92hd73xx_slave_dig_outs[2] = { + 0x26, 0, +}; + +static hda_nid_t stac92hd73xx_adc_nids[2] = { + 0x1a, 0x1b +}; + +#define DELL_M6_AMP 2 +static hda_nid_t stac92hd73xx_amp_nids[3] = { + 0x0b, 0x0c, 0x0e +}; + +#define STAC92HD73XX_NUM_DMICS 2 +static hda_nid_t stac92hd73xx_dmic_nids[STAC92HD73XX_NUM_DMICS + 1] = { + 0x13, 0x14, 0 +}; + +#define STAC92HD73_DAC_COUNT 5 +static hda_nid_t stac92hd73xx_dac_nids[STAC92HD73_DAC_COUNT] = { + 0x15, 0x16, 0x17, 0x18, 0x19, +}; + +static hda_nid_t stac92hd73xx_mux_nids[4] = { + 0x28, 0x29, 0x2a, 0x2b, +}; + +static hda_nid_t stac92hd73xx_dmux_nids[2] = { + 0x20, 0x21, +}; + +static hda_nid_t stac92hd73xx_smux_nids[2] = { + 0x22, 0x23, +}; + +#define STAC92HD83XXX_NUM_DMICS 2 +static hda_nid_t stac92hd83xxx_dmic_nids[STAC92HD83XXX_NUM_DMICS + 1] = { + 0x11, 0x12, 0 +}; + +#define STAC92HD81_DAC_COUNT 2 +#define STAC92HD83_DAC_COUNT 3 +static hda_nid_t stac92hd83xxx_dac_nids[STAC92HD73_DAC_COUNT] = { + 0x13, 0x14, 0x22, +}; + +static hda_nid_t stac92hd83xxx_dmux_nids[2] = { + 0x17, 0x18, +}; + +static hda_nid_t stac92hd83xxx_adc_nids[2] = { + 0x15, 0x16, +}; + +static hda_nid_t stac92hd83xxx_pwr_nids[4] = { + 0xa, 0xb, 0xd, 0xe, +}; + +static hda_nid_t stac92hd83xxx_slave_dig_outs[2] = { + 0x1e, 0, +}; + +static unsigned int stac92hd83xxx_pwr_mapping[4] = { + 0x03, 0x0c, 0x10, 0x40, +}; + +static hda_nid_t stac92hd71bxx_pwr_nids[3] = { + 0x0a, 0x0d, 0x0f +}; + +static hda_nid_t stac92hd71bxx_adc_nids[2] = { + 0x12, 0x13, +}; + +static hda_nid_t stac92hd71bxx_mux_nids[2] = { + 0x1a, 0x1b +}; + +static hda_nid_t stac92hd71bxx_dmux_nids[2] = { + 0x1c, 0x1d, +}; + +static hda_nid_t stac92hd71bxx_smux_nids[2] = { + 0x24, 0x25, +}; + +static hda_nid_t stac92hd71bxx_dac_nids[1] = { + 0x10, /*0x11, */ +}; + +#define STAC92HD71BXX_NUM_DMICS 2 +static hda_nid_t stac92hd71bxx_dmic_nids[STAC92HD71BXX_NUM_DMICS + 1] = { + 0x18, 0x19, 0 +}; + +static hda_nid_t stac92hd71bxx_slave_dig_outs[2] = { + 0x22, 0 +}; + +static hda_nid_t stac925x_adc_nids[1] = { + 0x03, +}; + +static hda_nid_t stac925x_mux_nids[1] = { + 0x0f, +}; + +static hda_nid_t stac925x_dac_nids[1] = { + 0x02, +}; + +#define STAC925X_NUM_DMICS 1 +static hda_nid_t stac925x_dmic_nids[STAC925X_NUM_DMICS + 1] = { + 0x15, 0 +}; + +static hda_nid_t stac925x_dmux_nids[1] = { + 0x14, +}; + +static hda_nid_t stac922x_adc_nids[2] = { + 0x06, 0x07, +}; + +static hda_nid_t stac922x_mux_nids[2] = { + 0x12, 0x13, +}; + +static hda_nid_t stac927x_adc_nids[3] = { + 0x07, 0x08, 0x09 +}; + +static hda_nid_t stac927x_mux_nids[3] = { + 0x15, 0x16, 0x17 +}; + +static hda_nid_t stac927x_smux_nids[1] = { + 0x21, +}; + +static hda_nid_t stac927x_dac_nids[6] = { + 0x02, 0x03, 0x04, 0x05, 0x06, 0 +}; + +static hda_nid_t stac927x_dmux_nids[1] = { + 0x1b, +}; + +#define STAC927X_NUM_DMICS 2 +static hda_nid_t stac927x_dmic_nids[STAC927X_NUM_DMICS + 1] = { + 0x13, 0x14, 0 +}; + +static const char *stac927x_spdif_labels[5] = { + "Digital Playback", "ADAT", "Analog Mux 1", + "Analog Mux 2", "Analog Mux 3" +}; + +static hda_nid_t stac9205_adc_nids[2] = { + 0x12, 0x13 +}; + +static hda_nid_t stac9205_mux_nids[2] = { + 0x19, 0x1a +}; + +static hda_nid_t stac9205_dmux_nids[1] = { + 0x1d, +}; + +static hda_nid_t stac9205_smux_nids[1] = { + 0x21, +}; + +#define STAC9205_NUM_DMICS 2 +static hda_nid_t stac9205_dmic_nids[STAC9205_NUM_DMICS + 1] = { + 0x17, 0x18, 0 +}; + +static hda_nid_t stac9200_pin_nids[8] = { + 0x08, 0x09, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, +}; + +static hda_nid_t stac925x_pin_nids[8] = { + 0x07, 0x08, 0x0a, 0x0b, + 0x0c, 0x0d, 0x10, 0x11, +}; + +static hda_nid_t stac922x_pin_nids[10] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x15, 0x1b, +}; + +static hda_nid_t stac92hd73xx_pin_nids[13] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x22, 0x23 +}; + +static hda_nid_t stac92hd83xxx_pin_nids[14] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, + 0x1d, 0x1e, 0x1f, 0x20 +}; +static hda_nid_t stac92hd71bxx_pin_nids[11] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x14, 0x18, 0x19, 0x1e, + 0x1f, +}; + +static hda_nid_t stac927x_pin_nids[14] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, + 0x14, 0x21, 0x22, 0x23, +}; + +static hda_nid_t stac9205_pin_nids[12] = { + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x14, 0x16, 0x17, 0x18, + 0x21, 0x22, +}; + +#define stac92xx_amp_volume_info snd_hda_mixer_amp_volume_info + +static int stac92xx_amp_volume_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = spec->amp_nids[spec->cur_amux]; + + kcontrol->private_value ^= get_amp_nid(kcontrol); + kcontrol->private_value |= nid; + + return snd_hda_mixer_amp_volume_get(kcontrol, ucontrol); +} + +static int stac92xx_amp_volume_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = spec->amp_nids[spec->cur_amux]; + + kcontrol->private_value ^= get_amp_nid(kcontrol); + kcontrol->private_value |= nid; + + return snd_hda_mixer_amp_volume_put(kcontrol, ucontrol); +} + +static int stac92xx_dmux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->dinput_mux, uinfo); +} + +static int stac92xx_dmux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int dmux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.enumerated.item[0] = spec->cur_dmux[dmux_idx]; + return 0; +} + +static int stac92xx_dmux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int dmux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + return snd_hda_input_mux_put(codec, spec->dinput_mux, ucontrol, + spec->dmux_nids[dmux_idx], &spec->cur_dmux[dmux_idx]); +} + +static int stac92xx_smux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->sinput_mux, uinfo); +} + +static int stac92xx_smux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + + ucontrol->value.enumerated.item[0] = spec->cur_smux[smux_idx]; + return 0; +} + +static int stac92xx_smux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *smux = &spec->private_smux; + unsigned int smux_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + int err, val; + hda_nid_t nid; + + err = snd_hda_input_mux_put(codec, spec->sinput_mux, ucontrol, + spec->smux_nids[smux_idx], &spec->cur_smux[smux_idx]); + if (err < 0) + return err; + + if (spec->spdif_mute) { + if (smux_idx == 0) + nid = spec->multiout.dig_out_nid; + else + nid = codec->slave_dig_outs[smux_idx - 1]; + if (spec->cur_smux[smux_idx] == smux->num_items - 1) + val = AMP_OUT_MUTE; + else + val = AMP_OUT_UNMUTE; + /* un/mute SPDIF out */ + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_AMP_GAIN_MUTE, val); + } + return 0; +} + +static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->input_mux, uinfo); +} + +static int stac92xx_mux_enum_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_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 stac92xx_mux_enum_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_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->mux_nids[adc_idx], &spec->cur_mux[adc_idx]); +} + +static int stac92xx_mono_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->mono_mux, uinfo); +} + +static int stac92xx_mono_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_mmux; + return 0; +} + +static int stac92xx_mono_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + return snd_hda_input_mux_put(codec, spec->mono_mux, ucontrol, + spec->mono_nid, &spec->cur_mmux); +} + +static int stac92xx_amp_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->amp_mux, uinfo); +} + +static int stac92xx_amp_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.enumerated.item[0] = spec->cur_amux; + return 0; +} + +static int stac92xx_amp_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + struct snd_kcontrol *ctl = + snd_hda_find_mixer_ctl(codec, "Amp Capture Volume"); + if (!ctl) + return -EINVAL; + + snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE | + SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + + return snd_hda_input_mux_put(codec, spec->amp_mux, ucontrol, + 0, &spec->cur_amux); +} + +#define stac92xx_aloopback_info snd_ctl_boolean_mono_info + +static int stac92xx_aloopback_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.integer.value[0] = !!(spec->aloopback & + (spec->aloopback_mask << idx)); + return 0; +} + +static int stac92xx_aloopback_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int dac_mode; + unsigned int val, idx_val; + + idx_val = spec->aloopback_mask << idx; + if (ucontrol->value.integer.value[0]) + val = spec->aloopback | idx_val; + else + val = spec->aloopback & ~idx_val; + if (spec->aloopback == val) + return 0; + + spec->aloopback = val; + + /* Only return the bits defined by the shift value of the + * first two bytes of the mask + */ + dac_mode = snd_hda_codec_read(codec, codec->afg, 0, + kcontrol->private_value & 0xFFFF, 0x0); + dac_mode >>= spec->aloopback_shift; + + if (spec->aloopback & idx_val) { + snd_hda_power_up(codec); + dac_mode |= idx_val; + } else { + snd_hda_power_down(codec); + dac_mode &= ~idx_val; + } + + snd_hda_codec_write_cache(codec, codec->afg, 0, + kcontrol->private_value >> 16, dac_mode); + + return 1; +} + +static struct hda_verb stac9200_core_init[] = { + /* set dac0mux for dac converter */ + { 0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +static struct hda_verb stac9200_eapd_init[] = { + /* set dac0mux for dac converter */ + {0x07, AC_VERB_SET_CONNECT_SEL, 0x00}, + {0x08, AC_VERB_SET_EAPD_BTLENABLE, 0x02}, + {} +}; + +static struct hda_verb stac92hd73xx_6ch_core_init[] = { + /* set master volume and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* setup audio connections */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00}, + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x11, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* setup adcs to point to mixer */ + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x21, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* setup import muxs */ + { 0x28, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x29, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +static struct hda_verb dell_eq_core_init[] = { + /* set master volume to max value without distortion + * and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xec}, + /* setup audio connections */ + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00}, + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x02}, + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* setup adcs to point to mixer */ + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x21, AC_VERB_SET_CONNECT_SEL, 0x0b}, + /* setup import muxs */ + { 0x28, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x29, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +static struct hda_verb dell_m6_core_init[] = { + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* setup audio connections */ + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x00}, + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* setup adcs to point to mixer */ + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x21, AC_VERB_SET_CONNECT_SEL, 0x0b}, + /* setup import muxs */ + { 0x28, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x29, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2b, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +static struct hda_verb stac92hd73xx_8ch_core_init[] = { + /* set master volume and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* setup audio connections */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00}, + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x11, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* connect hp ports to dac3 */ + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x03}, + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x03}, + /* setup adcs to point to mixer */ + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x21, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* setup import muxs */ + { 0x28, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x29, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2b, AC_VERB_SET_CONNECT_SEL, 0x03}, + {} +}; + +static struct hda_verb stac92hd73xx_10ch_core_init[] = { + /* set master volume and direct control */ + { 0x1f, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* setup audio connections */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x00 }, + { 0x10, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { 0x11, AC_VERB_SET_CONNECT_SEL, 0x02 }, + /* dac3 is connected to import3 mux */ + { 0x18, AC_VERB_SET_AMP_GAIN_MUTE, 0xb07f}, + /* connect hp ports to dac4 */ + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x04}, + { 0x0d, AC_VERB_SET_CONNECT_SEL, 0x04}, + /* setup adcs to point to mixer */ + { 0x20, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x21, AC_VERB_SET_CONNECT_SEL, 0x0b}, + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x10, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + { 0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + /* setup import muxs */ + { 0x28, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x29, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2a, AC_VERB_SET_CONNECT_SEL, 0x01}, + { 0x2b, AC_VERB_SET_CONNECT_SEL, 0x03}, + {} +}; + +static struct hda_verb stac92hd83xxx_core_init[] = { + /* start of config #1 */ + { 0xe, AC_VERB_SET_CONNECT_SEL, 0x3}, + + /* start of config #2 */ + { 0xa, AC_VERB_SET_CONNECT_SEL, 0x0}, + { 0xb, AC_VERB_SET_CONNECT_SEL, 0x0}, + { 0xd, AC_VERB_SET_CONNECT_SEL, 0x1}, + + /* power state controls amps */ + { 0x01, AC_VERB_SET_EAPD, 1 << 2}, + {} +}; + +static struct hda_verb stac92hd71bxx_core_init[] = { + /* set master volume and direct control */ + { 0x28, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* connect headphone jack to dac1 */ + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* unmute right and left channels for nodes 0x0a, 0xd, 0x0f */ + { 0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + { 0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {} +}; + +#define HD_DISABLE_PORTF 2 +static struct hda_verb stac92hd71bxx_analog_core_init[] = { + /* start of config #1 */ + + /* connect port 0f to audio mixer */ + { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x2}, + /* unmute right and left channels for node 0x0f */ + { 0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + /* start of config #2 */ + + /* set master volume and direct control */ + { 0x28, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* connect headphone jack to dac1 */ + { 0x0a, AC_VERB_SET_CONNECT_SEL, 0x01}, + /* unmute right and left channels for nodes 0x0a, 0xd */ + { 0x0a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + { 0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {} +}; + +static struct hda_verb stac925x_core_init[] = { + /* set dac0mux for dac converter */ + { 0x06, AC_VERB_SET_CONNECT_SEL, 0x00}, + {} +}; + +static struct hda_verb stac922x_core_init[] = { + /* set master volume and direct control */ + { 0x16, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + {} +}; + +static struct hda_verb d965_core_init[] = { + /* set master volume and direct control */ + { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* unmute node 0x1b */ + { 0x1b, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + /* select node 0x03 as DAC */ + { 0x0b, AC_VERB_SET_CONNECT_SEL, 0x01}, + {} +}; + +static struct hda_verb stac927x_core_init[] = { + /* set master volume and direct control */ + { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* enable analog pc beep path */ + { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5}, + {} +}; + +static struct hda_verb stac9205_core_init[] = { + /* set master volume and direct control */ + { 0x24, AC_VERB_SET_VOLUME_KNOB_CONTROL, 0xff}, + /* enable analog pc beep path */ + { 0x01, AC_VERB_SET_DIGI_CONVERT_2, 1 << 5}, + {} +}; + +#define STAC_MONO_MUX \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Mono Mux", \ + .count = 1, \ + .info = stac92xx_mono_mux_enum_info, \ + .get = stac92xx_mono_mux_enum_get, \ + .put = stac92xx_mono_mux_enum_put, \ + } + +#define STAC_AMP_MUX \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Amp Selector Capture Switch", \ + .count = 1, \ + .info = stac92xx_amp_mux_enum_info, \ + .get = stac92xx_amp_mux_enum_get, \ + .put = stac92xx_amp_mux_enum_put, \ + } + +#define STAC_AMP_VOL(xname, nid, chs, idx, dir) \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = 0, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = stac92xx_amp_volume_info, \ + .get = stac92xx_amp_volume_get, \ + .put = stac92xx_amp_volume_put, \ + .tlv = { .c = snd_hda_mixer_amp_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, chs, idx, dir) \ + } + +#define STAC_INPUT_SOURCE(cnt) \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Input Source", \ + .count = cnt, \ + .info = stac92xx_mux_enum_info, \ + .get = stac92xx_mux_enum_get, \ + .put = stac92xx_mux_enum_put, \ + } + +#define STAC_ANALOG_LOOPBACK(verb_read, verb_write, cnt) \ + { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = "Analog Loopback", \ + .count = cnt, \ + .info = stac92xx_aloopback_info, \ + .get = stac92xx_aloopback_get, \ + .put = stac92xx_aloopback_put, \ + .private_value = verb_read | (verb_write << 16), \ + } + +static struct snd_kcontrol_new stac9200_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0xb, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT), + STAC_INPUT_SOURCE(1), + HDA_CODEC_VOLUME("Capture Volume", 0x0a, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x0a, 0, HDA_OUTPUT), + { } /* end */ +}; + +#define DELL_M6_MIXER 6 +static struct snd_kcontrol_new stac92hd73xx_6ch_mixer[] = { + /* start of config #1 */ + HDA_CODEC_VOLUME("Front Mic Mixer Capture Volume", 0x1d, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Mixer Capture Switch", 0x1d, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("Line In Mixer Capture Volume", 0x1d, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Line In Mixer Capture Switch", 0x1d, 0x2, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Mixer Capture Volume", 0x1d, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("CD Mixer Capture Switch", 0x1d, 0x4, HDA_INPUT), + + /* start of config #2 */ + HDA_CODEC_VOLUME("Mic Mixer Capture Volume", 0x1d, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Mixer Capture Switch", 0x1d, 0x1, HDA_INPUT), + + HDA_CODEC_VOLUME("DAC Mixer Capture Volume", 0x1d, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("DAC Mixer Capture Switch", 0x1d, 0x3, HDA_INPUT), + + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 3), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x20, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x20, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x21, 0x0, HDA_OUTPUT), + + { } /* end */ +}; + +static struct snd_kcontrol_new stac92hd73xx_8ch_mixer[] = { + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 4), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x20, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x20, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Front Mic Mixer Capture Volume", 0x1d, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Mixer Capture Switch", 0x1d, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("Mic Mixer Capture Volume", 0x1d, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Mixer Capture Switch", 0x1d, 0x1, HDA_INPUT), + + HDA_CODEC_VOLUME("Line In Mixer Capture Volume", 0x1d, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Line In Mixer Capture Switch", 0x1d, 0x2, HDA_INPUT), + + HDA_CODEC_VOLUME("DAC Mixer Capture Volume", 0x1d, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("DAC Mixer Capture Switch", 0x1d, 0x3, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Mixer Capture Volume", 0x1d, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("CD Mixer Capture Switch", 0x1d, 0x4, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new stac92hd73xx_10ch_mixer[] = { + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A1, 5), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x20, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x20, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x21, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x21, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("Front Mic Mixer Capture Volume", 0x1d, 0, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Mixer Capture Switch", 0x1d, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("Mic Mixer Capture Volume", 0x1d, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("Mic Mixer Capture Switch", 0x1d, 0x1, HDA_INPUT), + + HDA_CODEC_VOLUME("Line In Mixer Capture Volume", 0x1d, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Line In Mixer Capture Switch", 0x1d, 0x2, HDA_INPUT), + + HDA_CODEC_VOLUME("DAC Mixer Capture Volume", 0x1d, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("DAC Mixer Capture Switch", 0x1d, 0x3, HDA_INPUT), + + HDA_CODEC_VOLUME("CD Mixer Capture Volume", 0x1d, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("CD Mixer Capture Switch", 0x1d, 0x4, HDA_INPUT), + { } /* end */ +}; + + +static struct snd_kcontrol_new stac92hd83xxx_mixer[] = { + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x17, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x17, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x18, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x18, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME("DAC0 Capture Volume", 0x1b, 0, HDA_INPUT), + HDA_CODEC_MUTE("DAC0 Capture Switch", 0x1b, 0, HDA_INPUT), + + HDA_CODEC_VOLUME("DAC1 Capture Volume", 0x1b, 0x1, HDA_INPUT), + HDA_CODEC_MUTE("DAC1 Capture Switch", 0x1b, 0x1, HDA_INPUT), + + HDA_CODEC_VOLUME("Front Mic Capture Volume", 0x1b, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Capture Switch", 0x1b, 0x2, HDA_INPUT), + + HDA_CODEC_VOLUME("Line In Capture Volume", 0x1b, 0x3, HDA_INPUT), + HDA_CODEC_MUTE("Line In Capture Switch", 0x1b, 0x3, HDA_INPUT), + + /* + HDA_CODEC_VOLUME("Mic Capture Volume", 0x1b, 0x4, HDA_INPUT), + HDA_CODEC_MUTE("Mic Capture Switch", 0x1b 0x4, HDA_INPUT), + */ + { } /* end */ +}; + +static struct snd_kcontrol_new stac92hd71bxx_analog_mixer[] = { + STAC_INPUT_SOURCE(2), + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A0, 2), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x1c, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x1d, 0x0, HDA_OUTPUT), + /* analog pc-beep replaced with digital beep support */ + /* + HDA_CODEC_VOLUME("PC Beep Volume", 0x17, 0x2, HDA_INPUT), + HDA_CODEC_MUTE("PC Beep Switch", 0x17, 0x2, HDA_INPUT), + */ + + HDA_CODEC_MUTE("Import0 Mux Capture Switch", 0x17, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Import0 Mux Capture Volume", 0x17, 0x0, HDA_INPUT), + + HDA_CODEC_MUTE("Import1 Mux Capture Switch", 0x17, 0x1, HDA_INPUT), + HDA_CODEC_VOLUME("Import1 Mux Capture Volume", 0x17, 0x1, HDA_INPUT), + + HDA_CODEC_MUTE("DAC0 Capture Switch", 0x17, 0x3, HDA_INPUT), + HDA_CODEC_VOLUME("DAC0 Capture Volume", 0x17, 0x3, HDA_INPUT), + + HDA_CODEC_MUTE("DAC1 Capture Switch", 0x17, 0x4, HDA_INPUT), + HDA_CODEC_VOLUME("DAC1 Capture Volume", 0x17, 0x4, HDA_INPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new stac92hd71bxx_mixer[] = { + STAC_INPUT_SOURCE(2), + STAC_ANALOG_LOOPBACK(0xFA0, 0x7A0, 2), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x1c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x1c, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x1d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x1d, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new stac925x_mixer[] = { + STAC_INPUT_SOURCE(1), + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x14, 0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new stac9205_mixer[] = { + STAC_INPUT_SOURCE(2), + STAC_ANALOG_LOOPBACK(0xFE0, 0x7E0, 1), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x1b, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x1d, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x1c, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x1e, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +/* This needs to be generated dynamically based on sequence */ +static struct snd_kcontrol_new stac922x_mixer[] = { + STAC_INPUT_SOURCE(2), + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x17, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x17, 0x0, HDA_INPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x18, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x18, 0x0, HDA_INPUT), + { } /* end */ +}; + + +static struct snd_kcontrol_new stac927x_mixer[] = { + STAC_INPUT_SOURCE(3), + STAC_ANALOG_LOOPBACK(0xFEB, 0x7EB, 1), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x0, 0x18, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x0, 0x1b, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x1, 0x19, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x1, 0x1c, 0x0, HDA_OUTPUT), + + HDA_CODEC_VOLUME_IDX("Capture Volume", 0x2, 0x1A, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 0x2, 0x1d, 0x0, HDA_OUTPUT), + { } /* end */ +}; + +static struct snd_kcontrol_new stac_dmux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Input Source", + /* count set later */ + .info = stac92xx_dmux_enum_info, + .get = stac92xx_dmux_enum_get, + .put = stac92xx_dmux_enum_put, +}; + +static struct snd_kcontrol_new stac_smux_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "IEC958 Playback Source", + /* count set later */ + .info = stac92xx_smux_enum_info, + .get = stac92xx_smux_enum_get, + .put = stac92xx_smux_enum_put, +}; + +static const char *slave_vols[] = { + "Front Playback Volume", + "Surround Playback Volume", + "Center Playback Volume", + "LFE Playback Volume", + "Side Playback Volume", + "Headphone Playback Volume", + "Headphone Playback Volume", + "Speaker Playback Volume", + "External Speaker Playback Volume", + "Speaker2 Playback Volume", + NULL +}; + +static const char *slave_sws[] = { + "Front Playback Switch", + "Surround Playback Switch", + "Center Playback Switch", + "LFE Playback Switch", + "Side Playback Switch", + "Headphone Playback Switch", + "Headphone Playback Switch", + "Speaker Playback Switch", + "External Speaker Playback Switch", + "Speaker2 Playback Switch", + "IEC958 Playback Switch", + NULL +}; + +static int stac92xx_build_controls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int err; + int i; + + err = snd_hda_add_new_ctls(codec, spec->mixer); + if (err < 0) + return err; + + 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->num_dmuxes > 0) { + stac_dmux_mixer.count = spec->num_dmuxes; + err = snd_ctl_add(codec->bus->card, + snd_ctl_new1(&stac_dmux_mixer, codec)); + if (err < 0) + return err; + } + if (spec->num_smuxes > 0) { + int wcaps = get_wcaps(codec, spec->multiout.dig_out_nid); + struct hda_input_mux *smux = &spec->private_smux; + /* check for mute support on SPDIF out */ + if (wcaps & AC_WCAP_OUT_AMP) { + smux->items[smux->num_items].label = "Off"; + smux->items[smux->num_items].index = 0; + smux->num_items++; + spec->spdif_mute = 1; + } + stac_smux_mixer.count = spec->num_smuxes; + err = snd_ctl_add(codec->bus->card, + snd_ctl_new1(&stac_smux_mixer, codec)); + 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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + } + if (spec->dig_in_nid && !(spec->gpio_dir & 0x01)) { + err = snd_hda_create_spdif_in_ctls(codec, spec->dig_in_nid); + if (err < 0) + return err; + } + + /* if we have no master control, let's create it */ + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Volume")) { + unsigned int vmaster_tlv[4]; + snd_hda_set_vmaster_tlv(codec, spec->multiout.dac_nids[0], + HDA_OUTPUT, vmaster_tlv); + err = snd_hda_add_vmaster(codec, "Master Playback Volume", + vmaster_tlv, slave_vols); + if (err < 0) + return err; + } + if (!snd_hda_find_mixer_ctl(codec, "Master Playback Switch")) { + err = snd_hda_add_vmaster(codec, "Master Playback Switch", + NULL, slave_sws); + if (err < 0) + return err; + } + + return 0; +} + +static unsigned int ref9200_pin_configs[8] = { + 0x01c47010, 0x01447010, 0x0221401f, 0x01114010, + 0x02a19020, 0x01a19021, 0x90100140, 0x01813122, +}; + +/* + STAC 9200 pin configs for + 102801A8 + 102801DE + 102801E8 +*/ +static unsigned int dell9200_d21_pin_configs[8] = { + 0x400001f0, 0x400001f1, 0x02214030, 0x01014010, + 0x02a19020, 0x01a19021, 0x90100140, 0x01813122, +}; + +/* + STAC 9200 pin configs for + 102801C0 + 102801C1 +*/ +static unsigned int dell9200_d22_pin_configs[8] = { + 0x400001f0, 0x400001f1, 0x0221401f, 0x01014010, + 0x01813020, 0x02a19021, 0x90100140, 0x400001f2, +}; + +/* + STAC 9200 pin configs for + 102801C4 (Dell Dimension E310) + 102801C5 + 102801C7 + 102801D9 + 102801DA + 102801E3 +*/ +static unsigned int dell9200_d23_pin_configs[8] = { + 0x400001f0, 0x400001f1, 0x0221401f, 0x01014010, + 0x01813020, 0x01a19021, 0x90100140, 0x400001f2, +}; + + +/* + STAC 9200-32 pin configs for + 102801B5 (Dell Inspiron 630m) + 102801D8 (Dell Inspiron 640m) +*/ +static unsigned int dell9200_m21_pin_configs[8] = { + 0x40c003fa, 0x03441340, 0x0321121f, 0x90170310, + 0x408003fb, 0x03a11020, 0x401003fc, 0x403003fd, +}; + +/* + STAC 9200-32 pin configs for + 102801C2 (Dell Latitude D620) + 102801C8 + 102801CC (Dell Latitude D820) + 102801D4 + 102801D6 +*/ +static unsigned int dell9200_m22_pin_configs[8] = { + 0x40c003fa, 0x0144131f, 0x0321121f, 0x90170310, + 0x90a70321, 0x03a11020, 0x401003fb, 0x40f000fc, +}; + +/* + STAC 9200-32 pin configs for + 102801CE (Dell XPS M1710) + 102801CF (Dell Precision M90) +*/ +static unsigned int dell9200_m23_pin_configs[8] = { + 0x40c003fa, 0x01441340, 0x0421421f, 0x90170310, + 0x408003fb, 0x04a1102e, 0x90170311, 0x403003fc, +}; + +/* + STAC 9200-32 pin configs for + 102801C9 + 102801CA + 102801CB (Dell Latitude 120L) + 102801D3 +*/ +static unsigned int dell9200_m24_pin_configs[8] = { + 0x40c003fa, 0x404003fb, 0x0321121f, 0x90170310, + 0x408003fc, 0x03a11020, 0x401003fd, 0x403003fe, +}; + +/* + STAC 9200-32 pin configs for + 102801BD (Dell Inspiron E1505n) + 102801EE + 102801EF +*/ +static unsigned int dell9200_m25_pin_configs[8] = { + 0x40c003fa, 0x01441340, 0x0421121f, 0x90170310, + 0x408003fb, 0x04a11020, 0x401003fc, 0x403003fd, +}; + +/* + STAC 9200-32 pin configs for + 102801F5 (Dell Inspiron 1501) + 102801F6 +*/ +static unsigned int dell9200_m26_pin_configs[8] = { + 0x40c003fa, 0x404003fb, 0x0421121f, 0x90170310, + 0x408003fc, 0x04a11020, 0x401003fd, 0x403003fe, +}; + +/* + STAC 9200-32 + 102801CD (Dell Inspiron E1705/9400) +*/ +static unsigned int dell9200_m27_pin_configs[8] = { + 0x40c003fa, 0x01441340, 0x0421121f, 0x90170310, + 0x90170310, 0x04a11020, 0x90170310, 0x40f003fc, +}; + +static unsigned int oqo9200_pin_configs[8] = { + 0x40c000f0, 0x404000f1, 0x0221121f, 0x02211210, + 0x90170111, 0x90a70120, 0x400000f2, 0x400000f3, +}; + + +static unsigned int *stac9200_brd_tbl[STAC_9200_MODELS] = { + [STAC_REF] = ref9200_pin_configs, + [STAC_9200_OQO] = oqo9200_pin_configs, + [STAC_9200_DELL_D21] = dell9200_d21_pin_configs, + [STAC_9200_DELL_D22] = dell9200_d22_pin_configs, + [STAC_9200_DELL_D23] = dell9200_d23_pin_configs, + [STAC_9200_DELL_M21] = dell9200_m21_pin_configs, + [STAC_9200_DELL_M22] = dell9200_m22_pin_configs, + [STAC_9200_DELL_M23] = dell9200_m23_pin_configs, + [STAC_9200_DELL_M24] = dell9200_m24_pin_configs, + [STAC_9200_DELL_M25] = dell9200_m25_pin_configs, + [STAC_9200_DELL_M26] = dell9200_m26_pin_configs, + [STAC_9200_DELL_M27] = dell9200_m27_pin_configs, + [STAC_9200_PANASONIC] = ref9200_pin_configs, +}; + +static const char *stac9200_models[STAC_9200_MODELS] = { + [STAC_REF] = "ref", + [STAC_9200_OQO] = "oqo", + [STAC_9200_DELL_D21] = "dell-d21", + [STAC_9200_DELL_D22] = "dell-d22", + [STAC_9200_DELL_D23] = "dell-d23", + [STAC_9200_DELL_M21] = "dell-m21", + [STAC_9200_DELL_M22] = "dell-m22", + [STAC_9200_DELL_M23] = "dell-m23", + [STAC_9200_DELL_M24] = "dell-m24", + [STAC_9200_DELL_M25] = "dell-m25", + [STAC_9200_DELL_M26] = "dell-m26", + [STAC_9200_DELL_M27] = "dell-m27", + [STAC_9200_GATEWAY] = "gateway", + [STAC_9200_PANASONIC] = "panasonic", +}; + +static struct snd_pci_quirk stac9200_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_REF), + /* Dell laptops have BIOS problem */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a8, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5, + "Dell Inspiron 630m", STAC_9200_DELL_M21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bd, + "Dell Inspiron E1505n", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c0, + "unknown Dell", STAC_9200_DELL_D22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c1, + "unknown Dell", STAC_9200_DELL_D22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2, + "Dell Latitude D620", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c5, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c7, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c8, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c9, + "unknown Dell", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ca, + "unknown Dell", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb, + "Dell Latitude 120L", STAC_9200_DELL_M24), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc, + "Dell Latitude D820", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cd, + "Dell Inspiron E1705/9400", STAC_9200_DELL_M27), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ce, + "Dell XPS M1710", STAC_9200_DELL_M23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cf, + "Dell Precision M90", STAC_9200_DELL_M23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d3, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d4, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d6, + "unknown Dell", STAC_9200_DELL_M22), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d8, + "Dell Inspiron 640m", STAC_9200_DELL_M21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d9, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01da, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01de, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e3, + "unknown Dell", STAC_9200_DELL_D23), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01e8, + "unknown Dell", STAC_9200_DELL_D21), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ee, + "unknown Dell", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ef, + "unknown Dell", STAC_9200_DELL_M25), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f5, + "Dell Inspiron 1501", STAC_9200_DELL_M26), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f6, + "unknown Dell", STAC_9200_DELL_M26), + /* Panasonic */ + SND_PCI_QUIRK(0x10f7, 0x8338, "Panasonic CF-74", STAC_9200_PANASONIC), + /* Gateway machines needs EAPD to be set on resume */ + SND_PCI_QUIRK(0x107b, 0x0205, "Gateway S-7110M", STAC_9200_GATEWAY), + SND_PCI_QUIRK(0x107b, 0x0317, "Gateway MT3423, MX341*", + STAC_9200_GATEWAY), + SND_PCI_QUIRK(0x107b, 0x0318, "Gateway ML3019, MT3707", + STAC_9200_GATEWAY), + /* OQO Mobile */ + SND_PCI_QUIRK(0x1106, 0x3288, "OQO Model 2", STAC_9200_OQO), + {} /* terminator */ +}; + +static unsigned int ref925x_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x90a70320, 0x02214210, 0x01019020, 0x9033032e, +}; + +static unsigned int stac925x_MA6_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x90a70320, 0x90100211, 0x400003f1, 0x9033032e, +}; + +static unsigned int stac925x_PA6_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x50a103f0, 0x90100211, 0x400003f1, 0x9033032e, +}; + +static unsigned int stac925xM2_2_pin_configs[8] = { + 0x40c003f3, 0x424503f2, 0x04180011, 0x02a19020, + 0x50a103f0, 0x90100212, 0x400003f1, 0x9033032e, +}; + +static unsigned int *stac925x_brd_tbl[STAC_925x_MODELS] = { + [STAC_REF] = ref925x_pin_configs, + [STAC_M2_2] = stac925xM2_2_pin_configs, + [STAC_MA6] = stac925x_MA6_pin_configs, + [STAC_PA6] = stac925x_PA6_pin_configs, +}; + +static const char *stac925x_models[STAC_925x_MODELS] = { + [STAC_REF] = "ref", + [STAC_M2_2] = "m2-2", + [STAC_MA6] = "m6", + [STAC_PA6] = "pa6", +}; + +static struct snd_pci_quirk stac925x_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, "DFI LanParty", STAC_REF), + SND_PCI_QUIRK(0x8384, 0x7632, "Stac9202 Reference Board", STAC_REF), + SND_PCI_QUIRK(0x107b, 0x0316, "Gateway M255", STAC_REF), + SND_PCI_QUIRK(0x107b, 0x0366, "Gateway MP6954", STAC_REF), + SND_PCI_QUIRK(0x107b, 0x0461, "Gateway NX560XL", STAC_MA6), + SND_PCI_QUIRK(0x107b, 0x0681, "Gateway NX860", STAC_PA6), + SND_PCI_QUIRK(0x1002, 0x437b, "Gateway MX6453", STAC_M2_2), + {} /* terminator */ +}; + +static unsigned int ref92hd73xx_pin_configs[13] = { + 0x02214030, 0x02a19040, 0x01a19020, 0x02214030, + 0x0181302e, 0x01014010, 0x01014020, 0x01014030, + 0x02319040, 0x90a000f0, 0x90a000f0, 0x01452050, + 0x01452050, +}; + +static unsigned int dell_m6_pin_configs[13] = { + 0x0321101f, 0x4f00000f, 0x4f0000f0, 0x90170110, + 0x03a11020, 0x0321101f, 0x4f0000f0, 0x4f0000f0, + 0x4f0000f0, 0x90a60160, 0x4f0000f0, 0x4f0000f0, + 0x4f0000f0, +}; + +static unsigned int *stac92hd73xx_brd_tbl[STAC_92HD73XX_MODELS] = { + [STAC_92HD73XX_REF] = ref92hd73xx_pin_configs, + [STAC_DELL_M6_AMIC] = dell_m6_pin_configs, + [STAC_DELL_M6_DMIC] = dell_m6_pin_configs, + [STAC_DELL_M6_BOTH] = dell_m6_pin_configs, + [STAC_DELL_EQ] = dell_m6_pin_configs, +}; + +static const char *stac92hd73xx_models[STAC_92HD73XX_MODELS] = { + [STAC_92HD73XX_NO_JD] = "no-jd", + [STAC_92HD73XX_REF] = "ref", + [STAC_DELL_M6_AMIC] = "dell-m6-amic", + [STAC_DELL_M6_DMIC] = "dell-m6-dmic", + [STAC_DELL_M6_BOTH] = "dell-m6", + [STAC_DELL_EQ] = "dell-eq", +}; + +static struct snd_pci_quirk stac92hd73xx_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD73XX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0254, + "Dell Studio 1535", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0255, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0256, + "unknown Dell", STAC_DELL_M6_BOTH), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0257, + "unknown Dell", STAC_DELL_M6_BOTH), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025e, + "unknown Dell", STAC_DELL_M6_AMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x025f, + "unknown Dell", STAC_DELL_M6_AMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0271, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0272, + "unknown Dell", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x029f, + "Dell Studio 1537", STAC_DELL_M6_DMIC), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02a0, + "Dell Studio 17", STAC_DELL_M6_DMIC), + {} /* terminator */ +}; + +static unsigned int ref92hd83xxx_pin_configs[14] = { + 0x02214030, 0x02211010, 0x02a19020, 0x02170130, + 0x01014050, 0x01819040, 0x01014020, 0x90a3014e, + 0x40f000f0, 0x40f000f0, 0x40f000f0, 0x40f000f0, + 0x01451160, 0x98560170, +}; + +static unsigned int *stac92hd83xxx_brd_tbl[STAC_92HD83XXX_MODELS] = { + [STAC_92HD83XXX_REF] = ref92hd83xxx_pin_configs, +}; + +static const char *stac92hd83xxx_models[STAC_92HD83XXX_MODELS] = { + [STAC_92HD83XXX_REF] = "ref", +}; + +static struct snd_pci_quirk stac92hd83xxx_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD71BXX_REF), + {} /* terminator */ +}; + +static unsigned int ref92hd71bxx_pin_configs[11] = { + 0x02214030, 0x02a19040, 0x01a19020, 0x01014010, + 0x0181302e, 0x01014010, 0x01019020, 0x90a000f0, + 0x90a000f0, 0x01452050, 0x01452050, +}; + +static unsigned int dell_m4_1_pin_configs[11] = { + 0x0421101f, 0x04a11221, 0x40f000f0, 0x90170110, + 0x23a1902e, 0x23014250, 0x40f000f0, 0x90a000f0, + 0x40f000f0, 0x4f0000f0, 0x4f0000f0, +}; + +static unsigned int dell_m4_2_pin_configs[11] = { + 0x0421101f, 0x04a11221, 0x90a70330, 0x90170110, + 0x23a1902e, 0x23014250, 0x40f000f0, 0x40f000f0, + 0x40f000f0, 0x044413b0, 0x044413b0, +}; + +static unsigned int dell_m4_3_pin_configs[11] = { + 0x0421101f, 0x04a11221, 0x90a70330, 0x90170110, + 0x40f000f0, 0x40f000f0, 0x40f000f0, 0x90a000f0, + 0x40f000f0, 0x044413b0, 0x044413b0, +}; + +static unsigned int *stac92hd71bxx_brd_tbl[STAC_92HD71BXX_MODELS] = { + [STAC_92HD71BXX_REF] = ref92hd71bxx_pin_configs, + [STAC_DELL_M4_1] = dell_m4_1_pin_configs, + [STAC_DELL_M4_2] = dell_m4_2_pin_configs, + [STAC_DELL_M4_3] = dell_m4_3_pin_configs, + [STAC_HP_M4] = NULL, + [STAC_HP_DV5] = NULL, +}; + +static const char *stac92hd71bxx_models[STAC_92HD71BXX_MODELS] = { + [STAC_92HD71BXX_REF] = "ref", + [STAC_DELL_M4_1] = "dell-m4-1", + [STAC_DELL_M4_2] = "dell-m4-2", + [STAC_DELL_M4_3] = "dell-m4-3", + [STAC_HP_M4] = "hp-m4", + [STAC_HP_DV5] = "hp-dv5", +}; + +static struct snd_pci_quirk stac92hd71bxx_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_92HD71BXX_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30f2, + "HP dv5", STAC_HP_M4), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30f4, + "HP dv7", STAC_HP_M4), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x30fc, + "HP dv7", STAC_HP_M4), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x3603, + "HP dv5", STAC_HP_DV5), + SND_PCI_QUIRK(PCI_VENDOR_ID_HP, 0x361a, + "unknown HP", STAC_HP_M4), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0233, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0234, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0250, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024f, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x024d, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0251, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0277, + "unknown Dell", STAC_DELL_M4_1), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0263, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0265, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0262, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0264, + "unknown Dell", STAC_DELL_M4_2), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02aa, + "unknown Dell", STAC_DELL_M4_3), + {} /* terminator */ +}; + +static unsigned int ref922x_pin_configs[10] = { + 0x01014010, 0x01016011, 0x01012012, 0x0221401f, + 0x01813122, 0x01011014, 0x01441030, 0x01c41030, + 0x40000100, 0x40000100, +}; + +/* + STAC 922X pin configs for + 102801A7 + 102801AB + 102801A9 + 102801D1 + 102801D2 +*/ +static unsigned int dell_922x_d81_pin_configs[10] = { + 0x02214030, 0x01a19021, 0x01111012, 0x01114010, + 0x02a19020, 0x01117011, 0x400001f0, 0x400001f1, + 0x01813122, 0x400001f2, +}; + +/* + STAC 922X pin configs for + 102801AC + 102801D0 +*/ +static unsigned int dell_922x_d82_pin_configs[10] = { + 0x02214030, 0x01a19021, 0x01111012, 0x01114010, + 0x02a19020, 0x01117011, 0x01451140, 0x400001f0, + 0x01813122, 0x400001f1, +}; + +/* + STAC 922X pin configs for + 102801BF +*/ +static unsigned int dell_922x_m81_pin_configs[10] = { + 0x0321101f, 0x01112024, 0x01111222, 0x91174220, + 0x03a11050, 0x01116221, 0x90a70330, 0x01452340, + 0x40C003f1, 0x405003f0, +}; + +/* + STAC 9221 A1 pin configs for + 102801D7 (Dell XPS M1210) +*/ +static unsigned int dell_922x_m82_pin_configs[10] = { + 0x02211211, 0x408103ff, 0x02a1123e, 0x90100310, + 0x408003f1, 0x0221121f, 0x03451340, 0x40c003f2, + 0x508003f3, 0x405003f4, +}; + +static unsigned int d945gtp3_pin_configs[10] = { + 0x0221401f, 0x01a19022, 0x01813021, 0x01014010, + 0x40000100, 0x40000100, 0x40000100, 0x40000100, + 0x02a19120, 0x40000100, +}; + +static unsigned int d945gtp5_pin_configs[10] = { + 0x0221401f, 0x01011012, 0x01813024, 0x01014010, + 0x01a19021, 0x01016011, 0x01452130, 0x40000100, + 0x02a19320, 0x40000100, +}; + +static unsigned int intel_mac_v1_pin_configs[10] = { + 0x0121e21f, 0x400000ff, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e030, 0x11c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v2_pin_configs[10] = { + 0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e230, 0x500000fa, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v3_pin_configs[10] = { + 0x0121e21f, 0x90a7012e, 0x9017e110, 0x400000fd, + 0x400000fe, 0x0181e020, 0x1145e230, 0x11c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v4_pin_configs[10] = { + 0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f, + 0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int intel_mac_v5_pin_configs[10] = { + 0x0321e21f, 0x03a1e02e, 0x9017e110, 0x9017e11f, + 0x400000fe, 0x0381e020, 0x1345e230, 0x13c5e240, + 0x400000fc, 0x400000fb, +}; + +static unsigned int ecs202_pin_configs[10] = { + 0x0221401f, 0x02a19020, 0x01a19020, 0x01114010, + 0x408000f0, 0x01813022, 0x074510a0, 0x40c400f1, + 0x9037012e, 0x40e000f2, +}; + +static unsigned int *stac922x_brd_tbl[STAC_922X_MODELS] = { + [STAC_D945_REF] = ref922x_pin_configs, + [STAC_D945GTP3] = d945gtp3_pin_configs, + [STAC_D945GTP5] = d945gtp5_pin_configs, + [STAC_INTEL_MAC_V1] = intel_mac_v1_pin_configs, + [STAC_INTEL_MAC_V2] = intel_mac_v2_pin_configs, + [STAC_INTEL_MAC_V3] = intel_mac_v3_pin_configs, + [STAC_INTEL_MAC_V4] = intel_mac_v4_pin_configs, + [STAC_INTEL_MAC_V5] = intel_mac_v5_pin_configs, + [STAC_INTEL_MAC_AUTO] = intel_mac_v3_pin_configs, + /* for backward compatibility */ + [STAC_MACMINI] = intel_mac_v3_pin_configs, + [STAC_MACBOOK] = intel_mac_v5_pin_configs, + [STAC_MACBOOK_PRO_V1] = intel_mac_v3_pin_configs, + [STAC_MACBOOK_PRO_V2] = intel_mac_v3_pin_configs, + [STAC_IMAC_INTEL] = intel_mac_v2_pin_configs, + [STAC_IMAC_INTEL_20] = intel_mac_v3_pin_configs, + [STAC_ECS_202] = ecs202_pin_configs, + [STAC_922X_DELL_D81] = dell_922x_d81_pin_configs, + [STAC_922X_DELL_D82] = dell_922x_d82_pin_configs, + [STAC_922X_DELL_M81] = dell_922x_m81_pin_configs, + [STAC_922X_DELL_M82] = dell_922x_m82_pin_configs, +}; + +static const char *stac922x_models[STAC_922X_MODELS] = { + [STAC_D945_REF] = "ref", + [STAC_D945GTP5] = "5stack", + [STAC_D945GTP3] = "3stack", + [STAC_INTEL_MAC_V1] = "intel-mac-v1", + [STAC_INTEL_MAC_V2] = "intel-mac-v2", + [STAC_INTEL_MAC_V3] = "intel-mac-v3", + [STAC_INTEL_MAC_V4] = "intel-mac-v4", + [STAC_INTEL_MAC_V5] = "intel-mac-v5", + [STAC_INTEL_MAC_AUTO] = "intel-mac-auto", + /* for backward compatibility */ + [STAC_MACMINI] = "macmini", + [STAC_MACBOOK] = "macbook", + [STAC_MACBOOK_PRO_V1] = "macbook-pro-v1", + [STAC_MACBOOK_PRO_V2] = "macbook-pro", + [STAC_IMAC_INTEL] = "imac-intel", + [STAC_IMAC_INTEL_20] = "imac-intel-20", + [STAC_ECS_202] = "ecs202", + [STAC_922X_DELL_D81] = "dell-d81", + [STAC_922X_DELL_D82] = "dell-d82", + [STAC_922X_DELL_M81] = "dell-m81", + [STAC_922X_DELL_M82] = "dell-m82", +}; + +static struct snd_pci_quirk stac922x_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_D945_REF), + /* Intel 945G based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0101, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0202, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0606, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0601, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0111, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1115, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1116, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1117, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1118, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x1119, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x8826, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5049, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5055, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x5048, + "Intel D945G", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0110, + "Intel D945G", STAC_D945GTP3), + /* Intel D945G 5-stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0404, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0303, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0013, + "Intel D945G", STAC_D945GTP5), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0417, + "Intel D945G", STAC_D945GTP5), + /* Intel 945P based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0b0b, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0112, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0d0d, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0909, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0505, + "Intel D945P", STAC_D945GTP3), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x0707, + "Intel D945P", STAC_D945GTP5), + /* other systems */ + /* Apple Intel Mac (Mac Mini, MacBook, MacBook Pro...) */ + SND_PCI_QUIRK(0x8384, 0x7680, + "Mac", STAC_INTEL_MAC_AUTO), + /* Dell systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a7, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01a9, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ab, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ac, + "unknown Dell", STAC_922X_DELL_D82), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01bf, + "unknown Dell", STAC_922X_DELL_M81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d0, + "unknown Dell", STAC_922X_DELL_D82), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d1, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d2, + "unknown Dell", STAC_922X_DELL_D81), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01d7, + "Dell XPS M1210", STAC_922X_DELL_M82), + /* ECS/PC Chips boards */ + SND_PCI_QUIRK(0x1019, 0x2144, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2608, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2633, + "ECS/PC chips P17G/1333", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2811, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2812, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2813, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2814, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2815, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2816, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2817, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2818, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2819, + "ECS/PC chips", STAC_ECS_202), + SND_PCI_QUIRK(0x1019, 0x2820, + "ECS/PC chips", STAC_ECS_202), + {} /* terminator */ +}; + +static unsigned int ref927x_pin_configs[14] = { + 0x02214020, 0x02a19080, 0x0181304e, 0x01014010, + 0x01a19040, 0x01011012, 0x01016011, 0x0101201f, + 0x183301f0, 0x18a001f0, 0x18a001f0, 0x01442070, + 0x01c42190, 0x40000100, +}; + +static unsigned int d965_3st_pin_configs[14] = { + 0x0221401f, 0x02a19120, 0x40000100, 0x01014011, + 0x01a19021, 0x01813024, 0x40000100, 0x40000100, + 0x40000100, 0x40000100, 0x40000100, 0x40000100, + 0x40000100, 0x40000100 +}; + +static unsigned int d965_5st_pin_configs[14] = { + 0x02214020, 0x02a19080, 0x0181304e, 0x01014010, + 0x01a19040, 0x01011012, 0x01016011, 0x40000100, + 0x40000100, 0x40000100, 0x40000100, 0x01442070, + 0x40000100, 0x40000100 +}; + +static unsigned int dell_3st_pin_configs[14] = { + 0x02211230, 0x02a11220, 0x01a19040, 0x01114210, + 0x01111212, 0x01116211, 0x01813050, 0x01112214, + 0x403003fa, 0x90a60040, 0x90a60040, 0x404003fb, + 0x40c003fc, 0x40000100 +}; + +static unsigned int *stac927x_brd_tbl[STAC_927X_MODELS] = { + [STAC_D965_REF_NO_JD] = ref927x_pin_configs, + [STAC_D965_REF] = ref927x_pin_configs, + [STAC_D965_3ST] = d965_3st_pin_configs, + [STAC_D965_5ST] = d965_5st_pin_configs, + [STAC_DELL_3ST] = dell_3st_pin_configs, + [STAC_DELL_BIOS] = NULL, +}; + +static const char *stac927x_models[STAC_927X_MODELS] = { + [STAC_D965_REF_NO_JD] = "ref-no-jd", + [STAC_D965_REF] = "ref", + [STAC_D965_3ST] = "3stack", + [STAC_D965_5ST] = "5stack", + [STAC_DELL_3ST] = "dell-3stack", + [STAC_DELL_BIOS] = "dell-bios", +}; + +static struct snd_pci_quirk stac927x_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_D965_REF), + /* Intel 946 based systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x3d01, "Intel D946", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0xa301, "Intel D946", STAC_D965_3ST), + /* 965 based 3 stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2116, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2115, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2114, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2113, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2112, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2111, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2110, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2009, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2008, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2007, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2006, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2005, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2004, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2003, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2002, "Intel D965", STAC_D965_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2001, "Intel D965", STAC_D965_3ST), + /* Dell 3 stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f7, "Dell XPS M1730", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01dd, "Dell Dimension E520", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ed, "Dell ", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f4, "Dell ", STAC_DELL_3ST), + /* Dell 3 stack systems with verb table in BIOS */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f3, "Dell Inspiron 1420", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0227, "Dell Vostro 1400 ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022e, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x022f, "Dell Inspiron 1525", STAC_DELL_3ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0242, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0243, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x02ff, "Dell ", STAC_DELL_BIOS), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0209, "Dell XPS 1330", STAC_DELL_BIOS), + /* 965 based 5 stack systems */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2301, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2302, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2303, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2304, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2305, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2501, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2502, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2503, "Intel D965", STAC_D965_5ST), + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2504, "Intel D965", STAC_D965_5ST), + {} /* terminator */ +}; + +static unsigned int ref9205_pin_configs[12] = { + 0x40000100, 0x40000100, 0x01016011, 0x01014010, + 0x01813122, 0x01a19021, 0x01019020, 0x40000100, + 0x90a000f0, 0x90a000f0, 0x01441030, 0x01c41030 +}; + +/* + STAC 9205 pin configs for + 102801F1 + 102801F2 + 102801FC + 102801FD + 10280204 + 1028021F + 10280228 (Dell Vostro 1500) +*/ +static unsigned int dell_9205_m42_pin_configs[12] = { + 0x0321101F, 0x03A11020, 0x400003FA, 0x90170310, + 0x400003FB, 0x400003FC, 0x400003FD, 0x40F000F9, + 0x90A60330, 0x400003FF, 0x0144131F, 0x40C003FE, +}; + +/* + STAC 9205 pin configs for + 102801F9 + 102801FA + 102801FE + 102801FF (Dell Precision M4300) + 10280206 + 10280200 + 10280201 +*/ +static unsigned int dell_9205_m43_pin_configs[12] = { + 0x0321101f, 0x03a11020, 0x90a70330, 0x90170310, + 0x400000fe, 0x400000ff, 0x400000fd, 0x40f000f9, + 0x400000fa, 0x400000fc, 0x0144131f, 0x40c003f8, +}; + +static unsigned int dell_9205_m44_pin_configs[12] = { + 0x0421101f, 0x04a11020, 0x400003fa, 0x90170310, + 0x400003fb, 0x400003fc, 0x400003fd, 0x400003f9, + 0x90a60330, 0x400003ff, 0x01441340, 0x40c003fe, +}; + +static unsigned int *stac9205_brd_tbl[STAC_9205_MODELS] = { + [STAC_9205_REF] = ref9205_pin_configs, + [STAC_9205_DELL_M42] = dell_9205_m42_pin_configs, + [STAC_9205_DELL_M43] = dell_9205_m43_pin_configs, + [STAC_9205_DELL_M44] = dell_9205_m44_pin_configs, +}; + +static const char *stac9205_models[STAC_9205_MODELS] = { + [STAC_9205_REF] = "ref", + [STAC_9205_DELL_M42] = "dell-m42", + [STAC_9205_DELL_M43] = "dell-m43", + [STAC_9205_DELL_M44] = "dell-m44", +}; + +static struct snd_pci_quirk stac9205_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_9205_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f1, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f2, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f8, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01f9, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fa, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fc, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fd, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01fe, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01ff, + "Dell Precision M4300", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0204, + "unknown Dell", STAC_9205_DELL_M42), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0206, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021b, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021c, + "Dell Precision", STAC_9205_DELL_M43), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x021f, + "Dell Inspiron", STAC_9205_DELL_M44), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x0228, + "Dell Vostro 1500", STAC_9205_DELL_M42), + {} /* terminator */ +}; + +static int stac92xx_save_bios_config_regs(struct hda_codec *codec) +{ + int i; + struct sigmatel_spec *spec = codec->spec; + + if (! spec->bios_pin_configs) { + spec->bios_pin_configs = kcalloc(spec->num_pins, + sizeof(*spec->bios_pin_configs), GFP_KERNEL); + if (! spec->bios_pin_configs) + return -ENOMEM; + } + + for (i = 0; i < spec->num_pins; i++) { + hda_nid_t nid = spec->pin_nids[i]; + unsigned int pin_cfg; + + pin_cfg = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0x00); + snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x bios pin config %8.8x\n", + nid, pin_cfg); + spec->bios_pin_configs[i] = pin_cfg; + } + + return 0; +} + +static void stac92xx_set_config_reg(struct hda_codec *codec, + hda_nid_t pin_nid, unsigned int pin_config) +{ + int i; + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_0, + pin_config & 0x000000ff); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_1, + (pin_config & 0x0000ff00) >> 8); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_2, + (pin_config & 0x00ff0000) >> 16); + snd_hda_codec_write(codec, pin_nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + pin_config >> 24); + i = snd_hda_codec_read(codec, pin_nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, + 0x00); + snd_printdd(KERN_INFO "hda_codec: pin nid %2.2x pin config %8.8x\n", + pin_nid, i); +} + +static void stac92xx_set_config_regs(struct hda_codec *codec) +{ + int i; + struct sigmatel_spec *spec = codec->spec; + + if (!spec->pin_configs) + return; + + for (i = 0; i < spec->num_pins; i++) + stac92xx_set_config_reg(codec, spec->pin_nids[i], + spec->pin_configs[i]); +} + +/* + * Analog playback callbacks + */ +static int stac92xx_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + if (spec->stream_delay) + msleep(spec->stream_delay); + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int stac92xx_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag, format, substream); +} + +static int stac92xx_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + +/* + * Digital playback callbacks + */ +static int stac92xx_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int stac92xx_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +static int stac92xx_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + return snd_hda_multi_out_dig_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + + +/* + * Analog capture callbacks + */ +static int stac92xx_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = spec->adc_nids[substream->number]; + + if (spec->powerdown_adcs) { + msleep(40); + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D0); + } + snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); + return 0; +} + +static int stac92xx_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = spec->adc_nids[substream->number]; + + snd_hda_codec_cleanup_stream(codec, nid); + if (spec->powerdown_adcs) + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + return 0; +} + +static struct hda_pcm_stream stac92xx_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in stac92xx_build_pcms */ + .ops = { + .open = stac92xx_dig_playback_pcm_open, + .close = stac92xx_dig_playback_pcm_close, + .prepare = stac92xx_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream stac92xx_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in stac92xx_build_pcms */ +}; + +static struct hda_pcm_stream stac92xx_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .nid = 0x02, /* NID to query formats and rates */ + .ops = { + .open = stac92xx_playback_pcm_open, + .prepare = stac92xx_playback_pcm_prepare, + .cleanup = stac92xx_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream stac92xx_pcm_analog_alt_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + .nid = 0x06, /* NID to query formats and rates */ + .ops = { + .open = stac92xx_playback_pcm_open, + .prepare = stac92xx_playback_pcm_prepare, + .cleanup = stac92xx_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream stac92xx_pcm_analog_capture = { + .channels_min = 2, + .channels_max = 2, + /* NID + .substreams is set in stac92xx_build_pcms */ + .ops = { + .prepare = stac92xx_capture_pcm_prepare, + .cleanup = stac92xx_capture_pcm_cleanup + }, +}; + +static int stac92xx_build_pcms(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + codec->num_pcms = 1; + codec->pcm_info = info; + + info->name = "STAC92xx Analog"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_playback; + info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = + spec->multiout.dac_nids[0]; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_analog_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0]; + info->stream[SNDRV_PCM_STREAM_CAPTURE].substreams = spec->num_adcs; + + if (spec->alt_switch) { + codec->num_pcms++; + info++; + info->name = "STAC92xx Analog Alt"; + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_alt_playback; + } + + if (spec->multiout.dig_out_nid || spec->dig_in_nid) { + codec->num_pcms++; + info++; + info->name = "STAC92xx Digital"; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + if (spec->multiout.dig_out_nid) { + info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_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] = stac92xx_pcm_digital_capture; + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->dig_in_nid; + } + } + + return 0; +} + +static unsigned int stac92xx_get_vref(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int pincap = snd_hda_param_read(codec, nid, + AC_PAR_PIN_CAP); + pincap = (pincap & AC_PINCAP_VREF) >> AC_PINCAP_VREF_SHIFT; + if (pincap & AC_PINCAP_VREF_100) + return AC_PINCTL_VREF_100; + if (pincap & AC_PINCAP_VREF_80) + return AC_PINCTL_VREF_80; + if (pincap & AC_PINCAP_VREF_50) + return AC_PINCTL_VREF_50; + if (pincap & AC_PINCAP_VREF_GRD) + return AC_PINCTL_VREF_GRD; + return 0; +} + +static void stac92xx_auto_set_pinctl(struct hda_codec *codec, hda_nid_t nid, int pin_type) + +{ + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, pin_type); +} + +#define stac92xx_hp_switch_info snd_ctl_boolean_mono_info + +static int stac92xx_hp_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.integer.value[0] = !!spec->hp_switch; + return 0; +} + +static int stac92xx_hp_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + int nid = kcontrol->private_value; + + spec->hp_switch = ucontrol->value.integer.value[0] ? nid : 0; + + /* check to be sure that the ports are upto date with + * switch changes + */ + codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); + + return 1; +} + +#define stac92xx_io_switch_info snd_ctl_boolean_mono_info + +static int stac92xx_io_switch_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + int io_idx = kcontrol-> private_value & 0xff; + + ucontrol->value.integer.value[0] = spec->io_switch[io_idx]; + return 0; +} + +static int stac92xx_io_switch_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = kcontrol->private_value >> 8; + int io_idx = kcontrol-> private_value & 0xff; + unsigned short val = !!ucontrol->value.integer.value[0]; + + spec->io_switch[io_idx] = val; + + if (val) + stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN); + else { + unsigned int pinctl = AC_PINCTL_IN_EN; + if (io_idx) /* set VREF for mic */ + pinctl |= stac92xx_get_vref(codec, nid); + stac92xx_auto_set_pinctl(codec, nid, pinctl); + } + + /* check the auto-mute again: we need to mute/unmute the speaker + * appropriately according to the pin direction + */ + if (spec->hp_detect) + codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); + + return 1; +} + +#define stac92xx_clfe_switch_info snd_ctl_boolean_mono_info + +static int stac92xx_clfe_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + + ucontrol->value.integer.value[0] = spec->clfe_swap; + return 0; +} + +static int stac92xx_clfe_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid = kcontrol->private_value & 0xff; + unsigned int val = !!ucontrol->value.integer.value[0]; + + if (spec->clfe_swap == val) + return 0; + + spec->clfe_swap = val; + + snd_hda_codec_write_cache(codec, nid, 0, AC_VERB_SET_EAPD_BTLENABLE, + spec->clfe_swap ? 0x4 : 0x0); + + return 1; +} + +#define STAC_CODEC_HP_SWITCH(xname) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = 0, \ + .info = stac92xx_hp_switch_info, \ + .get = stac92xx_hp_switch_get, \ + .put = stac92xx_hp_switch_put, \ + } + +#define STAC_CODEC_IO_SWITCH(xname, xpval) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = 0, \ + .info = stac92xx_io_switch_info, \ + .get = stac92xx_io_switch_get, \ + .put = stac92xx_io_switch_put, \ + .private_value = xpval, \ + } + +#define STAC_CODEC_CLFE_SWITCH(xname, xpval) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = 0, \ + .info = stac92xx_clfe_switch_info, \ + .get = stac92xx_clfe_switch_get, \ + .put = stac92xx_clfe_switch_put, \ + .private_value = xpval, \ + } + +enum { + STAC_CTL_WIDGET_VOL, + STAC_CTL_WIDGET_MUTE, + STAC_CTL_WIDGET_MONO_MUX, + STAC_CTL_WIDGET_AMP_MUX, + STAC_CTL_WIDGET_AMP_VOL, + STAC_CTL_WIDGET_HP_SWITCH, + STAC_CTL_WIDGET_IO_SWITCH, + STAC_CTL_WIDGET_CLFE_SWITCH +}; + +static struct snd_kcontrol_new stac92xx_control_templates[] = { + HDA_CODEC_VOLUME(NULL, 0, 0, 0), + HDA_CODEC_MUTE(NULL, 0, 0, 0), + STAC_MONO_MUX, + STAC_AMP_MUX, + STAC_AMP_VOL(NULL, 0, 0, 0, 0), + STAC_CODEC_HP_SWITCH(NULL), + STAC_CODEC_IO_SWITCH(NULL, 0), + STAC_CODEC_CLFE_SWITCH(NULL, 0), +}; + +/* add dynamic controls */ +static int stac92xx_add_control_temp(struct sigmatel_spec *spec, + struct snd_kcontrol_new *ktemp, + int idx, const char *name, + unsigned long val) +{ + struct snd_kcontrol_new *knew; + + if (spec->num_kctl_used >= spec->num_kctl_alloc) { + int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC; + + knew = kcalloc(num + 1, sizeof(*knew), GFP_KERNEL); /* array + terminator */ + if (! knew) + return -ENOMEM; + if (spec->kctl_alloc) { + memcpy(knew, spec->kctl_alloc, sizeof(*knew) * spec->num_kctl_alloc); + kfree(spec->kctl_alloc); + } + spec->kctl_alloc = knew; + spec->num_kctl_alloc = num; + } + + knew = &spec->kctl_alloc[spec->num_kctl_used]; + *knew = *ktemp; + knew->index = idx; + knew->name = kstrdup(name, GFP_KERNEL); + if (!knew->name) + return -ENOMEM; + knew->private_value = val; + spec->num_kctl_used++; + return 0; +} + +static inline int stac92xx_add_control_idx(struct sigmatel_spec *spec, + int type, int idx, const char *name, + unsigned long val) +{ + return stac92xx_add_control_temp(spec, + &stac92xx_control_templates[type], + idx, name, val); +} + + +/* add dynamic controls */ +static inline int stac92xx_add_control(struct sigmatel_spec *spec, int type, + const char *name, unsigned long val) +{ + return stac92xx_add_control_idx(spec, type, 0, name, val); +} + +/* flag inputs as additional dynamic lineouts */ +static int stac92xx_add_dyn_out_pins(struct hda_codec *codec, struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + unsigned int wcaps, wtype; + int i, num_dacs = 0; + + /* use the wcaps cache to count all DACs available for line-outs */ + for (i = 0; i < codec->num_nodes; i++) { + wcaps = codec->wcaps[i]; + wtype = (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + + if (wtype == AC_WID_AUD_OUT && !(wcaps & AC_WCAP_DIGITAL)) + num_dacs++; + } + + snd_printdd("%s: total dac count=%d\n", __func__, num_dacs); + + switch (cfg->line_outs) { + case 3: + /* add line-in as side */ + if (cfg->input_pins[AUTO_PIN_LINE] && num_dacs > 3) { + cfg->line_out_pins[cfg->line_outs] = + cfg->input_pins[AUTO_PIN_LINE]; + spec->line_switch = 1; + cfg->line_outs++; + } + break; + case 2: + /* add line-in as clfe and mic as side */ + if (cfg->input_pins[AUTO_PIN_LINE] && num_dacs > 2) { + cfg->line_out_pins[cfg->line_outs] = + cfg->input_pins[AUTO_PIN_LINE]; + spec->line_switch = 1; + cfg->line_outs++; + } + if (cfg->input_pins[AUTO_PIN_MIC] && num_dacs > 3) { + cfg->line_out_pins[cfg->line_outs] = + cfg->input_pins[AUTO_PIN_MIC]; + spec->mic_switch = 1; + cfg->line_outs++; + } + break; + case 1: + /* add line-in as surr and mic as clfe */ + if (cfg->input_pins[AUTO_PIN_LINE] && num_dacs > 1) { + cfg->line_out_pins[cfg->line_outs] = + cfg->input_pins[AUTO_PIN_LINE]; + spec->line_switch = 1; + cfg->line_outs++; + } + if (cfg->input_pins[AUTO_PIN_MIC] && num_dacs > 2) { + cfg->line_out_pins[cfg->line_outs] = + cfg->input_pins[AUTO_PIN_MIC]; + spec->mic_switch = 1; + cfg->line_outs++; + } + break; + } + + return 0; +} + + +static int is_in_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid) +{ + int i; + + for (i = 0; i < spec->multiout.num_dacs; i++) { + if (spec->multiout.dac_nids[i] == nid) + return 1; + } + + return 0; +} + +/* + * Fill in the dac_nids table from the parsed pin configuration + * This function only works when every pin in line_out_pins[] + * contains atleast one DAC in its connection list. Some 92xx + * codecs are not connected directly to a DAC, such as the 9200 + * and 9202/925x. For those, dac_nids[] must be hard-coded. + */ +static int stac92xx_auto_fill_dac_nids(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + int i, j, conn_len = 0; + hda_nid_t nid, conn[HDA_MAX_CONNECTIONS]; + unsigned int wcaps, wtype; + + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + conn_len = snd_hda_get_connections(codec, nid, conn, + HDA_MAX_CONNECTIONS); + for (j = 0; j < conn_len; j++) { + wcaps = snd_hda_param_read(codec, conn[j], + AC_PAR_AUDIO_WIDGET_CAP); + wtype = (wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; + if (wtype != AC_WID_AUD_OUT || + (wcaps & AC_WCAP_DIGITAL)) + continue; + /* conn[j] is a DAC routed to this line-out */ + if (!is_in_dac_nids(spec, conn[j])) + break; + } + + if (j == conn_len) { + if (spec->multiout.num_dacs > 0) { + /* we have already working output pins, + * so let's drop the broken ones again + */ + cfg->line_outs = spec->multiout.num_dacs; + break; + } + /* error out, no available DAC found */ + snd_printk(KERN_ERR + "%s: No available DAC for pin 0x%x\n", + __func__, nid); + return -ENODEV; + } + + spec->multiout.dac_nids[i] = conn[j]; + spec->multiout.num_dacs++; + if (conn_len > 1) { + /* select this DAC in the pin's input mux */ + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_CONNECT_SEL, j); + + } + } + + snd_printd("dac_nids=%d (0x%x/0x%x/0x%x/0x%x/0x%x)\n", + spec->multiout.num_dacs, + spec->multiout.dac_nids[0], + spec->multiout.dac_nids[1], + spec->multiout.dac_nids[2], + spec->multiout.dac_nids[3], + spec->multiout.dac_nids[4]); + return 0; +} + +/* create volume control/switch for the given prefx type */ +static int create_controls(struct sigmatel_spec *spec, const char *pfx, hda_nid_t nid, int chs) +{ + char name[32]; + int err; + + sprintf(name, "%s Playback Volume", pfx); + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", pfx); + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, chs, 0, HDA_OUTPUT)); + if (err < 0) + return err; + return 0; +} + +static int add_spec_dacs(struct sigmatel_spec *spec, hda_nid_t nid) +{ + if (!spec->multiout.hp_nid) + spec->multiout.hp_nid = nid; + else if (spec->multiout.num_dacs > 4) { + printk(KERN_WARNING "stac92xx: No space for DAC 0x%x\n", nid); + return 1; + } else { + spec->multiout.dac_nids[spec->multiout.num_dacs] = nid; + spec->multiout.num_dacs++; + } + return 0; +} + +static int check_in_dac_nids(struct sigmatel_spec *spec, hda_nid_t nid) +{ + if (is_in_dac_nids(spec, nid)) + return 1; + if (spec->multiout.hp_nid == nid) + return 1; + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int stac92xx_auto_create_multi_out_ctls(struct hda_codec *codec, + const struct auto_pin_cfg *cfg) +{ + static const char *chname[4] = { + "Front", "Surround", NULL /*CLFE*/, "Side" + }; + hda_nid_t nid = 0; + int i, err; + + struct sigmatel_spec *spec = codec->spec; + unsigned int wid_caps, pincap; + + + for (i = 0; i < cfg->line_outs && i < spec->multiout.num_dacs; i++) { + if (!spec->multiout.dac_nids[i]) + continue; + + nid = spec->multiout.dac_nids[i]; + + if (i == 2) { + /* Center/LFE */ + err = create_controls(spec, "Center", nid, 1); + if (err < 0) + return err; + err = create_controls(spec, "LFE", nid, 2); + if (err < 0) + return err; + + wid_caps = get_wcaps(codec, nid); + + if (wid_caps & AC_WCAP_LR_SWAP) { + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_CLFE_SWITCH, + "Swap Center/LFE Playback Switch", nid); + + if (err < 0) + return err; + } + + } else { + err = create_controls(spec, chname[i], nid, 3); + if (err < 0) + return err; + } + } + + if ((spec->multiout.num_dacs - cfg->line_outs) > 0 && + cfg->hp_outs == 1 && !spec->multiout.hp_nid) + spec->multiout.hp_nid = nid; + + if (cfg->hp_outs > 1 && cfg->line_out_type == AUTO_PIN_LINE_OUT) { + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_HP_SWITCH, + "Headphone as Line Out Switch", + cfg->hp_pins[cfg->hp_outs - 1]); + if (err < 0) + return err; + } + + if (spec->line_switch) { + nid = cfg->input_pins[AUTO_PIN_LINE]; + pincap = snd_hda_param_read(codec, nid, + AC_PAR_PIN_CAP); + if (pincap & AC_PINCAP_OUT) { + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_IO_SWITCH, + "Line In as Output Switch", nid << 8); + if (err < 0) + return err; + } + } + + if (spec->mic_switch) { + unsigned int def_conf; + unsigned int mic_pin = AUTO_PIN_MIC; +again: + nid = cfg->input_pins[mic_pin]; + def_conf = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + /* some laptops have an internal analog microphone + * which can't be used as a output */ + if (get_defcfg_connect(def_conf) != AC_JACK_PORT_FIXED) { + pincap = snd_hda_param_read(codec, nid, + AC_PAR_PIN_CAP); + if (pincap & AC_PINCAP_OUT) { + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_IO_SWITCH, + "Mic as Output Switch", (nid << 8) | 1); + nid = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + if (!check_in_dac_nids(spec, nid)) + add_spec_dacs(spec, nid); + if (err < 0) + return err; + } + } else if (mic_pin == AUTO_PIN_MIC) { + mic_pin = AUTO_PIN_FRONT_MIC; + goto again; + } + } + + return 0; +} + +/* add playback controls for Speaker and HP outputs */ +static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + hda_nid_t nid; + int i, old_num_dacs, err; + + old_num_dacs = spec->multiout.num_dacs; + for (i = 0; i < cfg->hp_outs; i++) { + unsigned int wid_caps = get_wcaps(codec, cfg->hp_pins[i]); + if (wid_caps & AC_WCAP_UNSOL_CAP) + spec->hp_detect = 1; + nid = snd_hda_codec_read(codec, cfg->hp_pins[i], 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + if (check_in_dac_nids(spec, nid)) + nid = 0; + if (! nid) + continue; + add_spec_dacs(spec, nid); + } + for (i = 0; i < cfg->speaker_outs; i++) { + nid = snd_hda_codec_read(codec, cfg->speaker_pins[i], 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + if (check_in_dac_nids(spec, nid)) + nid = 0; + if (! nid) + continue; + add_spec_dacs(spec, nid); + } + for (i = 0; i < cfg->line_outs; i++) { + nid = snd_hda_codec_read(codec, cfg->line_out_pins[i], 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + if (check_in_dac_nids(spec, nid)) + nid = 0; + if (! nid) + continue; + add_spec_dacs(spec, nid); + } + for (i = old_num_dacs; i < spec->multiout.num_dacs; i++) { + static const char *pfxs[] = { + "Speaker", "External Speaker", "Speaker2", + }; + err = create_controls(spec, pfxs[i - old_num_dacs], + spec->multiout.dac_nids[i], 3); + if (err < 0) + return err; + } + if (spec->multiout.hp_nid) { + err = create_controls(spec, "Headphone", + spec->multiout.hp_nid, 3); + if (err < 0) + return err; + } + + return 0; +} + +/* labels for mono mux outputs */ +static const char *stac92xx_mono_labels[4] = { + "DAC0", "DAC1", "Mixer", "DAC2" +}; + +/* create mono mux for mono out on capable codecs */ +static int stac92xx_auto_create_mono_output_ctls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *mono_mux = &spec->private_mono_mux; + int i, num_cons; + hda_nid_t con_lst[ARRAY_SIZE(stac92xx_mono_labels)]; + + num_cons = snd_hda_get_connections(codec, + spec->mono_nid, + con_lst, + HDA_MAX_NUM_INPUTS); + if (!num_cons || num_cons > ARRAY_SIZE(stac92xx_mono_labels)) + return -EINVAL; + + for (i = 0; i < num_cons; i++) { + mono_mux->items[mono_mux->num_items].label = + stac92xx_mono_labels[i]; + mono_mux->items[mono_mux->num_items].index = i; + mono_mux->num_items++; + } + + return stac92xx_add_control(spec, STAC_CTL_WIDGET_MONO_MUX, + "Mono Mux", spec->mono_nid); +} + +/* labels for amp mux outputs */ +static const char *stac92xx_amp_labels[3] = { + "Front Microphone", "Microphone", "Line In", +}; + +/* create amp out controls mux on capable codecs */ +static int stac92xx_auto_create_amp_output_ctls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *amp_mux = &spec->private_amp_mux; + int i, err; + + for (i = 0; i < spec->num_amps; i++) { + amp_mux->items[amp_mux->num_items].label = + stac92xx_amp_labels[i]; + amp_mux->items[amp_mux->num_items].index = i; + amp_mux->num_items++; + } + + if (spec->num_amps > 1) { + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_AMP_MUX, + "Amp Selector Capture Switch", 0); + if (err < 0) + return err; + } + return stac92xx_add_control(spec, STAC_CTL_WIDGET_AMP_VOL, + "Amp Capture Volume", + HDA_COMPOSE_AMP_VAL(spec->amp_nids[0], 3, 0, HDA_INPUT)); +} + + +/* create PC beep volume controls */ +static int stac92xx_auto_create_beep_ctls(struct hda_codec *codec, + hda_nid_t nid) +{ + struct sigmatel_spec *spec = codec->spec; + u32 caps = query_amp_caps(codec, nid, HDA_OUTPUT); + int err; + + /* check for mute support for the the amp */ + if ((caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT) { + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_MUTE, + "PC Beep Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + + /* check to see if there is volume support for the amp */ + if ((caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT) { + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_VOL, + "PC Beep Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + return 0; +} + +#ifdef CONFIG_SND_HDA_INPUT_BEEP +#define stac92xx_dig_beep_switch_info snd_ctl_boolean_mono_info + +static int stac92xx_dig_beep_switch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + ucontrol->value.integer.value[0] = codec->beep->enabled; + return 0; +} + +static int stac92xx_dig_beep_switch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + int enabled = !!ucontrol->value.integer.value[0]; + if (codec->beep->enabled != enabled) { + codec->beep->enabled = enabled; + return 1; + } + return 0; +} + +static struct snd_kcontrol_new stac92xx_dig_beep_ctrl = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .info = stac92xx_dig_beep_switch_info, + .get = stac92xx_dig_beep_switch_get, + .put = stac92xx_dig_beep_switch_put, +}; + +static int stac92xx_beep_switch_ctl(struct hda_codec *codec) +{ + return stac92xx_add_control_temp(codec->spec, &stac92xx_dig_beep_ctrl, + 0, "PC Beep Playback Switch", 0); +} +#endif + +static int stac92xx_auto_create_mux_input_ctls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int wcaps, nid, i, err = 0; + + for (i = 0; i < spec->num_muxes; i++) { + nid = spec->mux_nids[i]; + wcaps = get_wcaps(codec, nid); + + if (wcaps & AC_WCAP_OUT_AMP) { + err = stac92xx_add_control_idx(spec, + STAC_CTL_WIDGET_VOL, i, "Mux Capture Volume", + HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + } + } + return 0; +}; + +static const char *stac92xx_spdif_labels[3] = { + "Digital Playback", "Analog Mux 1", "Analog Mux 2", +}; + +static int stac92xx_auto_create_spdif_mux_ctls(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *spdif_mux = &spec->private_smux; + const char **labels = spec->spdif_labels; + int i, num_cons; + hda_nid_t con_lst[HDA_MAX_NUM_INPUTS]; + + num_cons = snd_hda_get_connections(codec, + spec->smux_nids[0], + con_lst, + HDA_MAX_NUM_INPUTS); + if (!num_cons) + return -EINVAL; + + if (!labels) + labels = stac92xx_spdif_labels; + + for (i = 0; i < num_cons; i++) { + spdif_mux->items[spdif_mux->num_items].label = labels[i]; + spdif_mux->items[spdif_mux->num_items].index = i; + spdif_mux->num_items++; + } + + return 0; +} + +/* labels for dmic mux inputs */ +static const char *stac92xx_dmic_labels[5] = { + "Analog Inputs", "Digital Mic 1", "Digital Mic 2", + "Digital Mic 3", "Digital Mic 4" +}; + +/* create playback/capture controls for input pins on dmic capable codecs */ +static int stac92xx_auto_create_dmic_input_ctls(struct hda_codec *codec, + const struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *dimux = &spec->private_dimux; + hda_nid_t con_lst[HDA_MAX_NUM_INPUTS]; + int err, i, j; + char name[32]; + + dimux->items[dimux->num_items].label = stac92xx_dmic_labels[0]; + dimux->items[dimux->num_items].index = 0; + dimux->num_items++; + + for (i = 0; i < spec->num_dmics; i++) { + hda_nid_t nid; + int index; + int num_cons; + unsigned int wcaps; + unsigned int def_conf; + + def_conf = snd_hda_codec_read(codec, + spec->dmic_nids[i], + 0, + AC_VERB_GET_CONFIG_DEFAULT, + 0); + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) + continue; + + nid = spec->dmic_nids[i]; + num_cons = snd_hda_get_connections(codec, + spec->dmux_nids[0], + con_lst, + HDA_MAX_NUM_INPUTS); + for (j = 0; j < num_cons; j++) + if (con_lst[j] == nid) { + index = j; + goto found; + } + continue; +found: + wcaps = get_wcaps(codec, nid) & + (AC_WCAP_OUT_AMP | AC_WCAP_IN_AMP); + + if (wcaps) { + sprintf(name, "%s Capture Volume", + stac92xx_dmic_labels[dimux->num_items]); + + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_VOL, + name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + (wcaps & AC_WCAP_OUT_AMP) ? + HDA_OUTPUT : HDA_INPUT)); + if (err < 0) + return err; + } + + dimux->items[dimux->num_items].label = + stac92xx_dmic_labels[dimux->num_items]; + dimux->items[dimux->num_items].index = index; + dimux->num_items++; + } + + return 0; +} + +/* create playback/capture controls for input pins */ +static int stac92xx_auto_create_analog_input_ctls(struct hda_codec *codec, const struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + struct hda_input_mux *imux = &spec->private_imux; + hda_nid_t con_lst[HDA_MAX_NUM_INPUTS]; + int i, j, k; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + int index; + + if (!cfg->input_pins[i]) + continue; + index = -1; + for (j = 0; j < spec->num_muxes; j++) { + int num_cons; + num_cons = snd_hda_get_connections(codec, + spec->mux_nids[j], + con_lst, + HDA_MAX_NUM_INPUTS); + for (k = 0; k < num_cons; k++) + if (con_lst[k] == cfg->input_pins[i]) { + index = k; + goto found; + } + } + continue; + found: + imux->items[imux->num_items].label = auto_pin_cfg_labels[i]; + imux->items[imux->num_items].index = index; + imux->num_items++; + } + + if (imux->num_items) { + /* + * Set the current input for the muxes. + * The STAC9221 has two input muxes with identical source + * NID lists. Hopefully this won't get confused. + */ + for (i = 0; i < spec->num_muxes; i++) { + snd_hda_codec_write_cache(codec, spec->mux_nids[i], 0, + AC_VERB_SET_CONNECT_SEL, + imux->items[0].index); + } + } + + return 0; +} + +static void stac92xx_auto_init_multi_out(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->autocfg.line_outs; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + stac92xx_auto_set_pinctl(codec, nid, AC_PINCTL_OUT_EN); + } +} + +static void stac92xx_auto_init_hp_out(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + for (i = 0; i < spec->autocfg.hp_outs; i++) { + hda_nid_t pin; + pin = spec->autocfg.hp_pins[i]; + if (pin) /* connect to front */ + stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN); + } + for (i = 0; i < spec->autocfg.speaker_outs; i++) { + hda_nid_t pin; + pin = spec->autocfg.speaker_pins[i]; + if (pin) /* connect to front */ + stac92xx_auto_set_pinctl(codec, pin, AC_PINCTL_OUT_EN); + } +} + +static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out, hda_nid_t dig_in) +{ + struct sigmatel_spec *spec = codec->spec; + int err; + int hp_speaker_swap = 0; + + if ((err = snd_hda_parse_pin_def_config(codec, + &spec->autocfg, + spec->dmic_nids)) < 0) + return err; + if (! spec->autocfg.line_outs) + return 0; /* can't find valid pin config */ + + /* If we have no real line-out pin and multiple hp-outs, HPs should + * be set up as multi-channel outputs. + */ + if (spec->autocfg.line_out_type == AUTO_PIN_SPEAKER_OUT && + spec->autocfg.hp_outs > 1) { + /* Copy hp_outs to line_outs, backup line_outs in + * speaker_outs so that the following routines can handle + * HP pins as primary outputs. + */ + memcpy(spec->autocfg.speaker_pins, spec->autocfg.line_out_pins, + sizeof(spec->autocfg.line_out_pins)); + spec->autocfg.speaker_outs = spec->autocfg.line_outs; + memcpy(spec->autocfg.line_out_pins, spec->autocfg.hp_pins, + sizeof(spec->autocfg.hp_pins)); + spec->autocfg.line_outs = spec->autocfg.hp_outs; + hp_speaker_swap = 1; + } + if (spec->autocfg.mono_out_pin) { + int dir = get_wcaps(codec, spec->autocfg.mono_out_pin) & + (AC_WCAP_OUT_AMP | AC_WCAP_IN_AMP); + u32 caps = query_amp_caps(codec, + spec->autocfg.mono_out_pin, dir); + hda_nid_t conn_list[1]; + + /* get the mixer node and then the mono mux if it exists */ + if (snd_hda_get_connections(codec, + spec->autocfg.mono_out_pin, conn_list, 1) && + snd_hda_get_connections(codec, conn_list[0], + conn_list, 1)) { + + int wcaps = get_wcaps(codec, conn_list[0]); + int wid_type = (wcaps & AC_WCAP_TYPE) + >> AC_WCAP_TYPE_SHIFT; + /* LR swap check, some stac925x have a mux that + * changes the DACs output path instead of the + * mono-mux path. + */ + if (wid_type == AC_WID_AUD_SEL && + !(wcaps & AC_WCAP_LR_SWAP)) + spec->mono_nid = conn_list[0]; + } + if (dir) { + hda_nid_t nid = spec->autocfg.mono_out_pin; + + /* most mono outs have a least a mute/unmute switch */ + dir = (dir & AC_WCAP_OUT_AMP) ? HDA_OUTPUT : HDA_INPUT; + err = stac92xx_add_control(spec, STAC_CTL_WIDGET_MUTE, + "Mono Playback Switch", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, dir)); + if (err < 0) + return err; + /* check for volume support for the amp */ + if ((caps & AC_AMPCAP_NUM_STEPS) + >> AC_AMPCAP_NUM_STEPS_SHIFT) { + err = stac92xx_add_control(spec, + STAC_CTL_WIDGET_VOL, + "Mono Playback Volume", + HDA_COMPOSE_AMP_VAL(nid, 1, 0, dir)); + if (err < 0) + return err; + } + } + + stac92xx_auto_set_pinctl(codec, spec->autocfg.mono_out_pin, + AC_PINCTL_OUT_EN); + } + + if ((err = stac92xx_add_dyn_out_pins(codec, &spec->autocfg)) < 0) + return err; + if (spec->multiout.num_dacs == 0) + if ((err = stac92xx_auto_fill_dac_nids(codec, &spec->autocfg)) < 0) + return err; + + err = stac92xx_auto_create_multi_out_ctls(codec, &spec->autocfg); + + if (err < 0) + return err; + + /* setup analog beep controls */ + if (spec->anabeep_nid > 0) { + err = stac92xx_auto_create_beep_ctls(codec, + spec->anabeep_nid); + if (err < 0) + return err; + } + + /* setup digital beep controls and input device */ +#ifdef CONFIG_SND_HDA_INPUT_BEEP + if (spec->digbeep_nid > 0) { + hda_nid_t nid = spec->digbeep_nid; + unsigned int caps; + + err = stac92xx_auto_create_beep_ctls(codec, nid); + if (err < 0) + return err; + err = snd_hda_attach_beep_device(codec, nid); + if (err < 0) + return err; + /* if no beep switch is available, make its own one */ + caps = query_amp_caps(codec, nid, HDA_OUTPUT); + if (codec->beep && + !((caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT)) { + err = stac92xx_beep_switch_ctl(codec); + if (err < 0) + return err; + } + } +#endif + + if (hp_speaker_swap == 1) { + /* Restore the hp_outs and line_outs */ + memcpy(spec->autocfg.hp_pins, spec->autocfg.line_out_pins, + sizeof(spec->autocfg.line_out_pins)); + spec->autocfg.hp_outs = spec->autocfg.line_outs; + memcpy(spec->autocfg.line_out_pins, spec->autocfg.speaker_pins, + sizeof(spec->autocfg.speaker_pins)); + spec->autocfg.line_outs = spec->autocfg.speaker_outs; + memset(spec->autocfg.speaker_pins, 0, + sizeof(spec->autocfg.speaker_pins)); + spec->autocfg.speaker_outs = 0; + } + + err = stac92xx_auto_create_hp_ctls(codec, &spec->autocfg); + + if (err < 0) + return err; + + err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg); + + if (err < 0) + return err; + + if (spec->mono_nid > 0) { + err = stac92xx_auto_create_mono_output_ctls(codec); + if (err < 0) + return err; + } + if (spec->num_amps > 0) { + err = stac92xx_auto_create_amp_output_ctls(codec); + if (err < 0) + return err; + } + if (spec->num_dmics > 0 && !spec->dinput_mux) + if ((err = stac92xx_auto_create_dmic_input_ctls(codec, + &spec->autocfg)) < 0) + return err; + if (spec->num_muxes > 0) { + err = stac92xx_auto_create_mux_input_ctls(codec); + if (err < 0) + return err; + } + if (spec->num_smuxes > 0) { + err = stac92xx_auto_create_spdif_mux_ctls(codec); + if (err < 0) + return err; + } + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + if (spec->multiout.max_channels > 2) + spec->surr_switch = 1; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = dig_out; + if (dig_in && spec->autocfg.dig_in_pin) + spec->dig_in_nid = dig_in; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux; + spec->dinput_mux = &spec->private_dimux; + spec->sinput_mux = &spec->private_smux; + spec->mono_mux = &spec->private_mono_mux; + spec->amp_mux = &spec->private_amp_mux; + return 1; +} + +/* add playback controls for HP output */ +static int stac9200_auto_create_hp_ctls(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + hda_nid_t pin = cfg->hp_pins[0]; + unsigned int wid_caps; + + if (! pin) + return 0; + + wid_caps = get_wcaps(codec, pin); + if (wid_caps & AC_WCAP_UNSOL_CAP) + spec->hp_detect = 1; + + return 0; +} + +/* add playback controls for LFE output */ +static int stac9200_auto_create_lfe_ctls(struct hda_codec *codec, + struct auto_pin_cfg *cfg) +{ + struct sigmatel_spec *spec = codec->spec; + int err; + hda_nid_t lfe_pin = 0x0; + int i; + + /* + * search speaker outs and line outs for a mono speaker pin + * with an amp. If one is found, add LFE controls + * for it. + */ + for (i = 0; i < spec->autocfg.speaker_outs && lfe_pin == 0x0; i++) { + hda_nid_t pin = spec->autocfg.speaker_pins[i]; + unsigned int wcaps = get_wcaps(codec, pin); + wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP); + if (wcaps == AC_WCAP_OUT_AMP) + /* found a mono speaker with an amp, must be lfe */ + lfe_pin = pin; + } + + /* if speaker_outs is 0, then speakers may be in line_outs */ + if (lfe_pin == 0 && spec->autocfg.speaker_outs == 0) { + for (i = 0; i < spec->autocfg.line_outs && lfe_pin == 0x0; i++) { + hda_nid_t pin = spec->autocfg.line_out_pins[i]; + unsigned int defcfg; + defcfg = snd_hda_codec_read(codec, pin, 0, + AC_VERB_GET_CONFIG_DEFAULT, + 0x00); + if (get_defcfg_device(defcfg) == AC_JACK_SPEAKER) { + unsigned int wcaps = get_wcaps(codec, pin); + wcaps &= (AC_WCAP_STEREO | AC_WCAP_OUT_AMP); + if (wcaps == AC_WCAP_OUT_AMP) + /* found a mono speaker with an amp, + must be lfe */ + lfe_pin = pin; + } + } + } + + if (lfe_pin) { + err = create_controls(spec, "LFE", lfe_pin, 1); + if (err < 0) + return err; + } + + return 0; +} + +static int stac9200_parse_auto_config(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int err; + + if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 0) + return err; + + if ((err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0) + return err; + + if ((err = stac9200_auto_create_hp_ctls(codec, &spec->autocfg)) < 0) + return err; + + if ((err = stac9200_auto_create_lfe_ctls(codec, &spec->autocfg)) < 0) + return err; + + if (spec->num_muxes > 0) { + err = stac92xx_auto_create_mux_input_ctls(codec); + if (err < 0) + return err; + } + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = 0x05; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = 0x04; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux; + spec->dinput_mux = &spec->private_dimux; + + return 1; +} + +/* + * Early 2006 Intel Macintoshes with STAC9220X5 codecs seem to have a + * funky external mute control using GPIO pins. + */ + +static void stac_gpio_set(struct hda_codec *codec, unsigned int mask, + unsigned int dir_mask, unsigned int data) +{ + unsigned int gpiostate, gpiomask, gpiodir; + + gpiostate = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DATA, 0); + gpiostate = (gpiostate & ~dir_mask) | (data & dir_mask); + + gpiomask = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_MASK, 0); + gpiomask |= mask; + + gpiodir = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DIRECTION, 0); + gpiodir |= dir_mask; + + /* Configure GPIOx as CMOS */ + snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0); + + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_MASK, gpiomask); + snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DIRECTION, gpiodir); /* sync */ + + msleep(1); + + snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DATA, gpiostate); /* sync */ +} + +static void enable_pin_detect(struct hda_codec *codec, hda_nid_t nid, + unsigned int event) +{ + if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + (AC_USRSP_EN | event)); +} + +static int is_nid_hp_pin(struct auto_pin_cfg *cfg, hda_nid_t nid) +{ + int i; + for (i = 0; i < cfg->hp_outs; i++) + if (cfg->hp_pins[i] == nid) + return 1; /* nid is a HP-Out */ + + return 0; /* nid is not a HP-Out */ +}; + +static void stac92xx_power_down(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + /* power down inactive DACs */ + hda_nid_t *dac; + for (dac = spec->dac_list; *dac; dac++) + if (!is_in_dac_nids(spec, *dac) && + spec->multiout.hp_nid != *dac) + snd_hda_codec_write_cache(codec, *dac, 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); +} + +static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid, + int enable); + +static int stac92xx_init(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + unsigned int gpio; + int i; + + snd_hda_sequence_write(codec, spec->init); + + /* power down adcs initially */ + if (spec->powerdown_adcs) + for (i = 0; i < spec->num_adcs; i++) + snd_hda_codec_write_cache(codec, + spec->adc_nids[i], 0, + AC_VERB_SET_POWER_STATE, AC_PWRST_D3); + + /* set up GPIO */ + gpio = spec->gpio_data; + /* turn on EAPD statically when spec->eapd_switch isn't set. + * otherwise, unsol event will turn it on/off dynamically + */ + if (!spec->eapd_switch) + gpio |= spec->eapd_mask; + stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, gpio); + + /* set up pins */ + if (spec->hp_detect) { + /* Enable unsolicited responses on the HP widget */ + for (i = 0; i < cfg->hp_outs; i++) + enable_pin_detect(codec, cfg->hp_pins[i], + STAC_HP_EVENT); + /* force to enable the first line-out; the others are set up + * in unsol_event + */ + stac92xx_auto_set_pinctl(codec, spec->autocfg.line_out_pins[0], + AC_PINCTL_OUT_EN); + stac92xx_auto_init_hp_out(codec); + /* fake event to set up pins */ + codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); + } else { + stac92xx_auto_init_multi_out(codec); + stac92xx_auto_init_hp_out(codec); + } + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = cfg->input_pins[i]; + if (nid) { + unsigned int pinctl; + if (i == AUTO_PIN_MIC || i == AUTO_PIN_FRONT_MIC) { + /* for mic pins, force to initialize */ + pinctl = stac92xx_get_vref(codec, nid); + } else { + pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + /* if PINCTL already set then skip */ + if (pinctl & AC_PINCTL_IN_EN) + continue; + } + pinctl |= AC_PINCTL_IN_EN; + stac92xx_auto_set_pinctl(codec, nid, pinctl); + } + } + for (i = 0; i < spec->num_dmics; i++) + stac92xx_auto_set_pinctl(codec, spec->dmic_nids[i], + AC_PINCTL_IN_EN); + if (cfg->dig_out_pin) + stac92xx_auto_set_pinctl(codec, cfg->dig_out_pin, + AC_PINCTL_OUT_EN); + if (cfg->dig_in_pin) + stac92xx_auto_set_pinctl(codec, cfg->dig_in_pin, + AC_PINCTL_IN_EN); + for (i = 0; i < spec->num_pwrs; i++) { + hda_nid_t nid = spec->pwr_nids[i]; + int pinctl, def_conf; + int event = STAC_PWR_EVENT; + + if (is_nid_hp_pin(cfg, nid) && spec->hp_detect) + continue; /* already has an unsol event */ + + pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + /* outputs are only ports capable of power management + * any attempts on powering down a input port cause the + * referenced VREF to act quirky. + */ + if (pinctl & AC_PINCTL_IN_EN) + continue; + def_conf = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + def_conf = get_defcfg_connect(def_conf); + /* skip any ports that don't have jacks since presence + * detection is useless */ + if (def_conf != AC_JACK_PORT_COMPLEX) { + if (def_conf != AC_JACK_PORT_NONE) + stac_toggle_power_map(codec, nid, 1); + continue; + } + enable_pin_detect(codec, spec->pwr_nids[i], event | i); + codec->patch_ops.unsol_event(codec, (event | i) << 26); + } + if (spec->dac_list) + stac92xx_power_down(codec); + return 0; +} + +static void stac92xx_free(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + + if (! spec) + return; + + if (spec->kctl_alloc) { + for (i = 0; i < spec->num_kctl_used; i++) + kfree(spec->kctl_alloc[i].name); + kfree(spec->kctl_alloc); + } + + if (spec->bios_pin_configs) + kfree(spec->bios_pin_configs); + + kfree(spec); + snd_hda_detach_beep_device(codec); +} + +static void stac92xx_set_pinctl(struct hda_codec *codec, hda_nid_t nid, + unsigned int flag) +{ + unsigned int pin_ctl = snd_hda_codec_read(codec, nid, + 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00); + + if (pin_ctl & AC_PINCTL_IN_EN) { + /* + * we need to check the current set-up direction of + * shared input pins since they can be switched via + * "xxx as Output" mixer switch + */ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + if ((nid == cfg->input_pins[AUTO_PIN_LINE] && + spec->line_switch) || + (nid == cfg->input_pins[AUTO_PIN_MIC] && + spec->mic_switch)) + return; + } + + /* if setting pin direction bits, clear the current + direction bits first */ + if (flag & (AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN)) + pin_ctl &= ~(AC_PINCTL_IN_EN | AC_PINCTL_OUT_EN); + + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl | flag); +} + +static void stac92xx_reset_pinctl(struct hda_codec *codec, hda_nid_t nid, + unsigned int flag) +{ + unsigned int pin_ctl = snd_hda_codec_read(codec, nid, + 0, AC_VERB_GET_PIN_WIDGET_CONTROL, 0x00); + snd_hda_codec_write_cache(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_ctl & ~flag); +} + +static int get_hp_pin_presence(struct hda_codec *codec, hda_nid_t nid) +{ + if (!nid) + return 0; + if (snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PIN_SENSE, 0x00) + & (1 << 31)) { + unsigned int pinctl; + pinctl = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_PIN_WIDGET_CONTROL, 0); + if (pinctl & AC_PINCTL_IN_EN) + return 0; /* mic- or line-input */ + else + return 1; /* HP-output */ + } + return 0; +} + +/* return non-zero if the hp-pin of the given array index isn't + * a jack-detection target + */ +static int no_hp_sensing(struct sigmatel_spec *spec, int i) +{ + struct auto_pin_cfg *cfg = &spec->autocfg; + + /* ignore sensing of shared line and mic jacks */ + if (spec->line_switch && + cfg->hp_pins[i] == cfg->input_pins[AUTO_PIN_LINE]) + return 1; + if (spec->mic_switch && + cfg->hp_pins[i] == cfg->input_pins[AUTO_PIN_MIC]) + return 1; + /* ignore if the pin is set as line-out */ + if (cfg->hp_pins[i] == spec->hp_switch) + return 1; + return 0; +} + +static void stac92xx_hp_detect(struct hda_codec *codec, unsigned int res) +{ + struct sigmatel_spec *spec = codec->spec; + struct auto_pin_cfg *cfg = &spec->autocfg; + int i, presence; + + presence = 0; + if (spec->gpio_mute) + presence = !(snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DATA, 0) & spec->gpio_mute); + + for (i = 0; i < cfg->hp_outs; i++) { + if (presence) + break; + if (no_hp_sensing(spec, i)) + continue; + presence = get_hp_pin_presence(codec, cfg->hp_pins[i]); + } + + if (presence) { + /* disable lineouts */ + if (spec->hp_switch) + stac92xx_reset_pinctl(codec, spec->hp_switch, + AC_PINCTL_OUT_EN); + for (i = 0; i < cfg->line_outs; i++) + stac92xx_reset_pinctl(codec, cfg->line_out_pins[i], + AC_PINCTL_OUT_EN); + for (i = 0; i < cfg->speaker_outs; i++) + stac92xx_reset_pinctl(codec, cfg->speaker_pins[i], + AC_PINCTL_OUT_EN); + if (spec->eapd_mask && spec->eapd_switch) + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data & + ~spec->eapd_mask); + } else { + /* enable lineouts */ + if (spec->hp_switch) + stac92xx_set_pinctl(codec, spec->hp_switch, + AC_PINCTL_OUT_EN); + for (i = 0; i < cfg->line_outs; i++) + stac92xx_set_pinctl(codec, cfg->line_out_pins[i], + AC_PINCTL_OUT_EN); + for (i = 0; i < cfg->speaker_outs; i++) + stac92xx_set_pinctl(codec, cfg->speaker_pins[i], + AC_PINCTL_OUT_EN); + if (spec->eapd_mask && spec->eapd_switch) + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data | + spec->eapd_mask); + } + /* toggle hp outs */ + for (i = 0; i < cfg->hp_outs; i++) { + unsigned int val = AC_PINCTL_OUT_EN | AC_PINCTL_HP_EN; + if (no_hp_sensing(spec, i)) + continue; + if (presence) + stac92xx_set_pinctl(codec, cfg->hp_pins[i], val); +#if 0 /* FIXME */ +/* Resetting the pinctl like below may lead to (a sort of) regressions + * on some devices since they use the HP pin actually for line/speaker + * outs although the default pin config shows a different pin (that is + * wrong and useless). + * + * So, it's basically a problem of default pin configs, likely a BIOS issue. + * But, disabling the code below just works around it, and I'm too tired of + * bug reports with such devices... + */ + else + stac92xx_reset_pinctl(codec, cfg->hp_pins[i], val); +#endif /* FIXME */ + } +} + +static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid, + int enable) +{ + struct sigmatel_spec *spec = codec->spec; + unsigned int idx, val; + + for (idx = 0; idx < spec->num_pwrs; idx++) { + if (spec->pwr_nids[idx] == nid) + break; + } + if (idx >= spec->num_pwrs) + return; + + /* several codecs have two power down bits */ + if (spec->pwr_mapping) + idx = spec->pwr_mapping[idx]; + else + idx = 1 << idx; + + val = snd_hda_codec_read(codec, codec->afg, 0, 0x0fec, 0x0) & 0xff; + if (enable) + val &= ~idx; + else + val |= idx; + + /* power down unused output ports */ + snd_hda_codec_write(codec, codec->afg, 0, 0x7ec, val); +} + +static void stac92xx_pin_sense(struct hda_codec *codec, hda_nid_t nid) +{ + stac_toggle_power_map(codec, nid, get_hp_pin_presence(codec, nid)); +} + +static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct sigmatel_spec *spec = codec->spec; + int idx = res >> 26 & 0x0f; + + switch ((res >> 26) & 0x70) { + case STAC_HP_EVENT: + stac92xx_hp_detect(codec, res); + /* fallthru */ + case STAC_PWR_EVENT: + if (spec->num_pwrs > 0) + stac92xx_pin_sense(codec, idx); + break; + case STAC_VREF_EVENT: { + int data = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DATA, 0); + /* toggle VREF state based on GPIOx status */ + snd_hda_codec_write(codec, codec->afg, 0, 0x7e0, + !!(data & (1 << idx))); + break; + } + } +} + +#ifdef SND_HDA_NEEDS_RESUME +static int stac92xx_resume(struct hda_codec *codec) +{ + struct sigmatel_spec *spec = codec->spec; + + stac92xx_set_config_regs(codec); + snd_hda_sequence_write(codec, spec->init); + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data); + snd_hda_codec_resume_amp(codec); + snd_hda_codec_resume_cache(codec); + /* power down inactive DACs */ + if (spec->dac_list) + stac92xx_power_down(codec); + /* invoke unsolicited event to reset the HP state */ + if (spec->hp_detect) + codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); + return 0; +} +#endif + +static struct hda_codec_ops stac92xx_patch_ops = { + .build_controls = stac92xx_build_controls, + .build_pcms = stac92xx_build_pcms, + .init = stac92xx_init, + .free = stac92xx_free, + .unsol_event = stac92xx_unsol_event, +#ifdef SND_HDA_NEEDS_RESUME + .resume = stac92xx_resume, +#endif +}; + +static int patch_stac9200(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->num_pins = ARRAY_SIZE(stac9200_pin_nids); + spec->pin_nids = stac9200_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_9200_MODELS, + stac9200_models, + stac9200_cfg_tbl); + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC9200, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac9200_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = stac9200_dac_nids; + spec->adc_nids = stac9200_adc_nids; + spec->mux_nids = stac9200_mux_nids; + spec->num_muxes = 1; + spec->num_dmics = 0; + spec->num_adcs = 1; + spec->num_pwrs = 0; + + if (spec->board_config == STAC_9200_GATEWAY || + spec->board_config == STAC_9200_OQO) + spec->init = stac9200_eapd_init; + else + spec->init = stac9200_core_init; + spec->mixer = stac9200_mixer; + + if (spec->board_config == STAC_9200_PANASONIC) { + spec->gpio_mask = spec->gpio_dir = 0x09; + spec->gpio_data = 0x00; + } + + err = stac9200_parse_auto_config(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + +static int patch_stac925x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->num_pins = ARRAY_SIZE(stac925x_pin_nids); + spec->pin_nids = stac925x_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_925x_MODELS, + stac925x_models, + stac925x_cfg_tbl); + again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC925x," + "using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else if (stac925x_brd_tbl[spec->board_config] != NULL){ + spec->pin_configs = stac925x_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = stac925x_dac_nids; + spec->adc_nids = stac925x_adc_nids; + spec->mux_nids = stac925x_mux_nids; + spec->num_muxes = 1; + spec->num_adcs = 1; + spec->num_pwrs = 0; + switch (codec->vendor_id) { + case 0x83847632: /* STAC9202 */ + case 0x83847633: /* STAC9202D */ + case 0x83847636: /* STAC9251 */ + case 0x83847637: /* STAC9251D */ + spec->num_dmics = STAC925X_NUM_DMICS; + spec->dmic_nids = stac925x_dmic_nids; + spec->num_dmuxes = ARRAY_SIZE(stac925x_dmux_nids); + spec->dmux_nids = stac925x_dmux_nids; + break; + default: + spec->num_dmics = 0; + break; + } + + spec->init = stac925x_core_init; + spec->mixer = stac925x_mixer; + + err = stac92xx_parse_auto_config(codec, 0x8, 0x7); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_925x_REF; + goto again; + } + err = -EINVAL; + } + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + +static struct hda_input_mux stac92hd73xx_dmux = { + .num_items = 4, + .items = { + { "Analog Inputs", 0x0b }, + { "Digital Mic 1", 0x09 }, + { "Digital Mic 2", 0x0a }, + { "CD", 0x08 }, + } +}; + +static int patch_stac92hd73xx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + hda_nid_t conn[STAC92HD73_DAC_COUNT + 2]; + int err = 0; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + codec->slave_dig_outs = stac92hd73xx_slave_dig_outs; + spec->num_pins = ARRAY_SIZE(stac92hd73xx_pin_nids); + spec->pin_nids = stac92hd73xx_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, + STAC_92HD73XX_MODELS, + stac92hd73xx_models, + stac92hd73xx_cfg_tbl); +again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for" + " STAC92HD73XX, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac92hd73xx_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->multiout.num_dacs = snd_hda_get_connections(codec, 0x0a, + conn, STAC92HD73_DAC_COUNT + 2) - 1; + + if (spec->multiout.num_dacs < 0) { + printk(KERN_WARNING "hda_codec: Could not determine " + "number of channels defaulting to DAC count\n"); + spec->multiout.num_dacs = STAC92HD73_DAC_COUNT; + } + + switch (spec->multiout.num_dacs) { + case 0x3: /* 6 Channel */ + spec->multiout.hp_nid = 0x17; + spec->mixer = stac92hd73xx_6ch_mixer; + spec->init = stac92hd73xx_6ch_core_init; + break; + case 0x4: /* 8 Channel */ + spec->multiout.hp_nid = 0x18; + spec->mixer = stac92hd73xx_8ch_mixer; + spec->init = stac92hd73xx_8ch_core_init; + break; + case 0x5: /* 10 Channel */ + spec->multiout.hp_nid = 0x19; + spec->mixer = stac92hd73xx_10ch_mixer; + spec->init = stac92hd73xx_10ch_core_init; + }; + + spec->multiout.dac_nids = stac92hd73xx_dac_nids; + spec->aloopback_mask = 0x01; + spec->aloopback_shift = 8; + + spec->digbeep_nid = 0x1c; + spec->mux_nids = stac92hd73xx_mux_nids; + spec->adc_nids = stac92hd73xx_adc_nids; + spec->dmic_nids = stac92hd73xx_dmic_nids; + spec->dmux_nids = stac92hd73xx_dmux_nids; + spec->smux_nids = stac92hd73xx_smux_nids; + spec->amp_nids = stac92hd73xx_amp_nids; + spec->num_amps = ARRAY_SIZE(stac92hd73xx_amp_nids); + + spec->num_muxes = ARRAY_SIZE(stac92hd73xx_mux_nids); + spec->num_adcs = ARRAY_SIZE(stac92hd73xx_adc_nids); + spec->num_dmuxes = ARRAY_SIZE(stac92hd73xx_dmux_nids); + memcpy(&spec->private_dimux, &stac92hd73xx_dmux, + sizeof(stac92hd73xx_dmux)); + + switch (spec->board_config) { + case STAC_DELL_EQ: + spec->init = dell_eq_core_init; + /* fallthru */ + case STAC_DELL_M6_AMIC: + case STAC_DELL_M6_DMIC: + case STAC_DELL_M6_BOTH: + spec->num_smuxes = 0; + spec->mixer = &stac92hd73xx_6ch_mixer[DELL_M6_MIXER]; + spec->amp_nids = &stac92hd73xx_amp_nids[DELL_M6_AMP]; + spec->eapd_switch = 0; + spec->num_amps = 1; + spec->multiout.hp_nid = 0; /* dual HPs */ + + if (!spec->init) + spec->init = dell_m6_core_init; + switch (spec->board_config) { + case STAC_DELL_M6_AMIC: /* Analog Mics */ + stac92xx_set_config_reg(codec, 0x0b, 0x90A70170); + spec->num_dmics = 0; + spec->private_dimux.num_items = 1; + break; + case STAC_DELL_M6_DMIC: /* Digital Mics */ + stac92xx_set_config_reg(codec, 0x13, 0x90A60160); + spec->num_dmics = 1; + spec->private_dimux.num_items = 2; + break; + case STAC_DELL_M6_BOTH: /* Both */ + stac92xx_set_config_reg(codec, 0x0b, 0x90A70170); + stac92xx_set_config_reg(codec, 0x13, 0x90A60160); + spec->num_dmics = 1; + spec->private_dimux.num_items = 2; + break; + } + break; + default: + spec->num_dmics = STAC92HD73XX_NUM_DMICS; + spec->num_smuxes = ARRAY_SIZE(stac92hd73xx_smux_nids); + spec->eapd_switch = 1; + } + if (spec->board_config > STAC_92HD73XX_REF) { + /* GPIO0 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1; + spec->gpio_data = 0x01; + } + spec->dinput_mux = &spec->private_dimux; + + spec->num_pwrs = ARRAY_SIZE(stac92hd73xx_pwr_nids); + spec->pwr_nids = stac92hd73xx_pwr_nids; + + err = stac92xx_parse_auto_config(codec, 0x25, 0x27); + + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_92HD73XX_REF; + goto again; + } + err = -EINVAL; + } + + if (err < 0) { + stac92xx_free(codec); + return err; + } + + if (spec->board_config == STAC_92HD73XX_NO_JD) + spec->hp_detect = 0; + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + +static struct hda_input_mux stac92hd83xxx_dmux = { + .num_items = 3, + .items = { + { "Analog Inputs", 0x03 }, + { "Digital Mic 1", 0x04 }, + { "Digital Mic 2", 0x05 }, + } +}; + +static int patch_stac92hd83xxx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + codec->slave_dig_outs = stac92hd83xxx_slave_dig_outs; + spec->mono_nid = 0x19; + spec->digbeep_nid = 0x21; + spec->dmic_nids = stac92hd83xxx_dmic_nids; + spec->dmux_nids = stac92hd83xxx_dmux_nids; + spec->adc_nids = stac92hd83xxx_adc_nids; + spec->pwr_nids = stac92hd83xxx_pwr_nids; + spec->pwr_mapping = stac92hd83xxx_pwr_mapping; + spec->num_pwrs = ARRAY_SIZE(stac92hd83xxx_pwr_nids); + spec->multiout.dac_nids = stac92hd83xxx_dac_nids; + + spec->init = stac92hd83xxx_core_init; + switch (codec->vendor_id) { + case 0x111d7605: + spec->multiout.num_dacs = STAC92HD81_DAC_COUNT; + break; + default: + spec->num_pwrs--; + spec->init++; /* switch to config #2 */ + spec->multiout.num_dacs = STAC92HD83_DAC_COUNT; + } + + spec->mixer = stac92hd83xxx_mixer; + spec->num_pins = ARRAY_SIZE(stac92hd83xxx_pin_nids); + spec->num_dmuxes = ARRAY_SIZE(stac92hd83xxx_dmux_nids); + spec->num_adcs = ARRAY_SIZE(stac92hd83xxx_adc_nids); + spec->num_dmics = STAC92HD83XXX_NUM_DMICS; + spec->dinput_mux = &stac92hd83xxx_dmux; + spec->pin_nids = stac92hd83xxx_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, + STAC_92HD83XXX_MODELS, + stac92hd83xxx_models, + stac92hd83xxx_cfg_tbl); +again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for" + " STAC92HD83XXX, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac92hd83xxx_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + err = stac92xx_parse_auto_config(codec, 0x1d, 0); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_92HD83XXX_REF; + goto again; + } + err = -EINVAL; + } + + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + +#ifdef SND_HDA_NEEDS_RESUME +static void stac92hd71xx_set_power_state(struct hda_codec *codec, int pwr) +{ + struct sigmatel_spec *spec = codec->spec; + int i; + snd_hda_codec_write_cache(codec, codec->afg, 0, + AC_VERB_SET_POWER_STATE, pwr); + + msleep(1); + for (i = 0; i < spec->num_adcs; i++) { + snd_hda_codec_write_cache(codec, + spec->adc_nids[i], 0, + AC_VERB_SET_POWER_STATE, pwr); + } +}; + +static int stac92hd71xx_resume(struct hda_codec *codec) +{ + stac92hd71xx_set_power_state(codec, AC_PWRST_D0); + return stac92xx_resume(codec); +} + +static int stac92hd71xx_suspend(struct hda_codec *codec, pm_message_t state) +{ + struct sigmatel_spec *spec = codec->spec; + + stac92hd71xx_set_power_state(codec, AC_PWRST_D3); + if (spec->eapd_mask) + stac_gpio_set(codec, spec->gpio_mask, + spec->gpio_dir, spec->gpio_data & + ~spec->eapd_mask); + return 0; +}; + +#endif + +static struct hda_codec_ops stac92hd71bxx_patch_ops = { + .build_controls = stac92xx_build_controls, + .build_pcms = stac92xx_build_pcms, + .init = stac92xx_init, + .free = stac92xx_free, + .unsol_event = stac92xx_unsol_event, +#ifdef SND_HDA_NEEDS_RESUME + .resume = stac92hd71xx_resume, + .suspend = stac92hd71xx_suspend, +#endif +}; + +static struct hda_input_mux stac92hd71bxx_dmux = { + .num_items = 4, + .items = { + { "Analog Inputs", 0x00 }, + { "Mixer", 0x01 }, + { "Digital Mic 1", 0x02 }, + { "Digital Mic 2", 0x03 }, + } +}; + +static int patch_stac92hd71bxx(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err = 0; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + codec->patch_ops = stac92xx_patch_ops; + spec->num_pins = ARRAY_SIZE(stac92hd71bxx_pin_nids); + spec->num_pwrs = ARRAY_SIZE(stac92hd71bxx_pwr_nids); + spec->pin_nids = stac92hd71bxx_pin_nids; + memcpy(&spec->private_dimux, &stac92hd71bxx_dmux, + sizeof(stac92hd71bxx_dmux)); + spec->board_config = snd_hda_check_board_config(codec, + STAC_92HD71BXX_MODELS, + stac92hd71bxx_models, + stac92hd71bxx_cfg_tbl); +again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for" + " STAC92HD71BXX, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac92hd71bxx_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + if (spec->board_config > STAC_92HD71BXX_REF) { + /* GPIO0 = EAPD */ + spec->gpio_mask = 0x01; + spec->gpio_dir = 0x01; + spec->gpio_data = 0x01; + } + + switch (codec->vendor_id) { + case 0x111d76b6: /* 4 Port without Analog Mixer */ + case 0x111d76b7: + case 0x111d76b4: /* 6 Port without Analog Mixer */ + case 0x111d76b5: + spec->mixer = stac92hd71bxx_mixer; + spec->init = stac92hd71bxx_core_init; + codec->slave_dig_outs = stac92hd71bxx_slave_dig_outs; + break; + case 0x111d7608: /* 5 Port with Analog Mixer */ + switch (spec->board_config) { + case STAC_HP_M4: + /* Enable VREF power saving on GPIO1 detect */ + snd_hda_codec_write_cache(codec, codec->afg, 0, + AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x02); + snd_hda_codec_write_cache(codec, codec->afg, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + (AC_USRSP_EN | STAC_VREF_EVENT | 0x01)); + spec->gpio_mask |= 0x02; + break; + } + if ((codec->revision_id & 0xf) == 0 || + (codec->revision_id & 0xf) == 1) { +#ifdef SND_HDA_NEEDS_RESUME + codec->patch_ops = stac92hd71bxx_patch_ops; +#endif + spec->stream_delay = 40; /* 40 milliseconds */ + } + + /* no output amps */ + spec->num_pwrs = 0; + spec->mixer = stac92hd71bxx_analog_mixer; + spec->dinput_mux = &spec->private_dimux; + + /* disable VSW */ + spec->init = &stac92hd71bxx_analog_core_init[HD_DISABLE_PORTF]; + stac92xx_set_config_reg(codec, 0xf, 0x40f000f0); + break; + case 0x111d7603: /* 6 Port with Analog Mixer */ + if ((codec->revision_id & 0xf) == 1) { +#ifdef SND_HDA_NEEDS_RESUME + codec->patch_ops = stac92hd71bxx_patch_ops; +#endif + spec->stream_delay = 40; /* 40 milliseconds */ + } + + /* no output amps */ + spec->num_pwrs = 0; + /* fallthru */ + default: + spec->dinput_mux = &spec->private_dimux; + spec->mixer = stac92hd71bxx_analog_mixer; + spec->init = stac92hd71bxx_analog_core_init; + codec->slave_dig_outs = stac92hd71bxx_slave_dig_outs; + } + + spec->aloopback_mask = 0x50; + spec->aloopback_shift = 0; + + spec->powerdown_adcs = 1; + spec->digbeep_nid = 0x26; + spec->mux_nids = stac92hd71bxx_mux_nids; + spec->adc_nids = stac92hd71bxx_adc_nids; + spec->dmic_nids = stac92hd71bxx_dmic_nids; + spec->dmux_nids = stac92hd71bxx_dmux_nids; + spec->smux_nids = stac92hd71bxx_smux_nids; + spec->pwr_nids = stac92hd71bxx_pwr_nids; + + spec->num_muxes = ARRAY_SIZE(stac92hd71bxx_mux_nids); + spec->num_adcs = ARRAY_SIZE(stac92hd71bxx_adc_nids); + + switch (spec->board_config) { + case STAC_HP_M4: + /* enable internal microphone */ + stac92xx_set_config_reg(codec, 0x0e, 0x01813040); + stac92xx_auto_set_pinctl(codec, 0x0e, + AC_PINCTL_IN_EN | AC_PINCTL_VREF_80); + /* fallthru */ + case STAC_DELL_M4_2: + spec->num_dmics = 0; + spec->num_smuxes = 0; + spec->num_dmuxes = 0; + break; + case STAC_DELL_M4_1: + case STAC_DELL_M4_3: + spec->num_dmics = 1; + spec->num_smuxes = 0; + spec->num_dmuxes = 1; + break; + default: + spec->num_dmics = STAC92HD71BXX_NUM_DMICS; + spec->num_smuxes = ARRAY_SIZE(stac92hd71bxx_smux_nids); + spec->num_dmuxes = ARRAY_SIZE(stac92hd71bxx_dmux_nids); + }; + + spec->multiout.num_dacs = 1; + spec->multiout.hp_nid = 0x11; + spec->multiout.dac_nids = stac92hd71bxx_dac_nids; + if (spec->dinput_mux) + spec->private_dimux.num_items += + spec->num_dmics - + (ARRAY_SIZE(stac92hd71bxx_dmic_nids) - 1); + + err = stac92xx_parse_auto_config(codec, 0x21, 0x23); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_92HD71BXX_REF; + goto again; + } + err = -EINVAL; + } + + if (err < 0) { + stac92xx_free(codec); + return err; + } + + return 0; +}; + +static int patch_stac922x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->num_pins = ARRAY_SIZE(stac922x_pin_nids); + spec->pin_nids = stac922x_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_922X_MODELS, + stac922x_models, + stac922x_cfg_tbl); + if (spec->board_config == STAC_INTEL_MAC_AUTO) { + spec->gpio_mask = spec->gpio_dir = 0x03; + spec->gpio_data = 0x03; + /* Intel Macs have all same PCI SSID, so we need to check + * codec SSID to distinguish the exact models + */ + printk(KERN_INFO "hda_codec: STAC922x, Apple subsys_id=%x\n", codec->subsystem_id); + switch (codec->subsystem_id) { + + case 0x106b0800: + spec->board_config = STAC_INTEL_MAC_V1; + break; + case 0x106b0600: + case 0x106b0700: + spec->board_config = STAC_INTEL_MAC_V2; + break; + case 0x106b0e00: + case 0x106b0f00: + case 0x106b1600: + case 0x106b1700: + case 0x106b0200: + case 0x106b1e00: + spec->board_config = STAC_INTEL_MAC_V3; + break; + case 0x106b1a00: + case 0x00000100: + spec->board_config = STAC_INTEL_MAC_V4; + break; + case 0x106b0a00: + case 0x106b2200: + spec->board_config = STAC_INTEL_MAC_V5; + break; + default: + spec->board_config = STAC_INTEL_MAC_V3; + break; + } + } + + again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC922x, " + "using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else if (stac922x_brd_tbl[spec->board_config] != NULL) { + spec->pin_configs = stac922x_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->adc_nids = stac922x_adc_nids; + spec->mux_nids = stac922x_mux_nids; + spec->num_muxes = ARRAY_SIZE(stac922x_mux_nids); + spec->num_adcs = ARRAY_SIZE(stac922x_adc_nids); + spec->num_dmics = 0; + spec->num_pwrs = 0; + + spec->init = stac922x_core_init; + spec->mixer = stac922x_mixer; + + spec->multiout.dac_nids = spec->dac_nids; + + err = stac92xx_parse_auto_config(codec, 0x08, 0x09); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_D945_REF; + goto again; + } + err = -EINVAL; + } + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + /* Fix Mux capture level; max to 2 */ + snd_hda_override_amp_caps(codec, 0x12, HDA_OUTPUT, + (0 << AC_AMPCAP_OFFSET_SHIFT) | + (2 << AC_AMPCAP_NUM_STEPS_SHIFT) | + (0x27 << AC_AMPCAP_STEP_SIZE_SHIFT) | + (0 << AC_AMPCAP_MUTE_SHIFT)); + + return 0; +} + +static int patch_stac927x(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->num_pins = ARRAY_SIZE(stac927x_pin_nids); + spec->pin_nids = stac927x_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_927X_MODELS, + stac927x_models, + stac927x_cfg_tbl); + again: + if (spec->board_config < 0 || !stac927x_brd_tbl[spec->board_config]) { + if (spec->board_config < 0) + snd_printdd(KERN_INFO "hda_codec: Unknown model for" + "STAC927x, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac927x_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->digbeep_nid = 0x23; + spec->adc_nids = stac927x_adc_nids; + spec->num_adcs = ARRAY_SIZE(stac927x_adc_nids); + spec->mux_nids = stac927x_mux_nids; + spec->num_muxes = ARRAY_SIZE(stac927x_mux_nids); + spec->smux_nids = stac927x_smux_nids; + spec->num_smuxes = ARRAY_SIZE(stac927x_smux_nids); + spec->spdif_labels = stac927x_spdif_labels; + spec->dac_list = stac927x_dac_nids; + spec->multiout.dac_nids = spec->dac_nids; + + switch (spec->board_config) { + case STAC_D965_3ST: + case STAC_D965_5ST: + /* GPIO0 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x01; + spec->gpio_data = 0x01; + spec->num_dmics = 0; + + spec->init = d965_core_init; + spec->mixer = stac927x_mixer; + break; + case STAC_DELL_BIOS: + switch (codec->subsystem_id) { + case 0x10280209: + case 0x1028022e: + /* correct the device field to SPDIF out */ + stac92xx_set_config_reg(codec, 0x21, 0x01442070); + break; + }; + /* configure the analog microphone on some laptops */ + stac92xx_set_config_reg(codec, 0x0c, 0x90a79130); + /* correct the front output jack as a hp out */ + stac92xx_set_config_reg(codec, 0x0f, 0x0227011f); + /* correct the front input jack as a mic */ + stac92xx_set_config_reg(codec, 0x0e, 0x02a79130); + /* fallthru */ + case STAC_DELL_3ST: + /* GPIO2 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x04; + spec->gpio_data = 0x04; + spec->dmic_nids = stac927x_dmic_nids; + spec->num_dmics = STAC927X_NUM_DMICS; + + spec->init = d965_core_init; + spec->mixer = stac927x_mixer; + spec->dmux_nids = stac927x_dmux_nids; + spec->num_dmuxes = ARRAY_SIZE(stac927x_dmux_nids); + break; + default: + if (spec->board_config > STAC_D965_REF) { + /* GPIO0 High = Enable EAPD */ + spec->eapd_mask = spec->gpio_mask = 0x01; + spec->gpio_dir = spec->gpio_data = 0x01; + } + spec->num_dmics = 0; + + spec->init = stac927x_core_init; + spec->mixer = stac927x_mixer; + } + + spec->num_pwrs = 0; + spec->aloopback_mask = 0x40; + spec->aloopback_shift = 0; + spec->eapd_switch = 1; + + err = stac92xx_parse_auto_config(codec, 0x1e, 0x20); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_D965_REF; + goto again; + } + err = -EINVAL; + } + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + /* + * !!FIXME!! + * The STAC927x seem to require fairly long delays for certain + * command sequences. With too short delays (even if the answer + * is set to RIRB properly), it results in the silence output + * on some hardwares like Dell. + * + * The below flag enables the longer delay (see get_response + * in hda_intel.c). + */ + codec->bus->needs_damn_long_delay = 1; + + /* no jack detecion for ref-no-jd model */ + if (spec->board_config == STAC_D965_REF_NO_JD) + spec->hp_detect = 0; + + return 0; +} + +static int patch_stac9205(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int err; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + spec->num_pins = ARRAY_SIZE(stac9205_pin_nids); + spec->pin_nids = stac9205_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_9205_MODELS, + stac9205_models, + stac9205_cfg_tbl); + again: + if (spec->board_config < 0) { + snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC9205, using BIOS defaults\n"); + err = stac92xx_save_bios_config_regs(codec); + if (err < 0) { + stac92xx_free(codec); + return err; + } + spec->pin_configs = spec->bios_pin_configs; + } else { + spec->pin_configs = stac9205_brd_tbl[spec->board_config]; + stac92xx_set_config_regs(codec); + } + + spec->digbeep_nid = 0x23; + spec->adc_nids = stac9205_adc_nids; + spec->num_adcs = ARRAY_SIZE(stac9205_adc_nids); + spec->mux_nids = stac9205_mux_nids; + spec->num_muxes = ARRAY_SIZE(stac9205_mux_nids); + spec->smux_nids = stac9205_smux_nids; + spec->num_smuxes = ARRAY_SIZE(stac9205_smux_nids); + spec->dmic_nids = stac9205_dmic_nids; + spec->num_dmics = STAC9205_NUM_DMICS; + spec->dmux_nids = stac9205_dmux_nids; + spec->num_dmuxes = ARRAY_SIZE(stac9205_dmux_nids); + spec->num_pwrs = 0; + + spec->init = stac9205_core_init; + spec->mixer = stac9205_mixer; + + spec->aloopback_mask = 0x40; + spec->aloopback_shift = 0; + spec->eapd_switch = 1; + spec->multiout.dac_nids = spec->dac_nids; + + switch (spec->board_config){ + case STAC_9205_DELL_M43: + /* Enable SPDIF in/out */ + stac92xx_set_config_reg(codec, 0x1f, 0x01441030); + stac92xx_set_config_reg(codec, 0x20, 0x1c410030); + + /* Enable unsol response for GPIO4/Dock HP connection */ + snd_hda_codec_write_cache(codec, codec->afg, 0, + AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, 0x10); + snd_hda_codec_write_cache(codec, codec->afg, 0, + AC_VERB_SET_UNSOLICITED_ENABLE, + (AC_USRSP_EN | STAC_HP_EVENT)); + + spec->gpio_dir = 0x0b; + spec->eapd_mask = 0x01; + spec->gpio_mask = 0x1b; + spec->gpio_mute = 0x10; + /* GPIO0 High = EAPD, GPIO1 Low = Headphone Mute, + * GPIO3 Low = DRM + */ + spec->gpio_data = 0x01; + break; + case STAC_9205_REF: + /* SPDIF-In enabled */ + break; + default: + /* GPIO0 High = EAPD */ + spec->eapd_mask = spec->gpio_mask = spec->gpio_dir = 0x1; + spec->gpio_data = 0x01; + break; + } + + err = stac92xx_parse_auto_config(codec, 0x1f, 0x20); + if (!err) { + if (spec->board_config < 0) { + printk(KERN_WARNING "hda_codec: No auto-config is " + "available, default to model=ref\n"); + spec->board_config = STAC_9205_REF; + goto again; + } + err = -EINVAL; + } + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + +/* + * STAC9872 hack + */ + +/* static config for Sony VAIO FE550G and Sony VAIO AR */ +static hda_nid_t vaio_dacs[] = { 0x2 }; +#define VAIO_HP_DAC 0x5 +static hda_nid_t vaio_adcs[] = { 0x8 /*,0x6*/ }; +static hda_nid_t vaio_mux_nids[] = { 0x15 }; + +static struct hda_input_mux vaio_mux = { + .num_items = 3, + .items = { + /* { "HP", 0x0 }, */ + { "Mic Jack", 0x1 }, + { "Internal Mic", 0x2 }, + { "PCM", 0x3 }, + } +}; + +static struct hda_verb vaio_init[] = { + {0x0a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP <- 0x2 */ + {0x0a, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | STAC_HP_EVENT}, + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Speaker <- 0x5 */ + {0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? (<- 0x2) */ + {0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* capture sw/vol -> 0x8 */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* CD-in -> 0x6 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */ + {} +}; + +static struct hda_verb vaio_ar_init[] = { + {0x0a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP }, /* HP <- 0x2 */ + {0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, /* Speaker <- 0x5 */ + {0x0d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? (<- 0x2) */ + {0x0e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, /* CD */ +/* {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT },*/ /* Optical Out */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80 }, /* Mic? */ + {0x15, AC_VERB_SET_CONNECT_SEL, 0x1}, /* mic-sel: 0a,0d,14,02 */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* HP */ + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, /* Speaker */ +/* {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},*/ /* Optical Out */ + {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, /* capture sw/vol -> 0x8 */ + {0x07, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, /* CD-in -> 0x6 */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, /* Mic-in -> 0x9 */ + {} +}; + +/* bind volumes of both NID 0x02 and 0x05 */ +static struct hda_bind_ctls vaio_bind_master_vol = { + .ops = &snd_hda_bind_vol, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x05, 3, 0, HDA_OUTPUT), + 0 + }, +}; + +/* bind volumes of both NID 0x02 and 0x05 */ +static struct hda_bind_ctls vaio_bind_master_sw = { + .ops = &snd_hda_bind_sw, + .values = { + HDA_COMPOSE_AMP_VAL(0x02, 3, 0, HDA_OUTPUT), + HDA_COMPOSE_AMP_VAL(0x05, 3, 0, HDA_OUTPUT), + 0, + }, +}; + +static struct snd_kcontrol_new vaio_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &vaio_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &vaio_bind_master_sw), + /* HDA_CODEC_VOLUME("CD Capture Volume", 0x07, 0, HDA_INPUT), */ + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .info = stac92xx_mux_enum_info, + .get = stac92xx_mux_enum_get, + .put = stac92xx_mux_enum_put, + }, + {} +}; + +static struct snd_kcontrol_new vaio_ar_mixer[] = { + HDA_BIND_VOL("Master Playback Volume", &vaio_bind_master_vol), + HDA_BIND_SW("Master Playback Switch", &vaio_bind_master_sw), + /* HDA_CODEC_VOLUME("CD Capture Volume", 0x07, 0, HDA_INPUT), */ + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_INPUT), + /*HDA_CODEC_MUTE("Optical Out Switch", 0x10, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Optical Out Volume", 0x10, 0, HDA_OUTPUT),*/ + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Capture Source", + .count = 1, + .info = stac92xx_mux_enum_info, + .get = stac92xx_mux_enum_get, + .put = stac92xx_mux_enum_put, + }, + {} +}; + +static struct hda_codec_ops stac9872_patch_ops = { + .build_controls = stac92xx_build_controls, + .build_pcms = stac92xx_build_pcms, + .init = stac92xx_init, + .free = stac92xx_free, +#ifdef SND_HDA_NEEDS_RESUME + .resume = stac92xx_resume, +#endif +}; + +static int stac9872_vaio_init(struct hda_codec *codec) +{ + int err; + + err = stac92xx_init(codec); + if (err < 0) + return err; + if (codec->patch_ops.unsol_event) + codec->patch_ops.unsol_event(codec, STAC_HP_EVENT << 26); + return 0; +} + +static void stac9872_vaio_hp_detect(struct hda_codec *codec, unsigned int res) +{ + if (get_hp_pin_presence(codec, 0x0a)) { + stac92xx_reset_pinctl(codec, 0x0f, AC_PINCTL_OUT_EN); + stac92xx_set_pinctl(codec, 0x0a, AC_PINCTL_OUT_EN); + } else { + stac92xx_reset_pinctl(codec, 0x0a, AC_PINCTL_OUT_EN); + stac92xx_set_pinctl(codec, 0x0f, AC_PINCTL_OUT_EN); + } +} + +static void stac9872_vaio_unsol_event(struct hda_codec *codec, unsigned int res) +{ + switch (res >> 26) { + case STAC_HP_EVENT: + stac9872_vaio_hp_detect(codec, res); + break; + } +} + +static struct hda_codec_ops stac9872_vaio_patch_ops = { + .build_controls = stac92xx_build_controls, + .build_pcms = stac92xx_build_pcms, + .init = stac9872_vaio_init, + .free = stac92xx_free, + .unsol_event = stac9872_vaio_unsol_event, +#ifdef CONFIG_PM + .resume = stac92xx_resume, +#endif +}; + +enum { /* FE and SZ series. id=0x83847661 and subsys=0x104D0700 or 104D1000. */ + CXD9872RD_VAIO, + /* Unknown. id=0x83847662 and subsys=0x104D1200 or 104D1000. */ + STAC9872AK_VAIO, + /* Unknown. id=0x83847661 and subsys=0x104D1200. */ + STAC9872K_VAIO, + /* AR Series. id=0x83847664 and subsys=104D1300 */ + CXD9872AKD_VAIO, + STAC_9872_MODELS, +}; + +static const char *stac9872_models[STAC_9872_MODELS] = { + [CXD9872RD_VAIO] = "vaio", + [CXD9872AKD_VAIO] = "vaio-ar", +}; + +static struct snd_pci_quirk stac9872_cfg_tbl[] = { + SND_PCI_QUIRK(0x104d, 0x81e6, "Sony VAIO F/S", CXD9872RD_VAIO), + SND_PCI_QUIRK(0x104d, 0x81ef, "Sony VAIO F/S", CXD9872RD_VAIO), + SND_PCI_QUIRK(0x104d, 0x81fd, "Sony VAIO AR", CXD9872AKD_VAIO), + SND_PCI_QUIRK(0x104d, 0x8205, "Sony VAIO AR", CXD9872AKD_VAIO), + {} +}; + +static int patch_stac9872(struct hda_codec *codec) +{ + struct sigmatel_spec *spec; + int board_config; + + board_config = snd_hda_check_board_config(codec, STAC_9872_MODELS, + stac9872_models, + stac9872_cfg_tbl); + if (board_config < 0) + /* unknown config, let generic-parser do its job... */ + return snd_hda_parse_generic_codec(codec); + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + switch (board_config) { + case CXD9872RD_VAIO: + case STAC9872AK_VAIO: + case STAC9872K_VAIO: + spec->mixer = vaio_mixer; + spec->init = vaio_init; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(vaio_dacs); + spec->multiout.dac_nids = vaio_dacs; + spec->multiout.hp_nid = VAIO_HP_DAC; + spec->num_adcs = ARRAY_SIZE(vaio_adcs); + spec->adc_nids = vaio_adcs; + spec->num_pwrs = 0; + spec->input_mux = &vaio_mux; + spec->mux_nids = vaio_mux_nids; + codec->patch_ops = stac9872_vaio_patch_ops; + break; + + case CXD9872AKD_VAIO: + spec->mixer = vaio_ar_mixer; + spec->init = vaio_ar_init; + spec->multiout.max_channels = 2; + spec->multiout.num_dacs = ARRAY_SIZE(vaio_dacs); + spec->multiout.dac_nids = vaio_dacs; + spec->multiout.hp_nid = VAIO_HP_DAC; + spec->num_adcs = ARRAY_SIZE(vaio_adcs); + spec->num_pwrs = 0; + spec->adc_nids = vaio_adcs; + spec->input_mux = &vaio_mux; + spec->mux_nids = vaio_mux_nids; + codec->patch_ops = stac9872_patch_ops; + break; + } + + return 0; +} + + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_sigmatel[] = { + { .id = 0x83847690, .name = "STAC9200", .patch = patch_stac9200 }, + { .id = 0x83847882, .name = "STAC9220 A1", .patch = patch_stac922x }, + { .id = 0x83847680, .name = "STAC9221 A1", .patch = patch_stac922x }, + { .id = 0x83847880, .name = "STAC9220 A2", .patch = patch_stac922x }, + { .id = 0x83847681, .name = "STAC9220D/9223D A2", .patch = patch_stac922x }, + { .id = 0x83847682, .name = "STAC9221 A2", .patch = patch_stac922x }, + { .id = 0x83847683, .name = "STAC9221D A2", .patch = patch_stac922x }, + { .id = 0x83847618, .name = "STAC9227", .patch = patch_stac927x }, + { .id = 0x83847619, .name = "STAC9227", .patch = patch_stac927x }, + { .id = 0x83847616, .name = "STAC9228", .patch = patch_stac927x }, + { .id = 0x83847617, .name = "STAC9228", .patch = patch_stac927x }, + { .id = 0x83847614, .name = "STAC9229", .patch = patch_stac927x }, + { .id = 0x83847615, .name = "STAC9229", .patch = patch_stac927x }, + { .id = 0x83847620, .name = "STAC9274", .patch = patch_stac927x }, + { .id = 0x83847621, .name = "STAC9274D", .patch = patch_stac927x }, + { .id = 0x83847622, .name = "STAC9273X", .patch = patch_stac927x }, + { .id = 0x83847623, .name = "STAC9273D", .patch = patch_stac927x }, + { .id = 0x83847624, .name = "STAC9272X", .patch = patch_stac927x }, + { .id = 0x83847625, .name = "STAC9272D", .patch = patch_stac927x }, + { .id = 0x83847626, .name = "STAC9271X", .patch = patch_stac927x }, + { .id = 0x83847627, .name = "STAC9271D", .patch = patch_stac927x }, + { .id = 0x83847628, .name = "STAC9274X5NH", .patch = patch_stac927x }, + { .id = 0x83847629, .name = "STAC9274D5NH", .patch = patch_stac927x }, + { .id = 0x83847632, .name = "STAC9202", .patch = patch_stac925x }, + { .id = 0x83847633, .name = "STAC9202D", .patch = patch_stac925x }, + { .id = 0x83847634, .name = "STAC9250", .patch = patch_stac925x }, + { .id = 0x83847635, .name = "STAC9250D", .patch = patch_stac925x }, + { .id = 0x83847636, .name = "STAC9251", .patch = patch_stac925x }, + { .id = 0x83847637, .name = "STAC9250D", .patch = patch_stac925x }, + { .id = 0x83847645, .name = "92HD206X", .patch = patch_stac927x }, + { .id = 0x83847646, .name = "92HD206D", .patch = patch_stac927x }, + /* The following does not take into account .id=0x83847661 when subsys = + * 104D0C00 which is STAC9225s. Because of this, some SZ Notebooks are + * currently not fully supported. + */ + { .id = 0x83847661, .name = "CXD9872RD/K", .patch = patch_stac9872 }, + { .id = 0x83847662, .name = "STAC9872AK", .patch = patch_stac9872 }, + { .id = 0x83847664, .name = "CXD9872AKD", .patch = patch_stac9872 }, + { .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 }, + { .id = 0x838476a1, .name = "STAC9205D", .patch = patch_stac9205 }, + { .id = 0x838476a2, .name = "STAC9204", .patch = patch_stac9205 }, + { .id = 0x838476a3, .name = "STAC9204D", .patch = patch_stac9205 }, + { .id = 0x838476a4, .name = "STAC9255", .patch = patch_stac9205 }, + { .id = 0x838476a5, .name = "STAC9255D", .patch = patch_stac9205 }, + { .id = 0x838476a6, .name = "STAC9254", .patch = patch_stac9205 }, + { .id = 0x838476a7, .name = "STAC9254D", .patch = patch_stac9205 }, + { .id = 0x111d7603, .name = "92HD75B3X5", .patch = patch_stac92hd71bxx}, + { .id = 0x111d7604, .name = "92HD83C1X5", .patch = patch_stac92hd83xxx}, + { .id = 0x111d7605, .name = "92HD81B1X5", .patch = patch_stac92hd83xxx}, + { .id = 0x111d7608, .name = "92HD75B2X5", .patch = patch_stac92hd71bxx}, + { .id = 0x111d7674, .name = "92HD73D1X5", .patch = patch_stac92hd73xx }, + { .id = 0x111d7675, .name = "92HD73C1X5", .patch = patch_stac92hd73xx }, + { .id = 0x111d7676, .name = "92HD73E1X5", .patch = patch_stac92hd73xx }, + { .id = 0x111d76b0, .name = "92HD71B8X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b1, .name = "92HD71B8X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b2, .name = "92HD71B7X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b3, .name = "92HD71B7X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b4, .name = "92HD71B6X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b5, .name = "92HD71B6X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b6, .name = "92HD71B5X", .patch = patch_stac92hd71bxx }, + { .id = 0x111d76b7, .name = "92HD71B5X", .patch = patch_stac92hd71bxx }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c new file mode 100644 index 0000000..63e4871 --- /dev/null +++ b/sound/pci/hda/patch_via.c @@ -0,0 +1,3335 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for VIA VT1702/VT1708/VT1709 codec + * + * Copyright (c) 2006-2008 Lydia Wang <lydiawang@viatech.com> + * 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 + */ + +/* * * * * * * * * * * * * * Release History * * * * * * * * * * * * * * * * */ +/* */ +/* 2006-03-03 Lydia Wang Create the basic patch to support VT1708 codec */ +/* 2006-03-14 Lydia Wang Modify hard code for some pin widget nid */ +/* 2006-08-02 Lydia Wang Add support to VT1709 codec */ +/* 2006-09-08 Lydia Wang Fix internal loopback recording source select bug */ +/* 2007-09-12 Lydia Wang Add EAPD enable during driver initialization */ +/* 2007-09-17 Lydia Wang Add VT1708B codec support */ +/* 2007-11-14 Lydia Wang Add VT1708A codec HP and CD pin connect config */ +/* 2008-02-03 Lydia Wang Fix Rear channels and Back channels inverse issue */ +/* 2008-03-06 Lydia Wang Add VT1702 codec and VT1708S codec support */ +/* 2008-04-09 Lydia Wang Add mute front speaker when HP plugin */ +/* 2008-04-09 Lydia Wang Add Independent HP feature */ +/* 2008-05-28 Lydia Wang Add second S/PDIF Out support for VT1702 */ +/* 2008-09-15 Logan Li Add VT1708S Mic Boost workaround/backdoor */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/asoundef.h> +#include "hda_codec.h" +#include "hda_local.h" +#include "hda_patch.h" + +/* amp values */ +#define AMP_VAL_IDX_SHIFT 19 +#define AMP_VAL_IDX_MASK (0x0f<<19) + +#define NUM_CONTROL_ALLOC 32 +#define NUM_VERB_ALLOC 32 + +/* Pin Widget NID */ +#define VT1708_HP_NID 0x13 +#define VT1708_DIGOUT_NID 0x14 +#define VT1708_DIGIN_NID 0x16 +#define VT1708_DIGIN_PIN 0x26 +#define VT1708_HP_PIN_NID 0x20 +#define VT1708_CD_PIN_NID 0x24 + +#define VT1709_HP_DAC_NID 0x28 +#define VT1709_DIGOUT_NID 0x13 +#define VT1709_DIGIN_NID 0x17 +#define VT1709_DIGIN_PIN 0x25 + +#define VT1708B_HP_NID 0x25 +#define VT1708B_DIGOUT_NID 0x12 +#define VT1708B_DIGIN_NID 0x15 +#define VT1708B_DIGIN_PIN 0x21 + +#define VT1708S_HP_NID 0x25 +#define VT1708S_DIGOUT_NID 0x12 + +#define VT1702_HP_NID 0x17 +#define VT1702_DIGOUT_NID 0x11 + +#define IS_VT1708_VENDORID(x) ((x) >= 0x11061708 && (x) <= 0x1106170b) +#define IS_VT1709_10CH_VENDORID(x) ((x) >= 0x1106e710 && (x) <= 0x1106e713) +#define IS_VT1709_6CH_VENDORID(x) ((x) >= 0x1106e714 && (x) <= 0x1106e717) +#define IS_VT1708B_8CH_VENDORID(x) ((x) >= 0x1106e720 && (x) <= 0x1106e723) +#define IS_VT1708B_4CH_VENDORID(x) ((x) >= 0x1106e724 && (x) <= 0x1106e727) +#define IS_VT1708S_VENDORID(x) ((x) >= 0x11060397 && (x) <= 0x11067397) +#define IS_VT1702_VENDORID(x) ((x) >= 0x11060398 && (x) <= 0x11067398) + +enum VIA_HDA_CODEC { + UNKNOWN = -1, + VT1708, + VT1709_10CH, + VT1709_6CH, + VT1708B_8CH, + VT1708B_4CH, + VT1708S, + VT1702, + CODEC_TYPES, +}; + +static enum VIA_HDA_CODEC get_codec_type(u32 vendor_id) +{ + u16 ven_id = vendor_id >> 16; + u16 dev_id = vendor_id & 0xffff; + enum VIA_HDA_CODEC codec_type; + + /* get codec type */ + if (ven_id != 0x1106) + codec_type = UNKNOWN; + else if (dev_id >= 0x1708 && dev_id <= 0x170b) + codec_type = VT1708; + else if (dev_id >= 0xe710 && dev_id <= 0xe713) + codec_type = VT1709_10CH; + else if (dev_id >= 0xe714 && dev_id <= 0xe717) + codec_type = VT1709_6CH; + else if (dev_id >= 0xe720 && dev_id <= 0xe723) + codec_type = VT1708B_8CH; + else if (dev_id >= 0xe724 && dev_id <= 0xe727) + codec_type = VT1708B_4CH; + else if ((dev_id & 0xfff) == 0x397 + && (dev_id >> 12) < 8) + codec_type = VT1708S; + else if ((dev_id & 0xfff) == 0x398 + && (dev_id >> 12) < 8) + codec_type = VT1702; + else + codec_type = UNKNOWN; + return codec_type; +}; + +#define VIA_HP_EVENT 0x01 +#define VIA_GPIO_EVENT 0x02 + +enum { + VIA_CTL_WIDGET_VOL, + VIA_CTL_WIDGET_MUTE, +}; + +enum { + AUTO_SEQ_FRONT = 0, + AUTO_SEQ_SURROUND, + AUTO_SEQ_CENLFE, + AUTO_SEQ_SIDE +}; + +#define get_amp_nid(kc) ((kc)->private_value & 0xffff) + +/* Some VT1708S based boards gets the micboost setting wrong, so we have + * to apply some brute-force and re-write the TLV's by software. */ +static int mic_boost_tlv(struct snd_kcontrol *kcontrol, int op_flag, + unsigned int size, unsigned int __user *_tlv) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + + if (get_codec_type(codec->vendor_id) == VT1708S + && (nid == 0x1a || nid == 0x1e)) { + if (size < 4 * sizeof(unsigned int)) + return -ENOMEM; + if (put_user(1, _tlv)) /* SNDRV_CTL_TLVT_DB_SCALE */ + return -EFAULT; + if (put_user(2 * sizeof(unsigned int), _tlv + 1)) + return -EFAULT; + if (put_user(0, _tlv + 2)) /* offset = 0 */ + return -EFAULT; + if (put_user(1000, _tlv + 3)) /* step size = 10 dB */ + return -EFAULT; + } + return 0; +} + +static int mic_boost_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + hda_nid_t nid = get_amp_nid(kcontrol); + + if (get_codec_type(codec->vendor_id) == VT1708S + && (nid == 0x1a || nid == 0x1e)) { + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 3; + } + return 0; +} + +static struct snd_kcontrol_new vt1708_control_templates[] = { + HDA_CODEC_VOLUME(NULL, 0, 0, 0), + HDA_CODEC_MUTE(NULL, 0, 0, 0), +}; + + +struct via_spec { + /* codec parameterization */ + struct snd_kcontrol_new *mixers[3]; + unsigned int num_mixers; + + struct hda_verb *init_verbs[5]; + unsigned int num_iverbs; + + 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; + hda_nid_t extra_dig_out_nid; + + /* 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]; + + /* PCM information */ + struct hda_pcm pcm_rec[3]; + + /* dynamic controls, init_verbs and input_mux */ + struct auto_pin_cfg autocfg; + unsigned int num_kctl_alloc, num_kctl_used; + struct snd_kcontrol_new *kctl_alloc; + struct hda_input_mux private_imux[2]; + hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS]; + + /* HP mode source */ + const struct hda_input_mux *hp_mux; + unsigned int hp_independent_mode; + +#ifdef CONFIG_SND_HDA_POWER_SAVE + struct hda_loopback_check loopback; +#endif +}; + +static hda_nid_t vt1708_adc_nids[2] = { + /* ADC1-2 */ + 0x15, 0x27 +}; + +static hda_nid_t vt1709_adc_nids[3] = { + /* ADC1-2 */ + 0x14, 0x15, 0x16 +}; + +static hda_nid_t vt1708B_adc_nids[2] = { + /* ADC1-2 */ + 0x13, 0x14 +}; + +static hda_nid_t vt1708S_adc_nids[2] = { + /* ADC1-2 */ + 0x13, 0x14 +}; + +static hda_nid_t vt1702_adc_nids[3] = { + /* ADC1-2 */ + 0x12, 0x20, 0x1F +}; + +/* add dynamic controls */ +static int via_add_control(struct via_spec *spec, int type, const char *name, + unsigned long val) +{ + struct snd_kcontrol_new *knew; + + if (spec->num_kctl_used >= spec->num_kctl_alloc) { + int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC; + + /* array + terminator */ + knew = kcalloc(num + 1, sizeof(*knew), GFP_KERNEL); + if (!knew) + return -ENOMEM; + if (spec->kctl_alloc) { + memcpy(knew, spec->kctl_alloc, + sizeof(*knew) * spec->num_kctl_alloc); + kfree(spec->kctl_alloc); + } + spec->kctl_alloc = knew; + spec->num_kctl_alloc = num; + } + + knew = &spec->kctl_alloc[spec->num_kctl_used]; + *knew = vt1708_control_templates[type]; + knew->name = kstrdup(name, GFP_KERNEL); + + if (!knew->name) + return -ENOMEM; + knew->private_value = val; + spec->num_kctl_used++; + return 0; +} + +/* create input playback/capture controls for the given pin */ +static int via_new_analog_input(struct via_spec *spec, hda_nid_t pin, + const char *ctlname, int idx, int mix_nid) +{ + char name[32]; + int err; + + sprintf(name, "%s Playback Volume", ctlname); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", ctlname); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(mix_nid, 3, idx, HDA_INPUT)); + if (err < 0) + return err; + return 0; +} + +static void via_auto_set_output_and_unmute(struct hda_codec *codec, + hda_nid_t nid, int pin_type, + int dac_idx) +{ + /* set as output */ + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, + pin_type); + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + AMP_OUT_UNMUTE); +} + + +static void via_auto_init_multi_out(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i; + + for (i = 0; i <= AUTO_SEQ_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + if (nid) + via_auto_set_output_and_unmute(codec, nid, PIN_OUT, i); + } +} + +static void via_auto_init_hp_out(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + hda_nid_t pin; + + pin = spec->autocfg.hp_pins[0]; + if (pin) /* connect to front */ + via_auto_set_output_and_unmute(codec, pin, PIN_HP, 0); +} + +static void via_auto_init_analog_input(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + hda_nid_t nid = spec->autocfg.input_pins[i]; + + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + (i <= AUTO_PIN_FRONT_MIC ? + PIN_VREF50 : PIN_IN)); + + } +} +/* + * input MUX handling + */ +static int via_mux_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->input_mux, uinfo); +} + +static int via_mux_enum_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_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 via_mux_enum_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id); + unsigned int vendor_id = codec->vendor_id; + + /* AIW0 lydia 060801 add for correct sw0 input select */ + if (IS_VT1708_VENDORID(vendor_id) && (adc_idx == 0)) + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + 0x18, &spec->cur_mux[adc_idx]); + else if ((IS_VT1709_10CH_VENDORID(vendor_id) || + IS_VT1709_6CH_VENDORID(vendor_id)) && (adc_idx == 0)) + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + 0x19, &spec->cur_mux[adc_idx]); + else if ((IS_VT1708B_8CH_VENDORID(vendor_id) || + IS_VT1708B_4CH_VENDORID(vendor_id)) && (adc_idx == 0)) + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + 0x17, &spec->cur_mux[adc_idx]); + else if (IS_VT1702_VENDORID(vendor_id) && (adc_idx == 0)) + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + 0x13, &spec->cur_mux[adc_idx]); + else + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + spec->adc_nids[adc_idx], + &spec->cur_mux[adc_idx]); +} + +static int via_independent_hp_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + return snd_hda_input_mux_info(spec->hp_mux, uinfo); +} + +static int via_independent_hp_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + hda_nid_t nid = spec->autocfg.hp_pins[0]; + unsigned int pinsel = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_SEL, + 0x00); + + ucontrol->value.enumerated.item[0] = pinsel; + + return 0; +} + +static int via_independent_hp_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct hda_codec *codec = snd_kcontrol_chip(kcontrol); + struct via_spec *spec = codec->spec; + hda_nid_t nid = spec->autocfg.hp_pins[0]; + unsigned int pinsel = ucontrol->value.enumerated.item[0]; + unsigned int con_nid = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONNECT_LIST, 0) & 0xff; + + if (con_nid == spec->multiout.hp_nid) { + if (pinsel == 0) { + if (!spec->hp_independent_mode) { + if (spec->multiout.num_dacs > 1) + spec->multiout.num_dacs -= 1; + spec->hp_independent_mode = 1; + } + } else if (pinsel == 1) { + if (spec->hp_independent_mode) { + if (spec->multiout.num_dacs > 1) + spec->multiout.num_dacs += 1; + spec->hp_independent_mode = 0; + } + } + } else { + if (pinsel == 0) { + if (spec->hp_independent_mode) { + if (spec->multiout.num_dacs > 1) + spec->multiout.num_dacs += 1; + spec->hp_independent_mode = 0; + } + } else if (pinsel == 1) { + if (!spec->hp_independent_mode) { + if (spec->multiout.num_dacs > 1) + spec->multiout.num_dacs -= 1; + spec->hp_independent_mode = 1; + } + } + } + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, + pinsel); + + if (spec->multiout.hp_nid && + spec->multiout.hp_nid != spec->multiout.dac_nids[HDA_FRONT]) + snd_hda_codec_setup_stream(codec, + spec->multiout.hp_nid, + 0, 0, 0); + + return 0; +} + +static struct snd_kcontrol_new via_hp_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Independent HP", + .count = 1, + .info = via_independent_hp_info, + .get = via_independent_hp_get, + .put = via_independent_hp_put, + }, + { } /* end */ +}; + +/* capture mixer elements */ +static struct snd_kcontrol_new vt1708_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x27, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x27, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = via_mux_enum_info, + .get = via_mux_enum_get, + .put = via_mux_enum_put, + }, + { } /* end */ +}; +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb vt1708_volume_init_verbs[] = { + /* + * Unmute ADC0-1 and set the default input to mic-in + */ + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output mixers (0x19 - 0x1b) + */ + /* set vol=0 to output mixers */ + {0x19, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Setup default input to PW4 */ + {0x20, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* PW9 Output enable */ + {0x25, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + { } +}; + +static int via_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream, + hinfo); +} + +static int via_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, + stream_tag, format, substream); +} + +static int via_playback_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout); +} + + +static void playback_multi_pcm_prep_0(struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + struct hda_multi_out *mout = &spec->multiout; + hda_nid_t *nids = mout->dac_nids; + int chs = substream->runtime->channels; + int i; + + mutex_lock(&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; + /* turn off SPDIF once; otherwise the IEC958 bits won't + * be updated */ + if (codec->spdif_ctls & AC_DIG1_ENABLE) + snd_hda_codec_write(codec, mout->dig_out_nid, 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & + ~AC_DIG1_ENABLE & 0xff); + snd_hda_codec_setup_stream(codec, mout->dig_out_nid, + stream_tag, 0, format); + /* turn on again (if needed) */ + if (codec->spdif_ctls & AC_DIG1_ENABLE) + snd_hda_codec_write(codec, mout->dig_out_nid, 0, + AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & 0xff); + } else { + mout->dig_out_used = 0; + snd_hda_codec_setup_stream(codec, mout->dig_out_nid, + 0, 0, 0); + } + } + mutex_unlock(&codec->spdif_mutex); + + /* front */ + snd_hda_codec_setup_stream(codec, nids[HDA_FRONT], stream_tag, + 0, format); + + if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT] && + !spec->hp_independent_mode) + /* headphone out will just decode front left/right (stereo) */ + snd_hda_codec_setup_stream(codec, mout->hp_nid, stream_tag, + 0, format); + + /* extra outputs copied from front */ + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) + if (mout->extra_out_nid[i]) + snd_hda_codec_setup_stream(codec, + mout->extra_out_nid[i], + stream_tag, 0, format); + + /* surrounds */ + for (i = 1; i < mout->num_dacs; i++) { + if (chs >= (i + 1) * 2) /* independent out */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + i * 2, format); + else /* copy front */ + snd_hda_codec_setup_stream(codec, nids[i], stream_tag, + 0, format); + } +} + +static int via_playback_multi_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + struct hda_multi_out *mout = &spec->multiout; + hda_nid_t *nids = mout->dac_nids; + + if (substream->number == 0) + playback_multi_pcm_prep_0(codec, stream_tag, format, + substream); + else { + if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT] && + spec->hp_independent_mode) + snd_hda_codec_setup_stream(codec, mout->hp_nid, + stream_tag, 0, format); + } + + return 0; +} + +static int via_playback_multi_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + struct hda_multi_out *mout = &spec->multiout; + hda_nid_t *nids = mout->dac_nids; + int i; + + if (substream->number == 0) { + for (i = 0; i < mout->num_dacs; i++) + snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0); + + if (mout->hp_nid && !spec->hp_independent_mode) + snd_hda_codec_setup_stream(codec, mout->hp_nid, + 0, 0, 0); + + for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++) + if (mout->extra_out_nid[i]) + snd_hda_codec_setup_stream(codec, + mout->extra_out_nid[i], + 0, 0, 0); + mutex_lock(&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; + } + mutex_unlock(&codec->spdif_mutex); + } else { + if (mout->hp_nid && mout->hp_nid != nids[HDA_FRONT] && + spec->hp_independent_mode) + snd_hda_codec_setup_stream(codec, mout->hp_nid, + 0, 0, 0); + } + + return 0; +} + +/* + * Digital out + */ +static int via_dig_playback_pcm_open(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + return snd_hda_multi_out_dig_open(codec, &spec->multiout); +} + +static int via_dig_playback_pcm_close(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + return snd_hda_multi_out_dig_close(codec, &spec->multiout); +} + +/* setup SPDIF output stream */ +static void setup_dig_playback_stream(struct hda_codec *codec, hda_nid_t nid, + unsigned int stream_tag, unsigned int format) +{ + /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */ + if (codec->spdif_ctls & AC_DIG1_ENABLE) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff); + snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format); + /* turn on again (if needed) */ + if (codec->spdif_ctls & AC_DIG1_ENABLE) + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, + codec->spdif_ctls & 0xff); +} + +static int via_dig_playback_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + hda_nid_t nid; + + /* 1st or 2nd S/PDIF */ + if (substream->number == 0) + nid = spec->multiout.dig_out_nid; + else if (substream->number == 1) + nid = spec->extra_dig_out_nid; + else + return -1; + + mutex_lock(&codec->spdif_mutex); + setup_dig_playback_stream(codec, nid, stream_tag, format); + mutex_unlock(&codec->spdif_mutex); + return 0; +} + +/* + * Analog capture + */ +static int via_capture_pcm_prepare(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + unsigned int stream_tag, + unsigned int format, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + + snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], + stream_tag, 0, format); + return 0; +} + +static int via_capture_pcm_cleanup(struct hda_pcm_stream *hinfo, + struct hda_codec *codec, + struct snd_pcm_substream *substream) +{ + struct via_spec *spec = codec->spec; + snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]); + return 0; +} + +static struct hda_pcm_stream vt1708_pcm_analog_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 8, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_multi_pcm_prepare, + .cleanup = via_playback_multi_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708_pcm_analog_s16_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 8, + .nid = 0x10, /* NID to query formats and rates */ + /* We got noisy outputs on the right channel on VT1708 when + * 24bit samples are used. Until any workaround is found, + * disable the 24bit format, so far. + */ + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_pcm_prepare, + .cleanup = via_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708_pcm_analog_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x15, /* NID to query formats and rates */ + .ops = { + .prepare = via_capture_pcm_prepare, + .cleanup = via_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in via_build_pcms */ + .ops = { + .open = via_dig_playback_pcm_open, + .close = via_dig_playback_pcm_close, + .prepare = via_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream vt1708_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +static int via_build_controls(struct hda_codec *codec) +{ + struct via_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; + err = snd_hda_create_spdif_share_sw(codec, + &spec->multiout); + if (err < 0) + return err; + spec->multiout.share_spdif = 1; + + if (spec->extra_dig_out_nid) { + err = snd_hda_create_spdif_out_ctls(codec, + spec->extra_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 via_build_pcms(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + struct hda_pcm *info = spec->pcm_rec; + + 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_PLAYBACK].nid = spec->multiout.dac_nids[0]; + info->stream[SNDRV_PCM_STREAM_CAPTURE] = *(spec->stream_analog_capture); + info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->adc_nids[0]; + + info->stream[SNDRV_PCM_STREAM_PLAYBACK].channels_max = + spec->multiout.max_channels; + + if (spec->multiout.dig_out_nid || spec->dig_in_nid) { + codec->num_pcms++; + info++; + info->name = spec->stream_name_digital; + info->pcm_type = HDA_PCM_TYPE_SPDIF; + 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 via_free(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + unsigned int i; + + if (!spec) + return; + + if (spec->kctl_alloc) { + for (i = 0; i < spec->num_kctl_used; i++) + kfree(spec->kctl_alloc[i].name); + kfree(spec->kctl_alloc); + } + + kfree(codec->spec); +} + +/* mute internal speaker if HP is plugged */ +static void via_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + struct via_spec *spec = codec->spec; + + present = snd_hda_codec_read(codec, spec->autocfg.hp_pins[0], 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, spec->autocfg.line_out_pins[0], + HDA_OUTPUT, 0, HDA_AMP_MUTE, + present ? HDA_AMP_MUTE : 0); +} + +static void via_gpio_control(struct hda_codec *codec) +{ + unsigned int gpio_data; + unsigned int vol_counter; + unsigned int vol; + unsigned int master_vol; + + struct via_spec *spec = codec->spec; + + gpio_data = snd_hda_codec_read(codec, codec->afg, 0, + AC_VERB_GET_GPIO_DATA, 0) & 0x03; + + vol_counter = (snd_hda_codec_read(codec, codec->afg, 0, + 0xF84, 0) & 0x3F0000) >> 16; + + vol = vol_counter & 0x1F; + master_vol = snd_hda_codec_read(codec, 0x1A, 0, + AC_VERB_GET_AMP_GAIN_MUTE, + AC_AMP_GET_INPUT); + + if (gpio_data == 0x02) { + /* unmute line out */ + snd_hda_codec_amp_stereo(codec, spec->autocfg.line_out_pins[0], + HDA_OUTPUT, 0, HDA_AMP_MUTE, 0); + + if (vol_counter & 0x20) { + /* decrease volume */ + if (vol > master_vol) + vol = master_vol; + snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT, + 0, HDA_AMP_VOLMASK, + master_vol-vol); + } else { + /* increase volume */ + snd_hda_codec_amp_stereo(codec, 0x1A, HDA_INPUT, 0, + HDA_AMP_VOLMASK, + ((master_vol+vol) > 0x2A) ? 0x2A : + (master_vol+vol)); + } + } else if (!(gpio_data & 0x02)) { + /* mute line out */ + snd_hda_codec_amp_stereo(codec, + spec->autocfg.line_out_pins[0], + HDA_OUTPUT, 0, HDA_AMP_MUTE, + HDA_AMP_MUTE); + } +} + +/* unsolicited event for jack sensing */ +static void via_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + if (res == VIA_HP_EVENT) + via_hp_automute(codec); + else if (res == VIA_GPIO_EVENT) + via_gpio_control(codec); +} + +static hda_nid_t slave_dig_outs[] = { + 0, +}; + +static int via_init(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i; + for (i = 0; i < spec->num_iverbs; i++) + snd_hda_sequence_write(codec, spec->init_verbs[i]); + + /* Lydia Add for EAPD enable */ + if (!spec->dig_in_nid) { /* No Digital In connection */ + if (IS_VT1708_VENDORID(codec->vendor_id)) { + snd_hda_codec_write(codec, VT1708_DIGIN_PIN, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_OUT); + snd_hda_codec_write(codec, VT1708_DIGIN_PIN, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + } else if (IS_VT1709_10CH_VENDORID(codec->vendor_id) || + IS_VT1709_6CH_VENDORID(codec->vendor_id)) { + snd_hda_codec_write(codec, VT1709_DIGIN_PIN, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_OUT); + snd_hda_codec_write(codec, VT1709_DIGIN_PIN, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + } else if (IS_VT1708B_8CH_VENDORID(codec->vendor_id) || + IS_VT1708B_4CH_VENDORID(codec->vendor_id)) { + snd_hda_codec_write(codec, VT1708B_DIGIN_PIN, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, + PIN_OUT); + snd_hda_codec_write(codec, VT1708B_DIGIN_PIN, 0, + AC_VERB_SET_EAPD_BTLENABLE, 0x02); + } + } else /* enable SPDIF-input pin */ + snd_hda_codec_write(codec, spec->autocfg.dig_in_pin, 0, + AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN); + + /* no slave outs */ + codec->slave_dig_outs = slave_dig_outs; + + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static int via_check_power_status(struct hda_codec *codec, hda_nid_t nid) +{ + struct via_spec *spec = codec->spec; + return snd_hda_check_amp_list_power(codec, &spec->loopback, nid); +} +#endif + +/* + */ +static struct hda_codec_ops via_patch_ops = { + .build_controls = via_build_controls, + .build_pcms = via_build_pcms, + .init = via_init, + .free = via_free, +#ifdef CONFIG_SND_HDA_POWER_SAVE + .check_power_status = via_check_power_status, +#endif +}; + +/* fill in the dac_nids table from the parsed pin configuration */ +static int vt1708_auto_fill_dac_nids(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int i; + hda_nid_t nid; + + spec->multiout.num_dacs = cfg->line_outs; + + spec->multiout.dac_nids = spec->private_dac_nids; + + for(i = 0; i < 4; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + /* config dac list */ + switch (i) { + case AUTO_SEQ_FRONT: + spec->multiout.dac_nids[i] = 0x10; + break; + case AUTO_SEQ_CENLFE: + spec->multiout.dac_nids[i] = 0x12; + break; + case AUTO_SEQ_SURROUND: + spec->multiout.dac_nids[i] = 0x11; + break; + case AUTO_SEQ_SIDE: + spec->multiout.dac_nids[i] = 0x13; + break; + } + } + } + + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int vt1708_auto_create_multi_out_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" }; + hda_nid_t nid, nid_vol = 0; + int i, err; + + for (i = 0; i <= AUTO_SEQ_SIDE; i++) { + nid = cfg->line_out_pins[i]; + + if (!nid) + continue; + + if (i != AUTO_SEQ_FRONT) + nid_vol = 0x18 + i; + + if (i == AUTO_SEQ_CENLFE) { + /* Center/LFE */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_FRONT){ + /* add control to mixer index 0 */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Master Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x17, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Master Front Playback Switch", + HDA_COMPOSE_AMP_VAL(0x17, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + + /* add control to PW3 */ + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + + return 0; +} + +static void create_hp_imux(struct via_spec *spec) +{ + int i; + struct hda_input_mux *imux = &spec->private_imux[1]; + static const char *texts[] = { "OFF", "ON", NULL}; + + /* for hp mode select */ + i = 0; + while (texts[i] != NULL) { + imux->items[imux->num_items].label = texts[i]; + imux->items[imux->num_items].index = i; + imux->num_items++; + i++; + } + + spec->hp_mux = &spec->private_imux[1]; +} + +static int vt1708_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin) +{ + int err; + + if (!pin) + return 0; + + spec->multiout.hp_nid = VT1708_HP_NID; /* AOW3 */ + + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + create_hp_imux(spec); + + return 0; +} + +/* create playback/capture controls for input pins */ +static int vt1708_auto_create_analog_input_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + static char *labels[] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL + }; + struct hda_input_mux *imux = &spec->private_imux[0]; + int i, err, idx = 0; + + /* for internal loopback recording select */ + imux->items[imux->num_items].label = "Stereo Mixer"; + imux->items[imux->num_items].index = idx; + imux->num_items++; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (!cfg->input_pins[i]) + continue; + + switch (cfg->input_pins[i]) { + case 0x1d: /* Mic */ + idx = 2; + break; + + case 0x1e: /* Line In */ + idx = 3; + break; + + case 0x21: /* Front Mic */ + idx = 4; + break; + + case 0x24: /* CD */ + idx = 1; + break; + } + err = via_new_analog_input(spec, cfg->input_pins[i], labels[i], + idx, 0x17); + if (err < 0) + return err; + imux->items[imux->num_items].label = labels[i]; + imux->items[imux->num_items].index = idx; + imux->num_items++; + } + return 0; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list vt1708_loopbacks[] = { + { 0x17, HDA_INPUT, 1 }, + { 0x17, HDA_INPUT, 2 }, + { 0x17, HDA_INPUT, 3 }, + { 0x17, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +static void vt1708_set_pinconfig_connect(struct hda_codec *codec, hda_nid_t nid) +{ + unsigned int def_conf; + unsigned char seqassoc; + + def_conf = snd_hda_codec_read(codec, nid, 0, + AC_VERB_GET_CONFIG_DEFAULT, 0); + seqassoc = (unsigned char) get_defcfg_association(def_conf); + seqassoc = (seqassoc << 4) | get_defcfg_sequence(def_conf); + if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE) { + if (seqassoc == 0xff) { + def_conf = def_conf & (~(AC_JACK_PORT_BOTH << 30)); + snd_hda_codec_write(codec, nid, 0, + AC_VERB_SET_CONFIG_DEFAULT_BYTES_3, + def_conf >> 24); + } + } + + return; +} + +static int vt1708_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + + /* Add HP and CD pin config connect bit re-config action */ + vt1708_set_pinconfig_connect(codec, VT1708_HP_PIN_NID); + vt1708_set_pinconfig_connect(codec, VT1708_CD_PIN_NID); + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); + if (err < 0) + return err; + err = vt1708_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0]) + return 0; /* can't find valid BIOS pin config */ + + err = vt1708_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = vt1708_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = vt1708_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = VT1708_DIGOUT_NID; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = VT1708_DIGIN_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->init_verbs[spec->num_iverbs++] = vt1708_volume_init_verbs; + + spec->input_mux = &spec->private_imux[0]; + + if (spec->hp_mux) + spec->mixers[spec->num_mixers++] = via_hp_mixer; + + return 1; +} + +/* init callback for auto-configuration model -- overriding the default init */ +static int via_auto_init(struct hda_codec *codec) +{ + via_init(codec); + via_auto_init_multi_out(codec); + via_auto_init_hp_out(codec); + via_auto_init_analog_input(codec); + return 0; +} + +static int patch_vt1708(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + /* automatic parse from the BIOS config */ + err = vt1708_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration " + "from BIOS. Using genenic mode...\n"); + } + + + spec->stream_name_analog = "VT1708 Analog"; + spec->stream_analog_playback = &vt1708_pcm_analog_playback; + /* disable 32bit format on VT1708 */ + if (codec->vendor_id == 0x11061708) + spec->stream_analog_playback = &vt1708_pcm_analog_s16_playback; + spec->stream_analog_capture = &vt1708_pcm_analog_capture; + + spec->stream_name_digital = "VT1708 Digital"; + spec->stream_digital_playback = &vt1708_pcm_digital_playback; + spec->stream_digital_capture = &vt1708_pcm_digital_capture; + + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1708_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1708_adc_nids); + spec->mixers[spec->num_mixers] = vt1708_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1708_loopbacks; +#endif + + return 0; +} + +/* capture mixer elements */ +static struct snd_kcontrol_new vt1709_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x14, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x15, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x15, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 2, 0x16, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 2, 0x16, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = via_mux_enum_info, + .get = via_mux_enum_get, + .put = via_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_verb vt1709_uniwill_init_verbs[] = { + {0x20, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_HP_EVENT}, + { } +}; + +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb vt1709_10ch_volume_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output selector (0x1a, 0x1b, 0x29) + */ + /* set vol=0 to output mixers */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* + * Unmute PW3 and PW4 + */ + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Set input of PW4 as AOW4 */ + {0x20, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* PW9 Output enable */ + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + { } +}; + +static struct hda_pcm_stream vt1709_10ch_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 10, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_pcm_prepare, + .cleanup = via_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1709_6ch_pcm_analog_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 6, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_pcm_prepare, + .cleanup = via_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1709_pcm_analog_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x14, /* NID to query formats and rates */ + .ops = { + .prepare = via_capture_pcm_prepare, + .cleanup = via_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1709_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in via_build_pcms */ + .ops = { + .open = via_dig_playback_pcm_open, + .close = via_dig_playback_pcm_close + }, +}; + +static struct hda_pcm_stream vt1709_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +static int vt1709_auto_fill_dac_nids(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int i; + hda_nid_t nid; + + if (cfg->line_outs == 4) /* 10 channels */ + spec->multiout.num_dacs = cfg->line_outs+1; /* AOW0~AOW4 */ + else if (cfg->line_outs == 3) /* 6 channels */ + spec->multiout.num_dacs = cfg->line_outs; /* AOW0~AOW2 */ + + spec->multiout.dac_nids = spec->private_dac_nids; + + if (cfg->line_outs == 4) { /* 10 channels */ + for (i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + /* config dac list */ + switch (i) { + case AUTO_SEQ_FRONT: + /* AOW0 */ + spec->multiout.dac_nids[i] = 0x10; + break; + case AUTO_SEQ_CENLFE: + /* AOW2 */ + spec->multiout.dac_nids[i] = 0x12; + break; + case AUTO_SEQ_SURROUND: + /* AOW3 */ + spec->multiout.dac_nids[i] = 0x11; + break; + case AUTO_SEQ_SIDE: + /* AOW1 */ + spec->multiout.dac_nids[i] = 0x27; + break; + default: + break; + } + } + } + spec->multiout.dac_nids[cfg->line_outs] = 0x28; /* AOW4 */ + + } else if (cfg->line_outs == 3) { /* 6 channels */ + for(i = 0; i < cfg->line_outs; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + /* config dac list */ + switch(i) { + case AUTO_SEQ_FRONT: + /* AOW0 */ + spec->multiout.dac_nids[i] = 0x10; + break; + case AUTO_SEQ_CENLFE: + /* AOW2 */ + spec->multiout.dac_nids[i] = 0x12; + break; + case AUTO_SEQ_SURROUND: + /* AOW1 */ + spec->multiout.dac_nids[i] = 0x11; + break; + default: + break; + } + } + } + } + + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int vt1709_auto_create_multi_out_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" }; + hda_nid_t nid = 0; + int i, err; + + for (i = 0; i <= AUTO_SEQ_SIDE; i++) { + nid = cfg->line_out_pins[i]; + + if (!nid) + continue; + + if (i == AUTO_SEQ_CENLFE) { + /* Center/LFE */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(0x1b, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(0x1b, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(0x1b, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(0x1b, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_FRONT){ + /* add control to mixer index 0 */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Master Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x18, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Master Front Playback Switch", + HDA_COMPOSE_AMP_VAL(0x18, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + + /* add control to PW3 */ + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_SURROUND) { + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(0x1a, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_SIDE) { + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(0x29, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(0x29, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + + return 0; +} + +static int vt1709_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin) +{ + int err; + + if (!pin) + return 0; + + if (spec->multiout.num_dacs == 5) /* 10 channels */ + spec->multiout.hp_nid = VT1709_HP_DAC_NID; + else if (spec->multiout.num_dacs == 3) /* 6 channels */ + spec->multiout.hp_nid = 0; + + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + return 0; +} + +/* create playback/capture controls for input pins */ +static int vt1709_auto_create_analog_input_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + static char *labels[] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL + }; + struct hda_input_mux *imux = &spec->private_imux[0]; + int i, err, idx = 0; + + /* for internal loopback recording select */ + imux->items[imux->num_items].label = "Stereo Mixer"; + imux->items[imux->num_items].index = idx; + imux->num_items++; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (!cfg->input_pins[i]) + continue; + + switch (cfg->input_pins[i]) { + case 0x1d: /* Mic */ + idx = 2; + break; + + case 0x1e: /* Line In */ + idx = 3; + break; + + case 0x21: /* Front Mic */ + idx = 4; + break; + + case 0x23: /* CD */ + idx = 1; + break; + } + err = via_new_analog_input(spec, cfg->input_pins[i], labels[i], + idx, 0x18); + if (err < 0) + return err; + imux->items[imux->num_items].label = labels[i]; + imux->items[imux->num_items].index = idx; + imux->num_items++; + } + return 0; +} + +static int vt1709_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); + if (err < 0) + return err; + err = vt1709_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0]) + return 0; /* can't find valid BIOS pin config */ + + err = vt1709_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = vt1709_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = vt1709_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = VT1709_DIGOUT_NID; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = VT1709_DIGIN_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux[0]; + + if (spec->hp_mux) + spec->mixers[spec->num_mixers++] = via_hp_mixer; + + return 1; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list vt1709_loopbacks[] = { + { 0x18, HDA_INPUT, 1 }, + { 0x18, HDA_INPUT, 2 }, + { 0x18, HDA_INPUT, 3 }, + { 0x18, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +static int patch_vt1709_10ch(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + err = vt1709_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration. " + "Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1709_10ch_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1709_uniwill_init_verbs; + + spec->stream_name_analog = "VT1709 Analog"; + spec->stream_analog_playback = &vt1709_10ch_pcm_analog_playback; + spec->stream_analog_capture = &vt1709_pcm_analog_capture; + + spec->stream_name_digital = "VT1709 Digital"; + spec->stream_digital_playback = &vt1709_pcm_digital_playback; + spec->stream_digital_capture = &vt1709_pcm_digital_capture; + + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1709_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1709_adc_nids); + spec->mixers[spec->num_mixers] = vt1709_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1709_loopbacks; +#endif + + return 0; +} +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb vt1709_6ch_volume_init_verbs[] = { + /* + * Unmute ADC0-2 and set the default input to mic-in + */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: AOW0=0, CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output selector (0x1a, 0x1b, 0x29) + */ + /* set vol=0 to output mixers */ + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x29, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* + * Unmute PW3 and PW4 + */ + {0x1f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Set input of PW4 as MW0 */ + {0x20, AC_VERB_SET_CONNECT_SEL, 0}, + /* PW9 Output enable */ + {0x24, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + { } +}; + +static int patch_vt1709_6ch(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + err = vt1709_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration. " + "Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1709_6ch_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1709_uniwill_init_verbs; + + spec->stream_name_analog = "VT1709 Analog"; + spec->stream_analog_playback = &vt1709_6ch_pcm_analog_playback; + spec->stream_analog_capture = &vt1709_pcm_analog_capture; + + spec->stream_name_digital = "VT1709 Digital"; + spec->stream_digital_playback = &vt1709_pcm_digital_playback; + spec->stream_digital_capture = &vt1709_pcm_digital_capture; + + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1709_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1709_adc_nids); + spec->mixers[spec->num_mixers] = vt1709_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1709_loopbacks; +#endif + return 0; +} + +/* capture mixer elements */ +static struct snd_kcontrol_new vt1708B_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x13, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x13, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x14, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x14, 0x0, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = via_mux_enum_info, + .get = via_mux_enum_get, + .put = via_mux_enum_put, + }, + { } /* end */ +}; +/* + * generic initialization of ADC, input mixers and output mixers + */ +static struct hda_verb vt1708B_8ch_volume_init_verbs[] = { + /* + * Unmute ADC0-1 and set the default input to mic-in + */ + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output mixers + */ + /* set vol=0 to output mixers */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Setup default input to PW4 */ + {0x1d, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* PW9 Output enable */ + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* PW10 Input enable */ + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + { } +}; + +static struct hda_verb vt1708B_4ch_volume_init_verbs[] = { + /* + * Unmute ADC0-1 and set the default input to mic-in + */ + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* + * Set up output mixers + */ + /* set vol=0 to output mixers */ + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x26, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x27, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + + /* Setup default input of PW4 to MW0 */ + {0x1d, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* PW9 Output enable */ + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* PW10 Input enable */ + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x20}, + { } +}; + +static struct hda_verb vt1708B_uniwill_init_verbs[] = { + {0x1D, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_HP_EVENT}, + { } +}; + +static struct hda_pcm_stream vt1708B_8ch_pcm_analog_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 8, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_multi_pcm_prepare, + .cleanup = via_playback_multi_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708B_4ch_pcm_analog_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 4, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_multi_pcm_prepare, + .cleanup = via_playback_multi_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708B_pcm_analog_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x13, /* NID to query formats and rates */ + .ops = { + .prepare = via_capture_pcm_prepare, + .cleanup = via_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708B_pcm_digital_playback = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, + /* NID is set in via_build_pcms */ + .ops = { + .open = via_dig_playback_pcm_open, + .close = via_dig_playback_pcm_close, + .prepare = via_dig_playback_pcm_prepare + }, +}; + +static struct hda_pcm_stream vt1708B_pcm_digital_capture = { + .substreams = 1, + .channels_min = 2, + .channels_max = 2, +}; + +/* fill in the dac_nids table from the parsed pin configuration */ +static int vt1708B_auto_fill_dac_nids(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int i; + hda_nid_t nid; + + spec->multiout.num_dacs = cfg->line_outs; + + spec->multiout.dac_nids = spec->private_dac_nids; + + for (i = 0; i < 4; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + /* config dac list */ + switch (i) { + case AUTO_SEQ_FRONT: + spec->multiout.dac_nids[i] = 0x10; + break; + case AUTO_SEQ_CENLFE: + spec->multiout.dac_nids[i] = 0x24; + break; + case AUTO_SEQ_SURROUND: + spec->multiout.dac_nids[i] = 0x11; + break; + case AUTO_SEQ_SIDE: + spec->multiout.dac_nids[i] = 0x25; + break; + } + } + } + + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int vt1708B_auto_create_multi_out_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" }; + hda_nid_t nid_vols[] = {0x16, 0x18, 0x26, 0x27}; + hda_nid_t nid, nid_vol = 0; + int i, err; + + for (i = 0; i <= AUTO_SEQ_SIDE; i++) { + nid = cfg->line_out_pins[i]; + + if (!nid) + continue; + + nid_vol = nid_vols[i]; + + if (i == AUTO_SEQ_CENLFE) { + /* Center/LFE */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_FRONT) { + /* add control to mixer index 0 */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Master Front Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Master Front Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + + /* add control to PW3 */ + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + + return 0; +} + +static int vt1708B_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin) +{ + int err; + + if (!pin) + return 0; + + spec->multiout.hp_nid = VT1708B_HP_NID; /* AOW3 */ + + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + create_hp_imux(spec); + + return 0; +} + +/* create playback/capture controls for input pins */ +static int vt1708B_auto_create_analog_input_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + static char *labels[] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL + }; + struct hda_input_mux *imux = &spec->private_imux[0]; + int i, err, idx = 0; + + /* for internal loopback recording select */ + imux->items[imux->num_items].label = "Stereo Mixer"; + imux->items[imux->num_items].index = idx; + imux->num_items++; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (!cfg->input_pins[i]) + continue; + + switch (cfg->input_pins[i]) { + case 0x1a: /* Mic */ + idx = 2; + break; + + case 0x1b: /* Line In */ + idx = 3; + break; + + case 0x1e: /* Front Mic */ + idx = 4; + break; + + case 0x1f: /* CD */ + idx = 1; + break; + } + err = via_new_analog_input(spec, cfg->input_pins[i], labels[i], + idx, 0x16); + if (err < 0) + return err; + imux->items[imux->num_items].label = labels[i]; + imux->items[imux->num_items].index = idx; + imux->num_items++; + } + return 0; +} + +static int vt1708B_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL); + if (err < 0) + return err; + err = vt1708B_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0]) + return 0; /* can't find valid BIOS pin config */ + + err = vt1708B_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = vt1708B_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = vt1708B_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = VT1708B_DIGOUT_NID; + if (spec->autocfg.dig_in_pin) + spec->dig_in_nid = VT1708B_DIGIN_NID; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux[0]; + + if (spec->hp_mux) + spec->mixers[spec->num_mixers++] = via_hp_mixer; + + return 1; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list vt1708B_loopbacks[] = { + { 0x16, HDA_INPUT, 1 }, + { 0x16, HDA_INPUT, 2 }, + { 0x16, HDA_INPUT, 3 }, + { 0x16, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +static int patch_vt1708B_8ch(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + /* automatic parse from the BIOS config */ + err = vt1708B_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration " + "from BIOS. Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1708B_8ch_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1708B_uniwill_init_verbs; + + spec->stream_name_analog = "VT1708B Analog"; + spec->stream_analog_playback = &vt1708B_8ch_pcm_analog_playback; + spec->stream_analog_capture = &vt1708B_pcm_analog_capture; + + spec->stream_name_digital = "VT1708B Digital"; + spec->stream_digital_playback = &vt1708B_pcm_digital_playback; + spec->stream_digital_capture = &vt1708B_pcm_digital_capture; + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1708B_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1708B_adc_nids); + spec->mixers[spec->num_mixers] = vt1708B_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1708B_loopbacks; +#endif + + return 0; +} + +static int patch_vt1708B_4ch(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + /* automatic parse from the BIOS config */ + err = vt1708B_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration " + "from BIOS. Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1708B_4ch_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1708B_uniwill_init_verbs; + + spec->stream_name_analog = "VT1708B Analog"; + spec->stream_analog_playback = &vt1708B_4ch_pcm_analog_playback; + spec->stream_analog_capture = &vt1708B_pcm_analog_capture; + + spec->stream_name_digital = "VT1708B Digital"; + spec->stream_digital_playback = &vt1708B_pcm_digital_playback; + spec->stream_digital_capture = &vt1708B_pcm_digital_capture; + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1708B_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1708B_adc_nids); + spec->mixers[spec->num_mixers] = vt1708B_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1708B_loopbacks; +#endif + + return 0; +} + +/* Patch for VT1708S */ + +/* VT1708S software backdoor based override for buggy hardware micboost + * setting */ +#define MIC_BOOST_VOLUME(xname, nid) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .index = 0, \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ | \ + SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \ + .info = mic_boost_volume_info, \ + .get = snd_hda_mixer_amp_volume_get, \ + .put = snd_hda_mixer_amp_volume_put, \ + .tlv = { .c = mic_boost_tlv }, \ + .private_value = HDA_COMPOSE_AMP_VAL(nid, 3, 0, HDA_INPUT) } + +/* capture mixer elements */ +static struct snd_kcontrol_new vt1708S_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x13, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x13, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x14, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x14, 0x0, HDA_INPUT), + MIC_BOOST_VOLUME("Mic Boost Capture Volume", 0x1A), + MIC_BOOST_VOLUME("Front Mic Boost Capture Volume", 0x1E), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = via_mux_enum_info, + .get = via_mux_enum_get, + .put = via_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_verb vt1708S_volume_init_verbs[] = { + /* Unmute ADC0-1 and set the default input to mic-in */ + {0x13, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the + * analog-loopback mixer widget */ + /* Amp Indices: CD = 1, Mic1 = 2, Line = 3, Mic2 = 4 */ + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x16, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* Setup default input of PW4 to MW0 */ + {0x1d, AC_VERB_SET_CONNECT_SEL, 0x0}, + /* PW9, PW10 Output enable */ + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x21, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + /* Enable Mic Boost Volume backdoor */ + {0x1, 0xf98, 0x1}, + { } +}; + +static struct hda_verb vt1708S_uniwill_init_verbs[] = { + {0x1D, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_HP_EVENT}, + { } +}; + +static struct hda_pcm_stream vt1708S_pcm_analog_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 8, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_pcm_prepare, + .cleanup = via_playback_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708S_pcm_analog_capture = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x13, /* NID to query formats and rates */ + .ops = { + .prepare = via_capture_pcm_prepare, + .cleanup = via_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1708S_pcm_digital_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + /* NID is set in via_build_pcms */ + .ops = { + .open = via_dig_playback_pcm_open, + .close = via_dig_playback_pcm_close, + .prepare = via_dig_playback_pcm_prepare + }, +}; + +/* fill in the dac_nids table from the parsed pin configuration */ +static int vt1708S_auto_fill_dac_nids(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int i; + hda_nid_t nid; + + spec->multiout.num_dacs = cfg->line_outs; + + spec->multiout.dac_nids = spec->private_dac_nids; + + for (i = 0; i < 4; i++) { + nid = cfg->line_out_pins[i]; + if (nid) { + /* config dac list */ + switch (i) { + case AUTO_SEQ_FRONT: + spec->multiout.dac_nids[i] = 0x10; + break; + case AUTO_SEQ_CENLFE: + spec->multiout.dac_nids[i] = 0x24; + break; + case AUTO_SEQ_SURROUND: + spec->multiout.dac_nids[i] = 0x11; + break; + case AUTO_SEQ_SIDE: + spec->multiout.dac_nids[i] = 0x25; + break; + } + } + } + + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int vt1708S_auto_create_multi_out_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + char name[32]; + static const char *chname[4] = { "Front", "Surround", "C/LFE", "Side" }; + hda_nid_t nid_vols[] = {0x10, 0x11, 0x24, 0x25}; + hda_nid_t nid_mutes[] = {0x1C, 0x18, 0x26, 0x27}; + hda_nid_t nid, nid_vol, nid_mute; + int i, err; + + for (i = 0; i <= AUTO_SEQ_SIDE; i++) { + nid = cfg->line_out_pins[i]; + + if (!nid) + continue; + + nid_vol = nid_vols[i]; + nid_mute = nid_mutes[i]; + + if (i == AUTO_SEQ_CENLFE) { + /* Center/LFE */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_vol, 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_mute, + 1, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_mute, + 2, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else if (i == AUTO_SEQ_FRONT) { + /* add control to mixer index 0 */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Master Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x16, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Master Front Playback Switch", + HDA_COMPOSE_AMP_VAL(0x16, 3, 0, + HDA_INPUT)); + if (err < 0) + return err; + + /* Front */ + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_mute, + 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_vol, 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_mute, + 3, 0, + HDA_OUTPUT)); + if (err < 0) + return err; + } + } + + return 0; +} + +static int vt1708S_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin) +{ + int err; + + if (!pin) + return 0; + + spec->multiout.hp_nid = VT1708S_HP_NID; /* AOW3 */ + + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(0x25, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + create_hp_imux(spec); + + return 0; +} + +/* create playback/capture controls for input pins */ +static int vt1708S_auto_create_analog_input_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + static char *labels[] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL + }; + struct hda_input_mux *imux = &spec->private_imux[0]; + int i, err, idx = 0; + + /* for internal loopback recording select */ + imux->items[imux->num_items].label = "Stereo Mixer"; + imux->items[imux->num_items].index = 5; + imux->num_items++; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (!cfg->input_pins[i]) + continue; + + switch (cfg->input_pins[i]) { + case 0x1a: /* Mic */ + idx = 2; + break; + + case 0x1b: /* Line In */ + idx = 3; + break; + + case 0x1e: /* Front Mic */ + idx = 4; + break; + + case 0x1f: /* CD */ + idx = 1; + break; + } + err = via_new_analog_input(spec, cfg->input_pins[i], labels[i], + idx, 0x16); + if (err < 0) + return err; + imux->items[imux->num_items].label = labels[i]; + imux->items[imux->num_items].index = idx-1; + imux->num_items++; + } + return 0; +} + +static int vt1708S_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + static hda_nid_t vt1708s_ignore[] = {0x21, 0}; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + vt1708s_ignore); + if (err < 0) + return err; + err = vt1708S_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0]) + return 0; /* can't find valid BIOS pin config */ + + err = vt1708S_auto_create_multi_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = vt1708S_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = vt1708S_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = VT1708S_DIGOUT_NID; + + spec->extra_dig_out_nid = 0x15; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux[0]; + + if (spec->hp_mux) + spec->mixers[spec->num_mixers++] = via_hp_mixer; + + return 1; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list vt1708S_loopbacks[] = { + { 0x16, HDA_INPUT, 1 }, + { 0x16, HDA_INPUT, 2 }, + { 0x16, HDA_INPUT, 3 }, + { 0x16, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +static int patch_vt1708S(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + /* automatic parse from the BIOS config */ + err = vt1708S_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration " + "from BIOS. Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1708S_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1708S_uniwill_init_verbs; + + spec->stream_name_analog = "VT1708S Analog"; + spec->stream_analog_playback = &vt1708S_pcm_analog_playback; + spec->stream_analog_capture = &vt1708S_pcm_analog_capture; + + spec->stream_name_digital = "VT1708S Digital"; + spec->stream_digital_playback = &vt1708S_pcm_digital_playback; + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1708S_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1708S_adc_nids); + spec->mixers[spec->num_mixers] = vt1708S_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1708S_loopbacks; +#endif + + return 0; +} + +/* Patch for VT1702 */ + +/* capture mixer elements */ +static struct snd_kcontrol_new vt1702_capture_mixer[] = { + HDA_CODEC_VOLUME("Capture Volume", 0x12, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x12, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME_IDX("Capture Volume", 1, 0x20, 0x0, HDA_INPUT), + HDA_CODEC_MUTE_IDX("Capture Switch", 1, 0x20, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Digital Mic Capture Volume", 0x1F, 0x0, HDA_INPUT), + HDA_CODEC_MUTE("Digital Mic Capture Switch", 0x1F, 0x0, HDA_INPUT), + HDA_CODEC_VOLUME("Digital Mic Boost Capture Volume", 0x1E, 0x0, + HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + /* The multiple "Capture Source" controls confuse alsamixer + * So call somewhat different.. + */ + /* .name = "Capture Source", */ + .name = "Input Source", + .count = 1, + .info = via_mux_enum_info, + .get = via_mux_enum_get, + .put = via_mux_enum_put, + }, + { } /* end */ +}; + +static struct hda_verb vt1702_volume_init_verbs[] = { + /* + * Unmute ADC0-1 and set the default input to mic-in + */ + {0x12, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1F, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x20, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + + + /* Unmute input amps (CD, Line In, Mic 1 & Mic 2) of the analog-loopback + * mixer widget + */ + /* Amp Indices: Mic1 = 1, Line = 1, Mic2 = 3 */ + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x1A, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + + /* Setup default input of PW4 to MW0 */ + {0x17, AC_VERB_SET_CONNECT_SEL, 0x1}, + /* PW6 PW7 Output enable */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + {0x1C, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x40}, + { } +}; + +static struct hda_verb vt1702_uniwill_init_verbs[] = { + {0x01, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_GPIO_EVENT}, + {0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | VIA_HP_EVENT}, + { } +}; + +static struct hda_pcm_stream vt1702_pcm_analog_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + .nid = 0x10, /* NID to query formats and rates */ + .ops = { + .open = via_playback_pcm_open, + .prepare = via_playback_multi_pcm_prepare, + .cleanup = via_playback_multi_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1702_pcm_analog_capture = { + .substreams = 3, + .channels_min = 2, + .channels_max = 2, + .nid = 0x12, /* NID to query formats and rates */ + .ops = { + .prepare = via_capture_pcm_prepare, + .cleanup = via_capture_pcm_cleanup + }, +}; + +static struct hda_pcm_stream vt1702_pcm_digital_playback = { + .substreams = 2, + .channels_min = 2, + .channels_max = 2, + /* NID is set in via_build_pcms */ + .ops = { + .open = via_dig_playback_pcm_open, + .close = via_dig_playback_pcm_close, + .prepare = via_dig_playback_pcm_prepare + }, +}; + +/* fill in the dac_nids table from the parsed pin configuration */ +static int vt1702_auto_fill_dac_nids(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + spec->multiout.num_dacs = 1; + spec->multiout.dac_nids = spec->private_dac_nids; + + if (cfg->line_out_pins[0]) { + /* config dac list */ + spec->multiout.dac_nids[0] = 0x10; + } + + return 0; +} + +/* add playback controls from the parsed DAC table */ +static int vt1702_auto_create_line_out_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + int err; + + if (!cfg->line_out_pins[0]) + return -1; + + /* add control to mixer index 0 */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Master Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x1A, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Master Front Playback Switch", + HDA_COMPOSE_AMP_VAL(0x1A, 3, 0, HDA_INPUT)); + if (err < 0) + return err; + + /* Front */ + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Front Playback Volume", + HDA_COMPOSE_AMP_VAL(0x10, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Front Playback Switch", + HDA_COMPOSE_AMP_VAL(0x16, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + return 0; +} + +static int vt1702_auto_create_hp_ctls(struct via_spec *spec, hda_nid_t pin) +{ + int err; + + if (!pin) + return 0; + + spec->multiout.hp_nid = 0x1D; + + err = via_add_control(spec, VIA_CTL_WIDGET_VOL, + "Headphone Playback Volume", + HDA_COMPOSE_AMP_VAL(0x1D, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + err = via_add_control(spec, VIA_CTL_WIDGET_MUTE, + "Headphone Playback Switch", + HDA_COMPOSE_AMP_VAL(pin, 3, 0, HDA_OUTPUT)); + if (err < 0) + return err; + + create_hp_imux(spec); + + return 0; +} + +/* create playback/capture controls for input pins */ +static int vt1702_auto_create_analog_input_ctls(struct via_spec *spec, + const struct auto_pin_cfg *cfg) +{ + static char *labels[] = { + "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux", NULL + }; + struct hda_input_mux *imux = &spec->private_imux[0]; + int i, err, idx = 0; + + /* for internal loopback recording select */ + imux->items[imux->num_items].label = "Stereo Mixer"; + imux->items[imux->num_items].index = 3; + imux->num_items++; + + for (i = 0; i < AUTO_PIN_LAST; i++) { + if (!cfg->input_pins[i]) + continue; + + switch (cfg->input_pins[i]) { + case 0x14: /* Mic */ + idx = 1; + break; + + case 0x15: /* Line In */ + idx = 2; + break; + + case 0x18: /* Front Mic */ + idx = 3; + break; + } + err = via_new_analog_input(spec, cfg->input_pins[i], + labels[i], idx, 0x1A); + if (err < 0) + return err; + imux->items[imux->num_items].label = labels[i]; + imux->items[imux->num_items].index = idx-1; + imux->num_items++; + } + return 0; +} + +static int vt1702_parse_auto_config(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int err; + static hda_nid_t vt1702_ignore[] = {0x1C, 0}; + + err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + vt1702_ignore); + if (err < 0) + return err; + err = vt1702_auto_fill_dac_nids(spec, &spec->autocfg); + if (err < 0) + return err; + if (!spec->autocfg.line_outs && !spec->autocfg.hp_pins[0]) + return 0; /* can't find valid BIOS pin config */ + + err = vt1702_auto_create_line_out_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + err = vt1702_auto_create_hp_ctls(spec, spec->autocfg.hp_pins[0]); + if (err < 0) + return err; + err = vt1702_auto_create_analog_input_ctls(spec, &spec->autocfg); + if (err < 0) + return err; + + spec->multiout.max_channels = spec->multiout.num_dacs * 2; + + if (spec->autocfg.dig_out_pin) + spec->multiout.dig_out_nid = VT1702_DIGOUT_NID; + + spec->extra_dig_out_nid = 0x1B; + + if (spec->kctl_alloc) + spec->mixers[spec->num_mixers++] = spec->kctl_alloc; + + spec->input_mux = &spec->private_imux[0]; + + if (spec->hp_mux) + spec->mixers[spec->num_mixers++] = via_hp_mixer; + + return 1; +} + +#ifdef CONFIG_SND_HDA_POWER_SAVE +static struct hda_amp_list vt1702_loopbacks[] = { + { 0x1A, HDA_INPUT, 1 }, + { 0x1A, HDA_INPUT, 2 }, + { 0x1A, HDA_INPUT, 3 }, + { 0x1A, HDA_INPUT, 4 }, + { } /* end */ +}; +#endif + +static int patch_vt1702(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + unsigned int response; + unsigned char control; + + /* create a codec specific record */ + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (spec == NULL) + return -ENOMEM; + + codec->spec = spec; + + /* automatic parse from the BIOS config */ + err = vt1702_parse_auto_config(codec); + if (err < 0) { + via_free(codec); + return err; + } else if (!err) { + printk(KERN_INFO "hda_codec: Cannot set up configuration " + "from BIOS. Using genenic mode...\n"); + } + + spec->init_verbs[spec->num_iverbs++] = vt1702_volume_init_verbs; + spec->init_verbs[spec->num_iverbs++] = vt1702_uniwill_init_verbs; + + spec->stream_name_analog = "VT1702 Analog"; + spec->stream_analog_playback = &vt1702_pcm_analog_playback; + spec->stream_analog_capture = &vt1702_pcm_analog_capture; + + spec->stream_name_digital = "VT1702 Digital"; + spec->stream_digital_playback = &vt1702_pcm_digital_playback; + + if (!spec->adc_nids && spec->input_mux) { + spec->adc_nids = vt1702_adc_nids; + spec->num_adc_nids = ARRAY_SIZE(vt1702_adc_nids); + spec->mixers[spec->num_mixers] = vt1702_capture_mixer; + spec->num_mixers++; + } + + codec->patch_ops = via_patch_ops; + + codec->patch_ops.init = via_auto_init; + codec->patch_ops.unsol_event = via_unsol_event; +#ifdef CONFIG_SND_HDA_POWER_SAVE + spec->loopback.amplist = vt1702_loopbacks; +#endif + + /* Open backdoor */ + response = snd_hda_codec_read(codec, codec->afg, 0, 0xF8C, 0); + control = (unsigned char)(response & 0xff); + control |= 0x3; + snd_hda_codec_write(codec, codec->afg, 0, 0xF88, control); + + /* Enable GPIO 0&1 for volume&mute control */ + /* Enable GPIO 2 for DMIC-DATA */ + response = snd_hda_codec_read(codec, codec->afg, 0, 0xF84, 0); + control = (unsigned char)((response >> 16) & 0x3f); + snd_hda_codec_write(codec, codec->afg, 0, 0xF82, control); + + return 0; +} + +/* + * patch entries + */ +struct hda_codec_preset snd_hda_preset_via[] = { + { .id = 0x11061708, .name = "VIA VT1708", .patch = patch_vt1708}, + { .id = 0x11061709, .name = "VIA VT1708", .patch = patch_vt1708}, + { .id = 0x1106170A, .name = "VIA VT1708", .patch = patch_vt1708}, + { .id = 0x1106170B, .name = "VIA VT1708", .patch = patch_vt1708}, + { .id = 0x1106E710, .name = "VIA VT1709 10-Ch", + .patch = patch_vt1709_10ch}, + { .id = 0x1106E711, .name = "VIA VT1709 10-Ch", + .patch = patch_vt1709_10ch}, + { .id = 0x1106E712, .name = "VIA VT1709 10-Ch", + .patch = patch_vt1709_10ch}, + { .id = 0x1106E713, .name = "VIA VT1709 10-Ch", + .patch = patch_vt1709_10ch}, + { .id = 0x1106E714, .name = "VIA VT1709 6-Ch", + .patch = patch_vt1709_6ch}, + { .id = 0x1106E715, .name = "VIA VT1709 6-Ch", + .patch = patch_vt1709_6ch}, + { .id = 0x1106E716, .name = "VIA VT1709 6-Ch", + .patch = patch_vt1709_6ch}, + { .id = 0x1106E717, .name = "VIA VT1709 6-Ch", + .patch = patch_vt1709_6ch}, + { .id = 0x1106E720, .name = "VIA VT1708B 8-Ch", + .patch = patch_vt1708B_8ch}, + { .id = 0x1106E721, .name = "VIA VT1708B 8-Ch", + .patch = patch_vt1708B_8ch}, + { .id = 0x1106E722, .name = "VIA VT1708B 8-Ch", + .patch = patch_vt1708B_8ch}, + { .id = 0x1106E723, .name = "VIA VT1708B 8-Ch", + .patch = patch_vt1708B_8ch}, + { .id = 0x1106E724, .name = "VIA VT1708B 4-Ch", + .patch = patch_vt1708B_4ch}, + { .id = 0x1106E725, .name = "VIA VT1708B 4-Ch", + .patch = patch_vt1708B_4ch}, + { .id = 0x1106E726, .name = "VIA VT1708B 4-Ch", + .patch = patch_vt1708B_4ch}, + { .id = 0x1106E727, .name = "VIA VT1708B 4-Ch", + .patch = patch_vt1708B_4ch}, + { .id = 0x11060397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11061397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11062397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11063397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11064397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11065397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11066397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11067397, .name = "VIA VT1708S", + .patch = patch_vt1708S}, + { .id = 0x11060398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11061398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11062398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11063398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11064398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11065398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11066398, .name = "VIA VT1702", + .patch = patch_vt1702}, + { .id = 0x11067398, .name = "VIA VT1702", + .patch = patch_vt1702}, + {} /* terminator */ +}; |