xref: /openbmc/linux/sound/soc/codecs/wm8524.c (revision 007b6a54c305688d1db7255e5c724e3d76ba5aa4)
1*007b6a54SMihai Serban /*
2*007b6a54SMihai Serban  * wm8524.c  --  WM8524 ALSA SoC Audio driver
3*007b6a54SMihai Serban  *
4*007b6a54SMihai Serban  * Copyright 2009 Wolfson Microelectronics plc
5*007b6a54SMihai Serban  * Copyright 2017 NXP
6*007b6a54SMihai Serban  *
7*007b6a54SMihai Serban  * Based on WM8523 ALSA SoC Audio driver written by Mark Brown
8*007b6a54SMihai Serban  *
9*007b6a54SMihai Serban  * This program is free software; you can redistribute it and/or modify
10*007b6a54SMihai Serban  * it under the terms of the GNU General Public License version 2 as
11*007b6a54SMihai Serban  * published by the Free Software Foundation.
12*007b6a54SMihai Serban  */
13*007b6a54SMihai Serban 
14*007b6a54SMihai Serban #include <linux/module.h>
15*007b6a54SMihai Serban #include <linux/moduleparam.h>
16*007b6a54SMihai Serban #include <linux/init.h>
17*007b6a54SMihai Serban #include <linux/delay.h>
18*007b6a54SMihai Serban #include <linux/slab.h>
19*007b6a54SMihai Serban #include <linux/gpio/consumer.h>
20*007b6a54SMihai Serban #include <linux/of_device.h>
21*007b6a54SMihai Serban #include <sound/core.h>
22*007b6a54SMihai Serban #include <sound/pcm.h>
23*007b6a54SMihai Serban #include <sound/pcm_params.h>
24*007b6a54SMihai Serban #include <sound/soc.h>
25*007b6a54SMihai Serban #include <sound/initval.h>
26*007b6a54SMihai Serban 
27*007b6a54SMihai Serban #define WM8524_NUM_RATES 7
28*007b6a54SMihai Serban 
29*007b6a54SMihai Serban /* codec private data */
30*007b6a54SMihai Serban struct wm8524_priv {
31*007b6a54SMihai Serban 	struct gpio_desc *mute;
32*007b6a54SMihai Serban 	unsigned int sysclk;
33*007b6a54SMihai Serban 	unsigned int rate_constraint_list[WM8524_NUM_RATES];
34*007b6a54SMihai Serban 	struct snd_pcm_hw_constraint_list rate_constraint;
35*007b6a54SMihai Serban };
36*007b6a54SMihai Serban 
37*007b6a54SMihai Serban 
38*007b6a54SMihai Serban static const struct snd_soc_dapm_widget wm8524_dapm_widgets[] = {
39*007b6a54SMihai Serban SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0),
40*007b6a54SMihai Serban SND_SOC_DAPM_OUTPUT("LINEVOUTL"),
41*007b6a54SMihai Serban SND_SOC_DAPM_OUTPUT("LINEVOUTR"),
42*007b6a54SMihai Serban };
43*007b6a54SMihai Serban 
44*007b6a54SMihai Serban static const struct snd_soc_dapm_route wm8524_dapm_routes[] = {
45*007b6a54SMihai Serban 	{ "LINEVOUTL", NULL, "DAC" },
46*007b6a54SMihai Serban 	{ "LINEVOUTR", NULL, "DAC" },
47*007b6a54SMihai Serban };
48*007b6a54SMihai Serban 
49*007b6a54SMihai Serban static const struct {
50*007b6a54SMihai Serban 	int value;
51*007b6a54SMihai Serban 	int ratio;
52*007b6a54SMihai Serban } lrclk_ratios[WM8524_NUM_RATES] = {
53*007b6a54SMihai Serban 	{ 1, 128 },
54*007b6a54SMihai Serban 	{ 2, 192 },
55*007b6a54SMihai Serban 	{ 3, 256 },
56*007b6a54SMihai Serban 	{ 4, 384 },
57*007b6a54SMihai Serban 	{ 5, 512 },
58*007b6a54SMihai Serban 	{ 6, 768 },
59*007b6a54SMihai Serban 	{ 7, 1152 },
60*007b6a54SMihai Serban };
61*007b6a54SMihai Serban 
62*007b6a54SMihai Serban static int wm8524_startup(struct snd_pcm_substream *substream,
63*007b6a54SMihai Serban 			  struct snd_soc_dai *dai)
64*007b6a54SMihai Serban {
65*007b6a54SMihai Serban 	struct snd_soc_codec *codec = dai->codec;
66*007b6a54SMihai Serban 	struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
67*007b6a54SMihai Serban 
68*007b6a54SMihai Serban 	/* The set of sample rates that can be supported depends on the
69*007b6a54SMihai Serban 	 * MCLK supplied to the CODEC - enforce this.
70*007b6a54SMihai Serban 	 */
71*007b6a54SMihai Serban 	if (!wm8524->sysclk) {
72*007b6a54SMihai Serban 		dev_err(codec->dev,
73*007b6a54SMihai Serban 			"No MCLK configured, call set_sysclk() on init\n");
74*007b6a54SMihai Serban 		return -EINVAL;
75*007b6a54SMihai Serban 	}
76*007b6a54SMihai Serban 
77*007b6a54SMihai Serban 	snd_pcm_hw_constraint_list(substream->runtime, 0,
78*007b6a54SMihai Serban 				   SNDRV_PCM_HW_PARAM_RATE,
79*007b6a54SMihai Serban 				   &wm8524->rate_constraint);
80*007b6a54SMihai Serban 
81*007b6a54SMihai Serban 	gpiod_set_value_cansleep(wm8524->mute, 1);
82*007b6a54SMihai Serban 
83*007b6a54SMihai Serban 	return 0;
84*007b6a54SMihai Serban }
85*007b6a54SMihai Serban 
86*007b6a54SMihai Serban static void wm8524_shutdown(struct snd_pcm_substream *substream,
87*007b6a54SMihai Serban 			  struct snd_soc_dai *dai)
88*007b6a54SMihai Serban {
89*007b6a54SMihai Serban 	struct snd_soc_codec *codec = dai->codec;
90*007b6a54SMihai Serban 	struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
91*007b6a54SMihai Serban 
92*007b6a54SMihai Serban 	gpiod_set_value_cansleep(wm8524->mute, 0);
93*007b6a54SMihai Serban }
94*007b6a54SMihai Serban 
95*007b6a54SMihai Serban static int wm8524_set_dai_sysclk(struct snd_soc_dai *codec_dai,
96*007b6a54SMihai Serban 		int clk_id, unsigned int freq, int dir)
97*007b6a54SMihai Serban {
98*007b6a54SMihai Serban 	struct snd_soc_codec *codec = codec_dai->codec;
99*007b6a54SMihai Serban 	struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
100*007b6a54SMihai Serban 	unsigned int val;
101*007b6a54SMihai Serban 	int i, j = 0;
102*007b6a54SMihai Serban 
103*007b6a54SMihai Serban 	wm8524->sysclk = freq;
104*007b6a54SMihai Serban 
105*007b6a54SMihai Serban 	wm8524->rate_constraint.count = 0;
106*007b6a54SMihai Serban 	for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) {
107*007b6a54SMihai Serban 		val = freq / lrclk_ratios[i].ratio;
108*007b6a54SMihai Serban 		/* Check that it's a standard rate since core can't
109*007b6a54SMihai Serban 		 * cope with others and having the odd rates confuses
110*007b6a54SMihai Serban 		 * constraint matching.
111*007b6a54SMihai Serban 		 */
112*007b6a54SMihai Serban 		switch (val) {
113*007b6a54SMihai Serban 		case 8000:
114*007b6a54SMihai Serban 		case 32000:
115*007b6a54SMihai Serban 		case 44100:
116*007b6a54SMihai Serban 		case 48000:
117*007b6a54SMihai Serban 		case 88200:
118*007b6a54SMihai Serban 		case 96000:
119*007b6a54SMihai Serban 		case 176400:
120*007b6a54SMihai Serban 		case 192000:
121*007b6a54SMihai Serban 			dev_err(codec->dev, "Supported sample rate: %dHz\n",
122*007b6a54SMihai Serban 				val);
123*007b6a54SMihai Serban 			wm8524->rate_constraint_list[j++] = val;
124*007b6a54SMihai Serban 			wm8524->rate_constraint.count++;
125*007b6a54SMihai Serban 			break;
126*007b6a54SMihai Serban 		default:
127*007b6a54SMihai Serban 			dev_dbg(codec->dev, "Skipping sample rate: %dHz\n",
128*007b6a54SMihai Serban 				val);
129*007b6a54SMihai Serban 		}
130*007b6a54SMihai Serban 	}
131*007b6a54SMihai Serban 
132*007b6a54SMihai Serban 	/* Need at least one supported rate... */
133*007b6a54SMihai Serban 	if (wm8524->rate_constraint.count == 0)
134*007b6a54SMihai Serban 		return -EINVAL;
135*007b6a54SMihai Serban 
136*007b6a54SMihai Serban 	return 0;
137*007b6a54SMihai Serban }
138*007b6a54SMihai Serban 
139*007b6a54SMihai Serban static int wm8524_set_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
140*007b6a54SMihai Serban {
141*007b6a54SMihai Serban 	fmt &= (SND_SOC_DAIFMT_FORMAT_MASK | SND_SOC_DAIFMT_INV_MASK |
142*007b6a54SMihai Serban 		SND_SOC_DAIFMT_MASTER_MASK);
143*007b6a54SMihai Serban 
144*007b6a54SMihai Serban 	if (fmt != (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
145*007b6a54SMihai Serban 		    SND_SOC_DAIFMT_CBS_CFS)) {
146*007b6a54SMihai Serban 		dev_err(codec_dai->dev, "Invalid DAI format\n");
147*007b6a54SMihai Serban 		return -EINVAL;
148*007b6a54SMihai Serban 	}
149*007b6a54SMihai Serban 
150*007b6a54SMihai Serban 	return 0;
151*007b6a54SMihai Serban }
152*007b6a54SMihai Serban 
153*007b6a54SMihai Serban static int wm8524_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
154*007b6a54SMihai Serban {
155*007b6a54SMihai Serban 	struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(dai->codec);
156*007b6a54SMihai Serban 
157*007b6a54SMihai Serban 	if (wm8524->mute)
158*007b6a54SMihai Serban 		gpiod_set_value_cansleep(wm8524->mute, mute);
159*007b6a54SMihai Serban 
160*007b6a54SMihai Serban 	return 0;
161*007b6a54SMihai Serban }
162*007b6a54SMihai Serban 
163*007b6a54SMihai Serban #define WM8524_RATES SNDRV_PCM_RATE_8000_192000
164*007b6a54SMihai Serban 
165*007b6a54SMihai Serban #define WM8524_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
166*007b6a54SMihai Serban 
167*007b6a54SMihai Serban static const struct snd_soc_dai_ops wm8524_dai_ops = {
168*007b6a54SMihai Serban 	.startup	= wm8524_startup,
169*007b6a54SMihai Serban 	.shutdown	= wm8524_shutdown,
170*007b6a54SMihai Serban 	.set_sysclk	= wm8524_set_dai_sysclk,
171*007b6a54SMihai Serban 	.set_fmt	= wm8524_set_fmt,
172*007b6a54SMihai Serban 	.mute_stream	= wm8524_mute_stream,
173*007b6a54SMihai Serban };
174*007b6a54SMihai Serban 
175*007b6a54SMihai Serban static struct snd_soc_dai_driver wm8524_dai = {
176*007b6a54SMihai Serban 	.name = "wm8524-hifi",
177*007b6a54SMihai Serban 	.playback = {
178*007b6a54SMihai Serban 		.stream_name = "Playback",
179*007b6a54SMihai Serban 		.channels_min = 2,
180*007b6a54SMihai Serban 		.channels_max = 2,
181*007b6a54SMihai Serban 		.rates = WM8524_RATES,
182*007b6a54SMihai Serban 		.formats = WM8524_FORMATS,
183*007b6a54SMihai Serban 	},
184*007b6a54SMihai Serban 	.ops = &wm8524_dai_ops,
185*007b6a54SMihai Serban };
186*007b6a54SMihai Serban 
187*007b6a54SMihai Serban static int wm8524_probe(struct snd_soc_codec *codec)
188*007b6a54SMihai Serban {
189*007b6a54SMihai Serban 	struct wm8524_priv *wm8524 = snd_soc_codec_get_drvdata(codec);
190*007b6a54SMihai Serban 
191*007b6a54SMihai Serban 	wm8524->rate_constraint.list = &wm8524->rate_constraint_list[0];
192*007b6a54SMihai Serban 	wm8524->rate_constraint.count =
193*007b6a54SMihai Serban 		ARRAY_SIZE(wm8524->rate_constraint_list);
194*007b6a54SMihai Serban 
195*007b6a54SMihai Serban 	return 0;
196*007b6a54SMihai Serban }
197*007b6a54SMihai Serban 
198*007b6a54SMihai Serban static const struct snd_soc_codec_driver soc_codec_dev_wm8524 = {
199*007b6a54SMihai Serban 	.probe =	wm8524_probe,
200*007b6a54SMihai Serban 
201*007b6a54SMihai Serban 	.component_driver = {
202*007b6a54SMihai Serban 		.dapm_widgets		= wm8524_dapm_widgets,
203*007b6a54SMihai Serban 		.num_dapm_widgets	= ARRAY_SIZE(wm8524_dapm_widgets),
204*007b6a54SMihai Serban 		.dapm_routes		= wm8524_dapm_routes,
205*007b6a54SMihai Serban 		.num_dapm_routes	= ARRAY_SIZE(wm8524_dapm_routes),
206*007b6a54SMihai Serban 	},
207*007b6a54SMihai Serban };
208*007b6a54SMihai Serban 
209*007b6a54SMihai Serban #ifdef CONFIG_OF
210*007b6a54SMihai Serban static const struct of_device_id wm8524_of_match[] = {
211*007b6a54SMihai Serban 	{ .compatible = "wlf,wm8524" },
212*007b6a54SMihai Serban 	{ /* sentinel*/ }
213*007b6a54SMihai Serban };
214*007b6a54SMihai Serban MODULE_DEVICE_TABLE(of, wm8524_of_match);
215*007b6a54SMihai Serban #endif
216*007b6a54SMihai Serban 
217*007b6a54SMihai Serban static int wm8524_codec_probe(struct platform_device *pdev)
218*007b6a54SMihai Serban {
219*007b6a54SMihai Serban 	struct wm8524_priv *wm8524;
220*007b6a54SMihai Serban 	int ret;
221*007b6a54SMihai Serban 
222*007b6a54SMihai Serban 	wm8524 = devm_kzalloc(&pdev->dev, sizeof(struct wm8524_priv),
223*007b6a54SMihai Serban 						  GFP_KERNEL);
224*007b6a54SMihai Serban 	if (wm8524 == NULL)
225*007b6a54SMihai Serban 		return -ENOMEM;
226*007b6a54SMihai Serban 
227*007b6a54SMihai Serban 	platform_set_drvdata(pdev, wm8524);
228*007b6a54SMihai Serban 
229*007b6a54SMihai Serban 	wm8524->mute = devm_gpiod_get(&pdev->dev, "wlf,mute", GPIOD_OUT_LOW);
230*007b6a54SMihai Serban 	if (IS_ERR(wm8524->mute)) {
231*007b6a54SMihai Serban 		ret = PTR_ERR(wm8524->mute);
232*007b6a54SMihai Serban 		dev_err(&pdev->dev, "Failed to get mute line: %d\n", ret);
233*007b6a54SMihai Serban 		return ret;
234*007b6a54SMihai Serban 	}
235*007b6a54SMihai Serban 
236*007b6a54SMihai Serban 	ret =  snd_soc_register_codec(&pdev->dev,
237*007b6a54SMihai Serban 			&soc_codec_dev_wm8524, &wm8524_dai, 1);
238*007b6a54SMihai Serban 	if (ret < 0) {
239*007b6a54SMihai Serban 		dev_err(&pdev->dev, "Failed to register codec: %d\n", ret);
240*007b6a54SMihai Serban 		snd_soc_unregister_platform(&pdev->dev);
241*007b6a54SMihai Serban 	}
242*007b6a54SMihai Serban 
243*007b6a54SMihai Serban 	return ret;
244*007b6a54SMihai Serban }
245*007b6a54SMihai Serban 
246*007b6a54SMihai Serban static int wm8524_codec_remove(struct platform_device *pdev)
247*007b6a54SMihai Serban {
248*007b6a54SMihai Serban 	snd_soc_unregister_codec(&pdev->dev);
249*007b6a54SMihai Serban 	return 0;
250*007b6a54SMihai Serban }
251*007b6a54SMihai Serban 
252*007b6a54SMihai Serban static struct platform_driver wm8524_codec_driver = {
253*007b6a54SMihai Serban 	.probe		= wm8524_codec_probe,
254*007b6a54SMihai Serban 	.remove		= wm8524_codec_remove,
255*007b6a54SMihai Serban 	.driver		= {
256*007b6a54SMihai Serban 		.name	= "wm8524-codec",
257*007b6a54SMihai Serban 		.of_match_table = wm8524_of_match,
258*007b6a54SMihai Serban 	},
259*007b6a54SMihai Serban };
260*007b6a54SMihai Serban module_platform_driver(wm8524_codec_driver);
261*007b6a54SMihai Serban 
262*007b6a54SMihai Serban MODULE_DESCRIPTION("ASoC WM8524 driver");
263*007b6a54SMihai Serban MODULE_AUTHOR("Mihai Serban <mihai.serban@nxp.com>");
264*007b6a54SMihai Serban MODULE_ALIAS("platform:wm8524-codec");
265*007b6a54SMihai Serban MODULE_LICENSE("GPL");
266