| /* |
| * espresso7420_wm5110.c |
| * |
| * 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. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/clk.h> |
| #include <linux/io.h> |
| #include <linux/slab.h> |
| #include <linux/gpio.h> |
| #include <linux/module.h> |
| |
| #include <linux/mfd/arizona/registers.h> |
| #include <linux/mfd/arizona/core.h> |
| |
| #include <sound/tlv.h> |
| |
| #include <sound/soc.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/initval.h> |
| |
| #if 0 |
| #include <mach/regs-pmu.h> |
| #endif |
| |
| #include "i2s.h" |
| #include "i2s-regs.h" |
| #include "../codecs/wm5102.h" |
| #include "../codecs/wm5110.h" |
| |
| #ifdef CONFIG_SND_ESA_SA_EFFECT |
| #include "esa_sa_effect.h" |
| #endif |
| |
| /* ESPRESSO use CLKOUT from AP */ |
| #define ESPRESSO_MCLK_FREQ 26000000 |
| #define ESPRESSO_AUD_PLL_FREQ 491520000 |
| |
| #define ESPRESSO_DEFAULT_MCLK1 26000000 |
| #define ESPRESSO_DEFAULT_MCLK2 32768 |
| |
| #define ESPRESSO_TELCLK_RATE (48000 * 1024) |
| |
| static DECLARE_TLV_DB_SCALE(digital_tlv, -6400, 50, 0); |
| struct arizona_machine_priv { |
| int clock_mode; |
| struct snd_soc_jack jack; |
| struct snd_soc_codec *codec; |
| struct snd_soc_dai *aif[3]; |
| struct delayed_work mic_work; |
| int aif2mode; |
| |
| int aif1rate; |
| int aif2rate; |
| |
| int asyncclk_rate; |
| unsigned int hp_impedance_step; |
| }; |
| |
| static struct arizona_machine_priv *priv = NULL; |
| static const struct snd_soc_component_driver espresso_cmpnt = { |
| .name = "espresso-audio", |
| }; |
| |
| const char *aif2_mode_text[] = { |
| "Slave", "Master" |
| }; |
| |
| static const struct soc_enum aif2_mode_enum[] = { |
| SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), |
| }; |
| #if 0 |
| static struct { |
| int min; /* Minimum impedance */ |
| int max; /* Maximum impedance */ |
| unsigned int gain; /* Register value to set for this measurement */ |
| } hp_gain_table[] = { |
| { 0, 42, 0 }, |
| { 43, 100, 2 }, |
| { 101, 200, 4 }, |
| { 201, 450, 6 }, |
| { 451, 1000, 8 }, |
| { 1001, INT_MAX, 0 }, |
| }; |
| #endif |
| static bool clkout_enabled; |
| static struct snd_soc_card espresso; |
| |
| static struct snd_soc_codec *the_codec; |
| |
| extern void update_cp_available(bool cpen); |
| |
| #if 0 |
| void espresso_wm5110_hpdet_cb(unsigned int meas) |
| { |
| int i; |
| struct arizona_machine_priv *priv; |
| |
| WARN_ON(!the_codec); |
| if (!the_codec) |
| return; |
| |
| priv = snd_soc_card_get_drvdata(the_codec->card); |
| |
| for (i = 0; i < ARRAY_SIZE(hp_gain_table); i++) { |
| if (meas < hp_gain_table[i].min || meas > hp_gain_table[i].max) |
| continue; |
| |
| dev_info(the_codec->dev, "SET GAIN %d step for %d ohms\n", |
| hp_gain_table[i].gain, meas); |
| priv->hp_impedance_step = hp_gain_table[i].gain; |
| } |
| } |
| #endif |
| static int arizona_put_impedance_volsw(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| struct soc_mixer_control *mc = |
| (struct soc_mixer_control *)kcontrol->private_value; |
| #if 0 |
| unsigned int reg = mc->reg; |
| #endif |
| unsigned int shift = mc->shift; |
| int max = mc->max; |
| unsigned int mask = (1 << fls(max)) - 1; |
| unsigned int invert = mc->invert; |
| int err = 0; |
| unsigned int val, val_mask; |
| |
| val = (ucontrol->value.integer.value[0] & mask); |
| val += priv->hp_impedance_step; |
| dev_info(component->dev, "SET GAIN %d according to impedance, moved %d step\n", |
| val, priv->hp_impedance_step); |
| |
| if (invert) |
| val = max - val; |
| val_mask = mask << shift; |
| val = val << shift; |
| #if 0 |
| err = snd_soc_update_bits_locked(codec, reg, val_mask, val); |
| if (err < 0) |
| return err; |
| #endif |
| |
| return err; |
| } |
| |
| static void espresso_enable_mclk(bool on) |
| { |
| pr_debug("%s: %s\n", __func__, on ? "on" : "off"); |
| |
| clkout_enabled = on; |
| #if 0 |
| writel(on ? 0x1F00 : 0x1F00, EXYNOS_PMU_PMU_DEBUG); |
| #endif |
| } |
| |
| static int get_aif2_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = priv->aif2mode; |
| return 0; |
| } |
| |
| static int set_aif2_mode(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); |
| |
| priv->aif2mode = ucontrol->value.integer.value[0]; |
| |
| dev_info(component->dev, "set aif2 mode: %s\n", |
| aif2_mode_text[priv->aif2mode]); |
| return 0; |
| } |
| |
| static const struct snd_kcontrol_new espresso_codec_controls[] = { |
| SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], |
| get_aif2_mode, set_aif2_mode), |
| |
| SOC_SINGLE_EXT_TLV("HPOUT1L Impedance Volume", |
| ARIZONA_DAC_DIGITAL_VOLUME_1L, |
| ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0, |
| snd_soc_get_volsw, arizona_put_impedance_volsw, |
| digital_tlv), |
| |
| SOC_SINGLE_EXT_TLV("HPOUT1R Impedance Volume", |
| ARIZONA_DAC_DIGITAL_VOLUME_1R, |
| ARIZONA_OUT1L_VOL_SHIFT, 0xbf, 0, |
| snd_soc_get_volsw, arizona_put_impedance_volsw, |
| digital_tlv), |
| }; |
| |
| static const struct snd_kcontrol_new espresso_controls[] = { |
| SOC_DAPM_PIN_SWITCH("HP"), |
| SOC_DAPM_PIN_SWITCH("SPK"), |
| SOC_DAPM_PIN_SWITCH("HDMI"), |
| SOC_DAPM_PIN_SWITCH("Main Mic"), |
| SOC_DAPM_PIN_SWITCH("Sub Mic"), |
| }; |
| |
| const struct snd_soc_dapm_widget espresso_dapm_widgets[] = { |
| SND_SOC_DAPM_OUTPUT("HDMIL"), |
| SND_SOC_DAPM_OUTPUT("HDMIR"), |
| SND_SOC_DAPM_HP("HP", NULL), |
| SND_SOC_DAPM_SPK("SPK", NULL), |
| SND_SOC_DAPM_LINE("HDMI", NULL), |
| |
| SND_SOC_DAPM_MIC("Main Mic", NULL), |
| SND_SOC_DAPM_MIC("Sub Mic", NULL), |
| }; |
| |
| const struct snd_soc_dapm_route espresso_dapm_routes[] = { |
| { "HP", NULL, "HPOUT1L" }, |
| { "HP", NULL, "HPOUT1R" }, |
| |
| { "SPK", NULL, "SPKOUTLN" }, |
| { "SPK", NULL, "SPKOUTLP" }, |
| { "SPK", NULL, "SPKOUTRN" }, |
| { "SPK", NULL, "SPKOUTRP" }, |
| |
| { "Main Mic", NULL, "MICBIAS1" }, |
| { "Sub Mic", NULL, "MICBIAS2" }, |
| { "IN1L", NULL, "Main Mic" }, |
| { "IN2L", NULL, "Sub Mic" }, |
| }; |
| |
| int espresso_set_media_clocking(struct snd_soc_card *card, |
| struct snd_soc_codec *codec) |
| { |
| int ret, fs; |
| |
| if (priv->aif1rate >= 192000) |
| fs = 256; |
| else |
| fs = 1024; |
| |
| #ifdef CONFIG_MFD_WM5110 |
| ret = snd_soc_codec_set_pll(codec, WM5110_FLL1_REFCLK, |
| ARIZONA_FLL_SRC_NONE, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret); |
| return ret; |
| } |
| ret = snd_soc_codec_set_pll(codec, WM5110_FLL1, ARIZONA_CLK_SRC_MCLK1, |
| ESPRESSO_DEFAULT_MCLK1, |
| priv->aif1rate * fs); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1: %d\n", ret); |
| return ret; |
| } |
| #else |
| ret = snd_soc_codec_set_pll(codec, WM5102_FLL1_REFCLK, |
| ARIZONA_FLL_SRC_NONE, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret); |
| return ret; |
| } |
| ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, ARIZONA_CLK_SRC_MCLK1, |
| ESPRESSO_DEFAULT_MCLK1, |
| priv->aif1rate * fs); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1: %d\n", ret); |
| return ret; |
| } |
| #endif |
| |
| ret = snd_soc_codec_set_sysclk(codec, |
| ARIZONA_CLK_SYSCLK, |
| ARIZONA_CLK_SRC_FLL1, |
| priv->aif1rate * fs, |
| SND_SOC_CLOCK_IN); |
| if (ret < 0) |
| dev_err(card->dev, "Failed to set SYSCLK to FLL1: %d\n", ret); |
| |
| ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, |
| ARIZONA_CLK_SRC_FLL2, |
| ESPRESSO_TELCLK_RATE, |
| SND_SOC_CLOCK_IN); |
| if (ret < 0) |
| dev_err(card->dev, |
| "Unable to set ASYNCCLK to FLL2: %d\n", ret); |
| |
| /* AIF1 from SYSCLK, AIF2 and 3 from ASYNCCLK */ |
| ret = snd_soc_dai_set_sysclk(priv->aif[0], ARIZONA_CLK_SYSCLK, 0, 0); |
| if (ret < 0) |
| dev_err(card->dev, "Can't set AIF1 to SYSCLK: %d\n", ret); |
| |
| ret = snd_soc_dai_set_sysclk(priv->aif[2], ARIZONA_CLK_ASYNCCLK, 0, 0); |
| if (ret < 0) |
| dev_err(card->dev, "Can't set AIF2 to ASYNCCLK: %d\n", ret); |
| |
| return 0; |
| } |
| |
| static int espresso_aif1_startup(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_card *card = rtd->card; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| static void espresso_aif1_shutdown(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_card *card = rtd->card; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| } |
| |
| static int espresso_aif1_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_card *card = rtd->card; |
| struct snd_soc_dai *codec_dai = rtd->codec_dai; |
| struct snd_soc_dai *cpu_dai = rtd->cpu_dai; |
| int ret; |
| |
| dev_info(card->dev, "aif1: %dch, %dHz, %dbytes\n", |
| params_channels(params), params_rate(params), |
| params_buffer_bytes(params)); |
| |
| priv->aif1rate = params_rate(params); |
| |
| espresso_set_media_clocking(card, codec_dai->codec); |
| |
| /* Set Codec DAI configuration */ |
| ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S |
| | SND_SOC_DAIFMT_NB_NF |
| | SND_SOC_DAIFMT_CBM_CFM); |
| if (ret < 0) { |
| dev_err(card->dev, "Failed to set aif1 codec fmt: %d\n", ret); |
| return ret; |
| } |
| |
| /* Set CPU DAI configuration */ |
| ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S |
| | SND_SOC_DAIFMT_NB_NF |
| | SND_SOC_DAIFMT_CBM_CFM); |
| if (ret < 0) { |
| dev_err(card->dev, "Failed to set aif1 cpu fmt: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, |
| 0, SND_SOC_CLOCK_IN); |
| if (ret < 0) { |
| dev_err(card->dev, "Failed to set SAMSUNG_I2S_CDCL: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, |
| 0, MOD_OPCLK_PCLK); |
| if (ret < 0) { |
| dev_err(card->dev, "Failed to set SAMSUNG_I2S_OPCL: %d\n", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int espresso_aif1_hw_free(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_card *card = rtd->card; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| static int espresso_aif1_prepare(struct snd_pcm_substream *substream) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_card *card = rtd->card; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| static int espresso_aif1_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| struct snd_soc_pcm_runtime *rtd = substream->private_data; |
| struct snd_soc_card *card = rtd->card; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| return 0; |
| } |
| |
| static struct snd_soc_ops espresso_aif1_ops = { |
| .startup = espresso_aif1_startup, |
| .shutdown = espresso_aif1_shutdown, |
| .hw_params = espresso_aif1_hw_params, |
| .hw_free = espresso_aif1_hw_free, |
| .prepare = espresso_aif1_prepare, |
| .trigger = espresso_aif1_trigger, |
| }; |
| |
| static int espresso_aif2_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_card *card = rtd->card; |
| struct snd_soc_dai *codec_dai = rtd->codec_dai; |
| unsigned int fmt; |
| int ret; |
| int prate, bclk; |
| |
| dev_info(card->dev, ":::::::::::: %s-%d %dch, %dHz\n", |
| rtd->dai_link->name, substream->stream, |
| params_channels(params), params_rate(params)); |
| |
| prate = params_rate(params); |
| switch (prate) { |
| case 8000: |
| bclk = 256000; |
| break; |
| case 16000: |
| bclk = 512000; |
| break; |
| default: |
| dev_warn(card->dev, |
| "Unsupported LRCLK %d, falling back to 8000Hz\n", |
| (int)params_rate(params)); |
| bclk = 256000; |
| } |
| |
| fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF; |
| |
| /* Set the codec DAI configuration, aif2_mode:0 is slave */ |
| if (priv->aif2mode == 0) |
| ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBS_CFS); |
| else |
| ret = snd_soc_dai_set_fmt(codec_dai, fmt | SND_SOC_DAIFMT_CBM_CFM); |
| |
| if (ret < 0) { |
| dev_err(card->dev, |
| "Failed to set audio format in codec: %d\n", ret); |
| return ret; |
| } |
| |
| priv->asyncclk_rate = 49152000; |
| if (priv->aif2mode == 0) { |
| ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2_REFCLK, |
| ARIZONA_FLL_SRC_MCLK1, |
| ESPRESSO_DEFAULT_MCLK1, |
| priv->asyncclk_rate); |
| if (ret != 0) { |
| dev_err(card->dev, |
| "Failed to start FLL2 REF: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2, |
| ARIZONA_FLL_SRC_AIF2BCLK, |
| bclk, |
| priv->asyncclk_rate); |
| if (ret != 0) { |
| dev_err(card->dev, |
| "Failed to start FLL2%d\n", ret); |
| return ret; |
| } |
| } else { |
| ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2, 0, 0, 0); |
| if (ret != 0) |
| dev_err(card->dev, |
| "Failed to stop FLL2: %d\n", ret); |
| |
| ret = snd_soc_dai_set_pll(codec_dai, WM5102_FLL2_REFCLK, |
| ARIZONA_FLL_SRC_NONE, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, |
| "Failed to start FLL2 REF: %d\n", ret); |
| return ret; |
| } |
| ret = snd_soc_dai_set_pll(codec_dai, WM5110_FLL2, |
| ARIZONA_CLK_SRC_MCLK1, |
| ESPRESSO_DEFAULT_MCLK1, |
| priv->asyncclk_rate); |
| if (ret != 0) { |
| dev_err(card->dev, |
| "Failed to start FLL2: %d\n", ret); |
| return ret; |
| } |
| } |
| |
| update_cp_available(true); |
| |
| return 0; |
| } |
| |
| static void espresso_aif2_shutdown(struct snd_pcm_substream *substream) |
| { |
| update_cp_available(false); |
| } |
| |
| static struct snd_soc_ops espresso_aif2_ops = { |
| .hw_params = espresso_aif2_hw_params, |
| .shutdown = espresso_aif2_shutdown, |
| }; |
| |
| #if 0 /* later */ |
| static int set_aud_pll_rate(unsigned long rate) |
| { |
| struct clk *fout_aud_pll; |
| |
| fout_aud_pll = clk_get(espresso.dev, "aud_pll"); |
| if (IS_ERR(fout_aud_pll)) { |
| printk(KERN_ERR "%s: failed to get fout_aud_pll\n", __func__); |
| return PTR_ERR(fout_aud_pll); |
| } |
| |
| if (rate == clk_get_rate(fout_aud_pll)) |
| goto out; |
| |
| rate += 20; /* margin */ |
| clk_set_rate(fout_aud_pll, rate); |
| pr_debug("%s: aud_pll rate = %ld\n", |
| __func__, clk_get_rate(fout_aud_pll)); |
| out: |
| clk_put(fout_aud_pll); |
| |
| return 0; |
| } |
| #endif |
| |
| static struct snd_soc_dai_link espresso_dai[] = { |
| { /* playback & recording */ |
| .name = "espresso-arizona playback", |
| .stream_name = "i2s0-pri", |
| .cpu_dai_name = "11440000.i2s", |
| .platform_name = "11440000.i2s", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* deep buffer playback */ |
| .name = "espresso-arizona multimedia playback", |
| .stream_name = "i2s0-sec", |
| .cpu_dai_name = "samsung-i2s-sec", |
| .platform_name = "samsung-i2s-sec", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* Voice Call */ |
| .name = "cp", |
| .stream_name = "voice call", |
| .cpu_dai_name = "cp_dummy", |
| .platform_name = "snd-soc-dummy", |
| .codec_dai_name = "wm5110-aif2", |
| .codec_name = "wm5110-codec", |
| .ops = &espresso_aif2_ops, |
| .ignore_suspend = 1, |
| }, { /* eax0 playback */ |
| .name = "playback-eax0", |
| .stream_name = "eax0", |
| .cpu_dai_name = "samsung-eax.0", |
| .platform_name = "samsung-eax.0", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* eax1 playback */ |
| .name = "playback-eax1", |
| .stream_name = "eax1", |
| .cpu_dai_name = "samsung-eax.1", |
| .platform_name = "samsung-eax.1", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* eax2 playback */ |
| .name = "playback-eax2", |
| .stream_name = "eax2", |
| .cpu_dai_name = "samsung-eax.2", |
| .platform_name = "samsung-eax.2", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* eax3 playback */ |
| .name = "playback-eax3", |
| .stream_name = "eax3", |
| .cpu_dai_name = "samsung-eax.3", |
| .platform_name = "samsung-eax.3", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| }, { /* compress playback */ |
| .name = "espresso-arizona compress playback", |
| .stream_name = "i2s0-compr", |
| .cpu_dai_name = "samsung-i2s-compr", |
| .platform_name = "samsung-i2s-compr", |
| #ifdef CONFIG_MFD_WM5110 |
| .codec_dai_name = "wm5110-aif1", |
| .codec_name = "wm5110-codec", |
| #else |
| .codec_dai_name = "wm5102-aif1", |
| #endif |
| .ops = &espresso_aif1_ops, |
| #if 0 |
| }, { /* compress capture */ |
| .name = "compress capture", |
| .stream_name = "compr-cap", |
| .codec_dai_name = "dummy-aif1", |
| #endif |
| } |
| }; |
| |
| static int espresso_late_probe(struct snd_soc_card *card) |
| { |
| struct snd_soc_codec *codec = card->rtd[0].codec; |
| struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai; |
| struct snd_soc_dai *cpu_dai = card->rtd[0].cpu_dai; |
| int i, ret; |
| |
| priv->codec = codec; |
| the_codec = codec; |
| |
| for (i = 0; i < 3; i++) |
| priv->aif[i] = card->rtd[i].codec_dai; |
| |
| codec_dai->driver->playback.channels_max = |
| cpu_dai->driver->playback.channels_max; |
| |
| espresso_enable_mclk(true); |
| |
| ret = snd_soc_add_codec_controls(codec, espresso_codec_controls, |
| ARRAY_SIZE(espresso_codec_controls)); |
| |
| if (ret < 0) { |
| dev_err(codec->dev, |
| "Failed to add controls to codec: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_codec_set_sysclk(codec, |
| ARIZONA_CLK_SYSCLK, |
| ARIZONA_CLK_SRC_FLL1, |
| 48000 * 1024, |
| SND_SOC_CLOCK_IN); |
| if (ret < 0) |
| dev_err(card->dev, "Failed to set SYSCLK to FLL1: %d\n", ret); |
| |
| ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK, |
| ARIZONA_CLK_SRC_FLL2, |
| ESPRESSO_TELCLK_RATE, |
| SND_SOC_CLOCK_IN); |
| #ifdef CONFIG_SND_ESA_SA_EFFECT |
| ret = esa_effect_register(card); |
| if (ret < 0) { |
| pr_err("Failed to add controls to effect: %d\n", ret); |
| return ret; |
| } |
| #endif |
| dev_info(card->dev, "%s: Successfully created\n", __func__); |
| #if 0 |
| arizona_set_hpdet_cb(codec, espresso_wm5110_hpdet_cb); |
| #endif |
| espresso_enable_mclk(false); |
| |
| return 0; |
| } |
| |
| static int espresso_suspend_post(struct snd_soc_card *card) |
| { |
| /* espresso_enable_mclk(false); */ |
| return 0; |
| } |
| |
| static int espresso_resume_pre(struct snd_soc_card *card) |
| { |
| /* espresso_enable_mclk(true); */ |
| return 0; |
| } |
| |
| static int espresso_start_sysclk(struct snd_soc_card *card) |
| { |
| struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card); |
| int ret, fs; |
| |
| if (!priv->aif1rate) |
| priv->aif1rate = 48000; |
| |
| if (priv->aif1rate >= 192000) |
| fs = 256; |
| else |
| fs = 1024; |
| |
| espresso_enable_mclk(true); |
| |
| ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1_REFCLK, |
| ARIZONA_FLL_SRC_NONE, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1 REF: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1, |
| ARIZONA_CLK_SRC_MCLK1, |
| ESPRESSO_DEFAULT_MCLK1, |
| priv->aif1rate * fs); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to start FLL1: %d\n", ret); |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int espresso_stop_sysclk(struct snd_soc_card *card) |
| { |
| struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card); |
| int ret; |
| |
| ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL1, 0, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to stop FLL1: %d\n", ret); |
| return ret; |
| } |
| |
| ret = snd_soc_codec_set_pll(priv->codec, WM5110_FLL2, 0, 0, 0); |
| if (ret != 0) { |
| dev_err(card->dev, "Failed to stop FLL1: %d\n", ret); |
| return ret; |
| } |
| |
| espresso_enable_mclk(false); |
| |
| return ret; |
| } |
| |
| static int espresso_set_bias_level(struct snd_soc_card *card, |
| struct snd_soc_dapm_context *dapm, |
| enum snd_soc_bias_level level) |
| { |
| struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card); |
| |
| if (!priv->codec || dapm != &priv->codec->dapm) |
| return 0; |
| |
| switch (level) { |
| case SND_SOC_BIAS_STANDBY: |
| if (card->dapm.bias_level == SND_SOC_BIAS_OFF) { |
| espresso_start_sysclk(card); |
| if (IS_ERR(devm_pinctrl_get_select(card->dev, "default"))) |
| dev_err(card->dev, "no pinctrl for irq\n"); |
| } |
| break; |
| case SND_SOC_BIAS_OFF: |
| if (IS_ERR(devm_pinctrl_get_select(card->dev, "idle"))) |
| dev_err(card->dev, "no pinctrl for irq\n"); |
| espresso_stop_sysclk(card); |
| break; |
| case SND_SOC_BIAS_PREPARE: |
| break; |
| default: |
| break; |
| } |
| |
| card->dapm.bias_level = level; |
| dev_dbg(card->dev, "%s: %d\n", __func__, level); |
| |
| return 0; |
| } |
| |
| static int espresso_set_bias_level_post(struct snd_soc_card *card, |
| struct snd_soc_dapm_context *dapm, |
| enum snd_soc_bias_level level) |
| { |
| dev_dbg(card->dev, "%s: %d\n", __func__, level); |
| |
| return 0; |
| } |
| |
| static struct snd_soc_card espresso = { |
| .name = "ESPRESSO-WM5110", |
| .owner = THIS_MODULE, |
| |
| .dai_link = espresso_dai, |
| .num_links = ARRAY_SIZE(espresso_dai), |
| |
| .controls = espresso_controls, |
| .num_controls = ARRAY_SIZE(espresso_controls), |
| .dapm_widgets = espresso_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(espresso_dapm_widgets), |
| .dapm_routes = espresso_dapm_routes, |
| .num_dapm_routes = ARRAY_SIZE(espresso_dapm_routes), |
| |
| .late_probe = espresso_late_probe, |
| .suspend_post = espresso_suspend_post, |
| .resume_pre = espresso_resume_pre, |
| .set_bias_level = espresso_set_bias_level, |
| .set_bias_level_post = espresso_set_bias_level_post, |
| |
| }; |
| |
| static int espresso_audio_probe(struct platform_device *pdev) |
| { |
| int n, ret; |
| struct device_node *np = pdev->dev.of_node; |
| struct snd_soc_card *card = &espresso; |
| bool hdmi_avail = true; |
| |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
| if (priv == NULL) { |
| dev_err(&pdev->dev, "Failed to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| card->dev = &pdev->dev; |
| |
| for (n = 0; np && n < ARRAY_SIZE(espresso_dai); n++) { |
| if (!espresso_dai[n].cpu_dai_name) { |
| espresso_dai[n].cpu_of_node = of_parse_phandle(np, |
| "samsung,audio-cpu", n); |
| |
| if (!espresso_dai[n].cpu_of_node && hdmi_avail) { |
| espresso_dai[n].cpu_of_node = of_parse_phandle(np, |
| "samsung,audio-cpu-hdmi", 0); |
| hdmi_avail = false; |
| } |
| |
| if (!espresso_dai[n].cpu_of_node) { |
| dev_err(&pdev->dev, "Property " |
| "'samsung,audio-cpu' missing or invalid\n"); |
| ret = -EINVAL; |
| } |
| } |
| |
| if (!espresso_dai[n].platform_name) |
| espresso_dai[n].platform_of_node = espresso_dai[n].cpu_of_node; |
| |
| #if 0 |
| espresso_dai[n].codec_of_node = of_parse_phandle(np, |
| "samsung,audio-codec", n); |
| if (!espresso_dai[0].codec_of_node) { |
| dev_err(&pdev->dev, |
| "Property 'samsung,audio-codec' missing or invalid\n"); |
| ret = -EINVAL; |
| } |
| #endif |
| } |
| |
| snd_soc_card_set_drvdata(card, priv); |
| |
| ret = snd_soc_register_card(card); |
| |
| if (ret) { |
| dev_err(&pdev->dev, "snd_soc_register_card() failed:%d\n", ret); |
| snd_soc_unregister_component(card->dev); |
| kfree(priv); |
| } |
| |
| return ret; |
| } |
| |
| static int espresso_audio_remove(struct platform_device *pdev) |
| { |
| struct snd_soc_card *card = platform_get_drvdata(pdev); |
| struct arizona_machine_priv *priv = snd_soc_card_get_drvdata(card); |
| |
| snd_soc_unregister_card(card); |
| kfree(priv); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_OF |
| static const struct of_device_id espresso_wm5110_of_match[] = { |
| { .compatible = "samsung,espresso_wm5110", }, |
| {}, |
| }; |
| MODULE_DEVICE_TABLE(of, espresso_wm5110_of_match); |
| #endif /* CONFIG_OF */ |
| |
| static struct platform_driver espresso_audio_driver = { |
| .driver = { |
| .name = "espresso-audio", |
| .owner = THIS_MODULE, |
| .pm = &snd_soc_pm_ops, |
| .of_match_table = of_match_ptr(espresso_wm5110_of_match), |
| }, |
| .probe = espresso_audio_probe, |
| .remove = espresso_audio_remove, |
| }; |
| |
| module_platform_driver(espresso_audio_driver); |
| |
| MODULE_DESCRIPTION("ALSA SoC ESPRESSO WM5110"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("platform:espresso-audio"); |