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