// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Universal Interface for Intel High Definition Audio Codec
*
* HD audio interface patch for Realtek ALC codecs
*
* Copyright (c) 2004 Kailang Yang <kailang@realtek.com.tw>
* PeiSen Hou <pshou@realtek.com.tw>
* Takashi Iwai <tiwai@suse.de>
* Jonathan Woithe <jwoithe@just42.net>
*/
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/pci.h>
#include <linux/dmi.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/leds.h>
#include <sound/core.h>
#include <sound/jack.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
#include "hda_auto_parser.h"
#include "hda_jack.h"
#include "hda_generic.h"
/* keep halting ALC5505 DSP, for power saving */
#define HALT_REALTEK_ALC5505
/* extra amp-initialization sequence types */
enum {
ALC_INIT_UNDEFINED,
ALC_INIT_NONE,
ALC_INIT_DEFAULT,
};
enum {
ALC_HEADSET_MODE_UNKNOWN,
ALC_HEADSET_MODE_UNPLUGGED,
ALC_HEADSET_MODE_HEADSET,
ALC_HEADSET_MODE_MIC,
ALC_HEADSET_MODE_HEADPHONE,
};
enum {
ALC_HEADSET_TYPE_UNKNOWN,
ALC_HEADSET_TYPE_CTIA,
ALC_HEADSET_TYPE_OMTP,
};
enum {
ALC_KEY_MICMUTE_INDEX,
};
struct alc_customize_define {
unsigned int sku_cfg;
unsigned char port_connectivity;
unsigned char check_sum;
unsigned char customization;
unsigned char external_amp;
unsigned int enable_pcbeep:1;
unsigned int platform_type:1;
unsigned int swap:1;
unsigned int override:1;
unsigned int fixup:1; /* Means that this sku is set by driver, not read from hw */
};
struct alc_coef_led {
unsigned int idx;
unsigned int mask;
unsigned int on;
unsigned int off;
};
struct alc_spec {
struct hda_gen_spec gen; /* must be at head */
/* codec parameterization */
struct alc_customize_define cdefine;
unsigned int parse_flags; /* flag for snd_hda_parse_pin_defcfg() */
/* GPIO bits */
unsigned int gpio_mask;
unsigned int gpio_dir;
unsigned int gpio_data;
bool gpio_write_delay; /* add a delay before writing gpio_data */
/* mute LED for HP laptops, see vref_mute_led_set() */
int mute_led_polarity;
int micmute_led_polarity;
hda_nid_t mute_led_nid;
hda_nid_t cap_mute_led_nid;
unsigned int gpio_mute_led_mask;
unsigned int gpio_mic_led_mask;
struct alc_coef_led mute_led_coef;
struct alc_coef_led mic_led_coef;
hda_nid_t headset_mic_pin;
hda_nid_t headphone_mic_pin;
int current_headset_mode;
int current_headset_type;
/* hooks */
void (*init_hook)(struct hda_codec *codec);
#ifdef CONFIG_PM
void (*power_hook)(struct hda_codec *codec);
#endif
void (*shutup)(struct hda_codec *codec);
void (*reboot_notify)(struct hda_codec *codec);
int init_amp;
int codec_variant; /* flag for other variants */
unsigned int has_alc5505_dsp:1;
unsigned int no_depop_delay:1;
unsigned int done_hp_init:1;
unsigned int no_shutup_pins:1;
unsigned int ultra_low_power:1;
unsigned int has_hs_key:1;
unsigned int no_internal_mic_pin:1;
/* for PLL fix */
hda_nid_t pll_nid;
unsigned int pll_coef_idx, pll_coef_bit;
unsigned int coef0;
struct input_dev *kb_dev;
u8 alc_mute_keycode_map[1];
};
/*
* COEF access helper functions
*/
static int alc_read_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
unsigned int coef_idx)
{
unsigned int val;
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx);
val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_PROC_COEF, 0);
return val;
}
#define alc_read_coef_idx(codec, coef_idx) \
alc_read_coefex_idx(codec, 0x20, coef_idx)
static void alc_write_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
unsigned int coef_idx, unsigned int coef_val)
{
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_COEF_INDEX, coef_idx);
snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PROC_COEF, coef_val);
}
#define alc_write_coef_idx(codec, coef_idx, coef_val) \
alc_write_coefex_idx(codec, 0x20, coef_idx, coef_val)
static void alc_update_coefex_idx(struct hda_codec *codec, hda_nid_t nid,
unsigned int coef_idx, unsigned int mask,
unsigned int bits_set)
{
unsigned int val = alc_read_coefex_idx(codec, nid, coef_idx);
if (val != -1)
alc_write_coefex_idx(codec, nid, coef_idx,
(val & ~mask) | bits_set);
}
#define alc_update_coef_idx(codec, coef_idx, mask, bits_set) \
alc_update_coefex_idx(codec, 0x20, coef_idx, mask, bits_set)
/* a special bypass for COEF 0; read the cached value at the second time */
static unsigned int alc_get_coef0(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
if (!spec->coef0)
spec->coef0 = alc_read_coef_idx(codec, 0);
return spec->coef0;
}
/* coef writes/updates batch */
struct coef_fw {
unsigned char nid;
unsigned char idx;
unsigned short mask;
unsigned short val;
};
#define UPDATE_COEFEX(_nid, _idx, _mask, _val) \
{ .nid = (_nid), .idx = (_idx), .mask = (_mask), .val = (_val) }
#define WRITE_COEFEX(_nid, _idx, _val) UPDATE_COEFEX(_nid, _idx, -1, _val)
#define WRITE_COEF(_idx, _val) WRITE_COEFEX(0x20, _idx, _val)
#define UPDATE_COEF(_idx, _mask, _val) UPDATE_COEFEX(0x20, _idx, _mask, _val)
static void alc_process_coef_fw(struct hda_codec *codec,
const struct coef_fw *fw)
{
for (; fw->nid; fw++) {
if (fw->mask == (unsigned short)-1)
alc_write_coefex_idx(codec, fw->nid, fw->idx, fw->val);
else
alc_update_coefex_idx(codec, fw->nid, fw->idx,
fw->mask, fw->val);
}
}
/*
* GPIO setup tables, used in initialization
*/
/* Enable GPIO mask and set output */
static void alc_setup_gpio(struct hda_codec *codec, unsigned int mask)
{
struct alc_spec *spec = codec->spec;
spec->gpio_mask |= mask;
spec->gpio_dir |= mask;
spec->gpio_data |= mask;
}
static void alc_write_gpio_data(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
spec->gpio_data);
}
static void alc_update_gpio_data(struct hda_codec *codec, unsigned int mask,
bool on)
{
struct alc_spec *spec = codec->spec;
unsigned int oldval = spec->gpio_data;
if (on)
spec->gpio_data |= mask;
else
spec->gpio_data &= ~mask;
if (oldval != spec->gpio_data)
alc_write_gpio_data(codec);
}
static void alc_write_gpio(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
if (!spec->gpio_mask)
return;
snd_hda_codec_write(codec, codec->core.afg, 0,
AC_VERB_SET_GPIO_MASK, spec->gpio_mask);
snd_hda_codec_write(codec, codec->core.afg, 0,
AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir);
if (spec->gpio_write_delay)
msleep(1);
alc_write_gpio_data(codec);
}
static void alc_fixup_gpio(struct hda_codec *codec, int action,
unsigned int mask)
{
if (action == HDA_FIXUP_ACT_PRE_PROBE)
alc_setup_gpio(codec, mask);
}
static void alc_fixup_gpio1(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
alc_fixup_gpio(codec, action, 0x01);
}
static void alc_fixup_gpio2(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
alc_fixup_gpio(codec, action, 0x02);
}
static void alc_fixup_gpio3(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
alc_fixup_gpio(codec, action, 0x03);
}
static void alc_fixup_gpio4(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
alc_fixup_gpio(codec, action, 0x04);
}
static void alc_fixup_micmute_led(struct hda_codec *codec,
const struct hda_fixup *fix, int action)
{
if (action == HDA_FIXUP_ACT_PROBE)
snd_hda_gen_add_micmute_led_cdev(codec, NULL);
}
/*
* Fix hardware PLL issue
* On some codecs, the analog PLL gating control must be off while
* the default value is 1.
*/
static void alc_fix_pll(struct hda_codec *codec)
{
struct alc_spec *spec = codec->spec;
if (spec->pll_nid)
alc_update_coefex_idx(codec, spec->pll_nid, spec->pll_coef_idx,
1 << spec->pll_coef_bit, 0);
}
static void alc_fix_pll_init(struct hda_codec *codec, hda_nid_t nid,
unsigned int coef_idx, unsigned int coef_bit)
{
struct alc_spec *spec = codec->spec;
spec->pll_nid = nid;
spec->pll_coef_idx = coef_idx;
spec->pll_coef_bit = coef_bit;
alc_fix_pll(codec);
}
/* update the master volume per volume-knob's unsol event */
static void alc_update_knob_master(struct hda_codec *codec,
struct hda_jack_callback *jack)
{
unsigned int val;
struct snd_kcontrol *kctl;
struct snd_ctl_elem_value *uctl;
kctl = snd_hda_find_mixer_ctl(codec, "Master Playback Volume");
if (!kctl)
return;
uctl = kzalloc(sizeof(*uctl), GFP_KERNEL);
if (!uctl)
return;
val = snd_hda_codec_read(codec, jack->nid, 0,
AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
val &= HDA_AMP_VOLMASK;
uctl->value.integer.value[0] = val;
uctl->value.integer.value[1] = val;
kctl->put(kctl, uctl);
kfree(uctl);
}
static void alc880_unsol_event(struct hda_codec *codec, unsigned int res)
{
/* For some reason, the res given from ALC880 is broken.
Here we adjust it properly. */
snd_hda_jack_unsol_event(codec, res >> 2);
}
/* Change EAPD to verb control */
static void alc_fill_eapd_coef(struct hda_codec *codec)
{
int coef;
coef = alc_get_coef0(codec);
switch (codec->core.vendor_id) {
case 0x10ec0262:
alc_update_coef_idx(codec, 0x7, 0, 1<<5);
break;
case 0x10ec0267:
case 0x10ec0268:
alc_update_coef_idx(codec, 0x7, 0, 1<<13);
break;
case 0x10ec0269:
if ((coef & 0x00f0) == 0x0010)
alc_update_coef_idx(codec, 0xd, 0, 1<<14);
if ((coef & 0x00f0) == 0x0020)
alc_update_coef_idx(codec, 0x4, 1<<15, 0);
if ((coef & 0x00f0) == 0x0030)
alc_update_coef_idx(codec, 0x10, 1<<9, 0);
break;
case 0x10ec0280:
case 0x10ec0284:
case 0x10ec0290:
case 0x10ec0292:
alc_update_coef_idx(codec, 0x4, 1<<15, 0);
break;
case 0x10ec0225:
case 0x10ec0295:
case 0x10ec0299:
alc_update_coef_idx(codec, 0x67, 0xf000, 0x3000);
fallthrough;
case 0x10ec0215:
case 0x10ec0233:
case 0x10ec0235:
case 0x10ec0236:
case 0x10ec0245:
case 0x10ec0255:
case 0x10ec0256:
case 0x10ec0257:
case 0x10ec0282:
case 0x10ec0283:
case 0x10ec0286:
case 0x10ec0287:
case 0x10ec0288:
case 0x10ec0285:
case 0x10ec0298:
case 0x10ec0289:
case 0x10ec0300:
alc_update_coef_idx(codec, 0x10, 1<<9, 0);
break;
case 0x10ec0275:
alc_update_coef_idx(codec, 0xe, 0, 1<<0);
break;
case 0x10ec0293:
alc_update_coef_idx(codec, 0xa, 1<<13, 0);
break;
case 0x10ec0234:
case 0x10ec0274:
case 0x10ec0294:
case 0x10ec0700:
case 0x10ec0701:
case 0x10ec0703:
case 0x10ec0711:
alc_update_coef_idx(codec, 0x10, 1<<15, 0);
break;
case 0x10ec0662:
if ((coef & 0x00f0) == 0x0030)
alc_update_coef_idx(codec, 0x4, 1<<10, 0); /* EAPD Ctrl */
break;
case 0x10ec0272:
case 0x10ec0273:
case 0x10ec0663:
case 0x10ec0665:
case 0x10ec0670:
case 0x10ec0671:
case 0x10ec0672:
alc_update_coef_idx(codec, 0xd, 0, 1<<14); /* EAPD Ctrl */
break;
case 0x10ec0222:
case 0x10ec0623:
alc_