diff options
author | Xinyu Chen <xinyu.chen@freescale.com> | 2012-04-09 13:34:16 +0800 |
---|---|---|
committer | Xinyu Chen <xinyu.chen@freescale.com> | 2012-04-09 13:34:16 +0800 |
commit | 52c5341f1302a0b328e7dd5890c12729406256fc (patch) | |
tree | 687adeb27a395f512c2a0fa373bd173c437f9321 /sound | |
parent | bee5f57f1e3e5174aa5390a330052e772afdc453 (diff) | |
parent | 3ec8c998827be5729ff98a0d7c097f7ad9ddbc6e (diff) |
Merge remote branch 'fsl-linux-sdk/imx_3.0.15' into imx_3.0.15_android
Diffstat (limited to 'sound')
-rw-r--r-- | sound/soc/codecs/wm8962.c | 404 | ||||
-rw-r--r-- | sound/soc/imx/imx-cs42888.c | 131 | ||||
-rw-r--r-- | sound/soc/imx/imx-esai.h | 41 | ||||
-rw-r--r-- | sound/soc/imx/imx-pcm-dma-mx2.c | 211 | ||||
-rw-r--r-- | sound/soc/imx/imx-pcm.h | 70 | ||||
-rw-r--r-- | sound/soc/imx/imx-wm8962.c | 254 |
6 files changed, 926 insertions, 185 deletions
diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 7b14746d9336..b8be06b3344a 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -63,6 +63,8 @@ struct wm8962_priv { int fll_fref; int fll_fout; + u16 dsp2_ena; + struct delayed_work mic_work; struct snd_soc_jack *jack; @@ -78,6 +80,8 @@ struct wm8962_priv { #ifdef CONFIG_GPIOLIB struct gpio_chip gpio_chip; #endif + + int irq; }; /* We can't use the same notifier block for more than one supply and @@ -835,7 +839,7 @@ static const struct wm8962_reg_access { [40] = { 0x00FF, 0x01FF, 0x0000 }, /* R40 - SPKOUTL volume */ [41] = { 0x00FF, 0x01FF, 0x0000 }, /* R41 - SPKOUTR volume */ - [47] = { 0x000F, 0x0000, 0x0000 }, /* R47 - Thermal Shutdown Status */ + [47] = { 0x000F, 0x0000, 0xFFFF }, /* R47 - Thermal Shutdown Status*/ [48] = { 0x7EC7, 0x7E07, 0xFFFF }, /* R48 - Additional Control (4) */ [49] = { 0x00D3, 0x00D7, 0xFFFF }, /* R49 - Class D Control 1 */ [51] = { 0x0047, 0x0047, 0x0000 }, /* R51 - Class D Control 2 */ @@ -963,7 +967,7 @@ static const struct wm8962_reg_access { [584] = { 0x002D, 0x002D, 0x0000 }, /* R584 - IRQ Debounce */ [586] = { 0xC000, 0xC000, 0x0000 }, /* R586 - MICINT Source Pol */ [768] = { 0x0001, 0x0001, 0x0000 }, /* R768 - DSP2 Power Management */ - [1037] = { 0x0000, 0x003F, 0x0000 }, /* R1037 - DSP2_ExecControl */ + [1037] = { 0x0000, 0x003F, 0xFFFF }, /* R1037 - DSP2_ExecControl */ [4096] = { 0x3FFF, 0x3FFF, 0x0000 }, /* R4096 - Write Sequencer 0 */ [4097] = { 0x00FF, 0x00FF, 0x0000 }, /* R4097 - Write Sequencer 1 */ [4098] = { 0x070F, 0x070F, 0x0000 }, /* R4098 - Write Sequencer 2 */ @@ -1969,7 +1973,7 @@ static int wm8962_reset(struct snd_soc_codec *codec) static const DECLARE_TLV_DB_SCALE(inpga_tlv, -2325, 75, 0); static const DECLARE_TLV_DB_SCALE(mixin_tlv, -1500, 300, 0); static const unsigned int mixinpga_tlv[] = { - TLV_DB_RANGE_HEAD(7), + TLV_DB_RANGE_HEAD(5), 0, 1, TLV_DB_SCALE_ITEM(0, 600, 0), 2, 2, TLV_DB_SCALE_ITEM(1300, 1300, 0), 3, 4, TLV_DB_SCALE_ITEM(1800, 200, 0), @@ -1984,10 +1988,127 @@ static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); static const DECLARE_TLV_DB_SCALE(hp_tlv, -700, 100, 0); static const unsigned int classd_tlv[] = { - TLV_DB_RANGE_HEAD(7), + TLV_DB_RANGE_HEAD(2), 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), }; +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static int wm8962_dsp2_write_config(struct snd_soc_codec *codec) +{ + return 0; +} + +static int wm8962_dsp2_set_enable(struct snd_soc_codec *codec, u16 val) +{ + u16 adcl = snd_soc_read(codec, WM8962_LEFT_ADC_VOLUME); + u16 adcr = snd_soc_read(codec, WM8962_RIGHT_ADC_VOLUME); + u16 dac = snd_soc_read(codec, WM8962_ADC_DAC_CONTROL_1); + + /* Mute the ADCs and DACs */ + snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, 0); + snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, WM8962_ADC_VU); + snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1, + WM8962_DAC_MUTE, WM8962_DAC_MUTE); + + snd_soc_write(codec, WM8962_SOUNDSTAGE_ENABLES_0, val); + + /* Restore the ADCs and DACs */ + snd_soc_write(codec, WM8962_LEFT_ADC_VOLUME, adcl); + snd_soc_write(codec, WM8962_RIGHT_ADC_VOLUME, adcr); + snd_soc_update_bits(codec, WM8962_ADC_DAC_CONTROL_1, + WM8962_DAC_MUTE, dac); + + return 0; +} + +static int wm8962_dsp2_start(struct snd_soc_codec *codec) +{ + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + + wm8962_dsp2_write_config(codec); + + snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_RUNR); + + wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena); + + return 0; +} + +static int wm8962_dsp2_stop(struct snd_soc_codec *codec) +{ + wm8962_dsp2_set_enable(codec, 0); + + snd_soc_write(codec, WM8962_DSP2_EXECCONTROL, WM8962_DSP2_STOP); + + return 0; +} + +#define WM8962_DSP2_ENABLE(xname, xshift) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = wm8962_dsp2_ena_info, \ + .get = wm8962_dsp2_ena_get, .put = wm8962_dsp2_ena_put, \ + .private_value = xshift } + +static int wm8962_dsp2_ena_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + + uinfo->count = 1; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int wm8962_dsp2_ena_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int shift = kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = !!(wm8962->dsp2_ena & 1 << shift); + + return 0; +} + +static int wm8962_dsp2_ena_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int shift = kcontrol->private_value; + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + int old = wm8962->dsp2_ena; + int ret = 0; + int dsp2_running = snd_soc_read(codec, WM8962_DSP2_POWER_MANAGEMENT) & + WM8962_DSP2_ENA; + + mutex_lock(&codec->mutex); + + if (ucontrol->value.integer.value[0]) + wm8962->dsp2_ena |= 1 << shift; + else + wm8962->dsp2_ena &= ~(1 << shift); + + if (wm8962->dsp2_ena == old) + goto out; + + ret = 1; + + if (dsp2_running) { + if (wm8962->dsp2_ena) + wm8962_dsp2_set_enable(codec, wm8962->dsp2_ena); + else + wm8962_dsp2_stop(codec); + } + +out: + mutex_unlock(&codec->mutex); + + return ret; +} /* The VU bits for the headphones are in a different register to the mute * bits and only take effect on the PGA if it is actually powered. @@ -2054,6 +2175,14 @@ static const char *cap_hpf_mode_text[] = { static const struct soc_enum cap_hpf_mode = SOC_ENUM_SINGLE(WM8962_ADC_DAC_CONTROL_2, 10, 2, cap_hpf_mode_text); + +static const char *cap_lhpf_mode_text[] = { + "LPF", "HPF" +}; + +static const struct soc_enum cap_lhpf_mode = + SOC_ENUM_SINGLE(WM8962_LHPF1, 1, 2, cap_lhpf_mode_text); + static const struct snd_kcontrol_new wm8962_snd_controls[] = { SOC_DOUBLE("Input Mixer Switch", WM8962_INPUT_MIXER_CONTROL_1, 3, 2, 1, 1), @@ -2082,6 +2211,8 @@ SOC_DOUBLE_R("Capture ZC Switch", WM8962_LEFT_INPUT_VOLUME, SOC_SINGLE("Capture HPF Switch", WM8962_ADC_DAC_CONTROL_1, 0, 1, 1), SOC_ENUM("Capture HPF Mode", cap_hpf_mode), SOC_SINGLE("Capture HPF Cutoff", WM8962_ADC_DAC_CONTROL_2, 7, 7, 0), +SOC_SINGLE("Capture LHPF Switch", WM8962_LHPF1, 0, 1, 0), +SOC_ENUM("Capture LHPF Mode", cap_lhpf_mode), SOC_DOUBLE_R_TLV("Sidetone Volume", WM8962_DAC_DSP_MIXING_1, WM8962_DAC_DSP_MIXING_2, 4, 12, 0, st_tlv), @@ -2127,6 +2258,23 @@ SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4, SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0, classd_tlv), + +SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0), +SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv), + +WM8962_DSP2_ENABLE("VSS Switch", WM8962_VSS_ENA_SHIFT), +WM8962_DSP2_ENABLE("HPF1 Switch", WM8962_HPF1_ENA_SHIFT), +WM8962_DSP2_ENABLE("HPF2 Switch", WM8962_HPF2_ENA_SHIFT), +WM8962_DSP2_ENABLE("HD Bass Switch", WM8962_HDBASS_ENA_SHIFT), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2192,6 +2340,8 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + unsigned long timeout; int src; int fll; @@ -2211,9 +2361,20 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - if (fll) + if (fll) { + try_wait_for_completion(&wm8962->fll_lock); + snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, WM8962_FLL_ENA); + + timeout = msecs_to_jiffies(5); + timeout = wait_for_completion_timeout(&wm8962->fll_lock, + timeout); + + if (wm8962->irq && timeout == 0) + dev_err(codec->dev, + "Timed out starting FLL\n"); + } break; case SND_SOC_DAPM_POST_PMD: @@ -2373,6 +2534,31 @@ static int out_pga_event(struct snd_soc_dapm_widget *w, } } +static int dsp2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + if (wm8962->dsp2_ena) + wm8962_dsp2_start(codec); + break; + + case SND_SOC_DAPM_PRE_PMD: + if (wm8962->dsp2_ena) + wm8962_dsp2_stop(codec); + break; + + default: + BUG(); + return -EINVAL; + } + + return 0; +} + static const char *st_text[] = { "None", "Right", "Left" }; static const struct soc_enum str_enum = @@ -2487,7 +2673,7 @@ SND_SOC_DAPM_INPUT("IN4R"), SND_SOC_DAPM_INPUT("Beep"), SND_SOC_DAPM_INPUT("DMICDAT"), -SND_SOC_DAPM_MICBIAS("MICBIAS", WM8962_PWR_MGMT_1, 1, 0), +SND_SOC_DAPM_SUPPLY("MICBIAS", WM8962_PWR_MGMT_1, 1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("Class G", WM8962_CHARGE_PUMP_B, 0, 1, NULL, 0), SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event, @@ -2495,6 +2681,11 @@ SND_SOC_DAPM_SUPPLY("SYSCLK", WM8962_CLOCKING2, 5, 0, sysclk_event, SND_SOC_DAPM_SUPPLY("Charge Pump", WM8962_CHARGE_PUMP_1, 0, 0, cp_event, SND_SOC_DAPM_POST_PMU), SND_SOC_DAPM_SUPPLY("TOCLK", WM8962_ADDITIONAL_CONTROL_1, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TEMP_HP", WM8962_ADDITIONAL_CONTROL_4, 2, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TEMP_SPK", WM8962_ADDITIONAL_CONTROL_4, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY_S("DSP2", 1, WM8962_DSP2_POWER_MANAGEMENT, + WM8962_DSP2_ENA_SHIFT, 0, dsp2_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_MIXER("INPGAL", WM8962_LEFT_INPUT_PGA_CONTROL, 4, 0, inpgal, ARRAY_SIZE(inpgal)), @@ -2505,7 +2696,7 @@ SND_SOC_DAPM_MIXER("MIXINL", WM8962_PWR_MGMT_1, 5, 0, SND_SOC_DAPM_MIXER("MIXINR", WM8962_PWR_MGMT_1, 4, 0, mixinr, ARRAY_SIZE(mixinr)), -SND_SOC_DAPM_AIF_IN("DMIC", NULL, 0, WM8962_PWR_MGMT_1, 10, 0), +SND_SOC_DAPM_AIF_IN("DMIC_ENA", NULL, 0, WM8962_PWR_MGMT_1, 10, 0), SND_SOC_DAPM_ADC("ADCL", "Capture", WM8962_PWR_MGMT_1, 3, 0), SND_SOC_DAPM_ADC("ADCR", "Capture", WM8962_PWR_MGMT_1, 2, 0), @@ -2584,17 +2775,19 @@ static const struct snd_soc_dapm_route wm8962_intercon[] = { { "MICBIAS", NULL, "SYSCLK" }, - { "DMIC", NULL, "DMICDAT" }, + { "DMIC_ENA", NULL, "DMICDAT" }, { "ADCL", NULL, "SYSCLK" }, { "ADCL", NULL, "TOCLK" }, { "ADCL", NULL, "MIXINL" }, - { "ADCL", NULL, "DMIC" }, + { "ADCL", NULL, "DMIC_ENA" }, + { "ADCL", NULL, "DSP2" }, { "ADCR", NULL, "SYSCLK" }, { "ADCR", NULL, "TOCLK" }, { "ADCR", NULL, "MIXINR" }, - { "ADCR", NULL, "DMIC" }, + { "ADCR", NULL, "DMIC_ENA" }, + { "ADCR", NULL, "DSP2" }, { "STL", "Left", "ADCL" }, { "STL", "Right", "ADCR" }, @@ -2606,11 +2799,13 @@ static const struct snd_soc_dapm_route wm8962_intercon[] = { { "DACL", NULL, "TOCLK" }, { "DACL", NULL, "Beep" }, { "DACL", NULL, "STL" }, + { "DACL", NULL, "DSP2" }, { "DACR", NULL, "SYSCLK" }, { "DACR", NULL, "TOCLK" }, { "DACR", NULL, "Beep" }, { "DACR", NULL, "STR" }, + { "DACR", NULL, "DSP2" }, { "HPMIXL", "IN4L Switch", "IN4L" }, { "HPMIXL", "IN4R Switch", "IN4R" }, @@ -2646,6 +2841,9 @@ static const struct snd_soc_dapm_route wm8962_intercon[] = { { "HPOUTL", NULL, "HPOUT" }, { "HPOUTR", NULL, "HPOUT" }, + + { "HPOUTL", NULL, "TEMP_HP" }, + { "HPOUTR", NULL, "TEMP_HP" }, }; static const struct snd_soc_dapm_route wm8962_spk_mono_intercon[] = { @@ -2662,6 +2860,7 @@ static const struct snd_soc_dapm_route wm8962_spk_mono_intercon[] = { { "Speaker Output", NULL, "Speaker PGA" }, { "Speaker Output", NULL, "SYSCLK" }, { "Speaker Output", NULL, "TOCLK" }, + { "Speaker Output", NULL, "TEMP_SPK" }, { "SPKOUT", NULL, "Speaker Output" }, }; @@ -2690,10 +2889,12 @@ static const struct snd_soc_dapm_route wm8962_spk_stereo_intercon[] = { { "SPKOUTL Output", NULL, "SPKOUTL PGA" }, { "SPKOUTL Output", NULL, "SYSCLK" }, { "SPKOUTL Output", NULL, "TOCLK" }, + { "SPKOUTL Output", NULL, "TEMP_SPK" }, { "SPKOUTR Output", NULL, "SPKOUTR PGA" }, { "SPKOUTR Output", NULL, "SYSCLK" }, { "SPKOUTR Output", NULL, "TOCLK" }, + { "SPKOUTR Output", NULL, "TEMP_SPK" }, { "SPKOUTL", NULL, "SPKOUTL Output" }, { "SPKOUTR", NULL, "SPKOUTR Output" }, @@ -2770,18 +2971,44 @@ static const int bclk_divs[] = { 1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32 }; +static const int sysclk_rates[] = { + 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536, +}; + static void wm8962_configure_bclk(struct snd_soc_codec *codec) { struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); int dspclk, i; int clocking2 = 0; + int clocking4 = 0; int aif2 = 0; - if (!wm8962->bclk) { - dev_dbg(codec->dev, "No BCLK rate configured\n"); + if (!wm8962->sysclk_rate) { + dev_dbg(codec->dev, "No SYSCLK configured\n"); + return; + } + + if (!wm8962->bclk || !wm8962->lrclk) { + dev_dbg(codec->dev, "No audio clocks configured\n"); return; } + for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) { + if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) { + clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT; + break; + } + } + + if (i == ARRAY_SIZE(sysclk_rates)) { + dev_err(codec->dev, "Unsupported sysclk ratio %d\n", + wm8962->sysclk_rate / wm8962->lrclk); + return; + } + + snd_soc_update_bits(codec, WM8962_CLOCKING_4, + WM8962_SYSCLK_RATE_MASK, clocking4); + dspclk = snd_soc_read(codec, WM8962_CLOCKING1); if (dspclk < 0) { dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk); @@ -2851,6 +3078,8 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec, /* VMID 2*50k */ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1, WM8962_VMID_SEL_MASK, 0x80); + + wm8962_configure_bclk(codec); break; case SND_SOC_BIAS_STANDBY: @@ -2879,12 +3108,6 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec, WM8962_BIAS_ENA | 0x180); msleep(5); - - snd_soc_update_bits(codec, WM8962_CLOCKING2, - WM8962_CLKREG_OVD, - WM8962_CLKREG_OVD); - - wm8962_configure_bclk(codec); } /* VMID 2*250k */ @@ -2925,10 +3148,6 @@ static const struct { { 96000, 6 }, }; -static const int sysclk_rates[] = { - 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536, -}; - static int wm8962_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -2936,52 +3155,44 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); - int rate = params_rate(params); int i; int aif0 = 0; int adctl3 = 0; - int clocking4 = 0; + + if (codec->dapm.bias_level != SND_SOC_BIAS_OFF) + return 0; wm8962->bclk = snd_soc_params_to_bclk(params); + if (params_channels(params) == 1) + wm8962->bclk *= 2; + wm8962->lrclk = params_rate(params); for (i = 0; i < ARRAY_SIZE(sr_vals); i++) { - if (sr_vals[i].rate == rate) { + if (sr_vals[i].rate == wm8962->lrclk) { adctl3 |= sr_vals[i].reg; break; } } if (i == ARRAY_SIZE(sr_vals)) { - dev_err(codec->dev, "Unsupported rate %dHz\n", rate); + dev_err(codec->dev, "Unsupported rate %dHz\n", wm8962->lrclk); return -EINVAL; } - if (rate % 8000 == 0) + if (wm8962->lrclk % 8000 == 0) adctl3 |= WM8962_SAMPLE_RATE_INT_MODE; - for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) { - if (sysclk_rates[i] == wm8962->sysclk_rate / rate) { - clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT; - break; - } - } - if (i == ARRAY_SIZE(sysclk_rates)) { - dev_err(codec->dev, "Unsupported sysclk ratio %d\n", - wm8962->sysclk_rate / rate); - return -EINVAL; - } - switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: break; case SNDRV_PCM_FORMAT_S20_3LE: - aif0 |= 0x40; + aif0 |= 0x4; break; case SNDRV_PCM_FORMAT_S24_LE: - aif0 |= 0x80; + aif0 |= 0x8; break; case SNDRV_PCM_FORMAT_S32_LE: - aif0 |= 0xc0; + aif0 |= 0xc; break; default: return -EINVAL; @@ -2992,8 +3203,6 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3, WM8962_SAMPLE_RATE_INT_MODE | WM8962_SAMPLE_RATE_MASK, adctl3); - snd_soc_update_bits(codec, WM8962_CLOCKING_4, - WM8962_SYSCLK_RATE_MASK, clocking4); wm8962_configure_bclk(codec); @@ -3262,22 +3471,39 @@ static int wm8962_set_fll(struct snd_soc_codec *codec, int fll_id, int source, snd_soc_write(codec, WM8962_FLL_CONTROL_7, fll_div.lambda); snd_soc_write(codec, WM8962_FLL_CONTROL_8, fll_div.n); + try_wait_for_completion(&wm8962->fll_lock); + snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1, WM8962_FLL_FRAC | WM8962_FLL_REFCLK_SRC_MASK | WM8962_FLL_ENA, fll1); dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); - /* This should be a massive overestimate */ - timeout = msecs_to_jiffies(1); + ret = 0; + + if (fll1 & WM8962_FLL_ENA) { + /* This should be a massive overestimate but go even + * higher if we'll error out + */ + if (wm8962->irq) + timeout = msecs_to_jiffies(5); + else + timeout = msecs_to_jiffies(1); + + timeout = wait_for_completion_timeout(&wm8962->fll_lock, + timeout); - wait_for_completion_timeout(&wm8962->fll_lock, timeout); + if (timeout == 0 && wm8962->irq) { + dev_err(codec->dev, "FLL lock timed out"); + ret = -ETIMEDOUT; + } + } wm8962->fll_fref = Fref; wm8962->fll_fout = Fout; wm8962->fll_src = source; - return 0; + return ret; } static int wm8962_mute(struct snd_soc_dai *dai, int mute) @@ -3317,7 +3543,7 @@ static struct snd_soc_dai_driver wm8962_dai = { }, .capture = { .stream_name = "Capture", - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .rates = WM8962_RATES, .formats = WM8962_FORMATS, @@ -3362,12 +3588,19 @@ static irqreturn_t wm8962_irq(int irq, void *data) struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); int mask; int active; + int reg; mask = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2_MASK); active = snd_soc_read(codec, WM8962_INTERRUPT_STATUS_2); active &= ~mask; + if (!active) + return IRQ_NONE; + + /* Acknowledge the interrupts */ + snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active); + if (active & WM8962_FLL_LOCK_EINT) { dev_dbg(codec->dev, "FLL locked\n"); complete(&wm8962->fll_lock); @@ -3376,9 +3609,21 @@ static irqreturn_t wm8962_irq(int irq, void *data) if (active & WM8962_FIFOS_ERR_EINT) dev_err(codec->dev, "FIFO error\n"); - if (active & WM8962_TEMP_SHUT_EINT) + if (active & WM8962_TEMP_SHUT_EINT) { dev_crit(codec->dev, "Thermal shutdown\n"); + reg = snd_soc_read(codec, WM8962_THERMAL_SHUTDOWN_STATUS); + + if (reg & WM8962_TEMP_ERR_HP) + dev_crit(codec->dev, "Headphone thermal error\n"); + if (reg & WM8962_TEMP_WARN_HP) + dev_crit(codec->dev, "Headphone thermal warning\n"); + if (reg & WM8962_TEMP_ERR_SPK) + dev_crit(codec->dev, "Speaker thermal error\n"); + if (reg & WM8962_TEMP_WARN_SPK) + dev_crit(codec->dev, "Speaker thermal warning\n"); + } + if (active & (WM8962_MICSCD_EINT | WM8962_MICD_EINT)) { dev_dbg(codec->dev, "Microphone event detected\n"); @@ -3392,9 +3637,6 @@ static irqreturn_t wm8962_irq(int irq, void *data) msecs_to_jiffies(250)); } - /* Acknowledge the interrupts */ - snd_soc_write(codec, WM8962_INTERRUPT_STATUS_2, active); - return IRQ_HANDLED; } @@ -3434,34 +3676,17 @@ int wm8962_mic_detect(struct snd_soc_codec *codec, struct snd_soc_jack *jack) snd_soc_jack_report(wm8962->jack, 0, SND_JACK_MICROPHONE | SND_JACK_BTN_0); - return 0; -} -EXPORT_SYMBOL_GPL(wm8962_mic_detect); - -#ifdef CONFIG_PM -static int wm8962_resume(struct snd_soc_codec *codec) -{ - u16 *reg_cache = codec->reg_cache; - int i; - - /* Restore the registers */ - for (i = 1; i < codec->driver->reg_cache_size; i++) { - switch (i) { - case WM8962_SOFTWARE_RESET: - continue; - default: - break; - } - - if (reg_cache[i] != wm8962_reg[i]) - snd_soc_write(codec, i, reg_cache[i]); + if (jack) { + snd_soc_dapm_force_enable_pin(&codec->dapm, "SYSCLK"); + snd_soc_dapm_force_enable_pin(&codec->dapm, "MICBIAS"); + } else { + snd_soc_dapm_disable_pin(&codec->dapm, "SYSCLK"); + snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS"); } return 0; } -#else -#define wm8962_resume NULL -#endif +EXPORT_SYMBOL_GPL(wm8962_mic_detect); #if defined(CONFIG_INPUT) || defined(CONFIG_INPUT_MODULE) static int beep_rates[] = { @@ -3738,8 +3963,6 @@ static int wm8962_probe(struct snd_soc_codec *codec) int ret; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); struct wm8962_pdata *pdata = dev_get_platdata(codec->dev); - struct i2c_client *i2c = container_of(codec->dev, struct i2c_client, - dev); u16 *reg_cache = codec->reg_cache; int i, trigger, irq_pol; bool dmicclk, dmicdat; @@ -3829,6 +4052,10 @@ static int wm8962_probe(struct snd_soc_codec *codec) */ snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_SYSCLK_ENA, 0); + /* Ensure we have soft control over all registers */ + snd_soc_update_bits(codec, WM8962_CLOCKING2, + WM8962_CLKREG_OVD, WM8962_CLKREG_OVD); + /* Ensure that the oscillator and PLLs are disabled */ snd_soc_update_bits(codec, WM8962_PLL2, WM8962_OSC_ENA | WM8962_PLL2_ENA | WM8962_PLL3_ENA, @@ -3883,6 +4110,9 @@ static int wm8962_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME, WM8962_HPOUT_VU, WM8962_HPOUT_VU); + /* Stereo control for EQ */ + snd_soc_update_bits(codec, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0); + wm8962_add_widgets(codec); /* Save boards having to disable DMIC when not in use */ @@ -3911,7 +4141,7 @@ static int wm8962_probe(struct snd_soc_codec *codec) wm8962_init_beep(codec); wm8962_init_gpio(codec); - if (i2c->irq) { + if (wm8962->irq) { if (pdata && pdata->irq_active_low) { trigger = IRQF_TRIGGER_LOW; irq_pol = WM8962_IRQ_POL; @@ -3923,12 +4153,13 @@ static int wm8962_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL, WM8962_IRQ_POL, irq_pol); - ret = request_threaded_irq(i2c->irq, NULL, wm8962_irq, + ret = request_threaded_irq(wm8962->irq, NULL, wm8962_irq, trigger | IRQF_ONESHOT, "wm8962", codec); if (ret != 0) { dev_err(codec->dev, "Failed to request IRQ %d: %d\n", - i2c->irq, ret); + wm8962->irq, ret); + wm8962->irq = 0; /* Non-fatal */ } else { /* Enable some IRQs by default */ @@ -3953,12 +4184,10 @@ err: static int wm8962_remove(struct snd_soc_codec *codec) { struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); - struct i2c_client *i2c = container_of(codec->dev, struct i2c_client, - dev); int i; - if (i2c->irq) - free_irq(i2c->irq, codec); + if (wm8962->irq) + free_irq(wm8962->irq, codec); cancel_delayed_work_sync(&wm8962->mic_work); @@ -3975,7 +4204,6 @@ static int wm8962_remove(struct snd_soc_codec *codec) static struct snd_soc_codec_driver soc_codec_dev_wm8962 = { .probe = wm8962_probe, .remove = wm8962_remove, - .resume = wm8962_resume, .set_bias_level = wm8962_set_bias_level, .reg_cache_size = WM8962_MAX_REGISTER + 1, .reg_word_size = sizeof(u16), @@ -3998,6 +4226,8 @@ static __devinit int wm8962_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8962); + wm8962->irq = i2c->irq; + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8962, &wm8962_dai, 1); if (ret < 0) diff --git a/sound/soc/imx/imx-cs42888.c b/sound/soc/imx/imx-cs42888.c index dc46fa676386..c58af0c46f25 100644 --- a/sound/soc/imx/imx-cs42888.c +++ b/sound/soc/imx/imx-cs42888.c @@ -21,12 +21,14 @@ #include <linux/regulator/consumer.h> #include <linux/fsl_devices.h> #include <linux/gpio.h> +#include <linux/mxc_asrc.h> #include <sound/core.h> #include <sound/pcm.h> #include <sound/soc.h> #include <sound/soc-dapm.h> #include <sound/initval.h> #include <sound/soc-dai.h> +#include <sound/pcm_params.h> #include <mach/hardware.h> #include <mach/clock.h> @@ -34,6 +36,99 @@ #include "imx-esai.h" #include "../codecs/cs42888.h" +#include "imx-pcm.h" + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC) +struct asrc_esai { + unsigned int cpu_dai_rates; + unsigned int codec_dai_rates; + enum asrc_pair_index asrc_index; + unsigned int output_sample_rate; +}; +static struct asrc_esai asrc_esai_data; +static bool asrc_support = 1; + +static int get_format_width(struct snd_pcm_hw_params *params) +{ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + case SNDRV_PCM_FORMAT_U8: + return 8; + case SNDRV_PCM_FORMAT_U16: + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + return 16; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + case SNDRV_PCM_FORMAT_S24_BE: + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_U24_BE: + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_U24_3BE: + case SNDRV_PCM_FORMAT_U24_3LE: + return 24; + case SNDRV_PCM_FORMAT_S32: + case SNDRV_PCM_FORMAT_U32: + return 32; + default: + pr_err("Format is not support!\r\n"); + return -EINVAL; + } +} + +static int config_asrc(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + unsigned int rate = params_rate(params); + unsigned int channel = params_channels(params); + unsigned int wordwidth = get_format_width(params); + struct imx_pcm_runtime_data *pcm_data = + substream->runtime->private_data; + struct asrc_config config = {0}; + int ret = 0; + + if (rate <= 32000 || rate == asrc_esai_data.output_sample_rate) + return -EINVAL; + + if (channel != 2) + return -EINVAL; + + if (wordwidth != 24) + return -EINVAL; + + ret = asrc_req_pair(channel, &asrc_esai_data.asrc_index); + if (ret < 0) { + pr_err("Fail to request asrc pair\n"); + asrc_release_pair(asrc_esai_data.asrc_index); + asrc_finish_conv(asrc_esai_data.asrc_index); + return -EINVAL; + } + + config.pair = asrc_esai_data.asrc_index; + config.channel_num = channel; + config.input_sample_rate = rate; + config.output_sample_rate = asrc_esai_data.output_sample_rate; + config.inclk = OUTCLK_ASRCK1_CLK; + config.word_width = wordwidth; + config.outclk = OUTCLK_ESAI_TX; + + ret = asrc_config_pair(&config); + if (ret < 0) { + pr_err("Fail to config asrc\n"); + asrc_release_pair(asrc_esai_data.asrc_index); + asrc_finish_conv(asrc_esai_data.asrc_index); + return ret; + } + pcm_data->asrc_index = asrc_esai_data.asrc_index; + pcm_data->asrc_enable = 1; + + return 0; +} +#else +static bool asrc_support; +#endif struct imx_priv_state { int hw; @@ -42,6 +137,7 @@ struct imx_priv_state { static struct imx_priv_state hw_state; unsigned int mclk_freq; + static int imx_3stack_startup(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; @@ -51,6 +147,14 @@ static int imx_3stack_startup(struct snd_pcm_substream *substream) hw_state.hw = 0; } + if (asrc_support) { + struct snd_soc_dai *codec_dai = rtd->codec_dai; + asrc_esai_data.cpu_dai_rates = + cpu_dai->driver->playback.rates; + asrc_esai_data.codec_dai_rates = + codec_dai->driver->playback.rates; + } + return 0; } @@ -58,6 +162,24 @@ static void imx_3stack_shutdown(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + if (asrc_support) { + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct imx_pcm_runtime_data *pcm_data = + substream->runtime->private_data; + if (pcm_data->asrc_enable) { + asrc_release_pair(asrc_esai_data.asrc_index); + asrc_finish_conv(asrc_esai_data.asrc_index); + } + pcm_data->asrc_enable = 0; + asrc_esai_data.asrc_index = -1; + + codec_dai->driver->playback.rates = + asrc_esai_data.codec_dai_rates; + cpu_dai->driver->playback.rates = + asrc_esai_data.cpu_dai_rates; + } + if (!cpu_dai->active) hw_state.hw = 0; } @@ -75,6 +197,12 @@ static int imx_3stack_surround_hw_params(struct snd_pcm_substream *substream, if (hw_state.hw) return 0; hw_state.hw = 1; + + if (asrc_support && + (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) && + !config_asrc(substream, params)) { + rate = asrc_esai_data.output_sample_rate; + } if (cpu_is_mx53() || machine_is_mx6q_sabreauto()) { switch (rate) { case 32000: @@ -211,6 +339,9 @@ static int imx_3stack_cs42888_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; + if (asrc_support) + asrc_esai_data.output_sample_rate = 44100; + snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets, ARRAY_SIZE(imx_3stack_dapm_widgets)); diff --git a/sound/soc/imx/imx-esai.h b/sound/soc/imx/imx-esai.h index 97cf1c44c004..d27cbd00ae96 100644 --- a/sound/soc/imx/imx-esai.h +++ b/sound/soc/imx/imx-esai.h @@ -1,7 +1,7 @@ /* * imx-esai.h -- ESAI driver header file for Freescale IMX * - * Copyright 2008-2011 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2008-2012 Freescale Semiconductor, Inc. All Rights Reserved. */ /* @@ -19,25 +19,26 @@ #ifdef IMX_ESAI_DUMP #define ESAI_DUMP() \ do {pr_info("dump @ %s\n", __func__); \ - pr_info("ecr %x\n", readl(esai->base + ESAI_ECR)); \ - pr_info("esr %x\n", readl(esai->base + ESAI_ESR)); \ - pr_info("tfcr %x\n", readl(esai->base + ESAI_TFCR)); \ - pr_info("tfsr %x\n", readl(esai->base + ESAI_TFSR)); \ - pr_info("rfcr %x\n", readl(esai->base + ESAI_RFCR)); \ - pr_info("rfsr %x\n", readl(esai->base + ESAI_RFSR)); \ - pr_info("tsr %x\n", readl(esai->base + ESAI_TSR)); \ - pr_info("saisr %x\n", readl(esai->base + ESAI_SAISR)); \ - pr_info("saicr %x\n", readl(esai->base + ESAI_SAICR)); \ - pr_info("tcr %x\n", readl(esai->base + ESAI_TCR)); \ - pr_info("tccr %x\n", readl(esai->base + ESAI_TCCR)); \ - pr_info("rcr %x\n", readl(esai->base + ESAI_RCR)); \ - pr_info("rccr %x\n", readl(esai->base + ESAI_RCCR)); \ - pr_info("tsma %x\n", readl(esai->base + ESAI_TSMA)); \ - pr_info("tsmb %x\n", readl(esai->base + ESAI_TSMB)); \ - pr_info("rsma %x\n", readl(esai->base + ESAI_RSMA)); \ - pr_info("rsmb %x\n", readl(esai->base + ESAI_RSMB)); \ - pr_info("prrc %x\n", readl(esai->base + ESAI_PRRC)); \ - pr_info("pcrc %x\n", readl(esai->base + ESAI_PCRC)); } while (0); + pr_info("ESAI_ECR 0x%08x\n", readl(esai->base + ESAI_ECR)); \ + pr_info("ESAI_ESR 0x%08x\n", readl(esai->base + ESAI_ESR)); \ + pr_info("ESAI_TFCR 0x%08x\n", readl(esai->base + ESAI_TFCR)); \ + pr_info("ESAI_TFSR 0x%08x\n", readl(esai->base + ESAI_TFSR)); \ + pr_info("ESAI_RFCR 0x%08x\n", readl(esai->base + ESAI_RFCR)); \ + pr_info("ESAI_RFSR 0x%08x\n", readl(esai->base + ESAI_RFSR)); \ + pr_info("ESAI_TSR 0x%08x\n", readl(esai->base + ESAI_TSR)); \ + pr_info("ESAI_SAISR 0x%08x\n", readl(esai->base + ESAI_SAISR)); \ + pr_info("ESAI_SAICR 0x%08x\n", readl(esai->base + ESAI_SAICR)); \ + pr_info("ESAI_TCR 0x%08x\n", readl(esai->base + ESAI_TCR)); \ + pr_info("ESAI_TCCR 0x%08x\n", readl(esai->base + ESAI_TCCR)); \ + pr_info("ESAI_RCR 0x%08x\n", readl(esai->base + ESAI_RCR)); \ + pr_info("ESAI_RCCR 0x%08x\n", readl(esai->base + ESAI_RCCR)); \ + pr_info("ESAI_TSMA 0x%08x\n", readl(esai->base + ESAI_TSMA)); \ + pr_info("ESAI_TSMB 0x%08x\n", readl(esai->base + ESAI_TSMB)); \ + pr_info("ESAI_RSMA 0x%08x\n", readl(esai->base + ESAI_RSMA)); \ + pr_info("ESAI_RSMB 0x%08x\n", readl(esai->base + ESAI_RSMB)); \ + pr_info("ESAI_PRRC 0x%08x\n", readl(esai->base + ESAI_PRRC)); \ + pr_info("ESAI_PCRC 0x%08x\n", readl(esai->base + ESAI_PCRC)); \ + } while (0); #else #define ESAI_DUMP() #endif diff --git a/sound/soc/imx/imx-pcm-dma-mx2.c b/sound/soc/imx/imx-pcm-dma-mx2.c index 7ff31a894fa4..386bd26564a8 100644 --- a/sound/soc/imx/imx-pcm-dma-mx2.c +++ b/sound/soc/imx/imx-pcm-dma-mx2.c @@ -21,6 +21,8 @@ #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dmaengine.h> +#include <linux/delay.h> +#include <linux/mxc_asrc.h> #include <sound/core.h> #include <sound/initval.h> @@ -31,19 +33,8 @@ #include <mach/dma.h> #include "imx-ssi.h" +#include "imx-pcm.h" -struct imx_pcm_runtime_data { - int period_bytes; - int periods; - int dma; - unsigned long offset; - unsigned long size; - void *buf; - int period_time; - struct dma_async_tx_descriptor *desc; - struct dma_chan *dma_chan; - struct imx_dma_data dma_data; -}; static void audio_dma_irq(void *data) { @@ -68,6 +59,106 @@ static bool filter(struct dma_chan *chan, void *param) return true; } +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC) +static bool asrc_filter(struct dma_chan *chan, void *param) +{ + struct imx_pcm_runtime_data *iprtd = param; + if (!imx_dma_is_general_purpose(chan)) + return false; + chan->private = &iprtd->asrc_dma_data; + return true; +} +static bool asrc_p2p_filter(struct dma_chan *chan, void *param) +{ + struct imx_pcm_runtime_data *iprtd = param; + if (!imx_dma_is_general_purpose(chan)) + return false; + chan->private = &iprtd->asrc_p2p_dma_data; + return true; +} +static int imx_ssi_asrc_dma_alloc(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct imx_pcm_dma_params *dma_params; + struct snd_pcm_runtime *runtime = substream->runtime; + struct imx_pcm_runtime_data *iprtd = runtime->private_data; + struct dma_slave_config slave_config; + + dma_cap_mask_t mask; + enum dma_slave_buswidth buswidth; + int ret; + + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S24_LE: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + goto error; + } + + dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + /*config m2p dma channel*/ + iprtd->asrc_dma_data.peripheral_type = IMX_DMATYPE_ASRC; + iprtd->asrc_dma_data.priority = DMA_PRIO_HIGH; + iprtd->asrc_dma_data.dma_request = + asrc_get_dma_request(iprtd->asrc_index, 1); + iprtd->asrc_dma_chan = dma_request_channel(mask, asrc_filter, iprtd); + + if (!iprtd->asrc_dma_chan) + goto error; + + slave_config.direction = DMA_TO_DEVICE; + slave_config.dst_addr = asrc_get_per_addr(iprtd->asrc_index, 1); + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = dma_params->burstsize * buswidth; + + ret = dmaengine_slave_config(iprtd->asrc_dma_chan, &slave_config); + if (ret) + goto error; + /*config p2p dma channel*/ + iprtd->asrc_p2p_dma_data.peripheral_type = IMX_DMATYPE_ASRC; + iprtd->asrc_p2p_dma_data.priority = DMA_PRIO_HIGH; + iprtd->asrc_p2p_dma_data.dma_request = + asrc_get_dma_request(iprtd->asrc_index, 0); + iprtd->asrc_p2p_dma_data.dma_request_p2p = dma_params->dma; + iprtd->asrc_p2p_dma_chan = + dma_request_channel(mask, asrc_p2p_filter, iprtd); + if (!iprtd->asrc_p2p_dma_chan) + goto error; + + slave_config.direction = DMA_DEV_TO_DEV;; + slave_config.src_addr = asrc_get_per_addr(iprtd->asrc_index, 0); + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = dma_params->burstsize * buswidth; + slave_config.dst_addr = dma_params->dma_addr; + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = dma_params->burstsize * buswidth; + + ret = dmaengine_slave_config(iprtd->asrc_p2p_dma_chan, &slave_config); + if (ret) + goto error; + + return 0; +error: + if (iprtd->asrc_dma_chan) { + dma_release_channel(iprtd->asrc_dma_chan); + iprtd->asrc_dma_chan = NULL; + } + if (iprtd->asrc_p2p_dma_chan) { + dma_release_channel(iprtd->asrc_p2p_dma_chan); + iprtd->asrc_p2p_dma_chan = NULL; + } + return -EINVAL; +} +#endif static int imx_ssi_dma_alloc(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -137,17 +228,36 @@ static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, int ret; dma_params = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); - ret = imx_ssi_dma_alloc(substream, params); - if (ret) - return ret; - chan = iprtd->dma_chan; + + if (iprtd->asrc_enable) { + ret = imx_ssi_asrc_dma_alloc(substream, params); + if (ret) + return ret; + chan = iprtd->asrc_p2p_dma_chan; + iprtd->asrc_p2p_desc = + chan->device->device_prep_dma_cyclic(chan, 0xffff, + 64, + 64, + DMA_DEV_TO_DEV); + if (!iprtd->asrc_p2p_desc) { + dev_err(&chan->dev->device, + "cannot prepare slave dma\n"); + return -EINVAL; + } + chan = iprtd->asrc_dma_chan; + } else { + ret = imx_ssi_dma_alloc(substream, params); + if (ret) + return ret; + chan = iprtd->dma_chan; + } iprtd->size = params_buffer_bytes(params); iprtd->periods = params_periods(params); iprtd->period_bytes = params_period_bytes(params); iprtd->offset = 0; iprtd->period_time = HZ / (params_rate(params) / - params_period_size(params)); + params_period_size(params)); snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); @@ -155,19 +265,38 @@ static int snd_imx_pcm_hw_params(struct snd_pcm_substream *substream, iprtd->buf = (unsigned int *)substream->dma_buffer.area; - iprtd->desc = chan->device->device_prep_dma_cyclic(chan, dma_addr, + if (iprtd->asrc_enable) { + iprtd->asrc_desc = + chan->device->device_prep_dma_cyclic(chan, dma_addr, + iprtd->period_bytes * iprtd->periods, + iprtd->period_bytes, + substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (!iprtd->asrc_desc) { + dev_err(&chan->dev->device, + "cannot prepare slave dma\n"); + return -EINVAL; + } + + iprtd->asrc_desc->callback = audio_dma_irq; + iprtd->asrc_desc->callback_param = substream; + } else { + iprtd->desc = chan->device->device_prep_dma_cyclic( + chan, dma_addr, iprtd->period_bytes * iprtd->periods, iprtd->period_bytes, substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? DMA_TO_DEVICE : DMA_FROM_DEVICE); - if (!iprtd->desc) { - dev_err(&chan->dev->device, "cannot prepare slave dma\n"); - return -EINVAL; + if (!iprtd->desc) { + dev_err(&chan->dev->device, + "cannot prepare slave dma\n"); + return -EINVAL; + } + + iprtd->desc->callback = audio_dma_irq; + iprtd->desc->callback_param = substream; } - iprtd->desc->callback = audio_dma_irq; - iprtd->desc->callback_param = substream; - return 0; } @@ -176,9 +305,21 @@ static int snd_imx_pcm_hw_free(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct imx_pcm_runtime_data *iprtd = runtime->private_data; - if (iprtd->dma_chan) { - dma_release_channel(iprtd->dma_chan); + if (iprtd->asrc_enable) { + if (iprtd->asrc_dma_chan) { + dma_release_channel(iprtd->asrc_dma_chan); + iprtd->asrc_dma_chan = NULL; + } + if (iprtd->asrc_p2p_dma_chan) { + dma_release_channel(iprtd->asrc_p2p_dma_chan); + iprtd->asrc_p2p_dma_chan = NULL; + } iprtd->dma_chan = NULL; + } else { + if (iprtd->dma_chan) { + dma_release_channel(iprtd->dma_chan); + iprtd->dma_chan = NULL; + } } return 0; @@ -203,15 +344,25 @@ static int snd_imx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - dmaengine_submit(iprtd->desc); - + if (iprtd->asrc_enable) { + dmaengine_submit(iprtd->asrc_p2p_desc); + dmaengine_submit(iprtd->asrc_desc); + asrc_start_conv(iprtd->asrc_index); + } else { + dmaengine_submit(iprtd->desc); + } break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - dmaengine_terminate_all(iprtd->dma_chan); - + if (iprtd->asrc_enable) { + dmaengine_terminate_all(iprtd->asrc_dma_chan); + dmaengine_terminate_all(iprtd->asrc_p2p_dma_chan); + asrc_stop_conv(iprtd->asrc_index); + } else { + dmaengine_terminate_all(iprtd->dma_chan); + } break; default: return -EINVAL; diff --git a/sound/soc/imx/imx-pcm.h b/sound/soc/imx/imx-pcm.h new file mode 100644 index 000000000000..be0d5ffbe5d1 --- /dev/null +++ b/sound/soc/imx/imx-pcm.h @@ -0,0 +1,70 @@ +/* + * MXC ALSA Soc Driver + * + * Copyright (C) 2012 Freescale Semiconductor, Inc. + */ + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _IMX_PCM_H +#define _IMX_PCM_H + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dmaengine.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <mach/dma.h> + +#include "imx-ssi.h" + +struct imx_pcm_runtime_data { + int period_bytes; + int periods; + int dma; + unsigned long offset; + unsigned long size; + void *buf; + int period_time; + struct dma_async_tx_descriptor *desc; + struct dma_chan *dma_chan; + struct imx_dma_data dma_data; + +#if defined(CONFIG_MXC_ASRC) || defined(CONFIG_IMX_HAVE_PLATFORM_IMX_ASRC) + int asrc_index; + int asrc_enable; + struct dma_async_tx_descriptor *asrc_desc; + struct dma_chan *asrc_dma_chan; + struct imx_dma_data asrc_dma_data; + struct dma_async_tx_descriptor *asrc_p2p_desc; + struct dma_chan *asrc_p2p_dma_chan; + struct imx_dma_data asrc_p2p_dma_data; +#endif +}; +#endif diff --git a/sound/soc/imx/imx-wm8962.c b/sound/soc/imx/imx-wm8962.c index 3154cbcd7dfc..2885d3a47889 100644 --- a/sound/soc/imx/imx-wm8962.c +++ b/sound/soc/imx/imx-wm8962.c @@ -37,6 +37,7 @@ #include <mach/dma.h> #include <mach/clock.h> #include <mach/audmux.h> +#include <mach/gpio.h> #include "imx-ssi.h" #include "../codecs/wm8962.h" @@ -45,31 +46,17 @@ struct imx_priv { int sysclk; /*mclk from the outside*/ int codec_sysclk; int dai_hifi; + int hp_irq; + int hp_status; + int amic_irq; + int amic_status; struct platform_device *pdev; }; - +unsigned int sample_format = SNDRV_PCM_FMTBIT_S16_LE; static struct imx_priv card_priv; static struct snd_soc_card snd_soc_card_imx; struct clk *wm8962_mclk; -static struct snd_soc_jack hs_jack; - -/* Headphones jack detection DAPM pins */ -static struct snd_soc_jack_pin hs_jack_pins[] = { - { - .pin = "Headphone Jack", - .mask = SND_JACK_HEADPHONE, - }, -}; - -/* Headphones jack detection gpios */ -static struct snd_soc_jack_gpio hs_jack_gpios[] = { - [0] = { - /* gpio is set on per-platform basis */ - .name = "hp-gpio", - .report = SND_JACK_HEADPHONE, - .debounce_time = 200, - }, -}; +static struct snd_soc_codec *gcodec; static int imx_hifi_startup(struct snd_pcm_substream *substream) { @@ -97,6 +84,7 @@ static int imx_hifi_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; struct snd_soc_dai *cpu_dai = rtd->cpu_dai; struct snd_soc_dai *codec_dai = rtd->codec_dai; struct imx_priv *priv = &card_priv; @@ -104,6 +92,10 @@ static int imx_hifi_hw_params(struct snd_pcm_substream *substream, unsigned int sample_rate = 44100; int ret = 0; u32 dai_format; + unsigned int pll_out; + + if (codec->dapm.bias_level != SND_SOC_BIAS_OFF) + return 0; dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM; @@ -125,16 +117,22 @@ static int imx_hifi_hw_params(struct snd_pcm_substream *substream, return ret; sample_rate = params_rate(params); + sample_format = params_format(params); + + if (sample_format == SNDRV_PCM_FORMAT_S24_LE) + pll_out = sample_rate * 192; + else + pll_out = sample_rate * 256; - ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL_INT, + ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL_MCLK, WM8962_FLL_MCLK, priv->sysclk, - sample_rate * 768); + pll_out); if (ret < 0) pr_err("Failed to start FLL: %d\n", ret); ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_FLL, - sample_rate * 768, + pll_out, SND_SOC_CLOCK_IN); if (ret < 0) { pr_err("Failed to set SYSCLK: %d\n", ret); @@ -145,17 +143,15 @@ static int imx_hifi_hw_params(struct snd_pcm_substream *substream, } static const struct snd_kcontrol_new controls[] = { - SOC_DAPM_PIN_SWITCH("Main Speaker"), - SOC_DAPM_PIN_SWITCH("DMIC"), + SOC_DAPM_PIN_SWITCH("Ext Spk"), }; /* imx card dapm widgets */ static const struct snd_soc_dapm_widget imx_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), - - SND_SOC_DAPM_MIC("AMIC", NULL), - SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("AMIC", NULL), + SND_SOC_DAPM_MIC("DMIC", NULL), }; /* imx machine connections to the codec pins */ @@ -169,14 +165,150 @@ static const struct snd_soc_dapm_route audio_map[] = { { "MICBIAS", NULL, "AMIC" }, { "IN3R", NULL, "MICBIAS" }, + + { "DMIC", NULL, "MICBIAS" }, + { "DMICDAT", NULL, "DMIC" }, + }; +static void headphone_detect_handler(struct work_struct *wor) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + char *envp[3]; + char *buf; + + /*sysfs_notify(&pdev->dev.kobj, NULL, "headphone");*/ + priv->hp_status = gpio_get_value(plat->hp_gpio); + + /* setup a message for userspace headphone in */ + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + pr_err("%s kmalloc failed\n", __func__); + return; + } + + if (priv->hp_status != plat->hp_active_low) + snprintf(buf, 32, "STATE=%d", 2); + else + snprintf(buf, 32, "STATE=%d", 0); + + envp[0] = "NAME=headphone"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + enable_irq(priv->hp_irq); + + return; +} + +static DECLARE_DELAYED_WORK(hp_event, headphone_detect_handler); + +static irqreturn_t imx_headphone_detect_handler(int irq, void *data) +{ + disable_irq_nosync(irq); + schedule_delayed_work(&hp_event, msecs_to_jiffies(200)); + return IRQ_HANDLED; +} + +static ssize_t show_headphone(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + /* determine whether hp is plugged in */ + priv->hp_status = gpio_get_value(plat->hp_gpio); + + if (priv->hp_status != plat->hp_active_low) + strcpy(buf, "headphone\n"); + else + strcpy(buf, "speaker\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(headphone, S_IRUGO | S_IWUSR, show_headphone, NULL); + +static void amic_detect_handler(struct work_struct *work) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + char *envp[3]; + char *buf; + + /* sysfs_notify(&pdev->dev.kobj, NULL, "amic"); */ + priv->amic_status = gpio_get_value(plat->mic_gpio); + + /* if amic is inserted, disable dmic */ + if (priv->amic_status != plat->mic_active_low) + snd_soc_dapm_nc_pin(&gcodec->dapm, "DMIC"); + else + snd_soc_dapm_enable_pin(&gcodec->dapm, "DMIC"); + + /* setup a message for userspace headphone in */ + buf = kmalloc(32, GFP_ATOMIC); + if (!buf) { + pr_err("%s kmalloc failed\n", __func__); + return; + } + + if (priv->amic_status == 0) + snprintf(buf, 32, "STATE=%d", 2); + else + snprintf(buf, 32, "STATE=%d", 0); + + envp[0] = "NAME=amic"; + envp[1] = buf; + envp[2] = NULL; + kobject_uevent_env(&pdev->dev.kobj, KOBJ_CHANGE, envp); + kfree(buf); + + enable_irq(priv->amic_irq); +} + +static DECLARE_DELAYED_WORK(amic_event, amic_detect_handler); + +static irqreturn_t imx_amic_detect_handler(int irq, void *data) +{ + disable_irq_nosync(irq); + schedule_delayed_work(&amic_event, msecs_to_jiffies(200)); + return IRQ_HANDLED; +} + +static ssize_t show_amic(struct device_driver *dev, char *buf) +{ + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + /* determine whether amic is plugged in */ + priv->amic_status = gpio_get_value(plat->hp_gpio); + + if (priv->amic_status != plat->mic_active_low) + strcpy(buf, "amic\n"); + else + strcpy(buf, "dmic\n"); + + return strlen(buf); +} + +static DRIVER_ATTR(amic, S_IRUGO | S_IWUSR, show_amic, NULL); + static int imx_wm8962_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; - int ret; + struct imx_priv *priv = &card_priv; + struct platform_device *pdev = priv->pdev; + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + gcodec = rtd->codec; -/* Add imx specific widgets */ + /* Add imx specific widgets */ snd_soc_dapm_new_controls(&codec->dapm, imx_dapm_widgets, ARRAY_SIZE(imx_dapm_widgets)); @@ -188,24 +320,15 @@ static int imx_wm8962_init(struct snd_soc_pcm_runtime *rtd) snd_soc_dapm_sync(&codec->dapm); - if (hs_jack_gpios[0].gpio != -1) { - /* Jack detection API stuff */ - ret = snd_soc_jack_new(codec, "Headphone Jack", - SND_JACK_HEADPHONE, &hs_jack); - if (ret) - return ret; + if (plat->mic_gpio != -1) { - ret = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), - hs_jack_pins); - if (ret) { - printk(KERN_ERR "failed to call snd_soc_jack_add_pins\n"); - return ret; - } + priv->amic_status = gpio_get_value(plat->mic_gpio); - ret = snd_soc_jack_add_gpios(&hs_jack, - ARRAY_SIZE(hs_jack_gpios), hs_jack_gpios); - if (ret) - printk(KERN_WARNING "failed to call snd_soc_jack_add_gpios\n"); + /* if amic is inserted, disable DMIC */ + if (priv->amic_status != plat->mic_active_low) + snd_soc_dapm_nc_pin(&gcodec->dapm, "DMIC"); + else + snd_soc_dapm_enable_pin(&gcodec->dapm, "DMIC"); } return 0; @@ -283,8 +406,43 @@ static int __devinit imx_wm8962_probe(struct platform_device *pdev) } priv->sysclk = plat->sysclk; - hs_jack_gpios[0].gpio = plat->hp_gpio; - hs_jack_gpios[0].invert = plat->hp_active_low; + priv->hp_irq = gpio_to_irq(plat->hp_gpio); + priv->amic_irq = gpio_to_irq(plat->mic_gpio); + + if (plat->hp_gpio != -1) { + ret = request_irq(priv->hp_irq, + imx_headphone_detect_handler, + IRQ_TYPE_EDGE_BOTH, pdev->name, priv); + + if (ret < 0) { + ret = -EINVAL; + return ret; + } + + ret = driver_create_file(pdev->dev.driver, + &driver_attr_headphone); + if (ret < 0) { + ret = -EINVAL; + return ret; + } + } + + if (plat->mic_gpio != -1) { + ret = request_irq(priv->amic_irq, + imx_amic_detect_handler, + IRQ_TYPE_EDGE_BOTH, pdev->name, priv); + + if (ret < 0) { + ret = -EINVAL; + return ret; + } + + ret = driver_create_file(pdev->dev.driver, &driver_attr_amic); + if (ret < 0) { + ret = -EINVAL; + return ret; + } + } return ret; } |