854 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * This driver supports the analog controls for the internal codec4 * 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 0x0024#define SUN8I_ADDA_HP_VOLC_PA_CLK_GATE 725#define SUN8I_ADDA_HP_VOLC_HP_VOL 026#define SUN8I_ADDA_LOMIXSC 0x0127#define SUN8I_ADDA_LOMIXSC_MIC1 628#define SUN8I_ADDA_LOMIXSC_MIC2 529#define SUN8I_ADDA_LOMIXSC_PHONE 430#define SUN8I_ADDA_LOMIXSC_PHONEN 331#define SUN8I_ADDA_LOMIXSC_LINEINL 232#define SUN8I_ADDA_LOMIXSC_DACL 133#define SUN8I_ADDA_LOMIXSC_DACR 034#define SUN8I_ADDA_ROMIXSC 0x0235#define SUN8I_ADDA_ROMIXSC_MIC1 636#define SUN8I_ADDA_ROMIXSC_MIC2 537#define SUN8I_ADDA_ROMIXSC_PHONE 438#define SUN8I_ADDA_ROMIXSC_PHONEP 339#define SUN8I_ADDA_ROMIXSC_LINEINR 240#define SUN8I_ADDA_ROMIXSC_DACR 141#define SUN8I_ADDA_ROMIXSC_DACL 042#define SUN8I_ADDA_DAC_PA_SRC 0x0343#define SUN8I_ADDA_DAC_PA_SRC_DACAREN 744#define SUN8I_ADDA_DAC_PA_SRC_DACALEN 645#define SUN8I_ADDA_DAC_PA_SRC_RMIXEN 546#define SUN8I_ADDA_DAC_PA_SRC_LMIXEN 447#define SUN8I_ADDA_DAC_PA_SRC_RHPPAMUTE 348#define SUN8I_ADDA_DAC_PA_SRC_LHPPAMUTE 249#define SUN8I_ADDA_DAC_PA_SRC_RHPIS 150#define SUN8I_ADDA_DAC_PA_SRC_LHPIS 051#define SUN8I_ADDA_PHONEIN_GCTRL 0x0452#define SUN8I_ADDA_PHONEIN_GCTRL_PHONEPG 453#define SUN8I_ADDA_PHONEIN_GCTRL_PHONENG 054#define SUN8I_ADDA_LINEIN_GCTRL 0x0555#define SUN8I_ADDA_LINEIN_GCTRL_LINEING 456#define SUN8I_ADDA_LINEIN_GCTRL_PHONEG 057#define SUN8I_ADDA_MICIN_GCTRL 0x0658#define SUN8I_ADDA_MICIN_GCTRL_MIC1G 459#define SUN8I_ADDA_MICIN_GCTRL_MIC2G 060#define SUN8I_ADDA_PAEN_HP_CTRL 0x0761#define SUN8I_ADDA_PAEN_HP_CTRL_HPPAEN 762#define SUN8I_ADDA_PAEN_HP_CTRL_LINEOUTEN 7 /* H3 specific */63#define SUN8I_ADDA_PAEN_HP_CTRL_HPCOM_FC 564#define SUN8I_ADDA_PAEN_HP_CTRL_COMPTEN 465#define SUN8I_ADDA_PAEN_HP_CTRL_PA_ANTI_POP_CTRL 266#define SUN8I_ADDA_PAEN_HP_CTRL_LTRNMUTE 167#define SUN8I_ADDA_PAEN_HP_CTRL_RTLNMUTE 068#define SUN8I_ADDA_PHONEOUT_CTRL 0x0869#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTG 570#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUTEN 471#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC1 372#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_MIC2 273#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_RMIX 174#define SUN8I_ADDA_PHONEOUT_CTRL_PHONEOUT_LMIX 075#define SUN8I_ADDA_PHONE_GAIN_CTRL 0x0976#define SUN8I_ADDA_PHONE_GAIN_CTRL_LINEOUT_VOL 377#define SUN8I_ADDA_PHONE_GAIN_CTRL_PHONEPREG 078#define SUN8I_ADDA_MIC2G_CTRL 0x0a79#define SUN8I_ADDA_MIC2G_CTRL_MIC2AMPEN 780#define SUN8I_ADDA_MIC2G_CTRL_MIC2BOOST 481#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLEN 382#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTREN 283#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTLSRC 184#define SUN8I_ADDA_MIC2G_CTRL_LINEOUTRSRC 085#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL 0x0b86#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIASEN 787#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MMICBIASEN 688#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_HMICBIAS_MODE 589#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1AMPEN 390#define SUN8I_ADDA_MIC1G_MICBIAS_CTRL_MIC1BOOST 091#define SUN8I_ADDA_LADCMIXSC 0x0c92#define SUN8I_ADDA_LADCMIXSC_MIC1 693#define SUN8I_ADDA_LADCMIXSC_MIC2 594#define SUN8I_ADDA_LADCMIXSC_PHONE 495#define SUN8I_ADDA_LADCMIXSC_PHONEN 396#define SUN8I_ADDA_LADCMIXSC_LINEINL 297#define SUN8I_ADDA_LADCMIXSC_OMIXRL 198#define SUN8I_ADDA_LADCMIXSC_OMIXRR 099#define SUN8I_ADDA_RADCMIXSC 0x0d100#define SUN8I_ADDA_RADCMIXSC_MIC1 6101#define SUN8I_ADDA_RADCMIXSC_MIC2 5102#define SUN8I_ADDA_RADCMIXSC_PHONE 4103#define SUN8I_ADDA_RADCMIXSC_PHONEP 3104#define SUN8I_ADDA_RADCMIXSC_LINEINR 2105#define SUN8I_ADDA_RADCMIXSC_OMIXR 1106#define SUN8I_ADDA_RADCMIXSC_OMIXL 0107#define SUN8I_ADDA_RES 0x0e108#define SUN8I_ADDA_RES_MMICBIAS_SEL 4109#define SUN8I_ADDA_RES_PA_ANTI_POP_CTRL 0110#define SUN8I_ADDA_ADC_AP_EN 0x0f111#define SUN8I_ADDA_ADC_AP_EN_ADCREN 7112#define SUN8I_ADDA_ADC_AP_EN_ADCLEN 6113#define SUN8I_ADDA_ADC_AP_EN_ADCG 0114 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 DAPM234 * contexts, we need to manually link the above widgets to their235 * 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 best354 * compromise between the time to let the amplifier up and the355 * 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 control698 * without MIC2 and Line In, for SoCs without these.699 * TODO: not all special cases are supported now, this case700 * 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 a743 * platform device matching this driver's name, without specifying a744 * 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");854