252 lines · c
1// SPDX-License-Identifier: GPL-2.0+2//3// Tobermory audio support4//5// Copyright 2011 Wolfson Microelectronics6 7#include <sound/soc.h>8#include <sound/soc-dapm.h>9#include <sound/jack.h>10#include <linux/gpio.h>11#include <linux/module.h>12 13#include "../codecs/wm8962.h"14 15static int sample_rate = 44100;16 17static int tobermory_set_bias_level(struct snd_soc_card *card,18 struct snd_soc_dapm_context *dapm,19 enum snd_soc_bias_level level)20{21 struct snd_soc_pcm_runtime *rtd;22 struct snd_soc_dai *codec_dai;23 int ret;24 25 rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);26 codec_dai = snd_soc_rtd_to_codec(rtd, 0);27 28 if (dapm->dev != codec_dai->dev)29 return 0;30 31 switch (level) {32 case SND_SOC_BIAS_PREPARE:33 if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {34 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,35 WM8962_FLL_MCLK, 32768,36 sample_rate * 512);37 if (ret < 0)38 pr_err("Failed to start FLL: %d\n", ret);39 40 ret = snd_soc_dai_set_sysclk(codec_dai,41 WM8962_SYSCLK_FLL,42 sample_rate * 512,43 SND_SOC_CLOCK_IN);44 if (ret < 0) {45 pr_err("Failed to set SYSCLK: %d\n", ret);46 snd_soc_dai_set_pll(codec_dai, WM8962_FLL,47 0, 0, 0);48 return ret;49 }50 }51 break;52 53 default:54 break;55 }56 57 return 0;58}59 60static int tobermory_set_bias_level_post(struct snd_soc_card *card,61 struct snd_soc_dapm_context *dapm,62 enum snd_soc_bias_level level)63{64 struct snd_soc_pcm_runtime *rtd;65 struct snd_soc_dai *codec_dai;66 int ret;67 68 rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);69 codec_dai = snd_soc_rtd_to_codec(rtd, 0);70 71 if (dapm->dev != codec_dai->dev)72 return 0;73 74 switch (level) {75 case SND_SOC_BIAS_STANDBY:76 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,77 32768, SND_SOC_CLOCK_IN);78 if (ret < 0) {79 pr_err("Failed to switch away from FLL: %d\n", ret);80 return ret;81 }82 83 ret = snd_soc_dai_set_pll(codec_dai, WM8962_FLL,84 0, 0, 0);85 if (ret < 0) {86 pr_err("Failed to stop FLL: %d\n", ret);87 return ret;88 }89 break;90 91 default:92 break;93 }94 95 dapm->bias_level = level;96 97 return 0;98}99 100static int tobermory_hw_params(struct snd_pcm_substream *substream,101 struct snd_pcm_hw_params *params)102{103 sample_rate = params_rate(params);104 105 return 0;106}107 108static const struct snd_soc_ops tobermory_ops = {109 .hw_params = tobermory_hw_params,110};111 112SND_SOC_DAILINK_DEFS(cpu,113 DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0")),114 DAILINK_COMP_ARRAY(COMP_CODEC("wm8962.1-001a", "wm8962")),115 DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0")));116 117static struct snd_soc_dai_link tobermory_dai[] = {118 {119 .name = "CPU",120 .stream_name = "CPU",121 .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF122 | SND_SOC_DAIFMT_CBM_CFM,123 .ops = &tobermory_ops,124 SND_SOC_DAILINK_REG(cpu),125 },126};127 128static const struct snd_kcontrol_new controls[] = {129 SOC_DAPM_PIN_SWITCH("Main Speaker"),130 SOC_DAPM_PIN_SWITCH("DMIC"),131};132 133static const struct snd_soc_dapm_widget widgets[] = {134 SND_SOC_DAPM_HP("Headphone", NULL),135 SND_SOC_DAPM_MIC("Headset Mic", NULL),136 137 SND_SOC_DAPM_MIC("DMIC", NULL),138 SND_SOC_DAPM_MIC("AMIC", NULL),139 140 SND_SOC_DAPM_SPK("Main Speaker", NULL),141};142 143static const struct snd_soc_dapm_route audio_paths[] = {144 { "Headphone", NULL, "HPOUTL" },145 { "Headphone", NULL, "HPOUTR" },146 147 { "Main Speaker", NULL, "SPKOUTL" },148 { "Main Speaker", NULL, "SPKOUTR" },149 150 { "Headset Mic", NULL, "MICBIAS" },151 { "IN4L", NULL, "Headset Mic" },152 { "IN4R", NULL, "Headset Mic" },153 154 { "AMIC", NULL, "MICBIAS" },155 { "IN1L", NULL, "AMIC" },156 { "IN1R", NULL, "AMIC" },157 158 { "DMIC", NULL, "MICBIAS" },159 { "DMICDAT", NULL, "DMIC" },160};161 162static struct snd_soc_jack tobermory_headset;163 164/* Headset jack detection DAPM pins */165static struct snd_soc_jack_pin tobermory_headset_pins[] = {166 {167 .pin = "Headset Mic",168 .mask = SND_JACK_MICROPHONE,169 },170 {171 .pin = "Headphone",172 .mask = SND_JACK_MICROPHONE,173 },174};175 176static int tobermory_late_probe(struct snd_soc_card *card)177{178 struct snd_soc_pcm_runtime *rtd;179 struct snd_soc_component *component;180 struct snd_soc_dai *codec_dai;181 int ret;182 183 rtd = snd_soc_get_pcm_runtime(card, &card->dai_link[0]);184 component = snd_soc_rtd_to_codec(rtd, 0)->component;185 codec_dai = snd_soc_rtd_to_codec(rtd, 0);186 187 ret = snd_soc_dai_set_sysclk(codec_dai, WM8962_SYSCLK_MCLK,188 32768, SND_SOC_CLOCK_IN);189 if (ret < 0)190 return ret;191 192 ret = snd_soc_card_jack_new_pins(card, "Headset", SND_JACK_HEADSET |193 SND_JACK_BTN_0, &tobermory_headset,194 tobermory_headset_pins,195 ARRAY_SIZE(tobermory_headset_pins));196 if (ret)197 return ret;198 199 wm8962_mic_detect(component, &tobermory_headset);200 201 return 0;202}203 204static struct snd_soc_card tobermory = {205 .name = "Tobermory",206 .owner = THIS_MODULE,207 .dai_link = tobermory_dai,208 .num_links = ARRAY_SIZE(tobermory_dai),209 210 .set_bias_level = tobermory_set_bias_level,211 .set_bias_level_post = tobermory_set_bias_level_post,212 213 .controls = controls,214 .num_controls = ARRAY_SIZE(controls),215 .dapm_widgets = widgets,216 .num_dapm_widgets = ARRAY_SIZE(widgets),217 .dapm_routes = audio_paths,218 .num_dapm_routes = ARRAY_SIZE(audio_paths),219 .fully_routed = true,220 221 .late_probe = tobermory_late_probe,222};223 224static int tobermory_probe(struct platform_device *pdev)225{226 struct snd_soc_card *card = &tobermory;227 int ret;228 229 card->dev = &pdev->dev;230 231 ret = devm_snd_soc_register_card(&pdev->dev, card);232 if (ret)233 dev_err_probe(&pdev->dev, ret, "snd_soc_register_card() failed\n");234 235 return ret;236}237 238static struct platform_driver tobermory_driver = {239 .driver = {240 .name = "tobermory",241 .pm = &snd_soc_pm_ops,242 },243 .probe = tobermory_probe,244};245 246module_platform_driver(tobermory_driver);247 248MODULE_DESCRIPTION("Tobermory audio support");249MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");250MODULE_LICENSE("GPL");251MODULE_ALIAS("platform:tobermory");252