| /* |
| * ASoC Driver for the Excelsior dev board |
| * |
| * Copyright (C) 2019 Google, LLC. |
| * |
| * 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. |
| * |
| * 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/gpio.h> |
| #include <linux/module.h> |
| #include <linux/of_gpio.h> |
| #include <sound/jack.h> |
| #include <sound/soc.h> |
| |
| static struct snd_soc_jack mt8167_excelsior_jack; |
| |
| static struct snd_soc_jack_pin mt8167_excelsior_jack_pins[] = { |
| { |
| .pin = "Headphone", |
| .mask = SND_JACK_HEADSET, |
| }, |
| }; |
| |
| static struct snd_soc_jack_gpio mt8167_excelsior_jack_gpios[] = { |
| { |
| .gpio = -1, |
| .name = "headset-gpio", |
| .report = SND_JACK_HEADSET, |
| .invert = 0, |
| .debounce_time = 200, |
| }, |
| }; |
| |
| enum PINCTRL_PIN_STATE { PIN_STATE_DEFAULT = 0, PIN_STATE_MAX }; |
| |
| struct mt8167_excelsior_priv { |
| struct pinctrl *pinctrl; |
| struct pinctrl_state *pin_states[PIN_STATE_MAX]; |
| int jack_gpio; |
| }; |
| |
| static const char *const mt8167_excelsior_pinctrl_pin_str[PIN_STATE_MAX] = { |
| "default", |
| }; |
| |
| static int mt8167_excelsior_mic1_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_dapm_context *dapm = w->dapm; |
| struct snd_soc_card *card = dapm->card; |
| |
| dev_dbg(card->dev, "%s, event %d\n", __func__, event); |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| break; |
| case SND_SOC_DAPM_POST_PMD: |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int mt8167_excelsior_mic2_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, int event) |
| { |
| struct snd_soc_dapm_context *dapm = w->dapm; |
| struct snd_soc_card *card = dapm->card; |
| |
| dev_dbg(card->dev, "%s, event %d\n", __func__, event); |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| break; |
| case SND_SOC_DAPM_POST_PMD: |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int mt8167_excelsior_headset_mic_event(struct snd_soc_dapm_widget *w, |
| struct snd_kcontrol *kcontrol, |
| int event) |
| { |
| struct snd_soc_dapm_context *dapm = w->dapm; |
| struct snd_soc_card *card = dapm->card; |
| |
| dev_dbg(card->dev, "%s, event %d\n", __func__, event); |
| |
| switch (event) { |
| case SND_SOC_DAPM_PRE_PMU: |
| break; |
| case SND_SOC_DAPM_POST_PMD: |
| break; |
| default: |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int mt8167_excelsior_int_adda_init(struct snd_soc_pcm_runtime *runtime) |
| { |
| int ret = 0; |
| struct mt8167_excelsior_priv *card_data; |
| struct snd_soc_card *card = runtime->card; |
| card_data = snd_soc_card_get_drvdata(card); |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| if (gpio_is_valid(card_data->jack_gpio)) { |
| ret = snd_soc_card_jack_new(card, "Headset Jack", |
| SND_JACK_HEADSET, |
| &mt8167_excelsior_jack, |
| mt8167_excelsior_jack_pins, |
| ARRAY_SIZE |
| (mt8167_excelsior_jack_pins)); |
| if (ret) { |
| dev_err(card->dev, "Can't snd_soc_jack_new %d\n", ret); |
| return ret; |
| } |
| |
| mt8167_excelsior_jack_gpios[0].gpio = card_data->jack_gpio; |
| ret = snd_soc_jack_add_gpios(&mt8167_excelsior_jack, |
| ARRAY_SIZE |
| (mt8167_excelsior_jack_gpios), |
| mt8167_excelsior_jack_gpios); |
| if (ret) { |
| dev_err(card->dev, "Can't snd_soc_jack_add_pins %d\n", |
| ret); |
| return ret; |
| } |
| |
| } else { |
| dev_err(card->dev, "Invalid gpio for headphone jack.\n"); |
| } |
| return ret; |
| } |
| |
| static int mt8167_excelsior_card_remove(struct snd_soc_card *card) |
| { |
| snd_soc_jack_free_gpios(&mt8167_excelsior_jack, |
| ARRAY_SIZE(mt8167_excelsior_jack_gpios), |
| mt8167_excelsior_jack_gpios); |
| return 0; |
| } |
| |
| static const struct snd_soc_dapm_widget mt8167_excelsior_dapm_widgets[] = { |
| SND_SOC_DAPM_HP("Headphone", NULL), |
| SND_SOC_DAPM_MIC("Mic 1", mt8167_excelsior_mic1_event), |
| SND_SOC_DAPM_MIC("Mic 2", mt8167_excelsior_mic2_event), |
| SND_SOC_DAPM_MIC("Headset Mic", mt8167_excelsior_headset_mic_event), |
| }; |
| |
| static const struct snd_soc_dapm_route mt8167_excelsior_audio_map[] = { |
| /* Uplink */ |
| {"AU_VIN0", NULL, "Mic 1"}, |
| {"AU_VIN1", NULL, "Headset Mic"}, |
| |
| /* Downlink */ |
| /* use internal spk amp of MT6392 */ |
| {"Int Spk Amp", NULL, "AU_LOL"}, |
| {"Headphone", NULL, "AU_HPL"}, |
| {"Headphone", NULL, "AU_HPR"}, |
| }; |
| |
| static struct snd_soc_dai_link mt8167_excelsior_dais[] = { |
| /* Front End DAI links */ |
| { |
| .name = "DL1 Playback", |
| .stream_name = "MultiMedia1_PLayback", |
| .cpu_dai_name = "DL1", |
| .codec_name = "snd-soc-dummy", |
| .codec_dai_name = "snd-soc-dummy-dai", |
| .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
| SND_SOC_DPCM_TRIGGER_POST}, |
| .init = mt8167_excelsior_int_adda_init, |
| .dynamic = 1, |
| .dpcm_playback = 1, |
| }, |
| { |
| .name = "VUL Capture", |
| .stream_name = "MultiMedia1_Capture", |
| .cpu_dai_name = "VUL", |
| .codec_name = "snd-soc-dummy", |
| .codec_dai_name = "snd-soc-dummy-dai", |
| .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
| SND_SOC_DPCM_TRIGGER_POST}, |
| .dynamic = 1, |
| .dpcm_capture = 1, |
| }, |
| { |
| .name = "AWB Capture", |
| .stream_name = "DL1_AWB_Record", |
| .cpu_dai_name = "AWB", |
| .codec_name = "snd-soc-dummy", |
| .codec_dai_name = "snd-soc-dummy-dai", |
| .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
| SND_SOC_DPCM_TRIGGER_POST}, |
| .dynamic = 1, |
| .dpcm_capture = 1, |
| }, |
| { |
| .name = "DL2 Playback", |
| .stream_name = "MultiMedia2_PLayback", |
| .cpu_dai_name = "DL2", |
| .codec_name = "snd-soc-dummy", |
| .codec_dai_name = "snd-soc-dummy-dai", |
| .trigger = {SND_SOC_DPCM_TRIGGER_POST, |
| SND_SOC_DPCM_TRIGGER_POST}, |
| .dynamic = 1, |
| .dpcm_playback = 1, |
| }, |
| /* Back End DAI links */ |
| { |
| .name = "MTK Codec", |
| .cpu_dai_name = "INT ADDA", |
| .no_pcm = 1, |
| .codec_name = "mt8167-codec", |
| .codec_dai_name = "mt8167-codec-dai", |
| .dpcm_playback = 1, |
| .dpcm_capture = 1, |
| }, |
| }; |
| |
| static const struct snd_kcontrol_new mt8167_excelsior_card_controls[] = { |
| SOC_DAPM_PIN_SWITCH("Headphone"), |
| }; |
| |
| static struct snd_soc_card mt8167_excelsior_card = { |
| .name = "excelsior-card", |
| .owner = THIS_MODULE, |
| .remove = mt8167_excelsior_card_remove, |
| .dai_link = mt8167_excelsior_dais, |
| .num_links = ARRAY_SIZE(mt8167_excelsior_dais), |
| .dapm_widgets = mt8167_excelsior_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(mt8167_excelsior_dapm_widgets), |
| .controls = mt8167_excelsior_card_controls, |
| .num_controls = ARRAY_SIZE(mt8167_excelsior_card_controls), |
| .dapm_routes = mt8167_excelsior_audio_map, |
| .num_dapm_routes = ARRAY_SIZE(mt8167_excelsior_audio_map), |
| }; |
| |
| static int mt8167_excelsior_gpio_probe(struct snd_soc_card *card) |
| { |
| struct mt8167_excelsior_priv *card_data; |
| int ret = 0; |
| int i; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| card_data = snd_soc_card_get_drvdata(card); |
| |
| card_data->pinctrl = devm_pinctrl_get(card->dev); |
| if (IS_ERR(card_data->pinctrl)) { |
| ret = PTR_ERR(card_data->pinctrl); |
| dev_err(card->dev, "%s pinctrl_get failed %d\n", __func__, ret); |
| goto exit; |
| } |
| |
| for (i = 0; i < PIN_STATE_MAX; i++) { |
| card_data->pin_states[i] = |
| pinctrl_lookup_state(card_data->pinctrl, |
| mt8167_excelsior_pinctrl_pin_str[i]); |
| if (IS_ERR(card_data->pin_states[i])) { |
| ret = PTR_ERR(card_data->pin_states[i]); |
| dev_warn(card->dev, |
| "%s Can't find pinctrl state %s %d\n", |
| __func__, mt8167_excelsior_pinctrl_pin_str[i], |
| ret); |
| } else { |
| dev_warn(card->dev, "%s Set pinctrl state %s %d\n", |
| __func__, mt8167_excelsior_pinctrl_pin_str[i], |
| ret); |
| } |
| } |
| |
| /* default state */ |
| if (!IS_ERR(card_data->pin_states[PIN_STATE_DEFAULT])) { |
| ret = pinctrl_select_state(card_data->pinctrl, |
| card_data-> |
| pin_states[PIN_STATE_DEFAULT]); |
| if (ret) { |
| dev_err(card->dev, "%s failed to select state %d\n", |
| __func__, ret); |
| goto exit; |
| } |
| } |
| |
| card_data->jack_gpio = |
| of_get_named_gpio(card->dev->of_node, "mediatek,jack-detect-gpio", |
| 0); |
| dev_dbg(card->dev, "jack gpio: %d\n", card_data->jack_gpio); |
| |
| exit: |
| return ret; |
| } |
| |
| static int mt8167_excelsior_dev_probe(struct platform_device *pdev) |
| { |
| struct snd_soc_card *card = &mt8167_excelsior_card; |
| struct device_node *platform_node; |
| int ret, i; |
| struct mt8167_excelsior_priv *card_data; |
| |
| dev_dbg(card->dev, "%s\n", __func__); |
| |
| platform_node = |
| of_parse_phandle(pdev->dev.of_node, "mediatek,platform", 0); |
| if (!platform_node) { |
| dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < card->num_links; i++) { |
| if (mt8167_excelsior_dais[i].platform_name) |
| continue; |
| mt8167_excelsior_dais[i].platform_of_node = platform_node; |
| } |
| |
| card->dev = &pdev->dev; |
| |
| card_data = |
| devm_kzalloc(&pdev->dev, sizeof(struct mt8167_excelsior_priv), |
| GFP_KERNEL); |
| |
| if (!card_data) { |
| ret = -ENOMEM; |
| dev_err(&pdev->dev, "%s allocate card private data fail %d\n", |
| __func__, ret); |
| return ret; |
| } |
| |
| snd_soc_card_set_drvdata(card, card_data); |
| |
| mt8167_excelsior_gpio_probe(card); |
| |
| ret = devm_snd_soc_register_card(&pdev->dev, card); |
| if (ret) |
| dev_err(&pdev->dev, "%s snd_soc_register_card fail %d\n", |
| __func__, ret); |
| |
| return ret; |
| } |
| |
| static int mt8167_excelsior_dev_remove(struct platform_device *pdev) |
| { |
| return 0; |
| } |
| |
| static const struct of_device_id mt8167_excelsior_dt_match[] = { |
| { |
| .compatible = "mediatek,snd-soc-mt8167-excelsior", |
| }, |
| {} |
| }; |
| |
| MODULE_DEVICE_TABLE(of, mt8167_excelsior_dt_match); |
| |
| static struct platform_driver mt8167_excelsior_mach_driver = { |
| .driver = { |
| .name = "mt8167-excelsior", |
| .of_match_table = mt8167_excelsior_dt_match, |
| #ifdef CONFIG_PM |
| .pm = &snd_soc_pm_ops, |
| #endif |
| }, |
| .probe = mt8167_excelsior_dev_probe, |
| .remove = mt8167_excelsior_dev_remove, |
| }; |
| |
| module_platform_driver(mt8167_excelsior_mach_driver); |
| |
| /* Module information */ |
| MODULE_DESCRIPTION("MT8167 Excelsior ALSA SoC machine driver"); |
| MODULE_LICENSE("GPL v2"); |
| MODULE_ALIAS("platform:mt8167-excelsior"); |