118334Speter// SPDX-License-Identifier: GPL-2.0+
250397Sobrien//
318334Speter// Tobermory audio support
418334Speter//
518334Speter// Copyright 2011 Wolfson Microelectronics
618334Speter
718334Speter#include <sound/soc.h>
818334Speter#include <sound/soc-dapm.h>
918334Speter#include <sound/jack.h>
1018334Speter#include <linux/gpio.h>
1118334Speter#include <linux/module.h>
1218334Speter
1318334Speter#include "../codecs/wm8962.h"
1418334Speter
1518334Speterstatic int sample_rate = 44100;
1618334Speter
1718334Speterstatic int tobermory_set_bias_level(struct snd_soc_card *card,
1818334Speter					  struct snd_soc_dapm_context *dapm,
1918334Speter					  enum snd_soc_bias_level level)
2018334Speter{
2118334Speter	struct snd_soc_pcm_runtime *rtd;
2218334Speter	struct snd_soc_dai *codec_dai;
2318334Speter	int ret;
2418334Speter
2518334Speter	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
2618334Speter	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
2718334Speter
2818334Speter	if (dapm->dev != codec_dai->dev)
2918334Speter		return 0;
3018334Speter
3118334Speter	switch (level) {
3218334Speter	case SND_SOC_BIAS_PREPARE:
3318334Speter		if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
3418334Speter			ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
3518334Speter						  WM8962_FLL_MCLK, 32768,
3618334Speter						  sample_rate * 512);
3718334Speter			if (ret < 0)
3818334Speter				pr_err("Failed to start FLL: %d\n", ret);
3918334Speter
4018334Speter			ret = snd_soc_dai_set_sysclk(codec_dai,
4118334Speter						     WM8962_SYSCLK_FLL,
4218334Speter						     sample_rate * 512,
4318334Speter						     SND_SOC_CLOCK_IN);
4418334Speter			if (ret < 0) {
4518334Speter				pr_err("Failed to set SYSCLK: %d\n", ret);
4618334Speter				snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
4718334Speter						    0, 0, 0);
4818334Speter				return ret;
4918334Speter			}
5018334Speter		}
5118334Speter		break;
5218334Speter
5318334Speter	default:
5418334Speter		break;
5550397Sobrien	}
5618334Speter
5718334Speter	return 0;
5818334Speter}
5918334Speter
6018334Speterstatic int tobermory_set_bias_level_post(struct snd_soc_card *card,
6118334Speter					       struct snd_soc_dapm_context *dapm,
6250397Sobrien					       enum snd_soc_bias_level level)
6350397Sobrien{
6418334Speter	struct snd_soc_pcm_runtime *rtd;
6518334Speter	struct snd_soc_dai *codec_dai;
6650397Sobrien	int ret;
6750397Sobrien
6818334Speter	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
6918334Speter	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
7018334Speter
7118334Speter	if (dapm->dev != codec_dai->dev)
7218334Speter		return 0;
7318334Speter
7418334Speter	switch (level) {
7518334Speter	case SND_SOC_BIAS_STANDBY:
7618334Speter		ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
7718334Speter					     32768, SND_SOC_CLOCK_IN);
7818334Speter		if (ret < 0) {
7918334Speter			pr_err("Failed to switch away from FLL: %d\n", ret);
8018334Speter			return ret;
8118334Speter		}
8218334Speter
8318334Speter		ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,
8418334Speter					  0, 0, 0);
8518334Speter		if (ret < 0) {
8618334Speter			pr_err("Failed to stop FLL: %d\n", ret);
8718334Speter			return ret;
8818334Speter		}
8918334Speter		break;
9018334Speter
9118334Speter	default:
9218334Speter		break;
9318334Speter	}
9418334Speter
9518334Speter	dapm->bias_level = level;
9618334Speter
9718334Speter	return 0;
9818334Speter}
9918334Speter
10018334Speterstatic int tobermory_hw_params(struct snd_pcm_substream *substream,
10118334Speter			      struct snd_pcm_hw_params *params)
10218334Speter{
10318334Speter	sample_rate = params_rate(params);
10418334Speter
10518334Speter	return 0;
10618334Speter}
10718334Speter
10850397Sobrienstatic const struct snd_soc_ops tobermory_ops = {
10918334Speter	.hw_params = tobermory_hw_params,
11018334Speter};
11118334Speter
11218334SpeterSND_SOC_DAILINK_DEFS(cpu,
11318334Speter	DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),
11418334Speter	DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),
11518334Speter	DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));
11618334Speter
11718334Speterstatic struct snd_soc_dai_link tobermory_dai[] = {
11818334Speter	{
11918334Speter		.name = "CPU",
12018334Speter		.stream_name = "CPU",
12150397Sobrien		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
12218334Speter				| SND_SOC_DAIFMT_CBM_CFM,
12350397Sobrien		.ops = &tobermory_ops,
12450397Sobrien		SND_SOC_DAILINK_REG(cpu),
12550397Sobrien	},
12618334Speter};
12718334Speter
12818334Speterstatic const struct snd_kcontrol_new controls[] = {
12918334Speter	SOC_DAPM_PIN_SWITCH("Main Speaker"),
13018334Speter	SOC_DAPM_PIN_SWITCH("DMIC"),
13118334Speter};
13218334Speter
13318334Speterstatic const struct snd_soc_dapm_widget widgets[] = {
13418334Speter	SND_SOC_DAPM_HP("Headphone", NULL),
13518334Speter	SND_SOC_DAPM_MIC("Headset Mic", NULL),
13618334Speter
13718334Speter	SND_SOC_DAPM_MIC("DMIC", NULL),
13818334Speter	SND_SOC_DAPM_MIC("AMIC", NULL),
13918334Speter
14018334Speter	SND_SOC_DAPM_SPK("Main Speaker", NULL),
14118334Speter};
14218334Speter
14318334Speterstatic const struct snd_soc_dapm_route audio_paths[] = {
14418334Speter	{ "Headphone", NULL, "HPOUTL" },
14518334Speter	{ "Headphone", NULL, "HPOUTR" },
14618334Speter
14718334Speter	{ "Main Speaker", NULL, "SPKOUTL" },
14818334Speter	{ "Main Speaker", NULL, "SPKOUTR" },
14918334Speter
15018334Speter	{ "Headset Mic", NULL, "MICBIAS" },
15118334Speter	{ "IN4L", NULL, "Headset Mic" },
15218334Speter	{ "IN4R", NULL, "Headset Mic" },
15318334Speter
15418334Speter	{ "AMIC", NULL, "MICBIAS" },
15550397Sobrien	{ "IN1L", NULL, "AMIC" },
15618334Speter	{ "IN1R", NULL, "AMIC" },
15718334Speter
15818334Speter	{ "DMIC", NULL, "MICBIAS" },
15918334Speter	{ "DMICDAT", NULL, "DMIC" },
16018334Speter};
16118334Speter
16218334Speterstatic struct snd_soc_jack tobermory_headset;
16318334Speter
16418334Speter/* Headset jack detection DAPM pins */
16518334Speterstatic struct snd_soc_jack_pin tobermory_headset_pins[] = {
16618334Speter	{
16718334Speter		.pin = "Headset Mic",
16818334Speter		.mask = SND_JACK_MICROPHONE,
16918334Speter	},
17018334Speter	{
17118334Speter		.pin = "Headphone",
17218334Speter		.mask = SND_JACK_MICROPHONE,
17318334Speter	},
17418334Speter};
17518334Speter
17618334Speterstatic int tobermory_late_probe(struct snd_soc_card *card)
17718334Speter{
17818334Speter	struct snd_soc_pcm_runtime *rtd;
17918334Speter	struct snd_soc_component *component;
18018334Speter	struct snd_soc_dai *codec_dai;
18118334Speter	int ret;
18218334Speter
18318334Speter	rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);
18418334Speter	component = snd_soc_rtd_to_codec(rtd, 0)->component;
18518334Speter	codec_dai = snd_soc_rtd_to_codec(rtd, 0);
18650397Sobrien
18750397Sobrien	ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,
18850397Sobrien				     32768, SND_SOC_CLOCK_IN);
18950397Sobrien	if (ret < 0)
19050397Sobrien		return ret;
19150397Sobrien
19218334Speter	ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |
19318334Speter					 SND_JACK_BTN_0, &tobermory_headset,
19418334Speter					 tobermory_headset_pins,
19518334Speter					 ARRAY_SIZE(tobermory_headset_pins));
19618334Speter	if (ret)
19718334Speter		return ret;
19818334Speter
19918334Speter	wm8962_mic_detect(component, &tobermory_headset);
20018334Speter
20118334Speter	return 0;
20218334Speter}
20318334Speter
20418334Speterstatic struct snd_soc_card tobermory = {
20518334Speter	.name = "Tobermory",
20618334Speter	.owner = THIS_MODULE,
20718334Speter	.dai_link = tobermory_dai,
20818334Speter	.num_links = ARRAY_SIZE(tobermory_dai),
20918334Speter
21018334Speter	.set_bias_level = tobermory_set_bias_level,
21118334Speter	.set_bias_level_post = tobermory_set_bias_level_post,
21218334Speter
21318334Speter	.controls = controls,
21418334Speter	.num_controls = ARRAY_SIZE(controls),
21518334Speter	.dapm_widgets = widgets,
21618334Speter	.num_dapm_widgets = ARRAY_SIZE(widgets),
21718334Speter	.dapm_routes = audio_paths,
21818334Speter	.num_dapm_routes = ARRAY_SIZE(audio_paths),
21918334Speter	.fully_routed = true,
22018334Speter
22118334Speter	.late_probe = tobermory_late_probe,
22218334Speter};
22318334Speter
22418334Speterstatic int tobermory_probe(struct platform_device *pdev)
22518334Speter{
22618334Speter	struct snd_soc_card *card = &tobermory;
22750397Sobrien	int ret;
22818334Speter
22918334Speter	card->dev = &pdev->dev;
23050397Sobrien
23118334Speter	ret = devm_snd_soc_register_card(&pdev->dev, card);
23218334Speter	if (ret)
23318334Speter		dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");
23418334Speter
23518334Speter	return ret;
23618334Speter}
23718334Speter
23818334Speterstatic struct platform_driver tobermory_driver = {
23918334Speter	.driver = {
24018334Speter		.name = "tobermory",
24118334Speter		.pm = &snd_soc_pm_ops,
24218334Speter	},
24318334Speter	.probe = tobermory_probe,
24418334Speter};
24518334Speter
24618334Spetermodule_platform_driver(tobermory_driver);
24718334Speter
24818334SpeterMODULE_DESCRIPTION("Tobermory audio support");
24918334SpeterMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
25018334SpeterMODULE_LICENSE("GPL");
25118334SpeterMODULE_ALIAS("platform:tobermory");
25250397Sobrien