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