]> git.proxmox.com Git - mirror_ubuntu-kernels.git/commitdiff
ALSA: hda/cirrus: Move CS8409 HDA bridge to separate module
authorLucas Tanure <tanureal@opensource.cirrus.com>
Wed, 11 Aug 2021 18:56:28 +0000 (19:56 +0100)
committerTakashi Iwai <tiwai@suse.de>
Thu, 12 Aug 2021 11:41:36 +0000 (13:41 +0200)
Signed-off-by: Lucas Tanure <tanureal@opensource.cirrus.com>
Signed-off-by: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
Link: https://lore.kernel.org/r/20210811185654.6837-2-vitalyr@opensource.cirrus.com
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/Kconfig
sound/pci/hda/Makefile
sound/pci/hda/patch_cirrus.c
sound/pci/hda/patch_cs8409.c [new file with mode: 0644]
sound/pci/hda/patch_cs8409.h [new file with mode: 0644]

index c4360cdbc7282e4bb4bf2541bc2c41f23d27dd94..ab9d2746e80460adfb5e6aec40f4eb2cef63eb57 100644 (file)
@@ -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
index b57432f000568eb1b40f5131464218ba1f07bcff..1b73e08dc5633e587a21e299f08e3253d9a8b824 100644 (file)
@@ -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
index 8629e84fef23df7d739cfa30ddb9f01d90593b9f..678fbcaf2a3bc348ddb4fc4eb3fd6fc7e7a1622f 100644 (file)
@@ -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 (file)
index 0000000..9b16f1b
--- /dev/null
@@ -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 (file)
index 0000000..2ab02a5
--- /dev/null
@@ -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