diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-09 08:24:04 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-09 08:24:04 -0800 |
commit | 6026179519896e7d35b2564e7544487d1c8948e7 (patch) | |
tree | c78c7032abce24d846423572204f1cd4e97d8efc /sound/pci/hda | |
parent | d27146dd5b72ab7d7e641f56f4bee1484dabd0b7 (diff) | |
parent | c2902c8ae06762d941fab64198467f78cab6f8cd (diff) |
Merge branch 'linus' of master.kernel.org:/pub/scm/linux/kernel/git/perex/alsa
* 'linus' of master.kernel.org:/pub/scm/linux/kernel/git/perex/alsa: (212 commits)
[PATCH] Fix breakage with CONFIG_SYSFS_DEPRECATED
[ALSA] version 1.0.14rc2
[ALSA] ASoC documentation updates
[ALSA] ca0106 - Add missing sysfs device assignment
[ALSA] aoa i2sbus: Stop Apple i2s DMA gracefully
[ALSA] hda-codec - Add support for Fujitsu PI1556 Realtek ALC880
[ALSA] aoa: remove suspend/resume printks
[ALSA] Fix possible deadlocks in sequencer at removal of ports
[ALSA] emu10k1 - Fix STAC9758 front channel
[ALSA] soc - Clean up with kmemdup()
[ALSA] snd-ak4114: Fix two array overflows
[ALSA] ac97_bus power management
[ALSA] usbaudio - Add support for Edirol UA-101
[ALSA] hda-codec - Add ALC861VD/ALC660VD support
[ALSA] soc - ASoC 0.13 Sharp poodle machine
[ALSA] soc - ASoC 0.13 Sharp tosa machine
[ALSA] soc - ASoC 0.13 spitz machine
[ALSA] soc - ASoC Sharp corgi machine
[ALSA] soc - ASoC 0.13 pxa2xx DMA
[ALSA] soc - ASoC 0.13 pxa2xx AC97 driver
...
Diffstat (limited to 'sound/pci/hda')
-rw-r--r-- | sound/pci/hda/Makefile | 11 | ||||
-rw-r--r-- | sound/pci/hda/hda_codec.c | 68 | ||||
-rw-r--r-- | sound/pci/hda/hda_intel.c | 33 | ||||
-rw-r--r-- | sound/pci/hda/hda_local.h | 12 | ||||
-rw-r--r-- | sound/pci/hda/hda_patch.h | 6 | ||||
-rw-r--r-- | sound/pci/hda/patch_analog.c | 165 | ||||
-rw-r--r-- | sound/pci/hda/patch_cmedia.c | 24 | ||||
-rw-r--r-- | sound/pci/hda/patch_conexant.c | 1311 | ||||
-rw-r--r-- | sound/pci/hda/patch_realtek.c | 2367 | ||||
-rw-r--r-- | sound/pci/hda/patch_sigmatel.c | 692 | ||||
-rw-r--r-- | sound/pci/hda/patch_via.c | 1396 |
11 files changed, 5460 insertions, 625 deletions
diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index dbacba6177d..60d7b05a204 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -1,5 +1,14 @@ snd-hda-intel-objs := hda_intel.o -snd-hda-codec-objs := hda_codec.o hda_generic.o patch_realtek.o patch_cmedia.o patch_analog.o patch_sigmatel.o patch_si3054.o patch_atihdmi.o +snd-hda-codec-objs := hda_codec.o \ + hda_generic.o \ + patch_realtek.o \ + patch_cmedia.o \ + patch_analog.o \ + patch_sigmatel.o \ + patch_si3054.o \ + patch_atihdmi.o \ + patch_conexant.o \ + patch_via.o ifdef CONFIG_PROC_FS snd-hda-codec-objs += hda_proc.o endif diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 18bbc87e376..8f34fb44798 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c @@ -52,6 +52,7 @@ struct hda_vendor_id { static struct hda_vendor_id hda_vendor_ids[] = { { 0x10ec, "Realtek" }, { 0x1057, "Motorola" }, + { 0x1106, "VIA" }, { 0x11d4, "Analog Devices" }, { 0x13f6, "C-Media" }, { 0x14f1, "Conexant" }, @@ -262,7 +263,7 @@ int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex) unsol->queue[wp] = res; unsol->queue[wp + 1] = res_ex; - queue_work(unsol->workq, &unsol->work); + schedule_work(&unsol->work); return 0; } @@ -309,12 +310,6 @@ static int init_unsol_queue(struct hda_bus *bus) snd_printk(KERN_ERR "hda_codec: can't allocate unsolicited queue\n"); return -ENOMEM; } - unsol->workq = create_singlethread_workqueue("hda_codec"); - if (! unsol->workq) { - snd_printk(KERN_ERR "hda_codec: can't create workqueue\n"); - kfree(unsol); - return -ENOMEM; - } INIT_WORK(&unsol->work, process_unsol_events); unsol->bus = bus; bus->unsol = unsol; @@ -333,7 +328,7 @@ static int snd_hda_bus_free(struct hda_bus *bus) if (! bus) return 0; if (bus->unsol) { - destroy_workqueue(bus->unsol->workq); + flush_scheduled_work(); kfree(bus->unsol); } list_for_each_safe(p, n, &bus->codec_list) { @@ -1714,6 +1709,8 @@ EXPORT_SYMBOL(snd_hda_build_pcms); /** * 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 @@ -1722,33 +1719,44 @@ EXPORT_SYMBOL(snd_hda_build_pcms); * * If no entries are matching, the function returns a negative value. */ -int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl) -{ - const struct hda_board_config *c; - - if (codec->bus->modelname) { - for (c = tbl; c->modelname || c->pci_subvendor; c++) { - if (c->modelname && - ! strcmp(codec->bus->modelname, c->modelname)) { - snd_printd(KERN_INFO "hda_codec: model '%s' is selected\n", c->modelname); - return c->config; +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) { - u16 subsystem_vendor, subsystem_device; - pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_VENDOR_ID, &subsystem_vendor); - pci_read_config_word(codec->bus->pci, PCI_SUBSYSTEM_ID, &subsystem_device); - for (c = tbl; c->modelname || c->pci_subvendor; c++) { - if (c->pci_subvendor == subsystem_vendor && - (! c->pci_subdevice /* all match */|| - (c->pci_subdevice == subsystem_device))) { - snd_printdd(KERN_INFO "hda_codec: PCI %x:%x, codec config %d is selected\n", - subsystem_vendor, subsystem_device, c->config); - return c->config; - } + 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_DETECT + 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; } diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 1a7e82104bb..b9a8e238b0a 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -199,7 +199,7 @@ enum { SDI0, SDI1, SDI2, SDI3, SDO0, SDO1, SDO2, SDO3 }; /* STATESTS int mask: SD2,SD1,SD0 */ #define STATESTS_INT_MASK 0x07 -#define AZX_MAX_CODECS 4 +#define AZX_MAX_CODECS 3 /* SD_CTL bits */ #define SD_CTL_STREAM_RESET 0x01 /* stream reset bit */ @@ -1285,7 +1285,7 @@ static int __devinit create_codec_pcm(struct azx *chip, struct hda_codec *codec, snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &azx_pcm_ops); snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, snd_dma_pci_data(chip->pci), - 1024 * 64, 1024 * 128); + 1024 * 64, 1024 * 1024); chip->pcm[pcm_dev] = pcm; if (chip->pcm_devs < pcm_dev + 1) chip->pcm_devs = pcm_dev + 1; @@ -1391,6 +1391,7 @@ static int azx_acquire_irq(struct azx *chip, int do_disconnect) return -1; } chip->irq = chip->pci->irq; + pci_intx(chip->pci, !chip->msi); return 0; } @@ -1502,6 +1503,31 @@ static int azx_dev_free(struct snd_device *device) } /* + * white/black-listing for position_fix + */ +static const struct snd_pci_quirk position_fix_list[] __devinitdata = { + SND_PCI_QUIRK(0x1028, 0x01cc, "Dell D820", POS_FIX_NONE), + {} +}; + +static int __devinit check_position_fix(struct azx *chip, int fix) +{ + const struct snd_pci_quirk *q; + + if (fix == POS_FIX_AUTO) { + q = snd_pci_quirk_lookup(chip->pci, position_fix_list); + if (q) { + snd_printdd(KERN_INFO + "hda_intel: position_fix set to %d " + "for device %04x:%04x\n", + q->value, q->subvendor, q->subdevice); + return q->value; + } + } + return fix; +} + +/* * constructor */ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, @@ -1535,7 +1561,8 @@ static int __devinit azx_create(struct snd_card *card, struct pci_dev *pci, chip->driver_type = driver_type; chip->msi = enable_msi; - chip->position_fix = position_fix; + chip->position_fix = check_position_fix(chip, position_fix); + chip->single_cmd = single_cmd; #if BITS_PER_LONG != 64 diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h index 9ca1baf860b..39718d6cdad 100644 --- a/sound/pci/hda/hda_local.h +++ b/sound/pci/hda/hda_local.h @@ -173,14 +173,9 @@ static inline int snd_hda_codec_proc_new(struct hda_codec *codec) { return 0; } /* * Misc */ -struct hda_board_config { - const char *modelname; - int config; - unsigned short pci_subvendor; - unsigned short pci_subdevice; -}; - -int snd_hda_check_board_config(struct hda_codec *codec, const struct hda_board_config *tbl); +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); /* @@ -204,7 +199,6 @@ struct hda_bus_unsolicited { unsigned int rp, wp; /* workqueue */ - struct workqueue_struct *workq; struct work_struct work; struct hda_bus *bus; }; diff --git a/sound/pci/hda/hda_patch.h b/sound/pci/hda/hda_patch.h index 0b668793fac..9f9e9ae44a9 100644 --- a/sound/pci/hda/hda_patch.h +++ b/sound/pci/hda/hda_patch.h @@ -14,6 +14,10 @@ extern struct hda_codec_preset snd_hda_preset_sigmatel[]; 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[]; static const struct hda_codec_preset *hda_preset_tables[] = { snd_hda_preset_realtek, @@ -22,5 +26,7 @@ static const struct hda_codec_preset *hda_preset_tables[] = { snd_hda_preset_sigmatel, snd_hda_preset_si3054, snd_hda_preset_atihdmi, + snd_hda_preset_conexant, + snd_hda_preset_via, NULL }; diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c index 076365bc10e..38977bce70e 100644 --- a/sound/pci/hda/patch_analog.c +++ b/sound/pci/hda/patch_analog.c @@ -782,54 +782,63 @@ static struct hda_channel_mode ad1986a_modes[3] = { /* eapd initialization */ static struct hda_verb ad1986a_eapd_init_verbs[] = { - {0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00}, + {0x1b, AC_VERB_SET_EAPD_BTLENABLE, 0x00 }, {} }; +/* 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 */ +}; + /* models */ -enum { AD1986A_6STACK, AD1986A_3STACK, AD1986A_LAPTOP, AD1986A_LAPTOP_EAPD }; - -static struct hda_board_config ad1986a_cfg_tbl[] = { - { .modelname = "6stack", .config = AD1986A_6STACK }, - { .modelname = "3stack", .config = AD1986A_3STACK }, - { .pci_subvendor = 0x10de, .pci_subdevice = 0xcb84, - .config = AD1986A_3STACK }, /* ASUS A8N-VM CSM */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x817f, - .config = AD1986A_3STACK }, /* ASUS P5P-L2 */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x81b3, - .config = AD1986A_3STACK }, /* ASUS P5RD2-VM / P5GPL-X SE */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x81cb, - .config = AD1986A_3STACK }, /* ASUS M2NPV-VM */ - { .modelname = "laptop", .config = AD1986A_LAPTOP }, - { .pci_subvendor = 0x144d, .pci_subdevice = 0xc01e, - .config = AD1986A_LAPTOP }, /* FSC V2060 */ - { .pci_subvendor = 0x17c0, .pci_subdevice = 0x2017, - .config = AD1986A_LAPTOP }, /* Samsung M50 */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x818f, - .config = AD1986A_LAPTOP }, /* ASUS P5GV-MX */ - { .modelname = "laptop-eapd", .config = AD1986A_LAPTOP_EAPD }, - { .pci_subvendor = 0x144d, .pci_subdevice = 0xc023, - .config = AD1986A_LAPTOP_EAPD }, /* Samsung X60 Chane */ - { .pci_subvendor = 0x144d, .pci_subdevice = 0xc024, - .config = AD1986A_LAPTOP_EAPD }, /* Samsung R65-T2300 Charis */ - { .pci_subvendor = 0x144d, .pci_subdevice = 0xc026, - .config = AD1986A_LAPTOP_EAPD }, /* Samsung X11-T2300 Culesa */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1153, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS M9 */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1213, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS A6J */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x11f7, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS U5A */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1263, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS U5F */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1297, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS Z62F */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x12b3, - .config = AD1986A_LAPTOP_EAPD }, /* ASUS V1j */ - { .pci_subvendor = 0x103c, .pci_subdevice = 0x30af, - .config = AD1986A_LAPTOP_EAPD }, /* HP Compaq Presario B2800 */ - { .pci_subvendor = 0x17aa, .pci_subdevice = 0x2066, - .config = AD1986A_LAPTOP_EAPD }, /* Lenovo 3000 N100-07684JU */ +enum { + AD1986A_6STACK, + AD1986A_3STACK, + AD1986A_LAPTOP, + AD1986A_LAPTOP_EAPD, + AD1986A_ULTRA, + AD1986A_MODELS +}; + +static const char *ad1986a_models[AD1986A_MODELS] = { + [AD1986A_6STACK] = "6stack", + [AD1986A_3STACK] = "3stack", + [AD1986A_LAPTOP] = "laptop", + [AD1986A_LAPTOP_EAPD] = "laptop-eapd", + [AD1986A_ULTRA] = "ultra", +}; + +static struct snd_pci_quirk ad1986a_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30af, "HP B2800", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x10de, 0xcb84, "ASUS A8N-VM", AD1986A_3STACK), + SND_PCI_QUIRK(0x1043, 0x1153, "ASUS M9", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x1213, "ASUS A6J", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x1043, 0x11f7, "ASUS U5A", 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, 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(0x144d, 0xc01e, "FSC V2060", AD1986A_LAPTOP), + SND_PCI_QUIRK(0x144d, 0xc023, "Samsung X60", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x144d, 0xc024, "Samsung R65", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x144d, 0xc026, "Samsung X11", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x144d, 0xc504, "Samsung Q35", AD1986A_3STACK), + SND_PCI_QUIRK(0x144d, 0xc027, "Samsung Q1", AD1986A_ULTRA), + SND_PCI_QUIRK(0x17aa, 0x1017, "Lenovo A60", AD1986A_3STACK), + SND_PCI_QUIRK(0x17aa, 0x2066, "Lenovo N100", AD1986A_LAPTOP_EAPD), + SND_PCI_QUIRK(0x17c0, 0x2017, "Samsung M50", AD1986A_LAPTOP), {} }; @@ -861,7 +870,9 @@ static int patch_ad1986a(struct hda_codec *codec) codec->patch_ops = ad198x_patch_ops; /* override some parameters */ - board_config = snd_hda_check_board_config(codec, ad1986a_cfg_tbl); + 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; @@ -891,6 +902,15 @@ static int patch_ad1986a(struct hda_codec *codec) spec->multiout.dig_out_nid = 0; spec->input_mux = &ad1986a_laptop_eapd_capture_source; 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; } return 0; @@ -1391,20 +1411,27 @@ static struct hda_input_mux ad1981_thinkpad_capture_source = { }; /* models */ -enum { AD1981_BASIC, AD1981_HP, AD1981_THINKPAD }; +enum { + AD1981_BASIC, + AD1981_HP, + AD1981_THINKPAD, + AD1981_MODELS +}; + +static const char *ad1981_models[AD1981_MODELS] = { + [AD1981_HP] = "hp", + [AD1981_THINKPAD] = "thinkpad", + [AD1981_BASIC] = "basic", +}; -static struct hda_board_config ad1981_cfg_tbl[] = { - { .modelname = "hp", .config = AD1981_HP }, +static struct snd_pci_quirk ad1981_cfg_tbl[] = { /* All HP models */ - { .pci_subvendor = 0x103c, .config = AD1981_HP }, - { .pci_subvendor = 0x30b0, .pci_subdevice = 0x103c, - .config = AD1981_HP }, /* HP nx6320 (reversed SSID, H/W bug) */ - { .modelname = "thinkpad", .config = AD1981_THINKPAD }, + SND_PCI_QUIRK(0x103c, 0, "HP nx", AD1981_HP), + /* HP nx6320 (reversed SSID, H/W bug) */ + SND_PCI_QUIRK(0x30b0, 0x103c, "HP nx6320", AD1981_HP), /* Lenovo Thinkpad T60/X60/Z6xx */ - { .pci_subvendor = 0x17aa, .config = AD1981_THINKPAD }, - { .pci_subvendor = 0x1014, .pci_subdevice = 0x0597, - .config = AD1981_THINKPAD }, /* Z60m/t */ - { .modelname = "basic", .config = AD1981_BASIC }, + SND_PCI_QUIRK(0x17aa, 0, "Lenovo Thinkpad", AD1981_THINKPAD), + SND_PCI_QUIRK(0x1014, 0x0597, "Lenovo Z60", AD1981_THINKPAD), {} }; @@ -1437,7 +1464,9 @@ static int patch_ad1981(struct hda_codec *codec) codec->patch_ops = ad198x_patch_ops; /* override some parameters */ - board_config = snd_hda_check_board_config(codec, ad1981_cfg_tbl); + 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; @@ -2565,15 +2594,14 @@ static int ad1988_auto_init(struct hda_codec *codec) /* */ -static struct hda_board_config ad1988_cfg_tbl[] = { - { .modelname = "6stack", .config = AD1988_6STACK }, - { .modelname = "6stack-dig", .config = AD1988_6STACK_DIG }, - { .modelname = "3stack", .config = AD1988_3STACK }, - { .modelname = "3stack-dig", .config = AD1988_3STACK_DIG }, - { .modelname = "laptop", .config = AD1988_LAPTOP }, - { .modelname = "laptop-dig", .config = AD1988_LAPTOP_DIG }, - { .modelname = "auto", .config = AD1988_AUTO }, - {} +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 int patch_ad1988(struct hda_codec *codec) @@ -2591,8 +2619,9 @@ static int patch_ad1988(struct hda_codec *codec) 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_cfg_tbl); - if (board_config < 0 || board_config >= AD1988_MODEL_LAST) { + board_config = snd_hda_check_board_config(codec, AD1988_MODEL_LAST, + ad1988_models, NULL); + if (board_config < 0) { printk(KERN_INFO "hda_codec: Unknown model for AD1988, trying auto-probe from BIOS...\n"); board_config = AD1988_AUTO; } diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c index d38ce22507a..5b9d3a31a1a 100644 --- a/sound/pci/hda/patch_cmedia.c +++ b/sound/pci/hda/patch_cmedia.c @@ -40,6 +40,7 @@ enum { 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 { @@ -603,14 +604,17 @@ static void cmi9880_free(struct hda_codec *codec) /* */ -static struct hda_board_config cmi9880_cfg_tbl[] = { - { .modelname = "minimal", .config = CMI_MINIMAL }, - { .modelname = "min_fp", .config = CMI_MIN_FP }, - { .modelname = "full", .config = CMI_FULL }, - { .modelname = "full_dig", .config = CMI_FULL_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x813d, .config = CMI_FULL_DIG }, /* ASUS P5AD2 */ - { .modelname = "allout", .config = CMI_ALLOUT }, - { .modelname = "auto", .config = CMI_AUTO }, +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), {} /* terminator */ }; @@ -633,7 +637,9 @@ static int patch_cmi9880(struct hda_codec *codec) return -ENOMEM; codec->spec = spec; - spec->board_config = snd_hda_check_board_config(codec, cmi9880_cfg_tbl); + 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 */ diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c new file mode 100644 index 00000000000..73f4668238c --- /dev/null +++ b/sound/pci/hda/patch_conexant.c @@ -0,0 +1,1311 @@ +/* + * 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 <sound/driver.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + +#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 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[2]; /* used in build_pcms() */ + + struct mutex amp_mutex; /* PCM volume/mute control mutex */ + 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[4]; + +}; + +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); +} + +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); +} + +/* + * 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_setup_stream(codec, spec->adc_nids[substream->number], + 0, 0, 0); + 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 + }, +}; + +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 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]; + 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->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); +} + +#ifdef CONFIG_PM +static int conexant_resume(struct hda_codec *codec) +{ + struct conexant_spec *spec = codec->spec; + int i; + + codec->patch_ops.init(codec); + for (i = 0; i < spec->num_mixers; i++) + snd_hda_resume_ctls(codec, spec->mixers[i]); + if (spec->multiout.dig_out_nid) + snd_hda_resume_spdif_out(codec); + if (spec->dig_in_nid) + snd_hda_resume_spdif_in(codec); + return 0; +} +#endif + +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; + } + 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, +#ifdef CONFIG_PM + .resume = conexant_resume, +#endif +}; + +/* + * EAPD control + * the private value = nid | (invert << 8) + */ + +static int conexant_eapd_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int conexant_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 conexant_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 && !codec->in_resume) + return 0; + spec->cur_eapd = eapd; + snd_hda_codec_write(codec, nid, + 0, AC_VERB_SET_EAPD_BTLENABLE, + eapd ? 0x02 : 0x00); + return 1; +} + +/* controls for test mode */ +#ifdef CONFIG_SND_DEBUG + +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) } + +static int cxt_gpio_data_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int cxt_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 cxt_gpio_data_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 & 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); + unsigned int old_data = gpio_data; + + /* Set/unset the masked GPIO bit(s) as needed */ + if (val == 0) + gpio_data &= ~mask; + else + gpio_data |= mask; + if (gpio_data == old_data && !codec->in_resume) + return 0; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_GPIO_DATA, gpio_data); + return 1; +} + +#define CXT_GPIO_DATA_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = cxt_gpio_data_info, \ + .get = cxt_gpio_data_get, \ + .put = cxt_gpio_data_put, \ + .private_value = nid | (mask<<16) } + +static int cxt_spdif_ctrl_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + return 0; +} + +static int cxt_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, 0x00); + + *valp = (val & mask) != 0; + return 0; +} + +static int cxt_spdif_ctrl_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 & 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, + 0x00); + unsigned int old_data = ctrl_data; + + /* Set/unset the masked control bit(s) as needed */ + if (val == 0) + ctrl_data &= ~mask; + else + ctrl_data |= mask; + if (ctrl_data == old_data && !codec->in_resume) + return 0; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1, + ctrl_data); + return 1; +} + +#define CXT_SPDIF_CTRL_SWITCH(xname, nid, mask) \ + { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0, \ + .info = cxt_spdif_ctrl_info, \ + .get = cxt_spdif_ctrl_get, \ + .put = cxt_spdif_ctrl_put, \ + .private_value = nid | (mask<<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 0x13 + +static struct hda_channel_mode cxt5045_modes[1] = { + { 2, NULL }, +}; + +static struct hda_input_mux cxt5045_capture_source = { + .num_items = 2, + .items = { + { "ExtMic", 0x1 }, + { "LineIn", 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; + + if (!conexant_eapd_put(kcontrol, ucontrol)) + return 0; + + /* toggle HP mute appropriately */ + snd_hda_codec_amp_update(codec, 0x11, 0, HDA_OUTPUT, 0, + 0x80, spec->cur_eapd ? 0 : 0x80); + snd_hda_codec_amp_update(codec, 0x11, 1, HDA_OUTPUT, 0, + 0x80, spec->cur_eapd ? 0 : 0x80); + return 1; +} + +/* bind volumes of both NID 0x10 and 0x11 */ +static int cxt5045_hp_master_vol_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, 0x10, 0, HDA_OUTPUT, 0, + 0x7f, valp[0] & 0x7f); + change |= snd_hda_codec_amp_update(codec, 0x10, 1, HDA_OUTPUT, 0, + 0x7f, valp[1] & 0x7f); + snd_hda_codec_amp_update(codec, 0x11, 0, HDA_OUTPUT, 0, + 0x7f, valp[0] & 0x7f); + snd_hda_codec_amp_update(codec, 0x11, 1, HDA_OUTPUT, 0, + 0x7f, valp[1] & 0x7f); + return change; +} + + +/* mute internal speaker if HP is plugged */ +static void cxt5045_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_update(codec, 0x10, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x10, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); +} + +/* 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; + } +} + +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("Mic Bypass Capture Volume", 0x17, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Mic Bypass Capture Switch", 0x17, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x1a, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x1a, 0x02, HDA_INPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Volume", + .info = snd_hda_mixer_amp_volume_info, + .get = snd_hda_mixer_amp_volume_get, + .put = cxt5045_hp_master_vol_put, + .private_value = HDA_COMPOSE_AMP_VAL(0x11, 3, 0, HDA_OUTPUT), + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = conexant_eapd_info, + .get = conexant_eapd_get, + .put = cxt5045_hp_master_sw_put, + .private_value = 0x11, + }, + + {} +}; + +static struct hda_verb cxt5045_init_verbs[] = { + /* Line in, Mic */ + {0x12, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN }, + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN|AC_PINCTL_VREF_50 }, + /* HP, Amp */ + {0x11, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {0x1A, AC_VERB_SET_CONNECT_SEL,0x01}, + {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}, + /* Record selector: Front mic */ + {0x14, AC_VERB_SET_CONNECT_SEL,0x03}, + {0x17, 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_CONNECT_SEL, 0x0 }, + /* pin sensing on HP and Mic jacks */ + {0x11, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + /* EAPD */ + {0x10, AC_VERB_SET_EAPD_BTLENABLE, 0x0 }, /* default on */ + { } /* 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("OutAmp-1 Volume", 0x19, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("OutAmp-1 Switch", 0x19,0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("Speaker Playback Volume", 0x10, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Speaker Playback Switch", 0x10, 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), + + /* Loopback mixer controls */ + HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x17, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("MIC1 Playback Switch", 0x17, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("LINE loopback Playback Volume", 0x17, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("LINE loopback Playback Switch", 0x17, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("HP-OUT loopback Playback Volume", 0x17, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("HP-OUT loopback Playback Switch", 0x17, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x17, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x17, 0x04, HDA_INPUT), + + /* Controls for GPIO pins, assuming they exist and are configured as outputs */ + CXT_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01), +#if 0 /* limit this to one GPIO pin for now */ + CXT_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02), + CXT_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04), + CXT_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08), +#endif + CXT_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x13, 0x01), + + HDA_CODEC_VOLUME("Capture Volume", 0x17, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x17, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put, + }, + + { } /* end */ +}; + +static struct hda_verb cxt5045_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 */ + {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_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}, + + /* 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, /* Laptops w/ EAPD support */ +#ifdef CONFIG_SND_DEBUG + CXT5045_TEST, +#endif + CXT5045_MODELS +}; + +static const char *cxt5045_models[CXT5045_MODELS] = { + [CXT5045_LAPTOP] = "laptop", +#ifdef CONFIG_SND_DEBUG + [CXT5045_TEST] = "test", +#endif +}; + +static struct snd_pci_quirk cxt5045_cfg_tbl[] = { + SND_PCI_QUIRK(0x103c, 0x30b7, "HP DV6000Z", CXT5045_LAPTOP), + {} +}; + +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; + mutex_init(&spec->amp_mutex); + 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; + codec->patch_ops.unsol_event = cxt5045_hp_unsol_event; + + board_config = snd_hda_check_board_config(codec, CXT5045_MODELS, + cxt5045_models, + cxt5045_cfg_tbl); + switch (board_config) { + case CXT5045_LAPTOP: + spec->input_mux = &cxt5045_capture_source; + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5045_init_verbs; + spec->mixers[0] = cxt5045_mixers; + 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; +#endif + } + return 0; +} + + +/* Conexant 5047 specific */ + +static hda_nid_t cxt5047_dac_nids[1] = { 0x10 }; +static hda_nid_t cxt5047_adc_nids[1] = { 0x12 }; +static hda_nid_t cxt5047_capsrc_nids[1] = { 0x1a }; +#define CXT5047_SPDIF_OUT 0x11 + +static struct hda_channel_mode cxt5047_modes[1] = { + { 2, NULL }, +}; + +static struct hda_input_mux cxt5047_capture_source = { + .num_items = 2, + .items = { + { "ExtMic", 0x1 }, + { "IntMic", 0x2 }, + } +}; + +static struct hda_input_mux cxt5047_hp_capture_source = { + .num_items = 1, + .items = { + { "ExtMic", 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; + + if (!conexant_eapd_put(kcontrol, ucontrol)) + return 0; + + /* toggle HP mute appropriately */ + snd_hda_codec_amp_update(codec, 0x13, 0, HDA_OUTPUT, 0, + 0x80, spec->cur_eapd ? 0 : 0x80); + snd_hda_codec_amp_update(codec, 0x13, 1, HDA_OUTPUT, 0, + 0x80, spec->cur_eapd ? 0 : 0x80); + return 1; +} + +#if 0 +/* bind volumes of both NID 0x13 and 0x1d */ +static int cxt5047_hp_master_vol_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, 0x1c, 0, HDA_OUTPUT, 0, + 0x7f, valp[0] & 0x7f); + change |= snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, + 0x7f, valp[1] & 0x7f); + snd_hda_codec_amp_update(codec, 0x13, 0, HDA_OUTPUT, 0, + 0x7f, valp[0] & 0x7f); + snd_hda_codec_amp_update(codec, 0x13, 1, HDA_OUTPUT, 0, + 0x7f, valp[1] & 0x7f); + return change; +} +#endif + +/* mute internal speaker if HP is plugged */ +static void cxt5047_hp_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x13, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_update(codec, 0x1c, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x1c, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); +} + +/* 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, 0xb080}, + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb000}, + {} + }; + static struct hda_verb mic_jack_off[] = { + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, 0xb080}, + {0x15, 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 cxt5047_hp_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + res >>= 26; + switch (res) { + case CONEXANT_HP_EVENT: + cxt5047_hp_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5047_hp_automic(codec); + break; + } +} + +static struct snd_kcontrol_new cxt5047_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 = conexant_eapd_info, + .get = conexant_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 = conexant_eapd_info, + .get = conexant_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, Amp */ + {0x13, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT }, + {0x1A, AC_VERB_SET_CONNECT_SEL,0x03}, + {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}, + /* Record selector: Front 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}, + /* SPDIF route: PCM */ + { 0x18, AC_VERB_SET_CONNECT_SEL, 0x0 }, + { } /* 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}, + {} +}; + +/* configuration for HP Laptops */ +static struct hda_verb cxt5047_hp_init_verbs[] = { + /* 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}, + {} +}; + +/* 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 = 5, + .items = { + { "MIXER", 0x0 }, + { "LINE1 pin", 0x1 }, + { "MIC1 pin", 0x2 }, + { "MIC2 pin", 0x3 }, + { "CD pin", 0x4 }, + }, +}; + +static struct snd_kcontrol_new cxt5047_test_mixer[] = { + + /* Output only controls */ + HDA_CODEC_VOLUME("OutAmp-1 Volume", 0x10, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("OutAmp-1 Switch", 0x10,0x00, HDA_OUTPUT), + HDA_CODEC_VOLUME("OutAmp-2 Volume", 0x1c, 0x00, HDA_OUTPUT), + HDA_CODEC_MUTE("OutAmp-2 Switch", 0x1c, 0x00, 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), + + /* 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), + + /* Loopback mixer controls */ + HDA_CODEC_VOLUME("MIC1 Playback Volume", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_MUTE("MIC1 Playback Switch", 0x19, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("MIC2 Playback Volume", 0x19, 0x03, HDA_INPUT), + HDA_CODEC_MUTE("MIC2 Playback Switch", 0x19, 0x03, HDA_INPUT), + HDA_CODEC_VOLUME("LINE Playback Volume", 0x19, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("LINE Playback Switch", 0x19, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("CD Playback Volume", 0x19, 0x04, HDA_INPUT), + HDA_CODEC_MUTE("CD Playback Switch", 0x19, 0x04, HDA_INPUT), + +#if 0 + /* Controls for GPIO pins, assuming they exist and are configured as outputs */ + CXT_GPIO_DATA_SWITCH("GPIO pin 0", 0x01, 0x01), + CXT_GPIO_DATA_SWITCH("GPIO pin 1", 0x01, 0x02), + CXT_GPIO_DATA_SWITCH("GPIO pin 2", 0x01, 0x04), + CXT_GPIO_DATA_SWITCH("GPIO pin 3", 0x01, 0x08), +#endif + CXT_SPDIF_CTRL_SWITCH("SPDIF Playback Switch", 0x18, 0x01), + + HDA_CODEC_VOLUME("Capture Volume", 0x19, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x19, 0x0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .info = conexant_mux_enum_info, + .get = conexant_mux_enum_get, + .put = conexant_mux_enum_put, + }, + + { } /* end */ +}; + +static struct hda_verb cxt5047_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 */ + {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); + cxt5047_hp_automic(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, 0x30b2, "HP DV2000T/DV3000T", CXT5047_LAPTOP), + SND_PCI_QUIRK(0x103c, 0x30a5, "HP DV5200T/DV8000T", CXT5047_LAPTOP_HP), + 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; + mutex_init(&spec->amp_mutex); + 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; + codec->patch_ops.unsol_event = cxt5047_hp_unsol_event; + + board_config = snd_hda_check_board_config(codec, CXT5047_MODELS, + cxt5047_models, + cxt5047_cfg_tbl); + switch (board_config) { + case CXT5047_LAPTOP: + 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.init = cxt5047_hp_init; + break; + case CXT5047_LAPTOP_EAPD: + spec->num_init_verbs = 2; + spec->init_verbs[1] = cxt5047_toshiba_init_verbs; + 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; +#endif + } + return 0; +} + +struct hda_codec_preset snd_hda_preset_conexant[] = { + { .id = 0x14f15045, .name = "CXT5045", .patch = patch_cxt5045 }, + { .id = 0x14f15047, .name = "CXT5047", .patch = patch_cxt5047 }, + {} /* terminator */ +}; diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 4e0c3c1b908..145682b7807 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -32,6 +32,10 @@ #include "hda_codec.h" #include "hda_local.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 { @@ -48,7 +52,10 @@ enum { 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, @@ -77,8 +84,12 @@ enum { /* 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_BENQ_ED8, ALC262_AUTO, ALC262_MODEL_LAST /* last tag */ @@ -91,16 +102,30 @@ enum { 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, + ALC861VD_3ST, + ALC861VD_3ST_DIG, + ALC861VD_6ST_DIG, + ALC861VD_AUTO, + ALC861VD_MODEL_LAST, +}; + /* ALC882 models */ enum { ALC882_3ST_DIG, ALC882_6ST_DIG, ALC882_ARIMA, ALC882_AUTO, + ALC885_MACPRO, ALC882_MODEL_LAST, }; @@ -110,8 +135,12 @@ enum { ALC883_3ST_6ch_DIG, ALC883_3ST_6ch, ALC883_6ST_DIG, + ALC883_TARGA_DIG, + ALC883_TARGA_2ch_DIG, ALC888_DEMO_BOARD, ALC883_ACER, + ALC883_MEDION, + ALC883_LAPTOP_EAPD, ALC883_AUTO, ALC883_MODEL_LAST, }; @@ -1015,6 +1044,60 @@ static struct snd_kcontrol_new alc880_tcl_s700_mixer[] = { { } /* end */ }; +/* Uniwill */ +static struct snd_kcontrol_new alc880_uniwill_mixer[] = { + HDA_CODEC_VOLUME("HPhone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("HPhone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("iSpeaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("iSpeaker 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("HPhone Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("HPhone Playback Switch", 0x0c, 2, HDA_INPUT), + HDA_CODEC_VOLUME("iSpeaker Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_BIND_MUTE("iSpeaker 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 */ +}; + /* * build control elements */ @@ -1248,6 +1331,159 @@ static struct hda_verb alc880_pin_6stack_init_verbs[] = { { } }; +/* + * 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_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_update(codec, 0x15, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x15, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x16, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x16, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + + present = snd_hda_codec_read(codec, 0x18, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_write(codec, 0x0b, 0, AC_VERB_SET_AMP_GAIN_MUTE, + 0x7000 | (0x01 << 8) | (present ? 0x80 : 0)); +} + +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! + */ + if ((res >> 28) == ALC880_HP_EVENT || + (res >> 28) == ALC880_MIC_EVENT) + alc880_uniwill_automute(codec); +} + +static void alc880_uniwill_p53_hp_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_update(codec, 0x15, 0, HDA_INPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x15, 1, HDA_INPUT, 0, + 0x80, present ? 0x80 : 0); +} + +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) & 0x7f; + + snd_hda_codec_amp_update(codec, 0x0c, 0, HDA_OUTPUT, 0, + 0x7f, present); + snd_hda_codec_amp_update(codec, 0x0c, 1, HDA_OUTPUT, 0, + 0x7f, present); + + snd_hda_codec_amp_update(codec, 0x0d, 0, HDA_OUTPUT, 0, + 0x7f, present); + snd_hda_codec_amp_update(codec, 0x0d, 1, HDA_OUTPUT, 0, + 0x7f, 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); +} + /* FIXME! */ /* * F1734 pin configuration: @@ -2125,159 +2361,112 @@ static struct hda_verb alc880_test_init_verbs[] = { /* */ -static struct hda_board_config alc880_cfg_tbl[] = { - /* Back 3 jack, front 2 jack */ - { .modelname = "3stack", .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe200, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe201, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe202, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe203, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe204, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe205, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe206, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe207, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe208, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe209, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20a, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20b, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20c, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20d, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20e, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe20f, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe210, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe211, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe212, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe213, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe214, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe234, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe302, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe303, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe304, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe306, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe307, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe404, .config = ALC880_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xa101, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x3031, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4036, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4037, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4038, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4040, .config = ALC880_3ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4041, .config = ALC880_3ST }, - /* TCL S700 */ - { .modelname = "tcl", .config = ALC880_TCL_S700 }, - { .pci_subvendor = 0x19db, .pci_subdevice = 0x4188, .config = ALC880_TCL_S700 }, - - /* Back 3 jack, front 2 jack (Internal add Aux-In) */ - { .pci_subvendor = 0x1025, .pci_subdevice = 0xe310, .config = ALC880_3ST }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81d6, .config = ALC880_3ST }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81a0, .config = ALC880_3ST }, - - /* Back 3 jack plus 1 SPDIF out jack, front 2 jack */ - { .modelname = "3stack-digout", .config = ALC880_3ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe308, .config = ALC880_3ST_DIG }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x0070, .config = ALC880_3ST_DIG }, - - /* Clevo laptops */ - { .modelname = "clevo", .config = ALC880_CLEVO }, - { .pci_subvendor = 0x1558, .pci_subdevice = 0x0520, - .config = ALC880_CLEVO }, /* Clevo m520G NB */ - { .pci_subvendor = 0x1558, .pci_subdevice = 0x0660, - .config = ALC880_CLEVO }, /* Clevo m665n */ - - /* Back 3 jack plus 1 SPDIF out jack, front 2 jack (Internal add Aux-In)*/ - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe305, .config = ALC880_3ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xd402, .config = ALC880_3ST_DIG }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0xe309, .config = ALC880_3ST_DIG }, - - /* Back 5 jack, front 2 jack */ - { .modelname = "5stack", .config = ALC880_5ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x3033, .config = ALC880_5ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x4039, .config = ALC880_5ST }, - { .pci_subvendor = 0x107b, .pci_subdevice = 0x3032, .config = ALC880_5ST }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x2a09, .config = ALC880_5ST }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x814e, .config = ALC880_5ST }, - - /* Back 5 jack plus 1 SPDIF out jack, front 2 jack */ - { .modelname = "5stack-digout", .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe224, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe400, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe401, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xe402, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xd400, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xd401, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xa100, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x1565, .pci_subdevice = 0x8202, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0x1019, .pci_subdevice = 0xa880, .config = ALC880_5ST_DIG }, - { .pci_subvendor = 0xa0a0, .pci_subdevice = 0x0560, - .config = ALC880_5ST_DIG }, /* Aopen i915GMm-HFS */ - /* { .pci_subvendor = 0x1019, .pci_subdevice = 0xa884, .config = ALC880_5ST_DIG }, */ /* conflict with 6stack */ - { .pci_subvendor = 0x1695, .pci_subdevice = 0x400d, .config = ALC880_5ST_DIG }, - /* note subvendor = 0 below */ - /* { .pci_subvendor = 0x0000, .pci_subdevice = 0x8086, .config = ALC880_5ST_DIG }, */ - - { .modelname = "w810", .config = ALC880_W810 }, - { .pci_subvendor = 0x161f, .pci_subdevice = 0x203d, .config = ALC880_W810 }, - - { .modelname = "z71v", .config = ALC880_Z71V }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1964, .config = ALC880_Z71V }, - - { .modelname = "6stack", .config = ALC880_6ST }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x8196, .config = ALC880_6ST }, /* ASUS P5GD1-HVM */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x81b4, .config = ALC880_6ST }, - { .pci_subvendor = 0x1019, .pci_subdevice = 0xa884, .config = ALC880_6ST }, /* Acer APFV */ - { .pci_subvendor = 0x1458, .pci_subdevice = 0xa102, .config = ALC880_6ST }, /* Gigabyte K8N51 */ - - { .modelname = "6stack-digout", .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x2668, .pci_subdevice = 0x8086, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0x2668, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1462, .pci_subdevice = 0x1150, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0xe803, .pci_subdevice = 0x1019, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1039, .pci_subdevice = 0x1234, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x0077, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x0078, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x0087, .config = ALC880_6ST_DIG }, - { .pci_subvendor = 0x1297, .pci_subdevice = 0xc790, .config = ALC880_6ST_DIG }, /* Shuttle ST20G5 */ - { .pci_subvendor = 0x1509, .pci_subdevice = 0x925d, .config = ALC880_6ST_DIG }, /* FIC P4M-915GD1 */ - { .pci_subvendor = 0x1695, .pci_subdevice = 0x4012, .config = ALC880_5ST_DIG }, /* Epox EP-5LDA+ GLi */ - - { .modelname = "asus", .config = ALC880_ASUS }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1964, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1973, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x19b3, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1113, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1173, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1993, .config = ALC880_ASUS }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x10c2, .config = ALC880_ASUS_DIG }, /* Asus W6A */ - { .pci_subvendor = 0x1043, .pci_subdevice = 0x10c3, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1133, .config = ALC880_ASUS }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1123, .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x1143, .config = ALC880_ASUS }, - { .modelname = "asus-w1v", .config = ALC880_ASUS_W1V }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x10b3, .config = ALC880_ASUS_W1V }, - { .modelname = "asus-dig", .config = ALC880_ASUS_DIG }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x8181, .config = ALC880_ASUS_DIG }, /* ASUS P4GPL-X */ - { .modelname = "asus-dig2", .config = ALC880_ASUS_DIG2 }, - { .pci_subvendor = 0x1558, .pci_subdevice = 0x5401, .config = ALC880_ASUS_DIG2 }, - - { .modelname = "uniwill", .config = ALC880_UNIWILL_DIG }, - { .pci_subvendor = 0x1584, .pci_subdevice = 0x9050, .config = ALC880_UNIWILL_DIG }, - - { .modelname = "F1734", .config = ALC880_F1734 }, - { .pci_subvendor = 0x1734, .pci_subdevice = 0x107c, .config = ALC880_F1734 }, - { .pci_subvendor = 0x1584, .pci_subdevice = 0x9054, .config = ALC880_F1734 }, - - { .modelname = "lg", .config = ALC880_LG }, - { .pci_subvendor = 0x1854, .pci_subdevice = 0x003b, .config = ALC880_LG }, - { .pci_subvendor = 0x1854, .pci_subdevice = 0x0068, .config = ALC880_LG }, - - { .modelname = "lg-lw", .config = ALC880_LG_LW }, - { .pci_subvendor = 0x1854, .pci_subdevice = 0x0018, .config = ALC880_LG_LW }, - { .pci_subvendor = 0x1854, .pci_subdevice = 0x0077, .config = ALC880_LG_LW }, - +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", #ifdef CONFIG_SND_DEBUG - { .modelname = "test", .config = ALC880_TEST }, + [ALC880_TEST] = "test", #endif - { .modelname = "auto", .config = ALC880_AUTO }, + [ALC880_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc880_cfg_tbl[] = { + /* Broken BIOS configuration */ + SND_PCI_QUIRK(0x2668, 0x8086, NULL, ALC880_6ST_DIG), + SND_PCI_QUIRK(0x8086, 0x2668, NULL, ALC880_6ST_DIG), + + SND_PCI_QUIRK(0x1019, 0xa880, "ECS", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1019, 0xa884, "Acer APFV", ALC880_6ST), + SND_PCI_QUIRK(0x1019, 0x0f69, "Coeus G610P", ALC880_W810), + 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", ALC880_ASUS), + 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), + + SND_PCI_QUIRK(0x104d, 0x81d6, "Sony", ALC880_3ST), + SND_PCI_QUIRK(0x104d, 0x81a0, "Sony", ALC880_3ST), + SND_PCI_QUIRK(0x107b, 0x3033, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x107b, 0x4039, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x107b, 0x3032, "Gateway", ALC880_5ST), + SND_PCI_QUIRK(0x1558, 0x0520, "Clevo m520G", ALC880_CLEVO), + SND_PCI_QUIRK(0x1558, 0x0660, "Clevo m655n", ALC880_CLEVO), + SND_PCI_QUIRK(0x1565, 0x8202, "Biostar", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x161f, 0x203d, "W810", ALC880_W810), + SND_PCI_QUIRK(0x1695, 0x400d, "EPoX", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x19db, 0x4188, "TCL S700", ALC880_TCL_S700), + SND_PCI_QUIRK(0xa0a0, 0x0560, "AOpen i915GMm-HFS", ALC880_5ST_DIG), + SND_PCI_QUIRK(0xe803, 0x1019, NULL, ALC880_6ST_DIG), + 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, 0x5401, "ASUS", ALC880_ASUS_DIG2), + + SND_PCI_QUIRK(0x1584, 0x9050, "Uniwill", ALC880_UNIWILL_DIG), + SND_PCI_QUIRK(0x1584, 0x9070, "Uniwill", ALC880_UNIWILL), + SND_PCI_QUIRK(0x1584, 0x9077, "Uniwill P53", ALC880_UNIWILL_P53), + SND_PCI_QUIRK(0x1584, 0x9054, "Uniwlll", ALC880_F1734), + + SND_PCI_QUIRK(0x1695, 0x4012, "EPox EP-5LDA", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x1734, 0x10ac, "FSC", ALC880_UNIWILL), + SND_PCI_QUIRK(0x1734, 0x107c, "FSC F1734", ALC880_F1734), + SND_PCI_QUIRK(0x1734, 0x10b0, "Fujitsu", ALC880_FUJITSU), + + SND_PCI_QUIRK(0x1854, 0x003b, "LG", ALC880_LG), + SND_PCI_QUIRK(0x1854, 0x0068, "LG w1", ALC880_LG), + SND_PCI_QUIRK(0x1854, 0x0018, "LG LW20", ALC880_LG_LW), + SND_PCI_QUIRK(0x1854, 0x0077, "LG LW25", ALC880_LG_LW), + + SND_PCI_QUIRK(0x8086, 0xe308, "Intel mobo", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xe305, "Intel mobo", ALC880_3ST_DIG), + SND_PCI_QUIRK(0x8086, 0xd402, "Intel mobo", ALC880_3ST_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, 0xe224, "Intel mobo", ALC880_5ST_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, 0xa100, "Intel mobo", ALC880_5ST_DIG), + SND_PCI_QUIRK(0x8086, 0, "Intel mobo", ALC880_3ST), {} }; @@ -2438,7 +2627,8 @@ static struct alc_config_preset alc880_presets[] = { }, [ALC880_UNIWILL_DIG] = { .mixers = { alc880_asus_mixer, alc880_pcbeep_mixer }, - .init_verbs = { alc880_volume_init_verbs, alc880_pin_asus_init_verbs }, + .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, @@ -2447,6 +2637,46 @@ static struct alc_config_preset alc880_presets[] = { .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, + .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, @@ -2841,8 +3071,10 @@ static int patch_alc880(struct hda_codec *codec) codec->spec = spec; - board_config = snd_hda_check_board_config(codec, alc880_cfg_tbl); - if (board_config < 0 || board_config >= ALC880_MODEL_LAST) { + 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; @@ -3090,11 +3322,20 @@ static struct snd_kcontrol_new alc260_fujitsu_mixer[] = { * 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("Mono Speaker Playback Volume", 0x0a, 1, 0x0, + HDA_OUTPUT), + HDA_BIND_MUTE_MONO("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), @@ -3409,11 +3650,11 @@ static struct hda_verb alc260_acer_init_verbs[] = { {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)}, - {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}, @@ -3441,6 +3682,8 @@ static struct hda_verb alc260_acer_init_verbs[] = { /* 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. @@ -3928,33 +4171,33 @@ static void alc260_auto_init(struct hda_codec *codec) /* * ALC260 configurations */ -static struct hda_board_config alc260_cfg_tbl[] = { - { .modelname = "basic", .config = ALC260_BASIC }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81bb, - .config = ALC260_BASIC }, /* Sony VAIO */ - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81cc, - .config = ALC260_BASIC }, /* Sony VAIO VGN-S3HP */ - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81cd, - .config = ALC260_BASIC }, /* Sony VAIO */ - { .pci_subvendor = 0x152d, .pci_subdevice = 0x0729, - .config = ALC260_BASIC }, /* CTL Travel Master U553W */ - { .modelname = "hp", .config = ALC260_HP }, - { .modelname = "hp-3013", .config = ALC260_HP_3013 }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3010, .config = ALC260_HP_3013 }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3011, .config = ALC260_HP }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3012, .config = ALC260_HP_3013 }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3013, .config = ALC260_HP_3013 }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3014, .config = ALC260_HP }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3015, .config = ALC260_HP }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3016, .config = ALC260_HP }, - { .modelname = "fujitsu", .config = ALC260_FUJITSU_S702X }, - { .pci_subvendor = 0x10cf, .pci_subdevice = 0x1326, .config = ALC260_FUJITSU_S702X }, - { .modelname = "acer", .config = ALC260_ACER }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x008f, .config = ALC260_ACER }, +static const char *alc260_models[ALC260_MODEL_LAST] = { + [ALC260_BASIC] = "basic", + [ALC260_HP] = "hp", + [ALC260_HP_3013] = "hp-3013", + [ALC260_FUJITSU_S702X] = "fujitsu", + [ALC260_ACER] = "acer", #ifdef CONFIG_SND_DEBUG - { .modelname = "test", .config = ALC260_TEST }, + [ALC260_TEST] = "test", #endif - { .modelname = "auto", .config = ALC260_AUTO }, + [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, 0x3010, "HP", ALC260_HP_3013), + SND_PCI_QUIRK(0x103c, 0x3011, "HP", ALC260_HP), + SND_PCI_QUIRK(0x103c, 0x3012, "HP", ALC260_HP_3013), + 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), {} }; @@ -4053,8 +4296,10 @@ static int patch_alc260(struct hda_codec *codec) codec->spec = spec; - board_config = snd_hda_check_board_config(codec, alc260_cfg_tbl); - if (board_config < 0 || board_config >= ALC260_MODEL_LAST) { + 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; @@ -4207,8 +4452,10 @@ static struct snd_kcontrol_new alc882_base_mixer[] = { 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), @@ -4313,6 +4560,100 @@ static struct hda_verb alc882_eapd_verbs[] = { { } }; +/* 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}, + + { } +}; +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); +} + /* * generic initialization of ADC, input mixers and output mixers */ @@ -4435,19 +4776,20 @@ static struct snd_kcontrol_new alc882_capture_mixer[] = { /* * configuration and preset */ -static struct hda_board_config alc882_cfg_tbl[] = { - { .modelname = "3stack-dig", .config = ALC882_3ST_DIG }, - { .modelname = "6stack-dig", .config = ALC882_6ST_DIG }, - { .pci_subvendor = 0x1462, .pci_subdevice = 0x6668, - .config = ALC882_6ST_DIG }, /* MSI */ - { .pci_subvendor = 0x105b, .pci_subdevice = 0x6668, - .config = ALC882_6ST_DIG }, /* Foxconn */ - { .pci_subvendor = 0x1019, .pci_subdevice = 0x6668, - .config = ALC882_6ST_DIG }, /* ECS to Intel*/ - { .modelname = "arima", .config = ALC882_ARIMA }, - { .pci_subvendor = 0x161f, .pci_subdevice = 0x2054, - .config = ALC882_ARIMA }, /* Arima W820Di1 */ - { .modelname = "auto", .config = ALC882_AUTO }, +static const char *alc882_models[ALC882_MODEL_LAST] = { + [ALC882_3ST_DIG] = "3stack-dig", + [ALC882_6ST_DIG] = "6stack-dig", + [ALC882_ARIMA] = "arima", + [ALC885_MACPRO] = "macpro", + [ALC882_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc882_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC882_6ST_DIG), + SND_PCI_QUIRK(0x161f, 0x2054, "Arima W820", ALC882_ARIMA), + SND_PCI_QUIRK(0x1043, 0x81d8, "Asus P5WD", ALC882_6ST_DIG), {} }; @@ -4484,6 +4826,17 @@ static struct alc_config_preset alc882_presets[] = { .channel_mode = alc882_sixstack_modes, .input_mux = &alc882_capture_source, }, + [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, + }, }; @@ -4584,7 +4937,9 @@ static int patch_alc882(struct hda_codec *codec) codec->spec = spec; - board_config = snd_hda_check_board_config(codec, alc882_cfg_tbl); + 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) { printk(KERN_INFO "hda_codec: Unknown model for ALC882, " @@ -4609,6 +4964,11 @@ static int patch_alc882(struct hda_codec *codec) if (board_config != ALC882_AUTO) setup_preset(spec, &alc882_presets[board_config]); + if (board_config == ALC885_MACPRO) { + alc882_gpio_mute(codec, 0, 0); + alc882_gpio_mute(codec, 1, 0); + } + spec->stream_name_analog = "ALC882 Analog"; spec->stream_analog_playback = &alc882_pcm_analog_playback; spec->stream_analog_capture = &alc882_pcm_analog_capture; @@ -4767,6 +5127,13 @@ static struct hda_channel_mode alc883_sixstack_modes[2] = { { 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 */ @@ -4788,8 +5155,10 @@ static struct snd_kcontrol_new alc883_base_mixer[] = { 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), @@ -4818,8 +5187,10 @@ static struct snd_kcontrol_new alc883_3ST_2ch_mixer[] = { 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), @@ -4854,8 +5225,10 @@ static struct snd_kcontrol_new alc883_3ST_6ch_mixer[] = { 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), @@ -4875,6 +5248,101 @@ static struct snd_kcontrol_new alc883_3ST_6ch_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc883_fivestack_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Front Playback Switch", 0x14, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Surround Playback Switch", 0x15, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("Center Playback Volume", 0x0e, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME_MONO("LFE Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Center Playback Switch", 0x16, 1, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("LFE Playback Switch", 0x16, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE("Headphone Playback Switch", 0x1b, 0x0, HDA_OUTPUT), + HDA_CODEC_VOLUME("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("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_chmode_mixer[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -4963,6 +5431,45 @@ static struct hda_verb alc883_init_verbs[] = { { } }; +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 */ +}; + +/* toggle speaker-output according to the hp-jack state */ +static void alc883_tagra_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_update(codec, 0x1b, 0, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x1b, 1, HDA_OUTPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_write(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); +} + /* * generic initialization of ADC, input mixers and output mixers */ @@ -5057,32 +5564,42 @@ static struct snd_kcontrol_new alc883_capture_mixer[] = { /* * configuration and preset */ -static struct hda_board_config alc883_cfg_tbl[] = { - { .modelname = "3stack-dig", .config = ALC883_3ST_2ch_DIG }, - { .modelname = "3stack-6ch-dig", .config = ALC883_3ST_6ch_DIG }, - { .pci_subvendor = 0x1019, .pci_subdevice = 0x6668, - .config = ALC883_3ST_6ch_DIG }, /* ECS to Intel*/ - { .modelname = "3stack-6ch", .config = ALC883_3ST_6ch }, - { .pci_subvendor = 0x108e, .pci_subdevice = 0x534d, - .config = ALC883_3ST_6ch }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xd601, - .config = ALC883_3ST_6ch }, /* D102GGC */ - { .modelname = "6stack-dig", .config = ALC883_6ST_DIG }, - { .pci_subvendor = 0x1462, .pci_subdevice = 0x6668, - .config = ALC883_6ST_DIG }, /* MSI */ - { .pci_subvendor = 0x1462, .pci_subdevice = 0x7280, - .config = ALC883_6ST_DIG }, /* MSI K9A Platinum (MS-7280) */ - { .pci_subvendor = 0x105b, .pci_subdevice = 0x6668, - .config = ALC883_6ST_DIG }, /* Foxconn */ - { .modelname = "6stack-dig-demo", .config = ALC888_DEMO_BOARD }, - { .modelname = "acer", .config = ALC883_ACER }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0/*0x0102*/, - .config = ALC883_ACER }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x0102, - .config = ALC883_ACER }, - { .pci_subvendor = 0x1025, .pci_subdevice = 0x009f, - .config = ALC883_ACER }, - { .modelname = "auto", .config = ALC883_AUTO }, +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", + [ALC888_DEMO_BOARD] = "6stack-dig-demo", + [ALC883_ACER] = "acer", + [ALC883_MEDION] = "medion", + [ALC883_LAPTOP_EAPD] = "laptop-eapd", + [ALC883_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc883_cfg_tbl[] = { + SND_PCI_QUIRK(0x1019, 0x6668, "ECS", ALC883_3ST_6ch_DIG), + SND_PCI_QUIRK(0x108e, 0x534d, NULL, ALC883_3ST_6ch), + SND_PCI_QUIRK(0x1558, 0, "Clevo laptop", ALC883_LAPTOP_EAPD), + SND_PCI_QUIRK(0x105b, 0x6668, "Foxconn", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x6668, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7187, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x7280, "MSI", ALC883_6ST_DIG), + SND_PCI_QUIRK(0x1462, 0x0579, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x3ef9, "MSI", ALC883_TARGA_DIG), + SND_PCI_QUIRK(0x1462, 0x3b7f, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1462, 0x3fcc, "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, 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, 0xa422, "MSI", ALC883_TARGA_2ch_DIG), + SND_PCI_QUIRK(0x1025, 0, "Acer laptop", ALC883_ACER), + SND_PCI_QUIRK(0x161f, 0x2054, "Medion laptop", ALC883_MEDION), + SND_PCI_QUIRK(0x1071, 0x8258, "Evesham Voyaeger", ALC883_LAPTOP_EAPD), + SND_PCI_QUIRK(0x8086, 0xd601, "D102GGC", ALC883_3ST_6ch), {} }; @@ -5139,6 +5656,35 @@ static struct alc_config_preset alc883_presets[] = { .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_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_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, + .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_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_nids, + .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, + }, [ALC888_DEMO_BOARD] = { .mixers = { alc883_base_mixer, alc883_chmode_mixer }, .init_verbs = { alc883_init_verbs }, @@ -5169,6 +5715,31 @@ static struct alc_config_preset alc883_presets[] = { .channel_mode = alc883_3ST_2ch_modes, .input_mux = &alc883_capture_source, }, + [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_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, + .input_mux = &alc883_capture_source, + }, + [ALC883_LAPTOP_EAPD] = { + .mixers = { alc883_base_mixer, + alc883_chmode_mixer }, + .init_verbs = { alc883_init_verbs, alc882_eapd_verbs }, + .num_dacs = ARRAY_SIZE(alc883_dac_nids), + .dac_nids = alc883_dac_nids, + .num_adc_nids = ARRAY_SIZE(alc883_adc_nids), + .adc_nids = alc883_adc_nids, + .num_channel_mode = ARRAY_SIZE(alc883_3ST_2ch_modes), + .channel_mode = alc883_3ST_2ch_modes, + .input_mux = &alc883_capture_source, + }, }; @@ -5277,8 +5848,10 @@ static int patch_alc883(struct hda_codec *codec) codec->spec = spec; - board_config = snd_hda_check_board_config(codec, alc883_cfg_tbl); - if (board_config < 0 || board_config >= ALC883_MODEL_LAST) { + 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; @@ -5355,6 +5928,24 @@ static struct snd_kcontrol_new alc262_base_mixer[] = { { } /* 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("Front Mic Playback Volume", 0x0b, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Front Mic Playback Switch", 0x0b, 0x01, HDA_INPUT), + /* HDA_CODEC_VOLUME("PC Beep Playback Volume", 0x0b, 0x05, HDA_INPUT), + HDA_CODEC_MUTE("PC Beelp 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 */ +}; + static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = { HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT), HDA_CODEC_MUTE("Front Playback Switch", 0x15, 0x0, HDA_OUTPUT), @@ -5377,6 +5968,30 @@ static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc262_HP_BPC_WildWest_mixer[] = { + 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("Mono Playback Volume", 0x0e, 2, 0x0, HDA_OUTPUT), + HDA_CODEC_MUTE_MONO("Mono 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("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), + { } /* end */ +}; + #define alc262_capture_mixer alc882_capture_mixer #define alc262_capture_alt_mixer alc882_capture_alt_mixer @@ -5459,6 +6074,103 @@ static struct hda_verb alc262_init_verbs[] = { { } }; +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}, + {} +}; + +/* mute/unmute internal speaker according to the hp jack and mute state */ +static void alc262_hippo_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, 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; + spec->sense_updated = 1; + } + if (spec->jack_present) { + /* mute internal speaker */ + snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + 0x80, 0x80); + snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + 0x80, 0x80); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x15, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + 0x80, mute & 0x80); + mute = snd_hda_codec_amp_read(codec, 0x15, 1, HDA_OUTPUT, 0); + snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + 0x80, mute & 0x80); + } +} + +/* 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, 1); +} + +static void alc262_hippo1_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, 0x1b, 0, AC_VERB_SET_PIN_SENSE, 0); + present = snd_hda_codec_read(codec, 0x1b, 0, + AC_VERB_GET_PIN_SENSE, 0); + spec->jack_present = (present & 0x80000000) != 0; + spec->sense_updated = 1; + } + if (spec->jack_present) { + /* mute internal speaker */ + snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + 0x80, 0x80); + snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + 0x80, 0x80); + } else { + /* unmute internal speaker if necessary */ + mute = snd_hda_codec_amp_read(codec, 0x1b, 0, HDA_OUTPUT, 0); + snd_hda_codec_amp_update(codec, 0x14, 0, HDA_OUTPUT, 0, + 0x80, mute & 0x80); + mute = snd_hda_codec_amp_read(codec, 0x1b, 1, HDA_OUTPUT, 0); + snd_hda_codec_amp_update(codec, 0x14, 1, HDA_OUTPUT, 0, + 0x80, mute & 0x80); + } +} + +/* 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, 1); +} + /* * fujitsu model * 0x14 = headphone/spdif-out, 0x15 = internal speaker @@ -5809,6 +6521,100 @@ static struct hda_verb alc262_HP_BPC_init_verbs[] = { { } }; +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)}, + + /* 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_UNMUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(6)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(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))}, + + { } +}; + /* pcm configuration: identiacal with ALC880 */ #define alc262_pcm_analog_playback alc880_pcm_analog_playback #define alc262_pcm_analog_capture alc880_pcm_analog_capture @@ -5866,26 +6672,35 @@ static void alc262_auto_init(struct hda_codec *codec) /* * configuration and preset */ -static struct hda_board_config alc262_cfg_tbl[] = { - { .modelname = "basic", .config = ALC262_BASIC }, - { .modelname = "fujitsu", .config = ALC262_FUJITSU }, - { .pci_subvendor = 0x10cf, .pci_subdevice = 0x1397, - .config = ALC262_FUJITSU }, - { .modelname = "hp-bpc", .config = ALC262_HP_BPC }, - { .pci_subvendor = 0x103c, .pci_subdevice = 0x280c, - .config = ALC262_HP_BPC }, /* xw4400 */ - { .pci_subvendor = 0x103c, .pci_subdevice = 0x2801, - .config = ALC262_HP_BPC }, /* q965 */ - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3014, - .config = ALC262_HP_BPC }, /* xw6400 */ - { .pci_subvendor = 0x103c, .pci_subdevice = 0x3015, - .config = ALC262_HP_BPC }, /* xw8400 */ - { .pci_subvendor = 0x103c, .pci_subdevice = 0x12fe, - .config = ALC262_HP_BPC }, /* xw9400 */ - { .modelname = "benq", .config = ALC262_BENQ_ED8 }, - { .pci_subvendor = 0x17ff, .pci_subdevice = 0x0560, - .config = ALC262_BENQ_ED8 }, - { .modelname = "auto", .config = ALC262_AUTO }, +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_BENQ_ED8] = "benq", + [ALC262_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc262_cfg_tbl[] = { + SND_PCI_QUIRK(0x1002, 0x437b, "Hippo", ALC262_HIPPO), + SND_PCI_QUIRK(0x103c, 0x12fe, "HP xw9400", ALC262_HP_BPC), + 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, 0x2800, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2802, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2804, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2806, "HP D7000", ALC262_HP_BPC_D7000_WL), + SND_PCI_QUIRK(0x103c, 0x2801, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2803, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2805, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x103c, 0x2807, "HP D7000", ALC262_HP_BPC_D7000_WF), + SND_PCI_QUIRK(0x104d, 0x8203, "Sony UX-90", ALC262_HIPPO), + SND_PCI_QUIRK(0x10cf, 0x1397, "Fujitsu", ALC262_FUJITSU), + SND_PCI_QUIRK(0x17ff, 0x058f, "Benq Hippo", ALC262_HIPPO_1), + SND_PCI_QUIRK(0x17ff, 0x0560, "Benq ED8", ALC262_BENQ_ED8), {} }; @@ -5900,6 +6715,30 @@ static struct alc_config_preset alc262_presets[] = { .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, + }, + [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, + }, [ALC262_FUJITSU] = { .mixers = { alc262_fujitsu_mixer }, .init_verbs = { alc262_init_verbs, alc262_fujitsu_unsol_verbs }, @@ -5922,6 +6761,27 @@ static struct alc_config_preset alc262_presets[] = { .channel_mode = alc262_modes, .input_mux = &alc262_HP_capture_source, }, + [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_capture_source, + }, + [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_capture_source, + }, [ALC262_BENQ_ED8] = { .mixers = { alc262_base_mixer }, .init_verbs = { alc262_init_verbs, alc262_EAPD_verbs }, @@ -5940,7 +6800,7 @@ static int patch_alc262(struct hda_codec *codec) int board_config; int err; - spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + spec = kzalloc(sizeof(*spec), GFP_KERNEL); if (spec == NULL) return -ENOMEM; @@ -5956,9 +6816,11 @@ static int patch_alc262(struct hda_codec *codec) } #endif - board_config = snd_hda_check_board_config(codec, alc262_cfg_tbl); - - if (board_config < 0 || board_config >= ALC262_MODEL_LAST) { + 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; @@ -6078,6 +6940,44 @@ static struct hda_channel_mode alc861_uniwill_m31_modes[2] = { { 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[] = { @@ -6154,7 +7054,29 @@ static struct snd_kcontrol_new alc861_3ST_mixer[] = { .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), @@ -6196,7 +7118,58 @@ static struct snd_kcontrol_new alc861_uniwill_m31_mixer[] = { }, { } /* 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), /* was HDA_INPUT (why?) */ + + /* 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 */ @@ -6217,7 +7190,7 @@ static struct hda_verb alc861_base_init_verbs[] = { /* 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, 0x01 }, + { 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) */ @@ -6281,7 +7254,7 @@ static struct hda_verb alc861_threestack_init_verbs[] = { /* 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, 0x01 }, + { 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) */ @@ -6341,7 +7314,7 @@ static struct hda_verb alc861_uniwill_m31_init_verbs[] = { /* port-E for HP out (front panel) */ { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, // this has to be set to VREF80 /* route front PCM to HP */ - { 0x0f, AC_VERB_SET_CONNECT_SEL, 0x01 }, + { 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) */ @@ -6385,6 +7358,74 @@ static struct hda_verb alc861_uniwill_m31_init_verbs[] = { { } }; +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) */ + { 0x0f, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x24 }, /* this has to be set to VREF80 */ + /* 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)}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, /* hp used DAC 3 (Front) */ + {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 */ @@ -6437,6 +7478,39 @@ static struct hda_verb alc861_auto_init_verbs[] = { { } }; +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_update(codec, 0x16, 0, HDA_INPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x16, 1, HDA_INPUT, 0, + 0x80, present ? 0x80 : 0); + snd_hda_codec_amp_update(codec, 0x1a, 0, HDA_INPUT, 3, + 0x80, present ? 0 : 0x80); + snd_hda_codec_amp_update(codec, 0x1a, 1, HDA_INPUT, 3, + 0x80, present ? 0 : 0x80); +} + +static void alc861_toshiba_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Looks like the unsol event is incompatible with the standard + * definition. 6bit tag is placed at 26 bit! + */ + 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 @@ -6710,19 +7784,29 @@ static void alc861_auto_init(struct hda_codec *codec) /* * configuration and preset */ -static struct hda_board_config alc861_cfg_tbl[] = { - { .modelname = "3stack", .config = ALC861_3ST }, - { .pci_subvendor = 0x8086, .pci_subdevice = 0xd600, - .config = ALC861_3ST }, - { .modelname = "3stack-660", .config = ALC660_3ST }, - { .pci_subvendor = 0x1043, .pci_subdevice = 0x81e7, - .config = ALC660_3ST }, - { .modelname = "3stack-dig", .config = ALC861_3ST_DIG }, - { .modelname = "6stack-dig", .config = ALC861_6ST_DIG }, - { .modelname = "uniwill-m31", .config = ALC861_UNIWILL_M31}, - { .pci_subvendor = 0x1584, .pci_subdevice = 0x9072, - .config = ALC861_UNIWILL_M31 }, - { .modelname = "auto", .config = ALC861_AUTO }, +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, 0x81e7, "ASUS", ALC660_3ST), + SND_PCI_QUIRK(0x1179, 0xff00, "Toshiba", ALC861_TOSHIBA), + SND_PCI_QUIRK(0x1179, 0xff10, "Toshiba", ALC861_TOSHIBA), + SND_PCI_QUIRK(0x1584, 0x9072, "Uniwill m31", ALC861_UNIWILL_M31), + SND_PCI_QUIRK(0x1584, 0x2b01, "Uniwill X40AIx", ALC861_UNIWILL_M31), + SND_PCI_QUIRK(0x8086, 0xd600, "Intel", ALC861_3ST), {} }; @@ -6789,8 +7873,48 @@ static struct alc_config_preset alc861_presets[] = { .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) @@ -6799,15 +7923,17 @@ static int patch_alc861(struct hda_codec *codec) int board_config; int err; - spec = kcalloc(1, sizeof(*spec), GFP_KERNEL); + spec = kzalloc(sizeof(*spec), GFP_KERNEL); if (spec == NULL) return -ENOMEM; codec->spec = spec; - board_config = snd_hda_check_board_config(codec, alc861_cfg_tbl); + board_config = snd_hda_check_board_config(codec, ALC861_MODEL_LAST, + alc861_models, + alc861_cfg_tbl); - if (board_config < 0 || board_config >= ALC861_MODEL_LAST) { + if (board_config < 0) { printk(KERN_INFO "hda_codec: Unknown model for ALC861, " "trying auto-probe from BIOS...\n"); board_config = ALC861_AUTO; @@ -6846,19 +7972,706 @@ static int patch_alc861(struct hda_codec *codec) } /* + * 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, +}; + +/* 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 }, + }, +}; + +#define alc861vd_mux_enum_info alc_mux_enum_info +#define alc861vd_mux_enum_get alc_mux_enum_get + +static int alc861vd_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); + static hda_nid_t capture_mixers[1] = { 0x22 }; + hda_nid_t nid = capture_mixers[adc_idx]; + unsigned int *cur_val = &spec->cur_mux[adc_idx]; + unsigned int i, idx; + + idx = ucontrol->value.enumerated.item[0]; + if (idx >= imux->num_items) + idx = imux->num_items - 1; + if (*cur_val == idx && ! codec->in_resume) + return 0; + for (i = 0; i < imux->num_items; i++) { + unsigned int v = (i == idx) ? 0x7000 : 0x7080; + snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_AMP_GAIN_MUTE, + v | (imux->items[i].index << 8)); + } + *cur_val = idx; + return 1; +} + +/* + * 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.. + *FIXME: the controls appear in the "playback" view! + */ + /* .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 */ +}; + +/* + * 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_UNMUTE(0)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(3)}, + {0x0b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + + /* Capture mixer: unmute Mic, F-Mic, Line, CD inputs */ + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(4)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(5)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(6)}, + {0x22, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(8)}, + + /* + * 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}, + + { } +}; + +/* 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", + [ALC861VD_3ST] = "3stack", + [ALC861VD_3ST_DIG] = "3stack-digout", + [ALC861VD_6ST_DIG] = "6stack-digout", + [ALC861VD_AUTO] = "auto", +}; + +static struct snd_pci_quirk alc861vd_cfg_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1339, "Asus G1", ALC660VD_3ST), + SND_PCI_QUIRK(0x10de, 0x03f0, "Realtek ALC660 demo", ALC660VD_3ST), + SND_PCI_QUIRK(0x1019, 0xa88d, "Realtek ALC660 demo", ALC660VD_3ST), + + SND_PCI_QUIRK(0x17aa, 0x3802, "Lenovo 3000 C200", ALC861VD_3ST), + {} +}; + +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_adc_nids = ARRAY_SIZE(alc861vd_adc_nids), + .adc_nids = alc861vd_adc_nids, + .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, + }, +}; + +/* + * 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) +{ + /* 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 alc861vd_auto_init_multi_out(struct hda_codec *codec) +{ + struct alc_spec *spec = codec->spec; + int i; + + for (i = 0; i <= HDA_SIDE; i++) { + hda_nid_t nid = spec->autocfg.line_out_pins[i]; + if (nid) + alc861vd_auto_set_output_and_unmute(codec, nid, + PIN_OUT, 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); +} + +#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_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 */ + if ((err = add_control(spec, ALC_CTL_WIDGET_VOL, + "Center Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_v, 1, + 0, HDA_OUTPUT))) < 0) + return err; + if ((err = add_control(spec, ALC_CTL_WIDGET_VOL, + "LFE Playback Volume", + HDA_COMPOSE_AMP_VAL(nid_v, 2, + 0, HDA_OUTPUT))) < 0) + return err; + if ((err = add_control(spec, ALC_CTL_BIND_MUTE, + "Center Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_s, 1, + 2, HDA_INPUT))) < 0) + return err; + if ((err = add_control(spec, ALC_CTL_BIND_MUTE, + "LFE Playback Switch", + HDA_COMPOSE_AMP_VAL(nid_s, 2, + 2, HDA_INPUT))) < 0) + return err; + } else { + sprintf(name, "%s Playback Volume", chname[i]); + if ((err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_v, 3, + 0, HDA_OUTPUT))) < 0) + return err; + sprintf(name, "%s Playback Switch", chname[i]); + if ((err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_v, 3, + 2, HDA_INPUT))) < 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); + if ((err = add_control(spec, ALC_CTL_WIDGET_VOL, name, + HDA_COMPOSE_AMP_VAL(nid_v, 3, 0, + HDA_OUTPUT))) < 0) + return err; + sprintf(name, "%s Playback Switch", pfx); + if ((err = add_control(spec, ALC_CTL_BIND_MUTE, name, + HDA_COMPOSE_AMP_VAL(nid_s, 3, 2, + HDA_INPUT))) < 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); + if ((err = add_control(spec, ALC_CTL_WIDGET_MUTE, name, + HDA_COMPOSE_AMP_VAL(pin, 3, 0, + HDA_OUTPUT))) < 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 }; + + if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, + alc861vd_ignore)) < 0) + return err; + if (! spec->autocfg.line_outs) + return 0; /* can't find valid BIOS pin config */ + + if ((err = alc880_auto_fill_dac_nids(spec, &spec->autocfg)) < 0 || + (err = alc861vd_auto_create_multi_out_ctls(spec, + &spec->autocfg)) < 0 || + (err = alc861vd_auto_create_extra_out(spec, + spec->autocfg.speaker_pins[0], "Speaker")) < 0 || + (err = alc861vd_auto_create_extra_out(spec, + spec->autocfg.hp_pins[0], "Headphone")) < 0 || + (err = alc880_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 = 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; + + return 1; +} + +/* additional initialization for auto-configuration model */ +static void alc861vd_auto_init(struct hda_codec *codec) +{ + alc861vd_auto_init_multi_out(codec); + alc861vd_auto_init_hp_out(codec); + alc861vd_auto_init_analog_input(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]); + + spec->stream_name_analog = "ALC861VD Analog"; + spec->stream_analog_playback = &alc861vd_pcm_analog_playback; + spec->stream_analog_capture = &alc861vd_pcm_analog_capture; + + spec->stream_name_digital = "ALC861VD Digital"; + 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->mixers[spec->num_mixers] = alc861vd_capture_mixer; + spec->num_mixers++; + + codec->patch_ops = alc_patch_ops; + + if (board_config == ALC861VD_AUTO) + spec->init_hook = alc861vd_auto_init; + + 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 = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 }, + { .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 = 0x10ec0880, .name = "ALC880", .patch = patch_alc880 }, { .id = 0x10ec0882, .name = "ALC882", .patch = patch_alc882 }, { .id = 0x10ec0883, .name = "ALC883", .patch = patch_alc883 }, { .id = 0x10ec0885, .name = "ALC885", .patch = patch_alc882 }, { .id = 0x10ec0888, .name = "ALC888", .patch = patch_alc883 }, - { .id = 0x10ec0861, .rev = 0x100300, .name = "ALC861", - .patch = patch_alc861 }, - { .id = 0x10ec0861, .rev = 0x100340, .name = "ALC660", - .patch = patch_alc861 }, {} /* terminator */ }; diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c index fe51ef3e49d..6f4a39273b9 100644 --- a/sound/pci/hda/patch_sigmatel.c +++ b/sound/pci/hda/patch_sigmatel.c @@ -37,14 +37,37 @@ #define NUM_CONTROL_ALLOC 32 #define STAC_HP_EVENT 0x37 -#define STAC_REF 0 -#define STAC_D945GTP3 1 -#define STAC_D945GTP5 2 -#define STAC_MACMINI 3 -#define STAC_922X_MODELS 4 /* number of 922x models */ -#define STAC_D965_3ST 4 -#define STAC_D965_5ST 5 -#define STAC_927X_MODELS 6 /* number of 922x models */ +enum { + STAC_REF, + STAC_9200_MODELS +}; + +enum { + STAC_9205_REF, + STAC_9205_MODELS +}; + +enum { + STAC_925x_REF, + STAC_M2_2, + STAC_MA6, + STAC_925x_MODELS +}; + +enum { + STAC_D945_REF, + STAC_D945GTP3, + STAC_D945GTP5, + STAC_MACMINI, + STAC_922X_MODELS +}; + +enum { + STAC_D965_REF, + STAC_D965_3ST, + STAC_D965_5ST, + STAC_927X_MODELS +}; struct sigmatel_spec { struct snd_kcontrol_new *mixers[4]; @@ -67,6 +90,9 @@ struct sigmatel_spec { 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_nid; hda_nid_t dig_in_nid; /* pin widgets */ @@ -80,6 +106,8 @@ struct sigmatel_spec { struct snd_kcontrol_new *mixer; /* capture source */ + struct hda_input_mux *dinput_mux; + unsigned int cur_dmux; struct hda_input_mux *input_mux; unsigned int cur_mux[3]; @@ -92,6 +120,7 @@ struct sigmatel_spec { 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; }; @@ -107,6 +136,18 @@ static hda_nid_t stac9200_dac_nids[1] = { 0x02, }; +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, +}; + static hda_nid_t stac922x_adc_nids[2] = { 0x06, 0x07, }; @@ -131,11 +172,20 @@ static hda_nid_t stac9205_mux_nids[2] = { 0x19, 0x1a }; +static hda_nid_t stac9205_dmic_nids[3] = { + 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, @@ -154,6 +204,34 @@ static hda_nid_t stac9205_pin_nids[12] = { }; +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; + + ucontrol->value.enumerated.item[0] = spec->cur_dmux; + 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; + + return snd_hda_input_mux_put(codec, spec->dinput_mux, ucontrol, + spec->dmux_nid, &spec->cur_dmux); +} + static int stac92xx_mux_enum_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { struct hda_codec *codec = snd_kcontrol_chip(kcontrol); @@ -187,6 +265,12 @@ static struct hda_verb stac9200_core_init[] = { {} }; +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}, @@ -232,6 +316,23 @@ static struct snd_kcontrol_new stac9200_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new stac925x_mixer[] = { + HDA_CODEC_VOLUME("Master Playback Volume", 0xe, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Master Playback Switch", 0xe, 0, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Source", + .count = 1, + .info = stac92xx_mux_enum_info, + .get = stac92xx_mux_enum_get, + .put = stac92xx_mux_enum_put, + }, + HDA_CODEC_VOLUME("Capture Volume", 0x09, 0, HDA_OUTPUT), + HDA_CODEC_MUTE("Capture Switch", 0x09, 0, HDA_OUTPUT), + HDA_CODEC_VOLUME("Capture Mux Volume", 0x0f, 0, HDA_OUTPUT), + { } /* end */ +}; + /* This needs to be generated dynamically based on sequence */ static struct snd_kcontrol_new stac922x_mixer[] = { { @@ -263,7 +364,7 @@ static struct snd_kcontrol_new stac9227_mixer[] = { { } /* end */ }; -static snd_kcontrol_new_t stac927x_mixer[] = { +static struct snd_kcontrol_new stac927x_mixer[] = { { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Source", @@ -278,7 +379,15 @@ static snd_kcontrol_new_t stac927x_mixer[] = { { } /* end */ }; -static snd_kcontrol_new_t stac9205_mixer[] = { +static struct snd_kcontrol_new stac9205_mixer[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Digital Input Source", + .count = 1, + .info = stac92xx_dmux_enum_info, + .get = stac92xx_dmux_enum_get, + .put = stac92xx_dmux_enum_put, + }, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Input Source", @@ -327,22 +436,64 @@ static unsigned int ref9200_pin_configs[8] = { 0x02a19020, 0x01a19021, 0x90100140, 0x01813122, }; -static unsigned int *stac9200_brd_tbl[] = { - ref9200_pin_configs, +static unsigned int *stac9200_brd_tbl[STAC_9200_MODELS] = { + [STAC_REF] = ref9200_pin_configs, +}; + +static const char *stac9200_models[STAC_9200_MODELS] = { + [STAC_REF] = "ref", }; -static struct hda_board_config stac9200_cfg_tbl[] = { - { .modelname = "ref", - .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2668, /* DFI LanParty */ - .config = STAC_REF }, +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 */ - { .pci_subvendor = PCI_VENDOR_ID_DELL, .pci_subdevice = 0x01b5, - .config = STAC_REF }, /* Dell Inspiron 630m */ - { .pci_subvendor = PCI_VENDOR_ID_DELL, .pci_subdevice = 0x01c2, - .config = STAC_REF }, /* Dell Latitude D620 */ - { .pci_subvendor = PCI_VENDOR_ID_DELL, .pci_subdevice = 0x01cb, - .config = STAC_REF }, /* Dell Latitude 120L */ + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01b5, + "Dell Inspiron 630m", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01c2, + "Dell Latitude D620", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cb, + "Dell Latitude 120L", STAC_REF), + SND_PCI_QUIRK(PCI_VENDOR_ID_DELL, 0x01cc, + "Dell Latitude D820", STAC_REF), + {} /* terminator */ +}; + +static unsigned int ref925x_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x90a70320, 0x02214210, 0x400003f1, 0x9033032e, +}; + +static unsigned int stac925x_MA6_pin_configs[8] = { + 0x40c003f0, 0x424503f2, 0x01813022, 0x02a19021, + 0x90a70320, 0x90100211, 0x400003f1, 0x9033032e, +}; + +static unsigned int stac925xM2_2_pin_configs[8] = { + 0x40c003f3, 0x424503f2, 0x041800f4, 0x02a19020, + 0x50a103F0, 0x90100210, 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, +}; + +static const char *stac925x_models[STAC_925x_MODELS] = { + [STAC_REF] = "ref", + [STAC_M2_2] = "m2-2", + [STAC_MA6] = "m6", +}; + +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(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(0x1002, 0x437b, "Gateway MX6453", STAC_M2_2), {} /* terminator */ }; @@ -365,100 +516,80 @@ static unsigned int d945gtp5_pin_configs[10] = { }; static unsigned int *stac922x_brd_tbl[STAC_922X_MODELS] = { - [STAC_REF] = ref922x_pin_configs, + [STAC_D945_REF] = ref922x_pin_configs, [STAC_D945GTP3] = d945gtp3_pin_configs, [STAC_D945GTP5] = d945gtp5_pin_configs, [STAC_MACMINI] = d945gtp5_pin_configs, }; -static struct hda_board_config stac922x_cfg_tbl[] = { - { .modelname = "5stack", .config = STAC_D945GTP5 }, - { .modelname = "3stack", .config = STAC_D945GTP3 }, - { .modelname = "ref", - .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2668, /* DFI LanParty */ - .config = STAC_REF }, /* SigmaTel reference board */ - /* Intel 945G based systems */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0101, - .config = STAC_D945GTP3 }, /* Intel D945GTP - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0202, - .config = STAC_D945GTP3 }, /* Intel D945GNT - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0606, - .config = STAC_D945GTP3 }, /* Intel D945GTP - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0601, - .config = STAC_D945GTP3 }, /* Intel D945GTP - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0111, - .config = STAC_D945GTP3 }, /* Intel D945GZP - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x1115, - .config = STAC_D945GTP3 }, /* Intel D945GPM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x1116, - .config = STAC_D945GTP3 }, /* Intel D945GBO - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x1117, - .config = STAC_D945GTP3 }, /* Intel D945GPM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x1118, - .config = STAC_D945GTP3 }, /* Intel D945GPM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x1119, - .config = STAC_D945GTP3 }, /* Intel D945GPM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x8826, - .config = STAC_D945GTP3 }, /* Intel D945GPM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x5049, - .config = STAC_D945GTP3 }, /* Intel D945GCZ - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x5055, - .config = STAC_D945GTP3 }, /* Intel D945GCZ - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x5048, - .config = STAC_D945GTP3 }, /* Intel D945GPB - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0110, - .config = STAC_D945GTP3 }, /* Intel D945GLR - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0404, - .config = STAC_D945GTP5 }, /* Intel D945GTP - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0303, - .config = STAC_D945GTP5 }, /* Intel D945GNT - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0013, - .config = STAC_D945GTP5 }, /* Intel D955XBK - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0417, - .config = STAC_D945GTP5 }, /* Intel D975XBK - 5 Stack */ - /* Intel 945P based systems */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0b0b, - .config = STAC_D945GTP3 }, /* Intel D945PSN - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0112, - .config = STAC_D945GTP3 }, /* Intel D945PLN - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0d0d, - .config = STAC_D945GTP3 }, /* Intel D945PLM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0909, - .config = STAC_D945GTP3 }, /* Intel D945PAW - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0505, - .config = STAC_D945GTP3 }, /* Intel D945PLM - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x0707, - .config = STAC_D945GTP5 }, /* Intel D945PSV - 5 Stack */ - /* other systems */ - { .pci_subvendor = 0x8384, - .pci_subdevice = 0x7680, - .config = STAC_MACMINI }, /* Apple Mac Mini (early 2006) */ +static const char *stac922x_models[STAC_922X_MODELS] = { + [STAC_D945_REF] = "ref", + [STAC_D945GTP5] = "5stack", + [STAC_D945GTP3] = "3stack", + [STAC_MACMINI] = "macmini", +}; + +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 Mac Mini (early 2006) */ + SND_PCI_QUIRK(0x8384, 0x7680, + "Mac Mini", STAC_MACMINI), {} /* terminator */ }; @@ -484,120 +615,72 @@ static unsigned int d965_5st_pin_configs[14] = { }; static unsigned int *stac927x_brd_tbl[STAC_927X_MODELS] = { - [STAC_REF] = ref927x_pin_configs, + [STAC_D965_REF] = ref927x_pin_configs, [STAC_D965_3ST] = d965_3st_pin_configs, [STAC_D965_5ST] = d965_5st_pin_configs, }; -static struct hda_board_config stac927x_cfg_tbl[] = { - { .modelname = "5stack", .config = STAC_D965_5ST }, - { .modelname = "3stack", .config = STAC_D965_3ST }, - { .modelname = "ref", - .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2668, /* DFI LanParty */ - .config = STAC_REF }, /* SigmaTel reference board */ +static const char *stac927x_models[STAC_927X_MODELS] = { + [STAC_D965_REF] = "ref", + [STAC_D965_3ST] = "3stack", + [STAC_D965_5ST] = "5stack", +}; + +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 */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x3d01, - .config = STAC_D965_3ST }, /* D946 configuration */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0xa301, - .config = STAC_D965_3ST }, /* Intel D946GZT - 3 stack */ + 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 */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2116, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2115, - .config = STAC_D965_3ST }, /* Intel DQ965WC - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2114, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2113, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2112, - .config = STAC_D965_3ST }, /* Intel DG965MS - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2111, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2110, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2009, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2008, - .config = STAC_D965_3ST }, /* Intel DQ965GF - 3 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2007, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2006, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2005, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2004, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2003, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2002, - .config = STAC_D965_3ST }, /* Intel D965 3Stack config */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2001, - .config = STAC_D965_3ST }, /* Intel DQ965GF - 3 Stack */ + 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), /* 965 based 5 stack systems */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2301, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2302, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2303, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2304, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2305, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2501, - .config = STAC_D965_5ST }, /* Intel DG965MQ - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2502, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2503, - .config = STAC_D965_5ST }, /* Intel DG965 - 5 Stack */ - { .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2504, - .config = STAC_D965_5ST }, /* Intel DQ965GF - 5 Stack */ + 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, 0x40000100, 0x40000100, - 0x40000100, 0x40000100, 0x01441030, 0x01c41030 + 0x01813122, 0x01a19021, 0x40000100, 0x40000100, + 0x90a000f0, 0x90a000f0, 0x01441030, 0x01c41030 }; -static unsigned int *stac9205_brd_tbl[] = { +static unsigned int *stac9205_brd_tbl[STAC_9205_MODELS] = { ref9205_pin_configs, }; -static struct hda_board_config stac9205_cfg_tbl[] = { - { .modelname = "ref", - .pci_subvendor = PCI_VENDOR_ID_INTEL, - .pci_subdevice = 0x2668, /* DFI LanParty */ - .config = STAC_REF }, /* SigmaTel reference board */ +static const char *stac9205_models[STAC_9205_MODELS] = { + [STAC_9205_REF] = "ref", +}; + +static struct snd_pci_quirk stac9205_cfg_tbl[] = { + /* SigmaTel reference board */ + SND_PCI_QUIRK(PCI_VENDOR_ID_INTEL, 0x2668, + "DFI LanParty", STAC_9205_REF), {} /* terminator */ }; @@ -1154,6 +1237,58 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, 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 i, j; + + 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++) { + int index; + int num_cons; + 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; + + num_cons = snd_hda_get_connections(codec, + spec->dmux_nid, + con_lst, + HDA_MAX_NUM_INPUTS); + for (j = 0; j < num_cons; j++) + if (con_lst[j] == spec->dmic_nids[i]) { + index = j; + goto found; + } + continue; +found: + 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) { @@ -1238,7 +1373,9 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out struct sigmatel_spec *spec = codec->spec; int err; - if ((err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL)) < 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 */ @@ -1254,6 +1391,11 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out (err = stac92xx_auto_create_analog_input_ctls(codec, &spec->autocfg)) < 0) return err; + if (spec->num_dmics > 0) + if ((err = stac92xx_auto_create_dmic_input_ctls(codec, + &spec->autocfg)) < 0) + return err; + spec->multiout.max_channels = spec->multiout.num_dacs * 2; if (spec->multiout.max_channels > 2) spec->surr_switch = 1; @@ -1267,6 +1409,7 @@ static int stac92xx_parse_auto_config(struct hda_codec *codec, hda_nid_t dig_out spec->mixers[spec->num_mixers++] = spec->kctl_alloc; spec->input_mux = &spec->private_imux; + spec->dinput_mux = &spec->private_dimux; return 1; } @@ -1366,6 +1509,7 @@ static int stac9200_parse_auto_config(struct hda_codec *codec) spec->mixers[spec->num_mixers++] = spec->kctl_alloc; spec->input_mux = &spec->private_imux; + spec->dinput_mux = &spec->private_dimux; return 1; } @@ -1448,6 +1592,11 @@ static int stac92xx_init(struct hda_codec *codec) stac92xx_auto_set_pinctl(codec, nid, pinctl); } } + if (spec->num_dmics > 0) + 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); @@ -1598,7 +1747,9 @@ static int patch_stac9200(struct hda_codec *codec) codec->spec = spec; spec->num_pins = 8; spec->pin_nids = stac9200_pin_nids; - spec->board_config = snd_hda_check_board_config(codec, stac9200_cfg_tbl); + 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); @@ -1618,6 +1769,7 @@ static int patch_stac9200(struct hda_codec *codec) spec->adc_nids = stac9200_adc_nids; spec->mux_nids = stac9200_mux_nids; spec->num_muxes = 1; + spec->num_dmics = 0; spec->init = stac9200_core_init; spec->mixer = stac9200_mixer; @@ -1633,6 +1785,56 @@ static int patch_stac9200(struct hda_codec *codec) 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 = 8; + spec->pin_nids = stac925x_pin_nids; + spec->board_config = snd_hda_check_board_config(codec, STAC_925x_MODELS, + stac925x_models, + stac925x_cfg_tbl); + 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_dmics = 0; + + spec->init = stac925x_core_init; + spec->mixer = stac925x_mixer; + + err = stac92xx_parse_auto_config(codec, 0x8, 0x7); + if (err < 0) { + stac92xx_free(codec); + return err; + } + + codec->patch_ops = stac92xx_patch_ops; + + return 0; +} + static int patch_stac922x(struct hda_codec *codec) { struct sigmatel_spec *spec; @@ -1645,7 +1847,9 @@ static int patch_stac922x(struct hda_codec *codec) codec->spec = spec; spec->num_pins = 10; spec->pin_nids = stac922x_pin_nids; - spec->board_config = snd_hda_check_board_config(codec, stac922x_cfg_tbl); + spec->board_config = snd_hda_check_board_config(codec, STAC_922X_MODELS, + stac922x_models, + stac922x_cfg_tbl); if (spec->board_config < 0) { snd_printdd(KERN_INFO "hda_codec: Unknown model for STAC922x, " "using BIOS defaults\n"); @@ -1663,6 +1867,7 @@ static int patch_stac922x(struct hda_codec *codec) spec->adc_nids = stac922x_adc_nids; spec->mux_nids = stac922x_mux_nids; spec->num_muxes = 2; + spec->num_dmics = 0; spec->init = stac922x_core_init; spec->mixer = stac922x_mixer; @@ -1695,7 +1900,9 @@ static int patch_stac927x(struct hda_codec *codec) codec->spec = spec; spec->num_pins = 14; spec->pin_nids = stac927x_pin_nids; - spec->board_config = snd_hda_check_board_config(codec, stac927x_cfg_tbl); + spec->board_config = snd_hda_check_board_config(codec, STAC_927X_MODELS, + stac927x_models, + stac927x_cfg_tbl); 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); @@ -1714,6 +1921,7 @@ static int patch_stac927x(struct hda_codec *codec) spec->adc_nids = stac927x_adc_nids; spec->mux_nids = stac927x_mux_nids; spec->num_muxes = 3; + spec->num_dmics = 0; spec->init = d965_core_init; spec->mixer = stac9227_mixer; break; @@ -1721,6 +1929,7 @@ static int patch_stac927x(struct hda_codec *codec) spec->adc_nids = stac927x_adc_nids; spec->mux_nids = stac927x_mux_nids; spec->num_muxes = 3; + spec->num_dmics = 0; spec->init = d965_core_init; spec->mixer = stac9227_mixer; break; @@ -1728,6 +1937,7 @@ static int patch_stac927x(struct hda_codec *codec) spec->adc_nids = stac927x_adc_nids; spec->mux_nids = stac927x_mux_nids; spec->num_muxes = 3; + spec->num_dmics = 0; spec->init = stac927x_core_init; spec->mixer = stac927x_mixer; } @@ -1757,7 +1967,9 @@ static int patch_stac9205(struct hda_codec *codec) codec->spec = spec; spec->num_pins = 14; spec->pin_nids = stac9205_pin_nids; - spec->board_config = snd_hda_check_board_config(codec, stac9205_cfg_tbl); + spec->board_config = snd_hda_check_board_config(codec, STAC_9205_MODELS, + stac9205_models, + stac9205_cfg_tbl); 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); @@ -1773,13 +1985,28 @@ static int patch_stac9205(struct hda_codec *codec) spec->adc_nids = stac9205_adc_nids; spec->mux_nids = stac9205_mux_nids; - spec->num_muxes = 3; + spec->num_muxes = 2; + spec->dmic_nids = stac9205_dmic_nids; + spec->num_dmics = 2; + spec->dmux_nid = 0x1d; spec->init = stac9205_core_init; spec->mixer = stac9205_mixer; spec->multiout.dac_nids = spec->dac_nids; + /* Configure GPIO0 as EAPD output */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DIRECTION, 0x00000001); + /* Configure GPIO0 as CMOS */ + snd_hda_codec_write(codec, codec->afg, 0, 0x7e7, 0x00000000); + /* Assert GPIO0 high */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_DATA, 0x00000001); + /* Enable GPIO0 */ + snd_hda_codec_write(codec, codec->afg, 0, + AC_VERB_SET_GPIO_MASK, 0x00000001); + err = stac92xx_parse_auto_config(codec, 0x1f, 0x20); if (err < 0) { stac92xx_free(codec); @@ -1963,18 +2190,19 @@ enum { /* FE and SZ series. id=0x83847661 and subsys=0x104D0700 or 104D1000. */ /* Unknown. id=0x83847661 and subsys=0x104D1200. */ STAC9872K_VAIO, /* AR Series. id=0x83847664 and subsys=104D1300 */ - CXD9872AKD_VAIO - }; - -static struct hda_board_config stac9872_cfg_tbl[] = { - { .modelname = "vaio", .config = CXD9872RD_VAIO }, - { .modelname = "vaio-ar", .config = CXD9872AKD_VAIO }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81e6, - .config = CXD9872RD_VAIO }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81ef, - .config = CXD9872RD_VAIO }, - { .pci_subvendor = 0x104d, .pci_subdevice = 0x81fd, - .config = CXD9872AKD_VAIO }, + 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), {} }; @@ -1983,7 +2211,9 @@ static int patch_stac9872(struct hda_codec *codec) struct sigmatel_spec *spec; int board_config; - board_config = snd_hda_check_board_config(codec, stac9872_cfg_tbl); + 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); @@ -2055,6 +2285,12 @@ struct hda_codec_preset snd_hda_preset_sigmatel[] = { { .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 }, /* 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. diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c new file mode 100644 index 00000000000..4c839b03172 --- /dev/null +++ b/sound/pci/hda/patch_via.c @@ -0,0 +1,1396 @@ +/* + * Universal Interface for Intel High Definition Audio Codec + * + * HD audio interface patch for VIA VT1708 codec + * + * Copyright (c) 2006 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 */ +/* */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + +#include <sound/driver.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <sound/core.h> +#include "hda_codec.h" +#include "hda_local.h" + + +/* 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 VT1709_HP_DAC_NID 0x28 +#define VT1709_DIGOUT_NID 0x13 +#define VT1709_DIGIN_NID 0x17 + +#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) + + +enum { + VIA_CTL_WIDGET_VOL, + VIA_CTL_WIDGET_MUTE, +}; + +enum { + AUTO_SEQ_FRONT, + AUTO_SEQ_SURROUND, + AUTO_SEQ_CENLFE, + AUTO_SEQ_SIDE +}; + +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; + + char *stream_name_analog; + struct hda_pcm_stream *stream_analog_playback; + struct hda_pcm_stream *stream_analog_capture; + + char *stream_name_digital; + struct hda_pcm_stream *stream_digital_playback; + struct hda_pcm_stream *stream_digital_capture; + + /* playback */ + struct hda_multi_out multiout; + + /* capture */ + unsigned int num_adc_nids; + hda_nid_t *adc_nids; + hda_nid_t dig_in_nid; + + /* capture source */ + const struct hda_input_mux *input_mux; + unsigned int cur_mux[3]; + + /* PCM information */ + struct hda_pcm pcm_rec[2]; + + /* 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[4]; +}; + +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 +}; + +/* 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 + return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol, + spec->adc_nids[adc_idx], + &spec->cur_mux[adc_idx]); +} + +/* 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.. + * FIXME: the controls appear in the "playback" view! + */ + /* .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}, + /* Set mic as default input of sw0 */ + {0x18, AC_VERB_SET_CONNECT_SEL, 0x2}, + /* 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); +} + +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); +} + +/* + * 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); +} + +/* + * 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_setup_stream(codec, spec->adc_nids[substream->number], + 0, 0, 0); + return 0; +} + +static struct hda_pcm_stream vt1708_pcm_analog_playback = { + .substreams = 1, + .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 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 + }, +}; + +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; + } + 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; + 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); +} + +static int via_init(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + snd_hda_sequence_write(codec, spec->init_verbs); + return 0; +} + +#ifdef CONFIG_PM +/* + * resume + */ +static int via_resume(struct hda_codec *codec) +{ + struct via_spec *spec = codec->spec; + int i; + + via_init(codec); + for (i = 0; i < spec->num_mixers; i++) + snd_hda_resume_ctls(codec, spec->mixers[i]); + if (spec->multiout.dig_out_nid) + snd_hda_resume_spdif_out(codec); + if (spec->dig_in_nid) + snd_hda_resume_spdif_in(codec); + + return 0; +} +#endif + +/* + */ +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_PM + .resume = via_resume, +#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] = 0x13; + break; + case AUTO_SEQ_SIDE: + spec->multiout.dac_nids[i] = 0x11; + 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 = 0x1b - i + 1; + + 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 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; + + 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; + 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; +} + +static int vt1708_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 = 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 = vt1708_volume_init_verbs; + + spec->input_mux = &spec->private_imux; + + 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 = kcalloc(1, 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; + 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; + + 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.. + * FIXME: the controls appear in the "playback" view! + */ + /* .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 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}, + /* Set mic as default input of sw0 */ + {0x19, AC_VERB_SET_CONNECT_SEL, 0x2}, + /* 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] = 0x27; + break; + case AUTO_SEQ_SIDE: + /* AOW1 */ + spec->multiout.dac_nids[i] = 0x11; + 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(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; + } 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(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; + } + } + + 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; + 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; + + return 1; +} + +static int patch_vt1709_10ch(struct hda_codec *codec) +{ + struct via_spec *spec; + int err; + + /* create a codec specific record */ + spec = kcalloc(1, 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 = vt1709_10ch_volume_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; + + 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}, + /* Set mic as default input of sw0 */ + {0x19, AC_VERB_SET_CONNECT_SEL, 0x2}, + /* 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 = kcalloc(1, 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 = vt1709_6ch_volume_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; + + 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}, + {} /* terminator */ +}; |