| /* |
| * aml_pmu3.c -- AML_PMU3 ALSA Soc Codec driver |
| * |
| * Copyright 2013 AMLOGIC. |
| * |
| * Author: Shuai Li<shuai.li@amlogic.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/completion.h> |
| #include <linux/delay.h> |
| #include <linux/pm.h> |
| #include <linux/i2c.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <sound/core.h> |
| #include <sound/jack.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/tlv.h> |
| #include <sound/soc.h> |
| #include <sound/initval.h> |
| #include <linux/amlogic/aml_pmu.h> |
| #include "aml_pmu3.h" |
| #include <linux/reboot.h> |
| #include <linux/notifier.h> |
| |
| |
| static u16 pmu3_reg_defaults[] = { |
| 0x0000, /* R00h - SW Reset */ |
| 0x0000, /* R01h - Block Enable1 */ |
| 0x0000, /* R02h - Block Enable2 */ |
| 0x0000, /* R03h - PGAIN */ |
| 0x0000, /* R04h - MIXINL */ |
| 0x0000, /* R05h - MIXINR */ |
| 0x0000, /* R06h - MiXOUTL */ |
| 0x0000, /* R07h - MiXOUTR */ |
| 0x0000, /* R08h - RXV TO MIXOUT */ |
| 0x0000, /* R09h - Lineout&HP Driver */ |
| 0x0000, /* R0Ah - HP DC Offset */ |
| 0x2000, /* R0Bh - ADC & DAC */ |
| 0x00A8, /* R0Ch - HP & MIC Detect */ |
| 0x5050, /* R0Dh - ADC Digital Volume */ |
| 0xC0C0, /* R0Eh - DAC Digital Volume */ |
| 0xA000, /* R0Fh - Soft Mute and Unmute */ |
| 0x0000, /* R10h - Digital Sidetone & Mixing */ |
| 0x0000, /* R11h - DMIC & GPIO_AUDIO */ |
| 0x0000, /* R12h - Monitor register */ |
| }; |
| |
| static void pmu3_reset(struct snd_soc_codec *codec) |
| { |
| snd_soc_write(codec, PMU3_SOFTWARE_RESET, 0x500); |
| msleep(20); |
| } |
| |
| static int pmu3_adc_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_codec *codec = w->dapm->component->codec; |
| u16 val = snd_soc_read(codec, PMU3_BLOCK_ENABLE_2); |
| |
| switch (event) { |
| case SND_SOC_DAPM_POST_PMU: |
| val |= 0x2; |
| break; |
| |
| case SND_SOC_DAPM_PRE_PMD: |
| val &= ~0x2; |
| break; |
| |
| default: |
| break; |
| } |
| |
| snd_soc_write(codec, PMU3_BLOCK_ENABLE_2, val); |
| |
| return 0; |
| } |
| |
| static int pmu3_dac_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_codec *codec = w->dapm->component->codec; |
| u16 val = snd_soc_read(codec, PMU3_BLOCK_ENABLE_2); |
| unsigned int mask = 1 << w->shift; |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| val |= 0x1; |
| val |= mask; |
| break; |
| |
| case SND_SOC_DAPM_POST_PMD: |
| /*val &= ~0x1;*/ |
| break; |
| |
| default: |
| break; |
| } |
| |
| snd_soc_write(codec, PMU3_BLOCK_ENABLE_2, val); |
| |
| return 0; |
| } |
| |
| static int pmu3_hp_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_codec *codec = w->dapm->component->codec; |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| break; |
| |
| case SND_SOC_DAPM_POST_PMU: |
| /* Enable the input stage of HP driver |
| * and start the HP DC offset cancellation process |
| */ |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x1800, 0x1800); |
| |
| msleep(20); |
| |
| /* Finish the HP DC offset cancellation process */ |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x1000, 0); |
| |
| /* Remove shorting the HP driver |
| * output and enable its output stage |
| */ |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x400, 0x400); |
| snd_soc_update_bits(codec, PMU3_LINEOUT_HP_DRV, 0x2, 0x2); |
| |
| break; |
| |
| case SND_SOC_DAPM_PRE_PMD: |
| snd_soc_update_bits(codec, PMU3_LINEOUT_HP_DRV, 0x2, 0x0); |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x400, 0x0); |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x1800, 0x0); |
| |
| break; |
| |
| case SND_SOC_DAPM_POST_PMD: |
| break; |
| |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static const DECLARE_TLV_DB_SCALE(out_pga_tlv, -4000, 200, 1); |
| static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); |
| static const DECLARE_TLV_DB_SCALE(adc_tlv, -3000, 37, 1); |
| static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 37, 1); |
| static const DECLARE_TLV_DB_SCALE(adc_svol_tlv, -3750, 250, 1); |
| |
| static const DECLARE_TLV_DB_SCALE(pga_in_tlv, -250, 250, 1); |
| static const DECLARE_TLV_DB_SCALE(pga2mixin_tlv, -1600, 150, 1); |
| static const DECLARE_TLV_DB_SCALE(xx2mixout_tlv, -5400, 200, 1); |
| static const DECLARE_TLV_DB_SCALE(xx2mixin_tlv, -1600, 150, 1); |
| |
| static const char * const mic_bias_level_txt[] = { |
| "0.72*CPAVDD25", "0.85*CPAVDD25" |
| }; |
| |
| /* ADC HPF Mode*/ |
| static const char * const adc_hpf_mode_txt[] = { |
| "Voice", "Hi-fi" }; |
| |
| static const char * const lr_txt[] = { |
| "Left", "Right" }; |
| |
| static const char * const rl_txt[] = { |
| "Right", "Left" }; |
| |
| static const char * const sidetone_txt[] = { |
| "Disabled", "Left ADC", "Right ADC" }; |
| |
| static const char * const dac_mute_rate_txt[] = { "Fast", "Slow" }; |
| |
| static const char * const pmu3_lol1_in_texts[] = { |
| "Off", "MIXOUTL", "MIXOUTR", "Inverted MIXOUTR" |
| }; |
| |
| static const char * const pmu3_lol2_in_texts[] = { |
| "Off", "MIXOUTL", "Inverted MIXOUTL", "Inverted MIXOUTR" |
| }; |
| |
| static const char * const pmu3_lor1_in_texts[] = { |
| "Off", "MIXOUTR", "MIXOUTL", "Inverted MIXOUTL" |
| }; |
| |
| static const char * const pmu3_lor2_in_texts[] = { |
| "Off", "MIXOUTR", "Inverted MIXOUTR", "Inverted MIXOUTL" |
| }; |
| |
| static const struct soc_enum pmu3_lineout_enum[] = { |
| SOC_ENUM_SINGLE(PMU3_LINEOUT_HP_DRV, 14, |
| ARRAY_SIZE(pmu3_lol1_in_texts), |
| pmu3_lol1_in_texts), |
| SOC_ENUM_SINGLE(PMU3_LINEOUT_HP_DRV, 12, |
| ARRAY_SIZE(pmu3_lol2_in_texts), |
| pmu3_lol2_in_texts), |
| SOC_ENUM_SINGLE(PMU3_LINEOUT_HP_DRV, 10, |
| ARRAY_SIZE(pmu3_lor1_in_texts), |
| pmu3_lor1_in_texts), |
| SOC_ENUM_SINGLE(PMU3_LINEOUT_HP_DRV, 8, |
| ARRAY_SIZE(pmu3_lor2_in_texts), |
| pmu3_lor2_in_texts), |
| }; |
| |
| static const SOC_ENUM_SINGLE_DECL( |
| mic_bias1_enum, PMU3_HP_MIC_DET, |
| PMU3_MIC_BIAS1_SHIFT, mic_bias_level_txt); |
| |
| static const SOC_ENUM_SINGLE_DECL( |
| mic_bias2_enum, PMU3_HP_MIC_DET, |
| PMU3_MIC_BIAS2_SHIFT, mic_bias_level_txt); |
| |
| static const SOC_ENUM_SINGLE_DECL( |
| adc_hpf_mode, PMU3_ADC_DAC, |
| PMU3_ADC_HPF_MODE_SHIFT, adc_hpf_mode_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| aifl_src, PMU3_SIDETONE_MIXING, |
| PMU3_ADCDATL_SRC_SHIFT, lr_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| aifr_src, PMU3_SIDETONE_MIXING, |
| PMU3_ADCDATR_SRC_SHIFT, rl_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| dacl_src, PMU3_SIDETONE_MIXING, |
| PMU3_DACDATL_SRC_SHIFT, lr_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| dacr_src, PMU3_SIDETONE_MIXING, |
| PMU3_DACDATR_SRC_SHIFT, rl_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| dacl_sidetone, PMU3_SIDETONE_MIXING, |
| PMU3_DACL_ST_SRC_SHIFT, sidetone_txt); |
| static const SOC_ENUM_SINGLE_DECL( |
| dacr_sidetone, PMU3_SIDETONE_MIXING, |
| PMU3_DACR_ST_SRC_SHIFT, sidetone_txt); |
| |
| static const SOC_ENUM_SINGLE_DECL( |
| dac_mute_rate, PMU3_SOFT_MUTE, |
| PMU3_DAC_RAMP_RATE_SHIFT, dac_mute_rate_txt); |
| |
| static const struct snd_kcontrol_new pmu3_snd_controls[] = { |
| /* MIC */ |
| SOC_ENUM("Mic Bias1 Level", mic_bias1_enum), |
| SOC_ENUM("Mic Bias2 Level", mic_bias2_enum), |
| |
| SOC_SINGLE_TLV("PGAIN Left Gain", PMU3_PGA_IN, 12, 0xf, 0, pga_in_tlv), |
| SOC_SINGLE_TLV("PGAIN Right Gain", PMU3_PGA_IN, 4, 0xf, 0, pga_in_tlv), |
| |
| /* ADC */ |
| SOC_SINGLE_TLV("Left ADC Sidetone Volume", |
| PMU3_SIDETONE_MIXING, 12, 0xf, 0, |
| adc_svol_tlv), |
| SOC_SINGLE_TLV("Right ADC Sidetone Volume", |
| PMU3_SIDETONE_MIXING, 8, 0xf, 0, |
| adc_svol_tlv), |
| |
| SOC_DOUBLE_TLV("Digital Capture Volume", |
| PMU3_ADC_VOLUME_CTL, 8, 0, 0x7f, 0, adc_tlv), |
| |
| SOC_SINGLE("ADC HPF Switch", PMU3_ADC_DAC, 11, 1, 0), |
| SOC_ENUM("ADC HPF Mode", adc_hpf_mode), |
| |
| SOC_ENUM("Left Digital Audio Source", aifl_src), |
| SOC_ENUM("Right Digital Audio Source", aifr_src), |
| SOC_SINGLE_TLV("DACL to MIXOUTL Volume", PMU3_MIXOUT_L, 11, 0x1f, 0, |
| xx2mixout_tlv), |
| SOC_SINGLE_TLV("DACR to MIXOUTR Volume", PMU3_MIXOUT_R, 11, 0x1f, 0, |
| xx2mixout_tlv), |
| |
| SOC_ENUM("DACL DATA Source", dacl_src), |
| SOC_ENUM("DACR DATA Source", dacr_src), |
| SOC_ENUM("DACL Sidetone Source", dacl_sidetone), |
| SOC_ENUM("DACR Sidetone Source", dacr_sidetone), |
| SOC_DOUBLE("DAC Invert Switch", PMU3_SOFT_MUTE, 8, 7, 1, 0), |
| SOC_DOUBLE("ADC Invert Switch", PMU3_SOFT_MUTE, 10, 9, 1, 0), |
| |
| SOC_SINGLE("DAC Soft Mute Switch", PMU3_SOFT_MUTE, 15, 1, 0), |
| SOC_ENUM("DAC Mute Rate", dac_mute_rate), |
| |
| SOC_DOUBLE_TLV("Digital Playback Volume", |
| PMU3_DAC_VOLUME_CTL, 8, 0, 0xff, 0, dac_tlv), |
| }; |
| |
| static const struct snd_kcontrol_new pmu3_dapm_mixer_out_l_controls[] = { |
| SOC_DAPM_SINGLE_TLV("DACL to MIXOUTL Volume", |
| PMU3_MIXOUT_L, 11, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("PGAINL to MIXOUTL Volume", |
| PMU3_MIXOUT_L, 6, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("PGAINR to MIXOUTL Volume", |
| PMU3_MIXOUT_L, 1, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("RXV to MIXOUTL Volume", |
| PMU3_RXV_TO_MIXOUT, 11, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE("DACR Mono Switch", |
| PMU3_SIDETONE_MIXING, 5, 1, 0), |
| }; |
| |
| static const struct snd_kcontrol_new pmu3_dapm_mixer_out_r_controls[] = { |
| SOC_DAPM_SINGLE_TLV("DACR to MIXOUTR Volume", |
| PMU3_MIXOUT_R, 11, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("PGAINR to MIXOUTR Volume", |
| PMU3_MIXOUT_R, 6, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("PGAINL to MIXOUTR Volume", |
| PMU3_MIXOUT_R, 1, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE_TLV("RXV to MIXOUTR Volume", |
| PMU3_RXV_TO_MIXOUT, 6, 0x1f, 0, xx2mixout_tlv), |
| SOC_DAPM_SINGLE("DACL Mono Switch", |
| PMU3_SIDETONE_MIXING, 5, 1, 0), |
| }; |
| |
| static const struct snd_kcontrol_new pmu3_dapm_mixer_in_l_controls[] = { |
| SOC_DAPM_SINGLE_TLV("PGAINL to MIXINL Volume", PMU3_MIXIN_L, 12, 0xf, 0, |
| xx2mixin_tlv), |
| SOC_DAPM_SINGLE_TLV("RXV to MIXINL Volume", PMU3_MIXIN_L, 8, 0xf, 0, |
| xx2mixin_tlv), |
| SOC_DAPM_SINGLE_TLV("RECL to MIXINL Volume", PMU3_MIXIN_L, 4, 0xf, 0, |
| xx2mixin_tlv), |
| }; |
| static const struct snd_kcontrol_new pmu3_dapm_mixer_in_r_controls[] = { |
| SOC_DAPM_SINGLE_TLV("PGAINR to MIXINR Volume", PMU3_MIXIN_R, 12, 0xf, 0, |
| xx2mixin_tlv), |
| SOC_DAPM_SINGLE_TLV("RXV to MIXINR Volume", PMU3_MIXIN_R, 8, 0xf, 0, |
| xx2mixin_tlv), |
| SOC_DAPM_SINGLE_TLV("RECR to MIXINR Volume", PMU3_MIXIN_R, 4, 0xf, 0, |
| xx2mixin_tlv), |
| }; |
| |
| static const struct snd_kcontrol_new lol1_mux_controls = |
| SOC_DAPM_ENUM("Route", pmu3_lineout_enum[0]); |
| |
| static const struct snd_kcontrol_new lol2_mux_controls = |
| SOC_DAPM_ENUM("Route", pmu3_lineout_enum[1]); |
| |
| static const struct snd_kcontrol_new lor1_mux_controls = |
| SOC_DAPM_ENUM("Route", pmu3_lineout_enum[2]); |
| |
| static const struct snd_kcontrol_new lor2_mux_controls = |
| SOC_DAPM_ENUM("Route", pmu3_lineout_enum[3]); |
| |
| static const struct snd_kcontrol_new pmu3_dapm_hpin_mixer_l_controls[] = { |
| SOC_DAPM_SINGLE("DACL Switch", PMU3_LINEOUT_HP_DRV, 7, 1, 0), |
| SOC_DAPM_SINGLE("MIXOUTL Switch", PMU3_LINEOUT_HP_DRV, 6, 1, 0), |
| SOC_DAPM_SINGLE("MIXOUTR Switch", PMU3_LINEOUT_HP_DRV, 5, 1, 0), |
| }; |
| |
| static const struct snd_kcontrol_new pmu3_dapm_hpin_mixer_r_controls[] = { |
| SOC_DAPM_SINGLE("DACR Switch", PMU3_LINEOUT_HP_DRV, 4, 1, 0), |
| SOC_DAPM_SINGLE("MIXOUTR Switch", PMU3_LINEOUT_HP_DRV, 3, 1, 0), |
| SOC_DAPM_SINGLE("MIXOUTL Switch", PMU3_LINEOUT_HP_DRV, 2, 1, 0), |
| }; |
| |
| static const struct snd_kcontrol_new pgain_lp_switch_controls = |
| SOC_DAPM_SINGLE("Switch", PMU3_PGA_IN, 10, 1, 0); |
| |
| static const struct snd_kcontrol_new pgain_rp_switch_controls = |
| SOC_DAPM_SINGLE("Switch", PMU3_PGA_IN, 2, 1, 0); |
| |
| static const char * const plin_text[] = { "None", "AINL2", "AINL1" }; |
| static const struct soc_enum pgainl_enum = |
| SOC_ENUM_SINGLE(PMU3_PGA_IN, 8, ARRAY_SIZE(plin_text), plin_text); |
| static const struct snd_kcontrol_new pgain_ln_mux[] = { |
| SOC_DAPM_ENUM("route", pgainl_enum), |
| }; |
| static const char * const prin_text[] = { "None", "AINR2", "AINR1" }; |
| static const struct soc_enum pgainr_enum = |
| SOC_ENUM_SINGLE(PMU3_PGA_IN, 0, ARRAY_SIZE(prin_text), prin_text); |
| static const struct snd_kcontrol_new pgain_rn_mux[] = { |
| SOC_DAPM_ENUM("route", pgainr_enum), |
| }; |
| |
| static const struct snd_soc_dapm_widget pmu3_dapm_widgets[] = { |
| /* Externally visible pins */ |
| SND_SOC_DAPM_OUTPUT("LINEOUTL1"), |
| SND_SOC_DAPM_OUTPUT("LINEOUTR1"), |
| SND_SOC_DAPM_OUTPUT("LINEOUTL2"), |
| SND_SOC_DAPM_OUTPUT("LINEOUTR2"), |
| SND_SOC_DAPM_OUTPUT("HP_L"), |
| SND_SOC_DAPM_OUTPUT("HP_R"), |
| /* SND_SOC_DAPM_OUTPUT("AUXOUT"), */ |
| /* SND_SOC_DAPM_INPUT("RXV"), */ |
| |
| SND_SOC_DAPM_INPUT("LINEINLP"), |
| SND_SOC_DAPM_INPUT("LINEINLN1"), |
| SND_SOC_DAPM_INPUT("LINEINLN2"), |
| SND_SOC_DAPM_INPUT("LINEINRP"), |
| SND_SOC_DAPM_INPUT("LINEINRN1"), |
| SND_SOC_DAPM_INPUT("LINEINRN2"), |
| |
| /* Input */ |
| SND_SOC_DAPM_PGA("PGAIN LEFT", PMU3_BLOCK_ENABLE_1, 5, 0, NULL, 0), |
| SND_SOC_DAPM_PGA("PGAIN RIGHT", PMU3_BLOCK_ENABLE_1, 4, 0, NULL, 0), |
| SND_SOC_DAPM_SWITCH("PGAIN LEFT Pos", SND_SOC_NOPM, 0, 0, |
| &pgain_lp_switch_controls), |
| SND_SOC_DAPM_SWITCH("PGAIN RIGHT Pos", SND_SOC_NOPM, 0, 0, |
| &pgain_rp_switch_controls), |
| SND_SOC_DAPM_MUX("PGAIN LEFT Neg", SND_SOC_NOPM, 0, 0, pgain_ln_mux), |
| SND_SOC_DAPM_MUX("PGAIN RIGHT Neg", SND_SOC_NOPM, 0, 0, pgain_rn_mux), |
| |
| SND_SOC_DAPM_MIXER("MIXINL", PMU3_BLOCK_ENABLE_1, 7, 0, |
| &pmu3_dapm_mixer_in_l_controls[0], |
| ARRAY_SIZE(pmu3_dapm_mixer_in_l_controls)), |
| SND_SOC_DAPM_MIXER("MIXINR", PMU3_BLOCK_ENABLE_1, 6, 0, |
| &pmu3_dapm_mixer_in_r_controls[0], |
| ARRAY_SIZE(pmu3_dapm_mixer_in_r_controls)), |
| |
| SND_SOC_DAPM_MICBIAS("Mic Bias1", PMU3_BLOCK_ENABLE_2, 15, 0), |
| SND_SOC_DAPM_MICBIAS("Mic Bias2", PMU3_BLOCK_ENABLE_2, 14, 0), |
| |
| SND_SOC_DAPM_ADC_E("ADCL", "Capture", PMU3_BLOCK_ENABLE_2, 5, 0, |
| pmu3_adc_event, |
| SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), |
| SND_SOC_DAPM_ADC_E("ADCR", "Capture", PMU3_BLOCK_ENABLE_2, 4, 0, |
| pmu3_adc_event, |
| SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), |
| |
| /* Output */ |
| SND_SOC_DAPM_DAC_E("DACL", "Playback", SND_SOC_NOPM, 3, 0, |
| pmu3_dac_event, |
| SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), |
| SND_SOC_DAPM_DAC_E("DACR", "Playback", SND_SOC_NOPM, 2, 0, |
| pmu3_dac_event, |
| SND_SOC_DAPM_PRE_PMU|SND_SOC_DAPM_POST_PMD), |
| |
| SND_SOC_DAPM_MIXER("MIXOUTL", PMU3_BLOCK_ENABLE_1, 3, 0, |
| &pmu3_dapm_mixer_out_l_controls[0], |
| ARRAY_SIZE(pmu3_dapm_mixer_out_l_controls)), |
| SND_SOC_DAPM_MIXER("MIXOUTR", PMU3_BLOCK_ENABLE_1, 2, 0, |
| &pmu3_dapm_mixer_out_r_controls[0], |
| ARRAY_SIZE(pmu3_dapm_mixer_out_r_controls)), |
| |
| SND_SOC_DAPM_MUX("Lineout1 Left in", |
| SND_SOC_NOPM, 0, 0, &lol1_mux_controls), |
| SND_SOC_DAPM_MUX("Lineout2 Left in", |
| SND_SOC_NOPM, 0, 0, &lol2_mux_controls), |
| SND_SOC_DAPM_MUX("Lineout1 Right in", |
| SND_SOC_NOPM, 0, 0, &lor1_mux_controls), |
| SND_SOC_DAPM_MUX("Lineout2 Right in", |
| SND_SOC_NOPM, 0, 0, &lor2_mux_controls), |
| |
| SND_SOC_DAPM_MIXER("HPINL Mixer", SND_SOC_NOPM, 0, 0, |
| &pmu3_dapm_hpin_mixer_l_controls[0], |
| ARRAY_SIZE(pmu3_dapm_hpin_mixer_l_controls)), |
| |
| SND_SOC_DAPM_MIXER("HPINR Mixer", SND_SOC_NOPM, 0, 0, |
| &pmu3_dapm_hpin_mixer_r_controls[0], |
| ARRAY_SIZE(pmu3_dapm_hpin_mixer_r_controls)), |
| |
| SND_SOC_DAPM_OUT_DRV("LINEOUT1 Left driver", |
| PMU3_BLOCK_ENABLE_1, 11, 0, NULL, 0), |
| SND_SOC_DAPM_OUT_DRV("LINEOUT2 Left driver", |
| PMU3_BLOCK_ENABLE_1, 10, 0, NULL, 0), |
| SND_SOC_DAPM_OUT_DRV("LINEOUT1 Right driver", |
| PMU3_BLOCK_ENABLE_1, 9, 0, NULL, 0), |
| SND_SOC_DAPM_OUT_DRV("LINEOUT2 Right driver", |
| PMU3_BLOCK_ENABLE_1, 8, 0, NULL, 0), |
| |
| SND_SOC_DAPM_PGA_E("Headphone Amplifier", |
| PMU3_BLOCK_ENABLE_2, 13, 0, NULL, 0, |
| pmu3_hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), |
| SND_SOC_DAPM_PGA("RXV", SND_SOC_NOPM, 8, 0, NULL, 0), |
| }; |
| |
| static const struct snd_soc_dapm_route pmu3_intercon[] = { |
| /* Inputs */ |
| {"PGAIN LEFT Pos", "Switch", "LINEINLP"}, |
| {"PGAIN LEFT Neg", "AINL1", "LINEINLN1"}, |
| {"PGAIN LEFT Neg", "AINL2", "LINEINLN2"}, |
| |
| {"PGAIN RIGHT Pos", "Switch", "LINEINRP"}, |
| {"PGAIN RIGHT Neg", "AINR1", "LINEINRN1"}, |
| {"PGAIN RIGHT Neg", "AINR2", "LINEINRN2"}, |
| |
| {"PGAIN LEFT", NULL, "PGAIN LEFT Pos"}, |
| {"PGAIN LEFT", NULL, "PGAIN LEFT Neg"}, |
| |
| {"PGAIN RIGHT", NULL, "PGAIN RIGHT Pos"}, |
| {"PGAIN RIGHT", NULL, "PGAIN RIGHT Neg"}, |
| |
| {"RXV", NULL, "LINEINLN2"}, |
| {"RXV", NULL, "LINEINRN2"}, |
| |
| {"MIXINL", "PGAINL to MIXINL Volume", "PGAIN LEFT"}, |
| {"MIXINL", "RXV to MIXINL Volume", "RXV"}, |
| {"MIXINL", "RECL to MIXINL Volume", "MIXOUTL"}, |
| |
| {"MIXINR", "PGAINR to MIXINR Volume", "PGAIN RIGHT"}, |
| {"MIXINR", "RXV to MIXINR Volume", "RXV"}, |
| {"MIXINR", "RECR to MIXINR Volume", "MIXOUTR"}, |
| |
| {"ADCL", NULL, "MIXINL"}, |
| {"ADCR", NULL, "MIXINR"}, |
| |
| /* Outputs */ |
| /* LINEOUTL1 */ |
| {"LINEOUTL1", NULL, "LINEOUT1 Left driver"}, |
| {"LINEOUT1 Left driver", NULL, "Lineout1 Left in"}, |
| |
| {"Lineout1 Left in", "MIXOUTL", "MIXOUTL"}, |
| {"Lineout1 Left in", "MIXOUTR", "MIXOUTR"}, |
| {"Lineout1 Left in", "Inverted MIXOUTR", "MIXOUTR"}, |
| |
| /* LINEOUTR1 */ |
| {"LINEOUTR1", NULL, "LINEOUT1 Right driver"}, |
| {"LINEOUT1 Right driver", NULL, "Lineout1 Right in"}, |
| |
| {"Lineout1 Right in", "MIXOUTR", "MIXOUTL"}, |
| {"Lineout1 Right in", "MIXOUTL", "MIXOUTR"}, |
| {"Lineout1 Right in", "Inverted MIXOUTL", "MIXOUTL"}, |
| |
| /* LINEOUTL2 */ |
| {"LINEOUTL2", NULL, "LINEOUT2 Left driver"}, |
| {"LINEOUT2 Left driver", NULL, "Lineout2 Left in"}, |
| |
| {"Lineout2 Left in", "MIXOUTL", "MIXOUTL"}, |
| {"Lineout2 Left in", "Inverted MIXOUTL", "MIXOUTL"}, |
| {"Lineout2 Left in", "Inverted MIXOUTR", "MIXOUTR"}, |
| |
| /* LINEOUTR2 */ |
| {"LINEOUTR2", NULL, "LINEOUT2 Right driver"}, |
| {"LINEOUT2 Right driver", NULL, "Lineout2 Right in"}, |
| |
| {"Lineout2 Right in", "MIXOUTR", "MIXOUTR"}, |
| {"Lineout2 Right in", "Inverted MIXOUTR", "MIXOUTR"}, |
| {"Lineout2 Right in", "Inverted MIXOUTL", "MIXOUTL"}, |
| |
| /* MIXOUT */ |
| {"MIXOUTL", "DACL to MIXOUTL Volume", "DACL"}, |
| {"MIXOUTL", "PGAINL to MIXOUTL Volume", "PGAIN LEFT"}, |
| {"MIXOUTL", "PGAINR to MIXOUTL Volume", "PGAIN RIGHT"}, |
| {"MIXOUTL", "RXV to MIXOUTL Volume", "RXV"}, |
| {"MIXOUTL", "DACR Mono Switch", "DACR"}, |
| |
| {"MIXOUTR", "DACR to MIXOUTR Volume", "DACR"}, |
| {"MIXOUTR", "PGAINR to MIXOUTR Volume", "PGAIN RIGHT"}, |
| {"MIXOUTR", "PGAINL to MIXOUTR Volume", "PGAIN LEFT"}, |
| {"MIXOUTR", "RXV to MIXOUTR Volume", "RXV"}, |
| {"MIXOUTR", "DACL Mono Switch", "DACL"}, |
| |
| /* Headphone */ |
| |
| {"HPINL Mixer", "DACL Switch", "DACL"}, |
| {"HPINL Mixer", "MIXOUTL Switch", "MIXOUTL"}, |
| {"HPINL Mixer", "MIXOUTR Switch", "MIXOUTR"}, |
| |
| {"HPINR Mixer", "DACR Switch", "DACR"}, |
| {"HPINR Mixer", "MIXOUTR Switch", "MIXOUTR"}, |
| {"HPINR Mixer", "MIXOUTL Switch", "MIXOUTL"}, |
| |
| {"Headphone Amplifier", NULL, "HPINL Mixer"}, |
| {"Headphone Amplifier", NULL, "HPINR Mixer"}, |
| {"HP_L", NULL, "Headphone Amplifier"}, |
| {"HP_R", NULL, "Headphone Amplifier"}, |
| }; |
| |
| static int pmu3_set_bias_level(struct snd_soc_codec *codec, |
| enum snd_soc_bias_level level) |
| { |
| int value = 0; |
| |
| switch (level) { |
| case SND_SOC_BIAS_ON: |
| break; |
| |
| case SND_SOC_BIAS_PREPARE: |
| if (codec->component.dapm.bias_level == |
| SND_SOC_BIAS_STANDBY) { |
| value = snd_soc_read(codec, |
| PMU3_SIDETONE_MIXING); |
| if (value & 0x20) |
| snd_soc_write(codec, |
| PMU3_BLOCK_ENABLE_1, 0xbc00); |
| else |
| snd_soc_write(codec, |
| PMU3_BLOCK_ENABLE_1, 0xbf00); |
| } |
| break; |
| |
| case SND_SOC_BIAS_STANDBY: |
| if (codec->component.dapm.bias_level == SND_SOC_BIAS_OFF) { |
| /* VMID Gen & Bias Current & Refp Buf */ |
| snd_soc_update_bits(codec, |
| PMU3_BLOCK_ENABLE_1, |
| 0xf000, 0xf000); |
| msleep(200); |
| /* Clear Fast Charge */ |
| snd_soc_update_bits(codec, |
| PMU3_BLOCK_ENABLE_1, |
| 0x4000, 0x0); |
| value = snd_soc_read(codec, |
| PMU3_SIDETONE_MIXING); |
| if (value & 0x20) |
| snd_soc_write(codec, |
| PMU3_BLOCK_ENABLE_1, 0xbc00); |
| else |
| snd_soc_write(codec, |
| PMU3_BLOCK_ENABLE_1, 0xbf00); |
| } |
| break; |
| |
| case SND_SOC_BIAS_OFF: |
| snd_soc_write(codec, PMU3_BLOCK_ENABLE_1, 0); |
| |
| break; |
| } |
| |
| codec->component.dapm.bias_level = level; |
| |
| return 0; |
| } |
| |
| static int pmu3_set_dai_sysclk(struct snd_soc_dai *codec_dai, |
| int clk_id, unsigned int freq, int dir) |
| { |
| return 0; |
| } |
| |
| |
| static int pmu3_set_dai_fmt(struct snd_soc_dai *codec_dai, |
| unsigned int fmt) |
| { |
| struct snd_soc_codec *codec = codec_dai->codec; |
| u16 iface = 0; |
| |
| iface = snd_soc_read(codec, PMU3_ADC_DAC); |
| /* set master/slave audio interface */ |
| switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
| case SND_SOC_DAIFMT_CBM_CFM: |
| iface |= (0x1 << 14); |
| break; |
| case SND_SOC_DAIFMT_CBS_CFS: |
| break; |
| } |
| |
| snd_soc_write(codec, PMU3_ADC_DAC, iface); |
| |
| return 0; |
| } |
| |
| static int pmu3_digital_mute(struct snd_soc_dai *codec_dai, int mute) |
| { |
| struct snd_soc_codec *codec = codec_dai->codec; |
| u16 reg; |
| |
| reg = snd_soc_read(codec, PMU3_SOFT_MUTE); |
| |
| if (mute) |
| reg |= 0x8000; |
| else |
| reg &= ~0x8000; |
| |
| snd_soc_write(codec, PMU3_SOFT_MUTE, reg); |
| |
| return 0; |
| } |
| |
| static int pmu3_mute_stream(struct snd_soc_dai *dai, int mute, int stream) |
| { |
| struct snd_soc_codec *codec = dai->codec; |
| u16 reg; |
| |
| if (stream == SNDRV_PCM_STREAM_PLAYBACK) { |
| reg = snd_soc_read(codec, PMU3_SOFT_MUTE); |
| if (mute) |
| reg |= 0x8000; |
| else |
| reg &= ~0x8000; |
| snd_soc_write(codec, PMU3_SOFT_MUTE, reg); |
| } |
| if (stream == SNDRV_PCM_STREAM_CAPTURE) { |
| if (mute) |
| snd_soc_write(codec, PMU3_ADC_VOLUME_CTL, 0); |
| else { |
| msleep(300); |
| snd_soc_write(codec, PMU3_ADC_VOLUME_CTL, 0x6a6a); |
| } |
| } |
| return 0; |
| } |
| |
| |
| static struct { |
| int rate; |
| int value; |
| } sample_rates[] = { |
| { 8000, 0 }, |
| { 11025, 1 }, |
| { 12000, 2 }, |
| { 16000, 3 }, |
| { 22050, 4 }, |
| { 24000, 5 }, |
| { 32000, 6 }, |
| { 44100, 7 }, |
| { 48000, 8 }, |
| }; |
| |
| static int pmu3_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_codec *codec = rtd->codec; |
| int fs = params_rate(params); |
| u16 reg = 0; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(sample_rates); i++) { |
| if (sample_rates[i].rate == fs) |
| reg = sample_rates[i].value; |
| } |
| pr_info("pmu3_hw_params(rate)%x\n", reg); |
| if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) |
| snd_soc_update_bits(codec, PMU3_ADC_DAC, 0xf0, reg << 4); |
| else |
| snd_soc_update_bits(codec, PMU3_ADC_DAC, 0xf, reg); |
| |
| return 0; |
| } |
| |
| #define AML_PMU3_PLAYBACK_RATES SNDRV_PCM_RATE_8000_48000 |
| #define AML_PMU3_CAPTURE_RATES SNDRV_PCM_RATE_8000_48000 |
| #define AML_PMU3_FORMATS (SNDRV_PCM_FMTBIT_S16_LE \ |
| | SNDRV_PCM_FMTBIT_S24_LE) |
| |
| static struct snd_soc_dai_ops pmu3_dai_ops = { |
| .hw_params = pmu3_hw_params, |
| .digital_mute = pmu3_digital_mute, |
| .mute_stream = pmu3_mute_stream, |
| .set_fmt = pmu3_set_dai_fmt, |
| .set_sysclk = pmu3_set_dai_sysclk, |
| }; |
| |
| static struct snd_soc_dai_driver pmu3_dai = { |
| .name = "pmu3-hifi", |
| .playback = { |
| .stream_name = "Playback", |
| .channels_min = 2, |
| .channels_max = 2, |
| .rates = AML_PMU3_PLAYBACK_RATES, |
| .formats = AML_PMU3_FORMATS, |
| }, |
| .capture = { |
| .stream_name = "Capture", |
| .channels_min = 2, |
| .channels_max = 2, |
| .rates = AML_PMU3_CAPTURE_RATES, |
| .formats = AML_PMU3_FORMATS, |
| }, |
| .ops = &pmu3_dai_ops, |
| .symmetric_rates = 1, |
| }; |
| |
| static int pmu3_suspend(struct snd_soc_codec *codec) |
| { |
| pmu3_set_bias_level(codec, SND_SOC_BIAS_OFF); |
| |
| return 0; |
| } |
| |
| static int pmu3_resume(struct snd_soc_codec *codec) |
| { |
| snd_soc_cache_sync(codec); |
| |
| /* Bring the codec back up to standby |
| * first to minimise pop/clicks |
| */ |
| pmu3_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
| |
| return 0; |
| } |
| static int pmu3_audio_power_init(struct snd_soc_codec *codec) |
| { |
| uint8_t val = 0; |
| |
| // LDO1v8 for audio |
| aml1218_read(0x010e, &val); |
| val |= 0x1; |
| aml1218_write(0x010e, val); |
| // LDO2v5 for audio |
| aml1218_write(0x0139, 0x1f); |
| val = 0; |
| aml1218_read(0x010f, &val); |
| val |= 0x1; |
| aml1218_write(0x010f, val); |
| |
| return 0; |
| } |
| |
| static int pmu3_write(struct snd_soc_codec *codec, unsigned int reg, |
| unsigned int value) |
| { |
| uint32_t addr; |
| |
| addr = PMU3_AUDIO_BASE + (reg<<1); |
| pr_info("pmu3_write,addr=0x%x, reg=0x%x, value=0x%x\n", |
| addr, |
| reg, |
| value); |
| aml1218_write16(addr, value); |
| |
| return 0; |
| } |
| |
| static unsigned int pmu3_read(struct snd_soc_codec *codec, |
| unsigned int reg) |
| { |
| uint32_t addr; |
| uint16_t val = 0; |
| |
| addr = PMU3_AUDIO_BASE + (reg<<1); |
| aml1218_read16(addr, &val); |
| |
| return val; |
| } |
| |
| |
| static int pmu3_probe(struct snd_soc_codec *codec) |
| { |
| int ret = 0; |
| |
| pr_info("aml pmu3 codec probe\n"); |
| pmu3_audio_power_init(codec); |
| pmu3_reset(codec); |
| /* power on device */ |
| pmu3_set_bias_level(codec, SND_SOC_BIAS_STANDBY); |
| |
| /* Enable DAC soft mute by default */ |
| snd_soc_update_bits(codec, PMU3_SOFT_MUTE, 0x7800, 0x7800); |
| |
| /* Enable ZC */ |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0x3c0, 0x0); |
| |
| snd_soc_add_codec_controls(codec, pmu3_snd_controls, |
| ARRAY_SIZE(pmu3_snd_controls)); |
| /* ADC high pass filter Hi-fi mode */ |
| snd_soc_update_bits(codec, PMU3_ADC_DAC, 0xc00, 0xc00); |
| |
| snd_soc_write(codec, PMU3_MIXOUT_L, 0xe000); |
| snd_soc_write(codec, PMU3_MIXOUT_R, 0xe000); |
| snd_soc_write(codec, PMU3_MIXIN_L, 0xf000); |
| snd_soc_write(codec, PMU3_MIXIN_R, 0xf000); |
| snd_soc_write(codec, PMU3_PGA_IN, 0x1616); |
| |
| |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_1, 0xf0c, 0xf0c); |
| snd_soc_update_bits(codec, PMU3_BLOCK_ENABLE_2, 0xf, 0xf); |
| |
| snd_soc_update_bits(codec, PMU3_LINEOUT_HP_DRV, 0x90, 0x90); |
| snd_soc_write(codec, PMU3_DAC_VOLUME_CTL, 0xb9b9); |
| snd_soc_write(codec, PMU3_ADC_VOLUME_CTL, 0x6a6a); |
| |
| return ret; |
| } |
| |
| /* power down chip */ |
| static int pmu3_remove(struct snd_soc_codec *codec) |
| { |
| pmu3_set_bias_level(codec, SND_SOC_BIAS_OFF); |
| return 0; |
| } |
| |
| static struct snd_soc_codec_driver soc_codec_dev_pmu3 = { |
| .probe = pmu3_probe, |
| .remove = pmu3_remove, |
| .suspend = pmu3_suspend, |
| .resume = pmu3_resume, |
| .read = pmu3_read, |
| .write = pmu3_write, |
| .set_bias_level = pmu3_set_bias_level, |
| .reg_cache_size = ARRAY_SIZE(pmu3_reg_defaults), |
| .reg_word_size = sizeof(u16), |
| .reg_cache_step = 1, |
| .reg_cache_default = pmu3_reg_defaults, |
| .component_driver = { |
| .dapm_widgets = pmu3_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(pmu3_dapm_widgets), |
| .dapm_routes = pmu3_intercon, |
| .num_dapm_routes = ARRAY_SIZE(pmu3_intercon), |
| } |
| }; |
| |
| |
| static int pmu3_audio_codec_mute(void) |
| { |
| uint32_t addr; |
| unsigned int value = 0x8000; |
| unsigned int reg = PMU3_SOFT_MUTE; |
| |
| pr_info("pmu3_audio_codec_mute\n"); |
| |
| addr = PMU3_AUDIO_BASE + (reg<<1); |
| |
| aml1218_write16(addr, value); |
| |
| return 0; |
| } |
| |
| |
| |
| static int aml_pmu3_audio_reboot_work(struct notifier_block *nb, |
| unsigned long state, void *cmd) |
| { |
| pr_info("\n%s\n", __func__); |
| |
| pmu3_audio_codec_mute(); |
| |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block aml_pmu3_audio_reboot_nb = { |
| .notifier_call = aml_pmu3_audio_reboot_work, |
| .priority = 0, |
| }; |
| |
| static int aml_pmu3_codec_probe(struct platform_device *pdev) |
| { |
| int ret = snd_soc_register_codec(&pdev->dev, |
| &soc_codec_dev_pmu3, &pmu3_dai, 1); |
| |
| register_reboot_notifier(&aml_pmu3_audio_reboot_nb); |
| |
| return ret; |
| } |
| |
| |
| |
| static int aml_pmu3_codec_remove(struct platform_device *pdev) |
| { |
| snd_soc_unregister_codec(&pdev->dev); |
| return 0; |
| } |
| |
| #ifdef CONFIG_USE_OF |
| static const struct of_device_id amlogic_pmu3_codec_dt_match[] = { |
| { .compatible = "amlogic, aml_pmu3_codec", }, |
| {}, |
| }; |
| #else |
| #define amlogic_audio_dt_match NULL |
| #endif |
| static struct platform_driver aml_pmu3_codec_platform_driver = { |
| .driver = { |
| .name = "aml_pmu3_codec", |
| .owner = THIS_MODULE, |
| .of_match_table = amlogic_pmu3_codec_dt_match, |
| }, |
| .probe = aml_pmu3_codec_probe, |
| .remove = aml_pmu3_codec_remove, |
| }; |
| |
| static int __init aml_pmu3_modinit(void) |
| { |
| int ret = 0; |
| |
| ret = platform_driver_register(&aml_pmu3_codec_platform_driver); |
| if (ret != 0) { |
| pr_err("Failed to register AML PMU3 codec platform driver: %d\n", |
| ret); |
| } |
| |
| return ret; |
| } |
| module_init(aml_pmu3_modinit); |
| |
| static void __exit aml_pmu3_exit(void) |
| { |
| platform_driver_unregister(&aml_pmu3_codec_platform_driver); |
| } |
| module_exit(aml_pmu3_exit); |
| |
| MODULE_DESCRIPTION("ASoC AML_PUM3 driver"); |
| MODULE_AUTHOR("Shuai Li <Shuai.li@amlogic.com>"); |
| MODULE_LICENSE("GPL"); |
| |