162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * HD audio interface patch for Cirrus Logic CS420x chip
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2009 Takashi Iwai <tiwai@suse.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/module.h>
1162306a36Sopenharmony_ci#include <sound/core.h>
1262306a36Sopenharmony_ci#include <linux/pci.h>
1362306a36Sopenharmony_ci#include <sound/tlv.h>
1462306a36Sopenharmony_ci#include <sound/hda_codec.h>
1562306a36Sopenharmony_ci#include "hda_local.h"
1662306a36Sopenharmony_ci#include "hda_auto_parser.h"
1762306a36Sopenharmony_ci#include "hda_jack.h"
1862306a36Sopenharmony_ci#include "hda_generic.h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct cs_spec {
2462306a36Sopenharmony_ci	struct hda_gen_spec gen;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	unsigned int gpio_mask;
2762306a36Sopenharmony_ci	unsigned int gpio_dir;
2862306a36Sopenharmony_ci	unsigned int gpio_data;
2962306a36Sopenharmony_ci	unsigned int gpio_eapd_hp; /* EAPD GPIO bit for headphones */
3062306a36Sopenharmony_ci	unsigned int gpio_eapd_speaker; /* EAPD GPIO bit for speakers */
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	/* CS421x */
3362306a36Sopenharmony_ci	unsigned int spdif_detect:1;
3462306a36Sopenharmony_ci	unsigned int spdif_present:1;
3562306a36Sopenharmony_ci	unsigned int sense_b:1;
3662306a36Sopenharmony_ci	hda_nid_t vendor_nid;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	/* for MBP SPDIF control */
3962306a36Sopenharmony_ci	int (*spdif_sw_put)(struct snd_kcontrol *kcontrol,
4062306a36Sopenharmony_ci			    struct snd_ctl_elem_value *ucontrol);
4162306a36Sopenharmony_ci};
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci/* available models with CS420x */
4462306a36Sopenharmony_cienum {
4562306a36Sopenharmony_ci	CS420X_MBP53,
4662306a36Sopenharmony_ci	CS420X_MBP55,
4762306a36Sopenharmony_ci	CS420X_IMAC27,
4862306a36Sopenharmony_ci	CS420X_GPIO_13,
4962306a36Sopenharmony_ci	CS420X_GPIO_23,
5062306a36Sopenharmony_ci	CS420X_MBP101,
5162306a36Sopenharmony_ci	CS420X_MBP81,
5262306a36Sopenharmony_ci	CS420X_MBA42,
5362306a36Sopenharmony_ci	CS420X_AUTO,
5462306a36Sopenharmony_ci	/* aliases */
5562306a36Sopenharmony_ci	CS420X_IMAC27_122 = CS420X_GPIO_23,
5662306a36Sopenharmony_ci	CS420X_APPLE = CS420X_GPIO_13,
5762306a36Sopenharmony_ci};
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci/* CS421x boards */
6062306a36Sopenharmony_cienum {
6162306a36Sopenharmony_ci	CS421X_CDB4210,
6262306a36Sopenharmony_ci	CS421X_SENSE_B,
6362306a36Sopenharmony_ci	CS421X_STUMPY,
6462306a36Sopenharmony_ci};
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci/* Vendor-specific processing widget */
6762306a36Sopenharmony_ci#define CS420X_VENDOR_NID	0x11
6862306a36Sopenharmony_ci#define CS_DIG_OUT1_PIN_NID	0x10
6962306a36Sopenharmony_ci#define CS_DIG_OUT2_PIN_NID	0x15
7062306a36Sopenharmony_ci#define CS_DMIC1_PIN_NID	0x0e
7162306a36Sopenharmony_ci#define CS_DMIC2_PIN_NID	0x12
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/* coef indices */
7462306a36Sopenharmony_ci#define IDX_SPDIF_STAT		0x0000
7562306a36Sopenharmony_ci#define IDX_SPDIF_CTL		0x0001
7662306a36Sopenharmony_ci#define IDX_ADC_CFG		0x0002
7762306a36Sopenharmony_ci/* SZC bitmask, 4 modes below:
7862306a36Sopenharmony_ci * 0 = immediate,
7962306a36Sopenharmony_ci * 1 = digital immediate, analog zero-cross
8062306a36Sopenharmony_ci * 2 = digtail & analog soft-ramp
8162306a36Sopenharmony_ci * 3 = digital soft-ramp, analog zero-cross
8262306a36Sopenharmony_ci */
8362306a36Sopenharmony_ci#define   CS_COEF_ADC_SZC_MASK		(3 << 0)
8462306a36Sopenharmony_ci#define   CS_COEF_ADC_MIC_SZC_MODE	(3 << 0) /* SZC setup for mic */
8562306a36Sopenharmony_ci#define   CS_COEF_ADC_LI_SZC_MODE	(3 << 0) /* SZC setup for line-in */
8662306a36Sopenharmony_ci/* PGA mode: 0 = differential, 1 = signle-ended */
8762306a36Sopenharmony_ci#define   CS_COEF_ADC_MIC_PGA_MODE	(1 << 5) /* PGA setup for mic */
8862306a36Sopenharmony_ci#define   CS_COEF_ADC_LI_PGA_MODE	(1 << 6) /* PGA setup for line-in */
8962306a36Sopenharmony_ci#define IDX_DAC_CFG		0x0003
9062306a36Sopenharmony_ci/* SZC bitmask, 4 modes below:
9162306a36Sopenharmony_ci * 0 = Immediate
9262306a36Sopenharmony_ci * 1 = zero-cross
9362306a36Sopenharmony_ci * 2 = soft-ramp
9462306a36Sopenharmony_ci * 3 = soft-ramp on zero-cross
9562306a36Sopenharmony_ci */
9662306a36Sopenharmony_ci#define   CS_COEF_DAC_HP_SZC_MODE	(3 << 0) /* nid 0x02 */
9762306a36Sopenharmony_ci#define   CS_COEF_DAC_LO_SZC_MODE	(3 << 2) /* nid 0x03 */
9862306a36Sopenharmony_ci#define   CS_COEF_DAC_SPK_SZC_MODE	(3 << 4) /* nid 0x04 */
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci#define IDX_BEEP_CFG		0x0004
10162306a36Sopenharmony_ci/* 0x0008 - test reg key */
10262306a36Sopenharmony_ci/* 0x0009 - 0x0014 -> 12 test regs */
10362306a36Sopenharmony_ci/* 0x0015 - visibility reg */
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci/* Cirrus Logic CS4208 */
10662306a36Sopenharmony_ci#define CS4208_VENDOR_NID	0x24
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci/*
10962306a36Sopenharmony_ci * Cirrus Logic CS4210
11062306a36Sopenharmony_ci *
11162306a36Sopenharmony_ci * 1 DAC => HP(sense) / Speakers,
11262306a36Sopenharmony_ci * 1 ADC <= LineIn(sense) / MicIn / DMicIn,
11362306a36Sopenharmony_ci * 1 SPDIF OUT => SPDIF Trasmitter(sense)
11462306a36Sopenharmony_ci */
11562306a36Sopenharmony_ci#define CS4210_DAC_NID		0x02
11662306a36Sopenharmony_ci#define CS4210_ADC_NID		0x03
11762306a36Sopenharmony_ci#define CS4210_VENDOR_NID	0x0B
11862306a36Sopenharmony_ci#define CS421X_DMIC_PIN_NID	0x09 /* Port E */
11962306a36Sopenharmony_ci#define CS421X_SPDIF_PIN_NID	0x0A /* Port H */
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci#define CS421X_IDX_DEV_CFG	0x01
12262306a36Sopenharmony_ci#define CS421X_IDX_ADC_CFG	0x02
12362306a36Sopenharmony_ci#define CS421X_IDX_DAC_CFG	0x03
12462306a36Sopenharmony_ci#define CS421X_IDX_SPK_CTL	0x04
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/* Cirrus Logic CS4213 is like CS4210 but does not have SPDIF input/output */
12762306a36Sopenharmony_ci#define CS4213_VENDOR_NID	0x09
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic inline int cs_vendor_coef_get(struct hda_codec *codec, unsigned int idx)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	snd_hda_codec_write(codec, spec->vendor_nid, 0,
13562306a36Sopenharmony_ci			    AC_VERB_SET_COEF_INDEX, idx);
13662306a36Sopenharmony_ci	return snd_hda_codec_read(codec, spec->vendor_nid, 0,
13762306a36Sopenharmony_ci				  AC_VERB_GET_PROC_COEF, 0);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic inline void cs_vendor_coef_set(struct hda_codec *codec, unsigned int idx,
14162306a36Sopenharmony_ci				      unsigned int coef)
14262306a36Sopenharmony_ci{
14362306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	snd_hda_codec_write(codec, spec->vendor_nid, 0,
14662306a36Sopenharmony_ci			    AC_VERB_SET_COEF_INDEX, idx);
14762306a36Sopenharmony_ci	snd_hda_codec_write(codec, spec->vendor_nid, 0,
14862306a36Sopenharmony_ci			    AC_VERB_SET_PROC_COEF, coef);
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci/*
15262306a36Sopenharmony_ci * auto-mute and auto-mic switching
15362306a36Sopenharmony_ci * CS421x auto-output redirecting
15462306a36Sopenharmony_ci * HP/SPK/SPDIF
15562306a36Sopenharmony_ci */
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic void cs_automute(struct hda_codec *codec)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	/* mute HPs if spdif jack (SENSE_B) is present */
16262306a36Sopenharmony_ci	spec->gen.master_mute = !!(spec->spdif_present && spec->sense_b);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	snd_hda_gen_update_outputs(codec);
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (spec->gpio_eapd_hp || spec->gpio_eapd_speaker) {
16762306a36Sopenharmony_ci		if (spec->gen.automute_speaker)
16862306a36Sopenharmony_ci			spec->gpio_data = spec->gen.hp_jack_present ?
16962306a36Sopenharmony_ci				spec->gpio_eapd_hp : spec->gpio_eapd_speaker;
17062306a36Sopenharmony_ci		else
17162306a36Sopenharmony_ci			spec->gpio_data =
17262306a36Sopenharmony_ci				spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
17362306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0,
17462306a36Sopenharmony_ci				    AC_VERB_SET_GPIO_DATA, spec->gpio_data);
17562306a36Sopenharmony_ci	}
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_cistatic bool is_active_pin(struct hda_codec *codec, hda_nid_t nid)
17962306a36Sopenharmony_ci{
18062306a36Sopenharmony_ci	unsigned int val;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	val = snd_hda_codec_get_pincfg(codec, nid);
18362306a36Sopenharmony_ci	return (get_defcfg_connect(val) != AC_JACK_PORT_NONE);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_cistatic void init_input_coef(struct hda_codec *codec)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
18962306a36Sopenharmony_ci	unsigned int coef;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	/* CS420x has multiple ADC, CS421x has single ADC */
19262306a36Sopenharmony_ci	if (spec->vendor_nid == CS420X_VENDOR_NID) {
19362306a36Sopenharmony_ci		coef = cs_vendor_coef_get(codec, IDX_BEEP_CFG);
19462306a36Sopenharmony_ci		if (is_active_pin(codec, CS_DMIC2_PIN_NID))
19562306a36Sopenharmony_ci			coef |= 1 << 4; /* DMIC2 2 chan on, GPIO1 off */
19662306a36Sopenharmony_ci		if (is_active_pin(codec, CS_DMIC1_PIN_NID))
19762306a36Sopenharmony_ci			coef |= 1 << 3; /* DMIC1 2 chan on, GPIO0 off
19862306a36Sopenharmony_ci					 * No effect if SPDIF_OUT2 is
19962306a36Sopenharmony_ci					 * selected in IDX_SPDIF_CTL.
20062306a36Sopenharmony_ci					 */
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci		cs_vendor_coef_set(codec, IDX_BEEP_CFG, coef);
20362306a36Sopenharmony_ci	}
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic const struct hda_verb cs_coef_init_verbs[] = {
20762306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_STATE, 1},
20862306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, IDX_DAC_CFG},
20962306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF,
21062306a36Sopenharmony_ci	 (0x002a /* DAC1/2/3 SZCMode Soft Ramp */
21162306a36Sopenharmony_ci	  | 0x0040 /* Mute DACs on FIFO error */
21262306a36Sopenharmony_ci	  | 0x1000 /* Enable DACs High Pass Filter */
21362306a36Sopenharmony_ci	  | 0x0400 /* Disable Coefficient Auto increment */
21462306a36Sopenharmony_ci	  )},
21562306a36Sopenharmony_ci	/* ADC1/2 - Digital and Analog Soft Ramp */
21662306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG},
21762306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x000a},
21862306a36Sopenharmony_ci	/* Beep */
21962306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, IDX_BEEP_CFG},
22062306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x0007}, /* Enable Beep thru DAC1/2/3 */
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	{} /* terminator */
22362306a36Sopenharmony_ci};
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_cistatic const struct hda_verb cs4208_coef_init_verbs[] = {
22662306a36Sopenharmony_ci	{0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
22762306a36Sopenharmony_ci	{0x24, AC_VERB_SET_PROC_STATE, 0x01},  /* VPW: processing on */
22862306a36Sopenharmony_ci	{0x24, AC_VERB_SET_COEF_INDEX, 0x0033},
22962306a36Sopenharmony_ci	{0x24, AC_VERB_SET_PROC_COEF, 0x0001}, /* A1 ICS */
23062306a36Sopenharmony_ci	{0x24, AC_VERB_SET_COEF_INDEX, 0x0034},
23162306a36Sopenharmony_ci	{0x24, AC_VERB_SET_PROC_COEF, 0x1C01}, /* A1 Enable, A Thresh = 300mV */
23262306a36Sopenharmony_ci	{} /* terminator */
23362306a36Sopenharmony_ci};
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci/* Errata: CS4207 rev C0/C1/C2 Silicon
23662306a36Sopenharmony_ci *
23762306a36Sopenharmony_ci * http://www.cirrus.com/en/pubs/errata/ER880C3.pdf
23862306a36Sopenharmony_ci *
23962306a36Sopenharmony_ci * 6. At high temperature (TA > +85°C), the digital supply current (IVD)
24062306a36Sopenharmony_ci * may be excessive (up to an additional 200 μA), which is most easily
24162306a36Sopenharmony_ci * observed while the part is being held in reset (RESET# active low).
24262306a36Sopenharmony_ci *
24362306a36Sopenharmony_ci * Root Cause: At initial powerup of the device, the logic that drives
24462306a36Sopenharmony_ci * the clock and write enable to the S/PDIF SRC RAMs is not properly
24562306a36Sopenharmony_ci * initialized.
24662306a36Sopenharmony_ci * Certain random patterns will cause a steady leakage current in those
24762306a36Sopenharmony_ci * RAM cells. The issue will resolve once the SRCs are used (turned on).
24862306a36Sopenharmony_ci *
24962306a36Sopenharmony_ci * Workaround: The following verb sequence briefly turns on the S/PDIF SRC
25062306a36Sopenharmony_ci * blocks, which will alleviate the issue.
25162306a36Sopenharmony_ci */
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_cistatic const struct hda_verb cs_errata_init_verbs[] = {
25462306a36Sopenharmony_ci	{0x01, AC_VERB_SET_POWER_STATE, 0x00}, /* AFG: D0 */
25562306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_STATE, 0x01},  /* VPW: processing on */
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
25862306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x9999},
25962306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
26062306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0xa412},
26162306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
26262306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x0009},
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	{0x07, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Rx: D0 */
26562306a36Sopenharmony_ci	{0x08, AC_VERB_SET_POWER_STATE, 0x00}, /* S/PDIF Tx: D0 */
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0017},
26862306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x2412},
26962306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0008},
27062306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x0000},
27162306a36Sopenharmony_ci	{0x11, AC_VERB_SET_COEF_INDEX, 0x0001},
27262306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_COEF, 0x0008},
27362306a36Sopenharmony_ci	{0x11, AC_VERB_SET_PROC_STATE, 0x00},
27462306a36Sopenharmony_ci	{} /* terminator */
27562306a36Sopenharmony_ci};
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci/* SPDIF setup */
27862306a36Sopenharmony_cistatic void init_digital_coef(struct hda_codec *codec)
27962306a36Sopenharmony_ci{
28062306a36Sopenharmony_ci	unsigned int coef;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	coef = 0x0002; /* SRC_MUTE soft-mute on SPDIF (if no lock) */
28362306a36Sopenharmony_ci	coef |= 0x0008; /* Replace with mute on error */
28462306a36Sopenharmony_ci	if (is_active_pin(codec, CS_DIG_OUT2_PIN_NID))
28562306a36Sopenharmony_ci		coef |= 0x4000; /* RX to TX1 or TX2 Loopthru / SPDIF2
28662306a36Sopenharmony_ci				 * SPDIF_OUT2 is shared with GPIO1 and
28762306a36Sopenharmony_ci				 * DMIC_SDA2.
28862306a36Sopenharmony_ci				 */
28962306a36Sopenharmony_ci	cs_vendor_coef_set(codec, IDX_SPDIF_CTL, coef);
29062306a36Sopenharmony_ci}
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_cistatic int cs_init(struct hda_codec *codec)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	if (spec->vendor_nid == CS420X_VENDOR_NID) {
29762306a36Sopenharmony_ci		/* init_verb sequence for C0/C1/C2 errata*/
29862306a36Sopenharmony_ci		snd_hda_sequence_write(codec, cs_errata_init_verbs);
29962306a36Sopenharmony_ci		snd_hda_sequence_write(codec, cs_coef_init_verbs);
30062306a36Sopenharmony_ci	} else if (spec->vendor_nid == CS4208_VENDOR_NID) {
30162306a36Sopenharmony_ci		snd_hda_sequence_write(codec, cs4208_coef_init_verbs);
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	snd_hda_gen_init(codec);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	if (spec->gpio_mask) {
30762306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
30862306a36Sopenharmony_ci				    spec->gpio_mask);
30962306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
31062306a36Sopenharmony_ci				    spec->gpio_dir);
31162306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
31262306a36Sopenharmony_ci				    spec->gpio_data);
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	if (spec->vendor_nid == CS420X_VENDOR_NID) {
31662306a36Sopenharmony_ci		init_input_coef(codec);
31762306a36Sopenharmony_ci		init_digital_coef(codec);
31862306a36Sopenharmony_ci	}
31962306a36Sopenharmony_ci
32062306a36Sopenharmony_ci	return 0;
32162306a36Sopenharmony_ci}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic int cs_build_controls(struct hda_codec *codec)
32462306a36Sopenharmony_ci{
32562306a36Sopenharmony_ci	int err;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	err = snd_hda_gen_build_controls(codec);
32862306a36Sopenharmony_ci	if (err < 0)
32962306a36Sopenharmony_ci		return err;
33062306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD);
33162306a36Sopenharmony_ci	return 0;
33262306a36Sopenharmony_ci}
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci#define cs_free		snd_hda_gen_free
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_cistatic const struct hda_codec_ops cs_patch_ops = {
33762306a36Sopenharmony_ci	.build_controls = cs_build_controls,
33862306a36Sopenharmony_ci	.build_pcms = snd_hda_gen_build_pcms,
33962306a36Sopenharmony_ci	.init = cs_init,
34062306a36Sopenharmony_ci	.free = cs_free,
34162306a36Sopenharmony_ci	.unsol_event = snd_hda_jack_unsol_event,
34262306a36Sopenharmony_ci};
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_cistatic int cs_parse_auto_config(struct hda_codec *codec)
34562306a36Sopenharmony_ci{
34662306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
34762306a36Sopenharmony_ci	int err;
34862306a36Sopenharmony_ci	int i;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0);
35162306a36Sopenharmony_ci	if (err < 0)
35262306a36Sopenharmony_ci		return err;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
35562306a36Sopenharmony_ci	if (err < 0)
35662306a36Sopenharmony_ci		return err;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	/* keep the ADCs powered up when it's dynamically switchable */
35962306a36Sopenharmony_ci	if (spec->gen.dyn_adc_switch) {
36062306a36Sopenharmony_ci		unsigned int done = 0;
36162306a36Sopenharmony_ci
36262306a36Sopenharmony_ci		for (i = 0; i < spec->gen.input_mux.num_items; i++) {
36362306a36Sopenharmony_ci			int idx = spec->gen.dyn_adc_idx[i];
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_ci			if (done & (1 << idx))
36662306a36Sopenharmony_ci				continue;
36762306a36Sopenharmony_ci			snd_hda_gen_fix_pin_power(codec,
36862306a36Sopenharmony_ci						  spec->gen.adc_nids[idx]);
36962306a36Sopenharmony_ci			done |= 1 << idx;
37062306a36Sopenharmony_ci		}
37162306a36Sopenharmony_ci	}
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	return 0;
37462306a36Sopenharmony_ci}
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cistatic const struct hda_model_fixup cs420x_models[] = {
37762306a36Sopenharmony_ci	{ .id = CS420X_MBP53, .name = "mbp53" },
37862306a36Sopenharmony_ci	{ .id = CS420X_MBP55, .name = "mbp55" },
37962306a36Sopenharmony_ci	{ .id = CS420X_IMAC27, .name = "imac27" },
38062306a36Sopenharmony_ci	{ .id = CS420X_IMAC27_122, .name = "imac27_122" },
38162306a36Sopenharmony_ci	{ .id = CS420X_APPLE, .name = "apple" },
38262306a36Sopenharmony_ci	{ .id = CS420X_MBP101, .name = "mbp101" },
38362306a36Sopenharmony_ci	{ .id = CS420X_MBP81, .name = "mbp81" },
38462306a36Sopenharmony_ci	{ .id = CS420X_MBA42, .name = "mba42" },
38562306a36Sopenharmony_ci	{}
38662306a36Sopenharmony_ci};
38762306a36Sopenharmony_ci
38862306a36Sopenharmony_cistatic const struct snd_pci_quirk cs420x_fixup_tbl[] = {
38962306a36Sopenharmony_ci	SND_PCI_QUIRK(0x10de, 0x0ac0, "MacBookPro 5,3", CS420X_MBP53),
39062306a36Sopenharmony_ci	SND_PCI_QUIRK(0x10de, 0x0d94, "MacBookAir 3,1(2)", CS420X_MBP55),
39162306a36Sopenharmony_ci	SND_PCI_QUIRK(0x10de, 0xcb79, "MacBookPro 5,5", CS420X_MBP55),
39262306a36Sopenharmony_ci	SND_PCI_QUIRK(0x10de, 0xcb89, "MacBookPro 7,1", CS420X_MBP55),
39362306a36Sopenharmony_ci	/* this conflicts with too many other models */
39462306a36Sopenharmony_ci	/*SND_PCI_QUIRK(0x8086, 0x7270, "IMac 27 Inch", CS420X_IMAC27),*/
39562306a36Sopenharmony_ci
39662306a36Sopenharmony_ci	/* codec SSID */
39762306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x0600, "iMac 14,1", CS420X_IMAC27_122),
39862306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x0900, "iMac 12,1", CS420X_IMAC27_122),
39962306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x1c00, "MacBookPro 8,1", CS420X_MBP81),
40062306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x2000, "iMac 12,2", CS420X_IMAC27_122),
40162306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x2800, "MacBookPro 10,1", CS420X_MBP101),
40262306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x5600, "MacBookAir 5,2", CS420X_MBP81),
40362306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x5b00, "MacBookAir 4,2", CS420X_MBA42),
40462306a36Sopenharmony_ci	SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS420X_APPLE),
40562306a36Sopenharmony_ci	{} /* terminator */
40662306a36Sopenharmony_ci};
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_cistatic const struct hda_pintbl mbp53_pincfgs[] = {
40962306a36Sopenharmony_ci	{ 0x09, 0x012b4050 },
41062306a36Sopenharmony_ci	{ 0x0a, 0x90100141 },
41162306a36Sopenharmony_ci	{ 0x0b, 0x90100140 },
41262306a36Sopenharmony_ci	{ 0x0c, 0x018b3020 },
41362306a36Sopenharmony_ci	{ 0x0d, 0x90a00110 },
41462306a36Sopenharmony_ci	{ 0x0e, 0x400000f0 },
41562306a36Sopenharmony_ci	{ 0x0f, 0x01cbe030 },
41662306a36Sopenharmony_ci	{ 0x10, 0x014be060 },
41762306a36Sopenharmony_ci	{ 0x12, 0x400000f0 },
41862306a36Sopenharmony_ci	{ 0x15, 0x400000f0 },
41962306a36Sopenharmony_ci	{} /* terminator */
42062306a36Sopenharmony_ci};
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic const struct hda_pintbl mbp55_pincfgs[] = {
42362306a36Sopenharmony_ci	{ 0x09, 0x012b4030 },
42462306a36Sopenharmony_ci	{ 0x0a, 0x90100121 },
42562306a36Sopenharmony_ci	{ 0x0b, 0x90100120 },
42662306a36Sopenharmony_ci	{ 0x0c, 0x400000f0 },
42762306a36Sopenharmony_ci	{ 0x0d, 0x90a00110 },
42862306a36Sopenharmony_ci	{ 0x0e, 0x400000f0 },
42962306a36Sopenharmony_ci	{ 0x0f, 0x400000f0 },
43062306a36Sopenharmony_ci	{ 0x10, 0x014be040 },
43162306a36Sopenharmony_ci	{ 0x12, 0x400000f0 },
43262306a36Sopenharmony_ci	{ 0x15, 0x400000f0 },
43362306a36Sopenharmony_ci	{} /* terminator */
43462306a36Sopenharmony_ci};
43562306a36Sopenharmony_ci
43662306a36Sopenharmony_cistatic const struct hda_pintbl imac27_pincfgs[] = {
43762306a36Sopenharmony_ci	{ 0x09, 0x012b4050 },
43862306a36Sopenharmony_ci	{ 0x0a, 0x90100140 },
43962306a36Sopenharmony_ci	{ 0x0b, 0x90100142 },
44062306a36Sopenharmony_ci	{ 0x0c, 0x018b3020 },
44162306a36Sopenharmony_ci	{ 0x0d, 0x90a00110 },
44262306a36Sopenharmony_ci	{ 0x0e, 0x400000f0 },
44362306a36Sopenharmony_ci	{ 0x0f, 0x01cbe030 },
44462306a36Sopenharmony_ci	{ 0x10, 0x014be060 },
44562306a36Sopenharmony_ci	{ 0x12, 0x01ab9070 },
44662306a36Sopenharmony_ci	{ 0x15, 0x400000f0 },
44762306a36Sopenharmony_ci	{} /* terminator */
44862306a36Sopenharmony_ci};
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic const struct hda_pintbl mbp101_pincfgs[] = {
45162306a36Sopenharmony_ci	{ 0x0d, 0x40ab90f0 },
45262306a36Sopenharmony_ci	{ 0x0e, 0x90a600f0 },
45362306a36Sopenharmony_ci	{ 0x12, 0x50a600f0 },
45462306a36Sopenharmony_ci	{} /* terminator */
45562306a36Sopenharmony_ci};
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_cistatic const struct hda_pintbl mba42_pincfgs[] = {
45862306a36Sopenharmony_ci	{ 0x09, 0x012b4030 }, /* HP */
45962306a36Sopenharmony_ci	{ 0x0a, 0x400000f0 },
46062306a36Sopenharmony_ci	{ 0x0b, 0x90100120 }, /* speaker */
46162306a36Sopenharmony_ci	{ 0x0c, 0x400000f0 },
46262306a36Sopenharmony_ci	{ 0x0d, 0x90a00110 }, /* mic */
46362306a36Sopenharmony_ci	{ 0x0e, 0x400000f0 },
46462306a36Sopenharmony_ci	{ 0x0f, 0x400000f0 },
46562306a36Sopenharmony_ci	{ 0x10, 0x400000f0 },
46662306a36Sopenharmony_ci	{ 0x12, 0x400000f0 },
46762306a36Sopenharmony_ci	{ 0x15, 0x400000f0 },
46862306a36Sopenharmony_ci	{} /* terminator */
46962306a36Sopenharmony_ci};
47062306a36Sopenharmony_ci
47162306a36Sopenharmony_cistatic const struct hda_pintbl mba6_pincfgs[] = {
47262306a36Sopenharmony_ci	{ 0x10, 0x032120f0 }, /* HP */
47362306a36Sopenharmony_ci	{ 0x11, 0x500000f0 },
47462306a36Sopenharmony_ci	{ 0x12, 0x90100010 }, /* Speaker */
47562306a36Sopenharmony_ci	{ 0x13, 0x500000f0 },
47662306a36Sopenharmony_ci	{ 0x14, 0x500000f0 },
47762306a36Sopenharmony_ci	{ 0x15, 0x770000f0 },
47862306a36Sopenharmony_ci	{ 0x16, 0x770000f0 },
47962306a36Sopenharmony_ci	{ 0x17, 0x430000f0 },
48062306a36Sopenharmony_ci	{ 0x18, 0x43ab9030 }, /* Mic */
48162306a36Sopenharmony_ci	{ 0x19, 0x770000f0 },
48262306a36Sopenharmony_ci	{ 0x1a, 0x770000f0 },
48362306a36Sopenharmony_ci	{ 0x1b, 0x770000f0 },
48462306a36Sopenharmony_ci	{ 0x1c, 0x90a00090 },
48562306a36Sopenharmony_ci	{ 0x1d, 0x500000f0 },
48662306a36Sopenharmony_ci	{ 0x1e, 0x500000f0 },
48762306a36Sopenharmony_ci	{ 0x1f, 0x500000f0 },
48862306a36Sopenharmony_ci	{ 0x20, 0x500000f0 },
48962306a36Sopenharmony_ci	{ 0x21, 0x430000f0 },
49062306a36Sopenharmony_ci	{ 0x22, 0x430000f0 },
49162306a36Sopenharmony_ci	{} /* terminator */
49262306a36Sopenharmony_ci};
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_cistatic void cs420x_fixup_gpio_13(struct hda_codec *codec,
49562306a36Sopenharmony_ci				 const struct hda_fixup *fix, int action)
49662306a36Sopenharmony_ci{
49762306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
49862306a36Sopenharmony_ci		struct cs_spec *spec = codec->spec;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci		spec->gpio_eapd_hp = 2; /* GPIO1 = headphones */
50162306a36Sopenharmony_ci		spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */
50262306a36Sopenharmony_ci		spec->gpio_mask = spec->gpio_dir =
50362306a36Sopenharmony_ci			spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
50462306a36Sopenharmony_ci	}
50562306a36Sopenharmony_ci}
50662306a36Sopenharmony_ci
50762306a36Sopenharmony_cistatic void cs420x_fixup_gpio_23(struct hda_codec *codec,
50862306a36Sopenharmony_ci				 const struct hda_fixup *fix, int action)
50962306a36Sopenharmony_ci{
51062306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
51162306a36Sopenharmony_ci		struct cs_spec *spec = codec->spec;
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci		spec->gpio_eapd_hp = 4; /* GPIO2 = headphones */
51462306a36Sopenharmony_ci		spec->gpio_eapd_speaker = 8; /* GPIO3 = speakers */
51562306a36Sopenharmony_ci		spec->gpio_mask = spec->gpio_dir =
51662306a36Sopenharmony_ci			spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
51762306a36Sopenharmony_ci	}
51862306a36Sopenharmony_ci}
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_cistatic const struct hda_fixup cs420x_fixups[] = {
52162306a36Sopenharmony_ci	[CS420X_MBP53] = {
52262306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
52362306a36Sopenharmony_ci		.v.pins = mbp53_pincfgs,
52462306a36Sopenharmony_ci		.chained = true,
52562306a36Sopenharmony_ci		.chain_id = CS420X_APPLE,
52662306a36Sopenharmony_ci	},
52762306a36Sopenharmony_ci	[CS420X_MBP55] = {
52862306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
52962306a36Sopenharmony_ci		.v.pins = mbp55_pincfgs,
53062306a36Sopenharmony_ci		.chained = true,
53162306a36Sopenharmony_ci		.chain_id = CS420X_GPIO_13,
53262306a36Sopenharmony_ci	},
53362306a36Sopenharmony_ci	[CS420X_IMAC27] = {
53462306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
53562306a36Sopenharmony_ci		.v.pins = imac27_pincfgs,
53662306a36Sopenharmony_ci		.chained = true,
53762306a36Sopenharmony_ci		.chain_id = CS420X_GPIO_13,
53862306a36Sopenharmony_ci	},
53962306a36Sopenharmony_ci	[CS420X_GPIO_13] = {
54062306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
54162306a36Sopenharmony_ci		.v.func = cs420x_fixup_gpio_13,
54262306a36Sopenharmony_ci	},
54362306a36Sopenharmony_ci	[CS420X_GPIO_23] = {
54462306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
54562306a36Sopenharmony_ci		.v.func = cs420x_fixup_gpio_23,
54662306a36Sopenharmony_ci	},
54762306a36Sopenharmony_ci	[CS420X_MBP101] = {
54862306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
54962306a36Sopenharmony_ci		.v.pins = mbp101_pincfgs,
55062306a36Sopenharmony_ci		.chained = true,
55162306a36Sopenharmony_ci		.chain_id = CS420X_GPIO_13,
55262306a36Sopenharmony_ci	},
55362306a36Sopenharmony_ci	[CS420X_MBP81] = {
55462306a36Sopenharmony_ci		.type = HDA_FIXUP_VERBS,
55562306a36Sopenharmony_ci		.v.verbs = (const struct hda_verb[]) {
55662306a36Sopenharmony_ci			/* internal mic ADC2: right only, single ended */
55762306a36Sopenharmony_ci			{0x11, AC_VERB_SET_COEF_INDEX, IDX_ADC_CFG},
55862306a36Sopenharmony_ci			{0x11, AC_VERB_SET_PROC_COEF, 0x102a},
55962306a36Sopenharmony_ci			{}
56062306a36Sopenharmony_ci		},
56162306a36Sopenharmony_ci		.chained = true,
56262306a36Sopenharmony_ci		.chain_id = CS420X_GPIO_13,
56362306a36Sopenharmony_ci	},
56462306a36Sopenharmony_ci	[CS420X_MBA42] = {
56562306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
56662306a36Sopenharmony_ci		.v.pins = mba42_pincfgs,
56762306a36Sopenharmony_ci		.chained = true,
56862306a36Sopenharmony_ci		.chain_id = CS420X_GPIO_13,
56962306a36Sopenharmony_ci	},
57062306a36Sopenharmony_ci};
57162306a36Sopenharmony_ci
57262306a36Sopenharmony_cistatic struct cs_spec *cs_alloc_spec(struct hda_codec *codec, int vendor_nid)
57362306a36Sopenharmony_ci{
57462306a36Sopenharmony_ci	struct cs_spec *spec;
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
57762306a36Sopenharmony_ci	if (!spec)
57862306a36Sopenharmony_ci		return NULL;
57962306a36Sopenharmony_ci	codec->spec = spec;
58062306a36Sopenharmony_ci	spec->vendor_nid = vendor_nid;
58162306a36Sopenharmony_ci	codec->power_save_node = 1;
58262306a36Sopenharmony_ci	snd_hda_gen_spec_init(&spec->gen);
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	return spec;
58562306a36Sopenharmony_ci}
58662306a36Sopenharmony_ci
58762306a36Sopenharmony_cistatic int patch_cs420x(struct hda_codec *codec)
58862306a36Sopenharmony_ci{
58962306a36Sopenharmony_ci	struct cs_spec *spec;
59062306a36Sopenharmony_ci	int err;
59162306a36Sopenharmony_ci
59262306a36Sopenharmony_ci	spec = cs_alloc_spec(codec, CS420X_VENDOR_NID);
59362306a36Sopenharmony_ci	if (!spec)
59462306a36Sopenharmony_ci		return -ENOMEM;
59562306a36Sopenharmony_ci
59662306a36Sopenharmony_ci	codec->patch_ops = cs_patch_ops;
59762306a36Sopenharmony_ci	spec->gen.automute_hook = cs_automute;
59862306a36Sopenharmony_ci	codec->single_adc_amp = 1;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	snd_hda_pick_fixup(codec, cs420x_models, cs420x_fixup_tbl,
60162306a36Sopenharmony_ci			   cs420x_fixups);
60262306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	err = cs_parse_auto_config(codec);
60562306a36Sopenharmony_ci	if (err < 0)
60662306a36Sopenharmony_ci		goto error;
60762306a36Sopenharmony_ci
60862306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	return 0;
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci error:
61362306a36Sopenharmony_ci	cs_free(codec);
61462306a36Sopenharmony_ci	return err;
61562306a36Sopenharmony_ci}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci/*
61862306a36Sopenharmony_ci * CS4208 support:
61962306a36Sopenharmony_ci * Its layout is no longer compatible with CS4206/CS4207
62062306a36Sopenharmony_ci */
62162306a36Sopenharmony_cienum {
62262306a36Sopenharmony_ci	CS4208_MAC_AUTO,
62362306a36Sopenharmony_ci	CS4208_MBA6,
62462306a36Sopenharmony_ci	CS4208_MBP11,
62562306a36Sopenharmony_ci	CS4208_MACMINI,
62662306a36Sopenharmony_ci	CS4208_GPIO0,
62762306a36Sopenharmony_ci};
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_cistatic const struct hda_model_fixup cs4208_models[] = {
63062306a36Sopenharmony_ci	{ .id = CS4208_GPIO0, .name = "gpio0" },
63162306a36Sopenharmony_ci	{ .id = CS4208_MBA6, .name = "mba6" },
63262306a36Sopenharmony_ci	{ .id = CS4208_MBP11, .name = "mbp11" },
63362306a36Sopenharmony_ci	{ .id = CS4208_MACMINI, .name = "macmini" },
63462306a36Sopenharmony_ci	{}
63562306a36Sopenharmony_ci};
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_cistatic const struct snd_pci_quirk cs4208_fixup_tbl[] = {
63862306a36Sopenharmony_ci	SND_PCI_QUIRK_VENDOR(0x106b, "Apple", CS4208_MAC_AUTO),
63962306a36Sopenharmony_ci	{} /* terminator */
64062306a36Sopenharmony_ci};
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci/* codec SSID matching */
64362306a36Sopenharmony_cistatic const struct snd_pci_quirk cs4208_mac_fixup_tbl[] = {
64462306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x5e00, "MacBookPro 11,2", CS4208_MBP11),
64562306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x6c00, "MacMini 7,1", CS4208_MACMINI),
64662306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x7100, "MacBookAir 6,1", CS4208_MBA6),
64762306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x7200, "MacBookAir 6,2", CS4208_MBA6),
64862306a36Sopenharmony_ci	SND_PCI_QUIRK(0x106b, 0x7b00, "MacBookPro 12,1", CS4208_MBP11),
64962306a36Sopenharmony_ci	{} /* terminator */
65062306a36Sopenharmony_ci};
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_cistatic void cs4208_fixup_gpio0(struct hda_codec *codec,
65362306a36Sopenharmony_ci			       const struct hda_fixup *fix, int action)
65462306a36Sopenharmony_ci{
65562306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
65662306a36Sopenharmony_ci		struct cs_spec *spec = codec->spec;
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci		spec->gpio_eapd_hp = 0;
65962306a36Sopenharmony_ci		spec->gpio_eapd_speaker = 1;
66062306a36Sopenharmony_ci		spec->gpio_mask = spec->gpio_dir =
66162306a36Sopenharmony_ci			spec->gpio_eapd_hp | spec->gpio_eapd_speaker;
66262306a36Sopenharmony_ci	}
66362306a36Sopenharmony_ci}
66462306a36Sopenharmony_ci
66562306a36Sopenharmony_cistatic const struct hda_fixup cs4208_fixups[];
66662306a36Sopenharmony_ci
66762306a36Sopenharmony_ci/* remap the fixup from codec SSID and apply it */
66862306a36Sopenharmony_cistatic void cs4208_fixup_mac(struct hda_codec *codec,
66962306a36Sopenharmony_ci			     const struct hda_fixup *fix, int action)
67062306a36Sopenharmony_ci{
67162306a36Sopenharmony_ci	if (action != HDA_FIXUP_ACT_PRE_PROBE)
67262306a36Sopenharmony_ci		return;
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	codec->fixup_id = HDA_FIXUP_ID_NOT_SET;
67562306a36Sopenharmony_ci	snd_hda_pick_fixup(codec, NULL, cs4208_mac_fixup_tbl, cs4208_fixups);
67662306a36Sopenharmony_ci	if (codec->fixup_id == HDA_FIXUP_ID_NOT_SET)
67762306a36Sopenharmony_ci		codec->fixup_id = CS4208_GPIO0; /* default fixup */
67862306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, action);
67962306a36Sopenharmony_ci}
68062306a36Sopenharmony_ci
68162306a36Sopenharmony_ci/* MacMini 7,1 has the inverted jack detection */
68262306a36Sopenharmony_cistatic void cs4208_fixup_macmini(struct hda_codec *codec,
68362306a36Sopenharmony_ci				 const struct hda_fixup *fix, int action)
68462306a36Sopenharmony_ci{
68562306a36Sopenharmony_ci	static const struct hda_pintbl pincfgs[] = {
68662306a36Sopenharmony_ci		{ 0x18, 0x00ab9150 }, /* mic (audio-in) jack: disable detect */
68762306a36Sopenharmony_ci		{ 0x21, 0x004be140 }, /* SPDIF: disable detect */
68862306a36Sopenharmony_ci		{ }
68962306a36Sopenharmony_ci	};
69062306a36Sopenharmony_ci
69162306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_PRE_PROBE) {
69262306a36Sopenharmony_ci		/* HP pin (0x10) has an inverted detection */
69362306a36Sopenharmony_ci		codec->inv_jack_detect = 1;
69462306a36Sopenharmony_ci		/* disable the bogus Mic and SPDIF jack detections */
69562306a36Sopenharmony_ci		snd_hda_apply_pincfgs(codec, pincfgs);
69662306a36Sopenharmony_ci	}
69762306a36Sopenharmony_ci}
69862306a36Sopenharmony_ci
69962306a36Sopenharmony_cistatic int cs4208_spdif_sw_put(struct snd_kcontrol *kcontrol,
70062306a36Sopenharmony_ci			       struct snd_ctl_elem_value *ucontrol)
70162306a36Sopenharmony_ci{
70262306a36Sopenharmony_ci	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
70362306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
70462306a36Sopenharmony_ci	hda_nid_t pin = spec->gen.autocfg.dig_out_pins[0];
70562306a36Sopenharmony_ci	int pinctl = ucontrol->value.integer.value[0] ? PIN_OUT : 0;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	snd_hda_set_pin_ctl_cache(codec, pin, pinctl);
70862306a36Sopenharmony_ci	return spec->spdif_sw_put(kcontrol, ucontrol);
70962306a36Sopenharmony_ci}
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci/* hook the SPDIF switch */
71262306a36Sopenharmony_cistatic void cs4208_fixup_spdif_switch(struct hda_codec *codec,
71362306a36Sopenharmony_ci				      const struct hda_fixup *fix, int action)
71462306a36Sopenharmony_ci{
71562306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_BUILD) {
71662306a36Sopenharmony_ci		struct cs_spec *spec = codec->spec;
71762306a36Sopenharmony_ci		struct snd_kcontrol *kctl;
71862306a36Sopenharmony_ci
71962306a36Sopenharmony_ci		if (!spec->gen.autocfg.dig_out_pins[0])
72062306a36Sopenharmony_ci			return;
72162306a36Sopenharmony_ci		kctl = snd_hda_find_mixer_ctl(codec, "IEC958 Playback Switch");
72262306a36Sopenharmony_ci		if (!kctl)
72362306a36Sopenharmony_ci			return;
72462306a36Sopenharmony_ci		spec->spdif_sw_put = kctl->put;
72562306a36Sopenharmony_ci		kctl->put = cs4208_spdif_sw_put;
72662306a36Sopenharmony_ci	}
72762306a36Sopenharmony_ci}
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_cistatic const struct hda_fixup cs4208_fixups[] = {
73062306a36Sopenharmony_ci	[CS4208_MBA6] = {
73162306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
73262306a36Sopenharmony_ci		.v.pins = mba6_pincfgs,
73362306a36Sopenharmony_ci		.chained = true,
73462306a36Sopenharmony_ci		.chain_id = CS4208_GPIO0,
73562306a36Sopenharmony_ci	},
73662306a36Sopenharmony_ci	[CS4208_MBP11] = {
73762306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
73862306a36Sopenharmony_ci		.v.func = cs4208_fixup_spdif_switch,
73962306a36Sopenharmony_ci		.chained = true,
74062306a36Sopenharmony_ci		.chain_id = CS4208_GPIO0,
74162306a36Sopenharmony_ci	},
74262306a36Sopenharmony_ci	[CS4208_MACMINI] = {
74362306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
74462306a36Sopenharmony_ci		.v.func = cs4208_fixup_macmini,
74562306a36Sopenharmony_ci		.chained = true,
74662306a36Sopenharmony_ci		.chain_id = CS4208_GPIO0,
74762306a36Sopenharmony_ci	},
74862306a36Sopenharmony_ci	[CS4208_GPIO0] = {
74962306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
75062306a36Sopenharmony_ci		.v.func = cs4208_fixup_gpio0,
75162306a36Sopenharmony_ci	},
75262306a36Sopenharmony_ci	[CS4208_MAC_AUTO] = {
75362306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
75462306a36Sopenharmony_ci		.v.func = cs4208_fixup_mac,
75562306a36Sopenharmony_ci	},
75662306a36Sopenharmony_ci};
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ci/* correct the 0dB offset of input pins */
75962306a36Sopenharmony_cistatic void cs4208_fix_amp_caps(struct hda_codec *codec, hda_nid_t adc)
76062306a36Sopenharmony_ci{
76162306a36Sopenharmony_ci	unsigned int caps;
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	caps = query_amp_caps(codec, adc, HDA_INPUT);
76462306a36Sopenharmony_ci	caps &= ~(AC_AMPCAP_OFFSET);
76562306a36Sopenharmony_ci	caps |= 0x02;
76662306a36Sopenharmony_ci	snd_hda_override_amp_caps(codec, adc, HDA_INPUT, caps);
76762306a36Sopenharmony_ci}
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_cistatic int patch_cs4208(struct hda_codec *codec)
77062306a36Sopenharmony_ci{
77162306a36Sopenharmony_ci	struct cs_spec *spec;
77262306a36Sopenharmony_ci	int err;
77362306a36Sopenharmony_ci
77462306a36Sopenharmony_ci	spec = cs_alloc_spec(codec, CS4208_VENDOR_NID);
77562306a36Sopenharmony_ci	if (!spec)
77662306a36Sopenharmony_ci		return -ENOMEM;
77762306a36Sopenharmony_ci
77862306a36Sopenharmony_ci	codec->patch_ops = cs_patch_ops;
77962306a36Sopenharmony_ci	spec->gen.automute_hook = cs_automute;
78062306a36Sopenharmony_ci	/* exclude NID 0x10 (HP) from output volumes due to different steps */
78162306a36Sopenharmony_ci	spec->gen.out_vol_mask = 1ULL << 0x10;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	snd_hda_pick_fixup(codec, cs4208_models, cs4208_fixup_tbl,
78462306a36Sopenharmony_ci			   cs4208_fixups);
78562306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ci	snd_hda_override_wcaps(codec, 0x18,
78862306a36Sopenharmony_ci			       get_wcaps(codec, 0x18) | AC_WCAP_STEREO);
78962306a36Sopenharmony_ci	cs4208_fix_amp_caps(codec, 0x18);
79062306a36Sopenharmony_ci	cs4208_fix_amp_caps(codec, 0x1b);
79162306a36Sopenharmony_ci	cs4208_fix_amp_caps(codec, 0x1c);
79262306a36Sopenharmony_ci
79362306a36Sopenharmony_ci	err = cs_parse_auto_config(codec);
79462306a36Sopenharmony_ci	if (err < 0)
79562306a36Sopenharmony_ci		goto error;
79662306a36Sopenharmony_ci
79762306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
79862306a36Sopenharmony_ci
79962306a36Sopenharmony_ci	return 0;
80062306a36Sopenharmony_ci
80162306a36Sopenharmony_ci error:
80262306a36Sopenharmony_ci	cs_free(codec);
80362306a36Sopenharmony_ci	return err;
80462306a36Sopenharmony_ci}
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ci/*
80762306a36Sopenharmony_ci * Cirrus Logic CS4210
80862306a36Sopenharmony_ci *
80962306a36Sopenharmony_ci * 1 DAC => HP(sense) / Speakers,
81062306a36Sopenharmony_ci * 1 ADC <= LineIn(sense) / MicIn / DMicIn,
81162306a36Sopenharmony_ci * 1 SPDIF OUT => SPDIF Trasmitter(sense)
81262306a36Sopenharmony_ci */
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci/* CS4210 board names */
81562306a36Sopenharmony_cistatic const struct hda_model_fixup cs421x_models[] = {
81662306a36Sopenharmony_ci	{ .id = CS421X_CDB4210, .name = "cdb4210" },
81762306a36Sopenharmony_ci	{ .id = CS421X_STUMPY, .name = "stumpy" },
81862306a36Sopenharmony_ci	{}
81962306a36Sopenharmony_ci};
82062306a36Sopenharmony_ci
82162306a36Sopenharmony_cistatic const struct snd_pci_quirk cs421x_fixup_tbl[] = {
82262306a36Sopenharmony_ci	/* Test Intel board + CDB2410  */
82362306a36Sopenharmony_ci	SND_PCI_QUIRK(0x8086, 0x5001, "DP45SG/CDB4210", CS421X_CDB4210),
82462306a36Sopenharmony_ci	{} /* terminator */
82562306a36Sopenharmony_ci};
82662306a36Sopenharmony_ci
82762306a36Sopenharmony_ci/* CS4210 board pinconfigs */
82862306a36Sopenharmony_ci/* Default CS4210 (CDB4210)*/
82962306a36Sopenharmony_cistatic const struct hda_pintbl cdb4210_pincfgs[] = {
83062306a36Sopenharmony_ci	{ 0x05, 0x0321401f },
83162306a36Sopenharmony_ci	{ 0x06, 0x90170010 },
83262306a36Sopenharmony_ci	{ 0x07, 0x03813031 },
83362306a36Sopenharmony_ci	{ 0x08, 0xb7a70037 },
83462306a36Sopenharmony_ci	{ 0x09, 0xb7a6003e },
83562306a36Sopenharmony_ci	{ 0x0a, 0x034510f0 },
83662306a36Sopenharmony_ci	{} /* terminator */
83762306a36Sopenharmony_ci};
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci/* Stumpy ChromeBox */
84062306a36Sopenharmony_cistatic const struct hda_pintbl stumpy_pincfgs[] = {
84162306a36Sopenharmony_ci	{ 0x05, 0x022120f0 },
84262306a36Sopenharmony_ci	{ 0x06, 0x901700f0 },
84362306a36Sopenharmony_ci	{ 0x07, 0x02a120f0 },
84462306a36Sopenharmony_ci	{ 0x08, 0x77a70037 },
84562306a36Sopenharmony_ci	{ 0x09, 0x77a6003e },
84662306a36Sopenharmony_ci	{ 0x0a, 0x434510f0 },
84762306a36Sopenharmony_ci	{} /* terminator */
84862306a36Sopenharmony_ci};
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci/* Setup GPIO/SENSE for each board (if used) */
85162306a36Sopenharmony_cistatic void cs421x_fixup_sense_b(struct hda_codec *codec,
85262306a36Sopenharmony_ci				 const struct hda_fixup *fix, int action)
85362306a36Sopenharmony_ci{
85462306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	if (action == HDA_FIXUP_ACT_PRE_PROBE)
85762306a36Sopenharmony_ci		spec->sense_b = 1;
85862306a36Sopenharmony_ci}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_cistatic const struct hda_fixup cs421x_fixups[] = {
86162306a36Sopenharmony_ci	[CS421X_CDB4210] = {
86262306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
86362306a36Sopenharmony_ci		.v.pins = cdb4210_pincfgs,
86462306a36Sopenharmony_ci		.chained = true,
86562306a36Sopenharmony_ci		.chain_id = CS421X_SENSE_B,
86662306a36Sopenharmony_ci	},
86762306a36Sopenharmony_ci	[CS421X_SENSE_B] = {
86862306a36Sopenharmony_ci		.type = HDA_FIXUP_FUNC,
86962306a36Sopenharmony_ci		.v.func = cs421x_fixup_sense_b,
87062306a36Sopenharmony_ci	},
87162306a36Sopenharmony_ci	[CS421X_STUMPY] = {
87262306a36Sopenharmony_ci		.type = HDA_FIXUP_PINS,
87362306a36Sopenharmony_ci		.v.pins = stumpy_pincfgs,
87462306a36Sopenharmony_ci	},
87562306a36Sopenharmony_ci};
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_cistatic const struct hda_verb cs421x_coef_init_verbs[] = {
87862306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_STATE, 1},
87962306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DEV_CFG},
88062306a36Sopenharmony_ci	/*
88162306a36Sopenharmony_ci	 *  Disable Coefficient Index Auto-Increment(DAI)=1,
88262306a36Sopenharmony_ci	 *  PDREF=0
88362306a36Sopenharmony_ci	 */
88462306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0x0001 },
88562306a36Sopenharmony_ci
88662306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_ADC_CFG},
88762306a36Sopenharmony_ci	/* ADC SZCMode = Digital Soft Ramp */
88862306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0x0002 },
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, CS421X_IDX_DAC_CFG},
89162306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF,
89262306a36Sopenharmony_ci	 (0x0002 /* DAC SZCMode = Digital Soft Ramp */
89362306a36Sopenharmony_ci	  | 0x0004 /* Mute DAC on FIFO error */
89462306a36Sopenharmony_ci	  | 0x0008 /* Enable DAC High Pass Filter */
89562306a36Sopenharmony_ci	  )},
89662306a36Sopenharmony_ci	{} /* terminator */
89762306a36Sopenharmony_ci};
89862306a36Sopenharmony_ci
89962306a36Sopenharmony_ci/* Errata: CS4210 rev A1 Silicon
90062306a36Sopenharmony_ci *
90162306a36Sopenharmony_ci * http://www.cirrus.com/en/pubs/errata/
90262306a36Sopenharmony_ci *
90362306a36Sopenharmony_ci * Description:
90462306a36Sopenharmony_ci * 1. Performance degredation is present in the ADC.
90562306a36Sopenharmony_ci * 2. Speaker output is not completely muted upon HP detect.
90662306a36Sopenharmony_ci * 3. Noise is present when clipping occurs on the amplified
90762306a36Sopenharmony_ci *    speaker outputs.
90862306a36Sopenharmony_ci *
90962306a36Sopenharmony_ci * Workaround:
91062306a36Sopenharmony_ci * The following verb sequence written to the registers during
91162306a36Sopenharmony_ci * initialization will correct the issues listed above.
91262306a36Sopenharmony_ci */
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_cistatic const struct hda_verb cs421x_coef_init_verbs_A1_silicon_fixes[] = {
91562306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_STATE, 0x01},  /* VPW: processing on */
91662306a36Sopenharmony_ci
91762306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, 0x0006},
91862306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0x9999}, /* Test mode: on */
91962306a36Sopenharmony_ci
92062306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, 0x000A},
92162306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0x14CB}, /* Chop double */
92262306a36Sopenharmony_ci
92362306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, 0x0011},
92462306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0xA2D0}, /* Increase ADC current */
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, 0x001A},
92762306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0x02A9}, /* Mute speaker */
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_COEF_INDEX, 0x001B},
93062306a36Sopenharmony_ci	{0x0B, AC_VERB_SET_PROC_COEF, 0X1006}, /* Remove noise */
93162306a36Sopenharmony_ci
93262306a36Sopenharmony_ci	{} /* terminator */
93362306a36Sopenharmony_ci};
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci/* Speaker Amp Gain is controlled by the vendor widget's coef 4 */
93662306a36Sopenharmony_cistatic const DECLARE_TLV_DB_SCALE(cs421x_speaker_boost_db_scale, 900, 300, 0);
93762306a36Sopenharmony_ci
93862306a36Sopenharmony_cistatic int cs421x_boost_vol_info(struct snd_kcontrol *kcontrol,
93962306a36Sopenharmony_ci				struct snd_ctl_elem_info *uinfo)
94062306a36Sopenharmony_ci{
94162306a36Sopenharmony_ci	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
94262306a36Sopenharmony_ci	uinfo->count = 1;
94362306a36Sopenharmony_ci	uinfo->value.integer.min = 0;
94462306a36Sopenharmony_ci	uinfo->value.integer.max = 3;
94562306a36Sopenharmony_ci	return 0;
94662306a36Sopenharmony_ci}
94762306a36Sopenharmony_ci
94862306a36Sopenharmony_cistatic int cs421x_boost_vol_get(struct snd_kcontrol *kcontrol,
94962306a36Sopenharmony_ci				struct snd_ctl_elem_value *ucontrol)
95062306a36Sopenharmony_ci{
95162306a36Sopenharmony_ci	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
95262306a36Sopenharmony_ci
95362306a36Sopenharmony_ci	ucontrol->value.integer.value[0] =
95462306a36Sopenharmony_ci		cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL) & 0x0003;
95562306a36Sopenharmony_ci	return 0;
95662306a36Sopenharmony_ci}
95762306a36Sopenharmony_ci
95862306a36Sopenharmony_cistatic int cs421x_boost_vol_put(struct snd_kcontrol *kcontrol,
95962306a36Sopenharmony_ci				struct snd_ctl_elem_value *ucontrol)
96062306a36Sopenharmony_ci{
96162306a36Sopenharmony_ci	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
96262306a36Sopenharmony_ci
96362306a36Sopenharmony_ci	unsigned int vol = ucontrol->value.integer.value[0];
96462306a36Sopenharmony_ci	unsigned int coef =
96562306a36Sopenharmony_ci		cs_vendor_coef_get(codec, CS421X_IDX_SPK_CTL);
96662306a36Sopenharmony_ci	unsigned int original_coef = coef;
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ci	coef &= ~0x0003;
96962306a36Sopenharmony_ci	coef |= (vol & 0x0003);
97062306a36Sopenharmony_ci	if (original_coef != coef) {
97162306a36Sopenharmony_ci		cs_vendor_coef_set(codec, CS421X_IDX_SPK_CTL, coef);
97262306a36Sopenharmony_ci		return 1;
97362306a36Sopenharmony_ci	}
97462306a36Sopenharmony_ci
97562306a36Sopenharmony_ci	return 0;
97662306a36Sopenharmony_ci}
97762306a36Sopenharmony_ci
97862306a36Sopenharmony_cistatic const struct snd_kcontrol_new cs421x_speaker_boost_ctl = {
97962306a36Sopenharmony_ci
98062306a36Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
98162306a36Sopenharmony_ci	.access = (SNDRV_CTL_ELEM_ACCESS_READWRITE |
98262306a36Sopenharmony_ci			SNDRV_CTL_ELEM_ACCESS_TLV_READ),
98362306a36Sopenharmony_ci	.name = "Speaker Boost Playback Volume",
98462306a36Sopenharmony_ci	.info = cs421x_boost_vol_info,
98562306a36Sopenharmony_ci	.get = cs421x_boost_vol_get,
98662306a36Sopenharmony_ci	.put = cs421x_boost_vol_put,
98762306a36Sopenharmony_ci	.tlv = { .p = cs421x_speaker_boost_db_scale },
98862306a36Sopenharmony_ci};
98962306a36Sopenharmony_ci
99062306a36Sopenharmony_cistatic void cs4210_pinmux_init(struct hda_codec *codec)
99162306a36Sopenharmony_ci{
99262306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
99362306a36Sopenharmony_ci	unsigned int def_conf, coef;
99462306a36Sopenharmony_ci
99562306a36Sopenharmony_ci	/* GPIO, DMIC_SCL, DMIC_SDA and SENSE_B are multiplexed */
99662306a36Sopenharmony_ci	coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG);
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	if (spec->gpio_mask)
99962306a36Sopenharmony_ci		coef |= 0x0008; /* B1,B2 are GPIOs */
100062306a36Sopenharmony_ci	else
100162306a36Sopenharmony_ci		coef &= ~0x0008;
100262306a36Sopenharmony_ci
100362306a36Sopenharmony_ci	if (spec->sense_b)
100462306a36Sopenharmony_ci		coef |= 0x0010; /* B2 is SENSE_B, not inverted  */
100562306a36Sopenharmony_ci	else
100662306a36Sopenharmony_ci		coef &= ~0x0010;
100762306a36Sopenharmony_ci
100862306a36Sopenharmony_ci	cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef);
100962306a36Sopenharmony_ci
101062306a36Sopenharmony_ci	if ((spec->gpio_mask || spec->sense_b) &&
101162306a36Sopenharmony_ci	    is_active_pin(codec, CS421X_DMIC_PIN_NID)) {
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_ci		/*
101462306a36Sopenharmony_ci		 *  GPIO or SENSE_B forced - disconnect the DMIC pin.
101562306a36Sopenharmony_ci		 */
101662306a36Sopenharmony_ci		def_conf = snd_hda_codec_get_pincfg(codec, CS421X_DMIC_PIN_NID);
101762306a36Sopenharmony_ci		def_conf &= ~AC_DEFCFG_PORT_CONN;
101862306a36Sopenharmony_ci		def_conf |= (AC_JACK_PORT_NONE << AC_DEFCFG_PORT_CONN_SHIFT);
101962306a36Sopenharmony_ci		snd_hda_codec_set_pincfg(codec, CS421X_DMIC_PIN_NID, def_conf);
102062306a36Sopenharmony_ci	}
102162306a36Sopenharmony_ci}
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_cistatic void cs4210_spdif_automute(struct hda_codec *codec,
102462306a36Sopenharmony_ci				  struct hda_jack_callback *tbl)
102562306a36Sopenharmony_ci{
102662306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
102762306a36Sopenharmony_ci	bool spdif_present = false;
102862306a36Sopenharmony_ci	hda_nid_t spdif_pin = spec->gen.autocfg.dig_out_pins[0];
102962306a36Sopenharmony_ci
103062306a36Sopenharmony_ci	/* detect on spdif is specific to CS4210 */
103162306a36Sopenharmony_ci	if (!spec->spdif_detect ||
103262306a36Sopenharmony_ci	    spec->vendor_nid != CS4210_VENDOR_NID)
103362306a36Sopenharmony_ci		return;
103462306a36Sopenharmony_ci
103562306a36Sopenharmony_ci	spdif_present = snd_hda_jack_detect(codec, spdif_pin);
103662306a36Sopenharmony_ci	if (spdif_present == spec->spdif_present)
103762306a36Sopenharmony_ci		return;
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	spec->spdif_present = spdif_present;
104062306a36Sopenharmony_ci	/* SPDIF TX on/off */
104162306a36Sopenharmony_ci	snd_hda_set_pin_ctl(codec, spdif_pin, spdif_present ? PIN_OUT : 0);
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_ci	cs_automute(codec);
104462306a36Sopenharmony_ci}
104562306a36Sopenharmony_ci
104662306a36Sopenharmony_cistatic void parse_cs421x_digital(struct hda_codec *codec)
104762306a36Sopenharmony_ci{
104862306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
104962306a36Sopenharmony_ci	struct auto_pin_cfg *cfg = &spec->gen.autocfg;
105062306a36Sopenharmony_ci	int i;
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_ci	for (i = 0; i < cfg->dig_outs; i++) {
105362306a36Sopenharmony_ci		hda_nid_t nid = cfg->dig_out_pins[i];
105462306a36Sopenharmony_ci
105562306a36Sopenharmony_ci		if (get_wcaps(codec, nid) & AC_WCAP_UNSOL_CAP) {
105662306a36Sopenharmony_ci			spec->spdif_detect = 1;
105762306a36Sopenharmony_ci			snd_hda_jack_detect_enable_callback(codec, nid,
105862306a36Sopenharmony_ci							    cs4210_spdif_automute);
105962306a36Sopenharmony_ci		}
106062306a36Sopenharmony_ci	}
106162306a36Sopenharmony_ci}
106262306a36Sopenharmony_ci
106362306a36Sopenharmony_cistatic int cs421x_init(struct hda_codec *codec)
106462306a36Sopenharmony_ci{
106562306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
106662306a36Sopenharmony_ci
106762306a36Sopenharmony_ci	if (spec->vendor_nid == CS4210_VENDOR_NID) {
106862306a36Sopenharmony_ci		snd_hda_sequence_write(codec, cs421x_coef_init_verbs);
106962306a36Sopenharmony_ci		snd_hda_sequence_write(codec, cs421x_coef_init_verbs_A1_silicon_fixes);
107062306a36Sopenharmony_ci		cs4210_pinmux_init(codec);
107162306a36Sopenharmony_ci	}
107262306a36Sopenharmony_ci
107362306a36Sopenharmony_ci	snd_hda_gen_init(codec);
107462306a36Sopenharmony_ci
107562306a36Sopenharmony_ci	if (spec->gpio_mask) {
107662306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
107762306a36Sopenharmony_ci				    spec->gpio_mask);
107862306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION,
107962306a36Sopenharmony_ci				    spec->gpio_dir);
108062306a36Sopenharmony_ci		snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
108162306a36Sopenharmony_ci				    spec->gpio_data);
108262306a36Sopenharmony_ci	}
108362306a36Sopenharmony_ci
108462306a36Sopenharmony_ci	init_input_coef(codec);
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	cs4210_spdif_automute(codec, NULL);
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_ci	return 0;
108962306a36Sopenharmony_ci}
109062306a36Sopenharmony_ci
109162306a36Sopenharmony_cistatic void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac)
109262306a36Sopenharmony_ci{
109362306a36Sopenharmony_ci	unsigned int caps;
109462306a36Sopenharmony_ci
109562306a36Sopenharmony_ci	/* set the upper-limit for mixer amp to 0dB */
109662306a36Sopenharmony_ci	caps = query_amp_caps(codec, dac, HDA_OUTPUT);
109762306a36Sopenharmony_ci	caps &= ~(0x7f << AC_AMPCAP_NUM_STEPS_SHIFT);
109862306a36Sopenharmony_ci	caps |= ((caps >> AC_AMPCAP_OFFSET_SHIFT) & 0x7f)
109962306a36Sopenharmony_ci		<< AC_AMPCAP_NUM_STEPS_SHIFT;
110062306a36Sopenharmony_ci	snd_hda_override_amp_caps(codec, dac, HDA_OUTPUT, caps);
110162306a36Sopenharmony_ci}
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_cistatic int cs421x_parse_auto_config(struct hda_codec *codec)
110462306a36Sopenharmony_ci{
110562306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
110662306a36Sopenharmony_ci	hda_nid_t dac = CS4210_DAC_NID;
110762306a36Sopenharmony_ci	int err;
110862306a36Sopenharmony_ci
110962306a36Sopenharmony_ci	fix_volume_caps(codec, dac);
111062306a36Sopenharmony_ci
111162306a36Sopenharmony_ci	err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0);
111262306a36Sopenharmony_ci	if (err < 0)
111362306a36Sopenharmony_ci		return err;
111462306a36Sopenharmony_ci
111562306a36Sopenharmony_ci	err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
111662306a36Sopenharmony_ci	if (err < 0)
111762306a36Sopenharmony_ci		return err;
111862306a36Sopenharmony_ci
111962306a36Sopenharmony_ci	parse_cs421x_digital(codec);
112062306a36Sopenharmony_ci
112162306a36Sopenharmony_ci	if (spec->gen.autocfg.speaker_outs &&
112262306a36Sopenharmony_ci	    spec->vendor_nid == CS4210_VENDOR_NID) {
112362306a36Sopenharmony_ci		if (!snd_hda_gen_add_kctl(&spec->gen, NULL,
112462306a36Sopenharmony_ci					  &cs421x_speaker_boost_ctl))
112562306a36Sopenharmony_ci			return -ENOMEM;
112662306a36Sopenharmony_ci	}
112762306a36Sopenharmony_ci
112862306a36Sopenharmony_ci	return 0;
112962306a36Sopenharmony_ci}
113062306a36Sopenharmony_ci
113162306a36Sopenharmony_ci#ifdef CONFIG_PM
113262306a36Sopenharmony_ci/*
113362306a36Sopenharmony_ci *	Manage PDREF, when transitioning to D3hot
113462306a36Sopenharmony_ci *	(DAC,ADC) -> D3, PDREF=1, AFG->D3
113562306a36Sopenharmony_ci */
113662306a36Sopenharmony_cistatic int cs421x_suspend(struct hda_codec *codec)
113762306a36Sopenharmony_ci{
113862306a36Sopenharmony_ci	struct cs_spec *spec = codec->spec;
113962306a36Sopenharmony_ci	unsigned int coef;
114062306a36Sopenharmony_ci
114162306a36Sopenharmony_ci	snd_hda_shutup_pins(codec);
114262306a36Sopenharmony_ci
114362306a36Sopenharmony_ci	snd_hda_codec_write(codec, CS4210_DAC_NID, 0,
114462306a36Sopenharmony_ci			    AC_VERB_SET_POWER_STATE,  AC_PWRST_D3);
114562306a36Sopenharmony_ci	snd_hda_codec_write(codec, CS4210_ADC_NID, 0,
114662306a36Sopenharmony_ci			    AC_VERB_SET_POWER_STATE,  AC_PWRST_D3);
114762306a36Sopenharmony_ci
114862306a36Sopenharmony_ci	if (spec->vendor_nid == CS4210_VENDOR_NID) {
114962306a36Sopenharmony_ci		coef = cs_vendor_coef_get(codec, CS421X_IDX_DEV_CFG);
115062306a36Sopenharmony_ci		coef |= 0x0004; /* PDREF */
115162306a36Sopenharmony_ci		cs_vendor_coef_set(codec, CS421X_IDX_DEV_CFG, coef);
115262306a36Sopenharmony_ci	}
115362306a36Sopenharmony_ci
115462306a36Sopenharmony_ci	return 0;
115562306a36Sopenharmony_ci}
115662306a36Sopenharmony_ci#endif
115762306a36Sopenharmony_ci
115862306a36Sopenharmony_cistatic const struct hda_codec_ops cs421x_patch_ops = {
115962306a36Sopenharmony_ci	.build_controls = snd_hda_gen_build_controls,
116062306a36Sopenharmony_ci	.build_pcms = snd_hda_gen_build_pcms,
116162306a36Sopenharmony_ci	.init = cs421x_init,
116262306a36Sopenharmony_ci	.free = cs_free,
116362306a36Sopenharmony_ci	.unsol_event = snd_hda_jack_unsol_event,
116462306a36Sopenharmony_ci#ifdef CONFIG_PM
116562306a36Sopenharmony_ci	.suspend = cs421x_suspend,
116662306a36Sopenharmony_ci#endif
116762306a36Sopenharmony_ci};
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_cistatic int patch_cs4210(struct hda_codec *codec)
117062306a36Sopenharmony_ci{
117162306a36Sopenharmony_ci	struct cs_spec *spec;
117262306a36Sopenharmony_ci	int err;
117362306a36Sopenharmony_ci
117462306a36Sopenharmony_ci	spec = cs_alloc_spec(codec, CS4210_VENDOR_NID);
117562306a36Sopenharmony_ci	if (!spec)
117662306a36Sopenharmony_ci		return -ENOMEM;
117762306a36Sopenharmony_ci
117862306a36Sopenharmony_ci	codec->patch_ops = cs421x_patch_ops;
117962306a36Sopenharmony_ci	spec->gen.automute_hook = cs_automute;
118062306a36Sopenharmony_ci
118162306a36Sopenharmony_ci	snd_hda_pick_fixup(codec, cs421x_models, cs421x_fixup_tbl,
118262306a36Sopenharmony_ci			   cs421x_fixups);
118362306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci	/*
118662306a36Sopenharmony_ci	 *  Update the GPIO/DMIC/SENSE_B pinmux before the configuration
118762306a36Sopenharmony_ci	 *   is auto-parsed. If GPIO or SENSE_B is forced, DMIC input
118862306a36Sopenharmony_ci	 *   is disabled.
118962306a36Sopenharmony_ci	 */
119062306a36Sopenharmony_ci	cs4210_pinmux_init(codec);
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_ci	err = cs421x_parse_auto_config(codec);
119362306a36Sopenharmony_ci	if (err < 0)
119462306a36Sopenharmony_ci		goto error;
119562306a36Sopenharmony_ci
119662306a36Sopenharmony_ci	snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
119762306a36Sopenharmony_ci
119862306a36Sopenharmony_ci	return 0;
119962306a36Sopenharmony_ci
120062306a36Sopenharmony_ci error:
120162306a36Sopenharmony_ci	cs_free(codec);
120262306a36Sopenharmony_ci	return err;
120362306a36Sopenharmony_ci}
120462306a36Sopenharmony_ci
120562306a36Sopenharmony_cistatic int patch_cs4213(struct hda_codec *codec)
120662306a36Sopenharmony_ci{
120762306a36Sopenharmony_ci	struct cs_spec *spec;
120862306a36Sopenharmony_ci	int err;
120962306a36Sopenharmony_ci
121062306a36Sopenharmony_ci	spec = cs_alloc_spec(codec, CS4213_VENDOR_NID);
121162306a36Sopenharmony_ci	if (!spec)
121262306a36Sopenharmony_ci		return -ENOMEM;
121362306a36Sopenharmony_ci
121462306a36Sopenharmony_ci	codec->patch_ops = cs421x_patch_ops;
121562306a36Sopenharmony_ci
121662306a36Sopenharmony_ci	err = cs421x_parse_auto_config(codec);
121762306a36Sopenharmony_ci	if (err < 0)
121862306a36Sopenharmony_ci		goto error;
121962306a36Sopenharmony_ci
122062306a36Sopenharmony_ci	return 0;
122162306a36Sopenharmony_ci
122262306a36Sopenharmony_ci error:
122362306a36Sopenharmony_ci	cs_free(codec);
122462306a36Sopenharmony_ci	return err;
122562306a36Sopenharmony_ci}
122662306a36Sopenharmony_ci
122762306a36Sopenharmony_ci/*
122862306a36Sopenharmony_ci * patch entries
122962306a36Sopenharmony_ci */
123062306a36Sopenharmony_cistatic const struct hda_device_id snd_hda_id_cirrus[] = {
123162306a36Sopenharmony_ci	HDA_CODEC_ENTRY(0x10134206, "CS4206", patch_cs420x),
123262306a36Sopenharmony_ci	HDA_CODEC_ENTRY(0x10134207, "CS4207", patch_cs420x),
123362306a36Sopenharmony_ci	HDA_CODEC_ENTRY(0x10134208, "CS4208", patch_cs4208),
123462306a36Sopenharmony_ci	HDA_CODEC_ENTRY(0x10134210, "CS4210", patch_cs4210),
123562306a36Sopenharmony_ci	HDA_CODEC_ENTRY(0x10134213, "CS4213", patch_cs4213),
123662306a36Sopenharmony_ci	{} /* terminator */
123762306a36Sopenharmony_ci};
123862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cirrus);
123962306a36Sopenharmony_ci
124062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
124162306a36Sopenharmony_ciMODULE_DESCRIPTION("Cirrus Logic HD-audio codec");
124262306a36Sopenharmony_ci
124362306a36Sopenharmony_cistatic struct hda_codec_driver cirrus_driver = {
124462306a36Sopenharmony_ci	.id = snd_hda_id_cirrus,
124562306a36Sopenharmony_ci};
124662306a36Sopenharmony_ci
124762306a36Sopenharmony_cimodule_hda_codec_driver(cirrus_driver);
1248