@@ -5,6 +5,7 @@ Required properties:
- "allwinner,sun8i-a23-codec-analog"
- "allwinner,sun8i-h3-codec-analog"
- "allwinner,sun8i-v3s-codec-analog"
+ - "allwinner,sun50i-a64-codec-analog"
Required properties if not a sub-node of the PRCM node:
- reg: must contain the registers location and length
@@ -12,7 +12,7 @@ config SND_SUN4I_CODEC
config SND_SUN8I_CODEC
tristate "Allwinner SUN8I audio codec"
depends on OF
- depends on MACH_SUN8I || COMPILE_TEST
+ depends on MACH_SUN8I || (ARM64 && ARCH_SUNXI) || COMPILE_TEST
select REGMAP_MMIO
help
This option enables the digital part of the internal audio codec for
@@ -30,6 +30,7 @@
/* Codec analog control register offsets and bit fields */
#define SUN8I_ADDA_HP_VOLC 0x00
#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7
+#define SUN50I_ADDA_HP_VOLC_HPPAEN 6
#define SUN8I_ADDA_HP_VOLC_HP_VOL 0
#define SUN8I_ADDA_LOMIXSC 0x01
#define SUN8I_ADDA_LOMIXSC_MIC1 6
@@ -48,6 +49,7 @@
#define SUN8I_ADDA_ROMIXSC_DACR 1
#define SUN8I_ADDA_ROMIXSC_DACL 0
#define SUN8I_ADDA_DAC_PA_SRC 0x03
+#define SUN50I_ADDA_DAC_PA_SRC 0x0a
#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7
#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6
#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5
@@ -62,9 +64,16 @@
#define SUN8I_ADDA_LINEIN_GCTRL 0x05
#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4
#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0
+#define SUN50I_ADDA_LINEOUT_CTRL0 0x05
+#define SUN50I_ADDA_LINEOUT_CTRL0_LEN 7
+#define SUN50I_ADDA_LINEOUT_CTRL0_REN 6
+#define SUN50I_ADDA_LINEOUT_CTRL0_LSRC_SEL 5
+#define SUN50I_ADDA_LINEOUT_CTRL0_RSRC_SEL 4
#define SUN8I_ADDA_MICIN_GCTRL 0x06
#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4
#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0
+#define SUN50I_ADDA_LINEOUT_CTRL1 0x06
+#define SUN50I_ADDA_LINEOUT_CTRL1_VOL 0
#define SUN8I_ADDA_PAEN_HP_CTRL 0x07
#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7
#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */
@@ -364,6 +373,61 @@ static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = {
ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)),
};
+static const struct snd_soc_dapm_widget sun50i_codec_common_widgets[] = {
+ /* ADC */
+ SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
+ SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0),
+ SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN,
+ SUN8I_ADDA_ADC_AP_EN_ADCREN, 0),
+
+ /* DAC */
+ SND_SOC_DAPM_DAC("Left DAC", NULL, SUN50I_ADDA_DAC_PA_SRC,
+ SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0),
+ SND_SOC_DAPM_DAC("Right DAC", NULL, SUN50I_ADDA_DAC_PA_SRC,
+ SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0),
+ /*
+ * Due to this component and the codec belonging to separate DAPM
+ * contexts, we need to manually link the above widgets to their
+ * stream widgets at the card level.
+ */
+
+ /* Line In */
+ SND_SOC_DAPM_INPUT("LINEIN"),
+
+ /* Microphone inputs */
+ SND_SOC_DAPM_INPUT("MIC1"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+
+ /* Microphone Bias */
+ SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN,
+ 0, NULL, 0),
+
+ /* Mic input path */
+ SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
+ SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL,
+ SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0),
+
+ /* Mixers */
+ SND_SOC_DAPM_MIXER("Left Mixer", SUN50I_ADDA_DAC_PA_SRC,
+ SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0,
+ sun8i_codec_mixer_controls,
+ ARRAY_SIZE(sun8i_codec_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right Mixer", SUN50I_ADDA_DAC_PA_SRC,
+ SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0,
+ sun8i_codec_mixer_controls,
+ ARRAY_SIZE(sun8i_codec_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
+ SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0,
+ sun8i_codec_adc_mixer_controls,
+ ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
+ SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN,
+ SUN8I_ADDA_ADC_AP_EN_ADCREN, 0,
+ sun8i_codec_adc_mixer_controls,
+ ARRAY_SIZE(sun8i_codec_adc_mixer_controls)),
+};
+
static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = {
/* Microphone Routes */
{ "Mic1 Amplifier", NULL, "MIC1"},
@@ -408,6 +472,17 @@ static const struct snd_kcontrol_new sun8i_a23_codec_hp_ctrls[] = {
SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
};
+static const struct snd_kcontrol_new sun50i_a64_codec_hp_ctrls[] = {
+ SOC_SINGLE_TLV("Headphone Playback Volume",
+ SUN8I_ADDA_HP_VOLC,
+ SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0,
+ sun8i_codec_hp_vol_scale),
+ SOC_DOUBLE("Headphone Playback Switch",
+ SUN50I_ADDA_DAC_PA_SRC,
+ SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE,
+ SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0),
+};
+
static const char * const sun8i_codec_hp_src_enum_text[] = {
"DAC", "Mixer",
};
@@ -461,6 +536,14 @@ static const struct snd_soc_dapm_widget sun8i_a23_codec_hp_widgets[] = {
SND_SOC_DAPM_OUTPUT("HP"),
};
+static const struct snd_soc_dapm_widget sun50i_a64_codec_hp_widgets[] = {
+ SND_SOC_DAPM_MUX("Headphone Source Playback Route",
+ SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src),
+ SND_SOC_DAPM_OUT_DRV("Headphone Amp", SUN8I_ADDA_HP_VOLC,
+ SUN50I_ADDA_HP_VOLC_HPPAEN, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("HP"),
+};
+
static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
{ "Headphone Source Playback Route", "DAC", "Left DAC" },
{ "Headphone Source Playback Route", "DAC", "Right DAC" },
@@ -471,6 +554,15 @@ static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = {
{ "HP", NULL, "Headphone Amp" },
};
+static const struct snd_soc_dapm_route sun50i_codec_headphone_routes[] = {
+ { "Headphone Source Playback Route", "DAC", "Left DAC" },
+ { "Headphone Source Playback Route", "DAC", "Right DAC" },
+ { "Headphone Source Playback Route", "Mixer", "Left Mixer" },
+ { "Headphone Source Playback Route", "Mixer", "Right Mixer" },
+ { "Headphone Amp", NULL, "Headphone Source Playback Route" },
+ { "HP", NULL, "Headphone Amp" },
+};
+
static int sun8i_a23_codec_add_headphone(struct snd_soc_component *cmpnt)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
@@ -525,6 +617,41 @@ static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt)
return ret;
}
+static int sun50i_a64_codec_add_headphone(struct snd_soc_component *cmpnt)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
+ struct device *dev = cmpnt->dev;
+ int ret;
+
+ ret = snd_soc_add_component_controls(cmpnt,
+ sun50i_a64_codec_hp_ctrls,
+ ARRAY_SIZE(
+ sun50i_a64_codec_hp_ctrls));
+ if (ret) {
+ dev_err(dev, "Failed to add Headphone controls: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(dapm,
+ sun50i_a64_codec_hp_widgets,
+ ARRAY_SIZE(
+ sun50i_a64_codec_hp_widgets));
+ if (ret) {
+ dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(dapm, sun50i_codec_headphone_routes,
+ ARRAY_SIZE(
+ sun50i_codec_headphone_routes));
+ if (ret) {
+ dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
/* hmic specific widget */
static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = {
SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL,
@@ -618,6 +745,17 @@ static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = {
SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0),
};
+static const struct snd_kcontrol_new sun50i_codec_lineout_ctrls[] = {
+ SOC_SINGLE_TLV("Line Out Playback Volume",
+ SUN50I_ADDA_LINEOUT_CTRL1,
+ SUN50I_ADDA_LINEOUT_CTRL1_VOL, 0x1f, 0,
+ sun8i_codec_lineout_vol_scale),
+ SOC_DOUBLE("Line Out Playback Switch",
+ SUN50I_ADDA_LINEOUT_CTRL0,
+ SUN50I_ADDA_LINEOUT_CTRL0_REN,
+ SUN50I_ADDA_LINEOUT_CTRL0_REN, 1, 0),
+};
+
static const char * const sun8i_codec_lineout_src_enum_text[] = {
"Stereo", "Mono Differential",
};
@@ -628,11 +766,22 @@ static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum,
SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC,
sun8i_codec_lineout_src_enum_text);
+static SOC_ENUM_DOUBLE_DECL(sun50i_codec_lineout_src_enum,
+ SUN50I_ADDA_LINEOUT_CTRL0,
+ SUN50I_ADDA_LINEOUT_CTRL0_LSRC_SEL,
+ SUN50I_ADDA_LINEOUT_CTRL0_RSRC_SEL,
+ sun8i_codec_lineout_src_enum_text);
+
static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = {
SOC_DAPM_ENUM("Line Out Source Playback Route",
sun8i_codec_lineout_src_enum),
};
+static const struct snd_kcontrol_new sun50i_codec_lineout_src[] = {
+ SOC_DAPM_ENUM("Line Out Source Playback Route",
+ sun50i_codec_lineout_src_enum),
+};
+
static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
SND_SOC_DAPM_MUX("Line Out Source Playback Route",
SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src),
@@ -642,6 +791,17 @@ static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = {
SND_SOC_DAPM_OUTPUT("LINEOUT"),
};
+static const struct snd_soc_dapm_widget sun50i_codec_lineout_widgets[] = {
+ SND_SOC_DAPM_MUX("Line Out Source Playback Route",
+ SND_SOC_NOPM, 0, 0, sun50i_codec_lineout_src),
+ /* It is unclear if this is a buffer or gate, model it as a supply */
+ SND_SOC_DAPM_SUPPLY("Left Line Out Enable", SUN50I_ADDA_LINEOUT_CTRL0,
+ SUN50I_ADDA_LINEOUT_CTRL0_LEN, 0, NULL, 0),
+ SND_SOC_DAPM_SUPPLY("Right Line Out Enable", SUN50I_ADDA_LINEOUT_CTRL0,
+ SUN50I_ADDA_LINEOUT_CTRL0_REN, 0, NULL, 0),
+ SND_SOC_DAPM_OUTPUT("LINEOUT"),
+};
+
static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
{ "Line Out Source Playback Route", "Stereo", "Left Mixer" },
{ "Line Out Source Playback Route", "Stereo", "Right Mixer" },
@@ -651,6 +811,17 @@ static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = {
{ "LINEOUT", NULL, "Line Out Enable", },
};
+static const struct snd_soc_dapm_route sun50i_codec_lineout_routes[] = {
+ { "Line Out Source Playback Route", "Stereo", "Left Mixer" },
+ { "Line Out Source Playback Route", "Stereo", "Right Mixer" },
+ { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" },
+ { "Line Out Source Playback Route", "Mono Differential",
+ "Right Mixer" },
+ { "LINEOUT", NULL, "Line Out Source Playback Route" },
+ { "LINEOUT", NULL, "Left Line Out Enable", },
+ { "LINEOUT", NULL, "Right Line Out Enable", },
+};
+
static int sun8i_h3_codec_add_lineout(struct snd_soc_component *cmpnt)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
@@ -747,6 +918,39 @@ static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt)
return 0;
}
+static int sun50i_a64_codec_add_lineout(struct snd_soc_component *cmpnt)
+{
+ struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt);
+ struct device *dev = cmpnt->dev;
+ int ret;
+
+ ret = snd_soc_add_component_controls(cmpnt,
+ sun50i_codec_lineout_ctrls,
+ ARRAY_SIZE(
+ sun50i_codec_lineout_ctrls));
+ if (ret) {
+ dev_err(dev, "Failed to add Line Out controls: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_new_controls(dapm, sun50i_codec_lineout_widgets,
+ ARRAY_SIZE(
+ sun50i_codec_lineout_widgets));
+ if (ret) {
+ dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dapm_add_routes(dapm, sun50i_codec_lineout_routes,
+ ARRAY_SIZE(sun50i_codec_lineout_routes));
+ if (ret) {
+ dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
struct sun8i_codec_analog_quirks {
bool has_headphone;
bool has_hmic;
@@ -868,6 +1072,16 @@ static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = {
.probe = sun8i_codec_analog_cmpnt_probe,
};
+static const struct snd_soc_component_driver sun50i_codec_analog_cmpnt_drv = {
+ .controls = sun8i_codec_common_controls,
+ .num_controls = ARRAY_SIZE(sun8i_codec_common_controls),
+ .dapm_widgets = sun50i_codec_common_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sun50i_codec_common_widgets),
+ .dapm_routes = sun8i_codec_common_routes,
+ .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes),
+ .probe = sun8i_codec_analog_cmpnt_probe,
+};
+
static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = {
.has_headphone = true,
.has_hmic = true,
@@ -893,6 +1107,15 @@ static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = {
.cmpnt_drv = &sun8i_codec_analog_cmpnt_drv,
};
+static const struct sun8i_codec_analog_quirks sun50i_a64_quirks = {
+ .has_headphone = true,
+ .has_hmic = false,
+ .has_lineout = true,
+ .add_headphone = sun50i_a64_codec_add_headphone,
+ .add_lineout = sun50i_a64_codec_add_lineout,
+ .cmpnt_drv = &sun50i_codec_analog_cmpnt_drv,
+};
+
static const struct of_device_id sun8i_codec_analog_of_match[] = {
{
.compatible = "allwinner,sun8i-a23-codec-analog",
@@ -906,6 +1129,10 @@ static const struct of_device_id sun8i_codec_analog_of_match[] = {
.compatible = "allwinner,sun8i-v3s-codec-analog",
.data = &sun8i_v3s_quirks,
},
+ {
+ .compatible = "allwinner,sun50i-a64-codec-analog",
+ .data = &sun50i_a64_quirks,
+ },
{}
};
MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match);