diff options
Diffstat (limited to 'sound/soc/ux500/mop500_ab8500.c')
-rw-r--r-- | sound/soc/ux500/mop500_ab8500.c | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/sound/soc/ux500/mop500_ab8500.c b/sound/soc/ux500/mop500_ab8500.c new file mode 100644 index 000000000..b81a7a4c9 --- /dev/null +++ b/sound/soc/ux500/mop500_ab8500.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Author: Ola Lilja <ola.o.lilja@stericsson.com>, + * Kristoffer Karlsson <kristoffer.karlsson@stericsson.com> + * for ST-Ericsson. + * + * License terms: + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/mutex.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> + +#include "ux500_pcm.h" +#include "ux500_msp_dai.h" +#include "mop500_ab8500.h" +#include "../codecs/ab8500-codec.h" + +#define TX_SLOT_MONO 0x0008 +#define TX_SLOT_STEREO 0x000a +#define RX_SLOT_MONO 0x0001 +#define RX_SLOT_STEREO 0x0003 +#define TX_SLOT_8CH 0x00FF +#define RX_SLOT_8CH 0x00FF + +#define DEF_TX_SLOTS TX_SLOT_STEREO +#define DEF_RX_SLOTS RX_SLOT_MONO + +#define DRIVERMODE_NORMAL 0 +#define DRIVERMODE_CODEC_ONLY 1 + +/* Slot configuration */ +static unsigned int tx_slots = DEF_TX_SLOTS; +static unsigned int rx_slots = DEF_RX_SLOTS; + +/* Configuration consistency parameters */ +static DEFINE_MUTEX(mop500_ab8500_params_lock); +static unsigned long mop500_ab8500_usage; +static int mop500_ab8500_rate; +static int mop500_ab8500_channels; + +/* Clocks */ +static const char * const enum_mclk[] = { + "SYSCLK", + "ULPCLK" +}; +enum mclk { + MCLK_SYSCLK, + MCLK_ULPCLK, +}; + +static SOC_ENUM_SINGLE_EXT_DECL(soc_enum_mclk, enum_mclk); + +/* Private data for machine-part MOP500<->AB8500 */ +struct mop500_ab8500_drvdata { + /* Clocks */ + enum mclk mclk_sel; + struct clk *clk_ptr_intclk; + struct clk *clk_ptr_sysclk; + struct clk *clk_ptr_ulpclk; +}; + +static inline const char *get_mclk_str(enum mclk mclk_sel) +{ + switch (mclk_sel) { + case MCLK_SYSCLK: + return "SYSCLK"; + case MCLK_ULPCLK: + return "ULPCLK"; + default: + return "Unknown"; + } +} + +static int mop500_ab8500_set_mclk(struct device *dev, + struct mop500_ab8500_drvdata *drvdata) +{ + int status; + struct clk *clk_ptr; + + if (IS_ERR(drvdata->clk_ptr_intclk)) { + dev_err(dev, + "%s: ERROR: intclk not initialized!\n", __func__); + return -EIO; + } + + switch (drvdata->mclk_sel) { + case MCLK_SYSCLK: + clk_ptr = drvdata->clk_ptr_sysclk; + break; + case MCLK_ULPCLK: + clk_ptr = drvdata->clk_ptr_ulpclk; + break; + default: + return -EINVAL; + } + + if (IS_ERR(clk_ptr)) { + dev_err(dev, "%s: ERROR: %s not initialized!\n", __func__, + get_mclk_str(drvdata->mclk_sel)); + return -EIO; + } + + status = clk_set_parent(drvdata->clk_ptr_intclk, clk_ptr); + if (status) + dev_err(dev, + "%s: ERROR: Setting intclk parent to %s failed (ret = %d)!", + __func__, get_mclk_str(drvdata->mclk_sel), status); + else + dev_dbg(dev, + "%s: intclk parent changed to %s.\n", + __func__, get_mclk_str(drvdata->mclk_sel)); + + return status; +} + +/* + * Control-events + */ + +static int mclk_input_control_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mop500_ab8500_drvdata *drvdata = + snd_soc_card_get_drvdata(card); + + ucontrol->value.enumerated.item[0] = drvdata->mclk_sel; + + return 0; +} + +static int mclk_input_control_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); + struct mop500_ab8500_drvdata *drvdata = + snd_soc_card_get_drvdata(card); + unsigned int val = ucontrol->value.enumerated.item[0]; + + if (val > (unsigned int)MCLK_ULPCLK) + return -EINVAL; + if (drvdata->mclk_sel == val) + return 0; + + drvdata->mclk_sel = val; + + return 1; +} + +/* + * Controls + */ + +static struct snd_kcontrol_new mop500_ab8500_ctrls[] = { + SOC_ENUM_EXT("Master Clock Select", + soc_enum_mclk, + mclk_input_control_get, mclk_input_control_put), + SOC_DAPM_PIN_SWITCH("Headset Left"), + SOC_DAPM_PIN_SWITCH("Headset Right"), + SOC_DAPM_PIN_SWITCH("Earpiece"), + SOC_DAPM_PIN_SWITCH("Speaker Left"), + SOC_DAPM_PIN_SWITCH("Speaker Right"), + SOC_DAPM_PIN_SWITCH("LineOut Left"), + SOC_DAPM_PIN_SWITCH("LineOut Right"), + SOC_DAPM_PIN_SWITCH("Vibra 1"), + SOC_DAPM_PIN_SWITCH("Vibra 2"), + SOC_DAPM_PIN_SWITCH("Mic 1"), + SOC_DAPM_PIN_SWITCH("Mic 2"), + SOC_DAPM_PIN_SWITCH("LineIn Left"), + SOC_DAPM_PIN_SWITCH("LineIn Right"), + SOC_DAPM_PIN_SWITCH("DMic 1"), + SOC_DAPM_PIN_SWITCH("DMic 2"), + SOC_DAPM_PIN_SWITCH("DMic 3"), + SOC_DAPM_PIN_SWITCH("DMic 4"), + SOC_DAPM_PIN_SWITCH("DMic 5"), + SOC_DAPM_PIN_SWITCH("DMic 6"), +}; + +/* ASoC */ + +static int mop500_ab8500_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* Set audio-clock source */ + return mop500_ab8500_set_mclk(rtd->card->dev, + snd_soc_card_get_drvdata(rtd->card)); +} + +static void mop500_ab8500_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct device *dev = rtd->card->dev; + + dev_dbg(dev, "%s: Enter\n", __func__); + + /* Reset slots configuration to default(s) */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tx_slots = DEF_TX_SLOTS; + else + rx_slots = DEF_RX_SLOTS; +} + +static int mop500_ab8500_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; + struct device *dev = rtd->card->dev; + unsigned int fmt; + int channels, ret = 0, driver_mode, slots; + unsigned int sw_codec, sw_cpu; + bool is_playback; + + dev_dbg(dev, "%s: Enter\n", __func__); + + dev_dbg(dev, "%s: substream->pcm->name = %s\n" + "substream->pcm->id = %s.\n" + "substream->name = %s.\n" + "substream->number = %d.\n", + __func__, + substream->pcm->name, + substream->pcm->id, + substream->name, + substream->number); + + /* Ensure configuration consistency between DAIs */ + mutex_lock(&mop500_ab8500_params_lock); + if (mop500_ab8500_usage) { + if (mop500_ab8500_rate != params_rate(params) || + mop500_ab8500_channels != params_channels(params)) { + mutex_unlock(&mop500_ab8500_params_lock); + return -EBUSY; + } + } else { + mop500_ab8500_rate = params_rate(params); + mop500_ab8500_channels = params_channels(params); + } + __set_bit(cpu_dai->id, &mop500_ab8500_usage); + mutex_unlock(&mop500_ab8500_params_lock); + + channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + sw_cpu = 32; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + sw_cpu = 16; + break; + + default: + return -EINVAL; + } + + /* Setup codec depending on driver-mode */ + if (channels == 8) + driver_mode = DRIVERMODE_CODEC_ONLY; + else + driver_mode = DRIVERMODE_NORMAL; + dev_dbg(dev, "%s: Driver-mode: %s.\n", __func__, + (driver_mode == DRIVERMODE_NORMAL) ? "NORMAL" : "CODEC_ONLY"); + + /* Setup format */ + + if (driver_mode == DRIVERMODE_NORMAL) { + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CONT; + } else { + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_GATED; + } + + ret = snd_soc_runtime_set_dai_fmt(rtd, fmt); + if (ret) + return ret; + + /* Setup TDM-slots */ + + is_playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + switch (channels) { + case 1: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_MONO : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_MONO; + break; + case 2: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_STEREO : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_STEREO; + break; + case 8: + slots = 16; + tx_slots = (is_playback) ? TX_SLOT_8CH : 0; + rx_slots = (is_playback) ? 0 : RX_SLOT_8CH; + break; + default: + return -EINVAL; + } + + if (driver_mode == DRIVERMODE_NORMAL) + sw_codec = sw_cpu; + else + sw_codec = 20; + + dev_dbg(dev, "%s: CPU-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, + tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, tx_slots, rx_slots, slots, + sw_cpu); + if (ret) + return ret; + + dev_dbg(dev, "%s: CODEC-DAI TDM: TX=0x%04X RX=0x%04x\n", __func__, + tx_slots, rx_slots); + ret = snd_soc_dai_set_tdm_slot(codec_dai, tx_slots, rx_slots, slots, + sw_codec); + if (ret) + return ret; + + return 0; +} + +static int mop500_ab8500_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + + mutex_lock(&mop500_ab8500_params_lock); + __clear_bit(cpu_dai->id, &mop500_ab8500_usage); + mutex_unlock(&mop500_ab8500_params_lock); + + return 0; +} + +struct snd_soc_ops mop500_ab8500_ops[] = { + { + .hw_params = mop500_ab8500_hw_params, + .hw_free = mop500_ab8500_hw_free, + .startup = mop500_ab8500_startup, + .shutdown = mop500_ab8500_shutdown, + } +}; + +int mop500_ab8500_machine_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dapm_context *dapm = &rtd->card->dapm; + struct device *dev = rtd->card->dev; + struct mop500_ab8500_drvdata *drvdata; + int ret; + + dev_dbg(dev, "%s Enter.\n", __func__); + + /* Create driver private-data struct */ + drvdata = devm_kzalloc(dev, sizeof(struct mop500_ab8500_drvdata), + GFP_KERNEL); + snd_soc_card_set_drvdata(rtd->card, drvdata); + + /* Setup clocks */ + + drvdata->clk_ptr_sysclk = clk_get(dev, "sysclk"); + if (IS_ERR(drvdata->clk_ptr_sysclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'sysclk'!\n", + __func__); + drvdata->clk_ptr_ulpclk = clk_get(dev, "ulpclk"); + if (IS_ERR(drvdata->clk_ptr_ulpclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'ulpclk'!\n", + __func__); + drvdata->clk_ptr_intclk = clk_get(dev, "intclk"); + if (IS_ERR(drvdata->clk_ptr_intclk)) + dev_warn(dev, "%s: WARNING: clk_get failed for 'intclk'!\n", + __func__); + + /* Set intclk default parent to ulpclk */ + drvdata->mclk_sel = MCLK_ULPCLK; + ret = mop500_ab8500_set_mclk(dev, drvdata); + if (ret < 0) + dev_warn(dev, "%s: WARNING: mop500_ab8500_set_mclk!\n", + __func__); + + drvdata->mclk_sel = MCLK_ULPCLK; + + /* Add controls */ + ret = snd_soc_add_card_controls(rtd->card, mop500_ab8500_ctrls, + ARRAY_SIZE(mop500_ab8500_ctrls)); + if (ret < 0) { + pr_err("%s: Failed to add machine-controls (%d)!\n", + __func__, ret); + return ret; + } + + ret = snd_soc_dapm_disable_pin(dapm, "Earpiece"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "Speaker Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineOut Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Vibra 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "Mic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Left"); + ret |= snd_soc_dapm_disable_pin(dapm, "LineIn Right"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 1"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 2"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 3"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 4"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 5"); + ret |= snd_soc_dapm_disable_pin(dapm, "DMic 6"); + + return ret; +} + +void mop500_ab8500_remove(struct snd_soc_card *card) +{ + struct mop500_ab8500_drvdata *drvdata = snd_soc_card_get_drvdata(card); + + if (drvdata->clk_ptr_sysclk != NULL) + clk_put(drvdata->clk_ptr_sysclk); + if (drvdata->clk_ptr_ulpclk != NULL) + clk_put(drvdata->clk_ptr_ulpclk); + if (drvdata->clk_ptr_intclk != NULL) + clk_put(drvdata->clk_ptr_intclk); + + snd_soc_card_set_drvdata(card, drvdata); +} |