From 810c96accb986a0e2ae0a5fdd02f182845a86a1d Mon Sep 17 00:00:00 2001 From: Philippe Langlais Date: Tue, 14 Jun 2011 14:29:45 +0200 Subject: Ux500 ASoC: Dynamic audio master-clock select A new control in the Ux500_AB8500 machine-driver is added to be able to switch between SYSCLK and ULPCLK. Power management-code related to clocks is also moved from the codec-driver into the machine-driver. ST-Ericsson ID: 325090 Depends On: I641c6eb243552d8d0ff4902cae8ead336fff575c Signed-off-by: Ola Lilja Change-Id: I23ecbee74aeebc64a1dedd0d7c4cdab4194de24b Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/23500 Reviewed-by: Ola LILJA2 Tested-by: Ola LILJA2 Reviewed-by: Jonas ABERG Conflicts: sound/soc/codecs/ab8500_audio.c sound/soc/codecs/ab8500_audio.h sound/soc/ux500/ux500_ab8500.c --- sound/soc/codecs/ab8500_audio.c | 89 +++--------- sound/soc/codecs/ab8500_audio.h | 1 + sound/soc/ux500/ux500_ab8500.c | 290 ++++++++++++++++++++++++++++++---------- 3 files changed, 238 insertions(+), 142 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/ab8500_audio.c b/sound/soc/codecs/ab8500_audio.c index 080f25679f2..ae516469046 100644 --- a/sound/soc/codecs/ab8500_audio.c +++ b/sound/soc/codecs/ab8500_audio.c @@ -14,7 +14,6 @@ * by the Free Software Foundation. */ -#include #include #include #include @@ -173,10 +172,7 @@ static const u8 ab8500_reg_cache[AB8500_CACHEREGNUM] = { }; static struct snd_soc_codec *ab8500_codec; -static struct clk *clk_ptr_audioclk; -static struct clk *clk_ptr_sysclk; -static DEFINE_MUTEX(power_lock); -static int ab8500_power_count; + /* Reads an arbitrary register from the ab8500 chip. */ @@ -1703,59 +1699,26 @@ static void ab8500_codec_configure_audio_macrocell(struct snd_soc_codec *codec) ab8500_codec_write_reg(codec, AB8500_MISC, AB8500_GPIO_DIR4_REG, data); } -static int ab8500_codec_power_control_inc(struct snd_soc_codec *codec) -{ - int ret; - unsigned int set_mask; - - mutex_lock(&power_lock); +/* Extended interface for codec-driver */ - ab8500_power_count++; - pr_debug("ab8500_power_count changed from %d to %d", - ab8500_power_count-1, - ab8500_power_count); +void ab8500_audio_power_control(bool power_on) +{ + if (ab8500_codec == NULL) { + pr_err("%s: ERROR: AB8500 ASoC-driver not yet probed!\n", __func__); + return; + } - if (ab8500_power_count == 1) { + if (power_on) { + unsigned int set_mask; pr_debug("Enabling AB8500."); set_mask = BMASK(REG_POWERUP_POWERUP) | BMASK(REG_POWERUP_ENANA); - ab8500_codec_update_reg_audio(codec, REG_POWERUP, 0x00, set_mask); - - pr_debug("Enabling sysclk."); - ret = clk_enable(clk_ptr_audioclk); - if (ret) { - pr_err("ERROR: clk_enable failed (ret = %d)!", ret); - ab8500_power_count = 0; - return ret; - } - } - - mutex_unlock(&power_lock); - - return 0; -} - -static void ab8500_codec_power_control_dec(struct snd_soc_codec *codec) -{ - unsigned int clear_mask; - - mutex_lock(&power_lock); - - ab8500_power_count--; - - pr_debug("ab8500_power_count changed from %d to %d", - ab8500_power_count+1, - ab8500_power_count); - - if (ab8500_power_count == 0) { - pr_debug("Disabling sysclk."); - clk_disable(clk_ptr_audioclk); - + ab8500_codec_update_reg_audio(ab8500_codec, REG_POWERUP, 0x00, set_mask); + } else { + unsigned int clear_mask; pr_debug("Disabling AB8500."); clear_mask = BMASK(REG_POWERUP_POWERUP) | BMASK(REG_POWERUP_ENANA); - ab8500_codec_update_reg_audio(codec, REG_POWERUP, clear_mask, 0x00); + ab8500_codec_update_reg_audio(ab8500_codec, REG_POWERUP, clear_mask, 0x00); } - - mutex_unlock(&power_lock); } /* Extended interface for codec-driver */ @@ -1888,7 +1851,7 @@ static int ab8500_codec_pcm_prepare(struct snd_pcm_substream *substream, ab8500_codec_read_reg_audio(dai->codec, REG_AUDINTSOURCE1); ab8500_codec_read_reg_audio(dai->codec, REG_AUDINTSOURCE2); - return ab8500_codec_power_control_inc(dai->codec); + return 0; } static void ab8500_codec_pcm_shutdown(struct snd_pcm_substream *substream, @@ -1896,8 +1859,6 @@ static void ab8500_codec_pcm_shutdown(struct snd_pcm_substream *substream, { pr_debug("%s Enter.\n", __func__); - ab8500_codec_power_control_dec(dai->codec); - ab8500_codec_dump_all_reg(dai->codec); } @@ -2220,31 +2181,13 @@ static int ab8500_codec_probe(struct snd_soc_codec *codec) ab8500_codec = codec; - ab8500_power_count = 0; - clk_ptr_sysclk = clk_get(codec->dev, "sysclk"); - if (IS_ERR(clk_ptr_sysclk)) { - pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); - return -EFAULT; - } - clk_ptr_audioclk = clk_get(codec->dev, "audioclk"); - if (IS_ERR(clk_ptr_audioclk)) { - pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); - clk_put(clk_ptr_sysclk); - return -EFAULT; - } - ret = clk_set_parent(clk_ptr_audioclk, clk_ptr_sysclk); - if (ret) { - pr_err("ERROR: clk_set_parent failed (ret = %d)!", ret); - clk_put(clk_ptr_sysclk); - return ret; - } - return ret; } static int ab8500_codec_remove(struct snd_soc_codec *codec) { snd_soc_dapm_free(&codec->dapm); + ab8500_codec = NULL; return 0; } diff --git a/sound/soc/codecs/ab8500_audio.h b/sound/soc/codecs/ab8500_audio.h index a0ad1e3afe4..3c8e44d673c 100644 --- a/sound/soc/codecs/ab8500_audio.h +++ b/sound/soc/codecs/ab8500_audio.h @@ -23,6 +23,7 @@ extern struct snd_soc_dai_driver ab8500_codec_dai[]; extern struct snd_soc_codec_driver soc_codec_dev_ab8500; /* Extended interface for codec-driver */ +void ab8500_audio_power_control(bool power_on); int ab8500_audio_set_word_length(struct snd_soc_dai *dai, unsigned int wl); int ab8500_audio_set_bit_delay(struct snd_soc_dai *dai, unsigned int delay); int ab8500_audio_setup_if1(struct snd_soc_codec *codec, diff --git a/sound/soc/ux500/ux500_ab8500.c b/sound/soc/ux500/ux500_ab8500.c index ec6972478dd..43ba196d86c 100644 --- a/sound/soc/ux500/ux500_ab8500.c +++ b/sound/soc/ux500/ux500_ab8500.c @@ -14,6 +14,7 @@ * by the Free Software Foundation. */ +#include #include #include #include @@ -44,11 +45,68 @@ static struct snd_soc_jack jack; +static int master_clock_sel; +static struct clk *clk_ptr_audioclk; +static struct clk *clk_ptr_sysclk; +static struct clk *clk_ptr_ulpclk; +static DEFINE_MUTEX(power_lock); +static int ab8500_power_count; + /* Slot configuration */ static unsigned int tx_slots = DEF_TX_SLOTS; static unsigned int rx_slots = DEF_RX_SLOTS; -/* List the regulators that are to be controlled.. */ +/* Machine-driver ALSA-controls */ + +static int mclk_input_control_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item) { + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, "ULPCLK"); + } else { + strcpy(uinfo->value.enumerated.name, "SYSCLK"); + } + return 0; +} + +static int mclk_input_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = master_clock_sel; + return 0; +} + +static int mclk_input_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + unsigned int val; + + val = (ucontrol->value.enumerated.item[0] != 0); + if (master_clock_sel == val) + return 0; + + master_clock_sel = val; + + return 1; +} + +static const struct snd_kcontrol_new mclk_input_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Clock Select", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = mclk_input_control_info, + .get = mclk_input_control_get, + .put = mclk_input_control_put, + .private_value = 1 /* ULPCLK */ +}; + +/* Regulators */ + static struct regulator_bulk_data ab8500_regus[4] = { { .supply = "v-dmic" }, { .supply = "v-audio" }, @@ -66,7 +124,7 @@ static int enable_regulator(const char *name) status = regulator_enable(ab8500_regus[i].consumer); if (status != 0) { - pr_err("%s: Failure with regulator %s (%d)\n", + pr_err("%s: Failure with regulator %s (ret = %d)\n", __func__, name, status); return status; }; @@ -91,6 +149,110 @@ static void disable_regulator(const char *name) } } +static int create_regulators(void) +{ + int i, status = 0; + + pr_debug("%s: Enter.\n", __func__); + + for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) + ab8500_regus[i].consumer = NULL; + + for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) { + ab8500_regus[i].consumer = regulator_get(NULL, + ab8500_regus[i].supply); + if (IS_ERR(ab8500_regus[i].consumer)) { + status = PTR_ERR(ab8500_regus[i].consumer); + pr_err("%s: ERROR: Failed to get supply '%s' (ret = %d)!\n", + __func__, ab8500_regus[i].supply, status); + ab8500_regus[i].consumer = NULL; + goto err_get; + } + } + + return 0; + +err_get: + + for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) { + if (ab8500_regus[i].consumer) { + regulator_put(ab8500_regus[i].consumer); + ab8500_regus[i].consumer = NULL; + } + } + + return status; +} + +/* Master clock */ + +static int ux500_ab8500_power_control_inc(void) +{ + int ret; + + mutex_lock(&power_lock); + + ab8500_power_count++; + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count-1, + ab8500_power_count); + + if (ab8500_power_count == 1) { + ret = clk_set_parent(clk_ptr_audioclk, + (master_clock_sel == 0) ? clk_ptr_sysclk : clk_ptr_ulpclk); + if (ret) { + pr_err("%s: ERROR: Setting master-clock to %s failed (ret = %d)!", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK", + ret); + clk_put(clk_ptr_sysclk); + return ret; + } + + pr_debug("%s: Enabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + ret = clk_enable(clk_ptr_audioclk); + if (ret) { + pr_err("%s: ERROR: clk_enable failed (ret = %d)!", __func__, ret); + ab8500_power_count = 0; + return ret; + } + + ab8500_audio_power_control(true); + } + + mutex_unlock(&power_lock); + + return 0; +} + +static void ux500_ab8500_power_control_dec(void) +{ + mutex_lock(&power_lock); + + ab8500_power_count--; + + pr_debug("%s: ab8500_power_count changed from %d to %d", + __func__, + ab8500_power_count+1, + ab8500_power_count); + + if (ab8500_power_count == 0) { + pr_debug("%s: Disabling master-clock (%s).", + __func__, + (master_clock_sel == 0) ? "SYSCLK" : "ULPCLK"); + clk_disable(clk_ptr_audioclk); + + ab8500_audio_power_control(false); + } + + mutex_unlock(&power_lock); +} + +/* ASoC */ + int ux500_ab8500_startup(struct snd_pcm_substream *substream) { int i; @@ -102,6 +264,8 @@ int ux500_ab8500_startup(struct snd_pcm_substream *substream) for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) status += enable_regulator(ab8500_regus[i].supply); + return ux500_ab8500_power_control_inc(); + return status; } @@ -120,6 +284,8 @@ void ux500_ab8500_shutdown(struct snd_pcm_substream *substream) /* Disable regulators */ for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) disable_regulator(ab8500_regus[i].supply); + + ux500_ab8500_power_control_dec(); } int ux500_ab8500_hw_params(struct snd_pcm_substream *substream, @@ -170,7 +336,7 @@ int ux500_ab8500_hw_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { - pr_err("%s: snd_soc_dai_set_fmt failed for codec_dai (ret = %d).\n", + pr_err("%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n", __func__, ret); return ret; @@ -178,7 +344,7 @@ int ux500_ab8500_hw_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { - pr_err("%s: snd_soc_dai_set_fmt for cpu_dai (ret = %d).\n", + pr_err("%s: ERROR: snd_soc_dai_set_fmt for cpu_dai (ret = %d)!\n", __func__, ret); return ret; @@ -233,78 +399,62 @@ int ux500_ab8500_hw_params(struct snd_pcm_substream *substream, return 0; } -static int create_jack(struct snd_soc_codec *codec) -{ - return snd_soc_jack_new(codec, - "AB8500 Hs Status", - SND_JACK_HEADPHONE | - SND_JACK_MICROPHONE | - SND_JACK_HEADSET | - SND_JACK_LINEOUT | - SND_JACK_MECHANICAL | - SND_JACK_VIDEOOUT, - &jack); -} - -void ux500_ab8500_jack_report(int value) -{ - if (jack.jack) - snd_soc_jack_report(&jack, value, 0xFF); -} -EXPORT_SYMBOL_GPL(ux500_ab8500_jack_report); - +struct snd_soc_ops ux500_ab8500_ops[] = { + { + .hw_params = ux500_ab8500_hw_params, + .startup = ux500_ab8500_startup, + .shutdown = ux500_ab8500_shutdown, + } +}; int ux500_ab8500_machine_codec_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_codec *codec = rtd->codec; - int status; + int ret; pr_info("%s Enter.\n", __func__); /* TODO: Add required DAPM routes to control regulators on demand */ - status = create_jack(codec); - if (status < 0) { - pr_err("%s: Failed to create Jack (%d).\n", __func__, status); - return status; + ret = snd_soc_jack_new(codec, + "AB8500 Hs Status", + SND_JACK_HEADPHONE | + SND_JACK_MICROPHONE | + SND_JACK_HEADSET | + SND_JACK_LINEOUT | + SND_JACK_MECHANICAL | + SND_JACK_VIDEOOUT, + &jack); + if (ret < 0) { + pr_err("%s: ERROR: Failed to create Jack (ret = %d)!\n", __func__, ret); + return ret; } - return 0; -} - -static int create_regulators(void) -{ - int i, status = 0; - - pr_debug("%s: Enter.\n", __func__); + /* Add controls */ + snd_ctl_add(codec->card->snd_card, snd_ctl_new1(&mclk_input_control, codec)); - for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) - ab8500_regus[i].consumer = NULL; - - for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) { - ab8500_regus[i].consumer = regulator_get(NULL, - ab8500_regus[i].supply); - if (IS_ERR(ab8500_regus[i].consumer)) { - status = PTR_ERR(ab8500_regus[i].consumer); - pr_err("%s: Failed to get supply '%s' (%d)\n", - __func__, ab8500_regus[i].supply, status); - ab8500_regus[i].consumer = NULL; - goto err_get; - } + /* Setup master clocks */ + ab8500_power_count = 0; + clk_ptr_sysclk = clk_get(codec->dev, "sysclk"); + if (IS_ERR(clk_ptr_sysclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; } - - return 0; - -err_get: - - for (i = 0; i < ARRAY_SIZE(ab8500_regus); ++i) { - if (ab8500_regus[i].consumer) { - regulator_put(ab8500_regus[i].consumer); - ab8500_regus[i].consumer = NULL; - } + clk_ptr_ulpclk = clk_get(codec->dev, "ulpclk"); + if (IS_ERR(clk_ptr_sysclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + return -EFAULT; + } + clk_ptr_audioclk = clk_get(codec->dev, "audioclk"); + if (IS_ERR(clk_ptr_audioclk)) { + pr_err("ERROR: clk_get failed (ret = %d)!", -EFAULT); + clk_put(clk_ptr_sysclk); + return -EFAULT; } - return status; + master_clock_sel = 1; + + return 0; } int ux500_ab8500_soc_machine_drv_init(void) @@ -315,7 +465,7 @@ int ux500_ab8500_soc_machine_drv_init(void) status = create_regulators(); if (status < 0) { - pr_err("%s: Failed to instantiate regulators (%d).\n", + pr_err("%s: ERROR: Failed to instantiate regulators (ret = %d)!\n", __func__, status); return status; } @@ -330,10 +480,12 @@ void ux500_ab8500_soc_machine_drv_cleanup(void) regulator_bulk_free(ARRAY_SIZE(ab8500_regus), ab8500_regus); } -struct snd_soc_ops ux500_ab8500_ops[] = { - { - .hw_params = ux500_ab8500_hw_params, - .startup = ux500_ab8500_startup, - .shutdown = ux500_ab8500_shutdown, - } -}; +/* Extended interface */ + +void ux500_ab8500_jack_report(int value) +{ + if (jack.jack) + snd_soc_jack_report(&jack, value, 0xFF); +} +EXPORT_SYMBOL_GPL(ux500_ab8500_jack_report); + -- cgit v1.2.3