[PATCH v3 1/2] ASoC: Add ak4619 codec support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Khanh Le <khanh.le.xr@xxxxxxxxxxx>

Add support for the Asahi Kasei AK4619 audio codec.

[Kuninori cleanuped and adjusted to upstream code]

Signed-off-by: Khanh Le <khanh.le.xr@xxxxxxxxxxx>
Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@xxxxxxxxxxx>
---
 sound/soc/codecs/Kconfig  |   5 +
 sound/soc/codecs/Makefile |   2 +
 sound/soc/codecs/ak4619.c | 912 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 919 insertions(+)
 create mode 100644 sound/soc/codecs/ak4619.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 7b99556f24d3..910303adef70 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -45,6 +45,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_AK4535
 	imply SND_SOC_AK4554
 	imply SND_SOC_AK4613
+	imply SND_SOC_AK4619
 	imply SND_SOC_AK4641
 	imply SND_SOC_AK4642
 	imply SND_SOC_AK4671
@@ -598,6 +599,10 @@ config SND_SOC_AK4613
 	tristate "AKM AK4613 CODEC"
 	depends on I2C
 
+config SND_SOC_AK4619
+        tristate "AKM AK4619 CODEC"
+        depends on I2C
+
 config SND_SOC_AK4641
 	tristate
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index ca69f35cc0f7..2db7760d2aac 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -39,6 +39,7 @@ snd-soc-ak4458-y := ak4458.o
 snd-soc-ak4535-y := ak4535.o
 snd-soc-ak4554-y := ak4554.o
 snd-soc-ak4613-y := ak4613.o
+snd-soc-ak4619-y := ak4619.o
 snd-soc-ak4641-y := ak4641.o
 snd-soc-ak4642-y := ak4642.o
 snd-soc-ak4671-y := ak4671.o
@@ -439,6 +440,7 @@ obj-$(CONFIG_SND_SOC_AK4458)	+= snd-soc-ak4458.o
 obj-$(CONFIG_SND_SOC_AK4535)	+= snd-soc-ak4535.o
 obj-$(CONFIG_SND_SOC_AK4554)	+= snd-soc-ak4554.o
 obj-$(CONFIG_SND_SOC_AK4613)	+= snd-soc-ak4613.o
+obj-$(CONFIG_SND_SOC_AK4619)	+= snd-soc-ak4619.o
 obj-$(CONFIG_SND_SOC_AK4641)	+= snd-soc-ak4641.o
 obj-$(CONFIG_SND_SOC_AK4642)	+= snd-soc-ak4642.o
 obj-$(CONFIG_SND_SOC_AK4671)	+= snd-soc-ak4671.o
