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