xref: /openbmc/linux/sound/soc/samsung/tobermory.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
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