1d18360e0SSylwester Nawrocki // SPDX-License-Identifier: GPL-2.0+
2d18360e0SSylwester Nawrocki //
3d18360e0SSylwester Nawrocki // Tobermory audio support
4d18360e0SSylwester Nawrocki //
5d18360e0SSylwester Nawrocki // Copyright 2011 Wolfson Microelectronics
66414261fSMark Brown
76414261fSMark Brown #include <sound/soc.h>
86414261fSMark Brown #include <sound/soc-dapm.h>
96414261fSMark Brown #include <sound/jack.h>
106414261fSMark Brown #include <linux/gpio.h>
116414261fSMark Brown #include <linux/module.h>
126414261fSMark Brown
136414261fSMark Brown #include "../codecs/wm8962.h"
146414261fSMark Brown
156414261fSMark Brown static int sample_rate = 44100;
166414261fSMark Brown
tobermory_set_bias_level(struct snd_soc_card * card,struct snd_soc_dapm_context * dapm,enum snd_soc_bias_level level)176414261fSMark Brown static int tobermory_set_bias_level(struct snd_soc_card *card,
186414261fSMark Brown struct snd_soc_dapm_context *dapm,
196414261fSMark Brown enum snd_soc_bias_level level)
206414261fSMark Brown {
215015920aSMengdong Lin struct snd_soc_pcm_runtime *rtd;
225015920aSMengdong Lin struct snd_soc_dai *codec_dai;
236414261fSMark Brown int ret;
246414261fSMark Brown
254468189fSKuninori Morimoto rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
267de6b6bcSKuninori Morimoto codec_dai = asoc_rtd_to_codec(rtd, 0);
275015920aSMengdong Lin
286414261fSMark Brown if (dapm->dev != codec_dai->dev)
296414261fSMark Brown return 0;
306414261fSMark Brown
316414261fSMark Brown switch (level) {
326414261fSMark Brown case SND_SOC_BIAS_PREPARE:
336414261fSMark Brown if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
346414261fSMark Brown ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
356414261fSMark Brown WM8962_FLL_MCLK, 32768,
366414261fSMark Brown sample_rate * 512);
376414261fSMark Brown if (ret < 0)
386414261fSMark Brown pr_err("Failed to start FLL: %d\n", ret);
396414261fSMark Brown
406414261fSMark Brown ret = snd_soc_dai_set_sysclk(codec_dai,
416414261fSMark Brown WM8962_SYSCLK_FLL,
426414261fSMark Brown sample_rate * 512,
436414261fSMark Brown SND_SOC_CLOCK_IN);
446414261fSMark Brown if (ret < 0) {
456414261fSMark Brown pr_err("Failed to set SYSCLK: %d\n", ret);
46631f8e94SMark Brown snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
47631f8e94SMark Brown 0, 0, 0);
486414261fSMark Brown return ret;
496414261fSMark Brown }
506414261fSMark Brown }
516414261fSMark Brown break;
526414261fSMark Brown
536414261fSMark Brown default:
546414261fSMark Brown break;
556414261fSMark Brown }
566414261fSMark Brown
576414261fSMark Brown return 0;
586414261fSMark Brown }
596414261fSMark Brown
tobermory_set_bias_level_post(struct snd_soc_card * card,struct snd_soc_dapm_context * dapm,enum snd_soc_bias_level level)606414261fSMark Brown static int tobermory_set_bias_level_post(struct snd_soc_card *card,
616414261fSMark Brown struct snd_soc_dapm_context *dapm,
626414261fSMark Brown enum snd_soc_bias_level level)
636414261fSMark Brown {
645015920aSMengdong Lin struct snd_soc_pcm_runtime *rtd;
655015920aSMengdong Lin struct snd_soc_dai *codec_dai;
666414261fSMark Brown int ret;
676414261fSMark Brown
684468189fSKuninori Morimoto rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
697de6b6bcSKuninori Morimoto codec_dai = asoc_rtd_to_codec(rtd, 0);
705015920aSMengdong Lin
716414261fSMark Brown if (dapm->dev != codec_dai->dev)
726414261fSMark Brown return 0;
736414261fSMark Brown
746414261fSMark Brown switch (level) {
756414261fSMark Brown case SND_SOC_BIAS_STANDBY:
766414261fSMark Brown ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
776414261fSMark Brown 32768, SND_SOC_CLOCK_IN);
786414261fSMark Brown if (ret < 0) {
796414261fSMark Brown pr_err("Failed to switch away from FLL: %d\n", ret);
806414261fSMark Brown return ret;
816414261fSMark Brown }
826414261fSMark Brown
836414261fSMark Brown ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
846414261fSMark Brown 0, 0, 0);
856414261fSMark Brown if (ret < 0) {
866414261fSMark Brown pr_err("Failed to stop FLL: %d\n", ret);
876414261fSMark Brown return ret;
886414261fSMark Brown }
896414261fSMark Brown break;
906414261fSMark Brown
916414261fSMark Brown default:
926414261fSMark Brown break;
936414261fSMark Brown }
946414261fSMark Brown
956414261fSMark Brown dapm->bias_level = level;
966414261fSMark Brown
976414261fSMark Brown return 0;
986414261fSMark Brown }
996414261fSMark Brown
tobermory_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)1006414261fSMark Brown static int tobermory_hw_params(struct snd_pcm_substream *substream,
1016414261fSMark Brown struct snd_pcm_hw_params *params)
1026414261fSMark Brown {
1036414261fSMark Brown sample_rate = params_rate(params);
1046414261fSMark Brown
1056414261fSMark Brown return 0;
1066414261fSMark Brown }
1076414261fSMark Brown
1082080acf3SRikard Falkeborn static const struct snd_soc_ops tobermory_ops = {
1096414261fSMark Brown .hw_params = tobermory_hw_params,
1106414261fSMark Brown };
1116414261fSMark Brown
11219bca225SKuninori Morimoto SND_SOC_DAILINK_DEFS(cpu,
11319bca225SKuninori Morimoto DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
11419bca225SKuninori Morimoto DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),
11519bca225SKuninori Morimoto DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
11619bca225SKuninori Morimoto
1176414261fSMark Brown static struct snd_soc_dai_link tobermory_dai[] = {
1186414261fSMark Brown {
1196414261fSMark Brown .name = "CPU",
1206414261fSMark Brown .stream_name = "CPU",
1216414261fSMark Brown .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
1226414261fSMark Brown | SND_SOC_DAIFMT_CBM_CFM,
1236414261fSMark Brown .ops = &tobermory_ops,
12419bca225SKuninori Morimoto SND_SOC_DAILINK_REG(cpu),
1256414261fSMark Brown },
1266414261fSMark Brown };
1276414261fSMark Brown
1286414261fSMark Brown static const struct snd_kcontrol_new controls[] = {
1296414261fSMark Brown SOC_DAPM_PIN_SWITCH("Main Speaker"),
1306414261fSMark Brown SOC_DAPM_PIN_SWITCH("DMIC"),
1316414261fSMark Brown };
1326414261fSMark Brown
1335449fd7bSRikard Falkeborn static const struct snd_soc_dapm_widget widgets[] = {
1346414261fSMark Brown SND_SOC_DAPM_HP("Headphone", NULL),
1356414261fSMark Brown SND_SOC_DAPM_MIC("Headset Mic", NULL),
1366414261fSMark Brown
1376414261fSMark Brown SND_SOC_DAPM_MIC("DMIC", NULL),
1386414261fSMark Brown SND_SOC_DAPM_MIC("AMIC", NULL),
1396414261fSMark Brown
1406414261fSMark Brown SND_SOC_DAPM_SPK("Main Speaker", NULL),
1416414261fSMark Brown };
1426414261fSMark Brown
1435449fd7bSRikard Falkeborn static const struct snd_soc_dapm_route audio_paths[] = {
1446414261fSMark Brown { "Headphone", NULL, "HPOUTL" },
1456414261fSMark Brown { "Headphone", NULL, "HPOUTR" },
1466414261fSMark Brown
1476414261fSMark Brown { "Main Speaker", NULL, "SPKOUTL" },
1486414261fSMark Brown { "Main Speaker", NULL, "SPKOUTR" },
1496414261fSMark Brown
1506414261fSMark Brown { "Headset Mic", NULL, "MICBIAS" },
1516414261fSMark Brown { "IN4L", NULL, "Headset Mic" },
1526414261fSMark Brown { "IN4R", NULL, "Headset Mic" },
1536414261fSMark Brown
1546414261fSMark Brown { "AMIC", NULL, "MICBIAS" },
1556414261fSMark Brown { "IN1L", NULL, "AMIC" },
1566414261fSMark Brown { "IN1R", NULL, "AMIC" },
1576414261fSMark Brown
1586414261fSMark Brown { "DMIC", NULL, "MICBIAS" },
1596414261fSMark Brown { "DMICDAT", NULL, "DMIC" },
1606414261fSMark Brown };
1616414261fSMark Brown
1626414261fSMark Brown static struct snd_soc_jack tobermory_headset;
1636414261fSMark Brown
1646414261fSMark Brown /* Headset jack detection DAPM pins */
1656414261fSMark Brown static struct snd_soc_jack_pin tobermory_headset_pins[] = {
1666414261fSMark Brown {
1676414261fSMark Brown .pin = "Headset Mic",
1686414261fSMark Brown .mask = SND_JACK_MICROPHONE,
1696414261fSMark Brown },
1706414261fSMark Brown {
1716414261fSMark Brown .pin = "Headphone",
1726414261fSMark Brown .mask = SND_JACK_MICROPHONE,
1736414261fSMark Brown },
1746414261fSMark Brown };
1756414261fSMark Brown
tobermory_late_probe(struct snd_soc_card * card)1766414261fSMark Brown static int tobermory_late_probe(struct snd_soc_card *card)
1776414261fSMark Brown {
1785015920aSMengdong Lin struct snd_soc_pcm_runtime *rtd;
179f4ee2717SKuninori Morimoto struct snd_soc_component *component;
1805015920aSMengdong Lin struct snd_soc_dai *codec_dai;
1816414261fSMark Brown int ret;
1826414261fSMark Brown
1834468189fSKuninori Morimoto rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
1847de6b6bcSKuninori Morimoto component = asoc_rtd_to_codec(rtd, 0)->component;
1857de6b6bcSKuninori Morimoto codec_dai = asoc_rtd_to_codec(rtd, 0);
1865015920aSMengdong Lin
1876414261fSMark Brown ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
1886414261fSMark Brown 32768, SND_SOC_CLOCK_IN);
1896414261fSMark Brown if (ret < 0)
1906414261fSMark Brown return ret;
1916414261fSMark Brown
192*19aed2d6SAkihiko Odaki ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |
1933fd94f37SLars-Peter Clausen SND_JACK_BTN_0, &tobermory_headset,
1943fd94f37SLars-Peter Clausen tobermory_headset_pins,
1953fd94f37SLars-Peter Clausen ARRAY_SIZE(tobermory_headset_pins));
1966414261fSMark Brown if (ret)
1976414261fSMark Brown return ret;
1986414261fSMark Brown
199f4ee2717SKuninori Morimoto wm8962_mic_detect(component, &tobermory_headset);
2006414261fSMark Brown
2016414261fSMark Brown return 0;
2026414261fSMark Brown }
2036414261fSMark Brown
2046414261fSMark Brown static struct snd_soc_card tobermory = {
2056414261fSMark Brown .name = "Tobermory",
206095d79dcSAxel Lin .owner = THIS_MODULE,
2076414261fSMark Brown .dai_link = tobermory_dai,
2086414261fSMark Brown .num_links = ARRAY_SIZE(tobermory_dai),
2096414261fSMark Brown
2106414261fSMark Brown .set_bias_level = tobermory_set_bias_level,
2116414261fSMark Brown .set_bias_level_post = tobermory_set_bias_level_post,
2126414261fSMark Brown
2136414261fSMark Brown .controls = controls,
2146414261fSMark Brown .num_controls = ARRAY_SIZE(controls),
2156414261fSMark Brown .dapm_widgets = widgets,
2166414261fSMark Brown .num_dapm_widgets = ARRAY_SIZE(widgets),
2176414261fSMark Brown .dapm_routes = audio_paths,
2186414261fSMark Brown .num_dapm_routes = ARRAY_SIZE(audio_paths),
2196414261fSMark Brown .fully_routed = true,
2206414261fSMark Brown
2216414261fSMark Brown .late_probe = tobermory_late_probe,
2226414261fSMark Brown };
2236414261fSMark Brown
tobermory_probe(struct platform_device * pdev)224fdca21adSBill Pemberton static int tobermory_probe(struct platform_device *pdev)
2256414261fSMark Brown {
2266414261fSMark Brown struct snd_soc_card *card = &tobermory;
2276414261fSMark Brown int ret;
2286414261fSMark Brown
2296414261fSMark Brown card->dev = &pdev->dev;
2306414261fSMark Brown
231c583883eSTushar Behera ret = devm_snd_soc_register_card(&pdev->dev, card);
23227c6eaebSKuninori Morimoto if (ret)
23327c6eaebSKuninori Morimoto dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
234c583883eSTushar Behera
2356414261fSMark Brown return ret;
2366414261fSMark Brown }
2376414261fSMark Brown
2386414261fSMark Brown static struct platform_driver tobermory_driver = {
2396414261fSMark Brown .driver = {
2406414261fSMark Brown .name = "tobermory",
2416414261fSMark Brown .pm = &snd_soc_pm_ops,
2426414261fSMark Brown },
2436414261fSMark Brown .probe = tobermory_probe,
2446414261fSMark Brown };
2456414261fSMark Brown
2466414261fSMark Brown module_platform_driver(tobermory_driver);
2476414261fSMark Brown
2486414261fSMark Brown MODULE_DESCRIPTION("Tobermory audio support");
2496414261fSMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
2506414261fSMark Brown MODULE_LICENSE("GPL");
2516414261fSMark Brown MODULE_ALIAS("platform:tobermory");
252