diff options
Diffstat (limited to 'sound/soc')
-rw-r--r-- | sound/soc/codecs/mc1n2/mc1n2.c | 25 | ||||
-rw-r--r-- | sound/soc/codecs/mc1n2/mc1n2.h | 2 | ||||
-rw-r--r-- | sound/soc/codecs/wm8993.c | 86 | ||||
-rw-r--r-- | sound/soc/codecs/wm8994.c | 101 | ||||
-rw-r--r-- | sound/soc/codecs/wm_hubs.c | 226 | ||||
-rw-r--r-- | sound/soc/codecs/wm_hubs.h | 12 | ||||
-rw-r--r-- | sound/soc/samsung/Kconfig | 49 | ||||
-rw-r--r-- | sound/soc/samsung/Makefile | 16 | ||||
-rw-r--r-- | sound/soc/samsung/audss.c | 59 | ||||
-rw-r--r-- | sound/soc/samsung/dma.c | 8 | ||||
-rw-r--r-- | sound/soc/samsung/grande_wm1811.c | 1715 | ||||
-rw-r--r-- | sound/soc/samsung/i2s.c | 3 | ||||
-rw-r--r-- | sound/soc/samsung/m3_wm1811.c | 1219 | ||||
-rw-r--r-- | sound/soc/samsung/midas_wm1811.c | 138 | ||||
-rw-r--r-- | sound/soc/samsung/pcm.c | 39 | ||||
-rw-r--r-- | sound/soc/samsung/t0_wm1811.c | 1393 | ||||
-rw-r--r-- | sound/soc/samsung/t0duos_wm1811.c | 1479 | ||||
-rw-r--r-- | sound/soc/soc-core.c | 2 | ||||
-rw-r--r-- | sound/soc/soc-jack.c | 8 | ||||
-rw-r--r-- | sound/soc/soc-utils.c | 103 |
20 files changed, 6409 insertions, 274 deletions
diff --git a/sound/soc/codecs/mc1n2/mc1n2.c b/sound/soc/codecs/mc1n2/mc1n2.c index 975f40b..17dbefa 100644 --- a/sound/soc/codecs/mc1n2/mc1n2.c +++ b/sound/soc/codecs/mc1n2/mc1n2.c @@ -149,6 +149,7 @@ struct mc1n2_data { MCDRV_PDM_INFO pdm_store; UINT32 hdmicount; UINT32 delay_mic1in; + UINT32 lineoutenable; }; struct mc1n2_info_store { @@ -666,6 +667,12 @@ static int mc1n2_i2s_hw_params(struct snd_pcm_substream *substream, } #endif +/* Because of line out pop up noise issue, i2s port already opend */ + if ((mc1n2->lineoutenable == 1) && (port->stream & (1 << dir))) { + err = 0; + goto error; + } + port->rate = rate; port->channels = params_channels(params); @@ -739,6 +746,12 @@ static int mc1n2_hw_free(struct snd_pcm_substream *substream, } #endif +/* Because of line out pop up noise, leave codec opened */ + if (mc1n2->lineoutenable == 1) { + err = 0; + goto error; + } + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { err = mc1n2_control_dir(mc1n2, get_port_id(dai->id), 0); } else { @@ -3777,6 +3790,12 @@ static int mc1n2_hwdep_ioctl_notify(struct snd_soc_codec *codec, (mc1n2->hdmicount)--; } break; + case MCDRV_NOTIFY_LINEOUT_START: + mc1n2->lineoutenable = 1; + break; + case MCDRV_NOTIFY_LINEOUT_STOP: + mc1n2->lineoutenable = 0; + break; case MCDRV_NOTIFY_RECOVER: { int err, i; @@ -4335,7 +4354,7 @@ static int mc1n2_i2c_probe(struct i2c_client *client, #endif mc1n2->hdmicount = 0; - + mc1n2->lineoutenable = 0; mc1n2->pdata = client->dev.platform_data; /* setup i2c client data */ @@ -4449,7 +4468,6 @@ static int mc1n2_i2c_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_TARGET_LOCALE_KOR /* * Function to prevent tick-noise when reboot menu selected. * if you have Power-Off sound and same problem, use this function @@ -4521,7 +4539,6 @@ error: return; } -#endif static const struct i2c_device_id mc1n2_i2c_id[] = { {MC1N2_NAME, 0}, @@ -4536,9 +4553,7 @@ static struct i2c_driver mc1n2_i2c_driver = { }, .probe = mc1n2_i2c_probe, .remove = mc1n2_i2c_remove, -#ifdef CONFIG_TARGET_LOCALE_KOR .shutdown = mc1n2_i2c_shutdown, -#endif .id_table = mc1n2_i2c_id, }; diff --git a/sound/soc/codecs/mc1n2/mc1n2.h b/sound/soc/codecs/mc1n2/mc1n2.h index 4587273..8167f7c 100644 --- a/sound/soc/codecs/mc1n2/mc1n2.h +++ b/sound/soc/codecs/mc1n2/mc1n2.h @@ -100,6 +100,8 @@ struct mc1n2_ctrl_args { #define MCDRV_NOTIFY_HDMI_STOP 0x0000000B #define MCDRV_NOTIFY_RECOVER 0x0000000C #define MCDRV_NOTIFY_2MIC_CALL_START 0x0000000D +#define MCDRV_NOTIFY_LINEOUT_START 0x00000010 +#define MCDRV_NOTIFY_LINEOUT_STOP 0x00000011 #define MC1N2_MODE_IDLE (0x00) #define MC1N2_MODE_CALL_ON (0x1<<0) diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 5bbfde9..50cce1f 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -236,7 +236,6 @@ struct wm8993_priv { unsigned int sysclk_rate; unsigned int fs; unsigned int bclk; - int class_w_users; unsigned int fll_fref; unsigned int fll_fout; int fll_src; @@ -706,84 +705,6 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w, return 0; } -/* - * When used with DAC outputs only the WM8993 charge pump supports - * operation in class W mode, providing very low power consumption - * when used with digital sources. Enable and disable this mode - * automatically depending on the mixer configuration. - * - * Currently the only supported paths are the direct DAC->headphone - * paths (which provide minimum power consumption anyway). - */ -static int class_w_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_widget *widget = wlist->widgets[0]; - struct snd_soc_codec *codec = widget->codec; - struct wm8993_priv *wm8993 = snd_soc_codec_get_drvdata(codec); - int ret; - - /* Turn it off if we're using the main output mixer */ - if (ucontrol->value.integer.value[0] == 0) { - if (wm8993->class_w_users == 0) { - dev_dbg(codec->dev, "Disabling Class W\n"); - snd_soc_update_bits(codec, WM8993_CLASS_W_0, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V, - 0); - } - wm8993->class_w_users++; - wm8993->hubs_data.class_w = true; - } - - /* Implement the change */ - ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - - /* Enable it if we're using the direct DAC path */ - if (ucontrol->value.integer.value[0] == 1) { - if (wm8993->class_w_users == 1) { - dev_dbg(codec->dev, "Enabling Class W\n"); - snd_soc_update_bits(codec, WM8993_CLASS_W_0, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V, - WM8993_CP_DYN_FREQ | - WM8993_CP_DYN_V); - } - wm8993->class_w_users--; - wm8993->hubs_data.class_w = false; - } - - dev_dbg(codec->dev, "Indirect DAC use count now %d\n", - wm8993->class_w_users); - - return ret; -} - -#define SOC_DAPM_ENUM_W(xname, xenum) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_enum_double, \ - .get = snd_soc_dapm_get_enum_double, \ - .put = class_w_put, \ - .private_value = (unsigned long)&xenum } - -static const char *hp_mux_text[] = { - "Mixer", - "DAC", -}; - -static const struct soc_enum hpl_enum = - SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpl_mux = - SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum); - -static const struct soc_enum hpr_enum = - SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpr_mux = - SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum); - static const struct snd_kcontrol_new left_speaker_mixer[] = { SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), @@ -869,8 +790,8 @@ SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &sidetoner_mux), SND_SOC_DAPM_DAC("DACL", NULL, WM8993_POWER_MANAGEMENT_3, 1, 0), SND_SOC_DAPM_DAC("DACR", NULL, WM8993_POWER_MANAGEMENT_3, 0, 0), -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), @@ -1481,9 +1402,6 @@ static int wm8993_probe(struct snd_soc_codec *codec) codec->cache_only = 1; - /* By default we're using the output mixers */ - wm8993->class_w_users = 2; - /* Latch volume update bits and default ZC on */ snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME, WM8993_DAC_VU, WM8993_DAC_VU); diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 5282248..63d1a5f 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -564,7 +564,7 @@ static int wm8994_get_retune_mobile_enum(struct snd_kcontrol *kcontrol, return 0; } -#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_C1VZW) +#if defined(CONFIG_MACH_C1_KOR_LGT) static const char *fm_control[] = { "OFF", "RCV", "EAR", "SPK", "SPK", }; @@ -813,7 +813,7 @@ SOC_ENUM("AIF2DAC Noise Gate Hold Time", wm8958_aif2dac_ng_hold), SOC_SINGLE_TLV("AIF2DAC Noise Gate Threshold Volume", WM8958_AIF2_DAC_NOISE_GATE, WM8958_AIF2DAC_NG_THR_SHIFT, 7, 1, ng_tlv), -#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_C1VZW) +#if defined(CONFIG_MACH_C1_KOR_LGT) SOC_ENUM_EXT("FM Control", fm_control_enum, wm8994_get_fm_control, wm8994_put_fm_control), #endif @@ -1082,27 +1082,12 @@ static int vmid_event(struct snd_soc_dapm_widget *w, return 0; } -static void wm8994_update_class_w(struct snd_soc_codec *codec) +static bool wm8994_check_class_w_digital(struct snd_soc_codec *codec) { - struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); - int enable = 1; int source = 0; /* GCC flow analysis can't track enable */ int reg, reg_r; - /* Only support direct DAC->headphone paths */ - reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_1); - if (!(reg & WM8994_DAC1L_TO_HPOUT1L)) { - dev_vdbg(codec->dev, "HPL connected to output mixer\n"); - enable = 0; - } - - reg = snd_soc_read(codec, WM8994_OUTPUT_MIXER_2); - if (!(reg & WM8994_DAC1R_TO_HPOUT1R)) { - dev_vdbg(codec->dev, "HPR connected to output mixer\n"); - enable = 0; - } - - /* We also need the same setting for L/R and only one path */ + /* We also need the same AIF source for L/R and only one path */ reg = snd_soc_read(codec, WM8994_DAC1_LEFT_MIXER_ROUTING); switch (reg) { case WM8994_AIF2DACL_TO_DAC1L: @@ -1119,30 +1104,20 @@ static void wm8994_update_class_w(struct snd_soc_codec *codec) break; default: dev_vdbg(codec->dev, "DAC mixer setting: %x\n", reg); - enable = 0; - break; + return false; } reg_r = snd_soc_read(codec, WM8994_DAC1_RIGHT_MIXER_ROUTING); if (reg_r != reg) { dev_vdbg(codec->dev, "Left and right DAC mixers different\n"); - enable = 0; + return false; } - if (enable) { - dev_dbg(codec->dev, "Class W enabled\n"); - snd_soc_update_bits(codec, WM8994_CLASS_W_1, - WM8994_CP_DYN_PWR | - WM8994_CP_DYN_SRC_SEL_MASK, - source | WM8994_CP_DYN_PWR); - wm8994->hubs.class_w = true; + /* Set the source up */ + snd_soc_update_bits(codec, WM8994_CLASS_W_1, + WM8994_CP_DYN_SRC_SEL_MASK, source); - } else { - dev_dbg(codec->dev, "Class W disabled\n"); - snd_soc_update_bits(codec, WM8994_CLASS_W_1, - WM8994_CP_DYN_PWR, 0); - wm8994->hubs.class_w = false; - } + return true; } static int aif1clk_ev(struct snd_soc_dapm_widget *w, @@ -1289,7 +1264,7 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA, 0); - snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_4, WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA, 0); @@ -1425,45 +1400,6 @@ static int dac_ev(struct snd_soc_dapm_widget *w, return 0; } -static const char *hp_mux_text[] = { - "Mixer", - "DAC", -}; - -#define WM8994_HP_ENUM(xname, xenum) \ -{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ - .info = snd_soc_info_enum_double, \ - .get = snd_soc_dapm_get_enum_double, \ - .put = wm8994_put_hp_enum, \ - .private_value = (unsigned long)&xenum } - -static int wm8994_put_hp_enum(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); - struct snd_soc_dapm_widget *w = wlist->widgets[0]; - struct snd_soc_codec *codec = w->codec; - int ret; - - ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); - - wm8994_update_class_w(codec); - - return ret; -} - -static const struct soc_enum hpl_enum = - SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_1, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpl_mux = - WM8994_HP_ENUM("Left Headphone Mux", hpl_enum); - -static const struct soc_enum hpr_enum = - SOC_ENUM_SINGLE(WM8994_OUTPUT_MIXER_2, 8, 2, hp_mux_text); - -static const struct snd_kcontrol_new hpr_mux = - WM8994_HP_ENUM("Right Headphone Mux", hpr_enum); - static const char *adc_mux_text[] = { "ADC", "DMIC", @@ -1575,7 +1511,7 @@ static int wm8994_put_class_w(struct snd_kcontrol *kcontrol, ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); - wm8994_update_class_w(codec); + wm_hubs_update_class_w(codec); return ret; } @@ -1718,9 +1654,9 @@ SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer), late_enable_ev, SND_SOC_DAPM_PRE_PMU), -SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux, +SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux, late_enable_ev, SND_SOC_DAPM_PRE_PMU), -SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux, +SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux, late_enable_ev, SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev) @@ -1736,8 +1672,8 @@ SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &wm_hubs_hpr_mux), }; static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = { @@ -3940,7 +3876,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->hubs.dcs_readback_mode = 2; wm8994->hubs.no_series_update = 1; wm8994->hubs.hp_startup_mode = 1; - wm8994->hubs.no_cache_class_w = true; + wm8994->hubs.no_cache_dac_hp_direct = true; wm8994->fll_byp = true; switch (wm8994->revision) { @@ -4169,7 +4105,8 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; } - wm8994_update_class_w(codec); + wm8994->hubs.check_class_w_digital = wm8994_check_class_w_digital; + wm_hubs_update_class_w(codec); wm8994_handle_pdata(wm8994); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 92c5cda..130f650 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -110,12 +110,103 @@ irqreturn_t wm_hubs_dcs_done(int irq, void *data) } EXPORT_SYMBOL_GPL(wm_hubs_dcs_done); +static bool wm_hubs_dac_hp_direct(struct snd_soc_codec *codec) +{ + int reg; + + /* If we're going via the mixer we'll need to do additional checks */ + reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER1); + if (!(reg & WM8993_DACL_TO_HPOUT1L)) { + if (reg & ~WM8993_DACL_TO_MIXOUTL) { + dev_vdbg(codec->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACL_TO_HPOUT1L); + return false; + } else { + dev_vdbg(codec->dev, "HPL connected to mixer\n"); + } + } else { + dev_vdbg(codec->dev, "HPL connected to DAC\n"); + } + + reg = snd_soc_read(codec, WM8993_OUTPUT_MIXER2); + if (!(reg & WM8993_DACR_TO_HPOUT1R)) { + if (reg & ~WM8993_DACR_TO_MIXOUTR) { + dev_vdbg(codec->dev, "Analogue paths connected: %x\n", + reg & ~WM8993_DACR_TO_HPOUT1R); + return false; + } else { + dev_vdbg(codec->dev, "HPR connected to mixer\n"); + } + } else { + dev_vdbg(codec->dev, "HPR connected to DAC\n"); + } + + return true; +} + +struct wm_hubs_dcs_cache { + struct list_head list; + unsigned int left; + unsigned int right; + u16 dcs_cfg; +}; + +static bool wm_hubs_dcs_cache_get(struct snd_soc_codec *codec, + struct wm_hubs_dcs_cache **entry) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; + unsigned int left, right; + + left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); + left &= WM8993_HPOUT1L_VOL_MASK; + + right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); + right &= WM8993_HPOUT1R_VOL_MASK; + + list_for_each_entry(cache, &hubs->dcs_cache, list) { + if (cache->left != left || cache->right != right) + continue; + + *entry = cache; + return true; + } + + return false; +} + +static void wm_hubs_dcs_cache_set(struct snd_soc_codec *codec, u16 dcs_cfg) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; + + if (hubs->no_cache_dac_hp_direct) + return; + + cache = devm_kzalloc(codec->dev, sizeof(*cache), GFP_KERNEL); + if (!cache) { + dev_err(codec->dev, "Failed to allocate DCS cache entry\n"); + return; + } + + cache->left = snd_soc_read(codec, WM8993_LEFT_OUTPUT_VOLUME); + cache->left &= WM8993_HPOUT1L_VOL_MASK; + + cache->right = snd_soc_read(codec, WM8993_RIGHT_OUTPUT_VOLUME); + cache->right &= WM8993_HPOUT1R_VOL_MASK; + + cache->dcs_cfg = dcs_cfg; + + list_add_tail(&cache->list, &hubs->dcs_cache); +} + /* * Startup calibration of the DC servo */ static void calibrate_dc_servo(struct snd_soc_codec *codec) { struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + struct wm_hubs_dcs_cache *cache; s8 offset; u16 reg, reg_l, reg_r, dcs_cfg, dcs_reg; @@ -130,10 +221,11 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) /* If we're using a digital only path and have a previously * callibrated DC servo offset stored then use that. */ - if (hubs->class_w && hubs->class_w_dcs) { - dev_dbg(codec->dev, "Using cached DC servo offset %x\n", - hubs->class_w_dcs); - snd_soc_write(codec, dcs_reg, hubs->class_w_dcs); + if (wm_hubs_dac_hp_direct(codec) && + wm_hubs_dcs_cache_get(codec, &cache)) { + dev_dbg(codec->dev, "Using cached DCS offset %x for %d,%d\n", + cache->dcs_cfg, cache->left, cache->right); + snd_soc_write(codec, dcs_reg, cache->dcs_cfg); wait_for_dc_servo(codec, WM8993_DCS_TRIG_DAC_WR_0 | WM8993_DCS_TRIG_DAC_WR_1); @@ -208,8 +300,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) /* Save the callibrated offset if we're in class W mode and * therefore don't have any analogue signal mixed in. */ - if (hubs->class_w && !hubs->no_cache_class_w) - hubs->class_w_dcs = dcs_cfg; + if (wm_hubs_dac_hp_direct(codec)) + wm_hubs_dcs_cache_set(codec, dcs_cfg); } /* @@ -224,9 +316,6 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); - /* Updating the analogue gains invalidates the DC servo cache */ - hubs->class_w_dcs = 0; - /* If we're applying an offset correction then updating the * callibration would be likely to introduce further offsets. */ if (hubs->dcs_codes_l || hubs->dcs_codes_r || hubs->no_series_update) @@ -539,6 +628,86 @@ static int lineout_event(struct snd_soc_dapm_widget *w, return 0; } +void wm_hubs_update_class_w(struct snd_soc_codec *codec) +{ + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); + int enable = WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ; + + if (!wm_hubs_dac_hp_direct(codec)) + enable = false; + + if (hubs->check_class_w_digital && !hubs->check_class_w_digital(codec)) + enable = false; + + dev_vdbg(codec->dev, "Class W %s\n", enable ? "enabled" : "disabled"); + + snd_soc_update_bits(codec, WM8993_CLASS_W_0, + WM8993_CP_DYN_V | WM8993_CP_DYN_FREQ, enable); +} +EXPORT_SYMBOL_GPL(wm_hubs_update_class_w); + +#define WM_HUBS_SINGLE_W(xname, reg, shift, max, invert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_dapm_get_volsw, .put = class_w_put_volsw, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +static int class_w_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + int ret; + + ret = snd_soc_dapm_put_volsw(kcontrol, ucontrol); + + wm_hubs_update_class_w(codec); + + return ret; +} + +#define WM_HUBS_ENUM_W(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = class_w_put_double, \ + .private_value = (unsigned long)&xenum } + +static int class_w_put_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; + int ret; + + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + wm_hubs_update_class_w(codec); + + return ret; +} + +static const char *hp_mux_text[] = { + "Mixer", + "DAC", +}; + +static const struct soc_enum hpl_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpl_mux = + WM_HUBS_ENUM_W("Left Headphone Mux", hpl_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpl_mux); + +static const struct soc_enum hpr_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); + +const struct snd_kcontrol_new wm_hubs_hpr_mux = + WM_HUBS_ENUM_W("Right Headphone Mux", hpr_enum); +EXPORT_SYMBOL_GPL(wm_hubs_hpr_mux); + static const struct snd_kcontrol_new in1l_pga[] = { SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), @@ -570,25 +739,25 @@ SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), }; static const struct snd_kcontrol_new left_output_mixer[] = { -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), -SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +WM_HUBS_SINGLE_W("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), }; static const struct snd_kcontrol_new right_output_mixer[] = { -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), -SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +WM_HUBS_SINGLE_W("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +WM_HUBS_SINGLE_W("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +WM_HUBS_SINGLE_W("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +WM_HUBS_SINGLE_W("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +WM_HUBS_SINGLE_W("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +WM_HUBS_SINGLE_W("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +WM_HUBS_SINGLE_W("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +WM_HUBS_SINGLE_W("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), }; static const struct snd_kcontrol_new earpiece_mixer[] = { @@ -652,8 +821,6 @@ SND_SOC_DAPM_INPUT("IN2RP:VXRP"), SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0), SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0), -SND_SOC_DAPM_SUPPLY("LINEOUT_VMID_BUF", WM8993_ANTIPOP1, 7, 0, NULL, 0), - SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, in1l_pga, ARRAY_SIZE(in1l_pga)), SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, @@ -878,11 +1045,9 @@ static const struct snd_soc_dapm_route lineout1_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout1_se_routes[] = { - { "LINEOUT1N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Mixer", "Right Output Switch", "Right Output PGA" }, - { "LINEOUT1P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT1P Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, @@ -899,11 +1064,9 @@ static const struct snd_soc_dapm_route lineout2_diff_routes[] = { }; static const struct snd_soc_dapm_route lineout2_se_routes[] = { - { "LINEOUT2N Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2N Mixer", "Left Output Switch", "Left Output PGA" }, { "LINEOUT2N Mixer", "Right Output Switch", "Right Output PGA" }, - { "LINEOUT2P Mixer", NULL, "LINEOUT_VMID_BUF" }, { "LINEOUT2P Mixer", "Right Output Switch", "Right Output PGA" }, { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, @@ -958,6 +1121,7 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec, struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; + INIT_LIST_HEAD(&hubs->dcs_cache); init_completion(&hubs->dcs_done); snd_soc_dapm_add_routes(dapm, analogue_routes, diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index 5705276..da2dc89 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -16,6 +16,8 @@ #include <linux/completion.h> #include <linux/interrupt.h> +#include <linux/list.h> +#include <sound/control.h> struct snd_soc_codec; @@ -30,9 +32,9 @@ struct wm_hubs_data { int series_startup; int no_series_update; - bool no_cache_class_w; - bool class_w; - u16 class_w_dcs; + bool no_cache_dac_hp_direct; + struct list_head dcs_cache; + bool (*check_class_w_digital)(struct snd_soc_codec *); bool lineout1_se; bool lineout1n_ena; @@ -58,5 +60,9 @@ extern irqreturn_t wm_hubs_dcs_done(int irq, void *data); extern void wm_hubs_vmid_ena(struct snd_soc_codec *codec); extern void wm_hubs_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level); +extern void wm_hubs_update_class_w(struct snd_soc_codec *codec); + +extern const struct snd_kcontrol_new wm_hubs_hpl_mux; +extern const struct snd_kcontrol_new wm_hubs_hpr_mux; #endif diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig index e040010..de7cd13 100644 --- a/sound/soc/samsung/Kconfig +++ b/sound/soc/samsung/Kconfig @@ -77,13 +77,39 @@ config SND_SOC_SAMSUNG_MIDAS_WM1811 help Say Y if you want to add support for SoC audio on the MIDAS. -config SND_SOC_SAMSUNG_SLP_MIDAS_WM1811 +config SND_SOC_SAMSUNG_GRANDE_WM1811 + tristate "SoC I2S Audio support for WM1811 on Grande" + depends on SND_SOC_SAMSUNG && (MACH_M0_GRANDECTC || MACH_IRON || MACH_M0_DUOSCTC ) + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + +config SND_SOC_SAMSUNG_T0_WM1811 + tristate "SoC I2S Audio support for WM1811 on T0" + depends on SND_SOC_SAMSUNG && MACH_T0 + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on the T0. + +config SND_SOC_SAMSUNG_T0DUOS_WM1811 + tristate "SoC I2S Audio support for WM1811 on T0DUOS" + depends on SND_SOC_SAMSUNG && (MACH_T0_CHN_CTC || MACH_T0_CHN_CMCC || MACH_T0_CHN_OPEN_DUOS || MACH_T0_CHN_CU_DUOS) + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + +config SND_SOC_SAMSUNG_M3_WM1811 + tristate "SoC I2S Audio support for WM1811 on M3" + depends on SND_SOC_SAMSUNG && MACH_M3 + select SND_SOC_WM8994 + select SND_SAMSUNG_I2S + +config SND_SOC_SAMSUNG_SLP_EXYNOS_WM1811 tristate "SoC I2S Audio support for WM1811 on SLP MIDAS" - depends on SND_SOC_SAMSUNG && (MACH_SLP_MIDAS || MACH_SLP_MIDAS_D || MACH_SLP_MIDAS_Q || MACH_SLP_PQ || MACH_SLP_PQ_LTE) + depends on SND_SOC_SAMSUNG && (MACH_SLP_PQ || MACH_SLP_PQ_LTE || MACH_REDWOOD || MACH_SLP_T0_LTE) select SND_SOC_WM8994 select SND_SAMSUNG_I2S help - Say Y if you want to add support for SoC audio on the MIDAS. + Say Y if you want to add support for SoC audio on the SLP Exynos PQ_Proxima, Redwood. config SND_SOC_SAMSUNG_SLP_NAPLES_WM1811 tristate "SoC I2S Audio support for WM1811 on SLP NAPLES" @@ -107,6 +133,17 @@ config SND_SOC_U1_MC1N2 help Say Y if you want to add support for SoC audio on U1. +config SND_SOC_SLP_TRATS_MC1N2 + tristate "SoC I2S Audio support for MC1N2 on SLP TRATS" + depends on SND_SOC_SAMSUNG && (MACH_TRATS) + select SND_SOC_MC1N2 + select SND_SAMSUNG_I2S + help + Say Y if you want to add support for SoC audio on SLP TRATS. + This uses + Samsung Exynos-4210 audio subsystem + and Yamaha MC1N2 codec. + config SND_SOC_SAMSUNG_USE_DMA_WRAPPER bool "DMA wrapper for ALSA Platform(DMA)" @@ -273,6 +310,10 @@ config SND_SOC_SPEYSIDE select SND_SOC_WM8915 select SND_SOC_WM9081 +config SND_DUOS_MODEM_SWITCH + bool "Select DUOS Modem" + depends on SND_SOC_SAMSUNG + config SND_USE_SUB_MIC bool "Use SUB_MIC" depends on SND_SOC_SAMSUNG @@ -293,4 +334,4 @@ config SND_USE_LINEOUT_SWITCH config SND_USE_MUIC_SWITCH bool "Use MUIC_SWITCH CONTROL" - depends on SND_SOC_SAMSUNG
\ No newline at end of file + depends on SND_SOC_SAMSUNG diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile index f8a7d95..5891ebe 100644 --- a/sound/soc/samsung/Makefile +++ b/sound/soc/samsung/Makefile @@ -44,8 +44,16 @@ snd-soc-smdk-spdif-objs := smdk_spdif.o snd-soc-smdk-wm8580pcm-objs := smdk_wm8580pcm.o snd-soc-speyside-objs := speyside.o snd-soc-u1-mc1n2-objs := u1_mc1n2.o +snd-soc-slp-trats-mc1n2-objs := slp_trats_mc1n2.o +snd-soc-slp-jack-objs := slp_jack.o snd-soc-midas-wm1811-objs := midas_wm1811.o +snd-soc-t0-wm1811-objs := t0_wm1811.o +snd-soc-t0duos-wm1811-objs := t0duos_wm1811.o +snd-soc-m3-wm1811-objs := m3_wm1811.o +snd-soc-slp-exynos-wm1811-objs := slp_exynos_wm1811.o +snd-soc-slp-naples-wm1811-objs := slp_naples_wm1811.o snd-soc-lungo-wm1811-objs := lungo_wm1811.o +snd-soc-grande-wm1811-objs := grande_wm1811.o obj-$(CONFIG_SND_SOC_SAMSUNG_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_SOC_SAMSUNG_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -67,8 +75,16 @@ obj-$(CONFIG_SND_SOC_GONI_AQUILA_WM8994) += snd-soc-goni-wm8994.o obj-$(CONFIG_SND_SOC_SMDK_WM8580_PCM) += snd-soc-smdk-wm8580pcm.o obj-$(CONFIG_SND_SOC_SPEYSIDE) += snd-soc-speyside.o obj-$(CONFIG_SND_SOC_U1_MC1N2) += snd-soc-u1-mc1n2.o +obj-$(CONFIG_SND_SOC_SLP_TRATS_MC1N2) += snd-soc-slp-trats-mc1n2.o +obj-$(CONFIG_SND_SOC_SLP_TRATS_MC1N2) += snd-soc-slp-jack.o obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_T0_WM1811) += snd-soc-t0-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_T0DUOS_WM1811) += snd-soc-t0duos-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_M3_WM1811) += snd-soc-m3-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SLP_EXYNOS_WM1811) += snd-soc-slp-exynos-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_SLP_NAPLES_WM1811) += snd-soc-slp-naples-wm1811.o obj-$(CONFIG_SND_SOC_SAMSUNG_LUNGO_WM1811) += snd-soc-midas-wm1811.o +obj-$(CONFIG_SND_SOC_SAMSUNG_GRANDE_WM1811) += snd-soc-grande-wm1811.o obj-$(CONFIG_SND_SAMSUNG_RP) += srp_ulp/ obj-$(CONFIG_SND_SAMSUNG_ALP) += srp_alp/ diff --git a/sound/soc/samsung/audss.c b/sound/soc/samsung/audss.c index 1c052b0..a5300de 100644 --- a/sound/soc/samsung/audss.c +++ b/sound/soc/samsung/audss.c @@ -52,13 +52,27 @@ static DEFINE_SPINLOCK(lock); static int audss_clk_div_init(struct clk *src_clk) { - u32 clk_div = readl(S5P_CLKDIV_AUDSS); + struct clk *fout_epll; u32 src_clk_rate = 0; - u32 srp_div = 1; - u32 bus_div = 2; - u32 i2s_div = 2; + u64 srp_rate = 0; + u64 bus_rate = 0; + u64 i2s_rate = 0; + u32 srp_div; + u32 bus_div; + u32 i2s_div; u32 ret = -1; + writel(0xF84, S5P_CLKDIV_AUDSS); /* to avoid over-clock */ + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + pr_err("%s: failed to get fout_epll\n", __func__); + return ret; + } + clk_set_parent(audss.mout_audss, fout_epll); + + pr_debug("%s: CLKSRC[0x%x]\n", __func__, readl(S5P_CLKSRC_AUDSS)); + src_clk_rate = clk_get_rate(src_clk); if (!src_clk_rate) { pr_err("%s: Can't get current clk_rate %d\n", @@ -67,29 +81,32 @@ static int audss_clk_div_init(struct clk *src_clk) } pr_debug("%s: SRC Clock Rate[%d]\n", __func__, src_clk_rate); - if (src_clk_rate > 100000000) { - srp_div <<= 1; - bus_div <<= 1; - i2s_div <<= 1; - } - if (!strcmp(audss.rclksrc, "busclk")) - i2s_div = 16; /* Use max div */ + for (srp_div = 1; srp_div <= 16; srp_div++) { + if ((src_clk_rate >> (srp_div - 1)) <= 100000000) + break; + } - clk_div &= ~(S5P_AUDSS_CLKDIV_RP_MASK - | S5P_AUDSS_CLKDIV_BUSCLK_MASK - | S5P_AUDSS_CLKDIV_I2SCLK_MASK); + for (bus_div = 1; bus_div <= 16; bus_div++) { + if ((src_clk_rate >> (bus_div - 1)) <= 50000000) + break; + } - if (srp_div) - clk_div |= (srp_div - 1) << S5P_AUDSS_CLKDIV_RP_SHIFT; + for (i2s_div = 1; i2s_div <= 16; i2s_div++) { + if ((src_clk_rate >> (i2s_div - 1)) <= 50000000) + break; + } - if (bus_div) - clk_div |= (bus_div - 1) << S5P_AUDSS_CLKDIV_BUSCLK_SHIFT; + if (!strcmp(audss.rclksrc, "busclk")) + i2s_div = 16; /* Use max div */ - if (i2s_div) - clk_div |= (i2s_div - 1) << S5P_AUDSS_CLKDIV_I2SCLK_SHIFT; + srp_rate = src_clk_rate >> (srp_div - 1); + bus_rate = src_clk_rate >> (bus_div - 1); + i2s_rate = src_clk_rate >> (i2s_div - 1); - writel(clk_div, S5P_CLKDIV_AUDSS); + clk_set_rate(audss.dout_srp, srp_rate); + clk_set_rate(audss.bus_clk, bus_rate); + clk_set_rate(audss.i2s_clk, i2s_rate); pr_debug("%s: BUSCLK[%ld], I2SCLK[%ld]\n", __func__, clk_get_rate(audss.bus_clk), diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index a7178ed..63c475e 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -222,11 +222,19 @@ static int dma_hw_free(struct snd_pcm_substream *substream) /* TODO - do we need to ensure DMA flushed */ snd_pcm_set_runtime_buffer(substream, NULL); +#ifdef CONFIG_SLP_WIP + spin_lock(&prtd->lock); +#endif + if (prtd->params) { s3c2410_dma_free(prtd->params->channel, prtd->params->client); prtd->params = NULL; } +#ifdef CONFIG_SLP_WIP + spin_unlock(&prtd->lock); +#endif + return 0; } diff --git a/sound/soc/samsung/grande_wm1811.c b/sound/soc/samsung/grande_wm1811.c new file mode 100644 index 0000000..304b592 --- /dev/null +++ b/sound/soc/samsung/grande_wm1811.c @@ -0,0 +1,1715 @@ +/* + * grande_wm1811.c + * + * Copyright (c) 2011 Samsung Electronics Co. Ltd + * + * 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/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/suspend.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> + +#include <mach/regs-clock.h> +#include <mach/pmu.h> +#include <mach/midas-sound.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/gpio.h> + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) +#include <linux/mfd/max77693-private.h> +#endif + + +#include "i2s.h" +#include "s3c-i2s-v2.h" +#include "../codecs/wm8994.h" + + +#define MIDAS_DEFAULT_MCLK1 24000000 +#define MIDAS_DEFAULT_MCLK2 32768 +#define MIDAS_DEFAULT_SYNC_CLK 11289600 + +#define WM1811_JACKDET_MODE_NONE 0x0000 +#define WM1811_JACKDET_MODE_JACK 0x0100 +#define WM1811_JACKDET_MODE_MIC 0x0080 +#define WM1811_JACKDET_MODE_AUDIO 0x0180 + +#define WM1811_JACKDET_BTN0 0x04 +#define WM1811_JACKDET_BTN1 0x10 +#define WM1811_JACKDET_BTN2 0x08 + + +static struct wm8958_micd_rate midas_det_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 }, +}; + +static struct wm8958_micd_rate midas_jackdet_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 11, 11 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, +}; + +static int aif2_mode; +const char *aif2_mode_text[] = { + "Slave", "Master" +}; + +static int kpcs_mode = 2; +const char *kpcs_mode_text[] = { + "Off", "On" +}; + +static int input_clamp; +const char *input_clamp_text[] = { + "Off", "On" +}; + +static int lineout_mode; +const char *lineout_mode_text[] = { + "Off", "On" +}; + +static int modem_mode; +const char *modem_mode_text[] = { + "CP1", "CP2" +}; + + +#ifndef CONFIG_SEC_DEV_JACK +/* To support PBA function test */ +static struct class *jack_class; +static struct device *jack_dev; +#endif + +#ifdef SND_USE_BIAS_LEVEL +static bool midas_fll1_active; +struct snd_soc_dai *midas_aif1_dai; +#endif + +struct wm1811_machine_priv { + struct snd_soc_jack jack; + struct snd_soc_codec *codec; + struct delayed_work mic_work; + struct wake_lock jackdet_wake_lock; +}; + + +static void midas_gpio_init(void) +{ + int err; +#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS + /* Main Microphone BIAS */ + err = gpio_request(GPIO_MIC_BIAS_EN, "MAIN MIC"); + if (err) { + pr_err(KERN_ERR "MIC_BIAS_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_MIC_BIAS_EN, 1); + gpio_set_value(GPIO_MIC_BIAS_EN, 0); + gpio_free(GPIO_MIC_BIAS_EN); +#endif + +#ifdef CONFIG_SND_USE_SUB_MIC + /* Sub Microphone BIAS */ + err = gpio_request(GPIO_SUB_MIC_BIAS_EN, "SUB MIC"); + if (err) { + pr_err(KERN_ERR "SUB_MIC_BIAS_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_SUB_MIC_BIAS_EN, 1); + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0); + gpio_free(GPIO_SUB_MIC_BIAS_EN); +#endif + +#ifdef CONFIG_SND_USE_THIRD_MIC + /* Third Microphone BIAS */ + err = gpio_request(GPIO_THIRD_MIC_BIAS_EN, "THIRD MIC"); + if (err) { + pr_err(KERN_ERR "THIRD_MIC_BIAS_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_THIRD_MIC_BIAS_EN, 1); + gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 0); + gpio_free(GPIO_THIRD_MIC_BIAS_EN); +#endif + +#ifdef CONFIG_FM_RADIO + /* FM/Third Mic GPIO */ + err = gpio_request(GPIO_FM_MIC_SW, "GPL0"); + if (err) { + pr_err(KERN_ERR "FM/THIRD_MIC Switch GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_FM_MIC_SW, 1); + gpio_set_value(GPIO_FM_MIC_SW, 0); + gpio_free(GPIO_FM_MIC_SW); +#endif + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + err = gpio_request(GPIO_LINEOUT_EN, "LINEOUT_EN"); + if (err) { + pr_err(KERN_ERR "LINEOUT_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_LINEOUT_EN, 1); + gpio_set_value(GPIO_LINEOUT_EN, 0); + gpio_free(GPIO_LINEOUT_EN); +#endif + + err = gpio_request(GPIO_AUDIO_PCM_SEL, "AUDIO_PCM_SEL"); + if (err) { + pr_err(KERN_ERR "AUDIO_PCM_SEL GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_AUDIO_PCM_SEL, 1); + gpio_set_value(GPIO_AUDIO_PCM_SEL, 0); + gpio_free(GPIO_AUDIO_PCM_SEL); +} + + +static const struct soc_enum modem_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(modem_mode_text), modem_mode_text), +}; + +static int get_modem_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = modem_mode; + return 0; +} + +static int set_modem_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + modem_mode = ucontrol->value.integer.value[0]; + + if (modem_mode) { + gpio_set_value(GPIO_AUDIO_PCM_SEL, 1); + } else { + gpio_set_value(GPIO_AUDIO_PCM_SEL, 0); + /*msleep(50);*/ + } + dev_info(codec->dev, "set modem select : %s\n", + modem_mode_text[modem_mode]); + return 0; + +} + +static const struct soc_enum lineout_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text), +}; + +static int get_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lineout_mode; + return 0; +} + +static int set_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + lineout_mode = ucontrol->value.integer.value[0]; + dev_dbg(codec->dev, "set lineout mode : %s\n", + lineout_mode_text[lineout_mode]); + return 0; + +} +static const struct soc_enum aif2_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), +}; + +static const struct soc_enum kpcs_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(kpcs_mode_text), kpcs_mode_text), +}; + +static const struct soc_enum input_clamp_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text), +}; + +static int get_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aif2_mode; + return 0; +} + +static int set_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (aif2_mode == ucontrol->value.integer.value[0]) + return 0; + + aif2_mode = ucontrol->value.integer.value[0]; + + pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]); + + return 0; +} + +static int get_kpcs_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = kpcs_mode; + return 0; +} + +static int set_kpcs_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + + kpcs_mode = ucontrol->value.integer.value[0]; + + pr_info("set kpcs mode : %d\n", kpcs_mode); + + return 0; +} + +static int get_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = input_clamp; + return 0; +} + +static int set_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + input_clamp = ucontrol->value.integer.value[0]; + + if (input_clamp) { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP); + msleep(100); + } else { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, 0); + } + pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]); + + return 0; +} + + +static int midas_ext_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_dbg(codec->dev, "%s event is %02X", w->name, event); + +#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpio_set_value(GPIO_MIC_BIAS_EN, 1); + msleep(150); + break; + case SND_SOC_DAPM_POST_PMD: + gpio_set_value(GPIO_MIC_BIAS_EN, 0); + break; + } +#endif + return 0; +} + +static int midas_ext_submicbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_dbg(codec->dev, "%s event is %02X", w->name, event); + +#ifdef CONFIG_SND_USE_SUB_MIC + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 1); + msleep(150); + break; + case SND_SOC_DAPM_POST_PMD: + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0); + break; + } +#endif + return 0; +} + +static int midas_ext_thirdmicbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_dbg(codec->dev, "%s event is %02X", w->name, event); + +#ifdef CONFIG_SND_USE_THIRD_MIC + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 1); + break; + case SND_SOC_DAPM_POST_PMD: + gpio_set_value(GPIO_THIRD_MIC_BIAS_EN, 0); + break; + } +#endif + return 0; +} + +/* + * midas_ext_spkmode : + * For phone device have 1 external speaker + * should mix LR data in a speaker mixer (mono setting) + */ +static int midas_ext_spkmode(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + int ret = 0; +#ifndef CONFIG_SND_USE_STEREO_SPEAKER + struct snd_soc_codec *codec = w->codec; + + ret = snd_soc_update_bits(codec, WM8994_SPKOUT_MIXERS, + WM8994_SPKMIXR_TO_SPKOUTL_MASK, + WM8994_SPKMIXR_TO_SPKOUTL); +#endif + return ret; +} + +static int midas_lineout_switch(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_dbg(codec->dev, "%s event is %02X", w->name, event); + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(150); + max77693_muic_set_audio_switch(1); + break; + case SND_SOC_DAPM_PRE_PMD: + max77693_muic_set_audio_switch(0); + break; + } +#endif + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + switch (event) { + case SND_SOC_DAPM_POST_PMU: + gpio_set_value(GPIO_LINEOUT_EN, 1); + break; + case SND_SOC_DAPM_PRE_PMD: + gpio_set_value(GPIO_LINEOUT_EN, 0); + break; + } +#endif + return 0; +} + +static void midas_micd_set_rate(struct snd_soc_codec *codec) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int best, i, sysclk, val; + bool idle; + const struct wm8958_micd_rate *rates = NULL; + int num_rates = 0; + + idle = !wm8994->jack_mic; + + sysclk = snd_soc_read(codec, WM8994_CLOCKING_1); + if (sysclk & WM8994_SYSCLK_SRC) + sysclk = wm8994->aifclk[1]; + else + sysclk = wm8994->aifclk[0]; + + if (wm8994->jackdet) { + rates = midas_jackdet_rates; + num_rates = ARRAY_SIZE(midas_jackdet_rates); + wm8994->pdata->micd_rates = midas_jackdet_rates; + wm8994->pdata->num_micd_rates = num_rates; + } else { + rates = midas_det_rates; + num_rates = ARRAY_SIZE(midas_det_rates); + wm8994->pdata->micd_rates = midas_det_rates; + wm8994->pdata->num_micd_rates = num_rates; + } + + best = 0; + for (i = 0; i < num_rates; i++) { + if (rates[i].idle != idle) + continue; + if (abs(rates[i].sysclk - sysclk) < + abs(rates[best].sysclk - sysclk)) + best = i; + else if (rates[best].idle != idle) + best = i; + } + + val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT + | rates[best].rate << WM8958_MICD_RATE_SHIFT; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_BIAS_STARTTIME_MASK | + WM8958_MICD_RATE_MASK, val); +} + +#ifdef SND_USE_BIAS_LEVEL +static void midas_start_fll1(struct snd_soc_dai *aif1_dai) +{ + int ret; + if (midas_fll1_active) + return; + + dev_info(aif1_dai->dev, "Moving to audio clocking settings\n"); + + /* Switch AIF1 to MCLK2 while we bring stuff up */ + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to MCLK2: %d\n", ret); + + /* Start the 24MHz clock to provide a high frequency reference to + * provide a high frequency reference for the FLL, giving improved + * performance. + */ + midas_snd_set_mclk(true, true); + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1, + MIDAS_DEFAULT_SYNC_CLK); + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_FLL1, + MIDAS_DEFAULT_SYNC_CLK, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); + + midas_fll1_active = true; +} +#endif + +static void midas_micdet(u16 status, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + int report; + + + wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ); + + /* Either nothing present or just starting detection */ + if (!(status & WM8958_MICD_STS)) { + if (!wm8994->jackdet) { + /* If nothing present then clear our statuses */ + dev_dbg(wm1811->codec->dev, "Detected open circuit\n"); + wm8994->jack_mic = false; + wm8994->mic_detecting = true; + + midas_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); + } + /*ToDo*/ + /*return;*/ + } + + /* If the measurement is showing a high impedence we've got a + * microphone. + */ + if (wm8994->mic_detecting && (status & 0x400)) { + dev_info(wm1811->codec->dev, "Detected microphone\n"); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + midas_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + + if (wm8994->mic_detecting && status & 0x4) { + dev_info(wm1811->codec->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + midas_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); + + /* If we have jackdet that will detect removal */ + if (wm8994->jackdet) { + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(wm1811->codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } + + mutex_unlock(&wm8994->accdet_lock); + + if (wm8994->pdata->jd_ext_cap) { + mutex_lock(&wm1811->codec->mutex); + snd_soc_dapm_disable_pin(&wm1811->codec->dapm, + "MICBIAS2"); + snd_soc_dapm_sync(&wm1811->codec->dapm); + mutex_unlock(&wm1811->codec->mutex); + } + } + } + + /* Report short circuit as a button */ + if (wm8994->jack_mic) { + report = 0; + if (status & WM1811_JACKDET_BTN0) + report |= SND_JACK_BTN_0; + + if (status & WM1811_JACKDET_BTN1) + report |= SND_JACK_BTN_1; + + if (status & WM1811_JACKDET_BTN2) + report |= SND_JACK_BTN_2; + + dev_dbg(wm1811->codec->dev, "Detected Button: %08x (%08X)\n", + report, status); + + snd_soc_jack_report(wm8994->micdet[0].jack, report, + wm8994->btn_mask); + } +} + +#ifdef CONFIG_SND_SAMSUNG_I2S_MASTER +static int set_epll_rate(unsigned long rate) +{ + struct clk *fout_epll; + + fout_epll = clk_get(NULL, "fout_epll"); + if (IS_ERR(fout_epll)) { + printk(KERN_ERR "%s: failed to get fout_epll\n", __func__); + return -ENOENT; + } + + if (rate == clk_get_rate(fout_epll)) + goto out; + + clk_set_rate(fout_epll, rate); +out: + clk_put(fout_epll); + + return 0; +} +#endif /* CONFIG_SND_SAMSUNG_I2S_MASTER */ + +#ifndef CONFIG_SND_SAMSUNG_I2S_MASTER +static int midas_wm1811_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + 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) + return ret; + + /* Set the 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) + return ret; + +#ifndef SND_USE_BIAS_LEVEL + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1, + pll_out); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to start FLL1: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Unable to switch to FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, + 0, MOD_OPCLK_PCLK); + if (ret < 0) + return ret; +#else + midas_start_fll1(codec_dai); +#endif + + if (ret < 0) + return ret; + + dev_info(codec_dai->dev, "%s --\n", __func__); + + return 0; +} +#else /* CONFIG_SND_SAMSUNG_I2S_MASTER */ +static int midas_wm1811_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_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int bfs, psr, rfs, ret; + unsigned long rclk; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U24: + case SNDRV_PCM_FORMAT_S24: + bfs = 48; + break; + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + bfs = 32; + break; + default: + return -EINVAL; + } + + switch (params_rate(params)) { + case 16000: + case 22050: + case 24000: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + if (bfs == 48) + rfs = 384; + else + rfs = 256; + break; + case 64000: + rfs = 384; + break; + case 8000: + case 11025: + case 12000: + if (bfs == 48) + rfs = 768; + else + rfs = 512; + break; + default: + return -EINVAL; + } + + rclk = params_rate(params) * rfs; + + switch (rclk) { + case 4096000: + case 5644800: + case 6144000: + case 8467200: + case 9216000: + psr = 8; + break; + case 8192000: + case 11289600: + case 12288000: + case 16934400: + case 18432000: + psr = 4; + break; + case 22579200: + case 24576000: + case 33868800: + case 36864000: + psr = 2; + break; + case 67737600: + case 73728000: + psr = 1; + break; + default: + printk(KERN_INFO "Not yet supported!\n"); + return -EINVAL; + } + + set_epll_rate(rclk * psr); + + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_MCLK1, + rclk, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_CDCLK, + 0, SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, SAMSUNG_I2S_DIV_BCLK, bfs); + if (ret < 0) + return ret; + + return 0; +} +#endif /* CONFIG_SND_SAMSUNG_I2S_MASTER */ + +/* + * Midas WM1811 DAI operations. + */ +static struct snd_soc_ops midas_wm1811_aif1_ops = { + .hw_params = midas_wm1811_aif1_hw_params, +}; + +static int midas_wm1811_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_dai *codec_dai = rtd->codec_dai; + int ret; + int prate; + int bclk; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + prate = params_rate(params); + switch (params_rate(params)) { + case 8000: + case 16000: + break; + default: + dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8000Hz\n", + (int)params_rate(params)); + prate = 8000; + } + +#if defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_M0_DUOSCTC) + if (aif2_mode == 0) + /* Set the codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#elif defined(CONFIG_MACH_IRON) + /* Set the codec DAI configuration, aif2_mode:0 is slave */ + /* modem_mode:1 is CP2 */ + if (aif2_mode == 0) { + if (modem_mode == 1) + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + } else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#else + /* Set the codec DAI configuration, aif2_mode:0 is slave */ + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#endif + + if (ret < 0) + return ret; + +#if defined(CONFIG_MACH_GRANDE) || defined(CONFIG_MACH_M0_DUOSCTC) + bclk = 2048000; +#elif defined(CONFIG_MACH_IRON) + if (modem_mode == 1) + bclk = 2048000; + else + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } +#else + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } +#endif + +#ifdef SND_USE_BIAS_LEVEL + if (!midas_fll1_active) + midas_start_fll1(midas_aif1_dai); +#endif + + if (aif2_mode == 0) { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, + bclk, prate * 256); + } else { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, prate * 256); + } + + if (ret < 0) + dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + prate * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); + + dev_info(codec_dai->dev, "%s --\n", __func__); + return 0; +} + +static struct snd_soc_ops midas_wm1811_aif2_ops = { + .hw_params = midas_wm1811_aif2_hw_params, +}; + +static int midas_wm1811_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + pr_err("%s: enter\n", __func__); + return 0; +} + +static struct snd_soc_ops midas_wm1811_aif3_ops = { + .hw_params = midas_wm1811_aif3_hw_params, +}; + +static const struct snd_kcontrol_new midas_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("FM In"), + SOC_DAPM_PIN_SWITCH("LINE"), + SOC_DAPM_PIN_SWITCH("HDMI"), + SOC_DAPM_PIN_SWITCH("Main Mic"), + SOC_DAPM_PIN_SWITCH("Sub Mic"), + SOC_DAPM_PIN_SWITCH("Third Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + + SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], + get_aif2_mode, set_aif2_mode), + + SOC_ENUM_EXT("KPCS Mode", kpcs_mode_enum[0], + get_kpcs_mode, set_kpcs_mode), + + SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0], + get_input_clamp, set_input_clamp), + + SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0], + get_lineout_mode, set_lineout_mode), + + SOC_ENUM_EXT("ModemSwitch Mode", modem_mode_enum[0], + get_modem_mode, set_modem_mode), +}; + +const struct snd_soc_dapm_widget midas_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", midas_ext_spkmode), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("LINE", midas_lineout_switch), + SND_SOC_DAPM_LINE("HDMI", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", midas_ext_micbias), + SND_SOC_DAPM_MIC("Sub Mic", midas_ext_submicbias), + SND_SOC_DAPM_MIC("Third Mic", midas_ext_thirdmicbias), + SND_SOC_DAPM_LINE("FM In", NULL), + + SND_SOC_DAPM_INPUT("S5P RP"), +}; + +const struct snd_soc_dapm_route midas_dapm_routes[] = { + { "HP", NULL, "HPOUT1L" }, + { "HP", NULL, "HPOUT1R" }, + + { "SPK", NULL, "SPKOUTLN" }, + { "SPK", NULL, "SPKOUTLP" }, + { "SPK", NULL, "SPKOUTRN" }, + { "SPK", NULL, "SPKOUTRP" }, + + { "RCV", NULL, "HPOUT2N" }, + { "RCV", NULL, "HPOUT2P" }, + + { "LINE", NULL, "LINEOUT2N" }, + { "LINE", NULL, "LINEOUT2P" }, + + { "HDMI", NULL, "LINEOUT1N" }, + { "HDMI", NULL, "LINEOUT1P" }, + +#if defined(CONFIG_MACH_M0_DUOSCTC) + { "IN2LP:VXRN", NULL, "Main Mic" }, + { "IN2LN", NULL, "Main Mic" }, + + { "IN1RP", NULL, "MICBIAS1" }, + { "IN1RN", NULL, "MICBIAS1" }, + { "MICBIAS1", NULL, "Sub Mic" }, + + { "IN1LP", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + { "IN1LN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, +#else + { "IN1LP", NULL, "MICBIAS1" }, + { "IN1LN", NULL, "MICBIAS1" }, + { "MICBIAS1", NULL, "Main Mic" }, + + { "IN1RP", NULL, "Sub Mic" }, + { "IN1RN", NULL, "Sub Mic" }, + + { "IN2LP:VXRN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, +#endif + { "AIF1DAC1L", NULL, "S5P RP" }, + { "AIF1DAC1R", NULL, "S5P RP" }, + + { "IN2RN", NULL, "FM In" }, + { "IN2RP:VXRP", NULL, "FM In" }, + + { "IN2RN", NULL, "Third Mic" }, + { "IN2RP:VXRP", NULL, "Third Mic" }, +}; + +static struct snd_soc_dai_driver midas_ext_dai[] = { + { + .name = "midas.cp", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "midas.bt", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +#ifndef CONFIG_SEC_DEV_JACK +static ssize_t earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) || + (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) { + report = 1; + } + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_key_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0) + report = 1; + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_key_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_select_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + + return 0; +} + +static ssize_t earjack_select_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + midas_micd_set_rate(codec); + + if ((!size) || (buf[0] != '1')) { + snd_soc_jack_report(wm8994->micdet[0].jack, + 0, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced remove microphone\n"); + } else { + + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_HEADSET, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced detect microphone\n"); + } + + return size; +} + +static ssize_t reselect_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + return 0; +} + +static ssize_t reselect_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + + reg = snd_soc_read(codec, WM8958_MIC_DETECT_3); + if (reg == 0x402) { + dev_info(codec->dev, "Detected open circuit\n"); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } else { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_JACK); + } + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } + return size; +} + +static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP, + reselect_jack_show, reselect_jack_store); + +static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_select_jack_show, earjack_select_jack_store); + +static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_key_state_show, earjack_key_state_store); + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_state_show, earjack_state_store); +#endif + +static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + struct snd_soc_dai *aif1_dai = rtd->codec_dai; + struct wm8994 *control = codec->control_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int ret; + +#ifdef SND_USE_BIAS_LEVEL + midas_aif1_dai = aif1_dai; +#endif + + midas_snd_set_mclk(true, false); + + rtd->codec_dai->driver->playback.channels_max = + rtd->cpu_dai->driver->playback.channels_max; + + ret = snd_soc_add_controls(codec, midas_controls, + ARRAY_SIZE(midas_controls)); + + ret = snd_soc_dapm_new_controls(&codec->dapm, midas_dapm_widgets, + ARRAY_SIZE(midas_dapm_widgets)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret); + + ret = snd_soc_dapm_add_routes(&codec->dapm, midas_dapm_routes, + ARRAY_SIZE(midas_dapm_routes)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to boot clocking\n"); + + /* Force AIF1CLK on as it will be master for jack detection */ + if (wm8994->revision > 1) { + ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); + if (ret < 0) + dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", + ret); + } + + ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP"); + if (ret < 0) + dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret); + + snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HP"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Main Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Third Mic"); + + wm1811->codec = codec; + + midas_micd_set_rate(codec); + +#ifdef CONFIG_SEC_DEV_JACK + /* By default use idle_bias_off, will override for WM8994 */ + codec->dapm.idle_bias_off = 0; +#else /* CONFIG_SEC_DEV_JACK */ + wm1811->jack.status = 0; + + ret = snd_soc_jack_new(codec, "Midas Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &wm1811->jack); + + if (ret < 0) + dev_err(codec->dev, "Failed to create jack: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_0, KEY_MEDIA); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_1, + KEY_VOLUMEDOWN); + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_2, + KEY_VOLUMEUP); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret); + + if (wm8994->revision > 1) { + dev_info(codec->dev, "wm1811: Rev %c support mic detection\n", + 'A' + wm8994->revision); + ret = wm8958_mic_detect(codec, &wm1811->jack, midas_micdet, + wm1811); + + if (ret < 0) + dev_err(codec->dev, "Failed start detection: %d\n", + ret); + } else { + dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n", + 'A' + wm8994->revision); + codec->dapm.idle_bias_off = 0; + } + /* To wakeup for earjack event in suspend mode */ + enable_irq_wake(control->irq); + + wake_lock_init(&wm1811->jackdet_wake_lock, + WAKE_LOCK_SUSPEND, "midas_jackdet"); + + /* To support PBA function test */ + jack_class = class_create(THIS_MODULE, "audio"); + + if (IS_ERR(jack_class)) + pr_err("Failed to create class\n"); + + jack_dev = device_create(jack_class, NULL, 0, codec, "earjack"); + + if (device_create_file(jack_dev, &dev_attr_select_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_select_jack.attr.name); + + if (device_create_file(jack_dev, &dev_attr_key_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_key_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_reselect_jack.attr.name); + +#endif /* CONFIG_SEC_DEV_JACK */ + return snd_soc_dapm_sync(&codec->dapm); +} + +static struct snd_soc_dai_link midas_dai[] = { + { /* Sec_Fifo DAI i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s.4", + .codec_dai_name = "wm8994-aif1", +#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER + .platform_name = "samsung-audio-idma", +#else + .platform_name = "samsung-audio", +#endif + .codec_name = "wm8994-codec", + .init = midas_wm1811_init_paiftx, + .ops = &midas_wm1811_aif1_ops, + }, + { + .name = "Midas_WM1811 Voice", + .stream_name = "Voice Tx/Rx", + .cpu_dai_name = "midas.cp", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &midas_wm1811_aif2_ops, + .ignore_suspend = 1, + }, + { + .name = "Midas_WM1811 BT", + .stream_name = "BT Tx/Rx", + .cpu_dai_name = "midas.bt", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &midas_wm1811_aif3_ops, + .ignore_suspend = 1, + }, + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &midas_wm1811_aif1_ops, + }, +}; + +static int midas_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static int midas_card_suspend_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai; + int ret; + + if (!codec->active) { +#ifndef SND_USE_BIAS_LEVEL + ret = snd_soc_dai_set_sysclk(aif2_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2: %d\n", + ret); + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL2\n"); + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL1\n"); +#endif + + midas_snd_set_mclk(false, true); + } + +#ifdef CONFIG_ARCH_EXYNOS5 + exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0); +#else /* for CONFIG_ARCH_EXYNOS5 */ + exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0); +#endif + + return 0; +} + +static int midas_card_resume_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + int ret; + + midas_snd_set_mclk(true, false); + +#ifndef SND_USE_BIAS_LEVEL + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, + MIDAS_DEFAULT_SYNC_CLK); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + MIDAS_DEFAULT_SYNC_CLK, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); +#endif + + return 0; +} + +static int midas_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; +#if !defined(CONFIG_MACH_M0_DUOSCTC) + snd_soc_write(codec, 0x102, 0x3); + snd_soc_write(codec, 0xcb, 0x5151); + snd_soc_write(codec, 0xd3, 0x3f3f); + snd_soc_write(codec, 0xd4, 0x3f3f); + snd_soc_write(codec, 0xd5, 0x3f3f); + snd_soc_write(codec, 0xd6, 0x3226); + snd_soc_write(codec, 0x102, 0x0); + snd_soc_write(codec, 0xd1, 0x87); + snd_soc_write(codec, 0x3b, 0x9); + snd_soc_write(codec, 0x3c, 0x2); +#endif + + /* workaround for jack detection + * sometimes WM8994_GPIO_1 type changed wrong function type + * so if type mismatched, update to IRQ type + */ + reg = snd_soc_read(codec, WM8994_GPIO_1); + + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_IRQ) { + dev_err(codec->dev, "%s: GPIO1 type 0x%x\n", __func__, reg); + snd_soc_write(codec, WM8994_GPIO_1, WM8994_GP_FN_IRQ); + } + +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +#ifdef SND_USE_BIAS_LEVEL +static int midas_set_bias_level(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + + if (dapm->dev != aif1_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_PREPARE: + midas_start_fll1(card->rtd[0].codec_dai); + break; + + default: + break; + } + + return 0; +} + +static int midas_set_bias_level_post(struct snd_soc_card *card, + struct snd_soc_dapm_context *dapm, + enum snd_soc_bias_level level) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai; + int ret; + + if (dapm->dev != aif1_dai->dev) + return 0; + + switch (level) { + case SND_SOC_BIAS_STANDBY: + + /* When going idle stop FLL1 and revert to using MCLK2 + * directly for minimum power consumptin for accessory + * detection. + */ + if (card->dapm.bias_level == SND_SOC_BIAS_PREPARE) { + dev_info(aif1_dai->dev, "Moving to STANDBY\n"); + + ret = snd_soc_dai_set_sysclk(aif2_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, + 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, + "Failed to change FLL2\n"); + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, + "Failed to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + 0, 0, 0); + if (ret < 0) + dev_err(codec->dev, + "Failed to stop FLL1\n"); + + + midas_fll1_active = false; + midas_snd_set_mclk(false, false); + } + + break; + default: + break; + } + + card->dapm.bias_level = level; + + return 0; +} +#endif + +static struct snd_soc_card midas = { + .name = "Midas_WM1811", + .dai_link = midas_dai, + + /* If you want to use sec_fifo device, + * changes the num_link = 2 or ARRAY_SIZE(midas_dai). */ + .num_links = ARRAY_SIZE(midas_dai), + +#ifdef SND_USE_BIAS_LEVEL + .set_bias_level = midas_set_bias_level, + .set_bias_level_post = midas_set_bias_level_post, +#endif + + .suspend_post = midas_card_suspend_post, + .resume_pre = midas_card_resume_pre, + .suspend_pre = midas_card_suspend_pre, + .resume_post = midas_card_resume_post +}; + +static struct platform_device *midas_snd_device; + +static int __init midas_audio_init(void) +{ + struct wm1811_machine_priv *wm1811; + int ret; + + wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); + if (!wm1811) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + snd_soc_card_set_drvdata(&midas, wm1811); + + midas_snd_device = platform_device_alloc("soc-audio", -1); + if (!midas_snd_device) { + ret = -ENOMEM; + goto err_device_alloc; + } + + ret = snd_soc_register_dais(&midas_snd_device->dev, midas_ext_dai, + ARRAY_SIZE(midas_ext_dai)); + if (ret != 0) + pr_err("Failed to register external DAIs: %d\n", ret); + + platform_set_drvdata(midas_snd_device, &midas); + + ret = platform_device_add(midas_snd_device); + if (ret) + platform_device_put(midas_snd_device); + + midas_gpio_init(); + + return ret; + +err_device_alloc: + kfree(wm1811); +err_kzalloc: + return ret; +} +module_init(midas_audio_init); + +static void __exit midas_audio_exit(void) +{ + struct snd_soc_card *card = &midas; + struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card); + platform_device_unregister(midas_snd_device); + kfree(wm1811); +} +module_exit(midas_audio_exit); + +MODULE_AUTHOR("JS. Park <aitdark.park@samsung.com>"); +MODULE_DESCRIPTION("ALSA SoC Midas WM1811"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index 77279a4..52f308b 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -29,7 +29,8 @@ #include "i2s.h" #include "srp-types.h" -#if defined(CONFIG_SND_SAMSUNG_RP) && defined(CONFIG_MACH_U1) +#if defined(CONFIG_SND_SAMSUNG_RP) && \ + (defined(CONFIG_MACH_U1) || defined(CONFIG_MACH_TRATS)) #define USE_ALT_REG_RECOVER #endif diff --git a/sound/soc/samsung/m3_wm1811.c b/sound/soc/samsung/m3_wm1811.c new file mode 100644 index 0000000..949f7d7 --- /dev/null +++ b/sound/soc/samsung/m3_wm1811.c @@ -0,0 +1,1219 @@ +/* + * m3_wm1811.c + * + * Copyright (c) 2011 Samsung Electronics Co. Ltd + * + * 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/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/suspend.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> + +#include <mach/regs-clock.h> +#include <mach/pmu.h> +#include <mach/midas-sound.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) +#include <linux/mfd/max77693-private.h> +#endif + + +#include "i2s.h" +#include "s3c-i2s-v2.h" +#include "../codecs/wm8994.h" + + +#define MIDAS_DEFAULT_MCLK1 24000000 +#define MIDAS_DEFAULT_MCLK2 32768 +#define MIDAS_DEFAULT_SYNC_CLK 11289600 + +#define WM1811_JACKDET_MODE_NONE 0x0000 +#define WM1811_JACKDET_MODE_JACK 0x0100 +#define WM1811_JACKDET_MODE_MIC 0x0080 +#define WM1811_JACKDET_MODE_AUDIO 0x0180 + +#define WM1811_JACKDET_BTN0 0x04 +#define WM1811_JACKDET_BTN1 0x10 +#define WM1811_JACKDET_BTN2 0x08 + + +static struct wm8958_micd_rate m3_det_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 }, +}; + +static struct wm8958_micd_rate m3_jackdet_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, +}; + +static int aif2_mode; +const char *aif2_mode_text[] = { + "Slave", "Master" +}; + +static int input_clamp; +const char *input_clamp_text[] = { + "Off", "On" +}; + +static int lineout_mode; +const char *lineout_mode_text[] = { + "Off", "On" +}; + +#ifndef CONFIG_SEC_DEV_JACK +/* To support PBA function test */ +static struct class *jack_class; +static struct device *jack_dev; +#endif + +struct wm1811_machine_priv { + struct snd_soc_jack jack; + struct snd_soc_codec *codec; + struct delayed_work mic_work; + struct wake_lock jackdet_wake_lock; +}; + + +static void m3_gpio_init(void) +{ + int err; +#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS + /* Main Microphone BIAS */ + err = gpio_request(GPIO_MIC_BIAS_EN, "MAIN MIC"); + if (err) { + pr_err(KERN_ERR "MIC_BIAS_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_MIC_BIAS_EN, 1); + gpio_set_value(GPIO_MIC_BIAS_EN, 0); + gpio_free(GPIO_MIC_BIAS_EN); +#endif + +#ifdef CONFIG_SND_USE_SUB_MIC + /* Sub Microphone BIAS */ + err = gpio_request(GPIO_SUB_MIC_BIAS_EN, "SUB MIC"); + if (err) { + pr_err(KERN_ERR "SUB_MIC_BIAS_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_SUB_MIC_BIAS_EN, 1); + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0); + gpio_free(GPIO_SUB_MIC_BIAS_EN); +#endif + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + err = gpio_request(GPIO_VPS_SOUND_EN, "LINEOUT_EN"); + if (err) { + pr_err(KERN_ERR "LINEOUT_EN GPIO set error!\n"); + return; + } + gpio_direction_output(GPIO_VPS_SOUND_EN, 0); + gpio_set_value(GPIO_VPS_SOUND_EN, 0); + gpio_free(GPIO_VPS_SOUND_EN); +#endif +} + +static const struct soc_enum lineout_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text), +}; + +static int get_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lineout_mode; + return 0; +} + +static int set_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + lineout_mode = ucontrol->value.integer.value[0]; + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + if (lineout_mode) { + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + gpio_set_value(GPIO_VPS_SOUND_EN, 1); + } else { + gpio_set_value(GPIO_VPS_SOUND_EN, 0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } +#endif + dev_info(codec->dev, "set lineout mode : %s\n", + lineout_mode_text[lineout_mode]); + return 0; + +} +static const struct soc_enum aif2_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), +}; + +static const struct soc_enum input_clamp_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text), +}; + +static int get_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aif2_mode; + return 0; +} + +static int set_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (aif2_mode == ucontrol->value.integer.value[0]) + return 0; + + aif2_mode = ucontrol->value.integer.value[0]; + + pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]); + + return 0; +} + +static int get_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = input_clamp; + return 0; +} + +static int set_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + input_clamp = ucontrol->value.integer.value[0]; + + if (input_clamp) { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP); + msleep(100); + } else { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, 0); + } + pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]); + + return 0; +} + + +static int set_ext_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_info(codec->dev, "%s event is %02X", w->name, event); + +#ifdef CONFIG_SND_SOC_USE_EXTERNAL_MIC_BIAS + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpio_set_value(GPIO_MIC_BIAS_EN, 1); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMD: + gpio_set_value(GPIO_MIC_BIAS_EN, 0); + break; + } +#endif + return 0; +} + +static int set_ext_submicbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_info(codec->dev, "%s event is %02X", w->name, event); + +#ifdef CONFIG_SND_USE_SUB_MIC + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 1); + msleep(100); + break; + case SND_SOC_DAPM_POST_PMD: + gpio_set_value(GPIO_SUB_MIC_BIAS_EN, 0); + break; + } +#endif + return 0; +} + +static int set_lineout_switch(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_info(codec->dev, "%s event is %02X", w->name, event); + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(150); + max77693_muic_set_audio_switch(1); + break; + case SND_SOC_DAPM_PRE_PMD: + max77693_muic_set_audio_switch(0); + break; + } +#endif + + return 0; +} + +static void m3_micd_set_rate(struct snd_soc_codec *codec) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int best, i, sysclk, val; + bool idle; + const struct wm8958_micd_rate *rates = NULL; + int num_rates = 0; + + idle = !wm8994->jack_mic; + + sysclk = snd_soc_read(codec, WM8994_CLOCKING_1); + if (sysclk & WM8994_SYSCLK_SRC) + sysclk = wm8994->aifclk[1]; + else + sysclk = wm8994->aifclk[0]; + + if (wm8994->jackdet) { + rates = m3_jackdet_rates; + num_rates = ARRAY_SIZE(m3_jackdet_rates); + wm8994->pdata->micd_rates = m3_jackdet_rates; + wm8994->pdata->num_micd_rates = num_rates; + } else { + rates = m3_det_rates; + num_rates = ARRAY_SIZE(m3_det_rates); + wm8994->pdata->micd_rates = m3_det_rates; + wm8994->pdata->num_micd_rates = num_rates; + } + + best = 0; + for (i = 0; i < num_rates; i++) { + if (rates[i].idle != idle) + continue; + if (abs(rates[i].sysclk - sysclk) < + abs(rates[best].sysclk - sysclk)) + best = i; + else if (rates[best].idle != idle) + best = i; + } + + val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT + | rates[best].rate << WM8958_MICD_RATE_SHIFT; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_BIAS_STARTTIME_MASK | + WM8958_MICD_RATE_MASK, val); +} + +static void m3_micdet(u16 status, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + int report; + + + wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ); + + /* Either nothing present or just starting detection */ + if (!(status & WM8958_MICD_STS)) { + if (!wm8994->jackdet) { + /* If nothing present then clear our statuses */ + dev_dbg(wm1811->codec->dev, "Detected open circuit\n"); + wm8994->jack_mic = false; + wm8994->mic_detecting = true; + + m3_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); + } + /*ToDo*/ + /*return;*/ + } + + /* If the measurement is showing a high impedence we've got a + * microphone. + */ + if (wm8994->mic_detecting && (status & 0x400)) { + dev_info(wm1811->codec->dev, "Detected microphone\n"); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + m3_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + + if (wm8994->mic_detecting && status & 0x4) { + dev_info(wm1811->codec->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + m3_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); + + /* If we have jackdet that will detect removal */ + if (wm8994->jackdet) { + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(wm1811->codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } + + mutex_unlock(&wm8994->accdet_lock); + + if (wm8994->pdata->jd_ext_cap) { + mutex_lock(&wm1811->codec->mutex); + snd_soc_dapm_disable_pin(&wm1811->codec->dapm, + "MICBIAS2"); + snd_soc_dapm_sync(&wm1811->codec->dapm); + mutex_unlock(&wm1811->codec->mutex); + } + } + } + + /* Report short circuit as a button */ + if (wm8994->jack_mic) { + report = 0; + if (status & WM1811_JACKDET_BTN0) + report |= SND_JACK_BTN_0; + + if (status & WM1811_JACKDET_BTN1) + report |= SND_JACK_BTN_1; + + if (status & WM1811_JACKDET_BTN2) + report |= SND_JACK_BTN_2; + + dev_dbg(wm1811->codec->dev, "Detected Button: %08x (%08X)\n", + report, status); + + snd_soc_jack_report(wm8994->micdet[0].jack, report, + wm8994->btn_mask); + } +} + +static int m3_wm1811_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + 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) + return ret; + + /* Set the 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) + return ret; + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1, + pll_out); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to start FLL1: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Unable to switch to FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, + 0, MOD_OPCLK_PCLK); + if (ret < 0) + return ret; + + dev_info(codec_dai->dev, "%s --\n", __func__); + + return 0; +} + +/* + * m3 WM1811 DAI operations. + */ +static struct snd_soc_ops m3_wm1811_aif1_ops = { + .hw_params = m3_wm1811_aif1_hw_params, +}; + +static int m3_wm1811_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_dai *codec_dai = rtd->codec_dai; + int ret; + int prate; + int bclk; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + prate = params_rate(params); + switch (params_rate(params)) { + case 8000: + case 16000: + break; + default: + dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8000Hz\n", + (int)params_rate(params)); + prate = 8000; + } + + /* Set the codec DAI configuration, aif2_mode:0 is slave */ + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + 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) + return ret; + + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } + + if (aif2_mode == 0) { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, + bclk, prate * 256); + } else { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, prate * 256); + } + + if (ret < 0) + dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + prate * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); + + dev_info(codec_dai->dev, "%s --\n", __func__); + return 0; +} + +static struct snd_soc_ops m3_wm1811_aif2_ops = { + .hw_params = m3_wm1811_aif2_hw_params, +}; + +static int m3_wm1811_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + pr_err("%s: enter\n", __func__); + return 0; +} + +static struct snd_soc_ops m3_wm1811_aif3_ops = { + .hw_params = m3_wm1811_aif3_hw_params, +}; + +static const struct snd_kcontrol_new m3_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("FM In"), + SOC_DAPM_PIN_SWITCH("LINE"), + SOC_DAPM_PIN_SWITCH("HDMI"), + SOC_DAPM_PIN_SWITCH("Main Mic"), + SOC_DAPM_PIN_SWITCH("Sub Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + + SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], + get_aif2_mode, set_aif2_mode), + + SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0], + get_input_clamp, set_input_clamp), + + SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0], + get_lineout_mode, set_lineout_mode), + +}; + +const struct snd_soc_dapm_widget m3_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("LINE", set_lineout_switch), + SND_SOC_DAPM_LINE("HDMI", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias), + SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias), + SND_SOC_DAPM_LINE("FM In", NULL), + + SND_SOC_DAPM_INPUT("S5P RP"), +}; + +const struct snd_soc_dapm_route m3_dapm_routes[] = { + { "HP", NULL, "HPOUT1L" }, + { "HP", NULL, "HPOUT1R" }, + + { "SPK", NULL, "SPKOUTLN" }, + { "SPK", NULL, "SPKOUTLP" }, + + { "RCV", NULL, "HPOUT2N" }, + { "RCV", NULL, "HPOUT2P" }, + + { "LINE", NULL, "LINEOUT2N" }, + { "LINE", NULL, "LINEOUT2P" }, + + { "HDMI", NULL, "LINEOUT1N" }, + { "HDMI", NULL, "LINEOUT1P" }, + + { "IN2LP:VXRN", NULL, "Main Mic" }, + { "IN2LN", NULL, "Main Mic" }, + + { "IN1RP", NULL, "Sub Mic" }, + { "IN1RN", NULL, "Sub Mic" }, + + { "IN1LP", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + { "IN1LN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + + { "AIF1DAC1L", NULL, "S5P RP" }, + { "AIF1DAC1R", NULL, "S5P RP" }, + + { "IN2RN", NULL, "FM In" }, + { "IN2RP:VXRP", NULL, "FM In" }, +}; + +static struct snd_soc_dai_driver m3_ext_dai[] = { + { + .name = "m3.cp", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "m3.bt", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +#ifndef CONFIG_SEC_DEV_JACK +static ssize_t earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) || + (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) { + report = 1; + } + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_key_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0) + report = 1; + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_key_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_select_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + + return 0; +} + +static ssize_t earjack_select_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + m3_micd_set_rate(codec); + + if ((!size) || (buf[0] != '1')) { + snd_soc_jack_report(wm8994->micdet[0].jack, + 0, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced remove microphone\n"); + } else { + + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_HEADSET, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced detect microphone\n"); + } + + return size; +} + +static ssize_t reselect_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + return 0; +} + +static ssize_t reselect_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + + reg = snd_soc_read(codec, WM8958_MIC_DETECT_3); + if (reg == 0x402) { + dev_info(codec->dev, "Detected open circuit\n"); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } else { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_JACK); + } + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } + return size; +} + +static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP, + reselect_jack_show, reselect_jack_store); + +static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_select_jack_show, earjack_select_jack_store); + +static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_key_state_show, earjack_key_state_store); + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_state_show, earjack_state_store); +#endif + +static int m3_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + struct snd_soc_dai *aif1_dai = rtd->codec_dai; + struct wm8994 *control = codec->control_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int ret; + + midas_snd_set_mclk(true, false); + + rtd->codec_dai->driver->playback.channels_max = + rtd->cpu_dai->driver->playback.channels_max; + + ret = snd_soc_add_controls(codec, m3_controls, + ARRAY_SIZE(m3_controls)); + + ret = snd_soc_dapm_new_controls(&codec->dapm, m3_dapm_widgets, + ARRAY_SIZE(m3_dapm_widgets)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret); + + ret = snd_soc_dapm_add_routes(&codec->dapm, m3_dapm_routes, + ARRAY_SIZE(m3_dapm_routes)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to boot clocking\n"); + + /* Force AIF1CLK on as it will be master for jack detection */ + if (wm8994->revision > 1) { + ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); + if (ret < 0) + dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", + ret); + } + + ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP"); + if (ret < 0) + dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret); + + snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HP"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Main Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI"); + + wm1811->codec = codec; + + m3_micd_set_rate(codec); + +#ifdef CONFIG_SEC_DEV_JACK + /* By default use idle_bias_off, will override for WM8994 */ + codec->dapm.idle_bias_off = 0; +#else /* CONFIG_SEC_DEV_JACK */ + wm1811->jack.status = 0; + + ret = snd_soc_jack_new(codec, "m3 Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &wm1811->jack); + + if (ret < 0) + dev_err(codec->dev, "Failed to create jack: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_0, KEY_MEDIA); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_1, + KEY_VOLUMEDOWN); + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_2, + KEY_VOLUMEUP); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret); + + if (wm8994->revision > 1) { + dev_info(codec->dev, "wm1811: Rev %c support mic detection\n", + 'A' + wm8994->revision); + ret = wm8958_mic_detect(codec, &wm1811->jack, m3_micdet, + wm1811); + + if (ret < 0) + dev_err(codec->dev, "Failed start detection: %d\n", + ret); + } else { + dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n", + 'A' + wm8994->revision); + codec->dapm.idle_bias_off = 0; + } + /* To wakeup for earjack event in suspend mode */ + enable_irq_wake(control->irq); + + wake_lock_init(&wm1811->jackdet_wake_lock, + WAKE_LOCK_SUSPEND, "m3_jackdet"); + + /* To support PBA function test */ + jack_class = class_create(THIS_MODULE, "audio"); + + if (IS_ERR(jack_class)) + pr_err("Failed to create class\n"); + + jack_dev = device_create(jack_class, NULL, 0, codec, "earjack"); + + if (device_create_file(jack_dev, &dev_attr_select_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_select_jack.attr.name); + + if (device_create_file(jack_dev, &dev_attr_key_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_key_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_reselect_jack.attr.name); + +#endif /* CONFIG_SEC_DEV_JACK */ + return snd_soc_dapm_sync(&codec->dapm); +} + +static struct snd_soc_dai_link m3_dai[] = { + { /* Sec_Fifo DAI i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s.4", + .codec_dai_name = "wm8994-aif1", +#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER + .platform_name = "samsung-audio-idma", +#else + .platform_name = "samsung-audio", +#endif + .codec_name = "wm8994-codec", + .init = m3_wm1811_init_paiftx, + .ops = &m3_wm1811_aif1_ops, + }, + { + .name = "m3_WM1811 Voice", + .stream_name = "Voice Tx/Rx", + .cpu_dai_name = "m3.cp", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &m3_wm1811_aif2_ops, + .ignore_suspend = 1, + }, + { + .name = "m3_WM1811 BT", + .stream_name = "BT Tx/Rx", + .cpu_dai_name = "m3.bt", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &m3_wm1811_aif3_ops, + .ignore_suspend = 1, + }, + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &m3_wm1811_aif1_ops, + }, +}; + +static int m3_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_FORCE) { + dev_info(codec->dev, + "%s: entering force vmid mode\n", __func__); + gpio_set_value(GPIO_VPS_SOUND_EN, 0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } +#endif +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static int m3_card_suspend_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai; + int ret; + + if (!codec->active) { + ret = snd_soc_dai_set_sysclk(aif2_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2: %d\n", + ret); + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL2\n"); + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL1\n"); + + ret = snd_soc_dai_set_fmt(aif1_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + dev_err(codec->dev, "Unable to set I2S SLAVE MODE\n"); + + midas_snd_set_mclk(false, true); + } + +#ifdef CONFIG_ARCH_EXYNOS5 + exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0); +#else /* for CONFIG_ARCH_EXYNOS5 */ + exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0); +#endif + + return 0; +} + +static int m3_card_resume_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + int ret; + + midas_snd_set_mclk(true, false); + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, + MIDAS_DEFAULT_SYNC_CLK); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + MIDAS_DEFAULT_SYNC_CLK, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); + + ret = snd_soc_dai_set_fmt(aif1_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + dev_err(codec->dev, "Unable to set I2S MASTER MODE\n"); + + return 0; +} + +static int m3_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_NORMAL) { + dev_info(codec->dev, + "%s: entering normal vmid mode\n", __func__); + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + gpio_set_value(GPIO_VPS_SOUND_EN, 1); + } +#endif +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static struct snd_soc_card m3_card = { + .name = "m3_WM1811", + .dai_link = m3_dai, + + /* If you want to use sec_fifo device, + * changes the num_link = 2 or ARRAY_SIZE(m3_dai). */ + .num_links = ARRAY_SIZE(m3_dai), + + .suspend_pre = m3_card_suspend_pre, + .suspend_post = m3_card_suspend_post, + .resume_pre = m3_card_resume_pre, + .resume_post = m3_card_resume_post +}; + +static struct platform_device *m3_snd_device; + +static int __init m3_audio_init(void) +{ + struct wm1811_machine_priv *wm1811; + int ret; + + wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); + if (!wm1811) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + snd_soc_card_set_drvdata(&m3_card, wm1811); + + m3_snd_device = platform_device_alloc("soc-audio", -1); + if (!m3_snd_device) { + ret = -ENOMEM; + goto err_device_alloc; + } + + ret = snd_soc_register_dais(&m3_snd_device->dev, m3_ext_dai, + ARRAY_SIZE(m3_ext_dai)); + if (ret != 0) + pr_err("Failed to register external DAIs: %d\n", ret); + + platform_set_drvdata(m3_snd_device, &m3_card); + + ret = platform_device_add(m3_snd_device); + if (ret) + platform_device_put(m3_snd_device); + + m3_gpio_init(); + + return ret; + +err_device_alloc: + kfree(wm1811); +err_kzalloc: + return ret; +} +module_init(m3_audio_init); + +static void __exit m3_audio_exit(void) +{ + struct snd_soc_card *card = &m3_card; + struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card); + platform_device_unregister(m3_snd_device); + kfree(wm1811); +} +module_exit(m3_audio_exit); + +MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>"); +MODULE_DESCRIPTION("ALSA SoC m3 WM1811"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/midas_wm1811.c b/sound/soc/samsung/midas_wm1811.c index dd8a43a..1f5becc 100644 --- a/sound/soc/samsung/midas_wm1811.c +++ b/sound/soc/samsung/midas_wm1811.c @@ -28,10 +28,14 @@ #include <mach/regs-clock.h> #include <mach/pmu.h> #include <mach/midas-sound.h> +#ifdef CONFIG_MACH_GC1 +#include <mach/gc1-jack.h> +#endif #include <linux/mfd/wm8994/core.h> #include <linux/mfd/wm8994/registers.h> #include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/gpio.h> #if defined(CONFIG_SND_USE_MUIC_SWITCH) #include <linux/mfd/max77693-private.h> @@ -64,19 +68,28 @@ static struct wm8958_micd_rate midas_det_rates[] = { { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 }, }; +#ifdef CONFIG_MACH_GC1 +static struct wm8958_micd_rate midas_jackdet_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 10, 10 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, +}; +#else static struct wm8958_micd_rate midas_jackdet_rates[] = { { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 }, { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, }; +#endif static int aif2_mode; const char *aif2_mode_text[] = { "Slave", "Master" }; -static int kpcs_mode = 1; +static int kpcs_mode = 2; const char *kpcs_mode_text[] = { "Off", "On" }; @@ -109,6 +122,28 @@ struct wm1811_machine_priv { struct wake_lock jackdet_wake_lock; }; +#ifdef CONFIG_MACH_GC1 +static struct snd_soc_codec *wm1811_codec; + +void set_wm1811_micbias2(bool on) +{ + if (wm1811_codec == NULL) { + pr_err(KERN_ERR "WM1811 MICBIAS2 set error!\n"); + return; + } + + if (on) { + snd_soc_update_bits(wm1811_codec, WM8994_POWER_MANAGEMENT_1, + WM8994_MICB2_ENA, WM8994_MICB2_ENA); + } else { + snd_soc_update_bits(wm1811_codec, WM8994_POWER_MANAGEMENT_1, + WM8994_MICB2_ENA, 0); + + } +return; +} +EXPORT_SYMBOL(set_wm1811_micbias2); +#endif static void midas_gpio_init(void) { @@ -190,16 +225,6 @@ static int set_lineout_mode(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); lineout_mode = ucontrol->value.integer.value[0]; - -#ifdef CONFIG_SND_USE_LINEOUT_SWITCH - if (lineout_mode) { - wm8994_vmid_mode(codec, WM8994_VMID_FORCE); - gpio_set_value(GPIO_LINEOUT_EN, 1); - } else { - wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); - gpio_set_value(GPIO_LINEOUT_EN, 0); - } -#endif dev_dbg(codec->dev, "set lineout mode : %s\n", lineout_mode_text[lineout_mode]); return 0; @@ -250,7 +275,7 @@ static int set_kpcs_mode(struct snd_kcontrol *kcontrol, kpcs_mode = ucontrol->value.integer.value[0]; - pr_info("set kpcs mode : %s\n", kpcs_mode_text[kpcs_mode]); + pr_info("set kpcs mode : %d\n", kpcs_mode); return 0; } @@ -374,7 +399,7 @@ static int midas_lineout_switch(struct snd_soc_dapm_widget *w, #if defined(CONFIG_SND_USE_MUIC_SWITCH) switch (event) { case SND_SOC_DAPM_POST_PMU: - msleep(50); + msleep(150); max77693_muic_set_audio_switch(1); break; case SND_SOC_DAPM_PRE_PMD: @@ -383,6 +408,16 @@ static int midas_lineout_switch(struct snd_soc_dapm_widget *w, } #endif +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + switch (event) { + case SND_SOC_DAPM_POST_PMU: + gpio_set_value(GPIO_LINEOUT_EN, 1); + break; + case SND_SOC_DAPM_PRE_PMD: + gpio_set_value(GPIO_LINEOUT_EN, 0); + break; + } +#endif return 0; } @@ -795,7 +830,7 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream, } #if defined(CONFIG_LTE_MODEM_CMC221) || defined(CONFIG_MACH_M0_CTC) -#if defined(CONFIG_MACH_C1_KOR_LGT) || defined(CONFIG_MACH_C1VZW) +#if defined(CONFIG_MACH_C1_KOR_LGT) /* Set the codec DAI configuration */ if (aif2_mode == 0) { if (kpcs_mode == 1) @@ -837,7 +872,7 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; -#if defined(CONFIG_LTE_MODEM_CMC221) || defined(CONFIG_MACH_M0_CTC) +#if defined(CONFIG_LTE_MODEM_CMC221) if (kpcs_mode == 1) { switch (prate) { case 8000: @@ -852,6 +887,8 @@ static int midas_wm1811_aif2_hw_params(struct snd_pcm_substream *substream, } else { bclk = 2048000; } +#elif defined(CONFIG_MACH_M0_CTC) + bclk = 2048000; #else switch (prate) { case 8000: @@ -1171,9 +1208,9 @@ static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) { - - struct wm1811_machine_priv *wm1811; struct snd_soc_codec *codec = rtd->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); struct snd_soc_dai *aif1_dai = rtd->codec_dai; struct wm8994 *control = codec->control_data; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); @@ -1183,6 +1220,10 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) midas_aif1_dai = aif1_dai; #endif +#ifdef CONFIG_MACH_GC1 + wm1811_codec = codec; +#endif + midas_snd_set_mclk(true, false); rtd->codec_dai->driver->playback.channels_max = @@ -1205,7 +1246,7 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); if (ret < 0) dev_err(codec->dev, "Failed to boot clocking\n"); -#ifndef CONFIG_SEC_DEV_JACK + /* Force AIF1CLK on as it will be master for jack detection */ if (wm8994->revision > 1) { ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); @@ -1213,17 +1254,11 @@ static int midas_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", ret); } -#endif + ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP"); if (ret < 0) dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret); - wm1811 = kmalloc(sizeof *wm1811, GFP_KERNEL); - if (!wm1811) { - dev_err(codec->dev, "Failed to allocate memory!"); - return -ENOMEM; - } - snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV"); snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK"); snd_soc_dapm_ignore_suspend(&codec->dapm, "HP"); @@ -1371,17 +1406,11 @@ static struct snd_soc_dai_link midas_dai[] = { static int midas_card_suspend_pre(struct snd_soc_card *card) { -#ifdef CONFIG_SND_USE_LINEOUT_SWITCH struct snd_soc_codec *codec = card->rtd->codec; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); - if (lineout_mode == 1 && - wm8994->vmid_mode == WM8994_VMID_FORCE) { - dev_dbg(codec->dev, - "%s: entering force vmid mode\n", __func__); - gpio_set_value(GPIO_LINEOUT_EN, 0); - wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); - } +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); #endif return 0; @@ -1468,17 +1497,23 @@ static int midas_card_resume_pre(struct snd_soc_card *card) static int midas_card_resume_post(struct snd_soc_card *card) { -#ifdef CONFIG_SND_USE_LINEOUT_SWITCH struct snd_soc_codec *codec = card->rtd->codec; struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; - if (lineout_mode == 1 && - wm8994->vmid_mode == WM8994_VMID_NORMAL) { - dev_dbg(codec->dev, - "%s: entering normal vmid mode\n", __func__); - wm8994_vmid_mode(codec, WM8994_VMID_FORCE); - gpio_set_value(GPIO_LINEOUT_EN, 1); + /* workaround for jack detection + * sometimes WM8994_GPIO_1 type changed wrong function type + * so if type mismatched, update to IRQ type + */ + reg = snd_soc_read(codec, WM8994_GPIO_1); + + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_IRQ) { + dev_err(codec->dev, "%s: GPIO1 type 0x%x\n", __func__, reg); + snd_soc_write(codec, WM8994_GPIO_1, WM8994_GP_FN_IRQ); } + +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); #endif return 0; @@ -1595,11 +1630,22 @@ static struct platform_device *midas_snd_device; static int __init midas_audio_init(void) { + struct wm1811_machine_priv *wm1811; int ret; + wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); + if (!wm1811) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + snd_soc_card_set_drvdata(&midas, wm1811); + midas_snd_device = platform_device_alloc("soc-audio", -1); - if (!midas_snd_device) - return -ENOMEM; + if (!midas_snd_device) { + ret = -ENOMEM; + goto err_device_alloc; + } ret = snd_soc_register_dais(&midas_snd_device->dev, midas_ext_dai, ARRAY_SIZE(midas_ext_dai)); @@ -1615,12 +1661,20 @@ static int __init midas_audio_init(void) midas_gpio_init(); return ret; + +err_device_alloc: + kfree(wm1811); +err_kzalloc: + return ret; } module_init(midas_audio_init); static void __exit midas_audio_exit(void) { + struct snd_soc_card *card = &midas; + struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card); platform_device_unregister(midas_snd_device); + kfree(wm1811); } module_exit(midas_audio_exit); diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index af4b521..f4f6adf 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -547,6 +547,8 @@ EXPORT_SYMBOL_GPL(s3c_pcm_dai); static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev) { + struct clk *mout_epll = NULL; + struct clk *sclk_audio = NULL; struct s3c_pcm_info *pcm; struct resource *mem_res, *dmatx_res, *dmarx_res; struct s3c_audio_pdata *pcm_pdata; @@ -592,6 +594,37 @@ static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev) /* Default is 128fs */ pcm->sclk_per_fs = 128; + /* Clock configuration */ + mout_epll = clk_get(&pdev->dev, "mout_epll"); + if (IS_ERR(mout_epll)) { + dev_err(&pdev->dev, "failed to get mout_epll\n"); + ret = PTR_ERR(mout_epll); + return ret; + } + + switch (pdev->id) { + case 0: + sclk_audio = clk_get(&pdev->dev, "audio-bus"); + break; + case 1: + sclk_audio = clk_get(&pdev->dev, "audio-bus1"); + break; + case 2: + sclk_audio = clk_get(&pdev->dev, "audio-bus2"); + break; + default: + dev_err(&pdev->dev, "Not support device num\n"); + break; + } + + if (IS_ERR(sclk_audio)) { + dev_err(&pdev->dev, "failed to get sclk_audio\n"); + ret = PTR_ERR(sclk_audio); + goto err; + } + + clk_set_parent(sclk_audio, mout_epll); + pcm->cclk = clk_get(&pdev->dev, "audio-bus"); if (IS_ERR(pcm->cclk)) { dev_err(&pdev->dev, "failed to get audio-bus\n"); @@ -642,6 +675,9 @@ static __devinit int s3c_pcm_dev_probe(struct platform_device *pdev) pcm->dma_capture = &s3c_pcm_stereo_in[pdev->id]; pcm->dma_playback = &s3c_pcm_stereo_out[pdev->id]; + clk_put(mout_epll); + clk_put(sclk_audio); + return 0; err5: @@ -655,6 +691,9 @@ err2: clk_disable(pcm->cclk); clk_put(pcm->cclk); err1: + clk_put(sclk_audio); +err: + clk_put(mout_epll); return ret; } diff --git a/sound/soc/samsung/t0_wm1811.c b/sound/soc/samsung/t0_wm1811.c new file mode 100644 index 0000000..9a0ac52 --- /dev/null +++ b/sound/soc/samsung/t0_wm1811.c @@ -0,0 +1,1393 @@ +/* + * t0_wm1811.c + * + * Copyright (c) 2011 Samsung Electronics Co. Ltd + * + * 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/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/suspend.h> +#include <linux/irq.h> +#include <linux/interrupt.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> + +#include <mach/regs-clock.h> +#include <mach/pmu.h> +#include <mach/midas-sound.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/gpio.h> +#include <linux/exynos_audio.h> + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) +#include <linux/mfd/max77693-private.h> +#endif + + +#include "i2s.h" +#include "s3c-i2s-v2.h" +#include "../codecs/wm8994.h" + + +#define MIDAS_DEFAULT_MCLK1 24000000 +#define MIDAS_DEFAULT_MCLK2 32768 +#define MIDAS_DEFAULT_SYNC_CLK 11289600 + +#define WM1811_JACKDET_MODE_NONE 0x0000 +#define WM1811_JACKDET_MODE_JACK 0x0100 +#define WM1811_JACKDET_MODE_MIC 0x0080 +#define WM1811_JACKDET_MODE_AUDIO 0x0180 + +#define WM1811_JACKDET_BTN0 0x04 +#define WM1811_JACKDET_BTN1 0x10 +#define WM1811_JACKDET_BTN2 0x08 + +#define WM1811_MIC_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_MIC1_DET) +#define WM1811_JACKDET_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_GPIO(6)) + +static struct wm8958_micd_rate t0_det_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 }, +}; + +static struct wm8958_micd_rate t0_jackdet_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, +}; + +static int aif2_mode; +const char *aif2_mode_text[] = { + "Slave", "Master" +}; + +static int input_clamp; +const char *input_clamp_text[] = { + "Off", "On" +}; + +static int lineout_mode; +const char *lineout_mode_text[] = { + "Off", "On" +}; + +#ifndef CONFIG_SEC_DEV_JACK +/* To support PBA function test */ +static struct class *jack_class; +static struct device *jack_dev; +#endif + +struct wm1811_machine_priv { + struct snd_soc_jack jack; + struct snd_soc_codec *codec; + struct wake_lock jackdet_wake_lock; + void (*lineout_switch_f) (int on); + void (*set_main_mic_f) (int on); + void (*set_sub_mic_f) (int on); + int (*get_g_det_value_f) (void); + int (*get_g_det_irq_num_f) (void); +}; + +static const struct soc_enum lineout_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text), +}; + +static int get_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lineout_mode; + return 0; +} + +static int set_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + lineout_mode = ucontrol->value.integer.value[0]; + + if (lineout_mode) { + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(1); + } else { + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } + + dev_info(codec->dev, "set lineout mode : %s\n", + lineout_mode_text[lineout_mode]); + return 0; + +} +static const struct soc_enum aif2_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), +}; + +static const struct soc_enum input_clamp_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text), +}; + +static int get_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aif2_mode; + return 0; +} + +static int set_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (aif2_mode == ucontrol->value.integer.value[0]) + return 0; + + aif2_mode = ucontrol->value.integer.value[0]; + + pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]); + + return 0; +} + +static int get_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = input_clamp; + return 0; +} + +static int set_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + input_clamp = ucontrol->value.integer.value[0]; + + if (input_clamp) { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP); + msleep(100); + } else { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, 0); + } + pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]); + + return 0; +} + + +static int set_ext_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + dev_info(codec->dev, "%s event is %02X", w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm1811->set_main_mic_f) + wm1811->set_main_mic_f(1); + break; + case SND_SOC_DAPM_POST_PMD: + if (wm1811->set_main_mic_f) + wm1811->set_main_mic_f(0); + break; + } + + return 0; +} + +static int set_ext_submicbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + dev_info(codec->dev, "%s event is %02X", w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm1811->set_sub_mic_f) + wm1811->set_sub_mic_f(1); + break; + case SND_SOC_DAPM_POST_PMD: + if (wm1811->set_sub_mic_f) + wm1811->set_sub_mic_f(0); + break; + } + + return 0; +} + +static int set_muic_switch(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_info(codec->dev, "%s event is %02X", w->name, event); + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(150); + max77693_muic_set_audio_switch(1); + break; + case SND_SOC_DAPM_PRE_PMD: + max77693_muic_set_audio_switch(0); + break; + } +#endif + + return 0; +} + +static void t0_micd_set_rate(struct snd_soc_codec *codec) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int best, i, sysclk, val; + bool idle; + const struct wm8958_micd_rate *rates = NULL; + int num_rates = 0; + + idle = !wm8994->jack_mic; + + sysclk = snd_soc_read(codec, WM8994_CLOCKING_1); + if (sysclk & WM8994_SYSCLK_SRC) + sysclk = wm8994->aifclk[1]; + else + sysclk = wm8994->aifclk[0]; + + if (wm8994->jackdet) { + rates = t0_jackdet_rates; + num_rates = ARRAY_SIZE(t0_jackdet_rates); + wm8994->pdata->micd_rates = t0_jackdet_rates; + wm8994->pdata->num_micd_rates = num_rates; + } else { + rates = t0_det_rates; + num_rates = ARRAY_SIZE(t0_det_rates); + wm8994->pdata->micd_rates = t0_det_rates; + wm8994->pdata->num_micd_rates = num_rates; + } + + best = 0; + for (i = 0; i < num_rates; i++) { + if (rates[i].idle != idle) + continue; + if (abs(rates[i].sysclk - sysclk) < + abs(rates[best].sysclk - sysclk)) + best = i; + else if (rates[best].idle != idle) + best = i; + } + + val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT + | rates[best].rate << WM8958_MICD_RATE_SHIFT; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_BIAS_STARTTIME_MASK | + WM8958_MICD_RATE_MASK, val); +} + +static void t0_micdet(u16 status, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + int report; + + /* + * If the jack is inserted abnormally, + * The variable puts back to its previous status. + */ + if (!wm1811->get_g_det_value_f) { + dev_err(wm1811->codec->dev, "Do not use the ground detection\n"); + } else { + if (wm1811->get_g_det_value_f()) { + dev_info(wm1811->codec->dev, "The jack is inserted abnormally\n"); + + wm8994->mic_detecting = false; + } + } + + wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ); + + /* Either nothing present or just starting detection */ + if (!(status & WM8958_MICD_STS)) { + if (!wm8994->jackdet) { + /* If nothing present then clear our statuses */ + dev_dbg(wm1811->codec->dev, "Detected open circuit\n"); + wm8994->jack_mic = false; + wm8994->mic_detecting = true; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); + } + /*ToDo*/ + /*return;*/ + } + + /* If the measurement is showing a high impedence we've got a + * microphone. + */ + if (wm8994->mic_detecting && (status & 0x400)) { + dev_info(wm1811->codec->dev, "Detected microphone\n"); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + + if (wm8994->mic_detecting && status & 0x4) { + dev_info(wm1811->codec->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); + + /* If we have jackdet that will detect removal */ + if (wm8994->jackdet) { + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(wm1811->codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } + + mutex_unlock(&wm8994->accdet_lock); + + if (wm8994->pdata->jd_ext_cap) { + mutex_lock(&wm1811->codec->mutex); + snd_soc_dapm_disable_pin(&wm1811->codec->dapm, + "MICBIAS2"); + snd_soc_dapm_sync(&wm1811->codec->dapm); + mutex_unlock(&wm1811->codec->mutex); + } + } + } + + /* Report short circuit as a button */ + if (wm8994->jack_mic) { + report = 0; + if (status & WM1811_JACKDET_BTN0) + report |= SND_JACK_BTN_0; + + if (status & WM1811_JACKDET_BTN1) + report |= SND_JACK_BTN_1; + + if (status & WM1811_JACKDET_BTN2) + report |= SND_JACK_BTN_2; + + dev_info(wm1811->codec->dev, "Detected Button: %08x (%08X)\n", + report, status); + + snd_soc_jack_report(wm8994->micdet[0].jack, report, + wm8994->btn_mask); + } +} + +static int t0_wm1811_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + 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) + return ret; + + /* Set the 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) + return ret; + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1, + pll_out); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to start FLL1: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Unable to switch to FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, + 0, MOD_OPCLK_PCLK); + if (ret < 0) + return ret; + + dev_info(codec_dai->dev, "%s --\n", __func__); + + return 0; +} + +/* + * T0 WM1811 DAI operations. + */ +static struct snd_soc_ops t0_wm1811_aif1_ops = { + .hw_params = t0_wm1811_aif1_hw_params, +}; + +static int t0_wm1811_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_dai *codec_dai = rtd->codec_dai; + int ret; + int prate; + int bclk; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + prate = params_rate(params); + switch (params_rate(params)) { + case 8000: + case 16000: + break; + default: + dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8000Hz\n", + (int)params_rate(params)); + prate = 8000; + } + + /* Set the codec DAI configuration, aif2_mode:0 is slave */ + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + 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) + return ret; + + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } + + if (aif2_mode == 0) { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, + bclk, prate * 256); + } else { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, prate * 256); + } + + if (ret < 0) + dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + prate * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); + + dev_info(codec_dai->dev, "%s --\n", __func__); + return 0; +} + +static struct snd_soc_ops t0_wm1811_aif2_ops = { + .hw_params = t0_wm1811_aif2_hw_params, +}; + +static int t0_wm1811_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + pr_err("%s: enter\n", __func__); + return 0; +} + +static bool playback_stream_status; +static bool capture_stream_status; + +static int t0_wm1811_aif3_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994_pdata *pdata = wm8994->pdata; + int base = WM8994_GPIO_8 - WM8994_GPIO_1; + int i; + + pr_err("%s: enter\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + capture_stream_status = 1; + else + playback_stream_status = 1; + + for (i = 0; i < 4; i++) { + if (pdata->gpio_defaults[base + i]) { + snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i, + 0xffff, + pdata->gpio_defaults[base + i]); + } + } + return 0; +} + +static void t0_wm1811_aif3_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int i; + + pr_err("%s: enter, stream=%d\n", __func__, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + capture_stream_status = 0; + else + playback_stream_status = 0; + + if (playback_stream_status || capture_stream_status) + return; + + pr_info("%s: set input gpios for AIF3\n", __func__); + + for (i = 0; i < 4; i++) { + snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i, + 0xffff, + 0xA101); + } + return; +} + +static struct snd_soc_ops t0_wm1811_aif3_ops = { + .startup = t0_wm1811_aif3_startup, + .shutdown = t0_wm1811_aif3_shutdown, + .hw_params = t0_wm1811_aif3_hw_params, +}; + +static const struct snd_kcontrol_new t0_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("FM In"), + SOC_DAPM_PIN_SWITCH("LINE"), + SOC_DAPM_PIN_SWITCH("HDMI"), + SOC_DAPM_PIN_SWITCH("Main Mic"), + SOC_DAPM_PIN_SWITCH("Sub Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + + SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], + get_aif2_mode, set_aif2_mode), + + SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0], + get_input_clamp, set_input_clamp), + + SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0], + get_lineout_mode, set_lineout_mode), + +}; + +const struct snd_soc_dapm_widget t0_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("LINE", set_muic_switch), + SND_SOC_DAPM_LINE("HDMI", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias), + SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias), + SND_SOC_DAPM_LINE("FM In", NULL), + + SND_SOC_DAPM_INPUT("S5P RP"), +}; + +const struct snd_soc_dapm_route t0_dapm_routes[] = { + { "HP", NULL, "HPOUT1L" }, + { "HP", NULL, "HPOUT1R" }, + + { "SPK", NULL, "SPKOUTLN" }, + { "SPK", NULL, "SPKOUTLP" }, + + { "RCV", NULL, "HPOUT2N" }, + { "RCV", NULL, "HPOUT2P" }, + + { "LINE", NULL, "LINEOUT2N" }, + { "LINE", NULL, "LINEOUT2P" }, + + { "HDMI", NULL, "LINEOUT1N" }, + { "HDMI", NULL, "LINEOUT1P" }, + + { "IN2LP:VXRN", NULL, "Main Mic" }, + { "IN2LN", NULL, "Main Mic" }, + + { "IN1RP", NULL, "MICBIAS1" }, + { "IN1RN", NULL, "MICBIAS1" }, + { "MICBIAS1", NULL, "Sub Mic" }, + + { "IN1LP", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + { "IN1LN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + + { "AIF1DAC1L", NULL, "S5P RP" }, + { "AIF1DAC1R", NULL, "S5P RP" }, + + { "IN2RN", NULL, "FM In" }, + { "IN2RP:VXRP", NULL, "FM In" }, +}; + +static struct snd_soc_dai_driver t0_ext_dai[] = { + { + .name = "t0.cp", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "t0.bt", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +#ifndef CONFIG_SEC_DEV_JACK +static ssize_t earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) || + (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) { + report = 1; + } + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_key_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0) + report = 1; + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_key_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_select_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + + return 0; +} + +static ssize_t earjack_select_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + t0_micd_set_rate(codec); + + if ((!size) || (buf[0] != '1')) { + snd_soc_jack_report(wm8994->micdet[0].jack, + 0, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced remove microphone\n"); + } else { + + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_HEADSET, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced detect microphone\n"); + } + + return size; +} + +static ssize_t reselect_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + return 0; +} + +static ssize_t reselect_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + + reg = snd_soc_read(codec, WM8958_MIC_DETECT_3); + if (reg == 0x402) { + dev_info(codec->dev, "Detected open circuit\n"); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } else { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_JACK); + } + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } + return size; +} + +static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP, + reselect_jack_show, reselect_jack_store); + +static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_select_jack_show, earjack_select_jack_store); + +static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_key_state_show, earjack_key_state_store); + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_state_show, earjack_state_store); +#endif + +static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + struct snd_soc_dai *aif1_dai = rtd->codec_dai; + struct wm8994 *control = codec->control_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + const struct exynos_sound_platform_data *sound_pdata; + int ret; + + midas_snd_set_mclk(true, false); + + rtd->codec_dai->driver->playback.channels_max = + rtd->cpu_dai->driver->playback.channels_max; + + ret = snd_soc_add_controls(codec, t0_controls, + ARRAY_SIZE(t0_controls)); + + ret = snd_soc_dapm_new_controls(&codec->dapm, t0_dapm_widgets, + ARRAY_SIZE(t0_dapm_widgets)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret); + + ret = snd_soc_dapm_add_routes(&codec->dapm, t0_dapm_routes, + ARRAY_SIZE(t0_dapm_routes)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to boot clocking\n"); + + /* Force AIF1CLK on as it will be master for jack detection */ + if (wm8994->revision > 1) { + ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); + if (ret < 0) + dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", + ret); + } + + ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP"); + if (ret < 0) + dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret); + + snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HP"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Main Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI"); + + wm1811->codec = codec; + + t0_micd_set_rate(codec); + +#ifdef CONFIG_SEC_DEV_JACK + /* By default use idle_bias_off, will override for WM8994 */ + codec->dapm.idle_bias_off = 0; +#else /* CONFIG_SEC_DEV_JACK */ + wm1811->jack.status = 0; + + ret = snd_soc_jack_new(codec, "T0 Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &wm1811->jack); + + if (ret < 0) + dev_err(codec->dev, "Failed to create jack: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_0, KEY_MEDIA); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_1, + KEY_VOLUMEDOWN); + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_2, + KEY_VOLUMEUP); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret); + + if (wm8994->revision > 1) { + dev_info(codec->dev, "wm1811: Rev %c support mic detection\n", + 'A' + wm8994->revision); + ret = wm8958_mic_detect(codec, &wm1811->jack, t0_micdet, + wm1811); + + if (ret < 0) + dev_err(codec->dev, "Failed start detection: %d\n", + ret); + } else { + dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n", + 'A' + wm8994->revision); + codec->dapm.idle_bias_off = 0; + } + /* To wakeup for earjack event in suspend mode */ + enable_irq_wake(control->irq); + + wake_lock_init(&wm1811->jackdet_wake_lock, + WAKE_LOCK_SUSPEND, "T0_jackdet"); + + /* To support PBA function test */ + jack_class = class_create(THIS_MODULE, "audio"); + + if (IS_ERR(jack_class)) + pr_err("Failed to create class\n"); + + jack_dev = device_create(jack_class, NULL, 0, codec, "earjack"); + + if (device_create_file(jack_dev, &dev_attr_select_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_select_jack.attr.name); + + if (device_create_file(jack_dev, &dev_attr_key_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_key_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_reselect_jack.attr.name); + +#endif /* CONFIG_SEC_DEV_JACK */ + sound_pdata = exynos_sound_get_platform_data(); + + if (sound_pdata) { + wm8994->hubs.dcs_codes_l = sound_pdata->dcs_offset_l; + wm8994->hubs.dcs_codes_r = sound_pdata->dcs_offset_r; + } + + return snd_soc_dapm_sync(&codec->dapm); +} + +static struct snd_soc_dai_link t0_dai[] = { + { /* Sec_Fifo DAI i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s.4", + .codec_dai_name = "wm8994-aif1", +#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER + .platform_name = "samsung-audio-idma", +#else + .platform_name = "samsung-audio", +#endif + .codec_name = "wm8994-codec", + .init = t0_wm1811_init_paiftx, + .ops = &t0_wm1811_aif1_ops, + }, + { + .name = "T0_WM1811 Voice", + .stream_name = "Voice Tx/Rx", + .cpu_dai_name = "t0.cp", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif2_ops, + .ignore_suspend = 1, + }, + { + .name = "T0_WM1811 BT", + .stream_name = "BT Tx/Rx", + .cpu_dai_name = "t0.bt", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif3_ops, + .ignore_suspend = 1, + }, + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif1_ops, + }, +}; + +static int t0_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_FORCE) { + dev_info(codec->dev, + "%s: entering force vmid mode\n", __func__); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } +#endif +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static int t0_card_suspend_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai; + int ret; + + if (!codec->active) { + ret = snd_soc_dai_set_sysclk(aif2_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2: %d\n", + ret); + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL2\n"); + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL1\n"); + + midas_snd_set_mclk(false, true); + } + +#ifdef CONFIG_ARCH_EXYNOS5 + exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0); +#else /* for CONFIG_ARCH_EXYNOS5 */ + exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0); +#endif + + return 0; +} + +static int t0_card_resume_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + int ret; + + midas_snd_set_mclk(true, false); + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, + MIDAS_DEFAULT_SYNC_CLK); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + MIDAS_DEFAULT_SYNC_CLK, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); + + return 0; +} + +static int t0_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_NORMAL) { + dev_info(codec->dev, + "%s: entering normal vmid mode\n", __func__); + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(1); + } +#endif + /* workaround for jack detection + * sometimes WM8994_GPIO_1 type changed wrong function type + * so if type mismatched, update to IRQ type + */ + reg = snd_soc_read(codec, WM8994_GPIO_1); + + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_IRQ) { + dev_err(codec->dev, "%s: GPIO1 type 0x%x\n", __func__, reg); + snd_soc_write(codec, WM8994_GPIO_1, WM8994_GP_FN_IRQ); + } + +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static struct snd_soc_card t0_card = { + .name = "T0_WM1811", + .dai_link = t0_dai, + + /* If you want to use sec_fifo device, + * changes the num_link = 2 or ARRAY_SIZE(t0_dai). */ + .num_links = ARRAY_SIZE(t0_dai), + + .suspend_pre = t0_card_suspend_pre, + .suspend_post = t0_card_suspend_post, + .resume_pre = t0_card_resume_pre, + .resume_post = t0_card_resume_post +}; + +static struct platform_device *t0_snd_device; + +static void t0_jackdet_set_mode(struct snd_soc_codec *codec, u16 mode) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + if (!wm8994->jackdet || !wm8994->jack_cb) + return; + + if (wm8994->active_refcount) + mode = WM1811_JACKDET_MODE_AUDIO; + + if (mode == wm8994->jackdet_mode) + return; + + wm8994->jackdet_mode = mode; + + /* Always use audio mode to detect while the system is active */ + if (mode != WM1811_JACKDET_MODE_NONE) + mode = WM1811_JACKDET_MODE_AUDIO; + + snd_soc_update_bits(codec, WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, mode); +} + +static irqreturn_t t0_g_det_thread(int irq, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + struct snd_soc_codec *codec = wm8994->codec; + + + if (wm1811->get_g_det_value_f()) { + + pr_info("%s: G_DET_N GPIO is High!!!!", __func__); + + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + t0_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK); + + mutex_unlock(&wm8994->accdet_lock); + + mutex_lock(&codec->mutex); + + snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2"); + snd_soc_dapm_sync(&codec->dapm); + + mutex_unlock(&codec->mutex); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } else { + pr_info("%s: G_DET_N GPIO is Low!!!!", __func__); + + handle_nested_irq(WM1811_JACKDET_IRQ_NUM); + msleep(100); + handle_nested_irq(WM1811_MIC_IRQ_NUM); + } + + return IRQ_HANDLED; + +} + +static int __init t0_audio_init(void) +{ + struct wm1811_machine_priv *wm1811; + const struct exynos_sound_platform_data *sound_pdata; + int ret; + + wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); + if (!wm1811) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + snd_soc_card_set_drvdata(&t0_card, wm1811); + + t0_snd_device = platform_device_alloc("soc-audio", -1); + if (!t0_snd_device) { + ret = -ENOMEM; + goto err_device_alloc; + } + + ret = snd_soc_register_dais(&t0_snd_device->dev, t0_ext_dai, + ARRAY_SIZE(t0_ext_dai)); + if (ret != 0) + pr_err("Failed to register external DAIs: %d\n", ret); + + platform_set_drvdata(t0_snd_device, &t0_card); + + ret = platform_device_add(t0_snd_device); + if (ret) + platform_device_put(t0_snd_device); + + sound_pdata = exynos_sound_get_platform_data(); + if (!sound_pdata) { + pr_info("%s: don't use sound pdata\n", __func__); + goto err_out_free; + } + + if (sound_pdata->set_lineout_switch) + wm1811->lineout_switch_f = sound_pdata->set_lineout_switch; + + if (sound_pdata->set_ext_main_mic) + wm1811->set_main_mic_f = sound_pdata->set_ext_main_mic; + + if (sound_pdata->set_ext_sub_mic) + wm1811->set_sub_mic_f = sound_pdata->set_ext_sub_mic; + + if (sound_pdata->get_ground_det_value) + wm1811->get_g_det_value_f = sound_pdata->get_ground_det_value; + + if (sound_pdata->get_ground_det_irq_num) { + wm1811->get_g_det_irq_num_f = + sound_pdata->get_ground_det_irq_num; + ret = request_threaded_irq(wm1811->get_g_det_irq_num_f(), NULL, + t0_g_det_thread, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "g_det irq", wm1811); + if (ret != 0) + pr_err("%s: Failed to register IRQ\n", __func__); + + /* to handle insert/removal when we're sleeping in a call */ + ret = enable_irq_wake(wm1811->get_g_det_irq_num_f()); + if (ret) + pr_err("%s : Failed to enable_irq_wake\n", __func__); + } + + return ret; + +err_out_free: + platform_device_put(t0_snd_device); +err_device_alloc: + kfree(wm1811); +err_kzalloc: + return ret; +} +module_init(t0_audio_init); + +static void __exit t0_audio_exit(void) +{ + struct snd_soc_card *card = &t0_card; + struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card); + platform_device_unregister(t0_snd_device); + kfree(wm1811); +} +module_exit(t0_audio_exit); + +MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>"); +MODULE_DESCRIPTION("ALSA SoC T0 WM1811"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/samsung/t0duos_wm1811.c b/sound/soc/samsung/t0duos_wm1811.c new file mode 100644 index 0000000..dfa3001 --- /dev/null +++ b/sound/soc/samsung/t0duos_wm1811.c @@ -0,0 +1,1479 @@ +/* + * t0_wm1811.c + * + * Copyright (c) 2011 Samsung Electronics Co. Ltd + * + * 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/platform_device.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/wakelock.h> +#include <linux/suspend.h> +#include <linux/irq.h> +#include <linux/interrupt.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> + +#include <mach/regs-clock.h> +#include <mach/pmu.h> +#include <mach/midas-sound.h> + +#include <linux/mfd/wm8994/core.h> +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/pdata.h> +#include <linux/mfd/wm8994/gpio.h> +#include <linux/exynos_audio.h> + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) +#include <linux/mfd/max77693-private.h> +#endif + + +#include "i2s.h" +#include "s3c-i2s-v2.h" +#include "../codecs/wm8994.h" + + +#define MIDAS_DEFAULT_MCLK1 24000000 +#define MIDAS_DEFAULT_MCLK2 32768 +#define MIDAS_DEFAULT_SYNC_CLK 11289600 + +#define WM1811_JACKDET_MODE_NONE 0x0000 +#define WM1811_JACKDET_MODE_JACK 0x0100 +#define WM1811_JACKDET_MODE_MIC 0x0080 +#define WM1811_JACKDET_MODE_AUDIO 0x0180 + +#define WM1811_JACKDET_BTN0 0x04 +#define WM1811_JACKDET_BTN1 0x10 +#define WM1811_JACKDET_BTN2 0x08 + +#define WM1811_MIC_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_MIC1_DET) +#define WM1811_JACKDET_IRQ_NUM (IRQ_BOARD_CODEC_START + WM8994_IRQ_GPIO(6)) + +static struct wm8958_micd_rate t0_det_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 7, 7 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 7 }, +}; + +static struct wm8958_micd_rate t0_jackdet_rates[] = { + { MIDAS_DEFAULT_MCLK2, true, 0, 0 }, + { MIDAS_DEFAULT_MCLK2, false, 0, 0 }, + { MIDAS_DEFAULT_SYNC_CLK, true, 12, 12 }, + { MIDAS_DEFAULT_SYNC_CLK, false, 7, 8 }, +}; + +static int aif2_mode; +const char *aif2_mode_text[] = { + "Slave", "Master" +}; + +static int input_clamp; +const char *input_clamp_text[] = { + "Off", "On" +}; + +static int lineout_mode; +const char *lineout_mode_text[] = { + "Off", "On" +}; + +#if defined(CONFIG_SND_DUOS_MODEM_SWITCH) +static int modem_mode; +const char *modem_mode_text[] = { + "CP1", "CP2" +}; +#endif + +#ifndef CONFIG_SEC_DEV_JACK +/* To support PBA function test */ +static struct class *jack_class; +static struct device *jack_dev; +#endif + +struct wm1811_machine_priv { + struct snd_soc_jack jack; + struct snd_soc_codec *codec; + struct wake_lock jackdet_wake_lock; + void (*lineout_switch_f) (int on); + void (*set_main_mic_f) (int on); + void (*set_sub_mic_f) (int on); + int (*get_g_det_value_f) (void); + int (*get_g_det_irq_num_f) (void); +#if defined(CONFIG_SND_DUOS_MODEM_SWITCH) + void (*set_modem_switch_f) (int on); +#endif +}; + +#if defined(CONFIG_SND_DUOS_MODEM_SWITCH) +static const struct soc_enum modem_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(modem_mode_text), modem_mode_text), +}; + +static int get_modem_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = modem_mode; + return 0; +} + +static int set_modem_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + modem_mode = ucontrol->value.integer.value[0]; + + if (modem_mode) { + if (wm1811->set_modem_switch_f) + wm1811->set_modem_switch_f(1); + } else { + if (wm1811->set_modem_switch_f) + wm1811->set_modem_switch_f(0); + /*msleep(50);*/ + } + dev_info(codec->dev, "set modem select : %s\n", + modem_mode_text[modem_mode]); + return 0; + +} +#endif + +static const struct soc_enum lineout_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(lineout_mode_text), lineout_mode_text), +}; + +static int get_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lineout_mode; + return 0; +} + +static int set_lineout_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + lineout_mode = ucontrol->value.integer.value[0]; + + if (lineout_mode) { + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(1); + } else { + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } + + dev_info(codec->dev, "set lineout mode : %s\n", + lineout_mode_text[lineout_mode]); + return 0; + +} +static const struct soc_enum aif2_mode_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(aif2_mode_text), aif2_mode_text), +}; + +static const struct soc_enum input_clamp_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_clamp_text), input_clamp_text), +}; + +static int get_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = aif2_mode; + return 0; +} + +static int set_aif2_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + if (aif2_mode == ucontrol->value.integer.value[0]) + return 0; + + aif2_mode = ucontrol->value.integer.value[0]; + + pr_info("set aif2 mode : %s\n", aif2_mode_text[aif2_mode]); + + return 0; +} + +static int get_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = input_clamp; + return 0; +} + +static int set_input_clamp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + input_clamp = ucontrol->value.integer.value[0]; + + if (input_clamp) { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, WM8994_INPUTS_CLAMP); + msleep(100); + } else { + snd_soc_update_bits(codec, WM8994_INPUT_MIXER_1, + WM8994_INPUTS_CLAMP, 0); + } + pr_info("set fm input_clamp : %s\n", input_clamp_text[input_clamp]); + + return 0; +} + + +static int set_ext_micbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + dev_info(codec->dev, "%s event is %02X", w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm1811->set_main_mic_f) + wm1811->set_main_mic_f(1); + break; + case SND_SOC_DAPM_POST_PMD: + if (wm1811->set_main_mic_f) + wm1811->set_main_mic_f(0); + break; + } + + return 0; +} + +static int set_ext_submicbias(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + dev_info(codec->dev, "%s event is %02X", w->name, event); + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (wm1811->set_sub_mic_f) + wm1811->set_sub_mic_f(1); + break; + case SND_SOC_DAPM_POST_PMD: + if (wm1811->set_sub_mic_f) + wm1811->set_sub_mic_f(0); + break; + } + + return 0; +} + +static int set_muic_switch(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + dev_info(codec->dev, "%s event is %02X", w->name, event); + +#if defined(CONFIG_SND_USE_MUIC_SWITCH) + switch (event) { + case SND_SOC_DAPM_POST_PMU: + msleep(150); + max77693_muic_set_audio_switch(1); + break; + case SND_SOC_DAPM_PRE_PMD: + max77693_muic_set_audio_switch(0); + break; + } +#endif + + return 0; +} + +static void t0_micd_set_rate(struct snd_soc_codec *codec) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int best, i, sysclk, val; + bool idle; + const struct wm8958_micd_rate *rates = NULL; + int num_rates = 0; + + idle = !wm8994->jack_mic; + + sysclk = snd_soc_read(codec, WM8994_CLOCKING_1); + if (sysclk & WM8994_SYSCLK_SRC) + sysclk = wm8994->aifclk[1]; + else + sysclk = wm8994->aifclk[0]; + + if (wm8994->jackdet) { + rates = t0_jackdet_rates; + num_rates = ARRAY_SIZE(t0_jackdet_rates); + wm8994->pdata->micd_rates = t0_jackdet_rates; + wm8994->pdata->num_micd_rates = num_rates; + } else { + rates = t0_det_rates; + num_rates = ARRAY_SIZE(t0_det_rates); + wm8994->pdata->micd_rates = t0_det_rates; + wm8994->pdata->num_micd_rates = num_rates; + } + + best = 0; + for (i = 0; i < num_rates; i++) { + if (rates[i].idle != idle) + continue; + if (abs(rates[i].sysclk - sysclk) < + abs(rates[best].sysclk - sysclk)) + best = i; + else if (rates[best].idle != idle) + best = i; + } + + val = rates[best].start << WM8958_MICD_BIAS_STARTTIME_SHIFT + | rates[best].rate << WM8958_MICD_RATE_SHIFT; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_BIAS_STARTTIME_MASK | + WM8958_MICD_RATE_MASK, val); +} + +static void t0_micdet(u16 status, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + int report; + + /* + * If the jack is inserted abnormally, + * The variable puts back to its previous status. + */ + if (!wm1811->get_g_det_value_f) { + dev_err(wm1811->codec->dev, "Do not use the ground detection\n"); + } else { + if (wm1811->get_g_det_value_f()) { + dev_info(wm1811->codec->dev, "The jack is inserted abnormally\n"); + + wm8994->mic_detecting = false; + } + } + + wake_lock_timeout(&wm1811->jackdet_wake_lock, 5 * HZ); + + /* Either nothing present or just starting detection */ + if (!(status & WM8958_MICD_STS)) { + if (!wm8994->jackdet) { + /* If nothing present then clear our statuses */ + dev_dbg(wm1811->codec->dev, "Detected open circuit\n"); + wm8994->jack_mic = false; + wm8994->mic_detecting = true; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + wm8994->btn_mask | + SND_JACK_HEADSET); + } + /*ToDo*/ + /*return;*/ + } + + /* If the measurement is showing a high impedence we've got a + * microphone. + */ + if (wm8994->mic_detecting && (status & 0x400)) { + dev_info(wm1811->codec->dev, "Detected microphone\n"); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADSET, + SND_JACK_HEADSET); + } + + if (wm8994->mic_detecting && status & 0x4) { + dev_info(wm1811->codec->dev, "Detected headphone\n"); + wm8994->mic_detecting = false; + + t0_micd_set_rate(wm1811->codec); + + snd_soc_jack_report(wm8994->micdet[0].jack, SND_JACK_HEADPHONE, + SND_JACK_HEADSET); + + /* If we have jackdet that will detect removal */ + if (wm8994->jackdet) { + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(wm1811->codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(wm1811->codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } + + mutex_unlock(&wm8994->accdet_lock); + + if (wm8994->pdata->jd_ext_cap) { + mutex_lock(&wm1811->codec->mutex); + snd_soc_dapm_disable_pin(&wm1811->codec->dapm, + "MICBIAS2"); + snd_soc_dapm_sync(&wm1811->codec->dapm); + mutex_unlock(&wm1811->codec->mutex); + } + } + } + + /* Report short circuit as a button */ + if (wm8994->jack_mic) { + report = 0; + if (status & WM1811_JACKDET_BTN0) + report |= SND_JACK_BTN_0; + + if (status & WM1811_JACKDET_BTN1) + report |= SND_JACK_BTN_1; + + if (status & WM1811_JACKDET_BTN2) + report |= SND_JACK_BTN_2; + + dev_info(wm1811->codec->dev, "Detected Button: %08x (%08X)\n", + report, status); + + snd_soc_jack_report(wm8994->micdet[0].jack, report, + wm8994->btn_mask); + } +} + +static int t0_wm1811_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_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + unsigned int pll_out; + int ret; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + /* AIF1CLK should be >=3MHz for optimal performance */ + if (params_rate(params) == 8000 || params_rate(params) == 11025) + pll_out = params_rate(params) * 512; + else + pll_out = params_rate(params) * 256; + + 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) + return ret; + + /* Set the 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) + return ret; + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, MIDAS_DEFAULT_MCLK1, + pll_out); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to start FLL1: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, + pll_out, SND_SOC_CLOCK_IN); + if (ret < 0) { + dev_err(codec_dai->dev, "Unable to switch to FLL1: %d\n", ret); + return ret; + } + + ret = snd_soc_dai_set_sysclk(cpu_dai, SAMSUNG_I2S_OPCLK, + 0, MOD_OPCLK_PCLK); + if (ret < 0) + return ret; + + dev_info(codec_dai->dev, "%s --\n", __func__); + + return 0; +} + +/* + * T0 WM1811 DAI operations. + */ +static struct snd_soc_ops t0_wm1811_aif1_ops = { + .hw_params = t0_wm1811_aif1_hw_params, +}; + +static int t0_wm1811_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_dai *codec_dai = rtd->codec_dai; + int ret; + int prate; + int bclk; + + dev_info(codec_dai->dev, "%s ++\n", __func__); + prate = params_rate(params); + switch (params_rate(params)) { + case 8000: + case 16000: + break; + default: + dev_warn(codec_dai->dev, "Unsupported LRCLK %d, falling back to 8000Hz\n", + (int)params_rate(params)); + prate = 8000; + } + + /* Set the codec DAI configuration, aif2_mode:0 is slave */ +#if defined(CONFIG_MACH_T0_CHN_CTC) + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#elif defined(CONFIG_MACH_T0_CHN_CU_DUOS) \ + || defined(CONFIG_MACH_T0_CHN_OPEN_DUOS) + if (modem_mode == 1) { + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A + | SND_SOC_DAIFMT_IB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + } else { + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + } +#else + if (aif2_mode == 0) + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + else + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); +#endif + + if (ret < 0) + return ret; + + switch (prate) { + case 8000: + bclk = 256000; + break; + case 16000: + bclk = 512000; + break; + default: + return -EINVAL; + } + +#if defined(CONFIG_MACH_T0_CHN_CU_DUOS) + if (modem_mode == 1) + bclk = 2048000; +#endif + +#if defined(CONFIG_MACH_T0_CHN_CTC) + bclk = 2048000; +#endif + + if (aif2_mode == 0) { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_BCLK, + bclk, prate * 256); + } else { + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, prate * 256); + } + + if (ret < 0) + dev_err(codec_dai->dev, "Unable to configure FLL2: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, + prate * 256, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec_dai->dev, "Unable to switch to FLL2: %d\n", ret); + + dev_info(codec_dai->dev, "%s --\n", __func__); + return 0; +} + +static struct snd_soc_ops t0_wm1811_aif2_ops = { + .hw_params = t0_wm1811_aif2_hw_params, +}; + +static int t0_wm1811_aif3_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + pr_err("%s: enter\n", __func__); + return 0; +} + +static bool playback_stream_status; +static bool capture_stream_status; + +static int t0_wm1811_aif3_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + struct wm8994_pdata *pdata = wm8994->pdata; + int base = WM8994_GPIO_8 - WM8994_GPIO_1; + int i; + + pr_err("%s: enter\n", __func__); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + capture_stream_status = 1; + else + playback_stream_status = 1; + + for (i = 0; i < 4; i++) { + if (pdata->gpio_defaults[base + i]) { + snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i, + 0xffff, + pdata->gpio_defaults[base + i]); + } + } + return 0; +} + +static void t0_wm1811_aif3_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int i; + + pr_err("%s: enter, stream=%d\n", __func__, substream->stream); + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + capture_stream_status = 0; + else + playback_stream_status = 0; + + if (playback_stream_status || capture_stream_status) + return; + + pr_info("%s: set input gpios for AIF3\n", __func__); + + for (i = 0; i < 4; i++) { + snd_soc_update_bits(wm8994->codec, WM8994_GPIO_8 + i, + 0xffff, + 0xA101); + } + return; +} + +static struct snd_soc_ops t0_wm1811_aif3_ops = { + .startup = t0_wm1811_aif3_startup, + .shutdown = t0_wm1811_aif3_shutdown, + .hw_params = t0_wm1811_aif3_hw_params, +}; + +static const struct snd_kcontrol_new t0_controls[] = { + SOC_DAPM_PIN_SWITCH("HP"), + SOC_DAPM_PIN_SWITCH("SPK"), + SOC_DAPM_PIN_SWITCH("RCV"), + SOC_DAPM_PIN_SWITCH("FM In"), + SOC_DAPM_PIN_SWITCH("LINE"), + SOC_DAPM_PIN_SWITCH("HDMI"), + SOC_DAPM_PIN_SWITCH("Main Mic"), + SOC_DAPM_PIN_SWITCH("Sub Mic"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + + SOC_ENUM_EXT("AIF2 Mode", aif2_mode_enum[0], + get_aif2_mode, set_aif2_mode), + + SOC_ENUM_EXT("Input Clamp", input_clamp_enum[0], + get_input_clamp, set_input_clamp), + + SOC_ENUM_EXT("LineoutSwitch Mode", lineout_mode_enum[0], + get_lineout_mode, set_lineout_mode), +#if defined(CONFIG_SND_DUOS_MODEM_SWITCH) + SOC_ENUM_EXT("ModemSwitch Mode", modem_mode_enum[0], + get_modem_mode, set_modem_mode), +#endif +}; + +const struct snd_soc_dapm_widget t0_dapm_widgets[] = { + SND_SOC_DAPM_HP("HP", NULL), + SND_SOC_DAPM_SPK("SPK", NULL), + SND_SOC_DAPM_SPK("RCV", NULL), + SND_SOC_DAPM_LINE("LINE", set_muic_switch), + SND_SOC_DAPM_LINE("HDMI", NULL), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Main Mic", set_ext_micbias), + SND_SOC_DAPM_MIC("Sub Mic", set_ext_submicbias), + SND_SOC_DAPM_LINE("FM In", NULL), + + SND_SOC_DAPM_INPUT("S5P RP"), +}; + +const struct snd_soc_dapm_route t0_dapm_routes[] = { + { "HP", NULL, "HPOUT1L" }, + { "HP", NULL, "HPOUT1R" }, + + { "SPK", NULL, "SPKOUTLN" }, + { "SPK", NULL, "SPKOUTLP" }, + + { "RCV", NULL, "HPOUT2N" }, + { "RCV", NULL, "HPOUT2P" }, + + { "LINE", NULL, "LINEOUT2N" }, + { "LINE", NULL, "LINEOUT2P" }, + + { "HDMI", NULL, "LINEOUT1N" }, + { "HDMI", NULL, "LINEOUT1P" }, + + { "IN2LP:VXRN", NULL, "Main Mic" }, + { "IN2LN", NULL, "Main Mic" }, + + { "IN1RP", NULL, "MICBIAS1" }, + { "IN1RN", NULL, "MICBIAS1" }, + { "MICBIAS1", NULL, "Sub Mic" }, + + { "IN1LP", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + { "IN1LN", NULL, "MICBIAS2" }, + { "MICBIAS2", NULL, "Headset Mic" }, + + { "AIF1DAC1L", NULL, "S5P RP" }, + { "AIF1DAC1R", NULL, "S5P RP" }, + + { "IN2RN", NULL, "FM In" }, + { "IN2RP:VXRP", NULL, "FM In" }, +}; + +static struct snd_soc_dai_driver t0_ext_dai[] = { + { + .name = "t0.cp", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, + { + .name = "t0.bt", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 16000, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + }, +}; + +#ifndef CONFIG_SEC_DEV_JACK +static ssize_t earjack_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if ((wm8994->micdet[0].jack->status & SND_JACK_HEADPHONE) || + (wm8994->micdet[0].jack->status & SND_JACK_HEADSET)) { + report = 1; + } + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_key_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + int report = 0; + + if (wm8994->micdet[0].jack->status & SND_JACK_BTN_0) + report = 1; + + return sprintf(buf, "%d\n", report); +} + +static ssize_t earjack_key_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + pr_info("%s : operate nothing\n", __func__); + + return size; +} + +static ssize_t earjack_select_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + + return 0; +} + +static ssize_t earjack_select_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + wm8994->mic_detecting = false; + wm8994->jack_mic = true; + + t0_micd_set_rate(codec); + + if ((!size) || (buf[0] != '1')) { + snd_soc_jack_report(wm8994->micdet[0].jack, + 0, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced remove microphone\n"); + } else { + + snd_soc_jack_report(wm8994->micdet[0].jack, + SND_JACK_HEADSET, SND_JACK_HEADSET); + dev_info(codec->dev, "Forced detect microphone\n"); + } + + return size; +} + +static ssize_t reselect_jack_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + pr_info("%s : operate nothing\n", __func__); + return 0; +} + +static ssize_t reselect_jack_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct snd_soc_codec *codec = dev_get_drvdata(dev); + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + + reg = snd_soc_read(codec, WM8958_MIC_DETECT_3); + if (reg == 0x402) { + dev_info(codec->dev, "Detected open circuit\n"); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + if (wm8994->active_refcount) { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_AUDIO); + } else { + snd_soc_update_bits(codec, + WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, + WM1811_JACKDET_MODE_JACK); + } + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } + return size; +} + +static DEVICE_ATTR(reselect_jack, S_IRUGO | S_IWUSR | S_IWGRP, + reselect_jack_show, reselect_jack_store); + +static DEVICE_ATTR(select_jack, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_select_jack_show, earjack_select_jack_store); + +static DEVICE_ATTR(key_state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_key_state_show, earjack_key_state_store); + +static DEVICE_ATTR(state, S_IRUGO | S_IWUSR | S_IWGRP, + earjack_state_show, earjack_state_store); +#endif + +static int t0_wm1811_init_paiftx(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + struct snd_soc_dai *aif1_dai = rtd->codec_dai; + struct wm8994 *control = codec->control_data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int ret; + + midas_snd_set_mclk(true, false); + + rtd->codec_dai->driver->playback.channels_max = + rtd->cpu_dai->driver->playback.channels_max; + + ret = snd_soc_add_controls(codec, t0_controls, + ARRAY_SIZE(t0_controls)); + + ret = snd_soc_dapm_new_controls(&codec->dapm, t0_dapm_widgets, + ARRAY_SIZE(t0_dapm_widgets)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM widgets: %d\n", ret); + + ret = snd_soc_dapm_add_routes(&codec->dapm, t0_dapm_routes, + ARRAY_SIZE(t0_dapm_routes)); + if (ret != 0) + dev_err(codec->dev, "Failed to add DAPM routes: %d\n", ret); + + ret = snd_soc_dai_set_sysclk(aif1_dai, WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Failed to boot clocking\n"); + + /* Force AIF1CLK on as it will be master for jack detection */ + if (wm8994->revision > 1) { + ret = snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); + if (ret < 0) + dev_err(codec->dev, "Failed to enable AIF1CLK: %d\n", + ret); + } + + ret = snd_soc_dapm_disable_pin(&codec->dapm, "S5P RP"); + if (ret < 0) + dev_err(codec->dev, "Failed to disable S5P RP: %d\n", ret); + + snd_soc_dapm_ignore_suspend(&codec->dapm, "RCV"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "SPK"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HP"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Headset Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Sub Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "Main Mic"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3DACDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF1ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF2ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "AIF3ADCDAT"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "FM In"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "LINE"); + snd_soc_dapm_ignore_suspend(&codec->dapm, "HDMI"); + + wm1811->codec = codec; + + t0_micd_set_rate(codec); + +#ifdef CONFIG_SEC_DEV_JACK + /* By default use idle_bias_off, will override for WM8994 */ + codec->dapm.idle_bias_off = 0; +#else /* CONFIG_SEC_DEV_JACK */ + wm1811->jack.status = 0; + + ret = snd_soc_jack_new(codec, "T0 Jack", + SND_JACK_HEADSET | SND_JACK_BTN_0 | + SND_JACK_BTN_1 | SND_JACK_BTN_2, + &wm1811->jack); + + if (ret < 0) + dev_err(codec->dev, "Failed to create jack: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_0, KEY_MEDIA); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_MEDIA: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_1, + KEY_VOLUMEDOWN); + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEUP: %d\n", ret); + + ret = snd_jack_set_key(wm1811->jack.jack, SND_JACK_BTN_2, + KEY_VOLUMEUP); + + if (ret < 0) + dev_err(codec->dev, "Failed to set KEY_VOLUMEDOWN: %d\n", ret); + + if (wm8994->revision > 1) { + dev_info(codec->dev, "wm1811: Rev %c support mic detection\n", + 'A' + wm8994->revision); + ret = wm8958_mic_detect(codec, &wm1811->jack, t0_micdet, + wm1811); + + if (ret < 0) + dev_err(codec->dev, "Failed start detection: %d\n", + ret); + } else { + dev_info(codec->dev, "wm1811: Rev %c doesn't support mic detection\n", + 'A' + wm8994->revision); + codec->dapm.idle_bias_off = 0; + } + /* To wakeup for earjack event in suspend mode */ + enable_irq_wake(control->irq); + + wake_lock_init(&wm1811->jackdet_wake_lock, + WAKE_LOCK_SUSPEND, "T0_jackdet"); + + /* To support PBA function test */ + jack_class = class_create(THIS_MODULE, "audio"); + + if (IS_ERR(jack_class)) + pr_err("Failed to create class\n"); + + jack_dev = device_create(jack_class, NULL, 0, codec, "earjack"); + + if (device_create_file(jack_dev, &dev_attr_select_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_select_jack.attr.name); + + if (device_create_file(jack_dev, &dev_attr_key_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_key_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_state) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_state.attr.name); + + if (device_create_file(jack_dev, &dev_attr_reselect_jack) < 0) + pr_err("Failed to create device file (%s)!\n", + dev_attr_reselect_jack.attr.name); + +#endif /* CONFIG_SEC_DEV_JACK */ + return snd_soc_dapm_sync(&codec->dapm); +} + +static struct snd_soc_dai_link t0_dai[] = { + { /* Sec_Fifo DAI i/f */ + .name = "Sec_FIFO TX", + .stream_name = "Sec_Dai", + .cpu_dai_name = "samsung-i2s.4", + .codec_dai_name = "wm8994-aif1", +#ifndef CONFIG_SND_SOC_SAMSUNG_USE_DMA_WRAPPER + .platform_name = "samsung-audio-idma", +#else + .platform_name = "samsung-audio", +#endif + .codec_name = "wm8994-codec", + .init = t0_wm1811_init_paiftx, + .ops = &t0_wm1811_aif1_ops, + }, + { + .name = "T0_WM1811 Voice", + .stream_name = "Voice Tx/Rx", + .cpu_dai_name = "t0.cp", + .codec_dai_name = "wm8994-aif2", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif2_ops, + .ignore_suspend = 1, + }, + { + .name = "T0_WM1811 BT", + .stream_name = "BT Tx/Rx", + .cpu_dai_name = "t0.bt", + .codec_dai_name = "wm8994-aif3", + .platform_name = "snd-soc-dummy", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif3_ops, + .ignore_suspend = 1, + }, + { /* Primary DAI i/f */ + .name = "WM8994 AIF1", + .stream_name = "Pri_Dai", + .cpu_dai_name = "samsung-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "samsung-audio", + .codec_name = "wm8994-codec", + .ops = &t0_wm1811_aif1_ops, + }, +}; + +static int t0_card_suspend_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_FORCE) { + dev_info(codec->dev, + "%s: entering force vmid mode\n", __func__); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(0); + wm8994_vmid_mode(codec, WM8994_VMID_NORMAL); + } +#endif +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_disable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static int t0_card_suspend_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + struct snd_soc_dai *aif2_dai = card->rtd[1].codec_dai; + int ret; + + if (!codec->active) { + ret = snd_soc_dai_set_sysclk(aif2_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2: %d\n", + ret); + + ret = snd_soc_dai_set_pll(aif2_dai, WM8994_FLL2, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL2\n"); + + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_MCLK2, + MIDAS_DEFAULT_MCLK2, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(codec->dev, "Unable to switch to MCLK2\n"); + + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, 0, 0, 0); + + if (ret < 0) + dev_err(codec->dev, "Unable to stop FLL1\n"); + + midas_snd_set_mclk(false, true); + } + +#ifdef CONFIG_ARCH_EXYNOS5 + exynos5_sys_powerdown_xxti_control(midas_snd_get_mclk() ? 1 : 0); +#else /* for CONFIG_ARCH_EXYNOS5 */ + exynos4_sys_powerdown_xusbxti_control(midas_snd_get_mclk() ? 1 : 0); +#endif + + return 0; +} + +static int t0_card_resume_pre(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai; + int ret; + + midas_snd_set_mclk(true, false); + + /* Switch the FLL */ + ret = snd_soc_dai_set_pll(aif1_dai, WM8994_FLL1, + WM8994_FLL_SRC_MCLK1, + MIDAS_DEFAULT_MCLK1, + MIDAS_DEFAULT_SYNC_CLK); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to start FLL1: %d\n", ret); + + /* Then switch AIF1CLK to it */ + ret = snd_soc_dai_set_sysclk(aif1_dai, + WM8994_SYSCLK_FLL1, + MIDAS_DEFAULT_SYNC_CLK, + SND_SOC_CLOCK_IN); + + if (ret < 0) + dev_err(aif1_dai->dev, "Unable to switch to FLL1: %d\n", ret); + + return 0; +} + +static int t0_card_resume_post(struct snd_soc_card *card) +{ + struct snd_soc_codec *codec = card->rtd->codec; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + int reg = 0; + +#ifdef CONFIG_SND_USE_LINEOUT_SWITCH + struct wm1811_machine_priv *wm1811 + = snd_soc_card_get_drvdata(codec->card); + + if (lineout_mode == 1 && + wm8994->vmid_mode == WM8994_VMID_NORMAL) { + dev_info(codec->dev, + "%s: entering normal vmid mode\n", __func__); + wm8994_vmid_mode(codec, WM8994_VMID_FORCE); + if (wm1811->lineout_switch_f) + wm1811->lineout_switch_f(1); + } +#endif + /* workaround for jack detection + * sometimes WM8994_GPIO_1 type changed wrong function type + * so if type mismatched, update to IRQ type + */ + reg = snd_soc_read(codec, WM8994_GPIO_1); + + if ((reg & WM8994_GPN_FN_MASK) != WM8994_GP_FN_IRQ) { + dev_err(codec->dev, "%s: GPIO1 type 0x%x\n", __func__, reg); + snd_soc_write(codec, WM8994_GPIO_1, WM8994_GP_FN_IRQ); + } + +#ifdef CONFIG_SEC_DEV_JACK + snd_soc_dapm_force_enable_pin(&codec->dapm, "AIF1CLK"); +#endif + + return 0; +} + +static struct snd_soc_card t0_card = { + .name = "T0_WM1811", + .dai_link = t0_dai, + + /* If you want to use sec_fifo device, + * changes the num_link = 2 or ARRAY_SIZE(t0_dai). */ + .num_links = ARRAY_SIZE(t0_dai), + + .suspend_pre = t0_card_suspend_pre, + .suspend_post = t0_card_suspend_post, + .resume_pre = t0_card_resume_pre, + .resume_post = t0_card_resume_post +}; + +static struct platform_device *t0_snd_device; + +static void t0_jackdet_set_mode(struct snd_soc_codec *codec, u16 mode) +{ + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); + + if (!wm8994->jackdet || !wm8994->jack_cb) + return; + + if (wm8994->active_refcount) + mode = WM1811_JACKDET_MODE_AUDIO; + + if (mode == wm8994->jackdet_mode) + return; + + wm8994->jackdet_mode = mode; + + /* Always use audio mode to detect while the system is active */ + if (mode != WM1811_JACKDET_MODE_NONE) + mode = WM1811_JACKDET_MODE_AUDIO; + + snd_soc_update_bits(codec, WM8994_ANTIPOP_2, + WM1811_JACKDET_MODE_MASK, mode); +} + +static irqreturn_t t0_g_det_thread(int irq, void *data) +{ + struct wm1811_machine_priv *wm1811 = data; + struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(wm1811->codec); + struct snd_soc_codec *codec = wm8994->codec; + + + if (wm1811->get_g_det_value_f()) { + + pr_info("%s: G_DET_N GPIO is High!!!!", __func__); + + mutex_lock(&wm8994->accdet_lock); + + snd_soc_update_bits(codec, WM8958_MICBIAS2, + WM8958_MICB2_DISCH, WM8958_MICB2_DISCH); + + /* Enable debounce while removed */ + snd_soc_update_bits(codec, WM1811_JACKDET_CTRL, + WM1811_JACKDET_DB, WM1811_JACKDET_DB); + + wm8994->mic_detecting = false; + wm8994->jack_mic = false; + + snd_soc_update_bits(codec, WM8958_MIC_DETECT_1, + WM8958_MICD_ENA, 0); + + t0_jackdet_set_mode(codec, WM1811_JACKDET_MODE_JACK); + + mutex_unlock(&wm8994->accdet_lock); + + mutex_lock(&codec->mutex); + + snd_soc_dapm_disable_pin(&codec->dapm, "MICBIAS2"); + snd_soc_dapm_sync(&codec->dapm); + + mutex_unlock(&codec->mutex); + + snd_soc_jack_report(wm8994->micdet[0].jack, 0, + SND_JACK_MECHANICAL | SND_JACK_HEADSET | + wm8994->btn_mask); + } else { + pr_info("%s: G_DET_N GPIO is Low!!!!", __func__); + + handle_nested_irq(WM1811_JACKDET_IRQ_NUM); + msleep(100); + handle_nested_irq(WM1811_MIC_IRQ_NUM); + } + + return IRQ_HANDLED; + +} + +static int __init t0_audio_init(void) +{ + struct wm1811_machine_priv *wm1811; + const struct exynos_sound_platform_data *sound_pdata; + int ret; + + wm1811 = kzalloc(sizeof *wm1811, GFP_KERNEL); + if (!wm1811) { + pr_err("Failed to allocate memory\n"); + ret = -ENOMEM; + goto err_kzalloc; + } + snd_soc_card_set_drvdata(&t0_card, wm1811); + + t0_snd_device = platform_device_alloc("soc-audio", -1); + if (!t0_snd_device) { + ret = -ENOMEM; + goto err_device_alloc; + } + + ret = snd_soc_register_dais(&t0_snd_device->dev, t0_ext_dai, + ARRAY_SIZE(t0_ext_dai)); + if (ret != 0) + pr_err("Failed to register external DAIs: %d\n", ret); + + platform_set_drvdata(t0_snd_device, &t0_card); + + ret = platform_device_add(t0_snd_device); + if (ret) + platform_device_put(t0_snd_device); + + sound_pdata = exynos_sound_get_platform_data(); + if (!sound_pdata) { + pr_info("%s: don't use sound pdata\n", __func__); + goto err_out_free; + } + + if (sound_pdata->set_lineout_switch) + wm1811->lineout_switch_f = sound_pdata->set_lineout_switch; + + if (sound_pdata->set_ext_main_mic) + wm1811->set_main_mic_f = sound_pdata->set_ext_main_mic; + + if (sound_pdata->set_ext_sub_mic) + wm1811->set_sub_mic_f = sound_pdata->set_ext_sub_mic; +#if defined(CONFIG_SND_DUOS_MODEM_SWITCH) + if (sound_pdata->set_modem_switch) + wm1811->set_modem_switch_f = sound_pdata->set_modem_switch; +#endif + if (sound_pdata->get_ground_det_value) + wm1811->get_g_det_value_f = sound_pdata->get_ground_det_value; + + if (sound_pdata->get_ground_det_irq_num) { + wm1811->get_g_det_irq_num_f = + sound_pdata->get_ground_det_irq_num; + ret = request_threaded_irq(wm1811->get_g_det_irq_num_f(), NULL, + t0_g_det_thread, IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "g_det irq", wm1811); + if (ret != 0) + pr_err("%s: Failed to register IRQ\n", __func__); + + /* to handle insert/removal when we're sleeping in a call */ + ret = enable_irq_wake(wm1811->get_g_det_irq_num_f()); + if (ret) + pr_err("%s : Failed to enable_irq_wake\n", __func__); + } + + return ret; + +err_out_free: + platform_device_put(t0_snd_device); +err_device_alloc: + kfree(wm1811); +err_kzalloc: + return ret; +} +module_init(t0_audio_init); + +static void __exit t0_audio_exit(void) +{ + struct snd_soc_card *card = &t0_card; + struct wm1811_machine_priv *wm1811 = snd_soc_card_get_drvdata(card); + platform_device_unregister(t0_snd_device); + kfree(wm1811); +} +module_exit(t0_audio_exit); + +MODULE_AUTHOR("Uk Kim <w0806.kim@samsung.com>"); +MODULE_DESCRIPTION("ALSA SoC T0 WM1811"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 2fb57cf..582a285 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2354,7 +2354,7 @@ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, return ret; old = ret; - new = (old & ~mask) | value; + new = (old & ~mask) | (value & mask); change = old != new; if (change) { ret = snd_soc_write(codec, reg, new); diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 59984f2..004558f 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -23,13 +23,17 @@ #include <linux/jack.h> #endif +#ifdef CONFIG_SWITCH #include <linux/switch.h> +#endif #include <linux/sec_jack.h> +#ifdef CONFIG_SWITCH /* Android jack detection */ static struct switch_dev android_switch = { .name = "h2w", }; +#endif /** @@ -53,7 +57,9 @@ int snd_soc_jack_new(struct snd_soc_codec *codec, const char *id, int type, INIT_LIST_HEAD(&jack->jack_zones); BLOCKING_INIT_NOTIFIER_HEAD(&jack->notifier); +#ifdef CONFIG_SWITCH switch_dev_register(&android_switch); +#endif return snd_jack_new(codec->card->snd_card, id, type, &jack->jack); } @@ -81,6 +87,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) int enable; int oldstatus; +#ifdef CONFIG_SWITCH if (mask & SND_JACK_HEADSET) { if (status & SND_JACK_MICROPHONE) switch_set_state(&android_switch, SEC_HEADSET_4POLE); @@ -89,6 +96,7 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) else switch_set_state(&android_switch, SEC_JACK_NO_DEVICE); } +#endif #ifdef CONFIG_JACK_MON if (mask & SND_JACK_HEADSET) { diff --git a/sound/soc/soc-utils.c b/sound/soc/soc-utils.c index cd987de..241eb05 100644 --- a/sound/soc/soc-utils.c +++ b/sound/soc/soc-utils.c @@ -18,6 +18,9 @@ #include <sound/pcm.h> #include <sound/pcm_params.h> #include <sound/soc.h> +#ifdef CONFIG_SLP_WIP +#include <linux/slab.h> +#endif int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots) { @@ -72,16 +75,116 @@ static const struct snd_pcm_hardware dummy_dma_hardware = { .periods_max = 128, }; +#ifdef CONFIG_SLP_WIP +struct dma_dummy { + unsigned long base_time; + unsigned long pos; + unsigned long max; +}; +#endif + static int dummy_dma_open(struct snd_pcm_substream *substream) { +#ifdef CONFIG_SLP_WIP + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_dummy *pdma; + + pdma = kzalloc(sizeof(struct dma_dummy), GFP_KERNEL); + if (!pdma) + return -ENOMEM; + + runtime->private_data = pdma; +#endif snd_soc_set_runtime_hwparams(substream, &dummy_dma_hardware); return 0; } +#ifdef CONFIG_SLP_WIP +static int dummy_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_dummy *pdma = runtime->private_data; + + kfree(pdma); + + return 0; +} + +static int dummy_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_dummy *pdma = runtime->private_data; + + pdma->base_time = jiffies; + pdma->pos = 0; + pdma->max = runtime->buffer_size * HZ; + + return 0; +} + +static int dummy_dma_copy(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, + void __user *dst, snd_pcm_uframes_t count) +{ + /* do nothing */ + return 0; +} + +static snd_pcm_uframes_t +dummy_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_dummy *pdma = runtime->private_data; + unsigned long delta; + unsigned long pos; + + delta = jiffies - pdma->base_time; + if (!delta) + return pdma->pos; + + /* update base_time */ + pdma->base_time += delta; + + /* calc */ + delta *= runtime->rate; + pdma->pos += delta; + while (pdma->pos >= pdma->max) + pdma->pos -= pdma->max; + + pos = pdma->pos / HZ; + + return pos; +} + +static int dummy_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct dma_dummy *pdma = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + pdma->base_time = jiffies; + break; + default: + break; + } + + return 0; +} +#endif + static struct snd_pcm_ops dummy_dma_ops = { .open = dummy_dma_open, .ioctl = snd_pcm_lib_ioctl, +#ifdef CONFIG_SLP_WIP + .close = dummy_dma_close, + .copy = dummy_dma_copy, + .pointer = dummy_dma_pointer, + .prepare = dummy_dma_prepare, + .trigger = dummy_dma_trigger, +#endif }; static struct snd_soc_platform_driver dummy_platform = { |