Dell's laptops Inspiron 3500, Inspiron 3501, Inspiron 3505 are using Cirrus Logic
CS8409 HDA bridge with CS42L42 companion codec.
The CS8409 is a multichannel HD audio routing controller.
CS8409 includes support for four channels of digital
microphone data and two bidirectional ASPs for up to 32
channels of TDM data or 4 channels of I2S data. The CS8409 is
intended to be used with a remote companion codec that implements
high performance analog functions in close physical
proximity to the end-equipment audio port or speaker driver.
The CS42L42 is a low-power audio codec with integrated MIPI
SoundWire interface or I2C/I2S/TDM interfaces designed
for portable applications. It provides a high-dynamic range,
stereo DAC for audio playback and a mono high-dynamic-range
ADC for audio capture
CS42L42 is connected to CS8409 HDA bridge via I2C and I2S.
CS8409 CS42L42
------- --------
ASP1.A TX --> ASP_SDIN
ASP1.A RX <-- ASP_SDOUT
GPIO5 --> RST#
GPIO4 <-- INT#
GPIO3 <-- WAKE#
GPIO7 <-> I2C SDA
GPIO6 --> I2C CLK
Tested on DELL Inspiron-3500, DELL Inspiron-3501, DELL Inspiron-3505
This patch will register CS8409 with sound card and create
input/output paths and two input devices, initialise CS42L42
companion codec and configure it for ASP TX/RX TDM mode,
24bit, 48kHz.
cat /proc/asound/pcm
00-00: CS8409 Analog : CS8409 Analog : playback 1 : capture 1
00-03: HDMI 0 : HDMI 0 : playback 1
dmesg
snd_hda_codec_cirrus hdaudioC0D0: autoconfig for CS8409: line_outs=1 (0x2c/0x0/0x0/0x0/0x0) type:speaker
snd_hda_codec_cirrus hdaudioC0D0: speaker_outs=0 (0x0/0x0/0x0/0x0/0x0)
snd_hda_codec_cirrus hdaudioC0D0: hp_outs=1 (0x24/0x0/0x0/0x0/0x0)
snd_hda_codec_cirrus hdaudioC0D0: mono: mono_out=0x0
snd_hda_codec_cirrus hdaudioC0D0: inputs:
snd_hda_codec_cirrus hdaudioC0D0: Internal Mic=0x44
snd_hda_codec_cirrus hdaudioC0D0: Mic=0x34
input: HDA Intel PCH Headphone as /devices/pci0000:00/0000:00:1f.3/sound/card0/input8
input: HDA Intel PCH Headset Mic as /devices/pci0000:00/0000:00:1f.3/sound/card0/input9
Signed-off-by: Vitaly Rodionov <[email protected]>
---
Changes in v3:
- Fixed uninitialized variable warning (Reported-by: kernel test robot <[email protected]>)
- Moved gpio setup into s8409_cs42l42_hw_init()
- Improved susped() implementation
sound/pci/hda/patch_cirrus.c | 576 +++++++++++++++++++++++++++++++++++
1 file changed, 576 insertions(+)
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
index f46204ab0b90..d664eed5c3cf 100644
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -9,6 +9,7 @@
#include <linux/slab.h>
#include <linux/module.h>
#include <sound/core.h>
+#include <linux/pci.h>
#include <sound/tlv.h>
#include <sound/hda_codec.h>
#include "hda_local.h"
@@ -1219,6 +1220,580 @@ 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 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
+
+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_VERBS,
+};
+
+/* 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_verb cs8409_cs42l42_add_verbs[] = {
+ { 0x24, 0x71c, 0xF0 }, /* Widget node ASP-1-TX */
+ { 0x24, 0x71d, 0x20 },
+ { 0x24, 0x71e, 0x21 },
+ { 0x24, 0x71f, 0x04 },
+ { 0x34, 0x71c, 0x50 }, /* Widget node ASP-1-RX0 */
+ { 0x34, 0x71d, 0x20 },
+ { 0x34, 0x71e, 0xa1 },
+ { 0x34, 0x71f, 0x04 },
+ { 0x2C, 0x71c, 0xF0 }, /* Widget node ASP-2-TX */
+ { 0x2C, 0x71d, 0x00 },
+ { 0x2C, 0x71e, 0x10 },
+ { 0x2C, 0x71f, 0x90 },
+ { 0x44, 0x71c, 0x90 }, /* Widget node DMIC-1 */
+ { 0x44, 0x71d, 0x00 },
+ { 0x44, 0x71e, 0xA0 },
+ { 0x44, 0x71f, 0x90 },
+ {} /* terminator */
+};
+
+static const struct hda_fixup cs8409_fixups[] = {
+ [CS8409_BULLSEYE] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cs8409_cs42l42_pincfgs,
+ .chained = true,
+ .chain_id = CS8409_VERBS,
+ },
+ [CS8409_WARLOCK] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cs8409_cs42l42_pincfgs,
+ .chained = true,
+ .chain_id = CS8409_VERBS,
+ },
+ [CS8409_CYBORG] = {
+ .type = HDA_FIXUP_PINS,
+ .v.pins = cs8409_cs42l42_pincfgs,
+ .chained = true,
+ .chain_id = CS8409_VERBS,
+ },
+ [CS8409_VERBS] = {
+ .type = HDA_FIXUP_VERBS,
+ .v.verbs = cs8409_cs42l42_add_verbs,
+ },
+};
+
+/* 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 },
+ {} /* 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 */
+};
+
+/* Enable I2C clocks */
+static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int flag)
+{
+ unsigned int retval = 0;
+ unsigned int newval = 0;
+
+ retval = cs_vendor_coef_get(codec, 0x0);
+ newval = (flag) ? (retval | 0x8) : (retval & 0xfffffff7);
+ cs_vendor_coef_set(codec, 0x0, newval);
+}
+
+/* Wait I2C transaction */
+static int cs8409_i2c_wait_complete(struct hda_codec *codec)
+{
+ int repeat = 5;
+ unsigned int retval = 0;
+
+ do {
+ retval = cs_vendor_coef_get(codec, CIR_I2C_STATUS);
+ if ((retval & 0x18) != 0x18) {
+ usleep_range(2000, 4000);
+ --repeat;
+ } else
+ break;
+
+ } while (repeat);
+
+ return repeat > 0 ? 0 : -1;
+}
+
+/* CS8409 slave i2cRead */
+static unsigned 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 retval = 0;
+
+ 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) == -1) {
+ codec_err(codec,
+ "%s() Paged Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
+ __func__, i2c_address, i2c_reg, retval);
+ }
+ }
+
+ i2c_reg_data = (i2c_reg << 8) & 0x0ffff;
+ cs_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data);
+ if (cs8409_i2c_wait_complete(codec) == -1) {
+ codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
+ __func__, i2c_address, i2c_reg, retval);
+ }
+
+ /* Register in bits 15-8 and the data in 7-0 */
+ retval = cs_vendor_coef_get(codec, CIR_I2C_QREAD);
+ retval &= 0x0ff;
+
+ cs8409_enable_i2c_clock(codec, 0);
+
+ return retval;
+}
+
+/* CS8409 slave i2cWrite */
+static unsigned 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 retval = 0;
+ unsigned int i2c_reg_data = 0;
+
+ 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) == -1) {
+ codec_err(codec,
+ "%s() Paged Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
+ __func__, i2c_address, i2c_reg, retval);
+ }
+ }
+
+ 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) == -1) {
+ codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
+ __func__, i2c_address, i2c_reg, retval);
+ }
+
+ cs8409_enable_i2c_clock(codec, 0);
+
+ return retval;
+}
+
+/* Assert/release RTS# line to CS42L42 */
+static void cs8409_cs42l42_reset(struct hda_codec *codec)
+{
+ /* 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);
+
+ /* Clear interrupts status */
+ 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);
+
+}
+
+static void cs8409_cs42l42_reg_setup(struct hda_codec *codec)
+{
+ const struct cs8409_i2c_param *seq = cs42l42_init_reg_seq;
+
+ for (; seq->addr; seq++)
+ cs8409_i2c_write(codec, CS42L42_I2C_ADDR, seq->addr, seq->reg, 1);
+
+}
+
+static int cs8409_cs42l42_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;
+}
+
+#ifdef CONFIG_PM
+/* Manage PDREF, when transition to D3hot */
+static int cs8409_suspend(struct hda_codec *codec)
+{
+ /* 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
+
+/* Vendor specific HW configuration
+ * PLL, ASP, I2C, SPI, GPIOs, DMIC etc...
+ */
+static int cs8409_cs42l42_hw_init(struct hda_codec *codec)
+{
+ const struct cs8409_cir_param *seq = cs8409_cs42l42_hw_cfg;
+ 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);
+
+ /* 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 */
+ cs8409_i2c_write(codec, CS42L42_I2C_ADDR, 0x2001, 0x01, 1);
+ /* DMIC1_MO=00b, DMIC1/2_SR=1 */
+ cs_vendor_coef_set(codec, 0x09, 0x0003);
+ }
+
+ return 1;
+}
+
+static int cs8409_cs42l42_init(struct hda_codec *codec)
+{
+ int ret = 0;
+
+ ret = snd_hda_gen_init(codec);
+
+ if (!ret) {
+ /* On Dell platforms with suspend D3 mode support we
+ * have to re-initialise cs8409 bridge and companion
+ * cs42l42 codec
+ */
+ snd_hda_sequence_write(codec, cs8409_cs42l42_init_verbs);
+ snd_hda_sequence_write(codec, cs8409_cs42l42_add_verbs);
+
+ cs8409_cs42l42_hw_init(codec);
+
+ }
+
+ return ret;
+}
+
+static const struct hda_codec_ops cs8409_cs42l42_patch_ops = {
+ .build_controls = cs8409_cs42l42_build_controls,
+ .build_pcms = snd_hda_gen_build_pcms,
+ .init = cs8409_cs42l42_init,
+ .free = cs_free,
+ .unsol_event = snd_hda_jack_unsol_event,
+#ifdef CONFIG_PM
+ .suspend = cs8409_suspend,
+#endif
+};
+
+static int cs8409_cs42l42_fixup(struct hda_codec *codec)
+{
+ int err = 0;
+ struct cs_spec *spec = codec->spec;
+ unsigned int pincap = 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.
+ */
+ _snd_hdac_read_parm(&codec->core,
+ CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, &pincap);
+
+ snd_hdac_override_parm(&codec->core,
+ CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP,
+ (pincap | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT)));
+
+ _snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID,
+ AC_PAR_PIN_CAP, &pincap);
+
+ snd_hdac_override_parm(&codec->core,
+ CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP,
+ (pincap | (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));
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
+
+ err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, 0, 0);
+ if (err < 0)
+ return err;
+
+ err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
+ if (err < 0)
+ return err;
+
+ snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
+
+ return err;
+}
+
+static int patch_cs8409(struct hda_codec *codec)
+{
+ struct cs_spec *spec;
+ int err = -EINVAL;
+
+ spec = cs_alloc_spec(codec, CS8409_VENDOR_NID);
+ if (!spec)
+ 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);
+
+ switch (codec->fixup_id) {
+ /* Dell platforms with CS42L42 companion codec */
+ case CS8409_BULLSEYE:
+ case CS8409_WARLOCK:
+ case CS8409_CYBORG:
+
+ snd_hda_add_verbs(codec, cs8409_cs42l42_add_verbs);
+
+ codec->patch_ops = cs8409_cs42l42_patch_ops;
+
+ spec->gen.suppress_auto_mute = 1;
+ spec->gen.no_primary_hp = 1;
+ /* GPIO 5 out, 3,4 in */
+ spec->gpio_dir = GPIO5_INT;
+ spec->gpio_data = 0;
+ spec->gpio_mask = 0x03f;
+
+ err = cs8409_cs42l42_fixup(codec);
+
+ if (err > 0)
+ err = cs8409_cs42l42_hw_init(codec);
+ break;
+
+ default:
+ codec_err(codec, "VID=%08x, DEV=%08x not supported\n",
+ codec->bus->pci->subsystem_vendor,
+ codec->bus->pci->subsystem_device);
+ break;
+ }
+ if (err < 0)
+ cs_free(codec);
+ else
+ snd_hda_codec_set_name(codec, "CS8409/CS42L42");
+
+ return err;
+}
/*
* patch entries
@@ -1229,6 +1804,7 @@ 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);
--
2.25.1
> +/* Enable I2C clocks */
> +static void cs8409_enable_i2c_clock(struct hda_codec *codec, unsigned int flag)
> +{
> + unsigned int retval = 0;
> + unsigned int newval = 0;
initializations not needed
> + retval = cs_vendor_coef_get(codec, 0x0);
> + newval = (flag) ? (retval | 0x8) : (retval & 0xfffffff7);
> + cs_vendor_coef_set(codec, 0x0, newval);
> +}
> +
> +/* Wait I2C transaction */
> +static int cs8409_i2c_wait_complete(struct hda_codec *codec)
> +{
> + int repeat = 5;
> + unsigned int retval = 0;
initialization not needed.
> +
> + do {
> + retval = cs_vendor_coef_get(codec, CIR_I2C_STATUS);
> + if ((retval & 0x18) != 0x18) {
> + usleep_range(2000, 4000);
> + --repeat;
> + } else
> + break;
> +
> + } while (repeat);
> +
> + return repeat > 0 ? 0 : -1;
can we simplify by returning !!repeat ?
> +}
> +
> +/* CS8409 slave i2cRead */
> +static unsigned 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 retval = 0;
> +
> + 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) == -1) {
> + codec_err(codec,
> + "%s() Paged Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
> + __func__, i2c_address, i2c_reg, retval);
return an error?
> + }
> + }
> +
> + i2c_reg_data = (i2c_reg << 8) & 0x0ffff;
> + cs_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data);
> + if (cs8409_i2c_wait_complete(codec) == -1) {
> + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
> + __func__, i2c_address, i2c_reg, retval);
return and error?
> + }
> +
> + /* Register in bits 15-8 and the data in 7-0 */
> + retval = cs_vendor_coef_get(codec, CIR_I2C_QREAD);
> + retval &= 0x0ff;
> +
> + cs8409_enable_i2c_clock(codec, 0);
> +
> + return retval;
> +}
> +
> +/* CS8409 slave i2cWrite */
> +static unsigned 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 retval = 0;
> + unsigned int i2c_reg_data = 0;
> +
> + 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) == -1) {
> + codec_err(codec,
> + "%s() Paged Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
> + __func__, i2c_address, i2c_reg, retval);
return error?
> + }
> + }
> +
> + 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) == -1) {
> + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x = 0x%02x\n",
> + __func__, i2c_address, i2c_reg, retval);
return error?
> + }
> +
> + cs8409_enable_i2c_clock(codec, 0);
> +
> + return retval;
> +}
> +
> +/* Assert/release RTS# line to CS42L42 */
> +static void cs8409_cs42l42_reset(struct hda_codec *codec)
> +{
> + /* 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);
> +
> + /* Clear interrupts status */
> + 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);
clear on read?
> +static int cs8409_cs42l42_fixup(struct hda_codec *codec)
> +{
> + int err = 0;
useless init
> + struct cs_spec *spec = codec->spec;
> + unsigned int pincap = 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.
> + */
> + _snd_hdac_read_parm(&codec->core,
> + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, &pincap);
> +
> + snd_hdac_override_parm(&codec->core,
> + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP,
> + (pincap | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT)));
> +
> + _snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID,
> + AC_PAR_PIN_CAP, &pincap);
> +
> + snd_hdac_override_parm(&codec->core,
> + CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP,
> + (pincap | (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));
> +
> + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
> +
> + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, 0, 0);
> + if (err < 0)
> + return err;
> +
> + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
> + if (err < 0)
> + return err;
> +
> + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
> +
> + return err;
> +}
> +
> +static int patch_cs8409(struct hda_codec *codec)
> +{
> + struct cs_spec *spec;
> + int err = -EINVAL;
> +
> + spec = cs_alloc_spec(codec, CS8409_VENDOR_NID);
> + if (!spec)
> + 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);
> +
> + switch (codec->fixup_id) {
> + /* Dell platforms with CS42L42 companion codec */
> + case CS8409_BULLSEYE:
> + case CS8409_WARLOCK:
> + case CS8409_CYBORG:
> +
> + snd_hda_add_verbs(codec, cs8409_cs42l42_add_verbs);
> +
> + codec->patch_ops = cs8409_cs42l42_patch_ops;
> +
> + spec->gen.suppress_auto_mute = 1;
> + spec->gen.no_primary_hp = 1;
> + /* GPIO 5 out, 3,4 in */
> + spec->gpio_dir = GPIO5_INT;
> + spec->gpio_data = 0;
> + spec->gpio_mask = 0x03f;
> +
> + err = cs8409_cs42l42_fixup(codec);
> +
> + if (err > 0)
better keep the convention that errors are negative and zero is success.
> + err = cs8409_cs42l42_hw_init(codec);
> + break;
> +
> + default:
> + codec_err(codec, "VID=%08x, DEV=%08x not supported\n",
> + codec->bus->pci->subsystem_vendor,
> + codec->bus->pci->subsystem_device);
> + break;
> + }
> + if (err < 0)
> + cs_free(codec);
> + else
> + snd_hda_codec_set_name(codec, "CS8409/CS42L42");
> +
> + return err;
> +}
>
> /*
> * patch entries
> @@ -1229,6 +1804,7 @@ 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);
>
On 08/03/2021 3:33 pm, Pierre-Louis Bossart wrote:
Hi Pierre-Louis
Thanks a lot for your comments. Since this patch set has been merged we
will address
your comments in a next fix patch.
>
>> +/* Enable I2C clocks */
>> +static void cs8409_enable_i2c_clock(struct hda_codec *codec,
>> unsigned int flag)
>> +{
>> + unsigned int retval = 0;
>> + unsigned int newval = 0;
>
> initializations not needed
Will fix that.
>
>> + retval = cs_vendor_coef_get(codec, 0x0);
>> + newval = (flag) ? (retval | 0x8) : (retval & 0xfffffff7);
>> + cs_vendor_coef_set(codec, 0x0, newval);
>> +}
>> +
>> +/* Wait I2C transaction */
>> +static int cs8409_i2c_wait_complete(struct hda_codec *codec)
>> +{
>> + int repeat = 5;
>> + unsigned int retval = 0;
>
> initialization not needed.
Will fix that.
>
>> +
>> + do {
>> + retval = cs_vendor_coef_get(codec, CIR_I2C_STATUS);
>> + if ((retval & 0x18) != 0x18) {
>> + usleep_range(2000, 4000);
>> + --repeat;
>> + } else
>> + break;
>> +
>> + } while (repeat);
>> +
>> + return repeat > 0 ? 0 : -1;
>
> can we simplify by returning !!repeat ?
Yes, we can simplify this code as well.
>
>> +}
>> +
>> +/* CS8409 slave i2cRead */
>> +static unsigned 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 retval = 0;
>> +
>> + 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) == -1) {
>> + codec_err(codec,
>> + "%s() Paged Transaction Failed 0x%02x : 0x%04x =
>> 0x%02x\n",
>> + __func__, i2c_address, i2c_reg, retval);
>
> return an error?
Yes, you are right there is no reason to continue if we hit an error
here. Will be fixed.
>
>> + }
>> + }
>> +
>> + i2c_reg_data = (i2c_reg << 8) & 0x0ffff;
>> + cs_vendor_coef_set(codec, CIR_I2C_QREAD, i2c_reg_data);
>> + if (cs8409_i2c_wait_complete(codec) == -1) {
>> + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x =
>> 0x%02x\n",
>> + __func__, i2c_address, i2c_reg, retval);
>
> return and error?
Same as above
>
>> + }
>> +
>> + /* Register in bits 15-8 and the data in 7-0 */
>> + retval = cs_vendor_coef_get(codec, CIR_I2C_QREAD);
>> + retval &= 0x0ff;
>> +
>> + cs8409_enable_i2c_clock(codec, 0);
>> +
>> + return retval;
>> +}
>> +
>> +/* CS8409 slave i2cWrite */
>> +static unsigned 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 retval = 0;
>> + unsigned int i2c_reg_data = 0;
>> +
>> + 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) == -1) {
>> + codec_err(codec,
>> + "%s() Paged Transaction Failed 0x%02x : 0x%04x =
>> 0x%02x\n",
>> + __func__, i2c_address, i2c_reg, retval);
>
> return error?
Same as above
>
>> + }
>> + }
>> +
>> + 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) == -1) {
>> + codec_err(codec, "%s() Transaction Failed 0x%02x : 0x%04x =
>> 0x%02x\n",
>> + __func__, i2c_address, i2c_reg, retval);
>
> return error?
Same as above
>
>> + }
>> +
>> + cs8409_enable_i2c_clock(codec, 0);
>> +
>> + return retval;
>> +}
>> +
>> +/* Assert/release RTS# line to CS42L42 */
>> +static void cs8409_cs42l42_reset(struct hda_codec *codec)
>> +{
>> + /* 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);
>> +
>> + /* Clear interrupts status */
>> + 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);
>
> clear on read?
This is how CS42L42 works, to clear interrupts we have to read
interrupts status registers.
>
>
>> +static int cs8409_cs42l42_fixup(struct hda_codec *codec)
>> +{
>> + int err = 0;
>
> useless init
Will fix.
>
>> + struct cs_spec *spec = codec->spec;
>> + unsigned int pincap = 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.
>> + */
>> + _snd_hdac_read_parm(&codec->core,
>> + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP, &pincap);
>> +
>> + snd_hdac_override_parm(&codec->core,
>> + CS8409_CS42L42_HP_PIN_NID, AC_PAR_PIN_CAP,
>> + (pincap | (AC_PINCAP_IMP_SENSE | AC_PINCAP_PRES_DETECT)));
>> +
>> + _snd_hdac_read_parm(&codec->core, CS8409_CS42L42_AMIC_PIN_NID,
>> + AC_PAR_PIN_CAP, &pincap);
>> +
>> + snd_hdac_override_parm(&codec->core,
>> + CS8409_CS42L42_AMIC_PIN_NID, AC_PAR_PIN_CAP,
>> + (pincap | (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));
>> +
>> + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PRE_PROBE);
>> +
>> + err = snd_hda_parse_pin_defcfg(codec, &spec->gen.autocfg, 0, 0);
>> + if (err < 0)
>> + return err;
>> +
>> + err = snd_hda_gen_parse_auto_config(codec, &spec->gen.autocfg);
>> + if (err < 0)
>> + return err;
>> +
>> + snd_hda_apply_fixup(codec, HDA_FIXUP_ACT_PROBE);
>> +
>> + return err;
>> +}
>> +
>> +static int patch_cs8409(struct hda_codec *codec)
>> +{
>> + struct cs_spec *spec;
>> + int err = -EINVAL;
>> +
>> + spec = cs_alloc_spec(codec, CS8409_VENDOR_NID);
>> + if (!spec)
>> + 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);
>> +
>> + switch (codec->fixup_id) {
>> + /* Dell platforms with CS42L42 companion codec */
>> + case CS8409_BULLSEYE:
>> + case CS8409_WARLOCK:
>> + case CS8409_CYBORG:
>> +
>> + snd_hda_add_verbs(codec, cs8409_cs42l42_add_verbs);
>> +
>> + codec->patch_ops = cs8409_cs42l42_patch_ops;
>> +
>> + spec->gen.suppress_auto_mute = 1;
>> + spec->gen.no_primary_hp = 1;
>> + /* GPIO 5 out, 3,4 in */
>> + spec->gpio_dir = GPIO5_INT;
>> + spec->gpio_data = 0;
>> + spec->gpio_mask = 0x03f;
>> +
>> + err = cs8409_cs42l42_fixup(codec);
>> +
>> + if (err > 0)
>
> better keep the convention that errors are negative and zero is success.
Agreed. Will fix.
>
>> + err = cs8409_cs42l42_hw_init(codec);
>> + break;
>> +
>> + default:
>> + codec_err(codec, "VID=%08x, DEV=%08x not supported\n",
>> + codec->bus->pci->subsystem_vendor,
>> + codec->bus->pci->subsystem_device);
>> + break;
>> + }
>> + if (err < 0)
>> + cs_free(codec);
>> + else
>> + snd_hda_codec_set_name(codec, "CS8409/CS42L42");
>> +
>> + return err;
>> +}
>> /*
>> * patch entries
>> @@ -1229,6 +1804,7 @@ 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);
>>