1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * This driver supports the analog controls for the internal codec 4 * found in Allwinner's A31s, A23, A33 and H3 SoCs. 5 * 6 * Copyright 2016 Chen-Yu Tsai <wens@csie.org> 7 */ 8 9 #include <linux/io.h> 10 #include <linux/kernel.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/platform_device.h> 14 #include <linux/regmap.h> 15 16 #include <sound/soc.h> 17 #include <sound/soc-dapm.h> 18 #include <sound/tlv.h> 19 20 #include "sun8i-adda-pr-regmap.h" 21 22 /* Codec analog control register offsets and bit fields */ 23 #define SUN8I_ADDA_HP_VOLC 0x00 24 #define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 7 25 #define SUN8I_ADDA_HP_VOLC_HP_VOL 0 26 #define SUN8I_ADDA_LOMIXSC 0x01 27 #define SUN8I_ADDA_LOMIXSC_MIC1 6 28 #define SUN8I_ADDA_LOMIXSC_MIC2 5 29 #define SUN8I_ADDA_LOMIXSC_PHONE 4 30 #define SUN8I_ADDA_LOMIXSC_PHONEN 3 31 #define SUN8I_ADDA_LOMIXSC_LINEINL 2 32 #define SUN8I_ADDA_LOMIXSC_DACL 1 33 #define SUN8I_ADDA_LOMIXSC_DACR 0 34 #define SUN8I_ADDA_ROMIXSC 0x02 35 #define SUN8I_ADDA_ROMIXSC_MIC1 6 36 #define SUN8I_ADDA_ROMIXSC_MIC2 5 37 #define SUN8I_ADDA_ROMIXSC_PHONE 4 38 #define SUN8I_ADDA_ROMIXSC_PHONEP 3 39 #define SUN8I_ADDA_ROMIXSC_LINEINR 2 40 #define SUN8I_ADDA_ROMIXSC_DACR 1 41 #define SUN8I_ADDA_ROMIXSC_DACL 0 42 #define SUN8I_ADDA_DAC_PA_SRC 0x03 43 #define SUN8I_ADDA_DAC_PA_SRC_DACAREN 7 44 #define SUN8I_ADDA_DAC_PA_SRC_DACALEN 6 45 #define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 5 46 #define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 4 47 #define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 3 48 #define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 2 49 #define SUN8I_ADDA_DAC_PA_SRC_RHPIS 1 50 #define SUN8I_ADDA_DAC_PA_SRC_LHPIS 0 51 #define SUN8I_ADDA_PHONEIN_GCTRL 0x04 52 #define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 4 53 #define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 0 54 #define SUN8I_ADDA_LINEIN_GCTRL 0x05 55 #define SUN8I_ADDA_LINEIN_GCTRL_LINEING 4 56 #define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 0 57 #define SUN8I_ADDA_MICIN_GCTRL 0x06 58 #define SUN8I_ADDA_MICIN_GCTRL_MIC1G 4 59 #define SUN8I_ADDA_MICIN_GCTRL_MIC2G 0 60 #define SUN8I_ADDA_PAEN_HP_CTRL 0x07 61 #define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 7 62 #define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */ 63 #define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 5 64 #define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 4 65 #define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 2 66 #define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 1 67 #define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 0 68 #define SUN8I_ADDA_PHONEOUT_CTRL 0x08 69 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 5 70 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 4 71 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 3 72 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 2 73 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 1 74 #define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 0 75 #define SUN8I_ADDA_PHONE_GAIN_CTRL 0x09 76 #define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 3 77 #define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 0 78 #define SUN8I_ADDA_MIC2G_CTRL 0x0a 79 #define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 7 80 #define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 4 81 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 3 82 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 2 83 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 1 84 #define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 0 85 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b 86 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 7 87 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 6 88 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 5 89 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 3 90 #define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 0 91 #define SUN8I_ADDA_LADCMIXSC 0x0c 92 #define SUN8I_ADDA_LADCMIXSC_MIC1 6 93 #define SUN8I_ADDA_LADCMIXSC_MIC2 5 94 #define SUN8I_ADDA_LADCMIXSC_PHONE 4 95 #define SUN8I_ADDA_LADCMIXSC_PHONEN 3 96 #define SUN8I_ADDA_LADCMIXSC_LINEINL 2 97 #define SUN8I_ADDA_LADCMIXSC_OMIXRL 1 98 #define SUN8I_ADDA_LADCMIXSC_OMIXRR 0 99 #define SUN8I_ADDA_RADCMIXSC 0x0d 100 #define SUN8I_ADDA_RADCMIXSC_MIC1 6 101 #define SUN8I_ADDA_RADCMIXSC_MIC2 5 102 #define SUN8I_ADDA_RADCMIXSC_PHONE 4 103 #define SUN8I_ADDA_RADCMIXSC_PHONEP 3 104 #define SUN8I_ADDA_RADCMIXSC_LINEINR 2 105 #define SUN8I_ADDA_RADCMIXSC_OMIXR 1 106 #define SUN8I_ADDA_RADCMIXSC_OMIXL 0 107 #define SUN8I_ADDA_RES 0x0e 108 #define SUN8I_ADDA_RES_MMICBIAS_SEL 4 109 #define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0 110 #define SUN8I_ADDA_ADC_AP_EN 0x0f 111 #define SUN8I_ADDA_ADC_AP_EN_ADCREN 7 112 #define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6 113 #define SUN8I_ADDA_ADC_AP_EN_ADCG 0 114 115 /* mixer controls */ 116 static const struct snd_kcontrol_new sun8i_codec_mixer_controls[] = { 117 SOC_DAPM_DOUBLE_R("DAC Playback Switch", 118 SUN8I_ADDA_LOMIXSC, 119 SUN8I_ADDA_ROMIXSC, 120 SUN8I_ADDA_LOMIXSC_DACL, 1, 0), 121 SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", 122 SUN8I_ADDA_LOMIXSC, 123 SUN8I_ADDA_ROMIXSC, 124 SUN8I_ADDA_LOMIXSC_DACR, 1, 0), 125 SOC_DAPM_DOUBLE_R("Line In Playback Switch", 126 SUN8I_ADDA_LOMIXSC, 127 SUN8I_ADDA_ROMIXSC, 128 SUN8I_ADDA_LOMIXSC_LINEINL, 1, 0), 129 SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", 130 SUN8I_ADDA_LOMIXSC, 131 SUN8I_ADDA_ROMIXSC, 132 SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), 133 SOC_DAPM_DOUBLE_R("Mic2 Playback Switch", 134 SUN8I_ADDA_LOMIXSC, 135 SUN8I_ADDA_ROMIXSC, 136 SUN8I_ADDA_LOMIXSC_MIC2, 1, 0), 137 }; 138 139 /* mixer controls */ 140 static const struct snd_kcontrol_new sun8i_v3s_codec_mixer_controls[] = { 141 SOC_DAPM_DOUBLE_R("DAC Playback Switch", 142 SUN8I_ADDA_LOMIXSC, 143 SUN8I_ADDA_ROMIXSC, 144 SUN8I_ADDA_LOMIXSC_DACL, 1, 0), 145 SOC_DAPM_DOUBLE_R("DAC Reversed Playback Switch", 146 SUN8I_ADDA_LOMIXSC, 147 SUN8I_ADDA_ROMIXSC, 148 SUN8I_ADDA_LOMIXSC_DACR, 1, 0), 149 SOC_DAPM_DOUBLE_R("Mic1 Playback Switch", 150 SUN8I_ADDA_LOMIXSC, 151 SUN8I_ADDA_ROMIXSC, 152 SUN8I_ADDA_LOMIXSC_MIC1, 1, 0), 153 }; 154 155 /* ADC mixer controls */ 156 static const struct snd_kcontrol_new sun8i_codec_adc_mixer_controls[] = { 157 SOC_DAPM_DOUBLE_R("Mixer Capture Switch", 158 SUN8I_ADDA_LADCMIXSC, 159 SUN8I_ADDA_RADCMIXSC, 160 SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), 161 SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", 162 SUN8I_ADDA_LADCMIXSC, 163 SUN8I_ADDA_RADCMIXSC, 164 SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), 165 SOC_DAPM_DOUBLE_R("Line In Capture Switch", 166 SUN8I_ADDA_LADCMIXSC, 167 SUN8I_ADDA_RADCMIXSC, 168 SUN8I_ADDA_LADCMIXSC_LINEINL, 1, 0), 169 SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", 170 SUN8I_ADDA_LADCMIXSC, 171 SUN8I_ADDA_RADCMIXSC, 172 SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), 173 SOC_DAPM_DOUBLE_R("Mic2 Capture Switch", 174 SUN8I_ADDA_LADCMIXSC, 175 SUN8I_ADDA_RADCMIXSC, 176 SUN8I_ADDA_LADCMIXSC_MIC2, 1, 0), 177 }; 178 179 /* ADC mixer controls */ 180 static const struct snd_kcontrol_new sun8i_v3s_codec_adc_mixer_controls[] = { 181 SOC_DAPM_DOUBLE_R("Mixer Capture Switch", 182 SUN8I_ADDA_LADCMIXSC, 183 SUN8I_ADDA_RADCMIXSC, 184 SUN8I_ADDA_LADCMIXSC_OMIXRL, 1, 0), 185 SOC_DAPM_DOUBLE_R("Mixer Reversed Capture Switch", 186 SUN8I_ADDA_LADCMIXSC, 187 SUN8I_ADDA_RADCMIXSC, 188 SUN8I_ADDA_LADCMIXSC_OMIXRR, 1, 0), 189 SOC_DAPM_DOUBLE_R("Mic1 Capture Switch", 190 SUN8I_ADDA_LADCMIXSC, 191 SUN8I_ADDA_RADCMIXSC, 192 SUN8I_ADDA_LADCMIXSC_MIC1, 1, 0), 193 }; 194 195 /* volume / mute controls */ 196 static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, 197 -450, 150, 0); 198 static const DECLARE_TLV_DB_RANGE(sun8i_codec_mic_gain_scale, 199 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), 200 1, 7, TLV_DB_SCALE_ITEM(2400, 300, 0), 201 ); 202 203 static const struct snd_kcontrol_new sun8i_codec_common_controls[] = { 204 /* Mixer pre-gain */ 205 SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, 206 SUN8I_ADDA_MICIN_GCTRL_MIC1G, 207 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 208 209 /* Microphone Amp boost gain */ 210 SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 211 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, 212 sun8i_codec_mic_gain_scale), 213 214 /* ADC */ 215 SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, 216 SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0, 217 sun8i_codec_out_mixer_pregain_scale), 218 }; 219 220 static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { 221 /* ADC */ 222 SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN, 223 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0), 224 SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN, 225 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0), 226 227 /* DAC */ 228 SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, 229 SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0), 230 SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, 231 SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0), 232 /* 233 * Due to this component and the codec belonging to separate DAPM 234 * contexts, we need to manually link the above widgets to their 235 * stream widgets at the card level. 236 */ 237 238 /* Microphone input */ 239 SND_SOC_DAPM_INPUT("MIC1"), 240 241 /* Mic input path */ 242 SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 243 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), 244 }; 245 246 static const struct snd_soc_dapm_widget sun8i_codec_mixer_widgets[] = { 247 SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, 248 SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, 249 sun8i_codec_mixer_controls, 250 ARRAY_SIZE(sun8i_codec_mixer_controls)), 251 SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, 252 SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, 253 sun8i_codec_mixer_controls, 254 ARRAY_SIZE(sun8i_codec_mixer_controls)), 255 SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 256 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, 257 sun8i_codec_adc_mixer_controls, 258 ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), 259 SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 260 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, 261 sun8i_codec_adc_mixer_controls, 262 ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), 263 }; 264 265 static const struct snd_soc_dapm_widget sun8i_v3s_codec_mixer_widgets[] = { 266 SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, 267 SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, 268 sun8i_v3s_codec_mixer_controls, 269 ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), 270 SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, 271 SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, 272 sun8i_v3s_codec_mixer_controls, 273 ARRAY_SIZE(sun8i_v3s_codec_mixer_controls)), 274 SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 275 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, 276 sun8i_v3s_codec_adc_mixer_controls, 277 ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), 278 SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 279 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, 280 sun8i_v3s_codec_adc_mixer_controls, 281 ARRAY_SIZE(sun8i_v3s_codec_adc_mixer_controls)), 282 }; 283 284 static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { 285 /* Microphone Routes */ 286 { "Mic1 Amplifier", NULL, "MIC1"}, 287 }; 288 289 static const struct snd_soc_dapm_route sun8i_codec_mixer_routes[] = { 290 /* Left Mixer Routes */ 291 { "Left Mixer", "DAC Playback Switch", "Left DAC" }, 292 { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, 293 { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, 294 295 /* Right Mixer Routes */ 296 { "Right Mixer", "DAC Playback Switch", "Right DAC" }, 297 { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, 298 { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, 299 300 /* Left ADC Mixer Routes */ 301 { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, 302 { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, 303 { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, 304 305 /* Right ADC Mixer Routes */ 306 { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, 307 { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, 308 { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, 309 310 /* ADC Routes */ 311 { "Left ADC", NULL, "Left ADC Mixer" }, 312 { "Right ADC", NULL, "Right ADC Mixer" }, 313 }; 314 315 /* headphone specific controls, widgets, and routes */ 316 static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); 317 static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = { 318 SOC_SINGLE_TLV("Headphone Playback Volume", 319 SUN8I_ADDA_HP_VOLC, 320 SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0, 321 sun8i_codec_hp_vol_scale), 322 SOC_DOUBLE("Headphone Playback Switch", 323 SUN8I_ADDA_DAC_PA_SRC, 324 SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE, 325 SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0), 326 }; 327 328 static const char * const sun8i_codec_hp_src_enum_text[] = { 329 "DAC", "Mixer", 330 }; 331 332 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum, 333 SUN8I_ADDA_DAC_PA_SRC, 334 SUN8I_ADDA_DAC_PA_SRC_LHPIS, 335 SUN8I_ADDA_DAC_PA_SRC_RHPIS, 336 sun8i_codec_hp_src_enum_text); 337 338 static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { 339 SOC_DAPM_ENUM("Headphone Source Playback Route", 340 sun8i_codec_hp_src_enum), 341 }; 342 343 static int sun8i_headphone_amp_event(struct snd_soc_dapm_widget *w, 344 struct snd_kcontrol *k, int event) 345 { 346 struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm); 347 348 if (SND_SOC_DAPM_EVENT_ON(event)) { 349 snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, 350 BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), 351 BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN)); 352 /* 353 * Need a delay to have the amplifier up. 700ms seems the best 354 * compromise between the time to let the amplifier up and the 355 * time not to feel this delay while playing a sound. 356 */ 357 msleep(700); 358 } else if (SND_SOC_DAPM_EVENT_OFF(event)) { 359 snd_soc_component_update_bits(component, SUN8I_ADDA_PAEN_HP_CTRL, 360 BIT(SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN), 361 0x0); 362 } 363 364 return 0; 365 } 366 367 static const struct snd_soc_dapm_widget sun8i_codec_headphone_widgets[] = { 368 SND_SOC_DAPM_MUX("Headphone Source Playback Route", 369 SND_SOC_NOPM, 0, 0, sun8i_codec_hp_src), 370 SND_SOC_DAPM_OUT_DRV_E("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, 371 SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0, 372 sun8i_headphone_amp_event, 373 SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), 374 SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL, 375 SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), 376 SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL, 377 SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0), 378 SND_SOC_DAPM_OUTPUT("HP"), 379 }; 380 381 static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = { 382 { "Headphone Source Playback Route", "DAC", "Left DAC" }, 383 { "Headphone Source Playback Route", "DAC", "Right DAC" }, 384 { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, 385 { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, 386 { "Headphone Amp", NULL, "Headphone Source Playback Route" }, 387 { "HPCOM", NULL, "HPCOM Protection" }, 388 { "HP", NULL, "Headphone Amp" }, 389 }; 390 391 static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) 392 { 393 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 394 struct device *dev = cmpnt->dev; 395 int ret; 396 397 ret = snd_soc_add_component_controls(cmpnt, 398 sun8i_codec_headphone_controls, 399 ARRAY_SIZE(sun8i_codec_headphone_controls)); 400 if (ret) { 401 dev_err(dev, "Failed to add Headphone controls: %d\n", ret); 402 return ret; 403 } 404 405 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets, 406 ARRAY_SIZE(sun8i_codec_headphone_widgets)); 407 if (ret) { 408 dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret); 409 return ret; 410 } 411 412 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes, 413 ARRAY_SIZE(sun8i_codec_headphone_routes)); 414 if (ret) { 415 dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret); 416 return ret; 417 } 418 419 return 0; 420 } 421 422 /* mbias specific widget */ 423 static const struct snd_soc_dapm_widget sun8i_codec_mbias_widgets[] = { 424 SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 425 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, 426 0, NULL, 0), 427 }; 428 429 static int sun8i_codec_add_mbias(struct snd_soc_component *cmpnt) 430 { 431 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 432 struct device *dev = cmpnt->dev; 433 int ret; 434 435 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mbias_widgets, 436 ARRAY_SIZE(sun8i_codec_mbias_widgets)); 437 if (ret) 438 dev_err(dev, "Failed to add MBIAS DAPM widgets: %d\n", ret); 439 440 return ret; 441 } 442 443 /* hmic specific widget */ 444 static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { 445 SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 446 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, 447 0, NULL, 0), 448 }; 449 450 static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) 451 { 452 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 453 struct device *dev = cmpnt->dev; 454 int ret; 455 456 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets, 457 ARRAY_SIZE(sun8i_codec_hmic_widgets)); 458 if (ret) 459 dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret); 460 461 return ret; 462 } 463 464 /* line in specific controls, widgets and rines */ 465 static const struct snd_kcontrol_new sun8i_codec_linein_controls[] = { 466 /* Mixer pre-gain */ 467 SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, 468 SUN8I_ADDA_LINEIN_GCTRL_LINEING, 469 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 470 }; 471 472 static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { 473 /* Line input */ 474 SND_SOC_DAPM_INPUT("LINEIN"), 475 }; 476 477 static const struct snd_soc_dapm_route sun8i_codec_linein_routes[] = { 478 { "Left Mixer", "Line In Playback Switch", "LINEIN" }, 479 480 { "Right Mixer", "Line In Playback Switch", "LINEIN" }, 481 482 { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, 483 484 { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, 485 }; 486 487 static int sun8i_codec_add_linein(struct snd_soc_component *cmpnt) 488 { 489 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 490 struct device *dev = cmpnt->dev; 491 int ret; 492 493 ret = snd_soc_add_component_controls(cmpnt, 494 sun8i_codec_linein_controls, 495 ARRAY_SIZE(sun8i_codec_linein_controls)); 496 if (ret) { 497 dev_err(dev, "Failed to add Line In controls: %d\n", ret); 498 return ret; 499 } 500 501 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_linein_widgets, 502 ARRAY_SIZE(sun8i_codec_linein_widgets)); 503 if (ret) { 504 dev_err(dev, "Failed to add Line In DAPM widgets: %d\n", ret); 505 return ret; 506 } 507 508 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_linein_routes, 509 ARRAY_SIZE(sun8i_codec_linein_routes)); 510 if (ret) { 511 dev_err(dev, "Failed to add Line In DAPM routes: %d\n", ret); 512 return ret; 513 } 514 515 return 0; 516 } 517 518 519 /* line out specific controls, widgets and routes */ 520 static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, 521 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), 522 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), 523 ); 524 static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = { 525 SOC_SINGLE_TLV("Line Out Playback Volume", 526 SUN8I_ADDA_PHONE_GAIN_CTRL, 527 SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0, 528 sun8i_codec_lineout_vol_scale), 529 SOC_DOUBLE("Line Out Playback Switch", 530 SUN8I_ADDA_MIC2G_CTRL, 531 SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN, 532 SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0), 533 }; 534 535 static const char * const sun8i_codec_lineout_src_enum_text[] = { 536 "Stereo", "Mono Differential", 537 }; 538 539 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum, 540 SUN8I_ADDA_MIC2G_CTRL, 541 SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC, 542 SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC, 543 sun8i_codec_lineout_src_enum_text); 544 545 static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = { 546 SOC_DAPM_ENUM("Line Out Source Playback Route", 547 sun8i_codec_lineout_src_enum), 548 }; 549 550 static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = { 551 SND_SOC_DAPM_MUX("Line Out Source Playback Route", 552 SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src), 553 /* It is unclear if this is a buffer or gate, model it as a supply */ 554 SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL, 555 SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0), 556 SND_SOC_DAPM_OUTPUT("LINEOUT"), 557 }; 558 559 static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = { 560 { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, 561 { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, 562 { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, 563 { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, 564 { "LINEOUT", NULL, "Line Out Source Playback Route" }, 565 { "LINEOUT", NULL, "Line Out Enable", }, 566 }; 567 568 static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) 569 { 570 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 571 struct device *dev = cmpnt->dev; 572 int ret; 573 574 ret = snd_soc_add_component_controls(cmpnt, 575 sun8i_codec_lineout_controls, 576 ARRAY_SIZE(sun8i_codec_lineout_controls)); 577 if (ret) { 578 dev_err(dev, "Failed to add Line Out controls: %d\n", ret); 579 return ret; 580 } 581 582 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets, 583 ARRAY_SIZE(sun8i_codec_lineout_widgets)); 584 if (ret) { 585 dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret); 586 return ret; 587 } 588 589 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes, 590 ARRAY_SIZE(sun8i_codec_lineout_routes)); 591 if (ret) { 592 dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret); 593 return ret; 594 } 595 596 return 0; 597 } 598 599 /* mic2 specific controls, widgets and routes */ 600 static const struct snd_kcontrol_new sun8i_codec_mic2_controls[] = { 601 /* Mixer pre-gain */ 602 SOC_SINGLE_TLV("Mic2 Playback Volume", 603 SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, 604 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 605 606 /* Microphone Amp boost gain */ 607 SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, 608 SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, 609 sun8i_codec_mic_gain_scale), 610 }; 611 612 static const struct snd_soc_dapm_widget sun8i_codec_mic2_widgets[] = { 613 /* Microphone input */ 614 SND_SOC_DAPM_INPUT("MIC2"), 615 616 /* Mic input path */ 617 SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, 618 SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), 619 }; 620 621 static const struct snd_soc_dapm_route sun8i_codec_mic2_routes[] = { 622 { "Mic2 Amplifier", NULL, "MIC2"}, 623 624 { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, 625 626 { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, 627 628 { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, 629 630 { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, 631 }; 632 633 static int sun8i_codec_add_mic2(struct snd_soc_component *cmpnt) 634 { 635 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 636 struct device *dev = cmpnt->dev; 637 int ret; 638 639 ret = snd_soc_add_component_controls(cmpnt, 640 sun8i_codec_mic2_controls, 641 ARRAY_SIZE(sun8i_codec_mic2_controls)); 642 if (ret) { 643 dev_err(dev, "Failed to add MIC2 controls: %d\n", ret); 644 return ret; 645 } 646 647 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_mic2_widgets, 648 ARRAY_SIZE(sun8i_codec_mic2_widgets)); 649 if (ret) { 650 dev_err(dev, "Failed to add MIC2 DAPM widgets: %d\n", ret); 651 return ret; 652 } 653 654 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mic2_routes, 655 ARRAY_SIZE(sun8i_codec_mic2_routes)); 656 if (ret) { 657 dev_err(dev, "Failed to add MIC2 DAPM routes: %d\n", ret); 658 return ret; 659 } 660 661 return 0; 662 } 663 664 struct sun8i_codec_analog_quirks { 665 bool has_headphone; 666 bool has_hmic; 667 bool has_linein; 668 bool has_lineout; 669 bool has_mbias; 670 bool has_mic2; 671 }; 672 673 static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { 674 .has_headphone = true, 675 .has_hmic = true, 676 .has_linein = true, 677 .has_mbias = true, 678 .has_mic2 = true, 679 }; 680 681 static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { 682 .has_linein = true, 683 .has_lineout = true, 684 .has_mbias = true, 685 .has_mic2 = true, 686 }; 687 688 static int sun8i_codec_analog_add_mixer(struct snd_soc_component *cmpnt, 689 const struct sun8i_codec_analog_quirks *quirks) 690 { 691 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 692 struct device *dev = cmpnt->dev; 693 int ret; 694 695 if (!quirks->has_mic2 && !quirks->has_linein) { 696 /* 697 * Apply the special widget set which has uses a control 698 * without MIC2 and Line In, for SoCs without these. 699 * TODO: not all special cases are supported now, this case 700 * is present because it's the case of V3s. 701 */ 702 ret = snd_soc_dapm_new_controls(dapm, 703 sun8i_v3s_codec_mixer_widgets, 704 ARRAY_SIZE(sun8i_v3s_codec_mixer_widgets)); 705 if (ret) { 706 dev_err(dev, "Failed to add V3s Mixer DAPM widgets: %d\n", ret); 707 return ret; 708 } 709 } else { 710 /* Apply the generic mixer widget set. */ 711 ret = snd_soc_dapm_new_controls(dapm, 712 sun8i_codec_mixer_widgets, 713 ARRAY_SIZE(sun8i_codec_mixer_widgets)); 714 if (ret) { 715 dev_err(dev, "Failed to add Mixer DAPM widgets: %d\n", ret); 716 return ret; 717 } 718 } 719 720 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_mixer_routes, 721 ARRAY_SIZE(sun8i_codec_mixer_routes)); 722 if (ret) { 723 dev_err(dev, "Failed to add Mixer DAPM routes: %d\n", ret); 724 return ret; 725 } 726 727 return 0; 728 } 729 730 static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { 731 .has_headphone = true, 732 .has_hmic = true, 733 }; 734 735 static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) 736 { 737 struct device *dev = cmpnt->dev; 738 const struct sun8i_codec_analog_quirks *quirks; 739 int ret; 740 741 /* 742 * This would never return NULL unless someone directly registers a 743 * platform device matching this driver's name, without specifying a 744 * device tree node. 745 */ 746 quirks = of_device_get_match_data(dev); 747 748 /* Add controls, widgets, and routes for individual features */ 749 ret = sun8i_codec_analog_add_mixer(cmpnt, quirks); 750 if (ret) 751 return ret; 752 753 if (quirks->has_headphone) { 754 ret = sun8i_codec_add_headphone(cmpnt); 755 if (ret) 756 return ret; 757 } 758 759 if (quirks->has_hmic) { 760 ret = sun8i_codec_add_hmic(cmpnt); 761 if (ret) 762 return ret; 763 } 764 765 if (quirks->has_linein) { 766 ret = sun8i_codec_add_linein(cmpnt); 767 if (ret) 768 return ret; 769 } 770 771 if (quirks->has_lineout) { 772 ret = sun8i_codec_add_lineout(cmpnt); 773 if (ret) 774 return ret; 775 } 776 777 if (quirks->has_mbias) { 778 ret = sun8i_codec_add_mbias(cmpnt); 779 if (ret) 780 return ret; 781 } 782 783 if (quirks->has_mic2) { 784 ret = sun8i_codec_add_mic2(cmpnt); 785 if (ret) 786 return ret; 787 } 788 789 return 0; 790 } 791 792 static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = { 793 .controls = sun8i_codec_common_controls, 794 .num_controls = ARRAY_SIZE(sun8i_codec_common_controls), 795 .dapm_widgets = sun8i_codec_common_widgets, 796 .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets), 797 .dapm_routes = sun8i_codec_common_routes, 798 .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes), 799 .probe = sun8i_codec_analog_cmpnt_probe, 800 }; 801 802 static const struct of_device_id sun8i_codec_analog_of_match[] = { 803 { 804 .compatible = "allwinner,sun8i-a23-codec-analog", 805 .data = &sun8i_a23_quirks, 806 }, 807 { 808 .compatible = "allwinner,sun8i-h3-codec-analog", 809 .data = &sun8i_h3_quirks, 810 }, 811 { 812 .compatible = "allwinner,sun8i-v3s-codec-analog", 813 .data = &sun8i_v3s_quirks, 814 }, 815 {} 816 }; 817 MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); 818 819 static int sun8i_codec_analog_probe(struct platform_device *pdev) 820 { 821 struct regmap *regmap; 822 void __iomem *base; 823 824 base = devm_platform_ioremap_resource(pdev, 0); 825 if (IS_ERR(base)) { 826 dev_err(&pdev->dev, "Failed to map the registers\n"); 827 return PTR_ERR(base); 828 } 829 830 regmap = sun8i_adda_pr_regmap_init(&pdev->dev, base); 831 if (IS_ERR(regmap)) { 832 dev_err(&pdev->dev, "Failed to create regmap\n"); 833 return PTR_ERR(regmap); 834 } 835 836 return devm_snd_soc_register_component(&pdev->dev, 837 &sun8i_codec_analog_cmpnt_drv, 838 NULL, 0); 839 } 840 841 static struct platform_driver sun8i_codec_analog_driver = { 842 .driver = { 843 .name = "sun8i-codec-analog", 844 .of_match_table = sun8i_codec_analog_of_match, 845 }, 846 .probe = sun8i_codec_analog_probe, 847 }; 848 module_platform_driver(sun8i_codec_analog_driver); 849 850 MODULE_DESCRIPTION("Allwinner internal codec analog controls driver"); 851 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); 852 MODULE_LICENSE("GPL"); 853 MODULE_ALIAS("platform:sun8i-codec-analog"); 854
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.