1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018, Linaro Limited. |
3 | // Copyright (c) 2018, The Linux Foundation. All rights reserved. |
4 | |
5 | #include <dt-bindings/sound/qcom,q6afe.h> |
6 | #include <linux/module.h> |
7 | #include <sound/jack.h> |
8 | #include <linux/input-event-codes.h> |
9 | #include "common.h" |
10 | |
11 | static const struct snd_soc_dapm_widget qcom_jack_snd_widgets[] = { |
12 | SND_SOC_DAPM_HP("Headphone Jack" , NULL), |
13 | SND_SOC_DAPM_MIC("Mic Jack" , NULL), |
14 | }; |
15 | |
16 | int qcom_snd_parse_of(struct snd_soc_card *card) |
17 | { |
18 | struct device_node *np; |
19 | struct device_node *codec = NULL; |
20 | struct device_node *platform = NULL; |
21 | struct device_node *cpu = NULL; |
22 | struct device *dev = card->dev; |
23 | struct snd_soc_dai_link *link; |
24 | struct of_phandle_args args; |
25 | struct snd_soc_dai_link_component *dlc; |
26 | int ret, num_links; |
27 | |
28 | ret = snd_soc_of_parse_card_name(card, propname: "model" ); |
29 | if (ret == 0 && !card->name) |
30 | /* Deprecated, only for compatibility with old device trees */ |
31 | ret = snd_soc_of_parse_card_name(card, propname: "qcom,model" ); |
32 | if (ret) { |
33 | dev_err(dev, "Error parsing card name: %d\n" , ret); |
34 | return ret; |
35 | } |
36 | |
37 | if (of_property_read_bool(np: dev->of_node, propname: "widgets" )) { |
38 | ret = snd_soc_of_parse_audio_simple_widgets(card, propname: "widgets" ); |
39 | if (ret) |
40 | return ret; |
41 | } |
42 | |
43 | /* DAPM routes */ |
44 | if (of_property_read_bool(np: dev->of_node, propname: "audio-routing" )) { |
45 | ret = snd_soc_of_parse_audio_routing(card, propname: "audio-routing" ); |
46 | if (ret) |
47 | return ret; |
48 | } |
49 | /* Deprecated, only for compatibility with old device trees */ |
50 | if (of_property_read_bool(np: dev->of_node, propname: "qcom,audio-routing" )) { |
51 | ret = snd_soc_of_parse_audio_routing(card, propname: "qcom,audio-routing" ); |
52 | if (ret) |
53 | return ret; |
54 | } |
55 | |
56 | ret = snd_soc_of_parse_pin_switches(card, prop: "pin-switches" ); |
57 | if (ret) |
58 | return ret; |
59 | |
60 | ret = snd_soc_of_parse_aux_devs(card, propname: "aux-devs" ); |
61 | if (ret) |
62 | return ret; |
63 | |
64 | /* Populate links */ |
65 | num_links = of_get_available_child_count(np: dev->of_node); |
66 | |
67 | /* Allocate the DAI link array */ |
68 | card->dai_link = devm_kcalloc(dev, n: num_links, size: sizeof(*link), GFP_KERNEL); |
69 | if (!card->dai_link) |
70 | return -ENOMEM; |
71 | |
72 | card->num_links = num_links; |
73 | link = card->dai_link; |
74 | |
75 | for_each_available_child_of_node(dev->of_node, np) { |
76 | dlc = devm_kcalloc(dev, n: 2, size: sizeof(*dlc), GFP_KERNEL); |
77 | if (!dlc) { |
78 | ret = -ENOMEM; |
79 | goto err_put_np; |
80 | } |
81 | |
82 | link->cpus = &dlc[0]; |
83 | link->platforms = &dlc[1]; |
84 | |
85 | link->num_cpus = 1; |
86 | link->num_platforms = 1; |
87 | |
88 | ret = of_property_read_string(np, propname: "link-name" , out_string: &link->name); |
89 | if (ret) { |
90 | dev_err(card->dev, "error getting codec dai_link name\n" ); |
91 | goto err_put_np; |
92 | } |
93 | |
94 | cpu = of_get_child_by_name(node: np, name: "cpu" ); |
95 | platform = of_get_child_by_name(node: np, name: "platform" ); |
96 | codec = of_get_child_by_name(node: np, name: "codec" ); |
97 | |
98 | if (!cpu) { |
99 | dev_err(dev, "%s: Can't find cpu DT node\n" , link->name); |
100 | ret = -EINVAL; |
101 | goto err; |
102 | } |
103 | |
104 | ret = snd_soc_of_get_dlc(of_node: cpu, args: &args, dlc: link->cpus, index: 0); |
105 | if (ret) { |
106 | dev_err_probe(dev: card->dev, err: ret, |
107 | fmt: "%s: error getting cpu dai name\n" , link->name); |
108 | goto err; |
109 | } |
110 | |
111 | link->id = args.args[0]; |
112 | |
113 | if (platform) { |
114 | link->platforms->of_node = of_parse_phandle(np: platform, |
115 | phandle_name: "sound-dai" , |
116 | index: 0); |
117 | if (!link->platforms->of_node) { |
118 | dev_err(card->dev, "%s: platform dai not found\n" , link->name); |
119 | ret = -EINVAL; |
120 | goto err; |
121 | } |
122 | } else { |
123 | link->platforms->of_node = link->cpus->of_node; |
124 | } |
125 | |
126 | if (codec) { |
127 | ret = snd_soc_of_get_dai_link_codecs(dev, of_node: codec, dai_link: link); |
128 | if (ret < 0) { |
129 | dev_err_probe(dev: card->dev, err: ret, |
130 | fmt: "%s: codec dai not found\n" , link->name); |
131 | goto err; |
132 | } |
133 | |
134 | if (platform) { |
135 | /* DPCM backend */ |
136 | link->no_pcm = 1; |
137 | link->ignore_pmdown_time = 1; |
138 | } |
139 | } else { |
140 | /* DPCM frontend */ |
141 | link->codecs = &snd_soc_dummy_dlc; |
142 | link->num_codecs = 1; |
143 | link->dynamic = 1; |
144 | } |
145 | |
146 | if (platform || !codec) { |
147 | /* DPCM */ |
148 | snd_soc_dai_link_set_capabilities(dai_link: link); |
149 | link->ignore_suspend = 1; |
150 | link->nonatomic = 1; |
151 | } |
152 | |
153 | link->stream_name = link->name; |
154 | link++; |
155 | |
156 | of_node_put(node: cpu); |
157 | of_node_put(node: codec); |
158 | of_node_put(node: platform); |
159 | } |
160 | |
161 | if (!card->dapm_widgets) { |
162 | card->dapm_widgets = qcom_jack_snd_widgets; |
163 | card->num_dapm_widgets = ARRAY_SIZE(qcom_jack_snd_widgets); |
164 | } |
165 | |
166 | return 0; |
167 | err: |
168 | of_node_put(node: cpu); |
169 | of_node_put(node: codec); |
170 | of_node_put(node: platform); |
171 | err_put_np: |
172 | of_node_put(node: np); |
173 | return ret; |
174 | } |
175 | EXPORT_SYMBOL_GPL(qcom_snd_parse_of); |
176 | |
177 | static struct snd_soc_jack_pin qcom_headset_jack_pins[] = { |
178 | /* Headset */ |
179 | { |
180 | .pin = "Mic Jack" , |
181 | .mask = SND_JACK_MICROPHONE, |
182 | }, |
183 | { |
184 | .pin = "Headphone Jack" , |
185 | .mask = SND_JACK_HEADPHONE, |
186 | }, |
187 | }; |
188 | |
189 | int qcom_snd_wcd_jack_setup(struct snd_soc_pcm_runtime *rtd, |
190 | struct snd_soc_jack *jack, bool *jack_setup) |
191 | { |
192 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
193 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
194 | struct snd_soc_card *card = rtd->card; |
195 | int rval, i; |
196 | |
197 | if (!*jack_setup) { |
198 | rval = snd_soc_card_jack_new_pins(card, id: "Headset Jack" , |
199 | type: SND_JACK_HEADSET | SND_JACK_LINEOUT | |
200 | SND_JACK_MECHANICAL | |
201 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | |
202 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | |
203 | SND_JACK_BTN_4 | SND_JACK_BTN_5, |
204 | jack, pins: qcom_headset_jack_pins, |
205 | ARRAY_SIZE(qcom_headset_jack_pins)); |
206 | |
207 | if (rval < 0) { |
208 | dev_err(card->dev, "Unable to add Headphone Jack\n" ); |
209 | return rval; |
210 | } |
211 | |
212 | snd_jack_set_key(jack: jack->jack, type: SND_JACK_BTN_0, KEY_MEDIA); |
213 | snd_jack_set_key(jack: jack->jack, type: SND_JACK_BTN_1, KEY_VOICECOMMAND); |
214 | snd_jack_set_key(jack: jack->jack, type: SND_JACK_BTN_2, KEY_VOLUMEUP); |
215 | snd_jack_set_key(jack: jack->jack, type: SND_JACK_BTN_3, KEY_VOLUMEDOWN); |
216 | *jack_setup = true; |
217 | } |
218 | |
219 | switch (cpu_dai->id) { |
220 | case TX_CODEC_DMA_TX_0: |
221 | case TX_CODEC_DMA_TX_1: |
222 | case TX_CODEC_DMA_TX_2: |
223 | case TX_CODEC_DMA_TX_3: |
224 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
225 | rval = snd_soc_component_set_jack(component: codec_dai->component, |
226 | jack, NULL); |
227 | if (rval != 0 && rval != -ENOTSUPP) { |
228 | dev_warn(card->dev, "Failed to set jack: %d\n" , rval); |
229 | return rval; |
230 | } |
231 | } |
232 | |
233 | break; |
234 | default: |
235 | break; |
236 | } |
237 | |
238 | |
239 | return 0; |
240 | } |
241 | EXPORT_SYMBOL_GPL(qcom_snd_wcd_jack_setup); |
242 | MODULE_LICENSE("GPL" ); |
243 | |