xref: /openbmc/linux/sound/soc/samsung/bells.c (revision b545dd924b4ffaf1e4fdd73fe7e9b6eb01e45aea)
1*b545dd92SMark Brown /*
2*b545dd92SMark Brown  * Bells audio support
3*b545dd92SMark Brown  *
4*b545dd92SMark Brown  * Copyright 2012 Wolfson Microelectronics
5*b545dd92SMark Brown  *
6*b545dd92SMark Brown  * This program is free software; you can redistribute  it and/or modify it
7*b545dd92SMark Brown  * under  the terms of  the GNU General  Public License as published by the
8*b545dd92SMark Brown  * Free Software Foundation;  either version 2 of the  License, or (at your
9*b545dd92SMark Brown  * option) any later version.
10*b545dd92SMark Brown  */
11*b545dd92SMark Brown 
12*b545dd92SMark Brown #include <sound/soc.h>
13*b545dd92SMark Brown #include <sound/soc-dapm.h>
14*b545dd92SMark Brown #include <sound/jack.h>
15*b545dd92SMark Brown #include <linux/gpio.h>
16*b545dd92SMark Brown #include <linux/module.h>
17*b545dd92SMark Brown 
18*b545dd92SMark Brown #include "../codecs/wm5102.h"
19*b545dd92SMark Brown #include "../codecs/wm9081.h"
20*b545dd92SMark Brown 
21*b545dd92SMark Brown /*
22*b545dd92SMark Brown  * 44.1kHz based clocks for the SYSCLK domain, use a very high clock
23*b545dd92SMark Brown  * to allow all the DSP functionality to be enabled if desired.
24*b545dd92SMark Brown  */
25*b545dd92SMark Brown #define SYSCLK_RATE (44100 * 1024)
26*b545dd92SMark Brown 
27*b545dd92SMark Brown /* 48kHz based clocks for the ASYNC domain */
28*b545dd92SMark Brown #define ASYNCCLK_RATE (48000 * 512)
29*b545dd92SMark Brown 
30*b545dd92SMark Brown /* BCLK2 is fixed at this currently */
31*b545dd92SMark Brown #define BCLK2_RATE (64 * 8000)
32*b545dd92SMark Brown 
33*b545dd92SMark Brown /*
34*b545dd92SMark Brown  * Expect a 24.576MHz crystal if one is fitted (the driver will function
35*b545dd92SMark Brown  * if this is not fitted).
36*b545dd92SMark Brown  */
37*b545dd92SMark Brown #define MCLK_RATE 24576000
38*b545dd92SMark Brown 
39*b545dd92SMark Brown #define WM9081_AUDIO_RATE 44100
40*b545dd92SMark Brown #define WM9081_MCLK_RATE  (WM9081_AUDIO_RATE * 256)
41*b545dd92SMark Brown 
42*b545dd92SMark Brown static int bells_set_bias_level(struct snd_soc_card *card,
43*b545dd92SMark Brown 				struct snd_soc_dapm_context *dapm,
44*b545dd92SMark Brown 				enum snd_soc_bias_level level)
45*b545dd92SMark Brown {
46*b545dd92SMark Brown 	struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
47*b545dd92SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
48*b545dd92SMark Brown 	int ret;
49*b545dd92SMark Brown 
50*b545dd92SMark Brown 	if (dapm->dev != codec_dai->dev)
51*b545dd92SMark Brown 		return 0;
52*b545dd92SMark Brown 
53*b545dd92SMark Brown 	switch (level) {
54*b545dd92SMark Brown 	case SND_SOC_BIAS_PREPARE:
55*b545dd92SMark Brown 		if (dapm->bias_level == SND_SOC_BIAS_STANDBY) {
56*b545dd92SMark Brown 			ret = snd_soc_codec_set_pll(codec, WM5102_FLL1,
57*b545dd92SMark Brown 						    ARIZONA_FLL_SRC_MCLK1,
58*b545dd92SMark Brown 						    MCLK_RATE,
59*b545dd92SMark Brown 						    SYSCLK_RATE);
60*b545dd92SMark Brown 			if (ret < 0)
61*b545dd92SMark Brown 				pr_err("Failed to start FLL: %d\n", ret);
62*b545dd92SMark Brown 
63*b545dd92SMark Brown 			ret = snd_soc_codec_set_pll(codec, WM5102_FLL2,
64*b545dd92SMark Brown 						    ARIZONA_FLL_SRC_AIF2BCLK,
65*b545dd92SMark Brown 						    BCLK2_RATE,
66*b545dd92SMark Brown 						    ASYNCCLK_RATE);
67*b545dd92SMark Brown 			if (ret < 0)
68*b545dd92SMark Brown 				pr_err("Failed to start FLL: %d\n", ret);
69*b545dd92SMark Brown 		}
70*b545dd92SMark Brown 		break;
71*b545dd92SMark Brown 
72*b545dd92SMark Brown 	default:
73*b545dd92SMark Brown 		break;
74*b545dd92SMark Brown 	}
75*b545dd92SMark Brown 
76*b545dd92SMark Brown 	return 0;
77*b545dd92SMark Brown }
78*b545dd92SMark Brown 
79*b545dd92SMark Brown static int bells_set_bias_level_post(struct snd_soc_card *card,
80*b545dd92SMark Brown 				     struct snd_soc_dapm_context *dapm,
81*b545dd92SMark Brown 				     enum snd_soc_bias_level level)
82*b545dd92SMark Brown {
83*b545dd92SMark Brown 	struct snd_soc_dai *codec_dai = card->rtd[0].codec_dai;
84*b545dd92SMark Brown 	struct snd_soc_codec *codec = codec_dai->codec;
85*b545dd92SMark Brown 	int ret;
86*b545dd92SMark Brown 
87*b545dd92SMark Brown 	if (dapm->dev != codec_dai->dev)
88*b545dd92SMark Brown 		return 0;
89*b545dd92SMark Brown 
90*b545dd92SMark Brown 	switch (level) {
91*b545dd92SMark Brown 	case SND_SOC_BIAS_STANDBY:
92*b545dd92SMark Brown 		ret = snd_soc_codec_set_pll(codec, WM5102_FLL1, 0, 0, 0);
93*b545dd92SMark Brown 		if (ret < 0) {
94*b545dd92SMark Brown 			pr_err("Failed to stop FLL: %d\n", ret);
95*b545dd92SMark Brown 			return ret;
96*b545dd92SMark Brown 		}
97*b545dd92SMark Brown 
98*b545dd92SMark Brown 		ret = snd_soc_codec_set_pll(codec, WM5102_FLL2, 0, 0, 0);
99*b545dd92SMark Brown 		if (ret < 0) {
100*b545dd92SMark Brown 			pr_err("Failed to stop FLL: %d\n", ret);
101*b545dd92SMark Brown 			return ret;
102*b545dd92SMark Brown 		}
103*b545dd92SMark Brown 		break;
104*b545dd92SMark Brown 
105*b545dd92SMark Brown 	default:
106*b545dd92SMark Brown 		break;
107*b545dd92SMark Brown 	}
108*b545dd92SMark Brown 
109*b545dd92SMark Brown 	dapm->bias_level = level;
110*b545dd92SMark Brown 
111*b545dd92SMark Brown 	return 0;
112*b545dd92SMark Brown }
113*b545dd92SMark Brown 
114*b545dd92SMark Brown static int bells_late_probe(struct snd_soc_card *card)
115*b545dd92SMark Brown {
116*b545dd92SMark Brown 	struct snd_soc_codec *codec = card->rtd[0].codec;
117*b545dd92SMark Brown 	struct snd_soc_dai *aif1_dai = card->rtd[0].codec_dai;
118*b545dd92SMark Brown 	struct snd_soc_dai *aif2_dai = card->rtd[1].cpu_dai;
119*b545dd92SMark Brown 	struct snd_soc_dai *aif3_dai = card->rtd[2].cpu_dai;
120*b545dd92SMark Brown 	struct snd_soc_dai *wm9081_dai = card->rtd[2].codec_dai;
121*b545dd92SMark Brown 	int ret;
122*b545dd92SMark Brown 
123*b545dd92SMark Brown 	ret = snd_soc_dai_set_sysclk(aif1_dai, ARIZONA_CLK_SYSCLK, 0, 0);
124*b545dd92SMark Brown 	if (ret != 0) {
125*b545dd92SMark Brown 		dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
126*b545dd92SMark Brown 		return ret;
127*b545dd92SMark Brown 	}
128*b545dd92SMark Brown 
129*b545dd92SMark Brown 	ret = snd_soc_dai_set_sysclk(aif2_dai, ARIZONA_CLK_ASYNCCLK, 0, 0);
130*b545dd92SMark Brown 	if (ret != 0) {
131*b545dd92SMark Brown 		dev_err(aif2_dai->dev, "Failed to set AIF2 clock: %d\n", ret);
132*b545dd92SMark Brown 		return ret;
133*b545dd92SMark Brown 	}
134*b545dd92SMark Brown 
135*b545dd92SMark Brown 	ret = snd_soc_dai_set_sysclk(aif3_dai, ARIZONA_CLK_SYSCLK, 0, 0);
136*b545dd92SMark Brown 	if (ret != 0) {
137*b545dd92SMark Brown 		dev_err(aif1_dai->dev, "Failed to set AIF1 clock: %d\n", ret);
138*b545dd92SMark Brown 		return ret;
139*b545dd92SMark Brown 	}
140*b545dd92SMark Brown 
141*b545dd92SMark Brown 	ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_SYSCLK,
142*b545dd92SMark Brown 				       ARIZONA_CLK_SRC_FLL1, SYSCLK_RATE,
143*b545dd92SMark Brown 				       SND_SOC_CLOCK_IN);
144*b545dd92SMark Brown 	if (ret != 0) {
145*b545dd92SMark Brown 		dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
146*b545dd92SMark Brown 		return ret;
147*b545dd92SMark Brown 	}
148*b545dd92SMark Brown 
149*b545dd92SMark Brown 	ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_OPCLK, 0,
150*b545dd92SMark Brown 				       WM9081_MCLK_RATE, SND_SOC_CLOCK_OUT);
151*b545dd92SMark Brown 	if (ret != 0) {
152*b545dd92SMark Brown 		dev_err(codec->dev, "Failed to set OPCLK: %d\n", ret);
153*b545dd92SMark Brown 		return ret;
154*b545dd92SMark Brown 	}
155*b545dd92SMark Brown 
156*b545dd92SMark Brown 	ret = snd_soc_codec_set_sysclk(codec, ARIZONA_CLK_ASYNCCLK,
157*b545dd92SMark Brown 				       ARIZONA_CLK_SRC_FLL2, ASYNCCLK_RATE,
158*b545dd92SMark Brown 				       SND_SOC_CLOCK_IN);
159*b545dd92SMark Brown 	if (ret != 0) {
160*b545dd92SMark Brown 		dev_err(codec->dev, "Failed to set SYSCLK: %d\n", ret);
161*b545dd92SMark Brown 		return ret;
162*b545dd92SMark Brown 	}
163*b545dd92SMark Brown 
164*b545dd92SMark Brown 	ret = snd_soc_codec_set_sysclk(wm9081_dai->codec, WM9081_SYSCLK_MCLK,
165*b545dd92SMark Brown 				       0, WM9081_MCLK_RATE, 0);
166*b545dd92SMark Brown 	if (ret != 0) {
167*b545dd92SMark Brown 		dev_err(wm9081_dai->dev, "Failed to set MCLK: %d\n", ret);
168*b545dd92SMark Brown 		return ret;
169*b545dd92SMark Brown 	}
170*b545dd92SMark Brown 
171*b545dd92SMark Brown 	return 0;
172*b545dd92SMark Brown }
173*b545dd92SMark Brown 
174*b545dd92SMark Brown static const struct snd_soc_pcm_stream baseband_params = {
175*b545dd92SMark Brown 	.formats = SNDRV_PCM_FMTBIT_S32_LE,
176*b545dd92SMark Brown 	.rate_min = 8000,
177*b545dd92SMark Brown 	.rate_max = 8000,
178*b545dd92SMark Brown 	.channels_min = 2,
179*b545dd92SMark Brown 	.channels_max = 2,
180*b545dd92SMark Brown };
181*b545dd92SMark Brown 
182*b545dd92SMark Brown static const struct snd_soc_pcm_stream sub_params = {
183*b545dd92SMark Brown 	.formats = SNDRV_PCM_FMTBIT_S32_LE,
184*b545dd92SMark Brown 	.rate_min = WM9081_AUDIO_RATE,
185*b545dd92SMark Brown 	.rate_max = WM9081_AUDIO_RATE,
186*b545dd92SMark Brown 	.channels_min = 2,
187*b545dd92SMark Brown 	.channels_max = 2,
188*b545dd92SMark Brown };
189*b545dd92SMark Brown 
190*b545dd92SMark Brown static struct snd_soc_dai_link bells_dai_wm5102[] = {
191*b545dd92SMark Brown 	{
192*b545dd92SMark Brown 		.name = "CPU",
193*b545dd92SMark Brown 		.stream_name = "CPU",
194*b545dd92SMark Brown 		.cpu_dai_name = "samsung-i2s.0",
195*b545dd92SMark Brown 		.codec_dai_name = "wm5102-aif1",
196*b545dd92SMark Brown 		.platform_name = "samsung-audio",
197*b545dd92SMark Brown 		.codec_name = "wm5102-codec",
198*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
199*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBM_CFM,
200*b545dd92SMark Brown 	},
201*b545dd92SMark Brown 	{
202*b545dd92SMark Brown 		.name = "Baseband",
203*b545dd92SMark Brown 		.stream_name = "Baseband",
204*b545dd92SMark Brown 		.cpu_dai_name = "wm5102-aif2",
205*b545dd92SMark Brown 		.codec_dai_name = "wm1250-ev1",
206*b545dd92SMark Brown 		.codec_name = "wm1250-ev1.1-0027",
207*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
208*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBM_CFM,
209*b545dd92SMark Brown 		.ignore_suspend = 1,
210*b545dd92SMark Brown 		.params = &baseband_params,
211*b545dd92SMark Brown 	},
212*b545dd92SMark Brown 	{
213*b545dd92SMark Brown 		.name = "Sub",
214*b545dd92SMark Brown 		.stream_name = "Sub",
215*b545dd92SMark Brown 		.cpu_dai_name = "wm5102-aif3",
216*b545dd92SMark Brown 		.codec_dai_name = "wm9081-hifi",
217*b545dd92SMark Brown 		.codec_name = "wm9081.1-006c",
218*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
219*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBS_CFS,
220*b545dd92SMark Brown 		.ignore_suspend = 1,
221*b545dd92SMark Brown 		.params = &sub_params,
222*b545dd92SMark Brown 	},
223*b545dd92SMark Brown };
224*b545dd92SMark Brown 
225*b545dd92SMark Brown static struct snd_soc_dai_link bells_dai_wm5110[] = {
226*b545dd92SMark Brown 	{
227*b545dd92SMark Brown 		.name = "CPU",
228*b545dd92SMark Brown 		.stream_name = "CPU",
229*b545dd92SMark Brown 		.cpu_dai_name = "samsung-i2s.0",
230*b545dd92SMark Brown 		.codec_dai_name = "wm5110-aif1",
231*b545dd92SMark Brown 		.platform_name = "samsung-audio",
232*b545dd92SMark Brown 		.codec_name = "wm5110-codec",
233*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
234*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBM_CFM,
235*b545dd92SMark Brown 	},
236*b545dd92SMark Brown 	{
237*b545dd92SMark Brown 		.name = "Baseband",
238*b545dd92SMark Brown 		.stream_name = "Baseband",
239*b545dd92SMark Brown 		.cpu_dai_name = "wm5110-aif2",
240*b545dd92SMark Brown 		.codec_dai_name = "wm1250-ev1",
241*b545dd92SMark Brown 		.codec_name = "wm1250-ev1.1-0027",
242*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
243*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBM_CFM,
244*b545dd92SMark Brown 		.ignore_suspend = 1,
245*b545dd92SMark Brown 		.params = &baseband_params,
246*b545dd92SMark Brown 	},
247*b545dd92SMark Brown 	{
248*b545dd92SMark Brown 		.name = "Sub",
249*b545dd92SMark Brown 		.stream_name = "Sub",
250*b545dd92SMark Brown 		.cpu_dai_name = "wm5102-aif3",
251*b545dd92SMark Brown 		.codec_dai_name = "wm9081-hifi",
252*b545dd92SMark Brown 		.codec_name = "wm9081.1-006c",
253*b545dd92SMark Brown 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
254*b545dd92SMark Brown 				| SND_SOC_DAIFMT_CBS_CFS,
255*b545dd92SMark Brown 		.ignore_suspend = 1,
256*b545dd92SMark Brown 		.params = &sub_params,
257*b545dd92SMark Brown 	},
258*b545dd92SMark Brown };
259*b545dd92SMark Brown 
260*b545dd92SMark Brown static struct snd_soc_codec_conf bells_codec_conf[] = {
261*b545dd92SMark Brown 	{
262*b545dd92SMark Brown 		.dev_name = "wm9081.1-006c",
263*b545dd92SMark Brown 		.name_prefix = "Sub",
264*b545dd92SMark Brown 	},
265*b545dd92SMark Brown };
266*b545dd92SMark Brown 
267*b545dd92SMark Brown static struct snd_soc_dapm_route bells_routes[] = {
268*b545dd92SMark Brown 	{ "Sub CLK_SYS", NULL, "OPCLK" },
269*b545dd92SMark Brown };
270*b545dd92SMark Brown 
271*b545dd92SMark Brown static struct snd_soc_card bells_cards[] = {
272*b545dd92SMark Brown 	{
273*b545dd92SMark Brown 		.name = "Bells WM5102",
274*b545dd92SMark Brown 		.owner = THIS_MODULE,
275*b545dd92SMark Brown 		.dai_link = bells_dai_wm5102,
276*b545dd92SMark Brown 		.num_links = ARRAY_SIZE(bells_dai_wm5102),
277*b545dd92SMark Brown 		.codec_conf = bells_codec_conf,
278*b545dd92SMark Brown 		.num_configs = ARRAY_SIZE(bells_codec_conf),
279*b545dd92SMark Brown 
280*b545dd92SMark Brown 		.late_probe = bells_late_probe,
281*b545dd92SMark Brown 
282*b545dd92SMark Brown 		.dapm_routes = bells_routes,
283*b545dd92SMark Brown 		.num_dapm_routes = ARRAY_SIZE(bells_routes),
284*b545dd92SMark Brown 
285*b545dd92SMark Brown 		.set_bias_level = bells_set_bias_level,
286*b545dd92SMark Brown 		.set_bias_level_post = bells_set_bias_level_post,
287*b545dd92SMark Brown 	},
288*b545dd92SMark Brown 	{
289*b545dd92SMark Brown 		.name = "Bells WM5110",
290*b545dd92SMark Brown 		.owner = THIS_MODULE,
291*b545dd92SMark Brown 		.dai_link = bells_dai_wm5110,
292*b545dd92SMark Brown 		.num_links = ARRAY_SIZE(bells_dai_wm5110),
293*b545dd92SMark Brown 		.codec_conf = bells_codec_conf,
294*b545dd92SMark Brown 		.num_configs = ARRAY_SIZE(bells_codec_conf),
295*b545dd92SMark Brown 
296*b545dd92SMark Brown 		.late_probe = bells_late_probe,
297*b545dd92SMark Brown 
298*b545dd92SMark Brown 		.dapm_routes = bells_routes,
299*b545dd92SMark Brown 		.num_dapm_routes = ARRAY_SIZE(bells_routes),
300*b545dd92SMark Brown 
301*b545dd92SMark Brown 		.set_bias_level = bells_set_bias_level,
302*b545dd92SMark Brown 		.set_bias_level_post = bells_set_bias_level_post,
303*b545dd92SMark Brown 	},
304*b545dd92SMark Brown };
305*b545dd92SMark Brown 
306*b545dd92SMark Brown 
307*b545dd92SMark Brown static __devinit int bells_probe(struct platform_device *pdev)
308*b545dd92SMark Brown {
309*b545dd92SMark Brown 	int ret;
310*b545dd92SMark Brown 
311*b545dd92SMark Brown 	bells_cards[pdev->id].dev = &pdev->dev;
312*b545dd92SMark Brown 
313*b545dd92SMark Brown 	ret = snd_soc_register_card(&bells_cards[pdev->id]);
314*b545dd92SMark Brown 	if (ret) {
315*b545dd92SMark Brown 		dev_err(&pdev->dev,
316*b545dd92SMark Brown 			"snd_soc_register_card(%s) failed: %d\n",
317*b545dd92SMark Brown 			bells_cards[pdev->id].name, ret);
318*b545dd92SMark Brown 		return ret;
319*b545dd92SMark Brown 	}
320*b545dd92SMark Brown 
321*b545dd92SMark Brown 	return 0;
322*b545dd92SMark Brown }
323*b545dd92SMark Brown 
324*b545dd92SMark Brown static int __devexit bells_remove(struct platform_device *pdev)
325*b545dd92SMark Brown {
326*b545dd92SMark Brown 	snd_soc_unregister_card(&bells_cards[pdev->id]);
327*b545dd92SMark Brown 
328*b545dd92SMark Brown 	return 0;
329*b545dd92SMark Brown }
330*b545dd92SMark Brown 
331*b545dd92SMark Brown static struct platform_driver bells_driver = {
332*b545dd92SMark Brown 	.driver = {
333*b545dd92SMark Brown 		.name = "bells",
334*b545dd92SMark Brown 		.owner = THIS_MODULE,
335*b545dd92SMark Brown 		.pm = &snd_soc_pm_ops,
336*b545dd92SMark Brown 	},
337*b545dd92SMark Brown 	.probe = bells_probe,
338*b545dd92SMark Brown 	.remove = __devexit_p(bells_remove),
339*b545dd92SMark Brown };
340*b545dd92SMark Brown 
341*b545dd92SMark Brown module_platform_driver(bells_driver);
342*b545dd92SMark Brown 
343*b545dd92SMark Brown MODULE_DESCRIPTION("Bells audio support");
344*b545dd92SMark Brown MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
345*b545dd92SMark Brown MODULE_LICENSE("GPL");
346*b545dd92SMark Brown MODULE_ALIAS("platform:bells");
347