1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * sam9g20_wm8731 -- SoC audio for AT91SAM9G20-based |
4 | * ATMEL AT91SAM9G20ek board. |
5 | * |
6 | * Copyright (C) 2005 SAN People |
7 | * Copyright (C) 2008 Atmel |
8 | * |
9 | * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> |
10 | * |
11 | * Based on ati_b1_wm8731.c by: |
12 | * Frank Mandarino <fmandarino@endrelia.com> |
13 | * Copyright 2006 Endrelia Technologies Inc. |
14 | * Based on corgi.c by: |
15 | * Copyright 2005 Wolfson Microelectronics PLC. |
16 | * Copyright 2005 Openedhand Ltd. |
17 | */ |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/moduleparam.h> |
21 | #include <linux/kernel.h> |
22 | #include <linux/clk.h> |
23 | #include <linux/timer.h> |
24 | #include <linux/interrupt.h> |
25 | #include <linux/platform_device.h> |
26 | #include <linux/of.h> |
27 | |
28 | #include <linux/atmel-ssc.h> |
29 | |
30 | #include <sound/core.h> |
31 | #include <sound/pcm.h> |
32 | #include <sound/pcm_params.h> |
33 | #include <sound/soc.h> |
34 | |
35 | #include "../codecs/wm8731.h" |
36 | #include "atmel-pcm.h" |
37 | #include "atmel_ssc_dai.h" |
38 | |
39 | #define MCLK_RATE 12000000 |
40 | |
41 | /* |
42 | * As shipped the board does not have inputs. However, it is relatively |
43 | * straightforward to modify the board to hook them up so support is left |
44 | * in the driver. |
45 | */ |
46 | #undef ENABLE_MIC_INPUT |
47 | |
48 | static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = { |
49 | SND_SOC_DAPM_MIC("Int Mic" , NULL), |
50 | SND_SOC_DAPM_SPK("Ext Spk" , NULL), |
51 | }; |
52 | |
53 | static const struct snd_soc_dapm_route intercon[] = { |
54 | |
55 | /* speaker connected to LHPOUT/RHPOUT */ |
56 | {"Ext Spk" , NULL, "LHPOUT" }, |
57 | {"Ext Spk" , NULL, "RHPOUT" }, |
58 | |
59 | /* mic is connected to Mic Jack, with WM8731 Mic Bias */ |
60 | {"MICIN" , NULL, "Mic Bias" }, |
61 | {"Mic Bias" , NULL, "Int Mic" }, |
62 | }; |
63 | |
64 | /* |
65 | * Logic for a wm8731 as connected on a at91sam9g20ek board. |
66 | */ |
67 | static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) |
68 | { |
69 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
70 | struct device *dev = rtd->dev; |
71 | int ret; |
72 | |
73 | dev_dbg(dev, "%s called\n" , __func__); |
74 | |
75 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, WM8731_SYSCLK_MCLK, |
76 | MCLK_RATE, SND_SOC_CLOCK_IN); |
77 | if (ret < 0) { |
78 | dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n" , ret); |
79 | return ret; |
80 | } |
81 | |
82 | #ifndef ENABLE_MIC_INPUT |
83 | snd_soc_dapm_nc_pin(dapm: &rtd->card->dapm, pin: "Int Mic" ); |
84 | #endif |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | SND_SOC_DAILINK_DEFS(pcm, |
90 | DAILINK_COMP_ARRAY(COMP_CPU("at91rm9200_ssc.0" )), |
91 | DAILINK_COMP_ARRAY(COMP_CODEC("wm8731.0-001b" , "wm8731-hifi" )), |
92 | DAILINK_COMP_ARRAY(COMP_PLATFORM("at91rm9200_ssc.0" ))); |
93 | |
94 | static struct snd_soc_dai_link at91sam9g20ek_dai = { |
95 | .name = "WM8731" , |
96 | .stream_name = "WM8731 PCM" , |
97 | .init = at91sam9g20ek_wm8731_init, |
98 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
99 | SND_SOC_DAIFMT_CBP_CFP, |
100 | #ifndef ENABLE_MIC_INPUT |
101 | .playback_only = true, |
102 | #endif |
103 | SND_SOC_DAILINK_REG(pcm), |
104 | }; |
105 | |
106 | static struct snd_soc_card snd_soc_at91sam9g20ek = { |
107 | .name = "AT91SAMG20-EK" , |
108 | .owner = THIS_MODULE, |
109 | .dai_link = &at91sam9g20ek_dai, |
110 | .num_links = 1, |
111 | |
112 | .dapm_widgets = at91sam9g20ek_dapm_widgets, |
113 | .num_dapm_widgets = ARRAY_SIZE(at91sam9g20ek_dapm_widgets), |
114 | .dapm_routes = intercon, |
115 | .num_dapm_routes = ARRAY_SIZE(intercon), |
116 | .fully_routed = true, |
117 | }; |
118 | |
119 | static int at91sam9g20ek_audio_probe(struct platform_device *pdev) |
120 | { |
121 | struct device_node *np = pdev->dev.of_node; |
122 | struct device_node *codec_np, *cpu_np; |
123 | struct snd_soc_card *card = &snd_soc_at91sam9g20ek; |
124 | int ret; |
125 | |
126 | if (!np) { |
127 | return -ENODEV; |
128 | } |
129 | |
130 | ret = atmel_ssc_set_audio(ssc_id: 0); |
131 | if (ret) { |
132 | dev_err(&pdev->dev, "ssc channel is not valid: %d\n" , ret); |
133 | return ret; |
134 | } |
135 | |
136 | card->dev = &pdev->dev; |
137 | |
138 | /* Parse device node info */ |
139 | ret = snd_soc_of_parse_card_name(card, propname: "atmel,model" ); |
140 | if (ret) |
141 | goto err; |
142 | |
143 | ret = snd_soc_of_parse_audio_routing(card, |
144 | propname: "atmel,audio-routing" ); |
145 | if (ret) |
146 | goto err; |
147 | |
148 | /* Parse codec info */ |
149 | at91sam9g20ek_dai.codecs->name = NULL; |
150 | codec_np = of_parse_phandle(np, phandle_name: "atmel,audio-codec" , index: 0); |
151 | if (!codec_np) { |
152 | dev_err(&pdev->dev, "codec info missing\n" ); |
153 | ret = -EINVAL; |
154 | goto err; |
155 | } |
156 | at91sam9g20ek_dai.codecs->of_node = codec_np; |
157 | |
158 | /* Parse dai and platform info */ |
159 | at91sam9g20ek_dai.cpus->dai_name = NULL; |
160 | at91sam9g20ek_dai.platforms->name = NULL; |
161 | cpu_np = of_parse_phandle(np, phandle_name: "atmel,ssc-controller" , index: 0); |
162 | if (!cpu_np) { |
163 | dev_err(&pdev->dev, "dai and pcm info missing\n" ); |
164 | of_node_put(node: codec_np); |
165 | ret = -EINVAL; |
166 | goto err; |
167 | } |
168 | at91sam9g20ek_dai.cpus->of_node = cpu_np; |
169 | at91sam9g20ek_dai.platforms->of_node = cpu_np; |
170 | |
171 | of_node_put(node: codec_np); |
172 | of_node_put(node: cpu_np); |
173 | |
174 | ret = snd_soc_register_card(card); |
175 | if (ret) { |
176 | dev_err_probe(dev: &pdev->dev, err: ret, |
177 | fmt: "snd_soc_register_card() failed\n" ); |
178 | goto err; |
179 | } |
180 | |
181 | return 0; |
182 | |
183 | err: |
184 | atmel_ssc_put_audio(ssc_id: 0); |
185 | return ret; |
186 | } |
187 | |
188 | static void at91sam9g20ek_audio_remove(struct platform_device *pdev) |
189 | { |
190 | struct snd_soc_card *card = platform_get_drvdata(pdev); |
191 | |
192 | snd_soc_unregister_card(card); |
193 | atmel_ssc_put_audio(ssc_id: 0); |
194 | } |
195 | |
196 | #ifdef CONFIG_OF |
197 | static const struct of_device_id at91sam9g20ek_wm8731_dt_ids[] = { |
198 | { .compatible = "atmel,at91sam9g20ek-wm8731-audio" , }, |
199 | { } |
200 | }; |
201 | MODULE_DEVICE_TABLE(of, at91sam9g20ek_wm8731_dt_ids); |
202 | #endif |
203 | |
204 | static struct platform_driver at91sam9g20ek_audio_driver = { |
205 | .driver = { |
206 | .name = "at91sam9g20ek-audio" , |
207 | .of_match_table = of_match_ptr(at91sam9g20ek_wm8731_dt_ids), |
208 | }, |
209 | .probe = at91sam9g20ek_audio_probe, |
210 | .remove_new = at91sam9g20ek_audio_remove, |
211 | }; |
212 | |
213 | module_platform_driver(at91sam9g20ek_audio_driver); |
214 | |
215 | /* Module information */ |
216 | MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>" ); |
217 | MODULE_DESCRIPTION("ALSA SoC AT91SAM9G20EK_WM8731" ); |
218 | MODULE_ALIAS("platform:at91sam9g20ek-audio" ); |
219 | MODULE_LICENSE("GPL" ); |
220 | |