diff --git a/sound/soc/codecs/ak4619.c b/sound/soc/codecs/ak4619.c
new file mode 100644
index 000000000000..cbe27abe1f50
--- /dev/null
+++ b/sound/soc/codecs/ak4619.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ak4619.c -- Asahi Kasei ALSA SoC Audio driver
+ *
+ * Copyright (C) 2023 Renesas Electronics Corporation
+ * Khanh Le <khanh.le.xr@xxxxxxxxxxx>
+ *
+ * Based on ak4613.c by Kuninori Morimoto
+ * Based on da7213.c by Adam Thomson
+ * Based on ak4641.c by Harald Welte
+ */
+
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+/*
+ * Registers
+ */
+
+#define PWR_MGMT	0x00	/* Power Management */
+#define AU_IFF1		0x01	/* Audio I/F Format */
+#define AU_IFF2		0x02	/* Audio I/F Format (Extended) */
+#define SYS_CLK		0x03	/* System Clock Setting */
+#define MIC_AMP1	0x04	/* MIC AMP Gain 1 */
+#define MIC_AMP2	0x05	/* MIC AMP Gain 2 */
+#define LADC1		0x06	/* ADC1 Lch Digital Volume */
+#define RADC1		0x07	/* ADC1 Rch Digital Volume */
+#define LADC2		0x08	/* ADC2 Lch Digital Volume */
+#define RADC2		0x09	/* ADC2 Rch Digital Volume */
+#define ADC_DF		0x0a	/* ADC Digital Filter Setting */
+#define ADC_AI		0x0b	/* ADC Analog Input Setting */
+#define ADC_MHPF	0x0D	/* ADC Mute & HPF Control */
+#define LDAC1		0x0E	/* DAC1 Lch Digital Volume */
+#define RDAC1		0x0F	/* DAC1 Rch Digital Volume */
+#define LDAC2		0x10	/* DAC2 Lch Digital Volume */
+#define RDAC2		0x11	/* DAC2 Rch Digital Volume */
+#define DAC_IS		0x12	/* DAC Input Select Setting */
+#define DAC_DEMP	0x13	/* DAC De-Emphasis Setting */
+#define DAC_MF		0x14	/* DAC Mute & Filter Setting */
+
+/*
+ * Bit fields
+ */
+
+/* Power Management */
+#define PMAD2		BIT(5)
+#define PMAD1		BIT(4)
+#define PMDA2		BIT(2)
+#define PMDA1		BIT(1)
+#define RSTN		BIT(0)
+
+/* Audio_I/F Format */
+#define DCF_STEREO_I2S	(0x0 << 4)
+#define DCF_STEREO_MSB	(0x5 << 4)
+#define DCF_PCM_SF	(0x6 << 4)
+#define DCF_PCM_LF	(0x7 << 4)
+#define DSL_32		(0x3 << 2)
+#define DCF_MASK	(0x7 << 4)
+#define DSL_MASK	(0x3 << 2)
+#define BCKP		BIT(1)
+
+/* Audio_I/F Format (Extended) */
+#define DIDL_24		(0x0 << 2)
+#define DIDL_20		(0x1 << 2)
+#define DIDL_16		(0x2 << 2)
+#define DIDL_32		(0x3 << 2)
+#define DODL_24		(0x0 << 0)
+#define DODL_20		(0x1 << 0)
+#define DODL_16		(0x2 << 0)
+#define DIDL_MASK	(0x3 << 2)
+#define DODL_MASK	(0x3 << 0)
+#define SLOT            BIT(4)
+
+/* System Clock Setting */
+#define FS_MASK		0x7
+
+/* MIC AMP Gain */
+#define MGNL_SHIFT	4
+#define MGNR_SHIFT	0
+#define MGN_MAX		0xB
+
+/* ADC Digital Volume */
+#define VOLAD_SHIFT	0
+#define VOLAD_MAX	0xFF
+
+/* ADC Digital Filter Setting */
+#define AD1SL_SHIFT	0
+#define AD2SL_SHIFT	4
+
+/* Analog Input Select */
+#define AD1LSEL_SHIFT	6
+#define AD1RSEL_SHIFT	4
+#define AD2LSEL_SHIFT	2
+#define AD2RSEL_SHIFT	0
+
+/* ADC Mute & HPF Control */
+#define ATSPAD_SHIFT	7
+#define AD1MUTE_SHIFT	5
+#define AD2MUTE_SHIFT	6
+#define AD1MUTE_MAX	1
+#define AD2MUTE_MAX	1
+#define AD1MUTE_EN	BIT(5)
+#define AD2MUTE_EN	BIT(6)
+#define AD1HPFN_SHIFT	1
+#define AD1HPFN_MAX	1
+#define AD2HPFN_SHIFT	2
+#define AD2HPFN_MAX	1
+
+/* DAC Digital Volume */
+#define VOLDA_SHIFT	0
+#define VOLDA_MAX	0xFF
+
+/* DAC Input Select Setting */
+#define DAC1SEL_SHIFT	0
+#define DAC2SEL_SHIFT	2
+
+/* DAC De-Emphasis Setting */
+#define DEM1_32000	(0x3 << 0)
+#define DEM1_44100	(0x0 << 0)
+#define DEM1_48000	(0x2 << 0)
+#define DEM1_OFF	(0x1 << 0)
+#define DEM2_32000	(0x3 << 2)
+#define DEM2_44100	(0x0 << 2)
+#define DEM2_48000	(0x2 << 2)
+#define DEM2_OFF	(0x1 << 2)
+#define DEM1_MASK	(0x3 << 0)
+#define DEM2_MASK	(0x3 << 2)
+#define DEM1_SHIFT	0
+#define DEM2_SHIFT	2
+
+/* DAC Mute & Filter Setting */
+#define DA1MUTE_SHIFT	4
+#define DA1MUTE_MAX	1
+#define DA2MUTE_SHIFT	5
+#define DA2MUTE_MAX	1
+#define DA1MUTE_EN	BIT(4)
+#define DA2MUTE_EN	BIT(5)
+#define ATSPDA_SHIFT	7
+#define DA1SL_SHIFT	0
+#define DA2SL_SHIFT	2
+
+/* Codec private data */
+struct ak4619_priv {
+	struct regmap *regmap;
+	struct snd_pcm_hw_constraint_list constraint;
+	int deemph_en;
+	unsigned int playback_rate;
+	unsigned int sysclk;
+};
+
+/*
+ * DAC Volume
+ *
+ * max : 0x00 : +12.0 dB
+ *	( 0.5 dB step )
+ * min : 0xFE : -115.0 dB
+ * mute: 0xFF
+ */
+static const DECLARE_TLV_DB_SCALE(dac_tlv, -11550, 50, 1);
+
+/*
+ * MIC Volume
+ *
+ * max : 0x0B : +27.0 dB
+ *	( 3 dB step )
+ * min: 0x00 : -6.0 dB
+ */
+static const DECLARE_TLV_DB_SCALE(mic_tlv, -600, 300, 0);
+
+/*
+ * ADC Volume
+ *
+ * max : 0x00 : +24.0 dB
+ *	( 0.5 dB step )
+ * min : 0xFE : -103.0 dB
+ * mute: 0xFF
+ */
+static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1);
+
+/* ADC & DAC Volume Level Transition Time select */
+static const char * const ak4619_vol_trans_txt[] = {
+	"4/fs", "16/fs"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_adc_vol_trans, ADC_MHPF, ATSPAD_SHIFT, ak4619_vol_trans_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_vol_trans, DAC_MF,   ATSPDA_SHIFT, ak4619_vol_trans_txt);
+
+/* ADC Digital Filter select */
+static const char * const ak4619_adc_digi_fil_txt[] = {
+	"Sharp Roll-Off Filter",
+	"Slow Roll-Off Filter",
+	"Short Delay Sharp Roll-Off Filter",
+	"Short Delay Slow Roll-Off Filter",
+	"Voice Filter"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_adc_1_digi_fil, ADC_DF, AD1SL_SHIFT, ak4619_adc_digi_fil_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_adc_2_digi_fil, ADC_DF, AD2SL_SHIFT, ak4619_adc_digi_fil_txt);
+
+/* DAC De-Emphasis Filter select */
+static const char * const ak4619_dac_de_emp_txt[] = {
+	"44.1kHz", "OFF", "48kHz", "32kHz"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_1_de_emp, DAC_DEMP, DEM1_SHIFT, ak4619_dac_de_emp_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_2_de_emp, DAC_DEMP, DEM2_SHIFT, ak4619_dac_de_emp_txt);
+
+/* DAC Digital Filter select */
+static const char * const ak4619_dac_digi_fil_txt[] = {
+	"Sharp Roll-Off Filter",
+	"Slow Roll-Off Filter",
+	"Short Delay Sharp Roll-Off Filter",
+	"Short Delay Slow Roll-Off Filter"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_1_digi_fil, DAC_MF, DA1SL_SHIFT, ak4619_dac_digi_fil_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_2_digi_fil, DAC_MF, DA2SL_SHIFT, ak4619_dac_digi_fil_txt);
+
+/*
+ * Control functions
+ */
+
+static void ak4619_set_deemph(struct snd_soc_component *component)
+{
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+	u8 dem = 0;
+
+	if (!ak4619->deemph_en)
+		return;
+
+	switch (ak4619->playback_rate) {
+	case 32000:
+		dem |= DEM1_32000 | DEM2_32000;
+		break;
+	case 44100:
+		dem |= DEM1_44100 | DEM2_44100;
+		break;
+	case 48000:
+		dem |= DEM1_48000 | DEM2_48000;
+		break;
+	default:
+		dem |= DEM1_OFF | DEM2_OFF;
+		break;
+	}
+	snd_soc_component_update_bits(component, DAC_DEMP, DEM1_MASK | DEM2_MASK, dem);
+}
+
+static int ak4619_put_deemph(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+	int deemph_en = ucontrol->value.integer.value[0];
+	int ret = 0;
+
+	switch (deemph_en) {
+	case 0:
+	case 1:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (ak4619->deemph_en != deemph_en)
+		ret = 1; /* The value changed */
+
+	ak4619->deemph_en = deemph_en;
+	ak4619_set_deemph(component);
+
+	return ret;
+}
+
+static int ak4619_get_deemph(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+
+	ucontrol->value.integer.value[0] = ak4619->deemph_en;
+
+	return 0;
+};
+
+/*
+ * KControls
+ */
+static const struct snd_kcontrol_new ak4619_snd_controls[] = {
+
+	/* Volume controls */
+	SOC_DOUBLE_R_TLV("DAC 1 Volume", LDAC1, RDAC1, VOLDA_SHIFT, VOLDA_MAX, 1, dac_tlv),
+	SOC_DOUBLE_R_TLV("DAC 2 Volume", LDAC2, RDAC2, VOLDA_SHIFT, VOLDA_MAX, 1, dac_tlv),
+	SOC_DOUBLE_R_TLV("ADC 1 Volume", LADC1, RADC1, VOLAD_SHIFT, VOLAD_MAX, 1, adc_tlv),
+	SOC_DOUBLE_R_TLV("ADC 2 Volume", LADC2, RADC2, VOLAD_SHIFT, VOLAD_MAX, 1, adc_tlv),
+
+	SOC_DOUBLE_TLV("Mic 1 Volume", MIC_AMP1, MGNL_SHIFT, MGNR_SHIFT, MGN_MAX, 0, mic_tlv),
+	SOC_DOUBLE_TLV("Mic 2 Volume", MIC_AMP2, MGNL_SHIFT, MGNR_SHIFT, MGN_MAX, 0, mic_tlv),
+
+	/* Volume Level Transition Time controls */
+	SOC_ENUM("ADC Volume Level Transition Time", ak4619_adc_vol_trans),
+	SOC_ENUM("DAC Volume Level Transition Time", ak4619_dac_vol_trans),
+
+	/* Mute controls */
+	SOC_SINGLE("DAC 1 Switch", DAC_MF, DA1MUTE_SHIFT, DA1MUTE_MAX, 1),
+	SOC_SINGLE("DAC 2 Switch", DAC_MF, DA2MUTE_SHIFT, DA2MUTE_MAX, 1),
+
+	SOC_SINGLE("ADC 1 Switch", ADC_MHPF, AD1MUTE_SHIFT, AD1MUTE_MAX, 1),
+	SOC_SINGLE("ADC 2 Switch", ADC_MHPF, AD2MUTE_SHIFT, AD2MUTE_MAX, 1),
+
+	/* Filter controls */
+	SOC_ENUM("ADC 1 Digital Filter", ak4619_adc_1_digi_fil),
+	SOC_ENUM("ADC 2 Digital Filter", ak4619_adc_2_digi_fil),
+
+	SOC_SINGLE("ADC 1 HPF", ADC_MHPF, AD1HPFN_SHIFT, AD1HPFN_MAX, 1),
+	SOC_SINGLE("ADC 2 HPF", ADC_MHPF, AD2HPFN_SHIFT, AD2HPFN_MAX, 1),
+
+	SOC_ENUM("DAC 1 De-Emphasis Filter", ak4619_dac_1_de_emp),
+	SOC_ENUM("DAC 2 De-Emphasis Filter", ak4619_dac_2_de_emp),
+
+	SOC_ENUM("DAC 1 Digital Filter", ak4619_dac_1_digi_fil),
+	SOC_ENUM("DAC 2 Digital Filter", ak4619_dac_2_digi_fil),
+
+	SOC_SINGLE_BOOL_EXT("Playback De-Emphasis Switch", 0, ak4619_get_deemph, ak4619_put_deemph),
+};
+
+/*
+ * DAPM
+ */
+
+/* Analog input mode */
+static const char * const ak4619_analog_in_txt[] = {
+	"Differential", "Single-Ended1", "Single-Ended2", "Pseudo Differential"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_ad_1_left_in,  ADC_AI, AD1LSEL_SHIFT, ak4619_analog_in_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_ad_1_right_in, ADC_AI, AD1RSEL_SHIFT, ak4619_analog_in_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_ad_2_left_in,  ADC_AI, AD2LSEL_SHIFT, ak4619_analog_in_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_ad_2_right_in, ADC_AI, AD2RSEL_SHIFT, ak4619_analog_in_txt);
+
+static const struct snd_kcontrol_new ak4619_ad_1_left_in_mux =
+	SOC_DAPM_ENUM("Analog Input 1 Left MUX",  ak4619_ad_1_left_in);
+static const struct snd_kcontrol_new ak4619_ad_1_right_in_mux =
+	SOC_DAPM_ENUM("Analog Input 1 Right MUX", ak4619_ad_1_right_in);
+static const struct snd_kcontrol_new ak4619_ad_2_left_in_mux =
+	SOC_DAPM_ENUM("Analog Input 2 Left MUX",  ak4619_ad_2_left_in);
+static const struct snd_kcontrol_new ak4619_ad_2_right_in_mux =
+	SOC_DAPM_ENUM("Analog Input 2 Right MUX", ak4619_ad_2_right_in);
+
+/* DAC source mux */
+static const char * const ak4619_dac_in_txt[] = {
+	"SDIN1", "SDIN2", "SDOUT1", "SDOUT2"
+};
+
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_1_in, DAC_IS, DAC1SEL_SHIFT, ak4619_dac_in_txt);
+static SOC_ENUM_SINGLE_DECL(ak4619_dac_2_in, DAC_IS, DAC2SEL_SHIFT, ak4619_dac_in_txt);
+
+static const struct snd_kcontrol_new ak4619_dac_1_in_mux =
+	SOC_DAPM_ENUM("DAC 1 Source MUX", ak4619_dac_1_in);
+static const struct snd_kcontrol_new ak4619_dac_2_in_mux =
+	SOC_DAPM_ENUM("DAC 2 Source MUX", ak4619_dac_2_in);
+
+static const struct snd_soc_dapm_widget ak4619_dapm_widgets[] = {
+
+	/* DACs */
+	SND_SOC_DAPM_DAC("DAC1", NULL, PWR_MGMT, 1, 0),
+	SND_SOC_DAPM_DAC("DAC2", NULL, PWR_MGMT, 2, 0),
+
+	/* ADCs */
+	SND_SOC_DAPM_ADC("ADC1", NULL, PWR_MGMT, 4, 0),
+	SND_SOC_DAPM_ADC("ADC2", NULL, PWR_MGMT, 5, 0),
+
+	/* Outputs */
+	SND_SOC_DAPM_OUTPUT("AOUT1L"),
+	SND_SOC_DAPM_OUTPUT("AOUT2L"),
+
+	SND_SOC_DAPM_OUTPUT("AOUT1R"),
+	SND_SOC_DAPM_OUTPUT("AOUT2R"),
+
+	/* Inputs */
+	SND_SOC_DAPM_INPUT("AIN1L"),
+	SND_SOC_DAPM_INPUT("AIN2L"),
+	SND_SOC_DAPM_INPUT("AIN4L"),
+	SND_SOC_DAPM_INPUT("AIN5L"),
+
+	SND_SOC_DAPM_INPUT("AIN1R"),
+	SND_SOC_DAPM_INPUT("AIN2R"),
+	SND_SOC_DAPM_INPUT("AIN4R"),
+	SND_SOC_DAPM_INPUT("AIN5R"),
+
+	SND_SOC_DAPM_INPUT("MIC1L"),
+	SND_SOC_DAPM_INPUT("MIC1R"),
+	SND_SOC_DAPM_INPUT("MIC2L"),
+	SND_SOC_DAPM_INPUT("MIC2R"),
+
+	/* DAI */
+	SND_SOC_DAPM_AIF_IN("SDIN1",  "Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("SDIN2",  "Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("SDOUT1", "Capture", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("SDOUT2", "Capture", 0, SND_SOC_NOPM, 0, 0),
+
+	/* MUXs for Mic PGA source selection */
+	SND_SOC_DAPM_MUX("Analog Input 1 Left MUX",  SND_SOC_NOPM, 0, 0, &ak4619_ad_1_left_in_mux),
+	SND_SOC_DAPM_MUX("Analog Input 1 Right MUX", SND_SOC_NOPM, 0, 0, &ak4619_ad_1_right_in_mux),
+	SND_SOC_DAPM_MUX("Analog Input 2 Left MUX",  SND_SOC_NOPM, 0, 0, &ak4619_ad_2_left_in_mux),
+	SND_SOC_DAPM_MUX("Analog Input 2 Right MUX", SND_SOC_NOPM, 0, 0, &ak4619_ad_2_right_in_mux),
+
+	/* MUXs for DAC source selection */
+	SND_SOC_DAPM_MUX("DAC 1 Source MUX", SND_SOC_NOPM, 0, 0, &ak4619_dac_1_in_mux),
+	SND_SOC_DAPM_MUX("DAC 2 Source MUX", SND_SOC_NOPM, 0, 0, &ak4619_dac_2_in_mux),
+};
+
+static const struct snd_soc_dapm_route ak4619_intercon[] = {
+	/* Dest       Connecting Widget    Source */
+
+	/* Output path */
+	{"AOUT1L", NULL, "DAC1"},
+	{"AOUT2L", NULL, "DAC2"},
+
+	{"AOUT1R", NULL, "DAC1"},
+	{"AOUT2R", NULL, "DAC2"},
+
+	{"DAC1", NULL, "DAC 1 Source MUX"},
+	{"DAC2", NULL, "DAC 2 Source MUX"},
+
+	{"DAC 1 Source MUX", "SDIN1",  "SDIN1"},
+	{"DAC 1 Source MUX", "SDIN2",  "SDIN2"},
+	{"DAC 1 Source MUX", "SDOUT1", "SDOUT1"},
+	{"DAC 1 Source MUX", "SDOUT2", "SDOUT2"},
+
+	{"DAC 2 Source MUX", "SDIN1",  "SDIN1"},
+	{"DAC 2 Source MUX", "SDIN2",  "SDIN2"},
+	{"DAC 2 Source MUX", "SDOUT1", "SDOUT1"},
+	{"DAC 2 Source MUX", "SDOUT2", "SDOUT2"},
+
+	/* Input path */
+	{"SDOUT1", NULL, "ADC1"},
+	{"SDOUT2", NULL, "ADC2"},
+
+	{"ADC1", NULL, "Analog Input 1 Left MUX"},
+	{"ADC1", NULL, "Analog Input 1 Right MUX"},
+
+	{"ADC2", NULL, "Analog Input 2 Left MUX"},
+	{"ADC2", NULL, "Analog Input 2 Right MUX"},
+
+	{"Analog Input 1 Left MUX", "Differential",		"MIC1L"},
+	{"Analog Input 1 Left MUX", "Single-Ended1",		"MIC1L"},
+	{"Analog Input 1 Left MUX", "Single-Ended2",		"MIC1L"},
+	{"Analog Input 1 Left MUX", "Pseudo Differential",	"MIC1L"},
+
+	{"Analog Input 1 Right MUX", "Differential",		"MIC1R"},
+	{"Analog Input 1 Right MUX", "Single-Ended1",		"MIC1R"},
+	{"Analog Input 1 Right MUX", "Single-Ended2",		"MIC1R"},
+	{"Analog Input 1 Right MUX", "Pseudo Differential",	"MIC1R"},
+
+	{"Analog Input 2 Left MUX", "Differential",		"MIC2L"},
+	{"Analog Input 2 Left MUX", "Single-Ended1",		"MIC2L"},
+	{"Analog Input 2 Left MUX", "Single-Ended2",		"MIC2L"},
+	{"Analog Input 2 Left MUX", "Pseudo Differential",	"MIC2L"},
+
+	{"Analog Input 2 Right MUX", "Differential",		"MIC2R"},
+	{"Analog Input 2 Right MUX", "Single-Ended1",		"MIC2R"},
+	{"Analog Input 2 Right MUX", "Single-Ended2",		"MIC2R"},
+	{"Analog Input 2 Right MUX", "Pseudo Differential",	"MIC2R"},
+
+	{"MIC1L", NULL, "AIN1L"},
+	{"MIC1L", NULL, "AIN2L"},
+
+	{"MIC1R", NULL, "AIN1R"},
+	{"MIC1R", NULL, "AIN2R"},
+
+	{"MIC2L", NULL, "AIN4L"},
+	{"MIC2L", NULL, "AIN5L"},
+
+	{"MIC2R", NULL, "AIN4R"},
+	{"MIC2R", NULL, "AIN5R"},
+};
+
+static const struct reg_default ak4619_reg_defaults[] = {
+	{ PWR_MGMT,	0x00 },
+	{ AU_IFF1,	0x0C },
+	{ AU_IFF2,	0x0C },
+	{ SYS_CLK,	0x00 },
+	{ MIC_AMP1,	0x22 },
+	{ MIC_AMP2,	0x22 },
+	{ LADC1,	0x30 },
+	{ RADC1,	0x30 },
+	{ LADC2,	0x30 },
+	{ RADC2,	0x30 },
+	{ ADC_DF,	0x00 },
+	{ ADC_AI,	0x00 },
+	{ ADC_MHPF,	0x00 },
+	{ LDAC1,	0x18 },
+	{ RDAC1,	0x18 },
+	{ LDAC2,	0x18 },
+	{ RDAC2,	0x18 },
+	{ DAC_IS,	0x04 },
+	{ DAC_DEMP,	0x05 },
+	{ DAC_MF,	0x0A },
+};
+
+static int ak4619_set_bias_level(struct snd_soc_component *component,
+				 enum snd_soc_bias_level level)
+{
+	u8 pwr_ctrl = 0;
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		pwr_ctrl |= RSTN;
+		fallthrough;
+	case SND_SOC_BIAS_PREPARE:
+		pwr_ctrl |= PMAD1 | PMAD2 | PMDA1 | PMDA2;
+		fallthrough;
+	case SND_SOC_BIAS_STANDBY:
+	case SND_SOC_BIAS_OFF:
+	default:
+		break;
+	}
+
+	snd_soc_component_write(component, PWR_MGMT, pwr_ctrl);
+
+	return 0;
+}
+
+static int ak4619_dai_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+	unsigned int width;
+	unsigned int rate;
+	unsigned int fs;
+	bool is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
+	u8 dai_ctrl = 0;
+	u8 clk_mode = 0;
+
+	width = params_width(params);
+	switch (width) {
+	case 16:
+		dai_ctrl |= is_play ? DIDL_16 : DODL_16;
+		break;
+	case 20:
+		dai_ctrl |= is_play ? DIDL_20 : DODL_20;
+		break;
+	case 24:
+		dai_ctrl |= is_play ? DIDL_24 : DODL_24;
+		break;
+	case 32:
+		if (is_play)
+			dai_ctrl |= DIDL_32;
+		else
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	rate = params_rate(params);
+	if (rate)
+		fs = ak4619->sysclk / rate;
+	else
+		return -EINVAL;
+
+	switch (rate) {
+	case  8000:
+	case 11025:
+	case 12000:
+	case 16000:
+	case 22050:
+	case 24000:
+	case 32000:
+	case 44100:
+	case 48000:
+		switch (fs) {
+		case 256:
+			clk_mode |= (0x0 << 0);
+			break;
+		case 384:
+			clk_mode |= (0x2 << 0);
+			break;
+		case 512:
+			clk_mode |= (0x3 << 0);
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case 64000:
+	case 88200:
+	case 96000:
+		if (fs == 256)
+			clk_mode |= (0x1 << 0);
+		else
+			return -EINVAL;
+		break;
+	case 176400:
+	case 192000:
+		if (fs == 128)
+			clk_mode |= (0x4 << 0);
+		else
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_component_update_bits(component, SYS_CLK, FS_MASK, clk_mode);
+	snd_soc_component_update_bits(component, AU_IFF2,
+				      is_play ? DIDL_MASK : DODL_MASK, dai_ctrl);
+
+	if (is_play) {
+		ak4619->playback_rate = rate;
+		ak4619_set_deemph(component);
+	}
+
+	return 0;
+}
+
+static int ak4619_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct snd_soc_component *component = dai->component;
+	u8 dai_fmt1 = 0;
+	u8 dai_fmt2 = 0;
+
+	/* Set clock normal/inverted */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		dai_fmt1 |= BCKP;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+	case SND_SOC_DAIFMT_IB_IF:
+	default:
+		return -EINVAL;
+	}
+
+	/* Only Stereo modes are supported */
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		dai_fmt1 |= DCF_STEREO_I2S;
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		dai_fmt1 |= DCF_STEREO_MSB;
+		break;
+	case SND_SOC_DAIFMT_DSP_A: /* L data MSB after FRM LRC */
+		dai_fmt1 |= DCF_PCM_SF;
+		dai_fmt2 |= SLOT;
+		break;
+	case SND_SOC_DAIFMT_DSP_B: /* L data MSB during FRM LRC */
+		dai_fmt1 |= DCF_PCM_LF;
+		dai_fmt2 |= SLOT;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Only slave mode is support */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBC_CFC:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* By default only 64 BICK per LRCLK is supported */
+	dai_fmt1 |= DSL_32;
+
+	snd_soc_component_update_bits(component, AU_IFF1, DCF_MASK |
+					DSL_MASK | BCKP, dai_fmt1);
+	snd_soc_component_update_bits(component, AU_IFF2, SLOT, dai_fmt2);
+
+	return 0;
+}
+
+static int ak4619_dai_set_sysclk(struct snd_soc_dai *codec_dai,
+				 int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+
+	ak4619->sysclk = freq;
+
+	return 0;
+}
+
+static int ak4619_dai_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct snd_soc_component *component = dai->component;
+
+	snd_soc_component_update_bits(component, DAC_MF, DA1MUTE_EN, mute ? DA1MUTE_EN : 0);
+	snd_soc_component_update_bits(component, DAC_MF, DA2MUTE_EN, mute ? DA2MUTE_EN : 0);
+
+	return 0;
+}
+
+static void ak4619_hw_constraints(struct ak4619_priv *ak4619,
+				  struct snd_pcm_runtime *runtime)
+{
+	struct snd_pcm_hw_constraint_list *constraint = &ak4619->constraint;
+	int ak4619_rate_mask = 0;
+	unsigned int fs;
+	int i;
+	static const unsigned int ak4619_sr[] = {
+		  8000,
+		 11025,
+		 12000,
+		 16000,
+		 22050,
+		 24000,
+		 32000,
+		 44100,
+		 48000,
+		 64000,
+		 88200,
+		 96000,
+		176400,
+		192000,
+	};
+
+	/*
+	 *	[8kHz - 48kHz]		: 256fs, 384fs or 512fs
+	 *	[64kHz - 96kHz]		: 256fs
+	 *	[176.4kHz, 192kHz]	: 128fs
+	 */
+
+	for (i = 0; i < ARRAY_SIZE(ak4619_sr); i++) {
+		fs = ak4619->sysclk / ak4619_sr[i];
+
+		switch (fs) {
+		case 512:
+		case 384:
+		case 256:
+			ak4619_rate_mask |= (1 << i);
+			break;
+		case 128:
+			switch (i) {
+			case (ARRAY_SIZE(ak4619_sr) - 1):
+			case (ARRAY_SIZE(ak4619_sr) - 2):
+				ak4619_rate_mask |= (1 << i);
+				break;
+			default:
+				break;
+			}
+			break;
+		default:
+			break;
+		}
+	};
+
+	constraint->list	= ak4619_sr;
+	constraint->mask	= ak4619_rate_mask;
+	constraint->count	= ARRAY_SIZE(ak4619_sr);
+
+	snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, constraint);
+};
+
+#define PLAYBACK_MODE	0
+#define CAPTURE_MODE	1
+
+static int ak4619_dai_startup(struct snd_pcm_substream *substream,
+			      struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct ak4619_priv *ak4619 = snd_soc_component_get_drvdata(component);
+
+	ak4619_hw_constraints(ak4619, substream->runtime);
+
+	return 0;
+}
+
+static u64 ak4619_dai_formats[] = {
+	/*
+	 * Select below from Sound Card, not here
+	 *	SND_SOC_DAIFMT_CBC_CFC
+	 *	SND_SOC_DAIFMT_CBP_CFP
+	 */
+
+	/* First Priority */
+	SND_SOC_POSSIBLE_DAIFMT_I2S	|
+	SND_SOC_POSSIBLE_DAIFMT_LEFT_J,
+
+	/* Second Priority */
+	SND_SOC_POSSIBLE_DAIFMT_DSP_A	|
+	SND_SOC_POSSIBLE_DAIFMT_DSP_B,
+};
+
+static const struct snd_soc_dai_ops ak4619_dai_ops = {
+	.startup			= ak4619_dai_startup,
+	.set_sysclk			= ak4619_dai_set_sysclk,
+	.set_fmt			= ak4619_dai_set_fmt,
+	.hw_params			= ak4619_dai_hw_params,
+	.mute_stream			= ak4619_dai_mute,
+	.auto_selectable_formats	= ak4619_dai_formats,
+	.num_auto_selectable_formats	= ARRAY_SIZE(ak4619_dai_formats),
+};
+
+static const struct snd_soc_component_driver soc_component_dev_ak4619 = {
+	.set_bias_level		= ak4619_set_bias_level,
+	.controls		= ak4619_snd_controls,
+	.num_controls		= ARRAY_SIZE(ak4619_snd_controls),
+	.dapm_widgets		= ak4619_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(ak4619_dapm_widgets),
+	.dapm_routes		= ak4619_intercon,
+	.num_dapm_routes	= ARRAY_SIZE(ak4619_intercon),
+	.idle_bias_on		= 1,
+	.endianness		= 1,
+};
+
+static const struct regmap_config ak4619_regmap_cfg = {
+	.reg_bits		= 8,
+	.val_bits		= 8,
+	.max_register		= 0x14,
+	.reg_defaults		= ak4619_reg_defaults,
+	.num_reg_defaults	= ARRAY_SIZE(ak4619_reg_defaults),
+	.cache_type		= REGCACHE_MAPLE,
+};
+
+static const struct of_device_id ak4619_of_match[] = {
+	{ .compatible = "asahi-kasei,ak4619",	.data = &ak4619_regmap_cfg },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ak4619_of_match);
+
+static const struct i2c_device_id ak4619_i2c_id[] = {
+	{ "ak4619", (kernel_ulong_t)&ak4619_regmap_cfg },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ak4619_i2c_id);
+
+#define AK4619_RATES	SNDRV_PCM_RATE_8000_192000
+
+#define AK4619_DAC_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE |\
+				 SNDRV_PCM_FMTBIT_S20_LE |\
+				 SNDRV_PCM_FMTBIT_S24_LE |\
+				 SNDRV_PCM_FMTBIT_S32_LE)
+
+#define AK4619_ADC_FORMATS	(SNDRV_PCM_FMTBIT_S16_LE |\
+				 SNDRV_PCM_FMTBIT_S20_LE |\
+				 SNDRV_PCM_FMTBIT_S24_LE)
+
+static struct snd_soc_dai_driver ak4619_dai = {
+	.name = "ak4619-hifi",
+	.playback = {
+		.stream_name	= "Playback",
+		.channels_min	= 1,
+		.channels_max	= 2,
+		.rates		= AK4619_RATES,
+		.formats	= AK4619_DAC_FORMATS,
+	},
+	.capture = {
+		.stream_name	= "Capture",
+		.channels_min	= 1,
+		.channels_max	= 2,
+		.rates		= AK4619_RATES,
+		.formats	= AK4619_ADC_FORMATS,
+	},
+	.ops			= &ak4619_dai_ops,
+	.symmetric_rate		= 1,
+};
+
+static int ak4619_i2c_probe(struct i2c_client *i2c)
+{
+	struct device *dev = &i2c->dev;
+	struct ak4619_priv *ak4619;
+	int ret;
+
+	ak4619 = devm_kzalloc(dev, sizeof(*ak4619), GFP_KERNEL);
+	if (!ak4619)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, ak4619);
+
+	ak4619->regmap = devm_regmap_init_i2c(i2c, &ak4619_regmap_cfg);
+	if (IS_ERR(ak4619->regmap)) {
+		ret = PTR_ERR(ak4619->regmap);
+		dev_err(dev, "regmap_init() failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = devm_snd_soc_register_component(dev, &soc_component_dev_ak4619,
+				      &ak4619_dai, 1);
+	if (ret < 0) {
+		dev_err(dev, "Failed to register ak4619 component: %d\n",
+			ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver ak4619_i2c_driver = {
+	.driver = {
+		.name = "ak4619-codec",
+		.of_match_table = ak4619_of_match,
+	},
+	.probe		= ak4619_i2c_probe,
+	.id_table	= ak4619_i2c_id,
+};
+module_i2c_driver(ak4619_i2c_driver);
+
+MODULE_DESCRIPTION("SoC AK4619 driver");
+MODULE_AUTHOR("Khanh Le <khanh.le.xr@xxxxxxxxxxx>");
+MODULE_LICENSE("GPL v2");
-- 
2.43.0





[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux