summaryrefslogtreecommitdiff
path: root/sound/soc/sprd/sprd-asoc-card.c
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
committerYuval Adam <_@yuv.al>2017-08-30 08:25:59 +0000
commit8e4bff4cab0ddac6060645b0715210484d02ff40 (patch)
tree3a5c3024371a04692a3a6d9974d001cdff8ebf84 /sound/soc/sprd/sprd-asoc-card.c
Initial file dump from open source releaseHEADmaster
Diffstat (limited to 'sound/soc/sprd/sprd-asoc-card.c')
-rw-r--r--sound/soc/sprd/sprd-asoc-card.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/sound/soc/sprd/sprd-asoc-card.c b/sound/soc/sprd/sprd-asoc-card.c
new file mode 100644
index 00000000..ce60a47f
--- /dev/null
+++ b/sound/soc/sprd/sprd-asoc-card.c
@@ -0,0 +1,566 @@
+/*
+ * sound/soc/sprd/sprd-asoc-card.c
+ *
+ * Copyright (C) 2013 SpreadTrum Ltd.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 "sprd-asoc-debug.h"
+#define pr_fmt(fmt) pr_sprd_fmt("BOARD") fmt
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include <sound/sprd-audio-hook.h>
+
+#include <soc/sprd/sprd-audio.h>
+#include "sprd-asoc-common.h"
+#include "dai/vbc/dfm.h"
+
+#define FUN_REG(f) ((unsigned short)(-(f+1)))
+
+#define SWITCH_FUN_ON 1
+#define SWITCH_FUN_OFF 0
+
+enum {
+ BOARD_FUNC_SPKL = 0,
+ BOARD_FUNC_SPKR,
+ BOARD_FUNC_EAR,
+ BOARD_FUNC_HP,
+ BOARD_FUNC_MUTE_MAX,
+ BOARD_FUNC_LINE = BOARD_FUNC_MUTE_MAX,
+ BOARD_FUNC_MIC,
+ BOARD_FUNC_AUXMIC,
+ BOARD_FUNC_HP_MIC,
+ BOARD_FUNC_DMIC,
+ BOARD_FUNC_DMIC1,
+ BOARD_FUNC_DFM,
+ BOARD_FUNC_MAX
+};
+
+struct board_mute {
+ int need_mute;
+ int is_on;
+ int (*mute_func) (struct snd_soc_card *, int);
+};
+
+static struct board_priv {
+ int func[BOARD_FUNC_MAX];
+ struct board_mute m[BOARD_FUNC_MUTE_MAX];
+ int pa_type[BOARD_FUNC_MUTE_MAX];
+} board;
+
+#define BOARD_EXT_SPK "Ext Spk"
+#define BOARD_EXT_SPK2 "Ext Spk2"
+#define BOARD_EXT_EAR "Ext Ear"
+#define BOARD_HEADPHONE_JACK "HeadPhone Jack"
+#define BOARD_LINE_JACK "Line Jack"
+#define BOARD_MIC_JACK "Mic Jack"
+#define BOARD_AUX_MIC_JACK "Aux Mic Jack"
+#define BOARD_HP_MIC_JACK "HP Mic Jack"
+#define BOARD_DMIC_JACK "DMic Jack"
+#define BOARD_DMIC1_JACK "DMic1 Jack"
+#define BOARD_DIG_FM_JACK "Dig FM Jack"
+#define BOARD_INTER_HP_PA "inter HP PA"
+#define BOARD_INTER_SPK_PA "inter Spk PA"
+#define BOARD_INTER_SPK2_PA "inter Spk2 PA"
+
+static const char *func_name[BOARD_FUNC_MAX] = {
+ BOARD_EXT_SPK,
+ BOARD_EXT_SPK2,
+ BOARD_EXT_EAR,
+ BOARD_HEADPHONE_JACK,
+ BOARD_LINE_JACK,
+ BOARD_MIC_JACK,
+ BOARD_AUX_MIC_JACK,
+ BOARD_HP_MIC_JACK,
+ BOARD_DMIC_JACK,
+ BOARD_DMIC1_JACK,
+ BOARD_DIG_FM_JACK,
+};
+
+static void board_ext_control(struct snd_soc_dapm_context *dapm, int s, int e)
+{
+ int i;
+ BUG_ON(e > BOARD_FUNC_MAX);
+ for (i = s; i < e; i++) {
+ if (board.func[i] == SWITCH_FUN_ON)
+ snd_soc_dapm_enable_pin(dapm, func_name[i]);
+ else
+ snd_soc_dapm_disable_pin(dapm, func_name[i]);
+ }
+
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(dapm);
+}
+
+static inline void local_cpu_pa_control(struct snd_soc_card *card,
+ const char *id, bool enable, bool sync)
+{
+ if (enable) {
+ snd_soc_dapm_enable_pin(&card->dapm, id);
+ } else {
+ snd_soc_dapm_disable_pin(&card->dapm, id);
+ }
+
+ if (sync) {
+ /* signal a DAPM event */
+ snd_soc_dapm_sync(&card->dapm);
+ }
+}
+
+static void board_try_inter_pa_control(struct snd_soc_card *card, int id,
+ bool sync)
+{
+ char *pa_name;
+
+ if (id >= BOARD_FUNC_MUTE_MAX)
+ return;
+
+ switch (id) {
+ case BOARD_FUNC_SPKL:
+ pa_name = BOARD_INTER_SPK_PA;
+ break;
+ case BOARD_FUNC_SPKR:
+ pa_name = BOARD_INTER_SPK2_PA;
+ break;
+ case BOARD_FUNC_HP:
+ pa_name = BOARD_INTER_HP_PA;
+ break;
+ default:
+ return;
+ }
+
+ if (board.pa_type[id] & HOOK_BPY) {
+ int enable = 1;
+ if (board.func[id] == SWITCH_FUN_ON) {
+ if (board.m[id].need_mute) {
+ enable = 0;
+ }
+ } else {
+ enable = 0;
+ }
+ local_cpu_pa_control(card, pa_name, enable, sync);
+ }
+}
+
+static int audio_speaker_enable_inter(struct snd_soc_card *card, int enable)
+{
+ int ret;
+ if (enable && board.m[BOARD_FUNC_SPKL].need_mute) {
+ enable = 0;
+ }
+ ret = sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER, enable);
+ if (ret < 0) {
+ pr_err("ERR:Call external speaker control failed %d!\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+static void audio_speaker_enable(struct snd_soc_card *card, int enable)
+{
+ board.m[BOARD_FUNC_SPKL].is_on = enable;
+ board.m[BOARD_FUNC_SPKL].mute_func(card, enable);
+}
+
+static int audio_speaker2_enable_inter(struct snd_soc_card *card, int enable)
+{
+ int ret;
+ if (enable && board.m[BOARD_FUNC_SPKR].need_mute) {
+ enable = 0;
+ }
+ ret = sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER2, enable);
+ if (ret < 0) {
+ pr_err("ERR:Call external speaker2 control failed %d!\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+static void audio_speaker2_enable(struct snd_soc_card *card, int enable)
+{
+ board.m[BOARD_FUNC_SPKR].is_on = enable;
+ board.m[BOARD_FUNC_SPKR].mute_func(card, enable);
+}
+
+static int audio_headphone_enable_inter(struct snd_soc_card *card, int enable)
+{
+ int ret;
+ if (enable && board.m[BOARD_FUNC_HP].need_mute) {
+ enable = 0;
+ }
+ ret = sprd_ext_headphone_ctrl(SPRD_AUDIO_ID_HEADPHONE, enable);
+ if (ret < 0) {
+ pr_err("ERR:Call external headphone control failed %d!\n", ret);
+ return ret;
+ }
+ return ret;
+}
+
+static void audio_headphone_enable(struct snd_soc_card *card, int enable)
+{
+ board.m[BOARD_FUNC_HP].is_on = enable;
+ board.m[BOARD_FUNC_HP].mute_func(card, enable);
+}
+
+static int audio_earpiece_enable_inter(struct snd_soc_card *card, int enable)
+{
+ int ret;
+ if (enable && board.m[BOARD_FUNC_EAR].need_mute) {
+ enable = 0;
+ }
+ ret = sprd_ext_earpiece_ctrl(SPRD_AUDIO_ID_EARPIECE, enable);
+ if (ret < 0) {
+ pr_err("ERR:Call external earpiece control failed %d!\n", ret);
+ }
+ return ret;
+}
+
+static void audio_earpiece_enable(struct snd_soc_card *card, int enable)
+{
+ board.m[BOARD_FUNC_EAR].is_on = enable;
+ board.m[BOARD_FUNC_EAR].mute_func(card, enable);
+}
+
+static int board_headphone_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Headphone Switch %s\n", STR_ON_OFF(on));
+ audio_headphone_enable(w->dapm->card, on);
+ return 0;
+}
+
+static int board_earpiece_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Earpiece Switch %s\n", STR_ON_OFF(on));
+ audio_earpiece_enable(w->dapm->card, on);
+ return 0;
+}
+
+static int board_speaker_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Speaker Switch %s\n", STR_ON_OFF(on));
+ audio_speaker_enable(w->dapm->card, on);
+ return 0;
+}
+
+static int board_speaker2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Speaker2 Switch %s\n", STR_ON_OFF(on));
+ audio_speaker2_enable(w->dapm->card, on);
+ return 0;
+}
+
+static int board_main_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Main MIC Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_MAIN_MIC, on);
+ return 0;
+}
+
+static int board_sub_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Sub MIC Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_SUB_MIC, on);
+ return 0;
+}
+
+static int board_head_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Head MIC Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_HEAD_MIC, on);
+ return 0;
+}
+
+static int board_dig0_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Digtial0 MIC Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_DIG0_MIC, on);
+ return 0;
+}
+
+static int board_dig1_mic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Digtial1 MIC Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_DIG1_MIC, on);
+ return 0;
+}
+
+static int board_line_in_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("LINE IN Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_mic_ctrl(SPRD_AUDIO_ID_LINE_IN, on);
+ return 0;
+}
+
+static int board_dig_fm_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *k, int event)
+{
+ int on = ! !SND_SOC_DAPM_EVENT_ON(event);
+ sp_asoc_pr_dbg("Digtial FM Switch %s\n", STR_ON_OFF(on));
+ sprd_ext_fm_ctrl(SPRD_AUDIO_ID_DIG_FM, on);
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget sprd_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_MIC(BOARD_MIC_JACK, board_main_mic_event),
+ SND_SOC_DAPM_MIC(BOARD_AUX_MIC_JACK, board_sub_mic_event),
+ SND_SOC_DAPM_MIC(BOARD_HP_MIC_JACK, board_head_mic_event),
+ SND_SOC_DAPM_MIC(BOARD_DMIC_JACK, board_dig0_mic_event),
+ SND_SOC_DAPM_MIC(BOARD_DMIC1_JACK, board_dig1_mic_event),
+ /*digital fm input */
+ SND_SOC_DAPM_LINE(BOARD_DIG_FM_JACK, board_dig_fm_event),
+
+ SND_SOC_DAPM_SPK(BOARD_EXT_SPK, board_speaker_event),
+ SND_SOC_DAPM_SPK(BOARD_EXT_SPK2, board_speaker2_event),
+ SND_SOC_DAPM_SPK(BOARD_EXT_EAR, board_earpiece_event),
+ SND_SOC_DAPM_LINE(BOARD_LINE_JACK, board_line_in_event),
+ SND_SOC_DAPM_HP(BOARD_HEADPHONE_JACK, board_headphone_event),
+ SND_SOC_DAPM_HP(BOARD_INTER_HP_PA, NULL),
+ SND_SOC_DAPM_SPK(BOARD_INTER_SPK_PA, NULL),
+ SND_SOC_DAPM_SPK(BOARD_INTER_SPK2_PA, NULL),
+};
+
+static int board_func_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ ucontrol->value.integer.value[0] = board.func[id];
+ return 0;
+}
+
+static int board_func_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ int id = FUN_REG(mc->reg);
+
+ sp_asoc_pr_info("%s Switch %s\n", func_name[id],
+ STR_ON_OFF(ucontrol->value.integer.value[0]));
+
+ if (board.func[id] == ucontrol->value.integer.value[0])
+ return 0;
+
+ board.func[id] = ucontrol->value.integer.value[0];
+ board_try_inter_pa_control(card, id, 1);
+ board_ext_control(&card->dapm, id, id + 1);
+ return 1;
+}
+
+static int board_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int id = FUN_REG(mc->reg);
+ ucontrol->value.integer.value[0] = board.m[id].need_mute;
+ return 0;
+}
+
+static int board_mute_set(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+ int id = FUN_REG(mc->reg);
+
+ sp_asoc_pr_info("%s Switch %s\n", func_name[id],
+ ucontrol->value.integer.value[0] ? "Mute" : "Unmute");
+
+ if (board.m[id].need_mute == ucontrol->value.integer.value[0])
+ return 0;
+
+ board.m[id].need_mute = ucontrol->value.integer.value[0];
+ board.m[id].mute_func(card, board.m[id].is_on);
+ board_try_inter_pa_control(card, id, 1);
+ return 1;
+}
+
+static void board_inter_pa_check(int func_id)
+{
+ switch (func_id) {
+ case BOARD_FUNC_SPKL:
+ board.pa_type[func_id] =
+ sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER, 0);
+ break;
+ case BOARD_FUNC_SPKR:
+ board.pa_type[func_id] =
+ sprd_ext_speaker_ctrl(SPRD_AUDIO_ID_SPEAKER2, 0);
+ break;
+ case BOARD_FUNC_EAR:
+ board.pa_type[func_id] =
+ sprd_ext_earpiece_ctrl(SPRD_AUDIO_ID_EARPIECE, 0);
+ break;
+ case BOARD_FUNC_HP:
+ board.pa_type[func_id] =
+ sprd_ext_headphone_ctrl(SPRD_AUDIO_ID_HEADPHONE, 0);
+ break;
+ default:
+ return;
+ }
+}
+
+static void board_inter_pa_init(void)
+{
+ int id;
+ for (id = BOARD_FUNC_SPKL; id < BOARD_FUNC_MUTE_MAX; id++)
+ board_inter_pa_check(id);
+}
+
+static void board_inter_pa_control(struct snd_soc_card *card)
+{
+ int id;
+ for (id = BOARD_FUNC_SPKL; id < BOARD_FUNC_MUTE_MAX; id++)
+ board_try_inter_pa_control(card, id, 0);
+}
+
+static int board_late_probe(struct snd_soc_card *card)
+{
+ int i;
+ sprd_audio_debug_init(card->snd_card);
+
+ board_inter_pa_control(card);
+ board_ext_control(&card->dapm, 0, BOARD_FUNC_MAX);
+
+ for (i = 0; i < BOARD_FUNC_MAX; i++) {
+ snd_soc_dapm_ignore_suspend(&card->dapm, func_name[i]);
+ }
+
+ snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_HP_PA);
+ snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_SPK_PA);
+ snd_soc_dapm_ignore_suspend(&card->dapm, BOARD_INTER_SPK2_PA);
+ snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, "Playback-Vaudio");
+ snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm,
+ "Main-Capture-Vaudio");
+ snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm,
+ "Ext-Capture-Vaudio");
+ snd_soc_dapm_ignore_suspend(&card->rtd->codec->dapm, "DFM-Output");
+ return 0;
+}
+
+static void board_mute_init(void)
+{
+ board.m[BOARD_FUNC_SPKL].mute_func = audio_speaker_enable_inter;
+ board.m[BOARD_FUNC_SPKR].mute_func = audio_speaker2_enable_inter;
+ board.m[BOARD_FUNC_HP].mute_func = audio_headphone_enable_inter;
+ board.m[BOARD_FUNC_EAR].mute_func = audio_earpiece_enable_inter;
+}
+
+static int sprd_asoc_probe(struct platform_device *pdev,
+ struct snd_soc_card *card)
+{
+ struct device_node *node = pdev->dev.of_node;
+ card->dev = &pdev->dev;
+ if (node) {
+ int i;
+ struct device_node *pcm_node;
+ struct device_node *codec_node;
+
+ if (snd_soc_of_parse_card_name(card, "sprd,model")) {
+ pr_err("ERR:Card name is not provided\n");
+ return -ENODEV;
+ }
+#if 0
+ ret = snd_soc_of_parse_audio_routing(card,
+ "sprd,audio-routing");
+ if (ret) {
+ pr_err("ERR:Error while parsing DAPM routing\n");
+ return ret;
+ }
+#endif
+
+ pcm_node = of_parse_phandle(node, "sprd,pcm", 0);
+ if (!pcm_node) {
+ pr_err("ERR:PCM node is not provided\n");
+ return -EINVAL;
+ }
+
+ codec_node = of_parse_phandle(node, "sprd,codec", 0);
+ if (!codec_node) {
+ pr_err("ERR:CODEC node is not provided\n");
+ of_node_put(pcm_node);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < card->num_links; i++) {
+ card->dai_link[i].platform_name = NULL;
+ card->dai_link[i].platform_of_node = pcm_node;
+ card->dai_link[i].codec_name = NULL;
+ card->dai_link[i].codec_of_node = codec_node;
+ }
+ of_node_put(pcm_node);
+ of_node_put(codec_node);
+ }
+
+ board_mute_init();
+ board_inter_pa_init();
+ return snd_soc_register_card(card);
+}
+
+static int sprd_asoc_remove(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ snd_soc_unregister_card(card);
+ return 0;
+}
+
+static void sprd_asoc_shutdown(struct platform_device *pdev)
+{
+ struct snd_soc_card *card = platform_get_drvdata(pdev);
+ memset(&board.func, 0, sizeof(board.func));
+ board_inter_pa_control(card);
+ board_ext_control(&card->dapm, 0, BOARD_FUNC_MAX);
+}
+
+#define BOARD_CODEC_FUNC(xname, xreg) \
+ SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, board_func_get, board_func_set)
+
+#define BOARD_CODEC_MUTE(xname, xreg) \
+ SOC_SINGLE_EXT(xname, FUN_REG(xreg), 0, 1, 0, board_mute_get, board_mute_set)
+
+#if defined(CONFIG_SND_SOC_SPRD_VBC_R2P0_SPRD_CODEC_V4)
+#include "vbc-r2p0-sprd-codec-v4.h"
+#else
+#include "vbc-r1p0-sprd-codec-v1.h"
+#include "vbc-r2p0-sprd-codec-v3.h"
+#endif
+
+MODULE_DESCRIPTION("ALSA ASoC SpreadTrum VBC SPRD-CODEC");
+MODULE_AUTHOR("Zhenfang Wang <zhenfang.wang@spreadtrum.com>");
+MODULE_AUTHOR("Ken Kuang <ken.kuang@spreadtrum.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("machine:vbc-sprd-codec");