Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v6.14 853 lines 28 kB view raw
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 */ 116static 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 */ 140static 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 */ 156static 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 */ 180static 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 */ 196static const DECLARE_TLV_DB_SCALE(sun8i_codec_out_mixer_pregain_scale, 197 -450, 150, 0); 198static 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 203static 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 220static 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 246static 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 265static 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 284static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { 285 /* Microphone Routes */ 286 { "Mic1 Amplifier", NULL, "MIC1"}, 287}; 288 289static 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 */ 316static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); 317static 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 328static const char * const sun8i_codec_hp_src_enum_text[] = { 329 "DAC", "Mixer", 330}; 331 332static 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 338static 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 343static 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 367static 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 381static 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 391static 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 */ 423static 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 429static 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 */ 444static 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 450static 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 */ 465static 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 472static const struct snd_soc_dapm_widget sun8i_codec_linein_widgets[] = { 473 /* Line input */ 474 SND_SOC_DAPM_INPUT("LINEIN"), 475}; 476 477static 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 487static 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 */ 520static 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); 524static 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 535static const char * const sun8i_codec_lineout_src_enum_text[] = { 536 "Stereo", "Mono Differential", 537}; 538 539static 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 545static 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 550static 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 559static 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 568static 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 */ 600static 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 612static 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 621static 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 633static 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 664struct 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 673static 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 681static 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 688static 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 730static const struct sun8i_codec_analog_quirks sun8i_v3s_quirks = { 731 .has_headphone = true, 732 .has_hmic = true, 733}; 734 735static 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 792static 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 802static 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}; 817MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); 818 819static 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 841static 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}; 848module_platform_driver(sun8i_codec_analog_driver); 849 850MODULE_DESCRIPTION("Allwinner internal codec analog controls driver"); 851MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); 852MODULE_LICENSE("GPL"); 853MODULE_ALIAS("platform:sun8i-codec-analog");