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