xref: /openbmc/linux/sound/soc/atmel/sam9x5_wm8731.c (revision f91ca89e924eb287915522664a31afc71a49c05b)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2fdbcb3cbSNicolas Ferre /*
3fdbcb3cbSNicolas Ferre  * sam9x5_wm8731   --	SoC audio for AT91SAM9X5-based boards
4fdbcb3cbSNicolas Ferre  *			that are using WM8731 as codec.
5fdbcb3cbSNicolas Ferre  *
6fdbcb3cbSNicolas Ferre  *  Copyright (C) 2011 Atmel,
7fdbcb3cbSNicolas Ferre  *		  Nicolas Ferre <nicolas.ferre@atmel.com>
8fdbcb3cbSNicolas Ferre  *
9fdbcb3cbSNicolas Ferre  *  Copyright (C) 2013 Paratronic,
10fdbcb3cbSNicolas Ferre  *		  Richard Genoud <richard.genoud@gmail.com>
11fdbcb3cbSNicolas Ferre  *
12fdbcb3cbSNicolas Ferre  * Based on sam9g20_wm8731.c by:
13fdbcb3cbSNicolas Ferre  * Sedji Gaouaou <sedji.gaouaou@atmel.com>
14fdbcb3cbSNicolas Ferre  */
15fdbcb3cbSNicolas Ferre #include <linux/of.h>
16fdbcb3cbSNicolas Ferre #include <linux/export.h>
17fdbcb3cbSNicolas Ferre #include <linux/module.h>
18fdbcb3cbSNicolas Ferre #include <linux/mod_devicetable.h>
19fdbcb3cbSNicolas Ferre #include <linux/platform_device.h>
20fdbcb3cbSNicolas Ferre #include <linux/device.h>
21fdbcb3cbSNicolas Ferre 
22fdbcb3cbSNicolas Ferre #include <sound/soc.h>
23fdbcb3cbSNicolas Ferre #include <sound/soc-dai.h>
24fdbcb3cbSNicolas Ferre #include <sound/soc-dapm.h>
25fdbcb3cbSNicolas Ferre 
26fdbcb3cbSNicolas Ferre #include "../codecs/wm8731.h"
27fdbcb3cbSNicolas Ferre #include "atmel_ssc_dai.h"
28fdbcb3cbSNicolas Ferre 
29fdbcb3cbSNicolas Ferre 
30fdbcb3cbSNicolas Ferre #define MCLK_RATE 12288000
31fdbcb3cbSNicolas Ferre 
32fdbcb3cbSNicolas Ferre #define DRV_NAME "sam9x5-snd-wm8731"
33fdbcb3cbSNicolas Ferre 
34fdbcb3cbSNicolas Ferre struct sam9x5_drvdata {
35fdbcb3cbSNicolas Ferre 	int ssc_id;
36fdbcb3cbSNicolas Ferre };
37fdbcb3cbSNicolas Ferre 
38fdbcb3cbSNicolas Ferre /*
39fdbcb3cbSNicolas Ferre  * Logic for a wm8731 as connected on a at91sam9x5ek based board.
40fdbcb3cbSNicolas Ferre  */
sam9x5_wm8731_init(struct snd_soc_pcm_runtime * rtd)41fdbcb3cbSNicolas Ferre static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd)
42fdbcb3cbSNicolas Ferre {
43*6de2e582SKuninori Morimoto 	struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
44fdbcb3cbSNicolas Ferre 	struct device *dev = rtd->dev;
45fdbcb3cbSNicolas Ferre 	int ret;
46fdbcb3cbSNicolas Ferre 
472efb8a8fSLadislav Michl 	dev_dbg(dev, "%s called\n", __func__);
48fdbcb3cbSNicolas Ferre 
49fdbcb3cbSNicolas Ferre 	/* set the codec system clock for DAC and ADC */
50fdbcb3cbSNicolas Ferre 	ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL,
51fdbcb3cbSNicolas Ferre 				     MCLK_RATE, SND_SOC_CLOCK_IN);
52fdbcb3cbSNicolas Ferre 	if (ret < 0) {
532efb8a8fSLadislav Michl 		dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret);
54fdbcb3cbSNicolas Ferre 		return ret;
55fdbcb3cbSNicolas Ferre 	}
56fdbcb3cbSNicolas Ferre 
57fdbcb3cbSNicolas Ferre 	return 0;
58fdbcb3cbSNicolas Ferre }
59fdbcb3cbSNicolas Ferre 
60fdbcb3cbSNicolas Ferre /*
61fdbcb3cbSNicolas Ferre  * Audio paths on at91sam9x5ek board:
62fdbcb3cbSNicolas Ferre  *
63fdbcb3cbSNicolas Ferre  *  |A| ------------> |      | ---R----> Headphone Jack
64fdbcb3cbSNicolas Ferre  *  |T| <----\        |  WM  | ---L--/
65fdbcb3cbSNicolas Ferre  *  |9| ---> CLK <--> | 8731 | <--R----- Line In Jack
66fdbcb3cbSNicolas Ferre  *  |1| <------------ |      | <--L--/
67fdbcb3cbSNicolas Ferre  */
68fdbcb3cbSNicolas Ferre static const struct snd_soc_dapm_widget sam9x5_dapm_widgets[] = {
69fdbcb3cbSNicolas Ferre 	SND_SOC_DAPM_HP("Headphone Jack", NULL),
70fdbcb3cbSNicolas Ferre 	SND_SOC_DAPM_LINE("Line In Jack", NULL),
71fdbcb3cbSNicolas Ferre };
72fdbcb3cbSNicolas Ferre 
sam9x5_wm8731_driver_probe(struct platform_device * pdev)73fdbcb3cbSNicolas Ferre static int sam9x5_wm8731_driver_probe(struct platform_device *pdev)
74fdbcb3cbSNicolas Ferre {
75fdbcb3cbSNicolas Ferre 	struct device_node *np = pdev->dev.of_node;
76fdbcb3cbSNicolas Ferre 	struct device_node *codec_np, *cpu_np;
77fdbcb3cbSNicolas Ferre 	struct snd_soc_card *card;
78fdbcb3cbSNicolas Ferre 	struct snd_soc_dai_link *dai;
79fdbcb3cbSNicolas Ferre 	struct sam9x5_drvdata *priv;
8035617d82SKuninori Morimoto 	struct snd_soc_dai_link_component *comp;
81fdbcb3cbSNicolas Ferre 	int ret;
82fdbcb3cbSNicolas Ferre 
83fdbcb3cbSNicolas Ferre 	if (!np) {
84fdbcb3cbSNicolas Ferre 		dev_err(&pdev->dev, "No device node supplied\n");
85fdbcb3cbSNicolas Ferre 		return -EINVAL;
86fdbcb3cbSNicolas Ferre 	}
87fdbcb3cbSNicolas Ferre 
88fdbcb3cbSNicolas Ferre 	card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL);
89fdbcb3cbSNicolas Ferre 	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
90fdbcb3cbSNicolas Ferre 	dai = devm_kzalloc(&pdev->dev, sizeof(*dai), GFP_KERNEL);
91e1be6aa0SKuninori Morimoto 	comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL);
9235617d82SKuninori Morimoto 	if (!dai || !card || !priv || !comp) {
93fdbcb3cbSNicolas Ferre 		ret = -ENOMEM;
94fdbcb3cbSNicolas Ferre 		goto out;
95fdbcb3cbSNicolas Ferre 	}
96fdbcb3cbSNicolas Ferre 
9746bec25dSBo Shen 	snd_soc_card_set_drvdata(card, priv);
9846bec25dSBo Shen 
99fdbcb3cbSNicolas Ferre 	card->dev = &pdev->dev;
100fdbcb3cbSNicolas Ferre 	card->owner = THIS_MODULE;
101fdbcb3cbSNicolas Ferre 	card->dai_link = dai;
102fdbcb3cbSNicolas Ferre 	card->num_links = 1;
103fdbcb3cbSNicolas Ferre 	card->dapm_widgets = sam9x5_dapm_widgets;
104fdbcb3cbSNicolas Ferre 	card->num_dapm_widgets = ARRAY_SIZE(sam9x5_dapm_widgets);
10535617d82SKuninori Morimoto 
10635617d82SKuninori Morimoto 	dai->cpus = &comp[0];
10735617d82SKuninori Morimoto 	dai->num_cpus = 1;
10835617d82SKuninori Morimoto 	dai->codecs = &comp[1];
10935617d82SKuninori Morimoto 	dai->num_codecs = 1;
110e1be6aa0SKuninori Morimoto 	dai->platforms = &comp[2];
111e1be6aa0SKuninori Morimoto 	dai->num_platforms = 1;
11235617d82SKuninori Morimoto 
113fdbcb3cbSNicolas Ferre 	dai->name = "WM8731";
114fdbcb3cbSNicolas Ferre 	dai->stream_name = "WM8731 PCM";
11535617d82SKuninori Morimoto 	dai->codecs->dai_name = "wm8731-hifi";
116fdbcb3cbSNicolas Ferre 	dai->init = sam9x5_wm8731_init;
117bc567a93SBo Shen 	dai->dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF
1184a8cf938SMark Brown 		| SND_SOC_DAIFMT_CBP_CFP;
119fdbcb3cbSNicolas Ferre 
120fdbcb3cbSNicolas Ferre 	ret = snd_soc_of_parse_card_name(card, "atmel,model");
121fdbcb3cbSNicolas Ferre 	if (ret) {
122fdbcb3cbSNicolas Ferre 		dev_err(&pdev->dev, "atmel,model node missing\n");
123fdbcb3cbSNicolas Ferre 		goto out;
124fdbcb3cbSNicolas Ferre 	}
125fdbcb3cbSNicolas Ferre 
126fdbcb3cbSNicolas Ferre 	ret = snd_soc_of_parse_audio_routing(card, "atmel,audio-routing");
127fdbcb3cbSNicolas Ferre 	if (ret) {
128fdbcb3cbSNicolas Ferre 		dev_err(&pdev->dev, "atmel,audio-routing node missing\n");
129fdbcb3cbSNicolas Ferre 		goto out;
130fdbcb3cbSNicolas Ferre 	}
131fdbcb3cbSNicolas Ferre 
132fdbcb3cbSNicolas Ferre 	codec_np = of_parse_phandle(np, "atmel,audio-codec", 0);
133fdbcb3cbSNicolas Ferre 	if (!codec_np) {
134fdbcb3cbSNicolas Ferre 		dev_err(&pdev->dev, "atmel,audio-codec node missing\n");
135fdbcb3cbSNicolas Ferre 		ret = -EINVAL;
136fdbcb3cbSNicolas Ferre 		goto out;
137fdbcb3cbSNicolas Ferre 	}
138fdbcb3cbSNicolas Ferre 
13935617d82SKuninori Morimoto 	dai->codecs->of_node = codec_np;
140fdbcb3cbSNicolas Ferre 
141fdbcb3cbSNicolas Ferre 	cpu_np = of_parse_phandle(np, "atmel,ssc-controller", 0);
142fdbcb3cbSNicolas Ferre 	if (!cpu_np) {
143fdbcb3cbSNicolas Ferre 		dev_err(&pdev->dev, "atmel,ssc-controller node missing\n");
144fdbcb3cbSNicolas Ferre 		ret = -EINVAL;
145740dc3e8SMiaoqian Lin 		goto out_put_codec_np;
146fdbcb3cbSNicolas Ferre 	}
14735617d82SKuninori Morimoto 	dai->cpus->of_node = cpu_np;
148e1be6aa0SKuninori Morimoto 	dai->platforms->of_node = cpu_np;
149fdbcb3cbSNicolas Ferre 
150fdbcb3cbSNicolas Ferre 	priv->ssc_id = of_alias_get_id(cpu_np, "ssc");
151fdbcb3cbSNicolas Ferre 
152fdbcb3cbSNicolas Ferre 	ret = atmel_ssc_set_audio(priv->ssc_id);
153fdbcb3cbSNicolas Ferre 	if (ret != 0) {
1542efb8a8fSLadislav Michl 		dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n",
155fdbcb3cbSNicolas Ferre 			ret, priv->ssc_id);
156740dc3e8SMiaoqian Lin 		goto out_put_cpu_np;
157fdbcb3cbSNicolas Ferre 	}
158fdbcb3cbSNicolas Ferre 
1596522a848SYang Yingliang 	ret = devm_snd_soc_register_card(&pdev->dev, card);
160fdbcb3cbSNicolas Ferre 	if (ret) {
1612efb8a8fSLadislav Michl 		dev_err(&pdev->dev, "Platform device allocation failed\n");
162fdbcb3cbSNicolas Ferre 		goto out_put_audio;
163fdbcb3cbSNicolas Ferre 	}
164fdbcb3cbSNicolas Ferre 
1652efb8a8fSLadislav Michl 	dev_dbg(&pdev->dev, "%s ok\n", __func__);
166fdbcb3cbSNicolas Ferre 
167740dc3e8SMiaoqian Lin 	goto out_put_cpu_np;
168fdbcb3cbSNicolas Ferre 
169fdbcb3cbSNicolas Ferre out_put_audio:
170fdbcb3cbSNicolas Ferre 	atmel_ssc_put_audio(priv->ssc_id);
171740dc3e8SMiaoqian Lin out_put_cpu_np:
172740dc3e8SMiaoqian Lin 	of_node_put(cpu_np);
173740dc3e8SMiaoqian Lin out_put_codec_np:
174740dc3e8SMiaoqian Lin 	of_node_put(codec_np);
175fdbcb3cbSNicolas Ferre out:
176fdbcb3cbSNicolas Ferre 	return ret;
177fdbcb3cbSNicolas Ferre }
178fdbcb3cbSNicolas Ferre 
sam9x5_wm8731_driver_remove(struct platform_device * pdev)179c79ddc74SUwe Kleine-König static void sam9x5_wm8731_driver_remove(struct platform_device *pdev)
180fdbcb3cbSNicolas Ferre {
181fdbcb3cbSNicolas Ferre 	struct snd_soc_card *card = platform_get_drvdata(pdev);
182fdbcb3cbSNicolas Ferre 	struct sam9x5_drvdata *priv = card->drvdata;
183fdbcb3cbSNicolas Ferre 
184fdbcb3cbSNicolas Ferre 	atmel_ssc_put_audio(priv->ssc_id);
185fdbcb3cbSNicolas Ferre }
186fdbcb3cbSNicolas Ferre 
187fdbcb3cbSNicolas Ferre static const struct of_device_id sam9x5_wm8731_of_match[] = {
188fdbcb3cbSNicolas Ferre 	{ .compatible = "atmel,sam9x5-wm8731-audio", },
189fdbcb3cbSNicolas Ferre 	{},
190fdbcb3cbSNicolas Ferre };
191fdbcb3cbSNicolas Ferre MODULE_DEVICE_TABLE(of, sam9x5_wm8731_of_match);
192fdbcb3cbSNicolas Ferre 
193fdbcb3cbSNicolas Ferre static struct platform_driver sam9x5_wm8731_driver = {
194fdbcb3cbSNicolas Ferre 	.driver = {
195fdbcb3cbSNicolas Ferre 		.name = DRV_NAME,
196fdbcb3cbSNicolas Ferre 		.of_match_table = of_match_ptr(sam9x5_wm8731_of_match),
197fdbcb3cbSNicolas Ferre 	},
198fdbcb3cbSNicolas Ferre 	.probe = sam9x5_wm8731_driver_probe,
199c79ddc74SUwe Kleine-König 	.remove_new = sam9x5_wm8731_driver_remove,
200fdbcb3cbSNicolas Ferre };
201fdbcb3cbSNicolas Ferre module_platform_driver(sam9x5_wm8731_driver);
202fdbcb3cbSNicolas Ferre 
203fdbcb3cbSNicolas Ferre /* Module information */
204fdbcb3cbSNicolas Ferre MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com>");
205fdbcb3cbSNicolas Ferre MODULE_AUTHOR("Richard Genoud <richard.genoud@gmail.com>");
206fdbcb3cbSNicolas Ferre MODULE_DESCRIPTION("ALSA SoC machine driver for AT91SAM9x5 - WM8731");
207fdbcb3cbSNicolas Ferre MODULE_LICENSE("GPL");
208fdbcb3cbSNicolas Ferre MODULE_ALIAS("platform:" DRV_NAME);
209