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