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