From 5925e2c0be9cc5453d4441e5c16a1f25df4a0fc5 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Tue, 29 Mar 2011 09:45:35 +0800 Subject: [PATCH] ENGR00141217-8 ASoC: SGTL5000: Add DAP support for sgtl5000 codec upgrade to 2.6.38 kernel Add Digital Audio Process(DAP) for sgtl5000 codec Audio: imx53,loco,audio: make Loco sgtl5000 codec work config audmux when ssi probed, and fix sdma watermark settings Signed-off-by: Zeng Zhaoming --- sound/soc/codecs/sgtl5000.c | 287 +++++++++++++++++++++++++--- sound/soc/imx/Kconfig | 9 + sound/soc/imx/Makefile | 2 + sound/soc/imx/imx-sgtl5000.c | 355 +++++++++++++++++++++++++++++++++++ 4 files changed, 632 insertions(+), 21 deletions(-) create mode 100644 sound/soc/imx/imx-sgtl5000.c diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index c49317c255d1..556fd939a05b 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -171,6 +171,28 @@ static int small_pop_event(struct snd_soc_dapm_widget *w, return 0; } +static int dap_enable_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(w->codec, SGTL5000_DAP_CTRL, + 0x1, 0x1); + break; + + case SND_SOC_DAPM_PRE_PMD: + snd_soc_update_bits(w->codec, SGTL5000_DAP_CTRL, + 0x1, 0); + snd_soc_dapm_disable_pin(&w->codec->dapm, "DAP_MIXER Mixer Channel"); + snd_soc_dapm_sync(&w->codec->dapm); + break; + default: + break; + } + + return 0; +} + /* input sources for ADC */ static const char *adc_mux_text[] = { "MIC_IN", "LINE_IN" @@ -183,15 +205,50 @@ static const struct snd_kcontrol_new adc_mux = SOC_DAPM_ENUM("Capture Mux", adc_enum); /* input sources for DAC */ -static const char *dac_mux_text[] = { +static const char *hp_mux_text[] = { "DAC", "LINE_IN" }; -static const struct soc_enum dac_enum = -SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, dac_mux_text); +static const struct soc_enum hp_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_ANA_CTRL, 6, 2, hp_mux_text); -static const struct snd_kcontrol_new dac_mux = -SOC_DAPM_ENUM("Headphone Mux", dac_enum); +static const struct snd_kcontrol_new hp_mux = +SOC_DAPM_ENUM("Headphone Mux", hp_enum); + +static const char *dap_in_mux_text[] = { + "ADC", "I2S_IN" +}; +static const struct soc_enum dap_in_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 6, 2, dap_in_mux_text); + +static const struct snd_kcontrol_new dap_in_mux = +SOC_DAPM_ENUM("DAP input mux", dap_in_enum); + +static const struct soc_enum dap_mixer_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 8, 2, dap_in_mux_text); + +static const struct snd_kcontrol_new dap_mixer_mux = +SOC_DAPM_ENUM("DAP mixer channel mux", dap_mixer_enum); + +static const char *dap_out_mux_text[] = { + "ADC", "I2S_IN", "reserved", "DAP" +}; + +static const struct soc_enum dac_in_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 4, 4, dap_out_mux_text); + +static const struct snd_kcontrol_new dac_in_mux = +SOC_DAPM_ENUM("DAC input mux", dac_in_enum); + +static const struct soc_enum i2s_out_enum = +SOC_ENUM_SINGLE(SGTL5000_CHIP_SSS_CTRL, 0, 4, dap_out_mux_text); + +static const struct snd_kcontrol_new i2s_out_mux = +SOC_DAPM_ENUM("i2s output mux", i2s_out_enum); + +static const struct snd_kcontrol_new dap_mixer_controls[] = { +SOC_DAPM_SINGLE("Mixer Channel", SGTL5000_DAP_CTRL, 4, 1, 0), +}; static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_INPUT("LINE_IN"), @@ -200,6 +257,16 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("HP_OUT"), SND_SOC_DAPM_OUTPUT("LINE_OUT"), + /* aif for i2s input */ + SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", + 0, SGTL5000_CHIP_DIG_POWER, + 0, 0), + + /* aif for i2s output */ + SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", + 0, SGTL5000_CHIP_DIG_POWER, + 1, 0), + SND_SOC_DAPM_MICBIAS_E("Mic Bias", SGTL5000_CHIP_MIC_CTRL, 8, 0, mic_bias_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), @@ -212,20 +279,19 @@ static const struct snd_soc_dapm_widget sgtl5000_dapm_widgets[] = { SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_MUX("Capture Mux", SND_SOC_NOPM, 0, 0, &adc_mux), - SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &dac_mux), + SND_SOC_DAPM_MUX("Headphone Mux", SND_SOC_NOPM, 0, 0, &hp_mux), + SND_SOC_DAPM_MUX_E("DAP_IN", SGTL5000_CHIP_DIG_POWER, 4, 0, &dap_in_mux, + dap_enable_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("MIX_MUX", SND_SOC_NOPM, 0, 0, &dap_mixer_mux), + SND_SOC_DAPM_MUX("DAC_IN", SND_SOC_NOPM, 0, 0, &dac_in_mux), + SND_SOC_DAPM_MUX("I2S_OUT", SND_SOC_NOPM, 0, 0, &i2s_out_mux), - /* aif for i2s input */ - SND_SOC_DAPM_AIF_IN("AIFIN", "Playback", - 0, SGTL5000_CHIP_DIG_POWER, - 0, 0), - - /* aif for i2s output */ - SND_SOC_DAPM_AIF_OUT("AIFOUT", "Capture", - 0, SGTL5000_CHIP_DIG_POWER, - 1, 0), + SND_SOC_DAPM_MIXER("DAP_MIXER", SND_SOC_NOPM, 0, 0, + &dap_mixer_controls[0], + ARRAY_SIZE(dap_mixer_controls)), SND_SOC_DAPM_ADC("ADC", "Capture", SGTL5000_CHIP_ANA_POWER, 1, 0), - SND_SOC_DAPM_DAC("DAC", "Playback", SGTL5000_CHIP_ANA_POWER, 3, 0), }; @@ -235,9 +301,26 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Capture Mux", "MIC_IN", "MIC_IN"}, /* mic_in --> adc_mux */ {"ADC", NULL, "Capture Mux"}, /* adc_mux --> adc */ - {"AIFOUT", NULL, "ADC"}, /* adc --> i2s_out */ - {"DAC", NULL, "AIFIN"}, /* i2s-->dac,skip audio mux */ + {"DAP_IN", "ADC", "ADC"}, + {"DAP_IN", "I2S_IN", "AIFIN"}, + + {"MIX_MUX", "ADC", "ADC"}, + {"MIX_MUX", "I2S_IN", "AIFIN"}, + + {"DAP_MIXER", NULL, "DAP_IN"}, + {"DAP_MIXER", "Mixer Channel", "MIX_MUX"}, + + {"I2S_OUT", "ADC", "ADC"}, /* ADC --> I2S_OUT */ + {"I2S_OUT", "I2S_IN", "AIFIN"}, /* ADC --> Audio Switch */ + {"I2S_OUT", "DAP", "DAP_MIXER"}, /* ADC --> Audio Switch */ + {"AIFOUT", NULL, "I2S_OUT"}, /* ADC --> Audio Switch */ + + {"DAC_IN", "ADC", "ADC"}, /* i2s-->dac,skip audio mux */ + {"DAC_IN", "I2S_IN", "AIFIN"}, /* i2s-->dac,skip audio mux */ + {"DAC_IN", "DAP", "DAP_MIXER"}, /* i2s-->dac,skip audio mux */ + {"DAC", NULL, "DAC_IN"}, + {"Headphone Mux", "DAC", "DAC"}, /* dac --> hp_mux */ {"LO", NULL, "DAC"}, /* dac --> line_out */ @@ -363,6 +446,117 @@ static int dac_put_volsw(struct snd_kcontrol *kcontrol, return 0; } +/* + * we need to caculator threshold dB by: + * register_value = ((10^(dB/20))*0.636)*2^15 ==> + * dB = (fls(register_value) - 14.347)*6.02 + */ +static int avc_thrd_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg = snd_soc_read(codec, SGTL5000_DAP_AVC_THRESHOLD); + int l; + /* 1/1000 of dB */ + int fraction[5] = {585, 322, 170, 87, 44}; + int i; + int tmp; + + if (!reg) { + ucontrol->value.integer.value[0] = 96; + ucontrol->value.integer.value[1] = 96; + return 0; + } + + /* caculate fls(register_value) */ + l = fls(reg) - 1; + tmp = l * 1000; /* scale-up 1000 times */ + for (i = l-1; i >= 0 && i >= l-5; i--) { + if (reg & 0x1 << i) + tmp += fraction[l-1-i]; + } + tmp = ((tmp - 14347) * 6) / 1000; + tmp = clamp(-tmp, 0, 96); + + ucontrol->value.integer.value[0] = tmp; + ucontrol->value.integer.value[1] = tmp; + + return 0; +} + +/* + * we need to caculator threshold dB by: + * register_value = ((10^(dB/20))*0.636)*2^15 + * = 2^(3.322*db/20 + 15) * 0.636 + * = 2^(3.322*db/20 + 15 - 0.653) + */ +static int avc_thrd_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 reg; + int db; + int tmp; + int remain; + int i; + int fraction[5] = {1414, 1189, 1091, 1044, 1022}; + + db = -(ucontrol->value.integer.value[0]); + + tmp = ((3322 * db) / 20 + 15000 - 653); + tmp = clamp(tmp, 0, 16000); + + remain = tmp % 1000; + tmp = tmp / 1000; + + reg = 0x1 << tmp; + + for (i = 0; i < 5; i++) { + int level = 1000 / (0x1 << (i+1)); + if (remain >= level) { + remain -= level; + reg = reg * fraction[i] / 1000; + } + } + + snd_soc_write(codec, SGTL5000_DAP_AVC_THRESHOLD, reg); + + return 0; +} + +static int mixer_vol_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *control = + (struct soc_mixer_control *)kcontrol->private_value; + u16 reg = snd_soc_read(codec, control->reg); + int vol; + + vol = (reg * 100) >> 15; + + ucontrol->value.integer.value[0] = vol; + ucontrol->value.integer.value[1] = vol; + return 0; +} + +static int mixer_vol_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *control = + (struct soc_mixer_control *)kcontrol->private_value; + u16 reg; + int vol; + + vol = ucontrol->value.integer.value[0]; + reg = (vol << 15) / 100; + snd_soc_write(codec, control->reg, reg); + + return 0; +} + + static const DECLARE_TLV_DB_SCALE(capture_6db_attenuate, -600, 600, 0); /* tlv for mic gain, 0db 20db 30db 40db */ @@ -374,6 +568,14 @@ static const unsigned int mic_gain_tlv[] = { /* tlv for hp volume, -51.5db to 12.0db, step .5db */ static const DECLARE_TLV_DB_SCALE(headphone_volume, -5150, 50, 0); +static const unsigned int bass_high_filter_freq[] = { + TLV_DB_RANGE_HEAD(7), + 0, 0, TLV_DB_SCALE_ITEM(80, 0, 0), + 1, 6, TLV_DB_SCALE_ITEM(100, 25, 0), +}; +static const DECLARE_TLV_DB_SCALE(avc_max_gain, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(avc_threshold, 0, 1, 0); +static const DECLARE_TLV_DB_SCALE(mixer_volume, 0, 1, 0); static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { /* SOC_DOUBLE_S8_TLV with invert */ @@ -403,6 +605,37 @@ static const struct snd_kcontrol_new sgtl5000_snd_controls[] = { SOC_SINGLE_TLV("Mic Volume", SGTL5000_CHIP_MIC_CTRL, 0, 4, 0, mic_gain_tlv), + + /* Bass Enhance enable */ + SOC_SINGLE("Bass Enable", SGTL5000_DAP_BASS_ENHANCE, + 0, 1, 0), + SOC_SINGLE_TLV("Bass Filter Feq", SGTL5000_DAP_BASS_ENHANCE, + 6, 7, 0, bass_high_filter_freq), + SOC_SINGLE("Bass Volume", SGTL5000_DAP_BASS_ENHANCE_CTRL, + 8, 0x3f, 1), + /* Bass Harmonic Level Control */ + SOC_SINGLE("Bass Level", SGTL5000_DAP_BASS_ENHANCE_CTRL, + 0, 0x7f, 1), + + /* DAP Surround */ + SOC_SINGLE("Surround Width", SGTL5000_DAP_SURROUND, + 4, 0x7, 0), + + /* DAP MAIN Channel Voluem */ + SOC_SINGLE_EXT_TLV("Main Channel Volume", SGTL5000_DAP_MAIN_CHAN, + 0, 200, 0, mixer_vol_get, mixer_vol_put, mixer_volume), + SOC_SINGLE_EXT_TLV("Mixer Channel Volume", SGTL5000_DAP_MIX_CHAN, + 0, 200, 0, mixer_vol_get, mixer_vol_put, mixer_volume), + + /* DAP AVC */ + SOC_SINGLE("AVC Enable", SGTL5000_DAP_AVC_CTRL, + 0, 1, 0), + SOC_SINGLE("AVC Hard Limit", SGTL5000_DAP_AVC_CTRL, + 5, 1, 0), + SOC_SINGLE_TLV("AVC Max Gain", SGTL5000_DAP_AVC_CTRL, + 12, 2, 0, avc_max_gain), + SOC_SINGLE_EXT_TLV("AVC Threshold (-dB)", SGTL5000_DAP_AVC_THRESHOLD, + 0, 96, 0, avc_thrd_get, avc_thrd_put, avc_threshold), }; /* mute the codec used by alsa core */ @@ -747,6 +980,20 @@ static int ldo_regulator_enable(struct regulator_dev *dev) if (ldo_regulator_is_enabled(dev)) return 0; + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP| + SGTL5000_STARTUP_POWERUP| + SGTL5000_REFTOP_POWERUP, + 0); + udelay(10); + snd_soc_update_bits(codec, SGTL5000_CHIP_ANA_POWER, + SGTL5000_LINREG_SIMPLE_POWERUP| + SGTL5000_STARTUP_POWERUP| + SGTL5000_REFTOP_POWERUP, + SGTL5000_LINREG_SIMPLE_POWERUP| + SGTL5000_STARTUP_POWERUP| + SGTL5000_REFTOP_POWERUP); + udelay(10); /* set regulator value firstly */ reg = (1600 - ldo->voltage / 1000) / 50; reg = clamp(reg, 0x0, 0xf); @@ -951,8 +1198,7 @@ static struct snd_soc_dai_driver sgtl5000_dai = { .symmetric_rates = 1, }; -static int sgtl5000_volatile_register(struct snd_soc_codec *codec, - unsigned int reg) +static int sgtl5000_volatile_register(unsigned int reg) { switch (reg) { case SGTL5000_CHIP_ID: @@ -1199,7 +1445,6 @@ static int sgtl5000_enable_regulators(struct snd_soc_codec *codec) } sgtl5000->supplies[VDDD].supply = LDO_CONSUMER_NAME; - ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sgtl5000->supplies), sgtl5000->supplies); diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index bb699bb55a50..1b1206fca7a7 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -44,6 +44,15 @@ config SND_SOC_PHYCORE_AC97 Say Y if you want to add support for SoC audio on Phytec phyCORE and phyCARD boards in AC97 mode +config SND_SOC_IMX_SGTL5000 + tristate "SoC Audio support for i.MX boards with sgtl5000" + depends on MACH_MX35_3DS || MACH_MX51_BABBAGE + select SND_SOC_SGTL5000 + select SND_MXC_SOC_MX2 + help + Say Y if you want to add support for SoC audio on an i.MX board with + a sgtl5000 codec. + config SND_SOC_EUKREA_TLV320 tristate "Eukrea TLV320" depends on MACH_EUKREA_MBIMX27_BASEBOARD \ diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index d6d609ba7e24..38693ed074a0 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -12,8 +12,10 @@ snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx-sgtl5000-objs := imx-sgtl5000.o obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX_SGTL5000) += snd-soc-imx-sgtl5000.o diff --git a/sound/soc/imx/imx-sgtl5000.c b/sound/soc/imx/imx-sgtl5000.c new file mode 100644 index 000000000000..618d0fe018b5 --- /dev/null +++ b/sound/soc/imx/imx-sgtl5000.c @@ -0,0 +1,355 @@ +/* + * sound/soc/imx/3ds-sgtl5000.c -- SoC audio for i.MX 3ds boards with + * sgtl5000 codec + * + * Copyright 2009 Sascha Hauer, Pengutronix + * Copyright (C) 2011 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" +#include "imx-ssi.h" + +static struct imx_sgtl5000_priv { + int sysclk; + int hw; + struct platform_device *pdev; +} card_priv; + +static struct snd_soc_card imx_sgtl5000; + +static int sgtl5000_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; + u32 dai_format; + int ret; + unsigned int channels = params_channels(params); + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, card_priv.sysclk, 0); + + snd_soc_dai_set_sysclk(codec_dai, SGTL5000_LRCLK, params_rate(params), 0); + + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, dai_format); + if (ret < 0) + return ret; + + + /* TODO: The SSI driver should figure this out for us */ + switch (channels) { + case 2: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffc, 0xfffffffc, 2, 0); + break; + case 1: + snd_soc_dai_set_tdm_slot(cpu_dai, 0xfffffffe, 0xfffffffe, 1, 0); + break; + default: + return -EINVAL; + } + + /* set cpu DAI configuration */ + dai_format = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM; + ret = snd_soc_dai_set_fmt(cpu_dai, dai_format); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops imx_sgtl5000_hifi_ops = { + .hw_params = sgtl5000_params, +}; + +static int sgtl5000_jack_func; +static int sgtl5000_spk_func; +static int sgtl5000_line_in_func; + +static const char *jack_function[] = { "off", "on"}; + +static const char *spk_function[] = { "off", "on" }; + +static const char *line_in_function[] = { "off", "on" }; + +static const struct soc_enum sgtl5000_enum[] = { + SOC_ENUM_SINGLE_EXT(2, jack_function), + SOC_ENUM_SINGLE_EXT(2, spk_function), + SOC_ENUM_SINGLE_EXT(2, line_in_function), +}; + +static int sgtl5000_get_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_jack_func; + return 0; +} + +static int sgtl5000_set_jack(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_jack_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_jack_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_jack_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +static int sgtl5000_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_spk_func; + return 0; +} + +static int sgtl5000_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_spk_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_spk_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_spk_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +static int sgtl5000_get_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = sgtl5000_line_in_func; + return 0; +} + +static int sgtl5000_set_line_in(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (sgtl5000_line_in_func == ucontrol->value.enumerated.item[0]) + return 0; + + sgtl5000_line_in_func = ucontrol->value.enumerated.item[0]; + if (sgtl5000_line_in_func) + snd_soc_dapm_enable_pin(&codec->dapm, "Line In Jack"); + else + snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack"); + + snd_soc_dapm_sync(&codec->dapm); + return 1; +} + +/* imx_3stack card dapm widgets */ +static const struct snd_soc_dapm_widget imx_3stack_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Mic Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_HP("Headphone Jack", NULL), +}; + +static const struct snd_kcontrol_new sgtl5000_machine_controls[] = { + SOC_ENUM_EXT("Jack Function", sgtl5000_enum[0], sgtl5000_get_jack, + sgtl5000_set_jack), + SOC_ENUM_EXT("Speaker Function", sgtl5000_enum[1], sgtl5000_get_spk, + sgtl5000_set_spk), + SOC_ENUM_EXT("Line In Function", sgtl5000_enum[1], sgtl5000_get_line_in, + sgtl5000_set_line_in), +}; + +/* imx_3stack machine connections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Mic Jack --> MIC_IN (with automatic bias) */ + {"MIC_IN", NULL, "Mic Jack"}, + + /* Line in Jack --> LINE_IN */ + {"LINE_IN", NULL, "Line In Jack"}, + + /* HP_OUT --> Headphone Jack */ + {"Headphone Jack", NULL, "HP_OUT"}, + + /* LINE_OUT --> Ext Speaker */ + {"Ext Spk", NULL, "LINE_OUT"}, +}; + +static int imx_3stack_sgtl5000_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + int ret; + + ret = snd_soc_add_controls(codec, sgtl5000_machine_controls, + ARRAY_SIZE(sgtl5000_machine_controls)); + if (ret) + return ret; + + /* Add imx_3stack specific widgets */ + snd_soc_dapm_new_controls(&codec->dapm, imx_3stack_dapm_widgets, + ARRAY_SIZE(imx_3stack_dapm_widgets)); + + /* Set up imx_3stack specific audio path audio_map */ + snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_disable_pin(&codec->dapm, "Line In Jack"); + snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack"); + + snd_soc_dapm_sync(&codec->dapm); + + return 0; +} + +static struct snd_soc_dai_link imx_sgtl5000_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .codec_dai_name = "sgtl5000", + .codec_name = "sgtl5000.1-000a", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-pcm-audio.0", + .init = imx_3stack_sgtl5000_init, + .ops = &imx_sgtl5000_hifi_ops, + }, +}; + +static struct snd_soc_card imx_sgtl5000 = { + .name = "sgtl5000-audio", + .dai_link = imx_sgtl5000_dai, + .num_links = ARRAY_SIZE(imx_sgtl5000_dai), +}; + +static struct platform_device *imx_sgtl5000_snd_device; + +static int imx_audmux_config(int slave, int master) +{ + unsigned int ptcr, pdcr; + slave = slave - 1; + master = master - 1; + + /* SSI0 mastered by port 5 */ + ptcr = MXC_AUDMUX_V2_PTCR_SYN | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(master) | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TCSEL(master); + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(master); + mxc_audmux_v2_configure_port(slave, ptcr, pdcr); + + ptcr = MXC_AUDMUX_V2_PTCR_SYN; + pdcr = MXC_AUDMUX_V2_PDCR_RXDSEL(slave); + mxc_audmux_v2_configure_port(master, ptcr, pdcr); + + return 0; +} + +static int __devinit imx_sgtl5000_probe(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + int ret = 0; + + card_priv.pdev = pdev; + + imx_audmux_config(plat->src_port, plat->ext_port); + + ret = -EINVAL; + if (plat->init && plat->init()) + return ret; + + card_priv.sysclk = plat->sysclk; + + return 0; +} + +static int imx_sgtl5000_remove(struct platform_device *pdev) +{ + struct mxc_audio_platform_data *plat = pdev->dev.platform_data; + + if (plat->finit) + plat->finit(); + + return 0; +} + +static struct platform_driver imx_sgtl5000_audio_driver = { + .probe = imx_sgtl5000_probe, + .remove = imx_sgtl5000_remove, + .driver = { + .name = "imx-sgtl5000", + }, +}; + +static int __init imx_sgtl5000_init(void) +{ + int ret; + + ret = platform_driver_register(&imx_sgtl5000_audio_driver); + if (ret) + return -ENOMEM; + + if (machine_is_mx35_3ds()) + imx_sgtl5000_dai[0].codec_name = "sgtl5000.0-000a"; + else + imx_sgtl5000_dai[0].codec_name = "sgtl5000.1-000a"; + + imx_sgtl5000_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx_sgtl5000_snd_device) + return -ENOMEM; + + platform_set_drvdata(imx_sgtl5000_snd_device, &imx_sgtl5000); + + ret = platform_device_add(imx_sgtl5000_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(imx_sgtl5000_snd_device); + } + + return ret; +} + +static void __exit imx_sgtl5000_exit(void) +{ + platform_driver_unregister(&imx_sgtl5000_audio_driver); + platform_device_unregister(imx_sgtl5000_snd_device); +} + +late_initcall(imx_sgtl5000_init); +module_exit(imx_sgtl5000_exit); + +MODULE_AUTHOR("Sascha Hauer "); +MODULE_DESCRIPTION("PhyCORE ALSA SoC driver"); +MODULE_LICENSE("GPL"); -- 2.39.2