/* * Copyright (C) ST-Ericsson SA 2012 * * Author: Ola Lilja , * Kristoffer Karlsson * 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 #include #include #include #include #include #include #include #include #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_dai_set_fmt(codec_dai, fmt); if (ret < 0) { dev_err(dev, "%s: ERROR: snd_soc_dai_set_fmt failed for codec_dai (ret = %d)!\n", __func__, ret); return ret; } ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { dev_err(dev, "%s: ERROR: snd_soc_dai_set_fmt failed for cpu_dai (ret = %d)!\n", __func__, 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_codec *codec = rtd->codec; 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(codec->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(&codec->dapm, "Earpiece"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Left"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Speaker Right"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Left"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineOut Right"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 1"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Vibra 2"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 1"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "Mic 2"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Left"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "LineIn Right"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 1"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 2"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 3"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 4"); ret |= snd_soc_dapm_disable_pin(&codec->dapm, "DMic 5"); ret |= snd_soc_dapm_disable_pin(&codec->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); }