1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * ASoC driver for TI DAVINCI EVM platform |
4 | * |
5 | * Author: Vladimir Barinov, <vbarinov@embeddedalley.com> |
6 | * Copyright: (C) 2007 MontaVista Software, Inc., <source@mvista.com> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/moduleparam.h> |
11 | #include <linux/timer.h> |
12 | #include <linux/interrupt.h> |
13 | #include <linux/platform_device.h> |
14 | #include <linux/i2c.h> |
15 | #include <linux/of_platform.h> |
16 | #include <linux/clk.h> |
17 | #include <sound/core.h> |
18 | #include <sound/pcm.h> |
19 | #include <sound/soc.h> |
20 | |
21 | #include <asm/dma.h> |
22 | #include <asm/mach-types.h> |
23 | |
24 | struct snd_soc_card_drvdata_davinci { |
25 | struct clk *mclk; |
26 | unsigned sysclk; |
27 | }; |
28 | |
29 | static int evm_startup(struct snd_pcm_substream *substream) |
30 | { |
31 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
32 | struct snd_soc_card *soc_card = rtd->card; |
33 | struct snd_soc_card_drvdata_davinci *drvdata = |
34 | snd_soc_card_get_drvdata(card: soc_card); |
35 | |
36 | if (drvdata->mclk) |
37 | return clk_prepare_enable(clk: drvdata->mclk); |
38 | |
39 | return 0; |
40 | } |
41 | |
42 | static void evm_shutdown(struct snd_pcm_substream *substream) |
43 | { |
44 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
45 | struct snd_soc_card *soc_card = rtd->card; |
46 | struct snd_soc_card_drvdata_davinci *drvdata = |
47 | snd_soc_card_get_drvdata(card: soc_card); |
48 | |
49 | clk_disable_unprepare(clk: drvdata->mclk); |
50 | } |
51 | |
52 | static int evm_hw_params(struct snd_pcm_substream *substream, |
53 | struct snd_pcm_hw_params *params) |
54 | { |
55 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
56 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
57 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
58 | struct snd_soc_card *soc_card = rtd->card; |
59 | int ret = 0; |
60 | unsigned sysclk = ((struct snd_soc_card_drvdata_davinci *) |
61 | snd_soc_card_get_drvdata(card: soc_card))->sysclk; |
62 | |
63 | /* set the codec system clock */ |
64 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: sysclk, SND_SOC_CLOCK_OUT); |
65 | if (ret < 0) |
66 | return ret; |
67 | |
68 | /* set the CPU system clock */ |
69 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, clk_id: 0, freq: sysclk, SND_SOC_CLOCK_OUT); |
70 | if (ret < 0 && ret != -ENOTSUPP) |
71 | return ret; |
72 | |
73 | return 0; |
74 | } |
75 | |
76 | static const struct snd_soc_ops evm_ops = { |
77 | .startup = evm_startup, |
78 | .shutdown = evm_shutdown, |
79 | .hw_params = evm_hw_params, |
80 | }; |
81 | |
82 | /* davinci-evm machine dapm widgets */ |
83 | static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { |
84 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
85 | SND_SOC_DAPM_LINE("Line Out" , NULL), |
86 | SND_SOC_DAPM_MIC("Mic Jack" , NULL), |
87 | SND_SOC_DAPM_LINE("Line In" , NULL), |
88 | }; |
89 | |
90 | /* davinci-evm machine audio_mapnections to the codec pins */ |
91 | static const struct snd_soc_dapm_route audio_map[] = { |
92 | /* Headphone connected to HPLOUT, HPROUT */ |
93 | {"Headphone Jack" , NULL, "HPLOUT" }, |
94 | {"Headphone Jack" , NULL, "HPROUT" }, |
95 | |
96 | /* Line Out connected to LLOUT, RLOUT */ |
97 | {"Line Out" , NULL, "LLOUT" }, |
98 | {"Line Out" , NULL, "RLOUT" }, |
99 | |
100 | /* Mic connected to (MIC3L | MIC3R) */ |
101 | {"MIC3L" , NULL, "Mic Bias" }, |
102 | {"MIC3R" , NULL, "Mic Bias" }, |
103 | {"Mic Bias" , NULL, "Mic Jack" }, |
104 | |
105 | /* Line In connected to (LINE1L | LINE2L), (LINE1R | LINE2R) */ |
106 | {"LINE1L" , NULL, "Line In" }, |
107 | {"LINE2L" , NULL, "Line In" }, |
108 | {"LINE1R" , NULL, "Line In" }, |
109 | {"LINE2R" , NULL, "Line In" }, |
110 | }; |
111 | |
112 | /* Logic for a aic3x as connected on a davinci-evm */ |
113 | static int evm_aic3x_init(struct snd_soc_pcm_runtime *rtd) |
114 | { |
115 | struct snd_soc_card *card = rtd->card; |
116 | struct device_node *np = card->dev->of_node; |
117 | int ret; |
118 | |
119 | /* Add davinci-evm specific widgets */ |
120 | snd_soc_dapm_new_controls(dapm: &card->dapm, widget: aic3x_dapm_widgets, |
121 | ARRAY_SIZE(aic3x_dapm_widgets)); |
122 | |
123 | if (np) { |
124 | ret = snd_soc_of_parse_audio_routing(card, propname: "ti,audio-routing" ); |
125 | if (ret) |
126 | return ret; |
127 | } else { |
128 | /* Set up davinci-evm specific audio path audio_map */ |
129 | snd_soc_dapm_add_routes(dapm: &card->dapm, route: audio_map, |
130 | ARRAY_SIZE(audio_map)); |
131 | } |
132 | |
133 | /* not connected */ |
134 | snd_soc_dapm_nc_pin(dapm: &card->dapm, pin: "MONO_LOUT" ); |
135 | snd_soc_dapm_nc_pin(dapm: &card->dapm, pin: "HPLCOM" ); |
136 | snd_soc_dapm_nc_pin(dapm: &card->dapm, pin: "HPRCOM" ); |
137 | |
138 | return 0; |
139 | } |
140 | |
141 | /* |
142 | * The struct is used as place holder. It will be completely |
143 | * filled with data from dt node. |
144 | */ |
145 | SND_SOC_DAILINK_DEFS(evm, |
146 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
147 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "tlv320aic3x-hifi" )), |
148 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
149 | |
150 | static struct snd_soc_dai_link evm_dai_tlv320aic3x = { |
151 | .name = "TLV320AIC3X" , |
152 | .stream_name = "AIC3X" , |
153 | .ops = &evm_ops, |
154 | .init = evm_aic3x_init, |
155 | .dai_fmt = SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_CBM_CFM | |
156 | SND_SOC_DAIFMT_IB_NF, |
157 | SND_SOC_DAILINK_REG(evm), |
158 | }; |
159 | |
160 | static const struct of_device_id davinci_evm_dt_ids[] = { |
161 | { |
162 | .compatible = "ti,da830-evm-audio" , |
163 | .data = (void *) &evm_dai_tlv320aic3x, |
164 | }, |
165 | { /* sentinel */ } |
166 | }; |
167 | MODULE_DEVICE_TABLE(of, davinci_evm_dt_ids); |
168 | |
169 | /* davinci evm audio machine driver */ |
170 | static struct snd_soc_card evm_soc_card = { |
171 | .owner = THIS_MODULE, |
172 | .num_links = 1, |
173 | }; |
174 | |
175 | static int davinci_evm_probe(struct platform_device *pdev) |
176 | { |
177 | struct device_node *np = pdev->dev.of_node; |
178 | struct snd_soc_dai_link *dai; |
179 | struct snd_soc_card_drvdata_davinci *drvdata = NULL; |
180 | struct clk *mclk; |
181 | int ret = 0; |
182 | |
183 | dai = (struct snd_soc_dai_link *) device_get_match_data(dev: &pdev->dev); |
184 | if (!dai) { |
185 | dev_err(&pdev->dev, "Error: No device match found\n" ); |
186 | return -ENODEV; |
187 | } |
188 | |
189 | evm_soc_card.dai_link = dai; |
190 | |
191 | dai->codecs->of_node = of_parse_phandle(np, phandle_name: "ti,audio-codec" , index: 0); |
192 | if (!dai->codecs->of_node) |
193 | return -EINVAL; |
194 | |
195 | dai->cpus->of_node = of_parse_phandle(np, phandle_name: "ti,mcasp-controller" , index: 0); |
196 | if (!dai->cpus->of_node) |
197 | return -EINVAL; |
198 | |
199 | dai->platforms->of_node = dai->cpus->of_node; |
200 | |
201 | evm_soc_card.dev = &pdev->dev; |
202 | ret = snd_soc_of_parse_card_name(card: &evm_soc_card, propname: "ti,model" ); |
203 | if (ret) |
204 | return ret; |
205 | |
206 | mclk = devm_clk_get(dev: &pdev->dev, id: "mclk" ); |
207 | if (PTR_ERR(ptr: mclk) == -EPROBE_DEFER) { |
208 | return -EPROBE_DEFER; |
209 | } else if (IS_ERR(ptr: mclk)) { |
210 | dev_dbg(&pdev->dev, "mclk not found.\n" ); |
211 | mclk = NULL; |
212 | } |
213 | |
214 | drvdata = devm_kzalloc(dev: &pdev->dev, size: sizeof(*drvdata), GFP_KERNEL); |
215 | if (!drvdata) |
216 | return -ENOMEM; |
217 | |
218 | drvdata->mclk = mclk; |
219 | |
220 | ret = of_property_read_u32(np, propname: "ti,codec-clock-rate" , out_value: &drvdata->sysclk); |
221 | |
222 | if (ret < 0) { |
223 | if (!drvdata->mclk) { |
224 | dev_err(&pdev->dev, |
225 | "No clock or clock rate defined.\n" ); |
226 | return -EINVAL; |
227 | } |
228 | drvdata->sysclk = clk_get_rate(clk: drvdata->mclk); |
229 | } else if (drvdata->mclk) { |
230 | unsigned int requestd_rate = drvdata->sysclk; |
231 | clk_set_rate(clk: drvdata->mclk, rate: drvdata->sysclk); |
232 | drvdata->sysclk = clk_get_rate(clk: drvdata->mclk); |
233 | if (drvdata->sysclk != requestd_rate) |
234 | dev_warn(&pdev->dev, |
235 | "Could not get requested rate %u using %u.\n" , |
236 | requestd_rate, drvdata->sysclk); |
237 | } |
238 | |
239 | snd_soc_card_set_drvdata(card: &evm_soc_card, data: drvdata); |
240 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card: &evm_soc_card); |
241 | |
242 | if (ret) |
243 | dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n" , ret); |
244 | |
245 | return ret; |
246 | } |
247 | |
248 | static struct platform_driver davinci_evm_driver = { |
249 | .probe = davinci_evm_probe, |
250 | .driver = { |
251 | .name = "davinci_evm" , |
252 | .pm = &snd_soc_pm_ops, |
253 | .of_match_table = davinci_evm_dt_ids, |
254 | }, |
255 | }; |
256 | |
257 | module_platform_driver(davinci_evm_driver); |
258 | |
259 | MODULE_AUTHOR("Vladimir Barinov" ); |
260 | MODULE_DESCRIPTION("TI DAVINCI EVM ASoC driver" ); |
261 | MODULE_LICENSE("GPL" ); |
262 | |