Message ID | 20210728134408.369396-2-vitalyr@opensource.cirrus.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | ALSA: hda/cirrus: Split generic cirrus HDA codecs and CS8490 bridge into separate modules. | expand |
On Wed, 28 Jul 2021 15:43:42 +0200, Vitaly Rodionov wrote: > > From: Lucas Tanure <tanureal@opensource.cirrus.com> > > Signed-off-by: Lucas Tanure <tanureal@opensource.cirrus.com> > Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com> > > Changes in v2: > Fixed reverse-selection, making modules individual. Just a note: putting v2 changes text here would break the signed-off-by lines when applied. If you'd want to keep v2-changes in the commit log, put before signed-off-by lines. Or if you'd omit the text (only for the series review), put it after "---" line below. Then git-am ignores it. thanks, Takashi > > --- > sound/pci/hda/Kconfig | 10 + > sound/pci/hda/Makefile | 2 + > sound/pci/hda/patch_cirrus.c | 1074 ---------------------------------- > sound/pci/hda/patch_cs8409.c | 1060 +++++++++++++++++++++++++++++++++ > sound/pci/hda/patch_cs8409.h | 91 +++ > 5 files changed, 1163 insertions(+), 1074 deletions(-) > create mode 100644 sound/pci/hda/patch_cs8409.c > create mode 100644 sound/pci/hda/patch_cs8409.h > > diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig > index c4360cdbc728..ab9d2746e804 100644 > --- a/sound/pci/hda/Kconfig > +++ b/sound/pci/hda/Kconfig > @@ -157,6 +157,16 @@ config SND_HDA_CODEC_CIRRUS > comment "Set to Y if you want auto-loading the codec driver" > depends on SND_HDA=y && SND_HDA_CODEC_CIRRUS=m > > +config SND_HDA_CODEC_CS8409 > + tristate "Build Cirrus Logic HDA bridge support" > + select SND_HDA_GENERIC > + help > + Say Y or M here to include Cirrus Logic HDA bridge support in > + snd-hda-intel driver, such as CS8409. > + > +comment "Set to Y if you want auto-loading the codec driver" > + depends on SND_HDA=y && SND_HDA_CODEC_CS8409=m > + > config SND_HDA_CODEC_CONEXANT > tristate "Build Conexant HD-audio codec support" > select SND_HDA_GENERIC > diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile > index b57432f00056..1b73e08dc563 100644 > --- a/sound/pci/hda/Makefile > +++ b/sound/pci/hda/Makefile > @@ -20,6 +20,7 @@ snd-hda-codec-analog-objs := patch_analog.o > snd-hda-codec-idt-objs := patch_sigmatel.o > snd-hda-codec-si3054-objs := patch_si3054.o > snd-hda-codec-cirrus-objs := patch_cirrus.o > +snd-hda-codec-cs8409-objs := patch_cs8409.o > snd-hda-codec-ca0110-objs := patch_ca0110.o > snd-hda-codec-ca0132-objs := patch_ca0132.o > snd-hda-codec-conexant-objs := patch_conexant.o > @@ -37,6 +38,7 @@ obj-$(CONFIG_SND_HDA_CODEC_ANALOG) += snd-hda-codec-analog.o > obj-$(CONFIG_SND_HDA_CODEC_SIGMATEL) += snd-hda-codec-idt.o > obj-$(CONFIG_SND_HDA_CODEC_SI3054) += snd-hda-codec-si3054.o > obj-$(CONFIG_SND_HDA_CODEC_CIRRUS) += snd-hda-codec-cirrus.o > +obj-$(CONFIG_SND_HDA_CODEC_CS8409) += snd-hda-codec-cs8409.o > obj-$(CONFIG_SND_HDA_CODEC_CA0110) += snd-hda-codec-ca0110.o > obj-$(CONFIG_SND_HDA_CODEC_CA0132) += snd-hda-codec-ca0132.o > obj-$(CONFIG_SND_HDA_CODEC_CONEXANT) += snd-hda-codec-conexant.o > diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c > index 8629e84fef23..678fbcaf2a3b 100644 > --- a/sound/pci/hda/patch_cirrus.c > +++ b/sound/pci/hda/patch_cirrus.c > @@ -9,7 +9,6 @@ > #include <linux/slab.h> > #include <linux/module.h> > #include <sound/core.h> > -#include <linux/mutex.h> > #include <linux/pci.h> > #include <sound/tlv.h> > #include <sound/hda_codec.h> > @@ -21,9 +20,6 @@ > /* > */ > > -#define CS42L42_HP_CH (2U) > -#define CS42L42_HS_MIC_CH (1U) > - > struct cs_spec { > struct hda_gen_spec gen; > > @@ -42,18 +38,6 @@ struct cs_spec { > /* for MBP SPDIF control */ > int (*spdif_sw_put)(struct snd_kcontrol *kcontrol, > struct snd_ctl_elem_value *ucontrol); > - > - unsigned int cs42l42_hp_jack_in:1; > - unsigned int cs42l42_mic_jack_in:1; > - unsigned int cs42l42_volume_init:1; > - char cs42l42_hp_volume[CS42L42_HP_CH]; > - char cs42l42_hs_mic_volume[CS42L42_HS_MIC_CH]; > - > - struct mutex cs8409_i2c_mux; > - > - /* verb exec op override */ > - int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, > - unsigned int flags, unsigned int *res); > }; > > /* available models with CS420x */ > @@ -1239,1063 +1223,6 @@ static int patch_cs4213(struct hda_codec *codec) > return err; > } > > -/* Cirrus Logic CS8409 HDA bridge with > - * companion codec CS42L42 > - */ > -#define CS8409_VENDOR_NID 0x47 > - > -#define CS8409_CS42L42_HP_PIN_NID 0x24 > -#define CS8409_CS42L42_SPK_PIN_NID 0x2c > -#define CS8409_CS42L42_AMIC_PIN_NID 0x34 > -#define CS8409_CS42L42_DMIC_PIN_NID 0x44 > -#define CS8409_CS42L42_DMIC_ADC_PIN_NID 0x22 > - > -#define CS42L42_HSDET_AUTO_DONE 0x02 > -#define CS42L42_HSTYPE_MASK 0x03 > - > -#define CS42L42_JACK_INSERTED 0x0C > -#define CS42L42_JACK_REMOVED 0x00 > - > -#define GPIO3_INT (1 << 3) > -#define GPIO4_INT (1 << 4) > -#define GPIO5_INT (1 << 5) > - > -#define CS42L42_I2C_ADDR (0x48 << 1) > - > -#define CIR_I2C_ADDR 0x0059 > -#define CIR_I2C_DATA 0x005A > -#define CIR_I2C_CTRL 0x005B > -#define CIR_I2C_STATUS 0x005C > -#define CIR_I2C_QWRITE 0x005D > -#define CIR_I2C_QREAD 0x005E > - > -#define CS8409_CS42L42_HP_VOL_REAL_MIN (-63) > -#define CS8409_CS42L42_HP_VOL_REAL_MAX (0) > -#define CS8409_CS42L42_AMIC_VOL_REAL_MIN (-97) > -#define CS8409_CS42L42_AMIC_VOL_REAL_MAX (12) > -#define CS8409_CS42L42_REG_HS_VOLUME_CHA (0x2301) > -#define CS8409_CS42L42_REG_HS_VOLUME_CHB (0x2303) > -#define CS8409_CS42L42_REG_AMIC_VOLUME (0x1D03) > - > -struct cs8409_i2c_param { > - unsigned int addr; > - unsigned int reg; > -}; > - > -struct cs8409_cir_param { > - unsigned int nid; > - unsigned int cir; > - unsigned int coeff; > -}; > - > -enum { > - CS8409_BULLSEYE, > - CS8409_WARLOCK, > - CS8409_CYBORG, > - CS8409_FIXUPS, > -}; > - > -static void cs8409_cs42l42_fixups(struct hda_codec *codec, > - const struct hda_fixup *fix, int action); > -static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, > - unsigned int cmd, unsigned int flags, unsigned int *res); > - > -/* Dell Inspiron models with cs8409/cs42l42 */ > -static const struct hda_model_fixup cs8409_models[] = { > - { .id = CS8409_BULLSEYE, .name = "bullseye" }, > - { .id = CS8409_WARLOCK, .name = "warlock" }, > - { .id = CS8409_CYBORG, .name = "cyborg" }, > - {} > -}; > - > -/* Dell Inspiron platforms > - * with cs8409 bridge and cs42l42 codec > - */ > -static const struct snd_pci_quirk cs8409_fixup_tbl[] = { > - SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE), > - SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK), > - SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG), > - SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG), > - {} /* terminator */ > -}; > - > -static const struct hda_verb cs8409_cs42l42_init_verbs[] = { > - { 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */ > - { 0x47, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ > - { 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ > - { 0x47, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ > - { 0x47, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ > - { 0x47, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ > - {} /* terminator */ > -}; > - > -static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { > - { 0x24, 0x042120f0 }, /* ASP-1-TX */ > - { 0x34, 0x04a12050 }, /* ASP-1-RX */ > - { 0x2c, 0x901000f0 }, /* ASP-2-TX */ > - { 0x44, 0x90a00090 }, /* DMIC-1 */ > - {} /* terminator */ > -}; > - > -static const struct hda_fixup cs8409_fixups[] = { > - [CS8409_BULLSEYE] = { > - .type = HDA_FIXUP_PINS, > - .v.pins = cs8409_cs42l42_pincfgs, > - .chained = true, > - .chain_id = CS8409_FIXUPS, > - }, > - [CS8409_WARLOCK] = { > - .type = HDA_FIXUP_PINS, > - .v.pins = cs8409_cs42l42_pincfgs, > - .chained = true, > - .chain_id = CS8409_FIXUPS, > - }, > - [CS8409_CYBORG] = { > - .type = HDA_FIXUP_PINS, > - .v.pins = cs8409_cs42l42_pincfgs, > - .chained = true, > - .chain_id = CS8409_FIXUPS, > - }, > - [CS8409_FIXUPS] = { > - .type = HDA_FIXUP_FUNC, > - .v.func = cs8409_cs42l42_fixups, > - }, > -}; > - > -/* Vendor specific HW configuration for CS42L42 */ > -static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = { > - { 0x1010, 0xB0 }, > - { 0x1D01, 0x00 }, > - { 0x1D02, 0x06 }, > - { 0x1D03, 0x00 }, > - { 0x1107, 0x01 }, > - { 0x1009, 0x02 }, > - { 0x1007, 0x03 }, > - { 0x1201, 0x00 }, > - { 0x1208, 0x13 }, > - { 0x1205, 0xFF }, > - { 0x1206, 0x00 }, > - { 0x1207, 0x20 }, > - { 0x1202, 0x0D }, > - { 0x2A02, 0x02 }, > - { 0x2A03, 0x00 }, > - { 0x2A04, 0x00 }, > - { 0x2A05, 0x02 }, > - { 0x2A06, 0x00 }, > - { 0x2A07, 0x20 }, > - { 0x2A08, 0x02 }, > - { 0x2A09, 0x00 }, > - { 0x2A0A, 0x80 }, > - { 0x2A0B, 0x02 }, > - { 0x2A0C, 0x00 }, > - { 0x2A0D, 0xA0 }, > - { 0x2A01, 0x0C }, > - { 0x2902, 0x01 }, > - { 0x2903, 0x02 }, > - { 0x2904, 0x00 }, > - { 0x2905, 0x00 }, > - { 0x2901, 0x01 }, > - { 0x1101, 0x0A }, > - { 0x1102, 0x84 }, > - { 0x2301, 0x00 }, > - { 0x2303, 0x00 }, > - { 0x2302, 0x3f }, > - { 0x2001, 0x03 }, > - { 0x1B75, 0xB6 }, > - { 0x1B73, 0xC2 }, > - { 0x1129, 0x01 }, > - { 0x1121, 0xF3 }, > - { 0x1103, 0x20 }, > - { 0x1105, 0x00 }, > - { 0x1112, 0xC0 }, > - { 0x1113, 0x80 }, > - { 0x1C03, 0xC0 }, > - { 0x1105, 0x00 }, > - { 0x1112, 0xC0 }, > - { 0x1101, 0x02 }, > - {} /* Terminator */ > -}; > - > -/* Vendor specific hw configuration for CS8409 */ > -static const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = { > - { 0x47, 0x00, 0xb008 }, /* +PLL1/2_EN, +I2C_EN */ > - { 0x47, 0x01, 0x0002 }, /* ASP1/2_EN=0, ASP1_STP=1 */ > - { 0x47, 0x02, 0x0a80 }, /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ > - { 0x47, 0x19, 0x0800 }, /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ > - { 0x47, 0x1a, 0x0820 }, /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ > - { 0x47, 0x29, 0x0800 }, /* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ > - { 0x47, 0x2a, 0x2800 }, /* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */ > - { 0x47, 0x39, 0x0800 }, /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ > - { 0x47, 0x3a, 0x0800 }, /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ > - { 0x47, 0x03, 0x8000 }, /* ASP1: LCHI = 00h */ > - { 0x47, 0x04, 0x28ff }, /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ > - { 0x47, 0x05, 0x0062 }, /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ > - { 0x47, 0x06, 0x801f }, /* ASP2: LCHI=1Fh */ > - { 0x47, 0x07, 0x283f }, /* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */ > - { 0x47, 0x08, 0x805c }, /* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */ > - { 0x47, 0x09, 0x0023 }, /* DMIC1_MO=10b, DMIC1/2_SR=1 */ > - { 0x47, 0x0a, 0x0000 }, /* ASP1/2_BEEP=0 */ > - { 0x47, 0x01, 0x0062 }, /* ASP1/2_EN=1, ASP1_STP=1 */ > - { 0x47, 0x00, 0x9008 }, /* -PLL2_EN */ > - { 0x47, 0x68, 0x0000 }, /* TX2.A: pre-scale att.=0 dB */ > - { 0x47, 0x82, 0xfc03 }, /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */ > - { 0x47, 0xc0, 0x9999 }, /* test mode on */ > - { 0x47, 0xc5, 0x0000 }, /* GPIO hysteresis = 30 us */ > - { 0x47, 0xc0, 0x0000 }, /* test mode off */ > - {} /* Terminator */ > -}; > - > -static const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = { > - { 0x47, 0x65, 0x4000 }, /* EQ_SEL=1, EQ1/2_EN=0 */ > - { 0x47, 0x64, 0x4000 }, /* +EQ_ACC */ > - { 0x47, 0x65, 0x4010 }, /* +EQ2_EN */ > - { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ > - { 0x47, 0x64, 0xc0c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */ > - { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ > - { 0x47, 0x64, 0xc1c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */ > - { 0x47, 0x63, 0xf370 }, /* EQ_DATA_HI=0xf370 */ > - { 0x47, 0x64, 0xc271 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */ > - { 0x47, 0x63, 0x1ef8 }, /* EQ_DATA_HI=0x1ef8 */ > - { 0x47, 0x64, 0xc348 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */ > - { 0x47, 0x63, 0xc110 }, /* EQ_DATA_HI=0xc110 */ > - { 0x47, 0x64, 0xc45a }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */ > - { 0x47, 0x63, 0x1f29 }, /* EQ_DATA_HI=0x1f29 */ > - { 0x47, 0x64, 0xc574 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */ > - { 0x47, 0x63, 0x1d7a }, /* EQ_DATA_HI=0x1d7a */ > - { 0x47, 0x64, 0xc653 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */ > - { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ > - { 0x47, 0x64, 0xc714 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */ > - { 0x47, 0x63, 0x1ca3 }, /* EQ_DATA_HI=0x1ca3 */ > - { 0x47, 0x64, 0xc8c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */ > - { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ > - { 0x47, 0x64, 0xc914 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */ > - { 0x47, 0x64, 0x0000 }, /* -EQ_ACC, -EQ_WRT */ > - {} /* Terminator */ > -}; > - > -/** > - * cs8409_enable_i2c_clock - Enable I2C clocks > - * @codec: the codec instance > - * @enable: Enable or disable I2C clocks > - * > - * Enable or Disable I2C clocks. > - */ > -static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int enable) > -{ > - unsigned int retval; > - unsigned int newval; > - > - retval = cs_vendor_coef_get(codec, 0x0); > - newval = (enable) ? (retval | 0x8) : (retval & 0xfffffff7); > - cs_vendor_coef_set(codec, 0x0, newval); > -} > - > -/** > - * cs8409_i2c_wait_complete - Wait for I2C transaction > - * @codec: the codec instance > - * > - * Wait for I2C transaction to complete. > - * Return -1 if transaction wait times out. > - */ > -static int cs8409_i2c_wait_complete(struct hda_codec *codec) > -{ > - int repeat = 5; > - unsigned int retval; > - > - do { > - retval = cs_vendor_coef_get(codec, CIR_I2C_STATUS); > - if ((retval & 0x18) != 0x18) { > - usleep_range(2000, 4000); > - --repeat; > - } else > - return 0; > - > - } while (repeat); > - > - return -1; > -} > - > -/** > - * cs8409_i2c_read - CS8409 I2C Read. > - * @codec: the codec instance > - * @i2c_address: I2C Address > - * @i2c_reg: Register to read > - * @paged: Is a paged transaction > - * > - * CS8409 I2C Read. > - * Returns negative on error, otherwise returns read value in bits 0-7. > - */ > -static int cs8409_i2c_read(struct hda_codec *codec, > - unsigned int i2c_address, > - unsigned int i2c_reg, > - unsigned int paged) > -{ > - unsigned int i2c_reg_data; > - unsigned int read_data; > - > - cs8409_enable_i2c_clock(codec, 1); > - cs_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); > - > - if (paged) { > - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); > - if (cs8409_i2c_wait_complete(codec) < 0) { > - codec_err(codec, > - "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", > - __func__, i2c_address, i2c_reg); > - return -EIO; > - } > - } > - > - i2c_reg_data = (i2c_reg << 8) & 0x0ffff; > - cs_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data); > - if (cs8409_i2c_wait_complete(codec) < 0) { > - codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", > - __func__, i2c_address, i2c_reg); > - return -EIO; > - } > - > - /* Register in bits 15-8 and the data in 7-0 */ > - read_data = cs_vendor_coef_get(codec, CIR_I2C_QREAD); > - > - cs8409_enable_i2c_clock(codec, 0); > - > - return read_data & 0x0ff; > -} > - > -/** > - * cs8409_i2c_write - CS8409 I2C Write. > - * @codec: the codec instance > - * @i2c_address: I2C Address > - * @i2c_reg: Register to write to > - * @i2c_data: Data to write > - * @paged: Is a paged transaction > - * > - * CS8409 I2C Write. > - * Returns negative on error, otherwise returns 0. > - */ > -static int cs8409_i2c_write(struct hda_codec *codec, > - unsigned int i2c_address, unsigned int i2c_reg, > - unsigned int i2c_data, > - unsigned int paged) > -{ > - unsigned int i2c_reg_data; > - > - cs8409_enable_i2c_clock(codec, 1); > - cs_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); > - > - if (paged) { > - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); > - if (cs8409_i2c_wait_complete(codec) < 0) { > - codec_err(codec, > - "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", > - __func__, i2c_address, i2c_reg); > - return -EIO; > - } > - } > - > - i2c_reg_data = ((i2c_reg << 8) & 0x0ff00) | (i2c_data & 0x0ff); > - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg_data); > - > - if (cs8409_i2c_wait_complete(codec) < 0) { > - codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", > - __func__, i2c_address, i2c_reg); > - return -EIO; > - } > - > - cs8409_enable_i2c_clock(codec, 0); > - > - return 0; > -} > - > -static int cs8409_cs42l42_volume_info(struct snd_kcontrol *kcontrol, > - struct snd_ctl_elem_info *uinfo) > -{ > - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); > - u16 nid = get_amp_nid(kcontrol); > - u8 chs = get_amp_channels(kcontrol); > - > - codec_dbg(codec, "%s() nid: %d\n", __func__, nid); > - switch (nid) { > - case CS8409_CS42L42_HP_PIN_NID: > - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; > - uinfo->count = chs == 3 ? 2 : 1; > - uinfo->value.integer.min = CS8409_CS42L42_HP_VOL_REAL_MIN; > - uinfo->value.integer.max = CS8409_CS42L42_HP_VOL_REAL_MAX; > - break; > - case CS8409_CS42L42_AMIC_PIN_NID: > - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; > - uinfo->count = chs == 3 ? 2 : 1; > - uinfo->value.integer.min = CS8409_CS42L42_AMIC_VOL_REAL_MIN; > - uinfo->value.integer.max = CS8409_CS42L42_AMIC_VOL_REAL_MAX; > - break; > - default: > - break; > - } > - return 0; > -} > - > -static void cs8409_cs42l42_update_volume(struct hda_codec *codec) > -{ > - struct cs_spec *spec = codec->spec; > - int data; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHA, 1); > - if (data >= 0) > - spec->cs42l42_hp_volume[0] = -data; > - else > - spec->cs42l42_hp_volume[0] = CS8409_CS42L42_HP_VOL_REAL_MIN; > - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHB, 1); > - if (data >= 0) > - spec->cs42l42_hp_volume[1] = -data; > - else > - spec->cs42l42_hp_volume[1] = CS8409_CS42L42_HP_VOL_REAL_MIN; > - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_AMIC_VOLUME, 1); > - if (data >= 0) > - spec->cs42l42_hs_mic_volume[0] = -data; > - else > - spec->cs42l42_hs_mic_volume[0] = CS8409_CS42L42_AMIC_VOL_REAL_MIN; > - mutex_unlock(&spec->cs8409_i2c_mux); > - spec->cs42l42_volume_init = 1; > -} > - > -static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kcontrol, > - struct snd_ctl_elem_value *ucontrol) > -{ > - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); > - struct cs_spec *spec = codec->spec; > - hda_nid_t nid = get_amp_nid(kcontrol); > - int chs = get_amp_channels(kcontrol); > - long *valp = ucontrol->value.integer.value; > - > - if (!spec->cs42l42_volume_init) { > - snd_hda_power_up(codec); > - cs8409_cs42l42_update_volume(codec); > - snd_hda_power_down(codec); > - } > - switch (nid) { > - case CS8409_CS42L42_HP_PIN_NID: > - if (chs & BIT(0)) > - *valp++ = spec->cs42l42_hp_volume[0]; > - if (chs & BIT(1)) > - *valp++ = spec->cs42l42_hp_volume[1]; > - break; > - case CS8409_CS42L42_AMIC_PIN_NID: > - if (chs & BIT(0)) > - *valp++ = spec->cs42l42_hs_mic_volume[0]; > - break; > - default: > - break; > - } > - return 0; > -} > - > -static int cs8409_cs42l42_volume_put(struct snd_kcontrol *kcontrol, > - struct snd_ctl_elem_value *ucontrol) > -{ > - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); > - struct cs_spec *spec = codec->spec; > - hda_nid_t nid = get_amp_nid(kcontrol); > - int chs = get_amp_channels(kcontrol); > - long *valp = ucontrol->value.integer.value; > - int change = 0; > - char vol; > - > - snd_hda_power_up(codec); > - switch (nid) { > - case CS8409_CS42L42_HP_PIN_NID: > - mutex_lock(&spec->cs8409_i2c_mux); > - if (chs & BIT(0)) { > - vol = -(*valp); > - change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHA, vol, 1); > - valp++; > - } > - if (chs & BIT(1)) { > - vol = -(*valp); > - change |= cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHB, vol, 1); > - } > - mutex_unlock(&spec->cs8409_i2c_mux); > - break; > - case CS8409_CS42L42_AMIC_PIN_NID: > - mutex_lock(&spec->cs8409_i2c_mux); > - if (chs & BIT(0)) { > - change = cs8409_i2c_write( > - codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_AMIC_VOLUME, (char)*valp, 1); > - valp++; > - } > - mutex_unlock(&spec->cs8409_i2c_mux); > - break; > - default: > - break; > - } > - cs8409_cs42l42_update_volume(codec); > - snd_hda_power_down(codec); > - return change; > -} > - > -static const DECLARE_TLV_DB_SCALE( > - cs8409_cs42l42_hp_db_scale, > - CS8409_CS42L42_HP_VOL_REAL_MIN * 100, 100, 1); > - > -static const DECLARE_TLV_DB_SCALE( > - cs8409_cs42l42_amic_db_scale, > - CS8409_CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1); > - > -static const struct snd_kcontrol_new cs8409_cs42l42_hp_volume_mixer = { > - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, > - .index = 0, > - .name = "Headphone Playback Volume", > - .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), > - .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE > - | SNDRV_CTL_ELEM_ACCESS_TLV_READ), > - .info = cs8409_cs42l42_volume_info, > - .get = cs8409_cs42l42_volume_get, > - .put = cs8409_cs42l42_volume_put, > - .tlv = { .p = cs8409_cs42l42_hp_db_scale }, > - .private_value = HDA_COMPOSE_AMP_VAL( > - CS8409_CS42L42_HP_PIN_NID, 3, 0, HDA_OUTPUT) > - | HDA_AMP_VAL_MIN_MUTE > -}; > - > -static const struct snd_kcontrol_new cs8409_cs42l42_amic_volume_mixer = { > - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, > - .index = 0, > - .name = "Mic Capture Volume", > - .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), > - .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE > - | SNDRV_CTL_ELEM_ACCESS_TLV_READ), > - .info = cs8409_cs42l42_volume_info, > - .get = cs8409_cs42l42_volume_get, > - .put = cs8409_cs42l42_volume_put, > - .tlv = { .p = cs8409_cs42l42_amic_db_scale }, > - .private_value = HDA_COMPOSE_AMP_VAL( > - CS8409_CS42L42_AMIC_PIN_NID, 1, 0, HDA_INPUT) > - | HDA_AMP_VAL_MIN_MUTE > -}; > - > -/* Assert/release RTS# line to CS42L42 */ > -static void cs8409_cs42l42_reset(struct hda_codec *codec) > -{ > - struct cs_spec *spec = codec->spec; > - > - /* Assert RTS# line */ > - snd_hda_codec_write(codec, > - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); > - /* wait ~10ms */ > - usleep_range(10000, 15000); > - /* Release RTS# line */ > - snd_hda_codec_write(codec, > - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, GPIO5_INT); > - /* wait ~10ms */ > - usleep_range(10000, 15000); > - > - mutex_lock(&spec->cs8409_i2c_mux); > - > - /* Clear interrupts, by reading interrupt status registers */ > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1309, 1); > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130A, 1); > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130F, 1); > - > - mutex_unlock(&spec->cs8409_i2c_mux); > - > -} > - > -/* Configure CS42L42 slave codec for jack autodetect */ > -static void cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec) > -{ > - struct cs_spec *spec = codec->spec; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - > - /* Set TIP_SENSE_EN for analog front-end of tip sense. */ > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b70, 0x0020, 1); > - /* Clear WAKE# */ > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0001, 1); > - /* Wait ~2.5ms */ > - usleep_range(2500, 3000); > - /* Set mode WAKE# output follows the combination logic directly */ > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0020, 1); > - /* Clear interrupts status */ > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); > - /* Enable interrupt */ > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1320, 0x03, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b79, 0x00, 1); > - > - mutex_unlock(&spec->cs8409_i2c_mux); > -} > - > -/* Enable and run CS42L42 slave codec jack auto detect */ > -static void cs8409_cs42l42_run_jack_detect(struct hda_codec *codec) > -{ > - struct cs_spec *spec = codec->spec; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - > - /* Clear interrupts */ > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b77, 1); > - > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1102, 0x87, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1f06, 0x86, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b74, 0x07, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x131b, 0x01, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0x80, 1); > - /* Wait ~110ms*/ > - usleep_range(110000, 200000); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x111f, 0x77, 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0xc0, 1); > - /* Wait ~10ms */ > - usleep_range(10000, 25000); > - > - mutex_unlock(&spec->cs8409_i2c_mux); > - > -} > - > -static void cs8409_cs42l42_reg_setup(struct hda_codec *codec) > -{ > - const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq; > - struct cs_spec *spec = codec->spec; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - > - for (; seq->addr; seq++) > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1); > - > - mutex_unlock(&spec->cs8409_i2c_mux); > - > -} > - > -/* > - * In the case of CS8409 we do not have unsolicited events from NID's 0x24 > - * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will > - * generate interrupt via gpio 4 to notify jack events. We have to overwrite > - * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers > - * and then notify status via generic snd_hda_jack_unsol_event() call. > - */ > -static void cs8409_jack_unsol_event(struct hda_codec *codec, unsigned int res) > -{ > - struct cs_spec *spec = codec->spec; > - int status_changed = 0; > - int reg_cdc_status; > - int reg_hs_status; > - int reg_ts_status; > - int type; > - struct hda_jack_tbl *jk; > - > - /* jack_unsol_event() will be called every time gpio line changing state. > - * In this case gpio4 line goes up as a result of reading interrupt status > - * registers in previous cs8409_jack_unsol_event() call. > - * We don't need to handle this event, ignoring... > - */ > - if ((res & (1 << 4))) > - return; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - > - /* Read jack detect status registers */ > - reg_cdc_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > - reg_hs_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1124, 1); > - reg_ts_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); > - > - /* Clear interrupts, by reading interrupt status registers */ > - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); > - > - mutex_unlock(&spec->cs8409_i2c_mux); > - > - /* If status values are < 0, read error has occurred. */ > - if (reg_cdc_status < 0 || reg_hs_status < 0 || reg_ts_status < 0) > - return; > - > - /* HSDET_AUTO_DONE */ > - if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE) { > - > - type = ((reg_hs_status & CS42L42_HSTYPE_MASK) + 1); > - /* CS42L42 reports optical jack as type 4 > - * We don't handle optical jack > - */ > - if (type != 4) { > - if (!spec->cs42l42_hp_jack_in) { > - status_changed = 1; > - spec->cs42l42_hp_jack_in = 1; > - } > - /* type = 3 has no mic */ > - if ((!spec->cs42l42_mic_jack_in) && (type != 3)) { > - status_changed = 1; > - spec->cs42l42_mic_jack_in = 1; > - } > - } else { > - if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { > - status_changed = 1; > - spec->cs42l42_hp_jack_in = 0; > - spec->cs42l42_mic_jack_in = 0; > - } > - } > - > - } else { > - /* TIP_SENSE INSERT/REMOVE */ > - switch (reg_ts_status) { > - case CS42L42_JACK_INSERTED: > - cs8409_cs42l42_run_jack_detect(codec); > - break; > - > - case CS42L42_JACK_REMOVED: > - if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { > - status_changed = 1; > - spec->cs42l42_hp_jack_in = 0; > - spec->cs42l42_mic_jack_in = 0; > - } > - break; > - > - default: > - /* jack in transition */ > - status_changed = 0; > - break; > - } > - } > - > - if (status_changed) { > - > - snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID, > - spec->cs42l42_hp_jack_in ? 0 : PIN_OUT); > - > - /* Report jack*/ > - jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0); > - if (jk) { > - snd_hda_jack_unsol_event(codec, > - (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG); > - } > - /* Report jack*/ > - jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0); > - if (jk) { > - snd_hda_jack_unsol_event(codec, > - (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG); > - } > - } > -} > - > -#ifdef CONFIG_PM > -/* Manage PDREF, when transition to D3hot */ > -static int cs8409_suspend(struct hda_codec *codec) > -{ > - struct cs_spec *spec = codec->spec; > - > - mutex_lock(&spec->cs8409_i2c_mux); > - /* Power down CS42L42 ASP/EQ/MIX/HP */ > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1); > - mutex_unlock(&spec->cs8409_i2c_mux); > - /* Assert CS42L42 RTS# line */ > - snd_hda_codec_write(codec, > - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); > - > - snd_hda_shutup_pins(codec); > - > - return 0; > -} > -#endif > - > -/* Enable/Disable Unsolicited Response for gpio(s) 3,4 */ > -static void cs8409_enable_ur(struct hda_codec *codec, int flag) > -{ > - /* GPIO4 INT# and GPIO3 WAKE# */ > - snd_hda_codec_write(codec, codec->core.afg, > - 0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, > - flag ? (GPIO3_INT | GPIO4_INT) : 0); > - > - snd_hda_codec_write(codec, codec->core.afg, > - 0, AC_VERB_SET_UNSOLICITED_ENABLE, > - flag ? AC_UNSOL_ENABLED : 0); > - > -} > - > -/* Vendor specific HW configuration > - * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... > - */ > -static void cs8409_cs42l42_hw_init(struct hda_codec *codec) > -{ > - const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg; > - const struct cs8409_cir_param *seq_bullseye = cs8409_cs42l42_bullseye_atn; > - struct cs_spec *spec = codec->spec; > - > - if (spec->gpio_mask) { > - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, > - spec->gpio_mask); > - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, > - spec->gpio_dir); > - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, > - spec->gpio_data); > - } > - > - for (; seq->nid; seq++) > - cs_vendor_coef_set(codec, seq->cir, seq->coeff); > - > - if (codec->fixup_id == CS8409_BULLSEYE) > - for (; seq_bullseye->nid; seq_bullseye++) > - cs_vendor_coef_set(codec, seq_bullseye->cir, seq_bullseye->coeff); > - > - /* Disable Unsolicited Response during boot */ > - cs8409_enable_ur(codec, 0); > - > - /* Reset CS42L42 */ > - cs8409_cs42l42_reset(codec); > - > - /* Initialise CS42L42 companion codec */ > - cs8409_cs42l42_reg_setup(codec); > - > - if (codec->fixup_id == CS8409_WARLOCK || > - codec->fixup_id == CS8409_CYBORG) { > - /* FULL_SCALE_VOL = 0 for Warlock / Cyborg */ > - mutex_lock(&spec->cs8409_i2c_mux); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1); > - mutex_unlock(&spec->cs8409_i2c_mux); > - /* DMIC1_MO=00b, DMIC1/2_SR=1 */ > - cs_vendor_coef_set(codec, 0x09, 0x0003); > - } > - > - /* Restore Volumes after Resume */ > - if (spec->cs42l42_volume_init) { > - mutex_lock(&spec->cs8409_i2c_mux); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHA, > - -spec->cs42l42_hp_volume[0], > - 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_HS_VOLUME_CHB, > - -spec->cs42l42_hp_volume[1], > - 1); > - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > - CS8409_CS42L42_REG_AMIC_VOLUME, > - spec->cs42l42_hs_mic_volume[0], > - 1); > - mutex_unlock(&spec->cs8409_i2c_mux); > - } > - > - cs8409_cs42l42_update_volume(codec); > - > - cs8409_cs42l42_enable_jack_detect(codec); > - > - /* Enable Unsolicited Response */ > - cs8409_enable_ur(codec, 1); > -} > - > -static int cs8409_cs42l42_init(struct hda_codec *codec) > -{ > - int ret = snd_hda_gen_init(codec); > - > - if (!ret) > - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); > - > - return ret; > -} > - > -static const struct hda_codec_ops cs8409_cs42l42_patch_ops = { > - .build_controls = cs_build_controls, > - .build_pcms = snd_hda_gen_build_pcms, > - .init = cs8409_cs42l42_init, > - .free = cs_free, > - .unsol_event = cs8409_jack_unsol_event, > -#ifdef CONFIG_PM > - .suspend = cs8409_suspend, > -#endif > -}; > - > -static void cs8409_cs42l42_fixups(struct hda_codec *codec, > - const struct hda_fixup *fix, int action) > -{ > - struct cs_spec *spec = codec->spec; > - int caps; > - > - switch (action) { > - case HDA_FIXUP_ACT_PRE_PROBE: > - snd_hda_add_verbs(codec, cs8409_cs42l42_init_verbs); > - /* verb exec op override */ > - spec->exec_verb = codec->core.exec_verb; > - codec->core.exec_verb = cs8409_cs42l42_exec_verb; > - > - mutex_init(&spec->cs8409_i2c_mux); > - > - codec->patch_ops = cs8409_cs42l42_patch_ops; > - > - spec->gen.suppress_auto_mute = 1; > - spec->gen.no_primary_hp = 1; > - spec->gen.suppress_vmaster = 1; > - > - /* GPIO 5 out, 3,4 in */ > - spec->gpio_dir = GPIO5_INT; > - spec->gpio_data = 0; > - spec->gpio_mask = 0x03f; > - > - spec->cs42l42_hp_jack_in = 0; > - spec->cs42l42_mic_jack_in = 0; > - > - /* Basic initial sequence for specific hw configuration */ > - snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs); > - > - /* CS8409 is simple HDA bridge and intended to be used with a remote > - * companion codec. Most of input/output PIN(s) have only basic > - * capabilities. NID(s) 0x24 and 0x34 have only OUTC and INC > - * capabilities and no presence detect capable (PDC) and call to > - * snd_hda_gen_build_controls() will mark them as non detectable > - * phantom jacks. However, in this configuration companion codec > - * CS42L42 is connected to these pins and it has jack detect > - * capabilities. We have to override pin capabilities, > - * otherwise they will not be created as input devices. > - */ > - caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_HP_PIN_NID, > - AC_PAR_PIN_CAP); > - if (caps >= 0) > - snd_hdac_override_parm(&codec->core, > - CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, > - (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); > - > - caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID, > - AC_PAR_PIN_CAP); > - if (caps >= 0) > - snd_hdac_override_parm(&codec->core, > - CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP, > - (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); > - > - snd_hda_override_wcaps(codec, CS8409_CS42L42_HP_PIN_NID, > - (get_wcaps(codec, CS8409_CS42L42_HP_PIN_NID) | AC_WCAP_UNSOL_CAP)); > - > - snd_hda_override_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID, > - (get_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID) | AC_WCAP_UNSOL_CAP)); > - break; > - case HDA_FIXUP_ACT_PROBE: > - > - /* Set initial DMIC volume to -26 dB */ > - snd_hda_codec_amp_init_stereo(codec, CS8409_CS42L42_DMIC_ADC_PIN_NID, > - HDA_INPUT, 0, 0xff, 0x19); > - snd_hda_gen_add_kctl(&spec->gen, > - NULL, &cs8409_cs42l42_hp_volume_mixer); > - snd_hda_gen_add_kctl(&spec->gen, > - NULL, &cs8409_cs42l42_amic_volume_mixer); > - cs8409_cs42l42_hw_init(codec); > - snd_hda_codec_set_name(codec, "CS8409/CS42L42"); > - break; > - case HDA_FIXUP_ACT_INIT: > - cs8409_cs42l42_hw_init(codec); > - fallthrough; > - case HDA_FIXUP_ACT_BUILD: > - /* Run jack auto detect first time on boot > - * after controls have been added, to check if jack has > - * been already plugged in. > - * Run immediately after init. > - */ > - cs8409_cs42l42_run_jack_detect(codec); > - usleep_range(100000, 150000); > - break; > - default: > - break; > - } > -} > - > -static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, > - unsigned int cmd, unsigned int flags, unsigned int *res) > -{ > - struct hda_codec *codec = container_of(dev, struct hda_codec, core); > - struct cs_spec *spec = codec->spec; > - > - unsigned int nid = ((cmd >> 20) & 0x07f); > - unsigned int verb = ((cmd >> 8) & 0x0fff); > - > - /* CS8409 pins have no AC_PINSENSE_PRESENCE > - * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34 > - * and return correct pin sense values for read_pin_sense() call from > - * hda_jack based on CS42L42 jack detect status. > - */ > - switch (nid) { > - case CS8409_CS42L42_HP_PIN_NID: > - if (verb == AC_VERB_GET_PIN_SENSE) { > - *res = (spec->cs42l42_hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; > - return 0; > - } > - break; > - > - case CS8409_CS42L42_AMIC_PIN_NID: > - if (verb == AC_VERB_GET_PIN_SENSE) { > - *res = (spec->cs42l42_mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; > - return 0; > - } > - break; > - > - default: > - break; > - } > - > - return spec->exec_verb(dev, cmd, flags, res); > -} > - > -static int patch_cs8409(struct hda_codec *codec) > -{ > - int err; > - > - if (!cs_alloc_spec(codec, CS8409_VENDOR_NID)) > - return -ENOMEM; > - > - snd_hda_pick_fixup(codec, > - cs8409_models, cs8409_fixup_tbl, cs8409_fixups); > - > - codec_dbg(codec, "Picked ID=%d, VID=%08x, DEV=%08x\n", > - codec->fixup_id, > - codec->bus->pci->subsystem_vendor, > - codec->bus->pci->subsystem_device); > - > - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); > - > - err = cs_parse_auto_config(codec); > - if (err < 0) { > - cs_free(codec); > - return err; > - } > - > - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); > - return 0; > -} > - > /* > * patch entries > */ > @@ -2305,7 +1232,6 @@ static const struct hda_device_id snd_hda_id_cirrus[] = { > HDA_CODEC_ENTRY(0x10134208, "CS4208", patch_cs4208), > HDA_CODEC_ENTRY(0x10134210, "CS4210", patch_cs4210), > HDA_CODEC_ENTRY(0x10134213, "CS4213", patch_cs4213), > - HDA_CODEC_ENTRY(0x10138409, "CS8409", patch_cs8409), > {} /* terminator */ > }; > MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cirrus); > diff --git a/sound/pci/hda/patch_cs8409.c b/sound/pci/hda/patch_cs8409.c > new file mode 100644 > index 000000000000..9b16f1b5b828 > --- /dev/null > +++ b/sound/pci/hda/patch_cs8409.c > @@ -0,0 +1,1060 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip > + * > + * Copyright (C) 2021 Cirrus Logic, Inc. and > + * Cirrus Logic International Semiconductor Ltd. > + */ > + > +#include <linux/init.h> > +#include <linux/slab.h> > +#include <linux/module.h> > +#include <sound/core.h> > +#include <linux/mutex.h> > +#include <linux/pci.h> > +#include <sound/tlv.h> > +#include <sound/hda_codec.h> > +#include "hda_local.h" > +#include "hda_auto_parser.h" > +#include "hda_jack.h" > +#include "hda_generic.h" > + > +#include "patch_cs8409.h" > + > +static int cs8409_parse_auto_config(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + int err; > + int i; > + > + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); > + if (err < 0) > + return err; > + > + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); > + if (err < 0) > + return err; > + > + /* keep the ADCs powered up when it's dynamically switchable */ > + if (spec->gen.dyn_adc_switch) { > + unsigned int done = 0; > + > + for (i = 0; i < spec->gen.input_mux.num_items; i++) { > + int idx = spec->gen.dyn_adc_idx[i]; > + > + if (done & (1 << idx)) > + continue; > + snd_hda_gen_fix_pin_power(codec, spec->gen.adc_nids[idx]); > + done |= 1 << idx; > + } > + } > + > + return 0; > +} > + > +/* Dell Inspiron models with cs8409/cs42l42 */ > +static const struct hda_model_fixup cs8409_models[] = { > + { .id = CS8409_BULLSEYE, .name = "bullseye" }, > + { .id = CS8409_WARLOCK, .name = "warlock" }, > + { .id = CS8409_CYBORG, .name = "cyborg" }, > + {} > +}; > + > +/* Dell Inspiron platforms > + * with cs8409 bridge and cs42l42 codec > + */ > +static const struct snd_pci_quirk cs8409_fixup_tbl[] = { > + SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE), > + SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK), > + SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG), > + SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG), > + {} /* terminator */ > +}; > + > +static const struct hda_verb cs8409_cs42l42_init_verbs[] = { > + { 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */ > + { 0x47, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ > + { 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ > + { 0x47, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ > + { 0x47, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ > + { 0x47, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ > + {} /* terminator */ > +}; > + > +static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { > + { 0x24, 0x042120f0 }, /* ASP-1-TX */ > + { 0x34, 0x04a12050 }, /* ASP-1-RX */ > + { 0x2c, 0x901000f0 }, /* ASP-2-TX */ > + { 0x44, 0x90a00090 }, /* DMIC-1 */ > + {} /* terminator */ > +}; > + > +static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec; > + > + spec = kzalloc(sizeof(*spec), GFP_KERNEL); > + if (!spec) > + return NULL; > + codec->spec = spec; > + codec->power_save_node = 1; > + snd_hda_gen_spec_init(&spec->gen); > + > + return spec; > +} > + > +/* Vendor specific HW configuration for CS42L42 */ > +static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = { > + { 0x1010, 0xB0 }, > + { 0x1D01, 0x00 }, > + { 0x1D02, 0x06 }, > + { 0x1D03, 0x00 }, > + { 0x1107, 0x01 }, > + { 0x1009, 0x02 }, > + { 0x1007, 0x03 }, > + { 0x1201, 0x00 }, > + { 0x1208, 0x13 }, > + { 0x1205, 0xFF }, > + { 0x1206, 0x00 }, > + { 0x1207, 0x20 }, > + { 0x1202, 0x0D }, > + { 0x2A02, 0x02 }, > + { 0x2A03, 0x00 }, > + { 0x2A04, 0x00 }, > + { 0x2A05, 0x02 }, > + { 0x2A06, 0x00 }, > + { 0x2A07, 0x20 }, > + { 0x2A08, 0x02 }, > + { 0x2A09, 0x00 }, > + { 0x2A0A, 0x80 }, > + { 0x2A0B, 0x02 }, > + { 0x2A0C, 0x00 }, > + { 0x2A0D, 0xA0 }, > + { 0x2A01, 0x0C }, > + { 0x2902, 0x01 }, > + { 0x2903, 0x02 }, > + { 0x2904, 0x00 }, > + { 0x2905, 0x00 }, > + { 0x2901, 0x01 }, > + { 0x1101, 0x0A }, > + { 0x1102, 0x84 }, > + { 0x2301, 0x00 }, > + { 0x2303, 0x00 }, > + { 0x2302, 0x3f }, > + { 0x2001, 0x03 }, > + { 0x1B75, 0xB6 }, > + { 0x1B73, 0xC2 }, > + { 0x1129, 0x01 }, > + { 0x1121, 0xF3 }, > + { 0x1103, 0x20 }, > + { 0x1105, 0x00 }, > + { 0x1112, 0xC0 }, > + { 0x1113, 0x80 }, > + { 0x1C03, 0xC0 }, > + { 0x1105, 0x00 }, > + { 0x1112, 0xC0 }, > + { 0x1101, 0x02 }, > + {} /* Terminator */ > +}; > + > +/* Vendor specific hw configuration for CS8409 */ > +static const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = { > + { 0x47, 0x00, 0xb008 }, /* +PLL1/2_EN, +I2C_EN */ > + { 0x47, 0x01, 0x0002 }, /* ASP1/2_EN=0, ASP1_STP=1 */ > + { 0x47, 0x02, 0x0a80 }, /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ > + { 0x47, 0x19, 0x0800 }, /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ > + { 0x47, 0x1a, 0x0820 }, /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ > + { 0x47, 0x29, 0x0800 }, /* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ > + { 0x47, 0x2a, 0x2800 }, /* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */ > + { 0x47, 0x39, 0x0800 }, /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ > + { 0x47, 0x3a, 0x0800 }, /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ > + { 0x47, 0x03, 0x8000 }, /* ASP1: LCHI = 00h */ > + { 0x47, 0x04, 0x28ff }, /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ > + { 0x47, 0x05, 0x0062 }, /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ > + { 0x47, 0x06, 0x801f }, /* ASP2: LCHI=1Fh */ > + { 0x47, 0x07, 0x283f }, /* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */ > + { 0x47, 0x08, 0x805c }, /* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */ > + { 0x47, 0x09, 0x0023 }, /* DMIC1_MO=10b, DMIC1/2_SR=1 */ > + { 0x47, 0x0a, 0x0000 }, /* ASP1/2_BEEP=0 */ > + { 0x47, 0x01, 0x0062 }, /* ASP1/2_EN=1, ASP1_STP=1 */ > + { 0x47, 0x00, 0x9008 }, /* -PLL2_EN */ > + { 0x47, 0x68, 0x0000 }, /* TX2.A: pre-scale att.=0 dB */ > + { 0x47, 0x82, 0xfc03 }, /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */ > + { 0x47, 0xc0, 0x9999 }, /* test mode on */ > + { 0x47, 0xc5, 0x0000 }, /* GPIO hysteresis = 30 us */ > + { 0x47, 0xc0, 0x0000 }, /* test mode off */ > + {} /* Terminator */ > +}; > + > +static const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = { > + { 0x47, 0x65, 0x4000 }, /* EQ_SEL=1, EQ1/2_EN=0 */ > + { 0x47, 0x64, 0x4000 }, /* +EQ_ACC */ > + { 0x47, 0x65, 0x4010 }, /* +EQ2_EN */ > + { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ > + { 0x47, 0x64, 0xc0c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */ > + { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ > + { 0x47, 0x64, 0xc1c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */ > + { 0x47, 0x63, 0xf370 }, /* EQ_DATA_HI=0xf370 */ > + { 0x47, 0x64, 0xc271 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */ > + { 0x47, 0x63, 0x1ef8 }, /* EQ_DATA_HI=0x1ef8 */ > + { 0x47, 0x64, 0xc348 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */ > + { 0x47, 0x63, 0xc110 }, /* EQ_DATA_HI=0xc110 */ > + { 0x47, 0x64, 0xc45a }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */ > + { 0x47, 0x63, 0x1f29 }, /* EQ_DATA_HI=0x1f29 */ > + { 0x47, 0x64, 0xc574 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */ > + { 0x47, 0x63, 0x1d7a }, /* EQ_DATA_HI=0x1d7a */ > + { 0x47, 0x64, 0xc653 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */ > + { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ > + { 0x47, 0x64, 0xc714 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */ > + { 0x47, 0x63, 0x1ca3 }, /* EQ_DATA_HI=0x1ca3 */ > + { 0x47, 0x64, 0xc8c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */ > + { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ > + { 0x47, 0x64, 0xc914 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */ > + { 0x47, 0x64, 0x0000 }, /* -EQ_ACC, -EQ_WRT */ > + {} /* Terminator */ > +}; > + > +static inline int cs8409_vendor_coef_get(struct hda_codec *codec, unsigned int idx) > +{ > + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_COEF_INDEX, idx); > + return snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0, AC_VERB_GET_PROC_COEF, 0); > +} > + > +static inline void cs8409_vendor_coef_set(struct hda_codec *codec, unsigned int idx, > + unsigned int coef) > +{ > + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_COEF_INDEX, idx); > + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_PROC_COEF, coef); > +} > + > +/** > + * cs8409_enable_i2c_clock - Enable I2C clocks > + * @codec: the codec instance > + * @enable: Enable or disable I2C clocks > + * > + * Enable or Disable I2C clocks. > + */ > +static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int enable) > +{ > + unsigned int retval; > + unsigned int newval; > + > + retval = cs8409_vendor_coef_get(codec, 0x0); > + newval = (enable) ? (retval | 0x8) : (retval & 0xfffffff7); > + cs8409_vendor_coef_set(codec, 0x0, newval); > +} > + > +/** > + * cs8409_i2c_wait_complete - Wait for I2C transaction > + * @codec: the codec instance > + * > + * Wait for I2C transaction to complete. > + * Return -1 if transaction wait times out. > + */ > +static int cs8409_i2c_wait_complete(struct hda_codec *codec) > +{ > + int repeat = 5; > + unsigned int retval; > + > + do { > + retval = cs8409_vendor_coef_get(codec, CIR_I2C_STATUS); > + if ((retval & 0x18) != 0x18) { > + usleep_range(2000, 4000); > + --repeat; > + } else > + return 0; > + > + } while (repeat); > + > + return -1; > +} > + > +/** > + * cs8409_i2c_read - CS8409 I2C Read. > + * @codec: the codec instance > + * @i2c_address: I2C Address > + * @i2c_reg: Register to read > + * @paged: Is a paged transaction > + * > + * CS8409 I2C Read. > + * Returns negative on error, otherwise returns read value in bits 0-7. > + */ > +static int cs8409_i2c_read(struct hda_codec *codec, unsigned int i2c_address, unsigned int i2c_reg, > + unsigned int paged) > +{ > + unsigned int i2c_reg_data; > + unsigned int read_data; > + > + cs8409_enable_i2c_clock(codec, 1); > + cs8409_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); > + > + if (paged) { > + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); > + if (cs8409_i2c_wait_complete(codec) < 0) { > + codec_err(codec, "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", > + __func__, i2c_address, i2c_reg); > + return -EIO; > + } > + } > + > + i2c_reg_data = (i2c_reg << 8) & 0x0ffff; > + cs8409_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data); > + if (cs8409_i2c_wait_complete(codec) < 0) { > + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", > + __func__, i2c_address, i2c_reg); > + return -EIO; > + } > + > + /* Register in bits 15-8 and the data in 7-0 */ > + read_data = cs8409_vendor_coef_get(codec, CIR_I2C_QREAD); > + > + cs8409_enable_i2c_clock(codec, 0); > + > + return read_data & 0x0ff; > +} > + > +/** > + * cs8409_i2c_write - CS8409 I2C Write. > + * @codec: the codec instance > + * @i2c_address: I2C Address > + * @i2c_reg: Register to write to > + * @i2c_data: Data to write > + * @paged: Is a paged transaction > + * > + * CS8409 I2C Write. > + * Returns negative on error, otherwise returns 0. > + */ > +static int cs8409_i2c_write(struct hda_codec *codec, unsigned int i2c_address, unsigned int i2c_reg, > + unsigned int i2c_data, unsigned int paged) > +{ > + unsigned int i2c_reg_data; > + > + cs8409_enable_i2c_clock(codec, 1); > + cs8409_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); > + > + if (paged) { > + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); > + if (cs8409_i2c_wait_complete(codec) < 0) { > + codec_err(codec, "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", > + __func__, i2c_address, i2c_reg); > + return -EIO; > + } > + } > + > + i2c_reg_data = ((i2c_reg << 8) & 0x0ff00) | (i2c_data & 0x0ff); > + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg_data); > + > + if (cs8409_i2c_wait_complete(codec) < 0) { > + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", > + __func__, i2c_address, i2c_reg); > + return -EIO; > + } > + > + cs8409_enable_i2c_clock(codec, 0); > + > + return 0; > +} > + > +static int cs8409_cs42l42_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo) > +{ > + u16 nid = get_amp_nid(kctrl); > + u8 chs = get_amp_channels(kctrl); > + > + switch (nid) { > + case CS8409_CS42L42_HP_PIN_NID: > + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; > + uinfo->count = chs == 3 ? 2 : 1; > + uinfo->value.integer.min = CS8409_CS42L42_HP_VOL_REAL_MIN; > + uinfo->value.integer.max = CS8409_CS42L42_HP_VOL_REAL_MAX; > + break; > + case CS8409_CS42L42_AMIC_PIN_NID: > + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; > + uinfo->count = chs == 3 ? 2 : 1; > + uinfo->value.integer.min = CS8409_CS42L42_AMIC_VOL_REAL_MIN; > + uinfo->value.integer.max = CS8409_CS42L42_AMIC_VOL_REAL_MAX; > + break; > + default: > + break; > + } > + return 0; > +} > + > +static void cs8409_cs42l42_update_volume(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + int data; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHA, 1); > + if (data >= 0) > + spec->cs42l42_hp_volume[0] = -data; > + else > + spec->cs42l42_hp_volume[0] = CS8409_CS42L42_HP_VOL_REAL_MIN; > + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHB, 1); > + if (data >= 0) > + spec->cs42l42_hp_volume[1] = -data; > + else > + spec->cs42l42_hp_volume[1] = CS8409_CS42L42_HP_VOL_REAL_MIN; > + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_AMIC_VOLUME, 1); > + if (data >= 0) > + spec->cs42l42_hs_mic_volume[0] = -data; > + else > + spec->cs42l42_hs_mic_volume[0] = CS8409_CS42L42_AMIC_VOL_REAL_MIN; > + mutex_unlock(&spec->cs8409_i2c_mux); > + spec->cs42l42_volume_init = 1; > +} > + > +static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) > +{ > + struct hda_codec *codec = snd_kcontrol_chip(kctrl); > + struct cs8409_spec *spec = codec->spec; > + hda_nid_t nid = get_amp_nid(kctrl); > + int chs = get_amp_channels(kctrl); > + long *valp = uctrl->value.integer.value; > + > + if (!spec->cs42l42_volume_init) { > + snd_hda_power_up(codec); > + cs8409_cs42l42_update_volume(codec); > + snd_hda_power_down(codec); > + } > + switch (nid) { > + case CS8409_CS42L42_HP_PIN_NID: > + if (chs & BIT(0)) > + *valp++ = spec->cs42l42_hp_volume[0]; > + if (chs & BIT(1)) > + *valp++ = spec->cs42l42_hp_volume[1]; > + break; > + case CS8409_CS42L42_AMIC_PIN_NID: > + if (chs & BIT(0)) > + *valp++ = spec->cs42l42_hs_mic_volume[0]; > + break; > + default: > + break; > + } > + return 0; > +} > + > +static int cs8409_cs42l42_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) > +{ > + struct hda_codec *codec = snd_kcontrol_chip(kctrl); > + struct cs8409_spec *spec = codec->spec; > + hda_nid_t nid = get_amp_nid(kctrl); > + int chs = get_amp_channels(kctrl); > + long *valp = uctrl->value.integer.value; > + int change = 0; > + char vol; > + > + snd_hda_power_up(codec); > + switch (nid) { > + case CS8409_CS42L42_HP_PIN_NID: > + mutex_lock(&spec->cs8409_i2c_mux); > + if (chs & BIT(0)) { > + vol = -(*valp); > + change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > + CS8409_CS42L42_REG_HS_VOLUME_CHA, vol, 1); > + valp++; > + } > + if (chs & BIT(1)) { > + vol = -(*valp); > + change |= cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > + CS8409_CS42L42_REG_HS_VOLUME_CHB, vol, 1); > + } > + mutex_unlock(&spec->cs8409_i2c_mux); > + break; > + case CS8409_CS42L42_AMIC_PIN_NID: > + mutex_lock(&spec->cs8409_i2c_mux); > + if (chs & BIT(0)) { > + change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, > + CS8409_CS42L42_REG_AMIC_VOLUME, (char)*valp, 1); > + valp++; > + } > + mutex_unlock(&spec->cs8409_i2c_mux); > + break; > + default: > + break; > + } > + cs8409_cs42l42_update_volume(codec); > + snd_hda_power_down(codec); > + return change; > +} > + > +static const DECLARE_TLV_DB_SCALE(cs8409_cs42l42_hp_db_scale, > + CS8409_CS42L42_HP_VOL_REAL_MIN * 100, 100, 1); > + > +static const DECLARE_TLV_DB_SCALE(cs8409_cs42l42_amic_db_scale, > + CS8409_CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1); > + > +static const struct snd_kcontrol_new cs8409_cs42l42_hp_volume_mixer = { > + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, > + .index = 0, > + .name = "Headphone Playback Volume", > + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), > + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), > + .info = cs8409_cs42l42_volume_info, > + .get = cs8409_cs42l42_volume_get, > + .put = cs8409_cs42l42_volume_put, > + .tlv = { .p = cs8409_cs42l42_hp_db_scale }, > + .private_value = HDA_COMPOSE_AMP_VAL(CS8409_CS42L42_HP_PIN_NID, 3, 0, HDA_OUTPUT) | > + HDA_AMP_VAL_MIN_MUTE > +}; > + > +static const struct snd_kcontrol_new cs8409_cs42l42_amic_volume_mixer = { > + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, > + .index = 0, > + .name = "Mic Capture Volume", > + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), > + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), > + .info = cs8409_cs42l42_volume_info, > + .get = cs8409_cs42l42_volume_get, > + .put = cs8409_cs42l42_volume_put, > + .tlv = { .p = cs8409_cs42l42_amic_db_scale }, > + .private_value = HDA_COMPOSE_AMP_VAL(CS8409_CS42L42_AMIC_PIN_NID, 1, 0, HDA_INPUT) | > + HDA_AMP_VAL_MIN_MUTE > +}; > + > +/* Assert/release RTS# line to CS42L42 */ > +static void cs8409_cs42l42_reset(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + > + /* Assert RTS# line */ > + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); > + /* wait ~10ms */ > + usleep_range(10000, 15000); > + /* Release RTS# line */ > + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, GPIO5_INT); > + /* wait ~10ms */ > + usleep_range(10000, 15000); > + > + mutex_lock(&spec->cs8409_i2c_mux); > + > + /* Clear interrupts, by reading interrupt status registers */ > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1309, 1); > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130A, 1); > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130F, 1); > + > + mutex_unlock(&spec->cs8409_i2c_mux); > + > +} > + > +/* Configure CS42L42 slave codec for jack autodetect */ > +static void cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + > + /* Set TIP_SENSE_EN for analog front-end of tip sense. */ > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b70, 0x0020, 1); > + /* Clear WAKE# */ > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0001, 1); > + /* Wait ~2.5ms */ > + usleep_range(2500, 3000); > + /* Set mode WAKE# output follows the combination logic directly */ > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0020, 1); > + /* Clear interrupts status */ > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); > + /* Enable interrupt */ > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1320, 0x03, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b79, 0x00, 1); > + > + mutex_unlock(&spec->cs8409_i2c_mux); > +} > + > +/* Enable and run CS42L42 slave codec jack auto detect */ > +static void cs8409_cs42l42_run_jack_detect(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + > + /* Clear interrupts */ > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b77, 1); > + > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1102, 0x87, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1f06, 0x86, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b74, 0x07, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x131b, 0x01, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0x80, 1); > + /* Wait ~110ms*/ > + usleep_range(110000, 200000); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x111f, 0x77, 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0xc0, 1); > + /* Wait ~10ms */ > + usleep_range(10000, 25000); > + > + mutex_unlock(&spec->cs8409_i2c_mux); > + > +} > + > +static void cs8409_cs42l42_reg_setup(struct hda_codec *codec) > +{ > + const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq; > + struct cs8409_spec *spec = codec->spec; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + > + for (; seq->addr; seq++) > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1); > + > + mutex_unlock(&spec->cs8409_i2c_mux); > + > +} > + > +/* > + * In the case of CS8409 we do not have unsolicited events from NID's 0x24 > + * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will > + * generate interrupt via gpio 4 to notify jack events. We have to overwrite > + * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers > + * and then notify status via generic snd_hda_jack_unsol_event() call. > + */ > +static void cs8409_jack_unsol_event(struct hda_codec *codec, unsigned int res) > +{ > + struct cs8409_spec *spec = codec->spec; > + int status_changed = 0; > + int reg_cdc_status; > + int reg_hs_status; > + int reg_ts_status; > + int type; > + struct hda_jack_tbl *jk; > + > + /* jack_unsol_event() will be called every time gpio line changing state. > + * In this case gpio4 line goes up as a result of reading interrupt status > + * registers in previous cs8409_jack_unsol_event() call. > + * We don't need to handle this event, ignoring... > + */ > + if ((res & (1 << 4))) > + return; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + > + /* Read jack detect status registers */ > + reg_cdc_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); > + reg_hs_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1124, 1); > + reg_ts_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); > + > + /* Clear interrupts, by reading interrupt status registers */ > + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); > + > + mutex_unlock(&spec->cs8409_i2c_mux); > + > + /* If status values are < 0, read error has occurred. */ > + if (reg_cdc_status < 0 || reg_hs_status < 0 || reg_ts_status < 0) > + return; > + > + /* HSDET_AUTO_DONE */ > + if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE) { > + > + type = ((reg_hs_status & CS42L42_HSTYPE_MASK) + 1); > + /* CS42L42 reports optical jack as type 4 > + * We don't handle optical jack > + */ > + if (type != 4) { > + if (!spec->cs42l42_hp_jack_in) { > + status_changed = 1; > + spec->cs42l42_hp_jack_in = 1; > + } > + /* type = 3 has no mic */ > + if ((!spec->cs42l42_mic_jack_in) && (type != 3)) { > + status_changed = 1; > + spec->cs42l42_mic_jack_in = 1; > + } > + } else { > + if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { > + status_changed = 1; > + spec->cs42l42_hp_jack_in = 0; > + spec->cs42l42_mic_jack_in = 0; > + } > + } > + > + } else { > + /* TIP_SENSE INSERT/REMOVE */ > + switch (reg_ts_status) { > + case CS42L42_JACK_INSERTED: > + cs8409_cs42l42_run_jack_detect(codec); > + break; > + > + case CS42L42_JACK_REMOVED: > + if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { > + status_changed = 1; > + spec->cs42l42_hp_jack_in = 0; > + spec->cs42l42_mic_jack_in = 0; > + } > + break; > + > + default: > + /* jack in transition */ > + status_changed = 0; > + break; > + } > + } > + > + if (status_changed) { > + > + snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID, > + spec->cs42l42_hp_jack_in ? 0 : PIN_OUT); > + > + /* Report jack*/ > + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0); > + if (jk) { > + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & > + AC_UNSOL_RES_TAG); > + } > + /* Report jack*/ > + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0); > + if (jk) { > + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & > + AC_UNSOL_RES_TAG); > + } > + } > +} > + > +#ifdef CONFIG_PM > +/* Manage PDREF, when transition to D3hot */ > +static int cs8409_suspend(struct hda_codec *codec) > +{ > + struct cs8409_spec *spec = codec->spec; > + > + mutex_lock(&spec->cs8409_i2c_mux); > + /* Power down CS42L42 ASP/EQ/MIX/HP */ > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1); > + mutex_unlock(&spec->cs8409_i2c_mux); > + /* Assert CS42L42 RTS# line */ > + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); > + > + snd_hda_shutup_pins(codec); > + > + return 0; > +} > +#endif > + > +/* Enable/Disable Unsolicited Response for gpio(s) 3,4 */ > +static void cs8409_enable_ur(struct hda_codec *codec, int flag) > +{ > + /* GPIO4 INT# and GPIO3 WAKE# */ > + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, > + flag ? (GPIO3_INT | GPIO4_INT) : 0); > + > + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_UNSOLICITED_ENABLE, > + flag ? AC_UNSOL_ENABLED : 0); > + > +} > + > +/* Vendor specific HW configuration > + * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... > + */ > +static void cs8409_cs42l42_hw_init(struct hda_codec *codec) > +{ > + const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg; > + const struct cs8409_cir_param *seq_bullseye = cs8409_cs42l42_bullseye_atn; > + struct cs8409_spec *spec = codec->spec; > + > + if (spec->gpio_mask) { > + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, spec->gpio_mask); > + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir); > + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_data); > + } > + > + for (; seq->nid; seq++) > + cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); > + > + if (codec->fixup_id == CS8409_BULLSEYE) > + for (; seq_bullseye->nid; seq_bullseye++) > + cs8409_vendor_coef_set(codec, seq_bullseye->cir, seq_bullseye->coeff); > + > + /* Disable Unsolicited Response during boot */ > + cs8409_enable_ur(codec, 0); > + > + /* Reset CS42L42 */ > + cs8409_cs42l42_reset(codec); > + > + /* Initialise CS42L42 companion codec */ > + cs8409_cs42l42_reg_setup(codec); > + > + if (codec->fixup_id == CS8409_WARLOCK || codec->fixup_id == CS8409_CYBORG) { > + /* FULL_SCALE_VOL = 0 for Warlock / Cyborg */ > + mutex_lock(&spec->cs8409_i2c_mux); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1); > + mutex_unlock(&spec->cs8409_i2c_mux); > + /* DMIC1_MO=00b, DMIC1/2_SR=1 */ > + cs8409_vendor_coef_set(codec, 0x09, 0x0003); > + } > + > + /* Restore Volumes after Resume */ > + if (spec->cs42l42_volume_init) { > + mutex_lock(&spec->cs8409_i2c_mux); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHA, > + -spec->cs42l42_hp_volume[0], 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHB, > + -spec->cs42l42_hp_volume[1], 1); > + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_AMIC_VOLUME, > + spec->cs42l42_hs_mic_volume[0], 1); > + mutex_unlock(&spec->cs8409_i2c_mux); > + } > + > + cs8409_cs42l42_update_volume(codec); > + > + cs8409_cs42l42_enable_jack_detect(codec); > + > + /* Enable Unsolicited Response */ > + cs8409_enable_ur(codec, 1); > +} > + > +static int cs8409_cs42l42_init(struct hda_codec *codec) > +{ > + int ret = snd_hda_gen_init(codec); > + > + if (!ret) > + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); > + > + return ret; > +} > + > +static int cs8409_build_controls(struct hda_codec *codec) > +{ > + int err; > + > + err = snd_hda_gen_build_controls(codec); > + if (err < 0) > + return err; > + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD); > + > + return 0; > +} > + > +static const struct hda_codec_ops cs8409_cs42l42_patch_ops = { > + .build_controls = cs8409_build_controls, > + .build_pcms = snd_hda_gen_build_pcms, > + .init = cs8409_cs42l42_init, > + .free = snd_hda_gen_free, > + .unsol_event = cs8409_jack_unsol_event, > +#ifdef CONFIG_PM > + .suspend = cs8409_suspend, > +#endif > +}; > + > +static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, unsigned int cmd, unsigned int flags, > + unsigned int *res) > +{ > + struct hda_codec *codec = container_of(dev, struct hda_codec, core); > + struct cs8409_spec *spec = codec->spec; > + > + unsigned int nid = ((cmd >> 20) & 0x07f); > + unsigned int verb = ((cmd >> 8) & 0x0fff); > + > + /* CS8409 pins have no AC_PINSENSE_PRESENCE > + * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34 > + * and return correct pin sense values for read_pin_sense() call from > + * hda_jack based on CS42L42 jack detect status. > + */ > + switch (nid) { > + case CS8409_CS42L42_HP_PIN_NID: > + if (verb == AC_VERB_GET_PIN_SENSE) { > + *res = (spec->cs42l42_hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; > + return 0; > + } > + break; > + > + case CS8409_CS42L42_AMIC_PIN_NID: > + if (verb == AC_VERB_GET_PIN_SENSE) { > + *res = (spec->cs42l42_mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; > + return 0; > + } > + break; > + > + default: > + break; > + } > + > + return spec->exec_verb(dev, cmd, flags, res); > +} > + > +static void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action) > +{ > + struct cs8409_spec *spec = codec->spec; > + int caps; > + > + switch (action) { > + case HDA_FIXUP_ACT_PRE_PROBE: > + snd_hda_add_verbs(codec, cs8409_cs42l42_init_verbs); > + /* verb exec op override */ > + spec->exec_verb = codec->core.exec_verb; > + codec->core.exec_verb = cs8409_cs42l42_exec_verb; > + > + mutex_init(&spec->cs8409_i2c_mux); > + > + codec->patch_ops = cs8409_cs42l42_patch_ops; > + > + spec->gen.suppress_auto_mute = 1; > + spec->gen.no_primary_hp = 1; > + spec->gen.suppress_vmaster = 1; > + > + /* GPIO 5 out, 3,4 in */ > + spec->gpio_dir = GPIO5_INT; > + spec->gpio_data = 0; > + spec->gpio_mask = 0x03f; > + > + spec->cs42l42_hp_jack_in = 0; > + spec->cs42l42_mic_jack_in = 0; > + > + /* Basic initial sequence for specific hw configuration */ > + snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs); > + > + /* CS8409 is simple HDA bridge and intended to be used with a remote > + * companion codec. Most of input/output PIN(s) have only basic > + * capabilities. NID(s) 0x24 and 0x34 have only OUTC and INC > + * capabilities and no presence detect capable (PDC) and call to > + * snd_hda_gen_build_controls() will mark them as non detectable > + * phantom jacks. However, in this configuration companion codec > + * CS42L42 is connected to these pins and it has jack detect > + * capabilities. We have to override pin capabilities, > + * otherwise they will not be created as input devices. > + */ > + caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_HP_PIN_NID, > + AC_PAR_PIN_CAP); > + if (caps >= 0) > + snd_hdac_override_parm(&codec->core, > + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, > + (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); > + > + caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID, > + AC_PAR_PIN_CAP); > + if (caps >= 0) > + snd_hdac_override_parm(&codec->core, > + CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP, > + (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); > + > + snd_hda_override_wcaps(codec, CS8409_CS42L42_HP_PIN_NID, > + (get_wcaps(codec, CS8409_CS42L42_HP_PIN_NID) | AC_WCAP_UNSOL_CAP)); > + > + snd_hda_override_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID, > + (get_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID) | AC_WCAP_UNSOL_CAP)); > + break; > + case HDA_FIXUP_ACT_PROBE: > + /* Set initial DMIC volume to -26 dB */ > + snd_hda_codec_amp_init_stereo(codec, CS8409_CS42L42_DMIC_ADC_PIN_NID, > + HDA_INPUT, 0, 0xff, 0x19); > + snd_hda_gen_add_kctl(&spec->gen, NULL, &cs8409_cs42l42_hp_volume_mixer); > + snd_hda_gen_add_kctl(&spec->gen, NULL, &cs8409_cs42l42_amic_volume_mixer); > + cs8409_cs42l42_hw_init(codec); > + snd_hda_codec_set_name(codec, "CS8409/CS42L42"); > + break; > + case HDA_FIXUP_ACT_INIT: > + cs8409_cs42l42_hw_init(codec); > + fallthrough; > + case HDA_FIXUP_ACT_BUILD: > + /* Run jack auto detect first time on boot > + * after controls have been added, to check if jack has > + * been already plugged in. > + * Run immediately after init. > + */ > + cs8409_cs42l42_run_jack_detect(codec); > + usleep_range(100000, 150000); > + break; > + default: > + break; > + } > +} > + > +static const struct hda_fixup cs8409_fixups[] = { > + [CS8409_BULLSEYE] = { > + .type = HDA_FIXUP_PINS, > + .v.pins = cs8409_cs42l42_pincfgs, > + .chained = true, > + .chain_id = CS8409_FIXUPS, > + }, > + [CS8409_WARLOCK] = { > + .type = HDA_FIXUP_PINS, > + .v.pins = cs8409_cs42l42_pincfgs, > + .chained = true, > + .chain_id = CS8409_FIXUPS, > + }, > + [CS8409_CYBORG] = { > + .type = HDA_FIXUP_PINS, > + .v.pins = cs8409_cs42l42_pincfgs, > + .chained = true, > + .chain_id = CS8409_FIXUPS, > + }, > + [CS8409_FIXUPS] = { > + .type = HDA_FIXUP_FUNC, > + .v.func = cs8409_cs42l42_fixups, > + }, > +}; > + > +static int patch_cs8409(struct hda_codec *codec) > +{ > + int err; > + > + if (!cs8409_alloc_spec(codec)) > + return -ENOMEM; > + > + snd_hda_pick_fixup(codec, cs8409_models, cs8409_fixup_tbl, cs8409_fixups); > + > + codec_dbg(codec, "Picked ID=%d, VID=%08x, DEV=%08x\n", codec->fixup_id, > + codec->bus->pci->subsystem_vendor, > + codec->bus->pci->subsystem_device); > + > + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); > + > + err = cs8409_parse_auto_config(codec); > + if (err < 0) { > + snd_hda_gen_free(codec); > + return err; > + } > + > + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); > + return 0; > +} > + > +static const struct hda_device_id snd_hda_id_cs8409[] = { > + HDA_CODEC_ENTRY(0x10138409, "CS8409", patch_cs8409), > + {} /* terminator */ > +}; > +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cs8409); > + > +static struct hda_codec_driver cs8409_driver = { > + .id = snd_hda_id_cs8409, > +}; > +module_hda_codec_driver(cs8409_driver); > + > +MODULE_LICENSE("GPL"); > +MODULE_DESCRIPTION("Cirrus Logic HDA bridge"); > diff --git a/sound/pci/hda/patch_cs8409.h b/sound/pci/hda/patch_cs8409.h > new file mode 100644 > index 000000000000..2ab02a520f5a > --- /dev/null > +++ b/sound/pci/hda/patch_cs8409.h > @@ -0,0 +1,91 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip > + * > + * Copyright (C) 2021 Cirrus Logic, Inc. and > + * Cirrus Logic International Semiconductor Ltd. > + */ > + > +#ifndef __CS8409_PATCH_H > +#define __CS8409_PATCH_H > + > +/* Cirrus Logic CS8409 HDA bridge with > + * companion codec CS42L42 > + */ > +#define CS42L42_HP_CH (2U) > +#define CS42L42_HS_MIC_CH (1U) > + > +#define CS8409_VENDOR_NID 0x47 > + > +#define CS8409_CS42L42_HP_PIN_NID 0x24 > +#define CS8409_CS42L42_SPK_PIN_NID 0x2c > +#define CS8409_CS42L42_AMIC_PIN_NID 0x34 > +#define CS8409_CS42L42_DMIC_PIN_NID 0x44 > +#define CS8409_CS42L42_DMIC_ADC_PIN_NID 0x22 > + > +#define CS42L42_HSDET_AUTO_DONE 0x02 > +#define CS42L42_HSTYPE_MASK 0x03 > + > +#define CS42L42_JACK_INSERTED 0x0C > +#define CS42L42_JACK_REMOVED 0x00 > + > +#define GPIO3_INT (1 << 3) > +#define GPIO4_INT (1 << 4) > +#define GPIO5_INT (1 << 5) > + > +#define CS42L42_I2C_ADDR (0x48 << 1) > + > +#define CIR_I2C_ADDR 0x0059 > +#define CIR_I2C_DATA 0x005A > +#define CIR_I2C_CTRL 0x005B > +#define CIR_I2C_STATUS 0x005C > +#define CIR_I2C_QWRITE 0x005D > +#define CIR_I2C_QREAD 0x005E > + > +#define CS8409_CS42L42_HP_VOL_REAL_MIN (-63) > +#define CS8409_CS42L42_HP_VOL_REAL_MAX (0) > +#define CS8409_CS42L42_AMIC_VOL_REAL_MIN (-97) > +#define CS8409_CS42L42_AMIC_VOL_REAL_MAX (12) > +#define CS8409_CS42L42_REG_HS_VOLUME_CHA (0x2301) > +#define CS8409_CS42L42_REG_HS_VOLUME_CHB (0x2303) > +#define CS8409_CS42L42_REG_AMIC_VOLUME (0x1D03) > + > +enum { > + CS8409_BULLSEYE, > + CS8409_WARLOCK, > + CS8409_CYBORG, > + CS8409_FIXUPS, > +}; > + > +struct cs8409_i2c_param { > + unsigned int addr; > + unsigned int reg; > +}; > + > +struct cs8409_cir_param { > + unsigned int nid; > + unsigned int cir; > + unsigned int coeff; > +}; > + > +struct cs8409_spec { > + struct hda_gen_spec gen; > + > + unsigned int gpio_mask; > + unsigned int gpio_dir; > + unsigned int gpio_data; > + > + unsigned int cs42l42_hp_jack_in:1; > + unsigned int cs42l42_mic_jack_in:1; > + unsigned int cs42l42_volume_init:1; > + char cs42l42_hp_volume[CS42L42_HP_CH]; > + char cs42l42_hs_mic_volume[CS42L42_HS_MIC_CH]; > + > + struct mutex cs8409_i2c_mux; > + > + /* verb exec op override */ > + int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, > + unsigned int *res); > +}; > + > +#endif > -- > 2.25.1 > >
diff --git a/sound/pci/hda/Kconfig b/sound/pci/hda/Kconfig index c4360cdbc728..ab9d2746e804 100644 --- a/sound/pci/hda/Kconfig +++ b/sound/pci/hda/Kconfig @@ -157,6 +157,16 @@ config SND_HDA_CODEC_CIRRUS comment "Set to Y if you want auto-loading the codec driver" depends on SND_HDA=y && SND_HDA_CODEC_CIRRUS=m +config SND_HDA_CODEC_CS8409 + tristate "Build Cirrus Logic HDA bridge support" + select SND_HDA_GENERIC + help + Say Y or M here to include Cirrus Logic HDA bridge support in + snd-hda-intel driver, such as CS8409. + +comment "Set to Y if you want auto-loading the codec driver" + depends on SND_HDA=y && SND_HDA_CODEC_CS8409=m + config SND_HDA_CODEC_CONEXANT tristate "Build Conexant HD-audio codec support" select SND_HDA_GENERIC diff --git a/sound/pci/hda/Makefile b/sound/pci/hda/Makefile index b57432f00056..1b73e08dc563 100644 --- a/sound/pci/hda/Makefile +++ b/sound/pci/hda/Makefile @@ -20,6 +20,7 @@ snd-hda-codec-analog-objs := patch_analog.o snd-hda-codec-idt-objs := patch_sigmatel.o snd-hda-codec-si3054-objs := patch_si3054.o snd-hda-codec-cirrus-objs := patch_cirrus.o +snd-hda-codec-cs8409-objs := patch_cs8409.o snd-hda-codec-ca0110-objs := patch_ca0110.o snd-hda-codec-ca0132-objs := patch_ca0132.o snd-hda-codec-conexant-objs := patch_conexant.o @@ -37,6 +38,7 @@ obj-$(CONFIG_SND_HDA_CODEC_ANALOG) += snd-hda-codec-analog.o obj-$(CONFIG_SND_HDA_CODEC_SIGMATEL) += snd-hda-codec-idt.o obj-$(CONFIG_SND_HDA_CODEC_SI3054) += snd-hda-codec-si3054.o obj-$(CONFIG_SND_HDA_CODEC_CIRRUS) += snd-hda-codec-cirrus.o +obj-$(CONFIG_SND_HDA_CODEC_CS8409) += snd-hda-codec-cs8409.o obj-$(CONFIG_SND_HDA_CODEC_CA0110) += snd-hda-codec-ca0110.o obj-$(CONFIG_SND_HDA_CODEC_CA0132) += snd-hda-codec-ca0132.o obj-$(CONFIG_SND_HDA_CODEC_CONEXANT) += snd-hda-codec-conexant.o diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c index 8629e84fef23..678fbcaf2a3b 100644 --- a/sound/pci/hda/patch_cirrus.c +++ b/sound/pci/hda/patch_cirrus.c @@ -9,7 +9,6 @@ #include <linux/slab.h> #include <linux/module.h> #include <sound/core.h> -#include <linux/mutex.h> #include <linux/pci.h> #include <sound/tlv.h> #include <sound/hda_codec.h> @@ -21,9 +20,6 @@ /* */ -#define CS42L42_HP_CH (2U) -#define CS42L42_HS_MIC_CH (1U) - struct cs_spec { struct hda_gen_spec gen; @@ -42,18 +38,6 @@ struct cs_spec { /* for MBP SPDIF control */ int (*spdif_sw_put)(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol); - - unsigned int cs42l42_hp_jack_in:1; - unsigned int cs42l42_mic_jack_in:1; - unsigned int cs42l42_volume_init:1; - char cs42l42_hp_volume[CS42L42_HP_CH]; - char cs42l42_hs_mic_volume[CS42L42_HS_MIC_CH]; - - struct mutex cs8409_i2c_mux; - - /* verb exec op override */ - int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, - unsigned int flags, unsigned int *res); }; /* available models with CS420x */ @@ -1239,1063 +1223,6 @@ static int patch_cs4213(struct hda_codec *codec) return err; } -/* Cirrus Logic CS8409 HDA bridge with - * companion codec CS42L42 - */ -#define CS8409_VENDOR_NID 0x47 - -#define CS8409_CS42L42_HP_PIN_NID 0x24 -#define CS8409_CS42L42_SPK_PIN_NID 0x2c -#define CS8409_CS42L42_AMIC_PIN_NID 0x34 -#define CS8409_CS42L42_DMIC_PIN_NID 0x44 -#define CS8409_CS42L42_DMIC_ADC_PIN_NID 0x22 - -#define CS42L42_HSDET_AUTO_DONE 0x02 -#define CS42L42_HSTYPE_MASK 0x03 - -#define CS42L42_JACK_INSERTED 0x0C -#define CS42L42_JACK_REMOVED 0x00 - -#define GPIO3_INT (1 << 3) -#define GPIO4_INT (1 << 4) -#define GPIO5_INT (1 << 5) - -#define CS42L42_I2C_ADDR (0x48 << 1) - -#define CIR_I2C_ADDR 0x0059 -#define CIR_I2C_DATA 0x005A -#define CIR_I2C_CTRL 0x005B -#define CIR_I2C_STATUS 0x005C -#define CIR_I2C_QWRITE 0x005D -#define CIR_I2C_QREAD 0x005E - -#define CS8409_CS42L42_HP_VOL_REAL_MIN (-63) -#define CS8409_CS42L42_HP_VOL_REAL_MAX (0) -#define CS8409_CS42L42_AMIC_VOL_REAL_MIN (-97) -#define CS8409_CS42L42_AMIC_VOL_REAL_MAX (12) -#define CS8409_CS42L42_REG_HS_VOLUME_CHA (0x2301) -#define CS8409_CS42L42_REG_HS_VOLUME_CHB (0x2303) -#define CS8409_CS42L42_REG_AMIC_VOLUME (0x1D03) - -struct cs8409_i2c_param { - unsigned int addr; - unsigned int reg; -}; - -struct cs8409_cir_param { - unsigned int nid; - unsigned int cir; - unsigned int coeff; -}; - -enum { - CS8409_BULLSEYE, - CS8409_WARLOCK, - CS8409_CYBORG, - CS8409_FIXUPS, -}; - -static void cs8409_cs42l42_fixups(struct hda_codec *codec, - const struct hda_fixup *fix, int action); -static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, - unsigned int cmd, unsigned int flags, unsigned int *res); - -/* Dell Inspiron models with cs8409/cs42l42 */ -static const struct hda_model_fixup cs8409_models[] = { - { .id = CS8409_BULLSEYE, .name = "bullseye" }, - { .id = CS8409_WARLOCK, .name = "warlock" }, - { .id = CS8409_CYBORG, .name = "cyborg" }, - {} -}; - -/* Dell Inspiron platforms - * with cs8409 bridge and cs42l42 codec - */ -static const struct snd_pci_quirk cs8409_fixup_tbl[] = { - SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE), - SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK), - SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG), - SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG), - {} /* terminator */ -}; - -static const struct hda_verb cs8409_cs42l42_init_verbs[] = { - { 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */ - { 0x47, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ - { 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ - { 0x47, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ - { 0x47, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ - { 0x47, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ - {} /* terminator */ -}; - -static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { - { 0x24, 0x042120f0 }, /* ASP-1-TX */ - { 0x34, 0x04a12050 }, /* ASP-1-RX */ - { 0x2c, 0x901000f0 }, /* ASP-2-TX */ - { 0x44, 0x90a00090 }, /* DMIC-1 */ - {} /* terminator */ -}; - -static const struct hda_fixup cs8409_fixups[] = { - [CS8409_BULLSEYE] = { - .type = HDA_FIXUP_PINS, - .v.pins = cs8409_cs42l42_pincfgs, - .chained = true, - .chain_id = CS8409_FIXUPS, - }, - [CS8409_WARLOCK] = { - .type = HDA_FIXUP_PINS, - .v.pins = cs8409_cs42l42_pincfgs, - .chained = true, - .chain_id = CS8409_FIXUPS, - }, - [CS8409_CYBORG] = { - .type = HDA_FIXUP_PINS, - .v.pins = cs8409_cs42l42_pincfgs, - .chained = true, - .chain_id = CS8409_FIXUPS, - }, - [CS8409_FIXUPS] = { - .type = HDA_FIXUP_FUNC, - .v.func = cs8409_cs42l42_fixups, - }, -}; - -/* Vendor specific HW configuration for CS42L42 */ -static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = { - { 0x1010, 0xB0 }, - { 0x1D01, 0x00 }, - { 0x1D02, 0x06 }, - { 0x1D03, 0x00 }, - { 0x1107, 0x01 }, - { 0x1009, 0x02 }, - { 0x1007, 0x03 }, - { 0x1201, 0x00 }, - { 0x1208, 0x13 }, - { 0x1205, 0xFF }, - { 0x1206, 0x00 }, - { 0x1207, 0x20 }, - { 0x1202, 0x0D }, - { 0x2A02, 0x02 }, - { 0x2A03, 0x00 }, - { 0x2A04, 0x00 }, - { 0x2A05, 0x02 }, - { 0x2A06, 0x00 }, - { 0x2A07, 0x20 }, - { 0x2A08, 0x02 }, - { 0x2A09, 0x00 }, - { 0x2A0A, 0x80 }, - { 0x2A0B, 0x02 }, - { 0x2A0C, 0x00 }, - { 0x2A0D, 0xA0 }, - { 0x2A01, 0x0C }, - { 0x2902, 0x01 }, - { 0x2903, 0x02 }, - { 0x2904, 0x00 }, - { 0x2905, 0x00 }, - { 0x2901, 0x01 }, - { 0x1101, 0x0A }, - { 0x1102, 0x84 }, - { 0x2301, 0x00 }, - { 0x2303, 0x00 }, - { 0x2302, 0x3f }, - { 0x2001, 0x03 }, - { 0x1B75, 0xB6 }, - { 0x1B73, 0xC2 }, - { 0x1129, 0x01 }, - { 0x1121, 0xF3 }, - { 0x1103, 0x20 }, - { 0x1105, 0x00 }, - { 0x1112, 0xC0 }, - { 0x1113, 0x80 }, - { 0x1C03, 0xC0 }, - { 0x1105, 0x00 }, - { 0x1112, 0xC0 }, - { 0x1101, 0x02 }, - {} /* Terminator */ -}; - -/* Vendor specific hw configuration for CS8409 */ -static const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = { - { 0x47, 0x00, 0xb008 }, /* +PLL1/2_EN, +I2C_EN */ - { 0x47, 0x01, 0x0002 }, /* ASP1/2_EN=0, ASP1_STP=1 */ - { 0x47, 0x02, 0x0a80 }, /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ - { 0x47, 0x19, 0x0800 }, /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ - { 0x47, 0x1a, 0x0820 }, /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ - { 0x47, 0x29, 0x0800 }, /* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ - { 0x47, 0x2a, 0x2800 }, /* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */ - { 0x47, 0x39, 0x0800 }, /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ - { 0x47, 0x3a, 0x0800 }, /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ - { 0x47, 0x03, 0x8000 }, /* ASP1: LCHI = 00h */ - { 0x47, 0x04, 0x28ff }, /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ - { 0x47, 0x05, 0x0062 }, /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ - { 0x47, 0x06, 0x801f }, /* ASP2: LCHI=1Fh */ - { 0x47, 0x07, 0x283f }, /* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */ - { 0x47, 0x08, 0x805c }, /* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */ - { 0x47, 0x09, 0x0023 }, /* DMIC1_MO=10b, DMIC1/2_SR=1 */ - { 0x47, 0x0a, 0x0000 }, /* ASP1/2_BEEP=0 */ - { 0x47, 0x01, 0x0062 }, /* ASP1/2_EN=1, ASP1_STP=1 */ - { 0x47, 0x00, 0x9008 }, /* -PLL2_EN */ - { 0x47, 0x68, 0x0000 }, /* TX2.A: pre-scale att.=0 dB */ - { 0x47, 0x82, 0xfc03 }, /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */ - { 0x47, 0xc0, 0x9999 }, /* test mode on */ - { 0x47, 0xc5, 0x0000 }, /* GPIO hysteresis = 30 us */ - { 0x47, 0xc0, 0x0000 }, /* test mode off */ - {} /* Terminator */ -}; - -static const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = { - { 0x47, 0x65, 0x4000 }, /* EQ_SEL=1, EQ1/2_EN=0 */ - { 0x47, 0x64, 0x4000 }, /* +EQ_ACC */ - { 0x47, 0x65, 0x4010 }, /* +EQ2_EN */ - { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ - { 0x47, 0x64, 0xc0c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */ - { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ - { 0x47, 0x64, 0xc1c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */ - { 0x47, 0x63, 0xf370 }, /* EQ_DATA_HI=0xf370 */ - { 0x47, 0x64, 0xc271 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */ - { 0x47, 0x63, 0x1ef8 }, /* EQ_DATA_HI=0x1ef8 */ - { 0x47, 0x64, 0xc348 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */ - { 0x47, 0x63, 0xc110 }, /* EQ_DATA_HI=0xc110 */ - { 0x47, 0x64, 0xc45a }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */ - { 0x47, 0x63, 0x1f29 }, /* EQ_DATA_HI=0x1f29 */ - { 0x47, 0x64, 0xc574 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */ - { 0x47, 0x63, 0x1d7a }, /* EQ_DATA_HI=0x1d7a */ - { 0x47, 0x64, 0xc653 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */ - { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ - { 0x47, 0x64, 0xc714 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */ - { 0x47, 0x63, 0x1ca3 }, /* EQ_DATA_HI=0x1ca3 */ - { 0x47, 0x64, 0xc8c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */ - { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ - { 0x47, 0x64, 0xc914 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */ - { 0x47, 0x64, 0x0000 }, /* -EQ_ACC, -EQ_WRT */ - {} /* Terminator */ -}; - -/** - * cs8409_enable_i2c_clock - Enable I2C clocks - * @codec: the codec instance - * @enable: Enable or disable I2C clocks - * - * Enable or Disable I2C clocks. - */ -static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int enable) -{ - unsigned int retval; - unsigned int newval; - - retval = cs_vendor_coef_get(codec, 0x0); - newval = (enable) ? (retval | 0x8) : (retval & 0xfffffff7); - cs_vendor_coef_set(codec, 0x0, newval); -} - -/** - * cs8409_i2c_wait_complete - Wait for I2C transaction - * @codec: the codec instance - * - * Wait for I2C transaction to complete. - * Return -1 if transaction wait times out. - */ -static int cs8409_i2c_wait_complete(struct hda_codec *codec) -{ - int repeat = 5; - unsigned int retval; - - do { - retval = cs_vendor_coef_get(codec, CIR_I2C_STATUS); - if ((retval & 0x18) != 0x18) { - usleep_range(2000, 4000); - --repeat; - } else - return 0; - - } while (repeat); - - return -1; -} - -/** - * cs8409_i2c_read - CS8409 I2C Read. - * @codec: the codec instance - * @i2c_address: I2C Address - * @i2c_reg: Register to read - * @paged: Is a paged transaction - * - * CS8409 I2C Read. - * Returns negative on error, otherwise returns read value in bits 0-7. - */ -static int cs8409_i2c_read(struct hda_codec *codec, - unsigned int i2c_address, - unsigned int i2c_reg, - unsigned int paged) -{ - unsigned int i2c_reg_data; - unsigned int read_data; - - cs8409_enable_i2c_clock(codec, 1); - cs_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); - - if (paged) { - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); - if (cs8409_i2c_wait_complete(codec) < 0) { - codec_err(codec, - "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", - __func__, i2c_address, i2c_reg); - return -EIO; - } - } - - i2c_reg_data = (i2c_reg << 8) & 0x0ffff; - cs_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data); - if (cs8409_i2c_wait_complete(codec) < 0) { - codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", - __func__, i2c_address, i2c_reg); - return -EIO; - } - - /* Register in bits 15-8 and the data in 7-0 */ - read_data = cs_vendor_coef_get(codec, CIR_I2C_QREAD); - - cs8409_enable_i2c_clock(codec, 0); - - return read_data & 0x0ff; -} - -/** - * cs8409_i2c_write - CS8409 I2C Write. - * @codec: the codec instance - * @i2c_address: I2C Address - * @i2c_reg: Register to write to - * @i2c_data: Data to write - * @paged: Is a paged transaction - * - * CS8409 I2C Write. - * Returns negative on error, otherwise returns 0. - */ -static int cs8409_i2c_write(struct hda_codec *codec, - unsigned int i2c_address, unsigned int i2c_reg, - unsigned int i2c_data, - unsigned int paged) -{ - unsigned int i2c_reg_data; - - cs8409_enable_i2c_clock(codec, 1); - cs_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); - - if (paged) { - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); - if (cs8409_i2c_wait_complete(codec) < 0) { - codec_err(codec, - "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", - __func__, i2c_address, i2c_reg); - return -EIO; - } - } - - i2c_reg_data = ((i2c_reg << 8) & 0x0ff00) | (i2c_data & 0x0ff); - cs_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg_data); - - if (cs8409_i2c_wait_complete(codec) < 0) { - codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", - __func__, i2c_address, i2c_reg); - return -EIO; - } - - cs8409_enable_i2c_clock(codec, 0); - - return 0; -} - -static int cs8409_cs42l42_volume_info(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_info *uinfo) -{ - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - u16 nid = get_amp_nid(kcontrol); - u8 chs = get_amp_channels(kcontrol); - - codec_dbg(codec, "%s() nid: %d\n", __func__, nid); - switch (nid) { - case CS8409_CS42L42_HP_PIN_NID: - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = chs == 3 ? 2 : 1; - uinfo->value.integer.min = CS8409_CS42L42_HP_VOL_REAL_MIN; - uinfo->value.integer.max = CS8409_CS42L42_HP_VOL_REAL_MAX; - break; - case CS8409_CS42L42_AMIC_PIN_NID: - uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; - uinfo->count = chs == 3 ? 2 : 1; - uinfo->value.integer.min = CS8409_CS42L42_AMIC_VOL_REAL_MIN; - uinfo->value.integer.max = CS8409_CS42L42_AMIC_VOL_REAL_MAX; - break; - default: - break; - } - return 0; -} - -static void cs8409_cs42l42_update_volume(struct hda_codec *codec) -{ - struct cs_spec *spec = codec->spec; - int data; - - mutex_lock(&spec->cs8409_i2c_mux); - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHA, 1); - if (data >= 0) - spec->cs42l42_hp_volume[0] = -data; - else - spec->cs42l42_hp_volume[0] = CS8409_CS42L42_HP_VOL_REAL_MIN; - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHB, 1); - if (data >= 0) - spec->cs42l42_hp_volume[1] = -data; - else - spec->cs42l42_hp_volume[1] = CS8409_CS42L42_HP_VOL_REAL_MIN; - data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_AMIC_VOLUME, 1); - if (data >= 0) - spec->cs42l42_hs_mic_volume[0] = -data; - else - spec->cs42l42_hs_mic_volume[0] = CS8409_CS42L42_AMIC_VOL_REAL_MIN; - mutex_unlock(&spec->cs8409_i2c_mux); - spec->cs42l42_volume_init = 1; -} - -static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - struct cs_spec *spec = codec->spec; - hda_nid_t nid = get_amp_nid(kcontrol); - int chs = get_amp_channels(kcontrol); - long *valp = ucontrol->value.integer.value; - - if (!spec->cs42l42_volume_init) { - snd_hda_power_up(codec); - cs8409_cs42l42_update_volume(codec); - snd_hda_power_down(codec); - } - switch (nid) { - case CS8409_CS42L42_HP_PIN_NID: - if (chs & BIT(0)) - *valp++ = spec->cs42l42_hp_volume[0]; - if (chs & BIT(1)) - *valp++ = spec->cs42l42_hp_volume[1]; - break; - case CS8409_CS42L42_AMIC_PIN_NID: - if (chs & BIT(0)) - *valp++ = spec->cs42l42_hs_mic_volume[0]; - break; - default: - break; - } - return 0; -} - -static int cs8409_cs42l42_volume_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct hda_codec *codec = snd_kcontrol_chip(kcontrol); - struct cs_spec *spec = codec->spec; - hda_nid_t nid = get_amp_nid(kcontrol); - int chs = get_amp_channels(kcontrol); - long *valp = ucontrol->value.integer.value; - int change = 0; - char vol; - - snd_hda_power_up(codec); - switch (nid) { - case CS8409_CS42L42_HP_PIN_NID: - mutex_lock(&spec->cs8409_i2c_mux); - if (chs & BIT(0)) { - vol = -(*valp); - change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHA, vol, 1); - valp++; - } - if (chs & BIT(1)) { - vol = -(*valp); - change |= cs8409_i2c_write(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHB, vol, 1); - } - mutex_unlock(&spec->cs8409_i2c_mux); - break; - case CS8409_CS42L42_AMIC_PIN_NID: - mutex_lock(&spec->cs8409_i2c_mux); - if (chs & BIT(0)) { - change = cs8409_i2c_write( - codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_AMIC_VOLUME, (char)*valp, 1); - valp++; - } - mutex_unlock(&spec->cs8409_i2c_mux); - break; - default: - break; - } - cs8409_cs42l42_update_volume(codec); - snd_hda_power_down(codec); - return change; -} - -static const DECLARE_TLV_DB_SCALE( - cs8409_cs42l42_hp_db_scale, - CS8409_CS42L42_HP_VOL_REAL_MIN * 100, 100, 1); - -static const DECLARE_TLV_DB_SCALE( - cs8409_cs42l42_amic_db_scale, - CS8409_CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1); - -static const struct snd_kcontrol_new cs8409_cs42l42_hp_volume_mixer = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .index = 0, - .name = "Headphone Playback Volume", - .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), - .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE - | SNDRV_CTL_ELEM_ACCESS_TLV_READ), - .info = cs8409_cs42l42_volume_info, - .get = cs8409_cs42l42_volume_get, - .put = cs8409_cs42l42_volume_put, - .tlv = { .p = cs8409_cs42l42_hp_db_scale }, - .private_value = HDA_COMPOSE_AMP_VAL( - CS8409_CS42L42_HP_PIN_NID, 3, 0, HDA_OUTPUT) - | HDA_AMP_VAL_MIN_MUTE -}; - -static const struct snd_kcontrol_new cs8409_cs42l42_amic_volume_mixer = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, - .index = 0, - .name = "Mic Capture Volume", - .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), - .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE - | SNDRV_CTL_ELEM_ACCESS_TLV_READ), - .info = cs8409_cs42l42_volume_info, - .get = cs8409_cs42l42_volume_get, - .put = cs8409_cs42l42_volume_put, - .tlv = { .p = cs8409_cs42l42_amic_db_scale }, - .private_value = HDA_COMPOSE_AMP_VAL( - CS8409_CS42L42_AMIC_PIN_NID, 1, 0, HDA_INPUT) - | HDA_AMP_VAL_MIN_MUTE -}; - -/* Assert/release RTS# line to CS42L42 */ -static void cs8409_cs42l42_reset(struct hda_codec *codec) -{ - struct cs_spec *spec = codec->spec; - - /* Assert RTS# line */ - snd_hda_codec_write(codec, - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); - /* wait ~10ms */ - usleep_range(10000, 15000); - /* Release RTS# line */ - snd_hda_codec_write(codec, - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, GPIO5_INT); - /* wait ~10ms */ - usleep_range(10000, 15000); - - mutex_lock(&spec->cs8409_i2c_mux); - - /* Clear interrupts, by reading interrupt status registers */ - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1309, 1); - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130A, 1); - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130F, 1); - - mutex_unlock(&spec->cs8409_i2c_mux); - -} - -/* Configure CS42L42 slave codec for jack autodetect */ -static void cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec) -{ - struct cs_spec *spec = codec->spec; - - mutex_lock(&spec->cs8409_i2c_mux); - - /* Set TIP_SENSE_EN for analog front-end of tip sense. */ - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b70, 0x0020, 1); - /* Clear WAKE# */ - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0001, 1); - /* Wait ~2.5ms */ - usleep_range(2500, 3000); - /* Set mode WAKE# output follows the combination logic directly */ - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0020, 1); - /* Clear interrupts status */ - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); - /* Enable interrupt */ - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1320, 0x03, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b79, 0x00, 1); - - mutex_unlock(&spec->cs8409_i2c_mux); -} - -/* Enable and run CS42L42 slave codec jack auto detect */ -static void cs8409_cs42l42_run_jack_detect(struct hda_codec *codec) -{ - struct cs_spec *spec = codec->spec; - - mutex_lock(&spec->cs8409_i2c_mux); - - /* Clear interrupts */ - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b77, 1); - - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1102, 0x87, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1f06, 0x86, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b74, 0x07, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x131b, 0x01, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0x80, 1); - /* Wait ~110ms*/ - usleep_range(110000, 200000); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x111f, 0x77, 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0xc0, 1); - /* Wait ~10ms */ - usleep_range(10000, 25000); - - mutex_unlock(&spec->cs8409_i2c_mux); - -} - -static void cs8409_cs42l42_reg_setup(struct hda_codec *codec) -{ - const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq; - struct cs_spec *spec = codec->spec; - - mutex_lock(&spec->cs8409_i2c_mux); - - for (; seq->addr; seq++) - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1); - - mutex_unlock(&spec->cs8409_i2c_mux); - -} - -/* - * In the case of CS8409 we do not have unsolicited events from NID's 0x24 - * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will - * generate interrupt via gpio 4 to notify jack events. We have to overwrite - * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers - * and then notify status via generic snd_hda_jack_unsol_event() call. - */ -static void cs8409_jack_unsol_event(struct hda_codec *codec, unsigned int res) -{ - struct cs_spec *spec = codec->spec; - int status_changed = 0; - int reg_cdc_status; - int reg_hs_status; - int reg_ts_status; - int type; - struct hda_jack_tbl *jk; - - /* jack_unsol_event() will be called every time gpio line changing state. - * In this case gpio4 line goes up as a result of reading interrupt status - * registers in previous cs8409_jack_unsol_event() call. - * We don't need to handle this event, ignoring... - */ - if ((res & (1 << 4))) - return; - - mutex_lock(&spec->cs8409_i2c_mux); - - /* Read jack detect status registers */ - reg_cdc_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); - reg_hs_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1124, 1); - reg_ts_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); - - /* Clear interrupts, by reading interrupt status registers */ - cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); - - mutex_unlock(&spec->cs8409_i2c_mux); - - /* If status values are < 0, read error has occurred. */ - if (reg_cdc_status < 0 || reg_hs_status < 0 || reg_ts_status < 0) - return; - - /* HSDET_AUTO_DONE */ - if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE) { - - type = ((reg_hs_status & CS42L42_HSTYPE_MASK) + 1); - /* CS42L42 reports optical jack as type 4 - * We don't handle optical jack - */ - if (type != 4) { - if (!spec->cs42l42_hp_jack_in) { - status_changed = 1; - spec->cs42l42_hp_jack_in = 1; - } - /* type = 3 has no mic */ - if ((!spec->cs42l42_mic_jack_in) && (type != 3)) { - status_changed = 1; - spec->cs42l42_mic_jack_in = 1; - } - } else { - if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { - status_changed = 1; - spec->cs42l42_hp_jack_in = 0; - spec->cs42l42_mic_jack_in = 0; - } - } - - } else { - /* TIP_SENSE INSERT/REMOVE */ - switch (reg_ts_status) { - case CS42L42_JACK_INSERTED: - cs8409_cs42l42_run_jack_detect(codec); - break; - - case CS42L42_JACK_REMOVED: - if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { - status_changed = 1; - spec->cs42l42_hp_jack_in = 0; - spec->cs42l42_mic_jack_in = 0; - } - break; - - default: - /* jack in transition */ - status_changed = 0; - break; - } - } - - if (status_changed) { - - snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID, - spec->cs42l42_hp_jack_in ? 0 : PIN_OUT); - - /* Report jack*/ - jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0); - if (jk) { - snd_hda_jack_unsol_event(codec, - (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG); - } - /* Report jack*/ - jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0); - if (jk) { - snd_hda_jack_unsol_event(codec, - (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & AC_UNSOL_RES_TAG); - } - } -} - -#ifdef CONFIG_PM -/* Manage PDREF, when transition to D3hot */ -static int cs8409_suspend(struct hda_codec *codec) -{ - struct cs_spec *spec = codec->spec; - - mutex_lock(&spec->cs8409_i2c_mux); - /* Power down CS42L42 ASP/EQ/MIX/HP */ - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1); - mutex_unlock(&spec->cs8409_i2c_mux); - /* Assert CS42L42 RTS# line */ - snd_hda_codec_write(codec, - codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); - - snd_hda_shutup_pins(codec); - - return 0; -} -#endif - -/* Enable/Disable Unsolicited Response for gpio(s) 3,4 */ -static void cs8409_enable_ur(struct hda_codec *codec, int flag) -{ - /* GPIO4 INT# and GPIO3 WAKE# */ - snd_hda_codec_write(codec, codec->core.afg, - 0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, - flag ? (GPIO3_INT | GPIO4_INT) : 0); - - snd_hda_codec_write(codec, codec->core.afg, - 0, AC_VERB_SET_UNSOLICITED_ENABLE, - flag ? AC_UNSOL_ENABLED : 0); - -} - -/* Vendor specific HW configuration - * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... - */ -static void cs8409_cs42l42_hw_init(struct hda_codec *codec) -{ - const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg; - const struct cs8409_cir_param *seq_bullseye = cs8409_cs42l42_bullseye_atn; - struct cs_spec *spec = codec->spec; - - if (spec->gpio_mask) { - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, - spec->gpio_mask); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, - spec->gpio_dir); - snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, - spec->gpio_data); - } - - for (; seq->nid; seq++) - cs_vendor_coef_set(codec, seq->cir, seq->coeff); - - if (codec->fixup_id == CS8409_BULLSEYE) - for (; seq_bullseye->nid; seq_bullseye++) - cs_vendor_coef_set(codec, seq_bullseye->cir, seq_bullseye->coeff); - - /* Disable Unsolicited Response during boot */ - cs8409_enable_ur(codec, 0); - - /* Reset CS42L42 */ - cs8409_cs42l42_reset(codec); - - /* Initialise CS42L42 companion codec */ - cs8409_cs42l42_reg_setup(codec); - - if (codec->fixup_id == CS8409_WARLOCK || - codec->fixup_id == CS8409_CYBORG) { - /* FULL_SCALE_VOL = 0 for Warlock / Cyborg */ - mutex_lock(&spec->cs8409_i2c_mux); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1); - mutex_unlock(&spec->cs8409_i2c_mux); - /* DMIC1_MO=00b, DMIC1/2_SR=1 */ - cs_vendor_coef_set(codec, 0x09, 0x0003); - } - - /* Restore Volumes after Resume */ - if (spec->cs42l42_volume_init) { - mutex_lock(&spec->cs8409_i2c_mux); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHA, - -spec->cs42l42_hp_volume[0], - 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_HS_VOLUME_CHB, - -spec->cs42l42_hp_volume[1], - 1); - cs8409_i2c_write(codec, CS42L42_I2C_ADDR, - CS8409_CS42L42_REG_AMIC_VOLUME, - spec->cs42l42_hs_mic_volume[0], - 1); - mutex_unlock(&spec->cs8409_i2c_mux); - } - - cs8409_cs42l42_update_volume(codec); - - cs8409_cs42l42_enable_jack_detect(codec); - - /* Enable Unsolicited Response */ - cs8409_enable_ur(codec, 1); -} - -static int cs8409_cs42l42_init(struct hda_codec *codec) -{ - int ret = snd_hda_gen_init(codec); - - if (!ret) - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); - - return ret; -} - -static const struct hda_codec_ops cs8409_cs42l42_patch_ops = { - .build_controls = cs_build_controls, - .build_pcms = snd_hda_gen_build_pcms, - .init = cs8409_cs42l42_init, - .free = cs_free, - .unsol_event = cs8409_jack_unsol_event, -#ifdef CONFIG_PM - .suspend = cs8409_suspend, -#endif -}; - -static void cs8409_cs42l42_fixups(struct hda_codec *codec, - const struct hda_fixup *fix, int action) -{ - struct cs_spec *spec = codec->spec; - int caps; - - switch (action) { - case HDA_FIXUP_ACT_PRE_PROBE: - snd_hda_add_verbs(codec, cs8409_cs42l42_init_verbs); - /* verb exec op override */ - spec->exec_verb = codec->core.exec_verb; - codec->core.exec_verb = cs8409_cs42l42_exec_verb; - - mutex_init(&spec->cs8409_i2c_mux); - - codec->patch_ops = cs8409_cs42l42_patch_ops; - - spec->gen.suppress_auto_mute = 1; - spec->gen.no_primary_hp = 1; - spec->gen.suppress_vmaster = 1; - - /* GPIO 5 out, 3,4 in */ - spec->gpio_dir = GPIO5_INT; - spec->gpio_data = 0; - spec->gpio_mask = 0x03f; - - spec->cs42l42_hp_jack_in = 0; - spec->cs42l42_mic_jack_in = 0; - - /* Basic initial sequence for specific hw configuration */ - snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs); - - /* CS8409 is simple HDA bridge and intended to be used with a remote - * companion codec. Most of input/output PIN(s) have only basic - * capabilities. NID(s) 0x24 and 0x34 have only OUTC and INC - * capabilities and no presence detect capable (PDC) and call to - * snd_hda_gen_build_controls() will mark them as non detectable - * phantom jacks. However, in this configuration companion codec - * CS42L42 is connected to these pins and it has jack detect - * capabilities. We have to override pin capabilities, - * otherwise they will not be created as input devices. - */ - caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_HP_PIN_NID, - AC_PAR_PIN_CAP); - if (caps >= 0) - snd_hdac_override_parm(&codec->core, - CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, - (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); - - caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID, - AC_PAR_PIN_CAP); - if (caps >= 0) - snd_hdac_override_parm(&codec->core, - CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP, - (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); - - snd_hda_override_wcaps(codec, CS8409_CS42L42_HP_PIN_NID, - (get_wcaps(codec, CS8409_CS42L42_HP_PIN_NID) | AC_WCAP_UNSOL_CAP)); - - snd_hda_override_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID, - (get_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID) | AC_WCAP_UNSOL_CAP)); - break; - case HDA_FIXUP_ACT_PROBE: - - /* Set initial DMIC volume to -26 dB */ - snd_hda_codec_amp_init_stereo(codec, CS8409_CS42L42_DMIC_ADC_PIN_NID, - HDA_INPUT, 0, 0xff, 0x19); - snd_hda_gen_add_kctl(&spec->gen, - NULL, &cs8409_cs42l42_hp_volume_mixer); - snd_hda_gen_add_kctl(&spec->gen, - NULL, &cs8409_cs42l42_amic_volume_mixer); - cs8409_cs42l42_hw_init(codec); - snd_hda_codec_set_name(codec, "CS8409/CS42L42"); - break; - case HDA_FIXUP_ACT_INIT: - cs8409_cs42l42_hw_init(codec); - fallthrough; - case HDA_FIXUP_ACT_BUILD: - /* Run jack auto detect first time on boot - * after controls have been added, to check if jack has - * been already plugged in. - * Run immediately after init. - */ - cs8409_cs42l42_run_jack_detect(codec); - usleep_range(100000, 150000); - break; - default: - break; - } -} - -static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, - unsigned int cmd, unsigned int flags, unsigned int *res) -{ - struct hda_codec *codec = container_of(dev, struct hda_codec, core); - struct cs_spec *spec = codec->spec; - - unsigned int nid = ((cmd >> 20) & 0x07f); - unsigned int verb = ((cmd >> 8) & 0x0fff); - - /* CS8409 pins have no AC_PINSENSE_PRESENCE - * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34 - * and return correct pin sense values for read_pin_sense() call from - * hda_jack based on CS42L42 jack detect status. - */ - switch (nid) { - case CS8409_CS42L42_HP_PIN_NID: - if (verb == AC_VERB_GET_PIN_SENSE) { - *res = (spec->cs42l42_hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; - return 0; - } - break; - - case CS8409_CS42L42_AMIC_PIN_NID: - if (verb == AC_VERB_GET_PIN_SENSE) { - *res = (spec->cs42l42_mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; - return 0; - } - break; - - default: - break; - } - - return spec->exec_verb(dev, cmd, flags, res); -} - -static int patch_cs8409(struct hda_codec *codec) -{ - int err; - - if (!cs_alloc_spec(codec, CS8409_VENDOR_NID)) - return -ENOMEM; - - snd_hda_pick_fixup(codec, - cs8409_models, cs8409_fixup_tbl, cs8409_fixups); - - codec_dbg(codec, "Picked ID=%d, VID=%08x, DEV=%08x\n", - codec->fixup_id, - codec->bus->pci->subsystem_vendor, - codec->bus->pci->subsystem_device); - - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); - - err = cs_parse_auto_config(codec); - if (err < 0) { - cs_free(codec); - return err; - } - - snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); - return 0; -} - /* * patch entries */ @@ -2305,7 +1232,6 @@ static const struct hda_device_id snd_hda_id_cirrus[] = { HDA_CODEC_ENTRY(0x10134208, "CS4208", patch_cs4208), HDA_CODEC_ENTRY(0x10134210, "CS4210", patch_cs4210), HDA_CODEC_ENTRY(0x10134213, "CS4213", patch_cs4213), - HDA_CODEC_ENTRY(0x10138409, "CS8409", patch_cs8409), {} /* terminator */ }; MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cirrus); diff --git a/sound/pci/hda/patch_cs8409.c b/sound/pci/hda/patch_cs8409.c new file mode 100644 index 000000000000..9b16f1b5b828 --- /dev/null +++ b/sound/pci/hda/patch_cs8409.c @@ -0,0 +1,1060 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <sound/core.h> +#include <linux/mutex.h> +#include <linux/pci.h> +#include <sound/tlv.h> +#include <sound/hda_codec.h> +#include "hda_local.h" +#include "hda_auto_parser.h" +#include "hda_jack.h" +#include "hda_generic.h" + +#include "patch_cs8409.h" + +static int cs8409_parse_auto_config(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + int err; + int i; + + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, NULL, 0); + if (err < 0) + return err; + + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg); + if (err < 0) + return err; + + /* keep the ADCs powered up when it's dynamically switchable */ + if (spec->gen.dyn_adc_switch) { + unsigned int done = 0; + + for (i = 0; i < spec->gen.input_mux.num_items; i++) { + int idx = spec->gen.dyn_adc_idx[i]; + + if (done & (1 << idx)) + continue; + snd_hda_gen_fix_pin_power(codec, spec->gen.adc_nids[idx]); + done |= 1 << idx; + } + } + + return 0; +} + +/* Dell Inspiron models with cs8409/cs42l42 */ +static const struct hda_model_fixup cs8409_models[] = { + { .id = CS8409_BULLSEYE, .name = "bullseye" }, + { .id = CS8409_WARLOCK, .name = "warlock" }, + { .id = CS8409_CYBORG, .name = "cyborg" }, + {} +}; + +/* Dell Inspiron platforms + * with cs8409 bridge and cs42l42 codec + */ +static const struct snd_pci_quirk cs8409_fixup_tbl[] = { + SND_PCI_QUIRK(0x1028, 0x0A11, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A12, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A23, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A24, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A25, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A29, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A2A, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0A2B, "Bullseye", CS8409_BULLSEYE), + SND_PCI_QUIRK(0x1028, 0x0AB0, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB2, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB1, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB3, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB4, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AB5, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AD9, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADA, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADB, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0ADC, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AF4, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0AF5, "Warlock", CS8409_WARLOCK), + SND_PCI_QUIRK(0x1028, 0x0A77, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A78, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A79, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7A, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7D, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7E, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A7F, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0A80, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0ADF, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE0, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE1, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE2, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AE9, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEA, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEB, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEC, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AED, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEE, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AEF, "Cyborg", CS8409_CYBORG), + SND_PCI_QUIRK(0x1028, 0x0AF0, "Cyborg", CS8409_CYBORG), + {} /* terminator */ +}; + +static const struct hda_verb cs8409_cs42l42_init_verbs[] = { + { 0x01, AC_VERB_SET_GPIO_WAKE_MASK, 0x0018 }, /* WAKE from GPIO 3,4 */ + { 0x47, AC_VERB_SET_PROC_STATE, 0x0001 }, /* Enable VPW processing */ + { 0x47, AC_VERB_SET_COEF_INDEX, 0x0002 }, /* Configure GPIO 6,7 */ + { 0x47, AC_VERB_SET_PROC_COEF, 0x0080 }, /* I2C mode */ + { 0x47, AC_VERB_SET_COEF_INDEX, 0x005b }, /* Set I2C bus speed */ + { 0x47, AC_VERB_SET_PROC_COEF, 0x0200 }, /* 100kHz I2C_STO = 2 */ + {} /* terminator */ +}; + +static const struct hda_pintbl cs8409_cs42l42_pincfgs[] = { + { 0x24, 0x042120f0 }, /* ASP-1-TX */ + { 0x34, 0x04a12050 }, /* ASP-1-RX */ + { 0x2c, 0x901000f0 }, /* ASP-2-TX */ + { 0x44, 0x90a00090 }, /* DMIC-1 */ + {} /* terminator */ +}; + +static struct cs8409_spec *cs8409_alloc_spec(struct hda_codec *codec) +{ + struct cs8409_spec *spec; + + spec = kzalloc(sizeof(*spec), GFP_KERNEL); + if (!spec) + return NULL; + codec->spec = spec; + codec->power_save_node = 1; + snd_hda_gen_spec_init(&spec->gen); + + return spec; +} + +/* Vendor specific HW configuration for CS42L42 */ +static const struct cs8409_i2c_param cs42l42_init_reg_seq[] = { + { 0x1010, 0xB0 }, + { 0x1D01, 0x00 }, + { 0x1D02, 0x06 }, + { 0x1D03, 0x00 }, + { 0x1107, 0x01 }, + { 0x1009, 0x02 }, + { 0x1007, 0x03 }, + { 0x1201, 0x00 }, + { 0x1208, 0x13 }, + { 0x1205, 0xFF }, + { 0x1206, 0x00 }, + { 0x1207, 0x20 }, + { 0x1202, 0x0D }, + { 0x2A02, 0x02 }, + { 0x2A03, 0x00 }, + { 0x2A04, 0x00 }, + { 0x2A05, 0x02 }, + { 0x2A06, 0x00 }, + { 0x2A07, 0x20 }, + { 0x2A08, 0x02 }, + { 0x2A09, 0x00 }, + { 0x2A0A, 0x80 }, + { 0x2A0B, 0x02 }, + { 0x2A0C, 0x00 }, + { 0x2A0D, 0xA0 }, + { 0x2A01, 0x0C }, + { 0x2902, 0x01 }, + { 0x2903, 0x02 }, + { 0x2904, 0x00 }, + { 0x2905, 0x00 }, + { 0x2901, 0x01 }, + { 0x1101, 0x0A }, + { 0x1102, 0x84 }, + { 0x2301, 0x00 }, + { 0x2303, 0x00 }, + { 0x2302, 0x3f }, + { 0x2001, 0x03 }, + { 0x1B75, 0xB6 }, + { 0x1B73, 0xC2 }, + { 0x1129, 0x01 }, + { 0x1121, 0xF3 }, + { 0x1103, 0x20 }, + { 0x1105, 0x00 }, + { 0x1112, 0xC0 }, + { 0x1113, 0x80 }, + { 0x1C03, 0xC0 }, + { 0x1105, 0x00 }, + { 0x1112, 0xC0 }, + { 0x1101, 0x02 }, + {} /* Terminator */ +}; + +/* Vendor specific hw configuration for CS8409 */ +static const struct cs8409_cir_param cs8409_cs42l42_hw_cfg[] = { + { 0x47, 0x00, 0xb008 }, /* +PLL1/2_EN, +I2C_EN */ + { 0x47, 0x01, 0x0002 }, /* ASP1/2_EN=0, ASP1_STP=1 */ + { 0x47, 0x02, 0x0a80 }, /* ASP1/2_BUS_IDLE=10, +GPIO_I2C */ + { 0x47, 0x19, 0x0800 }, /* ASP1.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ + { 0x47, 0x1a, 0x0820 }, /* ASP1.A: TX.RAP=0, TX.RSZ=24 bits, TX.RCS=32 */ + { 0x47, 0x29, 0x0800 }, /* ASP2.A: TX.LAP=0, TX.LSZ=24 bits, TX.LCS=0 */ + { 0x47, 0x2a, 0x2800 }, /* ASP2.A: TX.RAP=1, TX.RSZ=24 bits, TX.RCS=0 */ + { 0x47, 0x39, 0x0800 }, /* ASP1.A: RX.LAP=0, RX.LSZ=24 bits, RX.LCS=0 */ + { 0x47, 0x3a, 0x0800 }, /* ASP1.A: RX.RAP=0, RX.RSZ=24 bits, RX.RCS=0 */ + { 0x47, 0x03, 0x8000 }, /* ASP1: LCHI = 00h */ + { 0x47, 0x04, 0x28ff }, /* ASP1: MC/SC_SRCSEL=PLL1, LCPR=FFh */ + { 0x47, 0x05, 0x0062 }, /* ASP1: MCEN=0, FSD=011, SCPOL_IN/OUT=0, SCDIV=1:4 */ + { 0x47, 0x06, 0x801f }, /* ASP2: LCHI=1Fh */ + { 0x47, 0x07, 0x283f }, /* ASP2: MC/SC_SRCSEL=PLL1, LCPR=3Fh */ + { 0x47, 0x08, 0x805c }, /* ASP2: 5050=1, MCEN=0, FSD=010, SCPOL_IN/OUT=1, SCDIV=1:16 */ + { 0x47, 0x09, 0x0023 }, /* DMIC1_MO=10b, DMIC1/2_SR=1 */ + { 0x47, 0x0a, 0x0000 }, /* ASP1/2_BEEP=0 */ + { 0x47, 0x01, 0x0062 }, /* ASP1/2_EN=1, ASP1_STP=1 */ + { 0x47, 0x00, 0x9008 }, /* -PLL2_EN */ + { 0x47, 0x68, 0x0000 }, /* TX2.A: pre-scale att.=0 dB */ + { 0x47, 0x82, 0xfc03 }, /* ASP1/2_xxx_EN=1, ASP1/2_MCLK_EN=0, DMIC1_SCL_EN=1 */ + { 0x47, 0xc0, 0x9999 }, /* test mode on */ + { 0x47, 0xc5, 0x0000 }, /* GPIO hysteresis = 30 us */ + { 0x47, 0xc0, 0x0000 }, /* test mode off */ + {} /* Terminator */ +}; + +static const struct cs8409_cir_param cs8409_cs42l42_bullseye_atn[] = { + { 0x47, 0x65, 0x4000 }, /* EQ_SEL=1, EQ1/2_EN=0 */ + { 0x47, 0x64, 0x4000 }, /* +EQ_ACC */ + { 0x47, 0x65, 0x4010 }, /* +EQ2_EN */ + { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ + { 0x47, 0x64, 0xc0c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=0, EQ_DATA_LO=0x67 */ + { 0x47, 0x63, 0x0647 }, /* EQ_DATA_HI=0x0647 */ + { 0x47, 0x64, 0xc1c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=1, EQ_DATA_LO=0x67 */ + { 0x47, 0x63, 0xf370 }, /* EQ_DATA_HI=0xf370 */ + { 0x47, 0x64, 0xc271 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=2, EQ_DATA_LO=0x71 */ + { 0x47, 0x63, 0x1ef8 }, /* EQ_DATA_HI=0x1ef8 */ + { 0x47, 0x64, 0xc348 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=3, EQ_DATA_LO=0x48 */ + { 0x47, 0x63, 0xc110 }, /* EQ_DATA_HI=0xc110 */ + { 0x47, 0x64, 0xc45a }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=4, EQ_DATA_LO=0x5a */ + { 0x47, 0x63, 0x1f29 }, /* EQ_DATA_HI=0x1f29 */ + { 0x47, 0x64, 0xc574 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=5, EQ_DATA_LO=0x74 */ + { 0x47, 0x63, 0x1d7a }, /* EQ_DATA_HI=0x1d7a */ + { 0x47, 0x64, 0xc653 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=6, EQ_DATA_LO=0x53 */ + { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ + { 0x47, 0x64, 0xc714 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=7, EQ_DATA_LO=0x14 */ + { 0x47, 0x63, 0x1ca3 }, /* EQ_DATA_HI=0x1ca3 */ + { 0x47, 0x64, 0xc8c7 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=8, EQ_DATA_LO=0xc7 */ + { 0x47, 0x63, 0xc38c }, /* EQ_DATA_HI=0xc38c */ + { 0x47, 0x64, 0xc914 }, /* +EQ_WRT, +EQ_ACC, EQ_ADR=9, EQ_DATA_LO=0x14 */ + { 0x47, 0x64, 0x0000 }, /* -EQ_ACC, -EQ_WRT */ + {} /* Terminator */ +}; + +static inline int cs8409_vendor_coef_get(struct hda_codec *codec, unsigned int idx) +{ + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_COEF_INDEX, idx); + return snd_hda_codec_read(codec, CS8409_VENDOR_NID, 0, AC_VERB_GET_PROC_COEF, 0); +} + +static inline void cs8409_vendor_coef_set(struct hda_codec *codec, unsigned int idx, + unsigned int coef) +{ + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_COEF_INDEX, idx); + snd_hda_codec_write(codec, CS8409_VENDOR_NID, 0, AC_VERB_SET_PROC_COEF, coef); +} + +/** + * cs8409_enable_i2c_clock - Enable I2C clocks + * @codec: the codec instance + * @enable: Enable or disable I2C clocks + * + * Enable or Disable I2C clocks. + */ +static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int enable) +{ + unsigned int retval; + unsigned int newval; + + retval = cs8409_vendor_coef_get(codec, 0x0); + newval = (enable) ? (retval | 0x8) : (retval & 0xfffffff7); + cs8409_vendor_coef_set(codec, 0x0, newval); +} + +/** + * cs8409_i2c_wait_complete - Wait for I2C transaction + * @codec: the codec instance + * + * Wait for I2C transaction to complete. + * Return -1 if transaction wait times out. + */ +static int cs8409_i2c_wait_complete(struct hda_codec *codec) +{ + int repeat = 5; + unsigned int retval; + + do { + retval = cs8409_vendor_coef_get(codec, CIR_I2C_STATUS); + if ((retval & 0x18) != 0x18) { + usleep_range(2000, 4000); + --repeat; + } else + return 0; + + } while (repeat); + + return -1; +} + +/** + * cs8409_i2c_read - CS8409 I2C Read. + * @codec: the codec instance + * @i2c_address: I2C Address + * @i2c_reg: Register to read + * @paged: Is a paged transaction + * + * CS8409 I2C Read. + * Returns negative on error, otherwise returns read value in bits 0-7. + */ +static int cs8409_i2c_read(struct hda_codec *codec, unsigned int i2c_address, unsigned int i2c_reg, + unsigned int paged) +{ + unsigned int i2c_reg_data; + unsigned int read_data; + + cs8409_enable_i2c_clock(codec, 1); + cs8409_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); + + if (paged) { + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); + if (cs8409_i2c_wait_complete(codec) < 0) { + codec_err(codec, "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", + __func__, i2c_address, i2c_reg); + return -EIO; + } + } + + i2c_reg_data = (i2c_reg << 8) & 0x0ffff; + cs8409_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data); + if (cs8409_i2c_wait_complete(codec) < 0) { + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", + __func__, i2c_address, i2c_reg); + return -EIO; + } + + /* Register in bits 15-8 and the data in 7-0 */ + read_data = cs8409_vendor_coef_get(codec, CIR_I2C_QREAD); + + cs8409_enable_i2c_clock(codec, 0); + + return read_data & 0x0ff; +} + +/** + * cs8409_i2c_write - CS8409 I2C Write. + * @codec: the codec instance + * @i2c_address: I2C Address + * @i2c_reg: Register to write to + * @i2c_data: Data to write + * @paged: Is a paged transaction + * + * CS8409 I2C Write. + * Returns negative on error, otherwise returns 0. + */ +static int cs8409_i2c_write(struct hda_codec *codec, unsigned int i2c_address, unsigned int i2c_reg, + unsigned int i2c_data, unsigned int paged) +{ + unsigned int i2c_reg_data; + + cs8409_enable_i2c_clock(codec, 1); + cs8409_vendor_coef_set(codec, CIR_I2C_ADDR, i2c_address); + + if (paged) { + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg >> 8); + if (cs8409_i2c_wait_complete(codec) < 0) { + codec_err(codec, "%s() Paged Transaction Failed 0x%02x : 0x%04x\n", + __func__, i2c_address, i2c_reg); + return -EIO; + } + } + + i2c_reg_data = ((i2c_reg << 8) & 0x0ff00) | (i2c_data & 0x0ff); + cs8409_vendor_coef_set(codec, CIR_I2C_QWRITE, i2c_reg_data); + + if (cs8409_i2c_wait_complete(codec) < 0) { + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x\n", + __func__, i2c_address, i2c_reg); + return -EIO; + } + + cs8409_enable_i2c_clock(codec, 0); + + return 0; +} + +static int cs8409_cs42l42_volume_info(struct snd_kcontrol *kctrl, struct snd_ctl_elem_info *uinfo) +{ + u16 nid = get_amp_nid(kctrl); + u8 chs = get_amp_channels(kctrl); + + switch (nid) { + case CS8409_CS42L42_HP_PIN_NID: + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = CS8409_CS42L42_HP_VOL_REAL_MIN; + uinfo->value.integer.max = CS8409_CS42L42_HP_VOL_REAL_MAX; + break; + case CS8409_CS42L42_AMIC_PIN_NID: + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = chs == 3 ? 2 : 1; + uinfo->value.integer.min = CS8409_CS42L42_AMIC_VOL_REAL_MIN; + uinfo->value.integer.max = CS8409_CS42L42_AMIC_VOL_REAL_MAX; + break; + default: + break; + } + return 0; +} + +static void cs8409_cs42l42_update_volume(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + int data; + + mutex_lock(&spec->cs8409_i2c_mux); + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHA, 1); + if (data >= 0) + spec->cs42l42_hp_volume[0] = -data; + else + spec->cs42l42_hp_volume[0] = CS8409_CS42L42_HP_VOL_REAL_MIN; + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHB, 1); + if (data >= 0) + spec->cs42l42_hp_volume[1] = -data; + else + spec->cs42l42_hp_volume[1] = CS8409_CS42L42_HP_VOL_REAL_MIN; + data = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_AMIC_VOLUME, 1); + if (data >= 0) + spec->cs42l42_hs_mic_volume[0] = -data; + else + spec->cs42l42_hs_mic_volume[0] = CS8409_CS42L42_AMIC_VOL_REAL_MIN; + mutex_unlock(&spec->cs8409_i2c_mux); + spec->cs42l42_volume_init = 1; +} + +static int cs8409_cs42l42_volume_get(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) +{ + struct hda_codec *codec = snd_kcontrol_chip(kctrl); + struct cs8409_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kctrl); + int chs = get_amp_channels(kctrl); + long *valp = uctrl->value.integer.value; + + if (!spec->cs42l42_volume_init) { + snd_hda_power_up(codec); + cs8409_cs42l42_update_volume(codec); + snd_hda_power_down(codec); + } + switch (nid) { + case CS8409_CS42L42_HP_PIN_NID: + if (chs & BIT(0)) + *valp++ = spec->cs42l42_hp_volume[0]; + if (chs & BIT(1)) + *valp++ = spec->cs42l42_hp_volume[1]; + break; + case CS8409_CS42L42_AMIC_PIN_NID: + if (chs & BIT(0)) + *valp++ = spec->cs42l42_hs_mic_volume[0]; + break; + default: + break; + } + return 0; +} + +static int cs8409_cs42l42_volume_put(struct snd_kcontrol *kctrl, struct snd_ctl_elem_value *uctrl) +{ + struct hda_codec *codec = snd_kcontrol_chip(kctrl); + struct cs8409_spec *spec = codec->spec; + hda_nid_t nid = get_amp_nid(kctrl); + int chs = get_amp_channels(kctrl); + long *valp = uctrl->value.integer.value; + int change = 0; + char vol; + + snd_hda_power_up(codec); + switch (nid) { + case CS8409_CS42L42_HP_PIN_NID: + mutex_lock(&spec->cs8409_i2c_mux); + if (chs & BIT(0)) { + vol = -(*valp); + change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, + CS8409_CS42L42_REG_HS_VOLUME_CHA, vol, 1); + valp++; + } + if (chs & BIT(1)) { + vol = -(*valp); + change |= cs8409_i2c_write(codec, CS42L42_I2C_ADDR, + CS8409_CS42L42_REG_HS_VOLUME_CHB, vol, 1); + } + mutex_unlock(&spec->cs8409_i2c_mux); + break; + case CS8409_CS42L42_AMIC_PIN_NID: + mutex_lock(&spec->cs8409_i2c_mux); + if (chs & BIT(0)) { + change = cs8409_i2c_write(codec, CS42L42_I2C_ADDR, + CS8409_CS42L42_REG_AMIC_VOLUME, (char)*valp, 1); + valp++; + } + mutex_unlock(&spec->cs8409_i2c_mux); + break; + default: + break; + } + cs8409_cs42l42_update_volume(codec); + snd_hda_power_down(codec); + return change; +} + +static const DECLARE_TLV_DB_SCALE(cs8409_cs42l42_hp_db_scale, + CS8409_CS42L42_HP_VOL_REAL_MIN * 100, 100, 1); + +static const DECLARE_TLV_DB_SCALE(cs8409_cs42l42_amic_db_scale, + CS8409_CS42L42_AMIC_VOL_REAL_MIN * 100, 100, 1); + +static const struct snd_kcontrol_new cs8409_cs42l42_hp_volume_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .name = "Headphone Playback Volume", + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = cs8409_cs42l42_volume_info, + .get = cs8409_cs42l42_volume_get, + .put = cs8409_cs42l42_volume_put, + .tlv = { .p = cs8409_cs42l42_hp_db_scale }, + .private_value = HDA_COMPOSE_AMP_VAL(CS8409_CS42L42_HP_PIN_NID, 3, 0, HDA_OUTPUT) | + HDA_AMP_VAL_MIN_MUTE +}; + +static const struct snd_kcontrol_new cs8409_cs42l42_amic_volume_mixer = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .index = 0, + .name = "Mic Capture Volume", + .subdevice = (HDA_SUBDEV_AMP_FLAG | HDA_SUBDEV_NID_FLAG), + .access = (SNDRV_CTL_ELEM_ACCESS_READWRITE | SNDRV_CTL_ELEM_ACCESS_TLV_READ), + .info = cs8409_cs42l42_volume_info, + .get = cs8409_cs42l42_volume_get, + .put = cs8409_cs42l42_volume_put, + .tlv = { .p = cs8409_cs42l42_amic_db_scale }, + .private_value = HDA_COMPOSE_AMP_VAL(CS8409_CS42L42_AMIC_PIN_NID, 1, 0, HDA_INPUT) | + HDA_AMP_VAL_MIN_MUTE +}; + +/* Assert/release RTS# line to CS42L42 */ +static void cs8409_cs42l42_reset(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + /* Assert RTS# line */ + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); + /* wait ~10ms */ + usleep_range(10000, 15000); + /* Release RTS# line */ + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, GPIO5_INT); + /* wait ~10ms */ + usleep_range(10000, 15000); + + mutex_lock(&spec->cs8409_i2c_mux); + + /* Clear interrupts, by reading interrupt status registers */ + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1309, 1); + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130A, 1); + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130F, 1); + + mutex_unlock(&spec->cs8409_i2c_mux); + +} + +/* Configure CS42L42 slave codec for jack autodetect */ +static void cs8409_cs42l42_enable_jack_detect(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + mutex_lock(&spec->cs8409_i2c_mux); + + /* Set TIP_SENSE_EN for analog front-end of tip sense. */ + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b70, 0x0020, 1); + /* Clear WAKE# */ + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0001, 1); + /* Wait ~2.5ms */ + usleep_range(2500, 3000); + /* Set mode WAKE# output follows the combination logic directly */ + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b71, 0x0020, 1); + /* Clear interrupts status */ + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); + /* Enable interrupt */ + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1320, 0x03, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b79, 0x00, 1); + + mutex_unlock(&spec->cs8409_i2c_mux); +} + +/* Enable and run CS42L42 slave codec jack auto detect */ +static void cs8409_cs42l42_run_jack_detect(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + mutex_lock(&spec->cs8409_i2c_mux); + + /* Clear interrupts */ + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b77, 1); + + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1102, 0x87, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1f06, 0x86, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1b74, 0x07, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x131b, 0x01, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0x80, 1); + /* Wait ~110ms*/ + usleep_range(110000, 200000); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x111f, 0x77, 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1120, 0xc0, 1); + /* Wait ~10ms */ + usleep_range(10000, 25000); + + mutex_unlock(&spec->cs8409_i2c_mux); + +} + +static void cs8409_cs42l42_reg_setup(struct hda_codec *codec) +{ + const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq; + struct cs8409_spec *spec = codec->spec; + + mutex_lock(&spec->cs8409_i2c_mux); + + for (; seq->addr; seq++) + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1); + + mutex_unlock(&spec->cs8409_i2c_mux); + +} + +/* + * In the case of CS8409 we do not have unsolicited events from NID's 0x24 + * and 0x34 where hs mic and hp are connected. Companion codec CS42L42 will + * generate interrupt via gpio 4 to notify jack events. We have to overwrite + * generic snd_hda_jack_unsol_event(), read CS42L42 jack detect status registers + * and then notify status via generic snd_hda_jack_unsol_event() call. + */ +static void cs8409_jack_unsol_event(struct hda_codec *codec, unsigned int res) +{ + struct cs8409_spec *spec = codec->spec; + int status_changed = 0; + int reg_cdc_status; + int reg_hs_status; + int reg_ts_status; + int type; + struct hda_jack_tbl *jk; + + /* jack_unsol_event() will be called every time gpio line changing state. + * In this case gpio4 line goes up as a result of reading interrupt status + * registers in previous cs8409_jack_unsol_event() call. + * We don't need to handle this event, ignoring... + */ + if ((res & (1 << 4))) + return; + + mutex_lock(&spec->cs8409_i2c_mux); + + /* Read jack detect status registers */ + reg_cdc_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1308, 1); + reg_hs_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1124, 1); + reg_ts_status = cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x130f, 1); + + /* Clear interrupts, by reading interrupt status registers */ + cs8409_i2c_read(codec, CS42L42_I2C_ADDR, 0x1b7b, 1); + + mutex_unlock(&spec->cs8409_i2c_mux); + + /* If status values are < 0, read error has occurred. */ + if (reg_cdc_status < 0 || reg_hs_status < 0 || reg_ts_status < 0) + return; + + /* HSDET_AUTO_DONE */ + if (reg_cdc_status & CS42L42_HSDET_AUTO_DONE) { + + type = ((reg_hs_status & CS42L42_HSTYPE_MASK) + 1); + /* CS42L42 reports optical jack as type 4 + * We don't handle optical jack + */ + if (type != 4) { + if (!spec->cs42l42_hp_jack_in) { + status_changed = 1; + spec->cs42l42_hp_jack_in = 1; + } + /* type = 3 has no mic */ + if ((!spec->cs42l42_mic_jack_in) && (type != 3)) { + status_changed = 1; + spec->cs42l42_mic_jack_in = 1; + } + } else { + if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { + status_changed = 1; + spec->cs42l42_hp_jack_in = 0; + spec->cs42l42_mic_jack_in = 0; + } + } + + } else { + /* TIP_SENSE INSERT/REMOVE */ + switch (reg_ts_status) { + case CS42L42_JACK_INSERTED: + cs8409_cs42l42_run_jack_detect(codec); + break; + + case CS42L42_JACK_REMOVED: + if (spec->cs42l42_hp_jack_in || spec->cs42l42_mic_jack_in) { + status_changed = 1; + spec->cs42l42_hp_jack_in = 0; + spec->cs42l42_mic_jack_in = 0; + } + break; + + default: + /* jack in transition */ + status_changed = 0; + break; + } + } + + if (status_changed) { + + snd_hda_set_pin_ctl(codec, CS8409_CS42L42_SPK_PIN_NID, + spec->cs42l42_hp_jack_in ? 0 : PIN_OUT); + + /* Report jack*/ + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_HP_PIN_NID, 0); + if (jk) { + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + } + /* Report jack*/ + jk = snd_hda_jack_tbl_get_mst(codec, CS8409_CS42L42_AMIC_PIN_NID, 0); + if (jk) { + snd_hda_jack_unsol_event(codec, (jk->tag << AC_UNSOL_RES_TAG_SHIFT) & + AC_UNSOL_RES_TAG); + } + } +} + +#ifdef CONFIG_PM +/* Manage PDREF, when transition to D3hot */ +static int cs8409_suspend(struct hda_codec *codec) +{ + struct cs8409_spec *spec = codec->spec; + + mutex_lock(&spec->cs8409_i2c_mux); + /* Power down CS42L42 ASP/EQ/MIX/HP */ + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x1101, 0xfe, 1); + mutex_unlock(&spec->cs8409_i2c_mux); + /* Assert CS42L42 RTS# line */ + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_DATA, 0); + + snd_hda_shutup_pins(codec); + + return 0; +} +#endif + +/* Enable/Disable Unsolicited Response for gpio(s) 3,4 */ +static void cs8409_enable_ur(struct hda_codec *codec, int flag) +{ + /* GPIO4 INT# and GPIO3 WAKE# */ + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_GPIO_UNSOLICITED_RSP_MASK, + flag ? (GPIO3_INT | GPIO4_INT) : 0); + + snd_hda_codec_write(codec, codec->core.afg, 0, AC_VERB_SET_UNSOLICITED_ENABLE, + flag ? AC_UNSOL_ENABLED : 0); + +} + +/* Vendor specific HW configuration + * PLL, ASP, I2C, SPI, GPIOs, DMIC etc... + */ +static void cs8409_cs42l42_hw_init(struct hda_codec *codec) +{ + const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg; + const struct cs8409_cir_param *seq_bullseye = cs8409_cs42l42_bullseye_atn; + struct cs8409_spec *spec = codec->spec; + + if (spec->gpio_mask) { + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK, spec->gpio_mask); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DIRECTION, spec->gpio_dir); + snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA, spec->gpio_data); + } + + for (; seq->nid; seq++) + cs8409_vendor_coef_set(codec, seq->cir, seq->coeff); + + if (codec->fixup_id == CS8409_BULLSEYE) + for (; seq_bullseye->nid; seq_bullseye++) + cs8409_vendor_coef_set(codec, seq_bullseye->cir, seq_bullseye->coeff); + + /* Disable Unsolicited Response during boot */ + cs8409_enable_ur(codec, 0); + + /* Reset CS42L42 */ + cs8409_cs42l42_reset(codec); + + /* Initialise CS42L42 companion codec */ + cs8409_cs42l42_reg_setup(codec); + + if (codec->fixup_id == CS8409_WARLOCK || codec->fixup_id == CS8409_CYBORG) { + /* FULL_SCALE_VOL = 0 for Warlock / Cyborg */ + mutex_lock(&spec->cs8409_i2c_mux); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1); + mutex_unlock(&spec->cs8409_i2c_mux); + /* DMIC1_MO=00b, DMIC1/2_SR=1 */ + cs8409_vendor_coef_set(codec, 0x09, 0x0003); + } + + /* Restore Volumes after Resume */ + if (spec->cs42l42_volume_init) { + mutex_lock(&spec->cs8409_i2c_mux); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHA, + -spec->cs42l42_hp_volume[0], 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_HS_VOLUME_CHB, + -spec->cs42l42_hp_volume[1], 1); + cs8409_i2c_write(codec, CS42L42_I2C_ADDR, CS8409_CS42L42_REG_AMIC_VOLUME, + spec->cs42l42_hs_mic_volume[0], 1); + mutex_unlock(&spec->cs8409_i2c_mux); + } + + cs8409_cs42l42_update_volume(codec); + + cs8409_cs42l42_enable_jack_detect(codec); + + /* Enable Unsolicited Response */ + cs8409_enable_ur(codec, 1); +} + +static int cs8409_cs42l42_init(struct hda_codec *codec) +{ + int ret = snd_hda_gen_init(codec); + + if (!ret) + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_INIT); + + return ret; +} + +static int cs8409_build_controls(struct hda_codec *codec) +{ + int err; + + err = snd_hda_gen_build_controls(codec); + if (err < 0) + return err; + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_BUILD); + + return 0; +} + +static const struct hda_codec_ops cs8409_cs42l42_patch_ops = { + .build_controls = cs8409_build_controls, + .build_pcms = snd_hda_gen_build_pcms, + .init = cs8409_cs42l42_init, + .free = snd_hda_gen_free, + .unsol_event = cs8409_jack_unsol_event, +#ifdef CONFIG_PM + .suspend = cs8409_suspend, +#endif +}; + +static int cs8409_cs42l42_exec_verb(struct hdac_device *dev, unsigned int cmd, unsigned int flags, + unsigned int *res) +{ + struct hda_codec *codec = container_of(dev, struct hda_codec, core); + struct cs8409_spec *spec = codec->spec; + + unsigned int nid = ((cmd >> 20) & 0x07f); + unsigned int verb = ((cmd >> 8) & 0x0fff); + + /* CS8409 pins have no AC_PINSENSE_PRESENCE + * capabilities. We have to intercept 2 calls for pins 0x24 and 0x34 + * and return correct pin sense values for read_pin_sense() call from + * hda_jack based on CS42L42 jack detect status. + */ + switch (nid) { + case CS8409_CS42L42_HP_PIN_NID: + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (spec->cs42l42_hp_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + + case CS8409_CS42L42_AMIC_PIN_NID: + if (verb == AC_VERB_GET_PIN_SENSE) { + *res = (spec->cs42l42_mic_jack_in) ? AC_PINSENSE_PRESENCE : 0; + return 0; + } + break; + + default: + break; + } + + return spec->exec_verb(dev, cmd, flags, res); +} + +static void cs8409_cs42l42_fixups(struct hda_codec *codec, const struct hda_fixup *fix, int action) +{ + struct cs8409_spec *spec = codec->spec; + int caps; + + switch (action) { + case HDA_FIXUP_ACT_PRE_PROBE: + snd_hda_add_verbs(codec, cs8409_cs42l42_init_verbs); + /* verb exec op override */ + spec->exec_verb = codec->core.exec_verb; + codec->core.exec_verb = cs8409_cs42l42_exec_verb; + + mutex_init(&spec->cs8409_i2c_mux); + + codec->patch_ops = cs8409_cs42l42_patch_ops; + + spec->gen.suppress_auto_mute = 1; + spec->gen.no_primary_hp = 1; + spec->gen.suppress_vmaster = 1; + + /* GPIO 5 out, 3,4 in */ + spec->gpio_dir = GPIO5_INT; + spec->gpio_data = 0; + spec->gpio_mask = 0x03f; + + spec->cs42l42_hp_jack_in = 0; + spec->cs42l42_mic_jack_in = 0; + + /* Basic initial sequence for specific hw configuration */ + snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs); + + /* CS8409 is simple HDA bridge and intended to be used with a remote + * companion codec. Most of input/output PIN(s) have only basic + * capabilities. NID(s) 0x24 and 0x34 have only OUTC and INC + * capabilities and no presence detect capable (PDC) and call to + * snd_hda_gen_build_controls() will mark them as non detectable + * phantom jacks. However, in this configuration companion codec + * CS42L42 is connected to these pins and it has jack detect + * capabilities. We have to override pin capabilities, + * otherwise they will not be created as input devices. + */ + caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_HP_PIN_NID, + AC_PAR_PIN_CAP); + if (caps >= 0) + snd_hdac_override_parm(&codec->core, + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, + (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); + + caps = snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID, + AC_PAR_PIN_CAP); + if (caps >= 0) + snd_hdac_override_parm(&codec->core, + CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP, + (caps | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT))); + + snd_hda_override_wcaps(codec, CS8409_CS42L42_HP_PIN_NID, + (get_wcaps(codec, CS8409_CS42L42_HP_PIN_NID) | AC_WCAP_UNSOL_CAP)); + + snd_hda_override_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID, + (get_wcaps(codec, CS8409_CS42L42_AMIC_PIN_NID) | AC_WCAP_UNSOL_CAP)); + break; + case HDA_FIXUP_ACT_PROBE: + /* Set initial DMIC volume to -26 dB */ + snd_hda_codec_amp_init_stereo(codec, CS8409_CS42L42_DMIC_ADC_PIN_NID, + HDA_INPUT, 0, 0xff, 0x19); + snd_hda_gen_add_kctl(&spec->gen, NULL, &cs8409_cs42l42_hp_volume_mixer); + snd_hda_gen_add_kctl(&spec->gen, NULL, &cs8409_cs42l42_amic_volume_mixer); + cs8409_cs42l42_hw_init(codec); + snd_hda_codec_set_name(codec, "CS8409/CS42L42"); + break; + case HDA_FIXUP_ACT_INIT: + cs8409_cs42l42_hw_init(codec); + fallthrough; + case HDA_FIXUP_ACT_BUILD: + /* Run jack auto detect first time on boot + * after controls have been added, to check if jack has + * been already plugged in. + * Run immediately after init. + */ + cs8409_cs42l42_run_jack_detect(codec); + usleep_range(100000, 150000); + break; + default: + break; + } +} + +static const struct hda_fixup cs8409_fixups[] = { + [CS8409_BULLSEYE] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_WARLOCK] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_CYBORG] = { + .type = HDA_FIXUP_PINS, + .v.pins = cs8409_cs42l42_pincfgs, + .chained = true, + .chain_id = CS8409_FIXUPS, + }, + [CS8409_FIXUPS] = { + .type = HDA_FIXUP_FUNC, + .v.func = cs8409_cs42l42_fixups, + }, +}; + +static int patch_cs8409(struct hda_codec *codec) +{ + int err; + + if (!cs8409_alloc_spec(codec)) + return -ENOMEM; + + snd_hda_pick_fixup(codec, cs8409_models, cs8409_fixup_tbl, cs8409_fixups); + + codec_dbg(codec, "Picked ID=%d, VID=%08x, DEV=%08x\n", codec->fixup_id, + codec->bus->pci->subsystem_vendor, + codec->bus->pci->subsystem_device); + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE); + + err = cs8409_parse_auto_config(codec); + if (err < 0) { + snd_hda_gen_free(codec); + return err; + } + + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE); + return 0; +} + +static const struct hda_device_id snd_hda_id_cs8409[] = { + HDA_CODEC_ENTRY(0x10138409, "CS8409", patch_cs8409), + {} /* terminator */ +}; +MODULE_DEVICE_TABLE(hdaudio, snd_hda_id_cs8409); + +static struct hda_codec_driver cs8409_driver = { + .id = snd_hda_id_cs8409, +}; +module_hda_codec_driver(cs8409_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Cirrus Logic HDA bridge"); diff --git a/sound/pci/hda/patch_cs8409.h b/sound/pci/hda/patch_cs8409.h new file mode 100644 index 000000000000..2ab02a520f5a --- /dev/null +++ b/sound/pci/hda/patch_cs8409.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * HD audio interface patch for Cirrus Logic CS8409 HDA bridge chip + * + * Copyright (C) 2021 Cirrus Logic, Inc. and + * Cirrus Logic International Semiconductor Ltd. + */ + +#ifndef __CS8409_PATCH_H +#define __CS8409_PATCH_H + +/* Cirrus Logic CS8409 HDA bridge with + * companion codec CS42L42 + */ +#define CS42L42_HP_CH (2U) +#define CS42L42_HS_MIC_CH (1U) + +#define CS8409_VENDOR_NID 0x47 + +#define CS8409_CS42L42_HP_PIN_NID 0x24 +#define CS8409_CS42L42_SPK_PIN_NID 0x2c +#define CS8409_CS42L42_AMIC_PIN_NID 0x34 +#define CS8409_CS42L42_DMIC_PIN_NID 0x44 +#define CS8409_CS42L42_DMIC_ADC_PIN_NID 0x22 + +#define CS42L42_HSDET_AUTO_DONE 0x02 +#define CS42L42_HSTYPE_MASK 0x03 + +#define CS42L42_JACK_INSERTED 0x0C +#define CS42L42_JACK_REMOVED 0x00 + +#define GPIO3_INT (1 << 3) +#define GPIO4_INT (1 << 4) +#define GPIO5_INT (1 << 5) + +#define CS42L42_I2C_ADDR (0x48 << 1) + +#define CIR_I2C_ADDR 0x0059 +#define CIR_I2C_DATA 0x005A +#define CIR_I2C_CTRL 0x005B +#define CIR_I2C_STATUS 0x005C +#define CIR_I2C_QWRITE 0x005D +#define CIR_I2C_QREAD 0x005E + +#define CS8409_CS42L42_HP_VOL_REAL_MIN (-63) +#define CS8409_CS42L42_HP_VOL_REAL_MAX (0) +#define CS8409_CS42L42_AMIC_VOL_REAL_MIN (-97) +#define CS8409_CS42L42_AMIC_VOL_REAL_MAX (12) +#define CS8409_CS42L42_REG_HS_VOLUME_CHA (0x2301) +#define CS8409_CS42L42_REG_HS_VOLUME_CHB (0x2303) +#define CS8409_CS42L42_REG_AMIC_VOLUME (0x1D03) + +enum { + CS8409_BULLSEYE, + CS8409_WARLOCK, + CS8409_CYBORG, + CS8409_FIXUPS, +}; + +struct cs8409_i2c_param { + unsigned int addr; + unsigned int reg; +}; + +struct cs8409_cir_param { + unsigned int nid; + unsigned int cir; + unsigned int coeff; +}; + +struct cs8409_spec { + struct hda_gen_spec gen; + + unsigned int gpio_mask; + unsigned int gpio_dir; + unsigned int gpio_data; + + unsigned int cs42l42_hp_jack_in:1; + unsigned int cs42l42_mic_jack_in:1; + unsigned int cs42l42_volume_init:1; + char cs42l42_hp_volume[CS42L42_HP_CH]; + char cs42l42_hs_mic_volume[CS42L42_HS_MIC_CH]; + + struct mutex cs8409_i2c_mux; + + /* verb exec op override */ + int (*exec_verb)(struct hdac_device *dev, unsigned int cmd, unsigned int flags, + unsigned int *res); +}; + +#endif