726 lines
18 KiB
C
726 lines
18 KiB
C
/* Copyright (c) 2011-2012, The Linux Foundation. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/mfd/pmic8058.h>
|
|
#include <linux/mfd/pmic8901.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/delay.h>
|
|
#include <mach/mpp.h>
|
|
#include <sound/core.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/soc-dsp.h>
|
|
#include <sound/pcm.h>
|
|
#include <asm/mach-types.h>
|
|
#include "msm-pcm-routing.h"
|
|
#include "../codecs/wm8903.h"
|
|
|
|
#define MSM_GPIO_CLASS_D0_EN 80
|
|
#define MSM_GPIO_CLASS_D1_EN 81
|
|
|
|
#define MSM_CDC_MIC_I2S_MCLK 108
|
|
|
|
static int msm8660_spk_func;
|
|
static int msm8660_headset_func;
|
|
static int msm8660_headphone_func;
|
|
|
|
static struct clk *mic_bit_clk;
|
|
static struct clk *spkr_osr_clk;
|
|
static struct clk *spkr_bit_clk;
|
|
static struct clk *wm8903_mclk;
|
|
|
|
static int rx_hw_param_status;
|
|
static int tx_hw_param_status;
|
|
/* Platform specific logic */
|
|
|
|
enum {
|
|
GET_ERR,
|
|
SET_ERR,
|
|
ENABLE_ERR,
|
|
NONE
|
|
};
|
|
|
|
enum {
|
|
FUNC_OFF,
|
|
FUNC_ON,
|
|
};
|
|
|
|
static struct wm8903_vdd {
|
|
struct regulator *reg_id;
|
|
const char *name;
|
|
u32 voltage;
|
|
} wm8903_vdds[] = {
|
|
{ NULL, "8058_l16", 1800000 },
|
|
{ NULL, "8058_l0", 1200000 },
|
|
{ NULL, "8058_s3", 1800000 },
|
|
};
|
|
|
|
static void classd_amp_pwr(int enable)
|
|
{
|
|
int rc;
|
|
|
|
pr_debug("%s, enable = %d\n", __func__, enable);
|
|
if (enable) {
|
|
/* currently external PA isn't used for LINEOUTL */
|
|
rc = gpio_request(MSM_GPIO_CLASS_D0_EN, "CLASSD0_EN");
|
|
if (rc) {
|
|
pr_err("%s: spkr PA gpio %d request failed\n",
|
|
__func__, MSM_GPIO_CLASS_D0_EN);
|
|
return;
|
|
}
|
|
gpio_direction_output(MSM_GPIO_CLASS_D0_EN, 1);
|
|
gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 1);
|
|
rc = gpio_request(MSM_GPIO_CLASS_D1_EN, "CLASSD1_EN");
|
|
if (rc) {
|
|
pr_err("%s: spkr PA gpio %d request failed\n",
|
|
__func__, MSM_GPIO_CLASS_D1_EN);
|
|
return;
|
|
}
|
|
gpio_direction_output(MSM_GPIO_CLASS_D1_EN, 1);
|
|
gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 1);
|
|
} else {
|
|
gpio_set_value_cansleep(MSM_GPIO_CLASS_D0_EN, 0);
|
|
gpio_free(MSM_GPIO_CLASS_D0_EN);
|
|
|
|
gpio_set_value_cansleep(MSM_GPIO_CLASS_D1_EN, 0);
|
|
gpio_free(MSM_GPIO_CLASS_D1_EN);
|
|
}
|
|
}
|
|
|
|
static void extern_poweramp_on(void)
|
|
{
|
|
pr_debug("%s: enable stereo spkr amp\n", __func__);
|
|
classd_amp_pwr(1);
|
|
}
|
|
|
|
static void extern_poweramp_off(void)
|
|
{
|
|
pr_debug("%s: disable stereo spkr amp\n", __func__);
|
|
classd_amp_pwr(0);
|
|
}
|
|
|
|
static int msm8660_wm8903_powerup(void)
|
|
{
|
|
int rc = 0, index, stage = NONE;
|
|
struct wm8903_vdd *vdd = NULL;
|
|
|
|
for (index = 0; index < ARRAY_SIZE(wm8903_vdds); index++) {
|
|
vdd = &wm8903_vdds[index];
|
|
vdd->reg_id = regulator_get(NULL, vdd->name);
|
|
if (IS_ERR(vdd->reg_id)) {
|
|
pr_err("%s: Unable to get %s\n", __func__, vdd->name);
|
|
stage = GET_ERR;
|
|
rc = -ENODEV;
|
|
break;
|
|
}
|
|
|
|
rc = regulator_set_voltage(vdd->reg_id,
|
|
vdd->voltage, vdd->voltage);
|
|
if (rc) {
|
|
pr_err("%s: unable to set %s voltage to %dV\n",
|
|
__func__, vdd->name, vdd->voltage);
|
|
stage = SET_ERR;
|
|
break;
|
|
}
|
|
|
|
rc = regulator_enable(vdd->reg_id);
|
|
if (rc) {
|
|
pr_err("%s:failed to enable %s\n", __func__, vdd->name);
|
|
stage = ENABLE_ERR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index != ARRAY_SIZE(wm8903_vdds)) {
|
|
if (stage != GET_ERR) {
|
|
vdd = &wm8903_vdds[index];
|
|
regulator_put(vdd->reg_id);
|
|
vdd->reg_id = NULL;
|
|
}
|
|
|
|
while (index--) {
|
|
vdd = &wm8903_vdds[index];
|
|
regulator_disable(vdd->reg_id);
|
|
regulator_put(vdd->reg_id);
|
|
vdd->reg_id = NULL;
|
|
}
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void msm8660_wm8903_powerdown(void)
|
|
{
|
|
int index = ARRAY_SIZE(wm8903_vdds);
|
|
struct wm8903_vdd *vdd = NULL;
|
|
|
|
while (index--) {
|
|
vdd = &wm8903_vdds[index];
|
|
if (vdd->reg_id) {
|
|
regulator_disable(vdd->reg_id);
|
|
regulator_put(vdd->reg_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int msm8660_wm8903_enable_mclk(int enable)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (enable) {
|
|
ret = gpio_request(MSM_CDC_MIC_I2S_MCLK, "I2S_Clock");
|
|
if (ret != 0) {
|
|
pr_err("%s: failed to request GPIO\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
wm8903_mclk = clk_get_sys(NULL, "i2s_mic_osr_clk");
|
|
if (IS_ERR(wm8903_mclk)) {
|
|
pr_err("Failed to get i2s_mic_osr_clk\n");
|
|
gpio_free(MSM_CDC_MIC_I2S_MCLK);
|
|
return IS_ERR(wm8903_mclk);
|
|
}
|
|
/* Master clock OSR 256 */
|
|
clk_set_rate(wm8903_mclk, 48000 * 256);
|
|
ret = clk_prepare_enable(wm8903_mclk);
|
|
if (ret != 0) {
|
|
pr_err("Unable to enable i2s_mic_osr_clk\n");
|
|
gpio_free(MSM_CDC_MIC_I2S_MCLK);
|
|
clk_put(wm8903_mclk);
|
|
return ret;
|
|
}
|
|
} else {
|
|
if (wm8903_mclk) {
|
|
clk_disable_unprepare(wm8903_mclk);
|
|
clk_put(wm8903_mclk);
|
|
gpio_free(MSM_CDC_MIC_I2S_MCLK);
|
|
wm8903_mclk = NULL;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int msm8660_wm8903_prepare(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = msm8660_wm8903_powerup();
|
|
if (ret) {
|
|
pr_err("Unable to powerup wm8903\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = msm8660_wm8903_enable_mclk(1);
|
|
if (ret) {
|
|
pr_err("Unable to enable mclk to wm8903\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void msm8660_wm8903_unprepare(void)
|
|
{
|
|
msm8660_wm8903_powerdown();
|
|
msm8660_wm8903_enable_mclk(0);
|
|
}
|
|
|
|
static int msm8660_i2s_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 rate = params_rate(params), ret = 0;
|
|
|
|
pr_debug("Enter %s rate = %d\n", __func__, rate);
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
if (rx_hw_param_status)
|
|
return 0;
|
|
/* wm8903 run @ LRC*256 */
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256,
|
|
SND_SOC_CLOCK_IN);
|
|
snd_soc_dai_digital_mute(codec_dai, 0);
|
|
if (ret < 0) {
|
|
pr_err("can't set rx codec clk configuration\n");
|
|
return ret;
|
|
}
|
|
clk_set_rate(wm8903_mclk, rate * 256);
|
|
/* set as slave mode CPU */
|
|
clk_set_rate(spkr_bit_clk, 0);
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
|
|
rx_hw_param_status++;
|
|
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
if (tx_hw_param_status)
|
|
return 0;
|
|
clk_set_rate(wm8903_mclk, rate * 256);
|
|
ret = snd_soc_dai_set_sysclk(codec_dai, 0, rate * 256,
|
|
SND_SOC_CLOCK_IN);
|
|
if (ret < 0) {
|
|
pr_err("can't set tx codec clk configuration\n");
|
|
return ret;
|
|
}
|
|
clk_set_rate(mic_bit_clk, 0);
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
|
|
tx_hw_param_status++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int msm8660_i2s_startup(struct snd_pcm_substream *substream)
|
|
{
|
|
int ret = 0;
|
|
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;
|
|
|
|
pr_debug("Enter %s\n", __func__);
|
|
/* ON Dragonboard, I2S between wm8903 and CPU is shared by
|
|
* CODEC_SPEAKER and CODEC_MIC therefore CPU only can operate
|
|
* as input SLAVE mode.
|
|
*/
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
/* config WM8903 in Mater mode */
|
|
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM |
|
|
SND_SOC_DAIFMT_I2S);
|
|
if (ret != 0) {
|
|
pr_err("codec_dai set_fmt error\n");
|
|
return ret;
|
|
}
|
|
/* config CPU in SLAVE mode */
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
|
|
if (ret != 0) {
|
|
pr_err("cpu_dai set_fmt error\n");
|
|
return ret;
|
|
}
|
|
spkr_osr_clk = clk_get_sys(NULL, "i2s_spkr_osr_clk");
|
|
if (IS_ERR(spkr_osr_clk)) {
|
|
pr_err("Failed to get i2s_spkr_osr_clk\n");
|
|
return PTR_ERR(spkr_osr_clk);
|
|
}
|
|
clk_set_rate(spkr_osr_clk, 48000 * 256);
|
|
ret = clk_prepare_enable(spkr_osr_clk);
|
|
if (ret != 0) {
|
|
pr_err("Unable to enable i2s_spkr_osr_clk\n");
|
|
clk_put(spkr_osr_clk);
|
|
return ret;
|
|
}
|
|
spkr_bit_clk = clk_get_sys(NULL, "i2s_spkr_bit_clk");
|
|
if (IS_ERR(spkr_bit_clk)) {
|
|
pr_err("Failed to get i2s_spkr_bit_clk\n");
|
|
clk_disable_unprepare(spkr_osr_clk);
|
|
clk_put(spkr_osr_clk);
|
|
return PTR_ERR(spkr_bit_clk);
|
|
}
|
|
clk_set_rate(spkr_bit_clk, 0);
|
|
ret = clk_prepare_enable(spkr_bit_clk);
|
|
if (ret != 0) {
|
|
pr_err("Unable to enable i2s_spkr_bit_clk\n");
|
|
clk_disable_unprepare(spkr_osr_clk);
|
|
clk_put(spkr_osr_clk);
|
|
clk_put(spkr_bit_clk);
|
|
return ret;
|
|
}
|
|
} else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
/* config WM8903 in Mater mode */
|
|
ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBM_CFM |
|
|
SND_SOC_DAIFMT_I2S);
|
|
if (ret != 0) {
|
|
pr_err("codec_dai set_fmt error\n");
|
|
return ret;
|
|
}
|
|
/* config CPU in SLAVE mode */
|
|
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM);
|
|
if (ret != 0) {
|
|
pr_err("codec_dai set_fmt error\n");
|
|
return ret;
|
|
}
|
|
|
|
mic_bit_clk = clk_get_sys(NULL, "i2s_mic_bit_clk");
|
|
if (IS_ERR(mic_bit_clk)) {
|
|
pr_err("Failed to get i2s_mic_bit_clk\n");
|
|
return PTR_ERR(mic_bit_clk);
|
|
}
|
|
clk_set_rate(mic_bit_clk, 0);
|
|
ret = clk_prepare_enable(mic_bit_clk);
|
|
if (ret != 0) {
|
|
pr_err("Unable to enable i2s_mic_bit_clk\n");
|
|
clk_put(mic_bit_clk);
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void msm8660_i2s_shutdown(struct snd_pcm_substream *substream)
|
|
{
|
|
pr_debug("Enter %s\n", __func__);
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
|
|
substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
tx_hw_param_status = 0;
|
|
rx_hw_param_status = 0;
|
|
if (spkr_bit_clk) {
|
|
clk_disable_unprepare(spkr_bit_clk);
|
|
clk_put(spkr_bit_clk);
|
|
spkr_bit_clk = NULL;
|
|
}
|
|
if (spkr_osr_clk) {
|
|
clk_disable_unprepare(spkr_osr_clk);
|
|
clk_put(spkr_osr_clk);
|
|
spkr_osr_clk = NULL;
|
|
}
|
|
if (mic_bit_clk) {
|
|
clk_disable_unprepare(mic_bit_clk);
|
|
clk_put(mic_bit_clk);
|
|
mic_bit_clk = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void msm8660_ext_control(struct snd_soc_codec *codec)
|
|
{
|
|
/* set the enpoints to their new connetion states */
|
|
if (msm8660_spk_func == FUNC_ON)
|
|
snd_soc_dapm_enable_pin(&codec->dapm, "Ext Spk");
|
|
else
|
|
snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk");
|
|
|
|
/* set the enpoints to their new connetion states */
|
|
if (msm8660_headset_func == FUNC_ON)
|
|
snd_soc_dapm_enable_pin(&codec->dapm, "Headset Jack");
|
|
else
|
|
snd_soc_dapm_disable_pin(&codec->dapm, "Headset Jack");
|
|
|
|
/* set the enpoints to their new connetion states */
|
|
if (msm8660_headphone_func == FUNC_ON)
|
|
snd_soc_dapm_enable_pin(&codec->dapm, "Headphone Jack");
|
|
else
|
|
snd_soc_dapm_disable_pin(&codec->dapm, "Headphone Jack");
|
|
|
|
/* signal a DAPM event */
|
|
snd_soc_dapm_sync(&codec->dapm);
|
|
}
|
|
|
|
static int msm8660_get_spk(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
ucontrol->value.integer.value[0] = msm8660_spk_func;
|
|
return 0;
|
|
}
|
|
|
|
static int msm8660_set_spk(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
pr_debug("%s()\n", __func__);
|
|
if (msm8660_spk_func == ucontrol->value.integer.value[0])
|
|
return 0;
|
|
|
|
msm8660_spk_func = ucontrol->value.integer.value[0];
|
|
msm8660_ext_control(codec);
|
|
return 1;
|
|
}
|
|
|
|
static int msm8660_get_hs(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
ucontrol->value.integer.value[0] = msm8660_headset_func;
|
|
return 0;
|
|
}
|
|
|
|
static int msm8660_set_hs(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
pr_debug("%s()\n", __func__);
|
|
if (msm8660_headset_func == ucontrol->value.integer.value[0])
|
|
return 0;
|
|
|
|
msm8660_headset_func = ucontrol->value.integer.value[0];
|
|
msm8660_ext_control(codec);
|
|
return 1;
|
|
}
|
|
|
|
static int msm8660_get_hph(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
ucontrol->value.integer.value[0] = msm8660_headphone_func;
|
|
return 0;
|
|
}
|
|
|
|
static int msm8660_set_hph(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
|
|
|
|
pr_debug("%s()\n", __func__);
|
|
if (msm8660_headphone_func == ucontrol->value.integer.value[0])
|
|
return 0;
|
|
|
|
msm8660_headphone_func = ucontrol->value.integer.value[0];
|
|
msm8660_ext_control(codec);
|
|
return 1;
|
|
}
|
|
|
|
static int msm8660_spkramp_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *k, int event)
|
|
{
|
|
if (SND_SOC_DAPM_EVENT_ON(event))
|
|
extern_poweramp_on();
|
|
else
|
|
extern_poweramp_off();
|
|
return 0;
|
|
}
|
|
|
|
static struct snd_soc_ops machine_ops = {
|
|
.startup = msm8660_i2s_startup,
|
|
.shutdown = msm8660_i2s_shutdown,
|
|
.hw_params = msm8660_i2s_hw_params,
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget msm8660_dapm_widgets[] = {
|
|
SND_SOC_DAPM_SPK("Ext Spk", msm8660_spkramp_event),
|
|
SND_SOC_DAPM_MIC("Headset Jack", NULL),
|
|
SND_SOC_DAPM_MIC("Headphone Jack", NULL),
|
|
/* to fix a bug in wm8903.c, where audio doesn't function
|
|
* after suspend/resume
|
|
*/
|
|
SND_SOC_DAPM_SUPPLY("CLK_SYS_ENA", WM8903_CLOCK_RATES_2, 2, 0, NULL, 0),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route audio_map[] = {
|
|
/* Match with wm8903 codec line out pin */
|
|
{"Ext Spk", NULL, "LINEOUTL"},
|
|
{"Ext Spk", NULL, "LINEOUTR"},
|
|
/* Headset connects to IN3L with Bias */
|
|
{"IN3L", NULL, "Mic Bias"},
|
|
{"Mic Bias", NULL, "Headset Jack"},
|
|
/* Headphone connects to IN3R with Bias */
|
|
{"IN3R", NULL, "Mic Bias"},
|
|
{"Mic Bias", NULL, "Headphone Jack"},
|
|
{"ADCL", NULL, "CLK_SYS_ENA"},
|
|
{"ADCR", NULL, "CLK_SYS_ENA"},
|
|
{"DACL", NULL, "CLK_SYS_ENA"},
|
|
{"DACR", NULL, "CLK_SYS_ENA"},
|
|
};
|
|
|
|
static const char *cmn_status[] = {"Off", "On"};
|
|
static const struct soc_enum msm8660_enum[] = {
|
|
SOC_ENUM_SINGLE_EXT(2, cmn_status),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new wm8903_msm8660_controls[] = {
|
|
SOC_ENUM_EXT("Speaker Function", msm8660_enum[0], msm8660_get_spk,
|
|
msm8660_set_spk),
|
|
SOC_ENUM_EXT("Headset Function", msm8660_enum[0], msm8660_get_hs,
|
|
msm8660_set_hs),
|
|
SOC_ENUM_EXT("Headphone Function", msm8660_enum[0], msm8660_get_hph,
|
|
msm8660_set_hph),
|
|
};
|
|
|
|
static int msm8660_audrx_init(struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_soc_codec *codec = rtd->codec;
|
|
int err;
|
|
|
|
snd_soc_dapm_disable_pin(&codec->dapm, "Ext Spk");
|
|
snd_soc_dapm_enable_pin(&codec->dapm, "CLK_SYS_ENA");
|
|
|
|
err = snd_soc_add_controls(codec, wm8903_msm8660_controls,
|
|
ARRAY_SIZE(wm8903_msm8660_controls));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
snd_soc_dapm_new_controls(&codec->dapm, msm8660_dapm_widgets,
|
|
ARRAY_SIZE(msm8660_dapm_widgets));
|
|
|
|
snd_soc_dapm_add_routes(&codec->dapm, audio_map, ARRAY_SIZE(audio_map));
|
|
|
|
snd_soc_dapm_sync(&codec->dapm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pri_i2s_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct snd_interval *rate = hw_param_interval(params,
|
|
SNDRV_PCM_HW_PARAM_RATE);
|
|
|
|
rate->min = rate->max = 48000;
|
|
return 0;
|
|
}
|
|
/*
|
|
* LPA Needs only RX BE DAI links.
|
|
* Hence define seperate BE list for lpa
|
|
*/
|
|
static const char *lpa_mm_be[] = {
|
|
LPASS_BE_PRI_I2S_RX,
|
|
};
|
|
|
|
static struct snd_soc_dsp_link lpa_fe_media = {
|
|
.supported_be = lpa_mm_be,
|
|
.num_be = ARRAY_SIZE(lpa_mm_be),
|
|
.fe_playback_channels = 2,
|
|
.fe_capture_channels = 1,
|
|
.trigger = {
|
|
SND_SOC_DSP_TRIGGER_POST,
|
|
SND_SOC_DSP_TRIGGER_POST
|
|
},
|
|
};
|
|
|
|
static const char *mm1_be[] = {
|
|
LPASS_BE_PRI_I2S_RX,
|
|
LPASS_BE_PRI_I2S_TX,
|
|
LPASS_BE_HDMI,
|
|
};
|
|
|
|
static struct snd_soc_dsp_link fe_media = {
|
|
.supported_be = mm1_be,
|
|
.num_be = ARRAY_SIZE(mm1_be),
|
|
.fe_playback_channels = 2,
|
|
.fe_capture_channels = 1,
|
|
.trigger = {
|
|
SND_SOC_DSP_TRIGGER_POST, SND_SOC_DSP_TRIGGER_POST},
|
|
};
|
|
|
|
/* Digital audio interface glue - connects codec <---> CPU */
|
|
static struct snd_soc_dai_link msm8660_dai[] = {
|
|
/* FrontEnd DAI Links */
|
|
{
|
|
.name = "MSM8660 Media",
|
|
.stream_name = "MultiMedia",
|
|
.cpu_dai_name = "MultiMedia1",
|
|
.platform_name = "msm-pcm-dsp",
|
|
.dynamic = 1,
|
|
.dsp_link = &fe_media,
|
|
.be_id = MSM_FRONTEND_DAI_MULTIMEDIA1
|
|
},
|
|
{
|
|
.name = "MSM8660 Media2",
|
|
.stream_name = "MultiMedia2",
|
|
.cpu_dai_name = "MultiMedia2",
|
|
.platform_name = "msm-pcm-dsp",
|
|
.dynamic = 1,
|
|
.dsp_link = &fe_media,
|
|
.be_id = MSM_FRONTEND_DAI_MULTIMEDIA2,
|
|
},
|
|
/* Backend DAI Links */
|
|
{
|
|
.name = LPASS_BE_PRI_I2S_RX,
|
|
.stream_name = "Primary I2S Playback",
|
|
.cpu_dai_name = "msm-dai-q6.0",
|
|
.platform_name = "msm-pcm-routing",
|
|
.codec_name = "wm8903-codec.3-001a",
|
|
.codec_dai_name = "wm8903-hifi",
|
|
.no_pcm = 1,
|
|
.be_hw_params_fixup = pri_i2s_be_hw_params_fixup,
|
|
.ops = &machine_ops,
|
|
.init = &msm8660_audrx_init,
|
|
.be_id = MSM_BACKEND_DAI_PRI_I2S_RX
|
|
},
|
|
{
|
|
.name = LPASS_BE_PRI_I2S_TX,
|
|
.stream_name = "Primary I2S Capture",
|
|
.cpu_dai_name = "msm-dai-q6.1",
|
|
.platform_name = "msm-pcm-routing",
|
|
.codec_name = "wm8903-codec.3-001a",
|
|
.codec_dai_name = "wm8903-hifi",
|
|
.no_pcm = 1,
|
|
.ops = &machine_ops,
|
|
.be_hw_params_fixup = pri_i2s_be_hw_params_fixup,
|
|
.be_id = MSM_BACKEND_DAI_PRI_I2S_TX
|
|
},
|
|
/* LPA frontend DAI link*/
|
|
{
|
|
.name = "MSM8660 LPA",
|
|
.stream_name = "LPA",
|
|
.cpu_dai_name = "MultiMedia3",
|
|
.platform_name = "msm-pcm-lpa",
|
|
.dynamic = 1,
|
|
.dsp_link = &lpa_fe_media,
|
|
.be_id = MSM_FRONTEND_DAI_MULTIMEDIA3,
|
|
},
|
|
/* HDMI backend DAI link */
|
|
{
|
|
.name = LPASS_BE_HDMI,
|
|
.stream_name = "HDMI Playback",
|
|
.cpu_dai_name = "msm-dai-q6.8",
|
|
.platform_name = "msm-pcm-routing",
|
|
.codec_name = "msm-stub-codec.1",
|
|
.codec_dai_name = "msm-stub-rx",
|
|
.no_codec = 1,
|
|
.no_pcm = 1,
|
|
.be_hw_params_fixup = pri_i2s_be_hw_params_fixup,
|
|
.be_id = MSM_BACKEND_DAI_HDMI_RX
|
|
},
|
|
};
|
|
|
|
struct snd_soc_card snd_soc_card_msm8660 = {
|
|
.name = "msm8660-snd-card",
|
|
.dai_link = msm8660_dai,
|
|
.num_links = ARRAY_SIZE(msm8660_dai),
|
|
};
|
|
|
|
static struct platform_device *msm_snd_device;
|
|
|
|
static int __init msm_audio_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (machine_is_msm8x60_dragon()) {
|
|
/* wm8903 audio codec needs to power up and mclk existing
|
|
before it's probed */
|
|
ret = msm8660_wm8903_prepare();
|
|
if (ret) {
|
|
pr_err("failed to prepare wm8903 audio codec\n");
|
|
return ret;
|
|
}
|
|
|
|
msm_snd_device = platform_device_alloc("soc-audio", 0);
|
|
if (!msm_snd_device) {
|
|
pr_err("Platform device allocation failed\n");
|
|
msm8660_wm8903_unprepare();
|
|
return -ENOMEM;
|
|
}
|
|
|
|
platform_set_drvdata(msm_snd_device, &snd_soc_card_msm8660);
|
|
ret = platform_device_add(msm_snd_device);
|
|
if (ret) {
|
|
platform_device_put(msm_snd_device);
|
|
msm8660_wm8903_unprepare();
|
|
return ret;
|
|
}
|
|
}
|
|
return ret;
|
|
|
|
}
|
|
module_init(msm_audio_init);
|
|
|
|
static void __exit msm_audio_exit(void)
|
|
{
|
|
msm8660_wm8903_unprepare();
|
|
platform_device_unregister(msm_snd_device);
|
|
}
|
|
module_exit(msm_audio_exit);
|
|
|
|
MODULE_DESCRIPTION("ALSA SoC MSM8660");
|
|
MODULE_LICENSE("GPL v2");
|