Commit | Line | Data |
---|---|---|
97fb5e8d | 1 | // SPDX-License-Identifier: GPL-2.0-only |
bdb052e8 SK |
2 | /* |
3 | * Copyright (c) 2015 The Linux Foundation. All rights reserved. | |
bdb052e8 SK |
4 | */ |
5 | ||
6 | #include <linux/device.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/kernel.h> | |
9 | #include <linux/io.h> | |
10 | #include <linux/of.h> | |
11 | #include <linux/clk.h> | |
12 | #include <linux/platform_device.h> | |
13 | #include <sound/pcm.h> | |
14 | #include <sound/pcm_params.h> | |
b47b91c8 | 15 | #include <sound/jack.h> |
bdb052e8 | 16 | #include <sound/soc.h> |
b47b91c8 | 17 | #include <uapi/linux/input-event-codes.h> |
bdb052e8 SK |
18 | #include <dt-bindings/sound/apq8016-lpass.h> |
19 | ||
20 | struct apq8016_sbc_data { | |
21 | void __iomem *mic_iomux; | |
22 | void __iomem *spkr_iomux; | |
b47b91c8 SK |
23 | struct snd_soc_jack jack; |
24 | bool jack_setup; | |
bdb052e8 SK |
25 | struct snd_soc_dai_link dai_link[]; /* dynamically allocated */ |
26 | }; | |
27 | ||
bbedefb9 | 28 | #define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21) |
bdb052e8 SK |
29 | #define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) |
30 | #define MIC_CTRL_TLMM_SCLK_EN BIT(1) | |
31 | #define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) | |
334822a3 | 32 | #define DEFAULT_MCLK_RATE 9600000 |
bdb052e8 SK |
33 | |
34 | static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) | |
35 | { | |
36 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
96e1b9ee | 37 | struct snd_soc_component *component; |
334822a3 | 38 | struct snd_soc_dai_link *dai_link = rtd->dai_link; |
bdb052e8 SK |
39 | struct snd_soc_card *card = rtd->card; |
40 | struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); | |
334822a3 | 41 | int i, rval; |
bdb052e8 SK |
42 | |
43 | switch (cpu_dai->id) { | |
44 | case MI2S_PRIMARY: | |
45 | writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, | |
46 | pdata->spkr_iomux); | |
47 | break; | |
48 | ||
49 | case MI2S_QUATERNARY: | |
50 | /* Configure the Quat MI2S to TLMM */ | |
51 | writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | | |
52 | MIC_CTRL_TLMM_SCLK_EN, | |
53 | pdata->mic_iomux); | |
54 | break; | |
bbedefb9 SK |
55 | case MI2S_TERTIARY: |
56 | writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL | | |
57 | MIC_CTRL_TLMM_SCLK_EN, | |
58 | pdata->mic_iomux); | |
59 | ||
60 | break; | |
bdb052e8 SK |
61 | |
62 | default: | |
63 | dev_err(card->dev, "unsupported cpu dai configuration\n"); | |
334822a3 SK |
64 | return -EINVAL; |
65 | ||
66 | } | |
bdb052e8 | 67 | |
b47b91c8 SK |
68 | if (!pdata->jack_setup) { |
69 | struct snd_jack *jack; | |
70 | ||
71 | rval = snd_soc_card_jack_new(card, "Headset Jack", | |
72 | SND_JACK_HEADSET | | |
73 | SND_JACK_HEADPHONE | | |
74 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | | |
75 | SND_JACK_BTN_2 | SND_JACK_BTN_3 | | |
76 | SND_JACK_BTN_4, | |
77 | &pdata->jack, NULL, 0); | |
78 | ||
79 | if (rval < 0) { | |
80 | dev_err(card->dev, "Unable to add Headphone Jack\n"); | |
81 | return rval; | |
82 | } | |
83 | ||
84 | jack = pdata->jack.jack; | |
85 | ||
5f6d1df8 | 86 | snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); |
b47b91c8 SK |
87 | snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); |
88 | snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); | |
89 | snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); | |
90 | pdata->jack_setup = true; | |
91 | } | |
92 | ||
334822a3 SK |
93 | for (i = 0 ; i < dai_link->num_codecs; i++) { |
94 | struct snd_soc_dai *dai = rtd->codec_dais[i]; | |
95 | ||
96e1b9ee | 96 | component = dai->component; |
334822a3 | 97 | /* Set default mclk for internal codec */ |
96e1b9ee | 98 | rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE, |
334822a3 SK |
99 | SND_SOC_CLOCK_IN); |
100 | if (rval != 0 && rval != -ENOTSUPP) { | |
101 | dev_warn(card->dev, "Failed to set mclk: %d\n", rval); | |
102 | return rval; | |
103 | } | |
96e1b9ee | 104 | rval = snd_soc_component_set_jack(component, &pdata->jack, NULL); |
b47b91c8 SK |
105 | if (rval != 0 && rval != -ENOTSUPP) { |
106 | dev_warn(card->dev, "Failed to set jack: %d\n", rval); | |
107 | return rval; | |
108 | } | |
bdb052e8 SK |
109 | } |
110 | ||
334822a3 | 111 | return 0; |
bdb052e8 SK |
112 | } |
113 | ||
114 | static struct apq8016_sbc_data *apq8016_sbc_parse_of(struct snd_soc_card *card) | |
115 | { | |
116 | struct device *dev = card->dev; | |
117 | struct snd_soc_dai_link *link; | |
118 | struct device_node *np, *codec, *cpu, *node = dev->of_node; | |
119 | struct apq8016_sbc_data *data; | |
98b232ca | 120 | struct snd_soc_dai_link_component *dlc; |
bdb052e8 SK |
121 | int ret, num_links; |
122 | ||
123 | ret = snd_soc_of_parse_card_name(card, "qcom,model"); | |
124 | if (ret) { | |
125 | dev_err(dev, "Error parsing card name: %d\n", ret); | |
126 | return ERR_PTR(ret); | |
127 | } | |
128 | ||
05f9033f SK |
129 | /* DAPM routes */ |
130 | if (of_property_read_bool(node, "qcom,audio-routing")) { | |
131 | ret = snd_soc_of_parse_audio_routing(card, | |
132 | "qcom,audio-routing"); | |
133 | if (ret) | |
134 | return ERR_PTR(ret); | |
135 | } | |
136 | ||
137 | ||
bdb052e8 SK |
138 | /* Populate links */ |
139 | num_links = of_get_child_count(node); | |
140 | ||
141 | /* Allocate the private data and the DAI link array */ | |
0ed2dd03 KC |
142 | data = devm_kzalloc(dev, |
143 | struct_size(data, dai_link, num_links), | |
bdb052e8 SK |
144 | GFP_KERNEL); |
145 | if (!data) | |
146 | return ERR_PTR(-ENOMEM); | |
147 | ||
148 | card->dai_link = &data->dai_link[0]; | |
149 | card->num_links = num_links; | |
150 | ||
151 | link = data->dai_link; | |
152 | ||
8201f11a SG |
153 | for_each_child_of_node(node, np) { |
154 | dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL); | |
155 | if (!dlc) | |
156 | return ERR_PTR(-ENOMEM); | |
98b232ca | 157 | |
8201f11a SG |
158 | link->cpus = &dlc[0]; |
159 | link->platforms = &dlc[1]; | |
291728a5 | 160 | |
8201f11a SG |
161 | link->num_cpus = 1; |
162 | link->num_platforms = 1; | |
98b232ca | 163 | |
bdb052e8 SK |
164 | cpu = of_get_child_by_name(np, "cpu"); |
165 | codec = of_get_child_by_name(np, "codec"); | |
166 | ||
167 | if (!cpu || !codec) { | |
168 | dev_err(dev, "Can't find cpu/codec DT node\n"); | |
8d166720 TI |
169 | ret = -EINVAL; |
170 | goto error; | |
bdb052e8 SK |
171 | } |
172 | ||
98b232ca KM |
173 | link->cpus->of_node = of_parse_phandle(cpu, "sound-dai", 0); |
174 | if (!link->cpus->of_node) { | |
bdb052e8 | 175 | dev_err(card->dev, "error getting cpu phandle\n"); |
8d166720 TI |
176 | ret = -EINVAL; |
177 | goto error; | |
bdb052e8 SK |
178 | } |
179 | ||
98b232ca | 180 | ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name); |
bdb052e8 SK |
181 | if (ret) { |
182 | dev_err(card->dev, "error getting cpu dai name\n"); | |
8d166720 | 183 | goto error; |
bdb052e8 SK |
184 | } |
185 | ||
09065f8b SK |
186 | ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); |
187 | ||
188 | if (ret < 0) { | |
bdb052e8 | 189 | dev_err(card->dev, "error getting codec dai name\n"); |
8d166720 | 190 | goto error; |
bdb052e8 SK |
191 | } |
192 | ||
291728a5 | 193 | link->platforms->of_node = link->cpus->of_node; |
bdb052e8 SK |
194 | ret = of_property_read_string(np, "link-name", &link->name); |
195 | if (ret) { | |
196 | dev_err(card->dev, "error getting codec dai_link name\n"); | |
8d166720 | 197 | goto error; |
bdb052e8 SK |
198 | } |
199 | ||
200 | link->stream_name = link->name; | |
201 | link->init = apq8016_sbc_dai_init; | |
202 | link++; | |
8d166720 TI |
203 | |
204 | of_node_put(cpu); | |
205 | of_node_put(codec); | |
bdb052e8 SK |
206 | } |
207 | ||
208 | return data; | |
8d166720 TI |
209 | |
210 | error: | |
211 | of_node_put(np); | |
212 | of_node_put(cpu); | |
213 | of_node_put(codec); | |
214 | return ERR_PTR(ret); | |
bdb052e8 SK |
215 | } |
216 | ||
1190300d SK |
217 | static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { |
218 | ||
219 | SND_SOC_DAPM_MIC("Handset Mic", NULL), | |
220 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
221 | SND_SOC_DAPM_MIC("Secondary Mic", NULL), | |
222 | SND_SOC_DAPM_MIC("Digital Mic1", NULL), | |
223 | SND_SOC_DAPM_MIC("Digital Mic2", NULL), | |
224 | }; | |
225 | ||
bdb052e8 SK |
226 | static int apq8016_sbc_platform_probe(struct platform_device *pdev) |
227 | { | |
228 | struct device *dev = &pdev->dev; | |
229 | struct snd_soc_card *card; | |
230 | struct apq8016_sbc_data *data; | |
231 | struct resource *res; | |
232 | ||
233 | card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); | |
234 | if (!card) | |
235 | return -ENOMEM; | |
236 | ||
237 | card->dev = dev; | |
1190300d SK |
238 | card->dapm_widgets = apq8016_sbc_dapm_widgets; |
239 | card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets); | |
bdb052e8 SK |
240 | data = apq8016_sbc_parse_of(card); |
241 | if (IS_ERR(data)) { | |
242 | dev_err(&pdev->dev, "Error resolving dai links: %ld\n", | |
243 | PTR_ERR(data)); | |
244 | return PTR_ERR(data); | |
245 | } | |
246 | ||
247 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); | |
248 | data->mic_iomux = devm_ioremap_resource(dev, res); | |
249 | if (IS_ERR(data->mic_iomux)) | |
250 | return PTR_ERR(data->mic_iomux); | |
251 | ||
252 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); | |
253 | data->spkr_iomux = devm_ioremap_resource(dev, res); | |
254 | if (IS_ERR(data->spkr_iomux)) | |
255 | return PTR_ERR(data->spkr_iomux); | |
256 | ||
bdb052e8 SK |
257 | snd_soc_card_set_drvdata(card, data); |
258 | ||
259 | return devm_snd_soc_register_card(&pdev->dev, card); | |
260 | } | |
261 | ||
262 | static const struct of_device_id apq8016_sbc_device_id[] = { | |
263 | { .compatible = "qcom,apq8016-sbc-sndcard" }, | |
264 | {}, | |
265 | }; | |
266 | MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); | |
267 | ||
268 | static struct platform_driver apq8016_sbc_platform_driver = { | |
269 | .driver = { | |
270 | .name = "qcom-apq8016-sbc", | |
271 | .of_match_table = of_match_ptr(apq8016_sbc_device_id), | |
272 | }, | |
273 | .probe = apq8016_sbc_platform_probe, | |
274 | }; | |
275 | module_platform_driver(apq8016_sbc_platform_driver); | |
276 | ||
277 | MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); | |
278 | MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); | |
279 | MODULE_LICENSE("GPL v2"); |