All of lore.kernel.org
 help / color / mirror / Atom feed
From: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
To: Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>
Cc: <alsa-devel@alsa-project.org>, <patches@opensource.cirrus.com>,
	<linux-kernel@vger.kernel.org>
Subject: [PATCH v3 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec.
Date: Sat, 6 Mar 2021 11:19:32 +0000	[thread overview]
Message-ID: <20210306111934.4832-3-vitalyr@opensource.cirrus.com> (raw)
In-Reply-To: <20210306111934.4832-1-vitalyr@opensource.cirrus.com>

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 <vitalyr@opensource.cirrus.com>
---

Changes in v3:
- Fixed uninitialized variable warning (Reported-by: kernel test robot <lkp@intel.com>)
- 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


WARNING: multiple messages have this Message-ID (diff)
From: Vitaly Rodionov <vitalyr@opensource.cirrus.com>
To: Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>
Cc: patches@opensource.cirrus.com, alsa-devel@alsa-project.org,
	linux-kernel@vger.kernel.org
Subject: [PATCH v3 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec.
Date: Sat, 6 Mar 2021 11:19:32 +0000	[thread overview]
Message-ID: <20210306111934.4832-3-vitalyr@opensource.cirrus.com> (raw)
In-Reply-To: <20210306111934.4832-1-vitalyr@opensource.cirrus.com>

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 <vitalyr@opensource.cirrus.com>
---

Changes in v3:
- Fixed uninitialized variable warning (Reported-by: kernel test robot <lkp@intel.com>)
- 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


  parent reply	other threads:[~2021-03-06 11:35 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-06 11:19 [PATCH v3 0/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Vitaly Rodionov
2021-03-06 11:19 ` Vitaly Rodionov
2021-03-06 11:19 ` [PATCH v3 1/4] ALSA: hda/cirrus: Increase AUTO_CFG_MAX_INS from 8 to 18 Vitaly Rodionov
2021-03-06 11:19   ` Vitaly Rodionov
2021-03-06 11:19 ` Vitaly Rodionov [this message]
2021-03-06 11:19   ` [PATCH v3 2/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Vitaly Rodionov
2021-03-08 15:33   ` Pierre-Louis Bossart
2021-03-09 13:31     ` Vitaly Rodionov
2021-03-09 13:31       ` Vitaly Rodionov
2021-03-06 11:19 ` [PATCH v3 3/4] ALSA: hda/cirrus: Add jack detect interrupt support from " Vitaly Rodionov
2021-03-06 11:19   ` Vitaly Rodionov
2021-03-08 15:35   ` Pierre-Louis Bossart
2021-03-09 13:41     ` Vitaly Rodionov
2021-03-09 13:41       ` Vitaly Rodionov
2021-03-06 11:19 ` [PATCH v3 4/4] ALSA: hda/cirrus: Add Headphone and Headset MIC Volume Control Vitaly Rodionov
2021-03-06 11:19   ` Vitaly Rodionov
2021-03-08 15:40   ` Pierre-Louis Bossart
2021-03-09 14:21     ` Gerrit
2021-03-09 14:21       ` Gerrit
2021-03-07  8:20 ` [PATCH v3 0/4] ALSA: hda/cirrus: Add support for CS8409 HDA bridge and CS42L42 companion codec Takashi Iwai
2021-03-07  8:20   ` Takashi Iwai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210306111934.4832-3-vitalyr@opensource.cirrus.com \
    --to=vitalyr@opensource.cirrus.com \
    --cc=alsa-devel@alsa-project.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=patches@opensource.cirrus.com \
    --cc=perex@perex.cz \
    --cc=tiwai@suse.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.