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-gains */ 256 SOC_SINGLE_TLV("Line In Playback Volume", SUN8I_ADDA_LINEIN_GCTRL, 257 SUN8I_ADDA_LINEIN_GCTRL_LINEING, 258 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 259 SOC_SINGLE_TLV("Mic1 Playback Volume", SUN8I_ADDA_MICIN_GCTRL, 260 SUN8I_ADDA_MICIN_GCTRL_MIC1G, 261 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 262 SOC_SINGLE_TLV("Mic2 Playback Volume", 263 SUN8I_ADDA_MICIN_GCTRL, SUN8I_ADDA_MICIN_GCTRL_MIC2G, 264 0x7, 0, sun8i_codec_out_mixer_pregain_scale), 265 266 /* Microphone Amp boost gains */ 267 SOC_SINGLE_TLV("Mic1 Boost Volume", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 268 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST, 0x7, 0, 269 sun8i_codec_mic_gain_scale), 270 SOC_SINGLE_TLV("Mic2 Boost Volume", SUN8I_ADDA_MIC2G_CTRL, 271 SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST, 0x7, 0, 272 sun8i_codec_mic_gain_scale), 273 274 /* ADC */ 275 SOC_SINGLE_TLV("ADC Gain Capture Volume", SUN8I_ADDA_ADC_AP_EN, 276 SUN8I_ADDA_ADC_AP_EN_ADCG, 0x7, 0, 277 sun8i_codec_out_mixer_pregain_scale), 278 }; 279 280 static const struct snd_soc_dapm_widget sun8i_codec_common_widgets[] = { 281 /* ADC */ 282 SND_SOC_DAPM_ADC("Left ADC", NULL, SUN8I_ADDA_ADC_AP_EN, 283 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0), 284 SND_SOC_DAPM_ADC("Right ADC", NULL, SUN8I_ADDA_ADC_AP_EN, 285 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0), 286 287 /* DAC */ 288 SND_SOC_DAPM_DAC("Left DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, 289 SUN8I_ADDA_DAC_PA_SRC_DACALEN, 0), 290 SND_SOC_DAPM_DAC("Right DAC", NULL, SUN8I_ADDA_DAC_PA_SRC, 291 SUN8I_ADDA_DAC_PA_SRC_DACAREN, 0), 292 /* 293 * Due to this component and the codec belonging to separate DAPM 294 * contexts, we need to manually link the above widgets to their 295 * stream widgets at the card level. 296 */ 297 298 /* Line In */ 299 SND_SOC_DAPM_INPUT("LINEIN"), 300 301 /* Microphone inputs */ 302 SND_SOC_DAPM_INPUT("MIC1"), 303 SND_SOC_DAPM_INPUT("MIC2"), 304 305 /* Microphone Bias */ 306 SND_SOC_DAPM_SUPPLY("MBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 307 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN, 308 0, NULL, 0), 309 310 /* Mic input path */ 311 SND_SOC_DAPM_PGA("Mic1 Amplifier", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 312 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN, 0, NULL, 0), 313 SND_SOC_DAPM_PGA("Mic2 Amplifier", SUN8I_ADDA_MIC2G_CTRL, 314 SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN, 0, NULL, 0), 315 316 /* Mixers */ 317 SND_SOC_DAPM_MIXER("Left Mixer", SUN8I_ADDA_DAC_PA_SRC, 318 SUN8I_ADDA_DAC_PA_SRC_LMIXEN, 0, 319 sun8i_codec_mixer_controls, 320 ARRAY_SIZE(sun8i_codec_mixer_controls)), 321 SND_SOC_DAPM_MIXER("Right Mixer", SUN8I_ADDA_DAC_PA_SRC, 322 SUN8I_ADDA_DAC_PA_SRC_RMIXEN, 0, 323 sun8i_codec_mixer_controls, 324 ARRAY_SIZE(sun8i_codec_mixer_controls)), 325 SND_SOC_DAPM_MIXER("Left ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 326 SUN8I_ADDA_ADC_AP_EN_ADCLEN, 0, 327 sun8i_codec_adc_mixer_controls, 328 ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), 329 SND_SOC_DAPM_MIXER("Right ADC Mixer", SUN8I_ADDA_ADC_AP_EN, 330 SUN8I_ADDA_ADC_AP_EN_ADCREN, 0, 331 sun8i_codec_adc_mixer_controls, 332 ARRAY_SIZE(sun8i_codec_adc_mixer_controls)), 333 }; 334 335 static const struct snd_soc_dapm_route sun8i_codec_common_routes[] = { 336 /* Microphone Routes */ 337 { "Mic1 Amplifier", NULL, "MIC1"}, 338 { "Mic2 Amplifier", NULL, "MIC2"}, 339 340 /* Left Mixer Routes */ 341 { "Left Mixer", "DAC Playback Switch", "Left DAC" }, 342 { "Left Mixer", "DAC Reversed Playback Switch", "Right DAC" }, 343 { "Left Mixer", "Line In Playback Switch", "LINEIN" }, 344 { "Left Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, 345 { "Left Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, 346 347 /* Right Mixer Routes */ 348 { "Right Mixer", "DAC Playback Switch", "Right DAC" }, 349 { "Right Mixer", "DAC Reversed Playback Switch", "Left DAC" }, 350 { "Right Mixer", "Line In Playback Switch", "LINEIN" }, 351 { "Right Mixer", "Mic1 Playback Switch", "Mic1 Amplifier" }, 352 { "Right Mixer", "Mic2 Playback Switch", "Mic2 Amplifier" }, 353 354 /* Left ADC Mixer Routes */ 355 { "Left ADC Mixer", "Mixer Capture Switch", "Left Mixer" }, 356 { "Left ADC Mixer", "Mixer Reversed Capture Switch", "Right Mixer" }, 357 { "Left ADC Mixer", "Line In Capture Switch", "LINEIN" }, 358 { "Left ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, 359 { "Left ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, 360 361 /* Right ADC Mixer Routes */ 362 { "Right ADC Mixer", "Mixer Capture Switch", "Right Mixer" }, 363 { "Right ADC Mixer", "Mixer Reversed Capture Switch", "Left Mixer" }, 364 { "Right ADC Mixer", "Line In Capture Switch", "LINEIN" }, 365 { "Right ADC Mixer", "Mic1 Capture Switch", "Mic1 Amplifier" }, 366 { "Right ADC Mixer", "Mic2 Capture Switch", "Mic2 Amplifier" }, 367 368 /* ADC Routes */ 369 { "Left ADC", NULL, "Left ADC Mixer" }, 370 { "Right ADC", NULL, "Right ADC Mixer" }, 371 }; 372 373 /* headphone specific controls, widgets, and routes */ 374 static const DECLARE_TLV_DB_SCALE(sun8i_codec_hp_vol_scale, -6300, 100, 1); 375 static const struct snd_kcontrol_new sun8i_codec_headphone_controls[] = { 376 SOC_SINGLE_TLV("Headphone Playback Volume", 377 SUN8I_ADDA_HP_VOLC, 378 SUN8I_ADDA_HP_VOLC_HP_VOL, 0x3f, 0, 379 sun8i_codec_hp_vol_scale), 380 SOC_DOUBLE("Headphone Playback Switch", 381 SUN8I_ADDA_DAC_PA_SRC, 382 SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE, 383 SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE, 1, 0), 384 }; 385 386 static const char * const sun8i_codec_hp_src_enum_text[] = { 387 "DAC", "Mixer", 388 }; 389 390 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_hp_src_enum, 391 SUN8I_ADDA_DAC_PA_SRC, 392 SUN8I_ADDA_DAC_PA_SRC_LHPIS, 393 SUN8I_ADDA_DAC_PA_SRC_RHPIS, 394 sun8i_codec_hp_src_enum_text); 395 396 static const struct snd_kcontrol_new sun8i_codec_hp_src[] = { 397 SOC_DAPM_ENUM("Headphone Source Playback Route", 398 sun8i_codec_hp_src_enum), 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("Headphone Amp", SUN8I_ADDA_PAEN_HP_CTRL, 405 SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN, 0, NULL, 0), 406 SND_SOC_DAPM_SUPPLY("HPCOM Protection", SUN8I_ADDA_PAEN_HP_CTRL, 407 SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN, 0, NULL, 0), 408 SND_SOC_DAPM_REG(snd_soc_dapm_supply, "HPCOM", SUN8I_ADDA_PAEN_HP_CTRL, 409 SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC, 0x3, 0x3, 0), 410 SND_SOC_DAPM_OUTPUT("HP"), 411 }; 412 413 static const struct snd_soc_dapm_route sun8i_codec_headphone_routes[] = { 414 { "Headphone Source Playback Route", "DAC", "Left DAC" }, 415 { "Headphone Source Playback Route", "DAC", "Right DAC" }, 416 { "Headphone Source Playback Route", "Mixer", "Left Mixer" }, 417 { "Headphone Source Playback Route", "Mixer", "Right Mixer" }, 418 { "Headphone Amp", NULL, "Headphone Source Playback Route" }, 419 { "HPCOM", NULL, "HPCOM Protection" }, 420 { "HP", NULL, "Headphone Amp" }, 421 }; 422 423 static int sun8i_codec_add_headphone(struct snd_soc_component *cmpnt) 424 { 425 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 426 struct device *dev = cmpnt->dev; 427 int ret; 428 429 ret = snd_soc_add_component_controls(cmpnt, 430 sun8i_codec_headphone_controls, 431 ARRAY_SIZE(sun8i_codec_headphone_controls)); 432 if (ret) { 433 dev_err(dev, "Failed to add Headphone controls: %d\n", ret); 434 return ret; 435 } 436 437 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_headphone_widgets, 438 ARRAY_SIZE(sun8i_codec_headphone_widgets)); 439 if (ret) { 440 dev_err(dev, "Failed to add Headphone DAPM widgets: %d\n", ret); 441 return ret; 442 } 443 444 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_headphone_routes, 445 ARRAY_SIZE(sun8i_codec_headphone_routes)); 446 if (ret) { 447 dev_err(dev, "Failed to add Headphone DAPM routes: %d\n", ret); 448 return ret; 449 } 450 451 return 0; 452 } 453 454 /* hmic specific widget */ 455 static const struct snd_soc_dapm_widget sun8i_codec_hmic_widgets[] = { 456 SND_SOC_DAPM_SUPPLY("HBIAS", SUN8I_ADDA_MIC1G_MICBIAS_CTRL, 457 SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN, 458 0, NULL, 0), 459 }; 460 461 static int sun8i_codec_add_hmic(struct snd_soc_component *cmpnt) 462 { 463 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 464 struct device *dev = cmpnt->dev; 465 int ret; 466 467 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_hmic_widgets, 468 ARRAY_SIZE(sun8i_codec_hmic_widgets)); 469 if (ret) 470 dev_err(dev, "Failed to add Mic3 DAPM widgets: %d\n", ret); 471 472 return ret; 473 } 474 475 /* line out specific controls, widgets and routes */ 476 static const DECLARE_TLV_DB_RANGE(sun8i_codec_lineout_vol_scale, 477 0, 1, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), 478 2, 31, TLV_DB_SCALE_ITEM(-4350, 150, 0), 479 ); 480 static const struct snd_kcontrol_new sun8i_codec_lineout_controls[] = { 481 SOC_SINGLE_TLV("Line Out Playback Volume", 482 SUN8I_ADDA_PHONE_GAIN_CTRL, 483 SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL, 0x1f, 0, 484 sun8i_codec_lineout_vol_scale), 485 SOC_DOUBLE("Line Out Playback Switch", 486 SUN8I_ADDA_MIC2G_CTRL, 487 SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN, 488 SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN, 1, 0), 489 }; 490 491 static const char * const sun8i_codec_lineout_src_enum_text[] = { 492 "Stereo", "Mono Differential", 493 }; 494 495 static SOC_ENUM_DOUBLE_DECL(sun8i_codec_lineout_src_enum, 496 SUN8I_ADDA_MIC2G_CTRL, 497 SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC, 498 SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC, 499 sun8i_codec_lineout_src_enum_text); 500 501 static const struct snd_kcontrol_new sun8i_codec_lineout_src[] = { 502 SOC_DAPM_ENUM("Line Out Source Playback Route", 503 sun8i_codec_lineout_src_enum), 504 }; 505 506 static const struct snd_soc_dapm_widget sun8i_codec_lineout_widgets[] = { 507 SND_SOC_DAPM_MUX("Line Out Source Playback Route", 508 SND_SOC_NOPM, 0, 0, sun8i_codec_lineout_src), 509 /* It is unclear if this is a buffer or gate, model it as a supply */ 510 SND_SOC_DAPM_SUPPLY("Line Out Enable", SUN8I_ADDA_PAEN_HP_CTRL, 511 SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN, 0, NULL, 0), 512 SND_SOC_DAPM_OUTPUT("LINEOUT"), 513 }; 514 515 static const struct snd_soc_dapm_route sun8i_codec_lineout_routes[] = { 516 { "Line Out Source Playback Route", "Stereo", "Left Mixer" }, 517 { "Line Out Source Playback Route", "Stereo", "Right Mixer" }, 518 { "Line Out Source Playback Route", "Mono Differential", "Left Mixer" }, 519 { "Line Out Source Playback Route", "Mono Differential", "Right Mixer" }, 520 { "LINEOUT", NULL, "Line Out Source Playback Route" }, 521 { "LINEOUT", NULL, "Line Out Enable", }, 522 }; 523 524 static int sun8i_codec_add_lineout(struct snd_soc_component *cmpnt) 525 { 526 struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(cmpnt); 527 struct device *dev = cmpnt->dev; 528 int ret; 529 530 ret = snd_soc_add_component_controls(cmpnt, 531 sun8i_codec_lineout_controls, 532 ARRAY_SIZE(sun8i_codec_lineout_controls)); 533 if (ret) { 534 dev_err(dev, "Failed to add Line Out controls: %d\n", ret); 535 return ret; 536 } 537 538 ret = snd_soc_dapm_new_controls(dapm, sun8i_codec_lineout_widgets, 539 ARRAY_SIZE(sun8i_codec_lineout_widgets)); 540 if (ret) { 541 dev_err(dev, "Failed to add Line Out DAPM widgets: %d\n", ret); 542 return ret; 543 } 544 545 ret = snd_soc_dapm_add_routes(dapm, sun8i_codec_lineout_routes, 546 ARRAY_SIZE(sun8i_codec_lineout_routes)); 547 if (ret) { 548 dev_err(dev, "Failed to add Line Out DAPM routes: %d\n", ret); 549 return ret; 550 } 551 552 return 0; 553 } 554 555 struct sun8i_codec_analog_quirks { 556 bool has_headphone; 557 bool has_hmic; 558 bool has_lineout; 559 }; 560 561 static const struct sun8i_codec_analog_quirks sun8i_a23_quirks = { 562 .has_headphone = true, 563 .has_hmic = true, 564 }; 565 566 static const struct sun8i_codec_analog_quirks sun8i_h3_quirks = { 567 .has_lineout = true, 568 }; 569 570 static int sun8i_codec_analog_cmpnt_probe(struct snd_soc_component *cmpnt) 571 { 572 struct device *dev = cmpnt->dev; 573 const struct sun8i_codec_analog_quirks *quirks; 574 int ret; 575 576 /* 577 * This would never return NULL unless someone directly registers a 578 * platform device matching this driver's name, without specifying a 579 * device tree node. 580 */ 581 quirks = of_device_get_match_data(dev); 582 583 /* Add controls, widgets, and routes for individual features */ 584 585 if (quirks->has_headphone) { 586 ret = sun8i_codec_add_headphone(cmpnt); 587 if (ret) 588 return ret; 589 } 590 591 if (quirks->has_hmic) { 592 ret = sun8i_codec_add_hmic(cmpnt); 593 if (ret) 594 return ret; 595 } 596 597 if (quirks->has_lineout) { 598 ret = sun8i_codec_add_lineout(cmpnt); 599 if (ret) 600 return ret; 601 } 602 603 return 0; 604 } 605 606 static const struct snd_soc_component_driver sun8i_codec_analog_cmpnt_drv = { 607 .controls = sun8i_codec_common_controls, 608 .num_controls = ARRAY_SIZE(sun8i_codec_common_controls), 609 .dapm_widgets = sun8i_codec_common_widgets, 610 .num_dapm_widgets = ARRAY_SIZE(sun8i_codec_common_widgets), 611 .dapm_routes = sun8i_codec_common_routes, 612 .num_dapm_routes = ARRAY_SIZE(sun8i_codec_common_routes), 613 .probe = sun8i_codec_analog_cmpnt_probe, 614 }; 615 616 static const struct of_device_id sun8i_codec_analog_of_match[] = { 617 { 618 .compatible = "allwinner,sun8i-a23-codec-analog", 619 .data = &sun8i_a23_quirks, 620 }, 621 { 622 .compatible = "allwinner,sun8i-h3-codec-analog", 623 .data = &sun8i_h3_quirks, 624 }, 625 {} 626 }; 627 MODULE_DEVICE_TABLE(of, sun8i_codec_analog_of_match); 628 629 static int sun8i_codec_analog_probe(struct platform_device *pdev) 630 { 631 struct resource *res; 632 struct regmap *regmap; 633 void __iomem *base; 634 635 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 636 base = devm_ioremap_resource(&pdev->dev, res); 637 if (IS_ERR(base)) { 638 dev_err(&pdev->dev, "Failed to map the registers\n"); 639 return PTR_ERR(base); 640 } 641 642 regmap = devm_regmap_init(&pdev->dev, NULL, base, &adda_pr_regmap_cfg); 643 if (IS_ERR(regmap)) { 644 dev_err(&pdev->dev, "Failed to create regmap\n"); 645 return PTR_ERR(regmap); 646 } 647 648 return devm_snd_soc_register_component(&pdev->dev, 649 &sun8i_codec_analog_cmpnt_drv, 650 NULL, 0); 651 } 652 653 static struct platform_driver sun8i_codec_analog_driver = { 654 .driver = { 655 .name = "sun8i-codec-analog", 656 .of_match_table = sun8i_codec_analog_of_match, 657 }, 658 .probe = sun8i_codec_analog_probe, 659 }; 660 module_platform_driver(sun8i_codec_analog_driver); 661 662 MODULE_DESCRIPTION("Allwinner internal codec analog controls driver"); 663 MODULE_AUTHOR("Chen-Yu Tsai <wens@csie.org>"); 664 MODULE_LICENSE("GPL"); 665 MODULE_ALIAS("platform:sun8i-codec-analog"); 666