Commit | Line | Data |
---|---|---|
eb50fd3a | 1 | // SPDX-License-Identifier: GPL-2.0 |
5793227c VA |
2 | /* |
3 | * Greybus audio driver | |
4 | * Copyright 2015 Google Inc. | |
5 | * Copyright 2015 Linaro Ltd. | |
5793227c VA |
6 | */ |
7 | #include <linux/kernel.h> | |
8 | #include <linux/module.h> | |
9 | #include <sound/soc.h> | |
10 | #include <sound/pcm_params.h> | |
5793227c VA |
11 | |
12 | #include "audio_codec.h" | |
13 | #include "audio_apbridgea.h" | |
14 | #include "audio_manager.h" | |
15 | ||
5793227c VA |
16 | /* |
17 | * gb_snd management functions | |
18 | */ | |
19 | ||
64a7e2cc | 20 | static int gbaudio_request_jack(struct gbaudio_module_info *module, |
8478c35a | 21 | struct gb_audio_jack_event_request *req) |
5793227c | 22 | { |
cec89df4 | 23 | int report; |
ac40b4d1 VA |
24 | struct snd_jack *jack = module->headset.jack.jack; |
25 | struct snd_jack *btn_jack = module->button.jack.jack; | |
cec89df4 VA |
26 | |
27 | if (!jack) { | |
28 | dev_err_ratelimited(module->dev, | |
8478c35a CS |
29 | "Invalid jack event received:type: %u, event: %u\n", |
30 | req->jack_attribute, req->event); | |
cec89df4 VA |
31 | return -EINVAL; |
32 | } | |
64a7e2cc | 33 | |
c6722ab5 VA |
34 | dev_warn_ratelimited(module->dev, |
35 | "Jack Event received: type: %u, event: %u\n", | |
36 | req->jack_attribute, req->event); | |
64a7e2cc | 37 | |
64a7e2cc VA |
38 | if (req->event == GB_AUDIO_JACK_EVENT_REMOVAL) { |
39 | module->jack_type = 0; | |
cec89df4 | 40 | if (btn_jack && module->button_status) { |
ac40b4d1 | 41 | snd_soc_jack_report(&module->button.jack, 0, |
847175e8 | 42 | module->button_mask); |
cec89df4 VA |
43 | module->button_status = 0; |
44 | } | |
ac40b4d1 | 45 | snd_soc_jack_report(&module->headset.jack, 0, |
847175e8 | 46 | module->jack_mask); |
64a7e2cc VA |
47 | return 0; |
48 | } | |
49 | ||
cec89df4 VA |
50 | report = req->jack_attribute & module->jack_mask; |
51 | if (!report) { | |
52 | dev_err_ratelimited(module->dev, | |
8478c35a CS |
53 | "Invalid jack event received:type: %u, event: %u\n", |
54 | req->jack_attribute, req->event); | |
cec89df4 VA |
55 | return -EINVAL; |
56 | } | |
57 | ||
64a7e2cc | 58 | if (module->jack_type) |
c6722ab5 VA |
59 | dev_warn_ratelimited(module->dev, |
60 | "Modifying jack from %d to %d\n", | |
61 | module->jack_type, report); | |
64a7e2cc VA |
62 | |
63 | module->jack_type = report; | |
ac40b4d1 | 64 | snd_soc_jack_report(&module->headset.jack, report, module->jack_mask); |
64a7e2cc VA |
65 | |
66 | return 0; | |
67 | } | |
68 | ||
69 | static int gbaudio_request_button(struct gbaudio_module_info *module, | |
70 | struct gb_audio_button_event_request *req) | |
71 | { | |
72 | int soc_button_id, report; | |
ac40b4d1 | 73 | struct snd_jack *btn_jack = module->button.jack.jack; |
cec89df4 VA |
74 | |
75 | if (!btn_jack) { | |
76 | dev_err_ratelimited(module->dev, | |
8478c35a CS |
77 | "Invalid button event received:type: %u, event: %u\n", |
78 | req->button_id, req->event); | |
cec89df4 VA |
79 | return -EINVAL; |
80 | } | |
64a7e2cc | 81 | |
c6722ab5 VA |
82 | dev_warn_ratelimited(module->dev, |
83 | "Button Event received: id: %u, event: %u\n", | |
84 | req->button_id, req->event); | |
64a7e2cc VA |
85 | |
86 | /* currently supports 4 buttons only */ | |
64a7e2cc | 87 | if (!module->jack_type) { |
c6722ab5 VA |
88 | dev_err_ratelimited(module->dev, |
89 | "Jack not present. Bogus event!!\n"); | |
64a7e2cc VA |
90 | return -EINVAL; |
91 | } | |
92 | ||
847175e8 | 93 | report = module->button_status & module->button_mask; |
6a57ddc9 | 94 | soc_button_id = 0; |
64a7e2cc VA |
95 | |
96 | switch (req->button_id) { | |
97 | case 1: | |
6a57ddc9 | 98 | soc_button_id = SND_JACK_BTN_0 & module->button_mask; |
64a7e2cc VA |
99 | break; |
100 | ||
101 | case 2: | |
6a57ddc9 | 102 | soc_button_id = SND_JACK_BTN_1 & module->button_mask; |
64a7e2cc VA |
103 | break; |
104 | ||
105 | case 3: | |
6a57ddc9 | 106 | soc_button_id = SND_JACK_BTN_2 & module->button_mask; |
64a7e2cc | 107 | break; |
5793227c | 108 | |
64a7e2cc | 109 | case 4: |
6a57ddc9 | 110 | soc_button_id = SND_JACK_BTN_3 & module->button_mask; |
64a7e2cc | 111 | break; |
6a57ddc9 VA |
112 | } |
113 | ||
114 | if (!soc_button_id) { | |
c6722ab5 VA |
115 | dev_err_ratelimited(module->dev, |
116 | "Invalid button request received\n"); | |
64a7e2cc VA |
117 | return -EINVAL; |
118 | } | |
119 | ||
120 | if (req->event == GB_AUDIO_BUTTON_EVENT_PRESS) | |
121 | report = report | soc_button_id; | |
122 | else | |
123 | report = report & ~soc_button_id; | |
124 | ||
125 | module->button_status = report; | |
126 | ||
ac40b4d1 | 127 | snd_soc_jack_report(&module->button.jack, report, module->button_mask); |
64a7e2cc VA |
128 | |
129 | return 0; | |
130 | } | |
131 | ||
132 | static int gbaudio_request_stream(struct gbaudio_module_info *module, | |
133 | struct gb_audio_streaming_event_request *req) | |
134 | { | |
135 | dev_warn(module->dev, "Audio Event received: cport: %u, event: %u\n", | |
055fb9ce | 136 | le16_to_cpu(req->data_cport), req->event); |
5793227c VA |
137 | |
138 | return 0; | |
139 | } | |
140 | ||
64a7e2cc VA |
141 | static int gbaudio_codec_request_handler(struct gb_operation *op) |
142 | { | |
143 | struct gb_connection *connection = op->connection; | |
144 | struct gbaudio_module_info *module = | |
145 | greybus_get_drvdata(connection->bundle); | |
146 | struct gb_operation_msg_hdr *header = op->request->header; | |
147 | struct gb_audio_streaming_event_request *stream_req; | |
148 | struct gb_audio_jack_event_request *jack_req; | |
149 | struct gb_audio_button_event_request *button_req; | |
150 | int ret; | |
151 | ||
152 | switch (header->type) { | |
153 | case GB_AUDIO_TYPE_STREAMING_EVENT: | |
154 | stream_req = op->request->payload; | |
155 | ret = gbaudio_request_stream(module, stream_req); | |
156 | break; | |
157 | ||
158 | case GB_AUDIO_TYPE_JACK_EVENT: | |
159 | jack_req = op->request->payload; | |
160 | ret = gbaudio_request_jack(module, jack_req); | |
161 | break; | |
162 | ||
163 | case GB_AUDIO_TYPE_BUTTON_EVENT: | |
164 | button_req = op->request->payload; | |
165 | ret = gbaudio_request_button(module, button_req); | |
166 | break; | |
167 | ||
168 | default: | |
c6722ab5 VA |
169 | dev_err_ratelimited(&connection->bundle->dev, |
170 | "Invalid Audio Event received\n"); | |
64a7e2cc VA |
171 | return -EINVAL; |
172 | } | |
173 | ||
174 | return ret; | |
175 | } | |
176 | ||
5793227c | 177 | static int gb_audio_add_mgmt_connection(struct gbaudio_module_info *gbmodule, |
383313b8 TK |
178 | struct greybus_descriptor_cport *cport_desc, |
179 | struct gb_bundle *bundle) | |
5793227c VA |
180 | { |
181 | struct gb_connection *connection; | |
182 | ||
183 | /* Management Cport */ | |
184 | if (gbmodule->mgmt_connection) { | |
185 | dev_err(&bundle->dev, | |
186 | "Can't have multiple Management connections\n"); | |
187 | return -ENODEV; | |
188 | } | |
189 | ||
190 | connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id), | |
191 | gbaudio_codec_request_handler); | |
192 | if (IS_ERR(connection)) | |
193 | return PTR_ERR(connection); | |
194 | ||
0634874a | 195 | greybus_set_drvdata(bundle, gbmodule); |
5793227c VA |
196 | gbmodule->mgmt_connection = connection; |
197 | ||
198 | return 0; | |
199 | } | |
200 | ||
201 | static int gb_audio_add_data_connection(struct gbaudio_module_info *gbmodule, | |
383313b8 TK |
202 | struct greybus_descriptor_cport *cport_desc, |
203 | struct gb_bundle *bundle) | |
5793227c VA |
204 | { |
205 | struct gb_connection *connection; | |
206 | struct gbaudio_data_connection *dai; | |
207 | ||
208 | dai = devm_kzalloc(gbmodule->dev, sizeof(*dai), GFP_KERNEL); | |
3282998c | 209 | if (!dai) |
5793227c | 210 | return -ENOMEM; |
5793227c | 211 | |
a8af1631 | 212 | connection = gb_connection_create_offloaded(bundle, |
8478c35a CS |
213 | le16_to_cpu(cport_desc->id), |
214 | GB_CONNECTION_FLAG_CSD); | |
5793227c VA |
215 | if (IS_ERR(connection)) { |
216 | devm_kfree(gbmodule->dev, dai); | |
217 | return PTR_ERR(connection); | |
218 | } | |
219 | ||
0634874a | 220 | greybus_set_drvdata(bundle, gbmodule); |
b7e7dc00 | 221 | dai->id = 0; |
50a390ad | 222 | dai->data_cport = cpu_to_le16(connection->intf_cport_id); |
5793227c VA |
223 | dai->connection = connection; |
224 | list_add(&dai->list, &gbmodule->data_list); | |
225 | ||
226 | return 0; | |
227 | } | |
228 | ||
229 | /* | |
230 | * This is the basic hook get things initialized and registered w/ gb | |
231 | */ | |
232 | ||
233 | static int gb_audio_probe(struct gb_bundle *bundle, | |
234 | const struct greybus_bundle_id *id) | |
235 | { | |
236 | struct device *dev = &bundle->dev; | |
237 | struct gbaudio_module_info *gbmodule; | |
238 | struct greybus_descriptor_cport *cport_desc; | |
239 | struct gb_audio_manager_module_descriptor desc; | |
240 | struct gbaudio_data_connection *dai, *_dai; | |
241 | int ret, i; | |
242 | struct gb_audio_topology *topology; | |
243 | ||
5793227c VA |
244 | /* There should be at least one Management and one Data cport */ |
245 | if (bundle->num_cports < 2) | |
246 | return -ENODEV; | |
247 | ||
5793227c VA |
248 | /* |
249 | * There can be only one Management connection and any number of data | |
250 | * connections. | |
251 | */ | |
252 | gbmodule = devm_kzalloc(dev, sizeof(*gbmodule), GFP_KERNEL); | |
e6ab7a15 | 253 | if (!gbmodule) |
5793227c | 254 | return -ENOMEM; |
5793227c VA |
255 | |
256 | gbmodule->num_data_connections = bundle->num_cports - 1; | |
5793227c VA |
257 | INIT_LIST_HEAD(&gbmodule->data_list); |
258 | INIT_LIST_HEAD(&gbmodule->widget_list); | |
259 | INIT_LIST_HEAD(&gbmodule->ctl_list); | |
260 | INIT_LIST_HEAD(&gbmodule->widget_ctl_list); | |
ac40b4d1 | 261 | INIT_LIST_HEAD(&gbmodule->jack_list); |
5793227c | 262 | gbmodule->dev = dev; |
1e37da45 | 263 | snprintf(gbmodule->name, sizeof(gbmodule->name), "%s.%s", dev->driver->name, |
5793227c VA |
264 | dev_name(dev)); |
265 | greybus_set_drvdata(bundle, gbmodule); | |
266 | ||
267 | /* Create all connections */ | |
268 | for (i = 0; i < bundle->num_cports; i++) { | |
269 | cport_desc = &bundle->cport_desc[i]; | |
270 | ||
271 | switch (cport_desc->protocol_id) { | |
272 | case GREYBUS_PROTOCOL_AUDIO_MGMT: | |
273 | ret = gb_audio_add_mgmt_connection(gbmodule, cport_desc, | |
274 | bundle); | |
275 | if (ret) | |
276 | goto destroy_connections; | |
277 | break; | |
278 | case GREYBUS_PROTOCOL_AUDIO_DATA: | |
279 | ret = gb_audio_add_data_connection(gbmodule, cport_desc, | |
280 | bundle); | |
281 | if (ret) | |
282 | goto destroy_connections; | |
283 | break; | |
284 | default: | |
285 | dev_err(dev, "Unsupported protocol: 0x%02x\n", | |
286 | cport_desc->protocol_id); | |
287 | ret = -ENODEV; | |
288 | goto destroy_connections; | |
289 | } | |
290 | } | |
291 | ||
292 | /* There must be a management cport */ | |
293 | if (!gbmodule->mgmt_connection) { | |
294 | ret = -EINVAL; | |
295 | dev_err(dev, "Missing management connection\n"); | |
296 | goto destroy_connections; | |
297 | } | |
298 | ||
299 | /* Initialize management connection */ | |
300 | ret = gb_connection_enable(gbmodule->mgmt_connection); | |
301 | if (ret) { | |
302 | dev_err(dev, "%d: Error while enabling mgmt connection\n", ret); | |
303 | goto destroy_connections; | |
304 | } | |
305 | gbmodule->dev_id = gbmodule->mgmt_connection->intf->interface_id; | |
306 | ||
307 | /* | |
308 | * FIXME: malloc for topology happens via audio_gb driver | |
309 | * should be done within codec driver itself | |
310 | */ | |
311 | ret = gb_audio_gb_get_topology(gbmodule->mgmt_connection, &topology); | |
312 | if (ret) { | |
313 | dev_err(dev, "%d:Error while fetching topology\n", ret); | |
314 | goto disable_connection; | |
315 | } | |
316 | ||
317 | /* process topology data */ | |
318 | ret = gbaudio_tplg_parse_data(gbmodule, topology); | |
319 | if (ret) { | |
320 | dev_err(dev, "%d:Error while parsing topology data\n", | |
8478c35a | 321 | ret); |
5793227c VA |
322 | goto free_topology; |
323 | } | |
324 | gbmodule->topology = topology; | |
325 | ||
5793227c VA |
326 | /* Initialize data connections */ |
327 | list_for_each_entry(dai, &gbmodule->data_list, list) { | |
328 | ret = gb_connection_enable(dai->connection); | |
bb9986ef VA |
329 | if (ret) { |
330 | dev_err(dev, | |
331 | "%d:Error while enabling %d:data connection\n", | |
50a390ad | 332 | ret, le16_to_cpu(dai->data_cport)); |
5793227c | 333 | goto disable_data_connection; |
bb9986ef | 334 | } |
5793227c | 335 | } |
5793227c | 336 | |
bb9986ef VA |
337 | /* register module with gbcodec */ |
338 | ret = gbaudio_register_module(gbmodule); | |
339 | if (ret) | |
340 | goto disable_data_connection; | |
341 | ||
5793227c VA |
342 | /* inform above layer for uevent */ |
343 | dev_dbg(dev, "Inform set_event:%d to above layer\n", 1); | |
344 | /* prepare for the audio manager */ | |
1e37da45 | 345 | strscpy(desc.name, gbmodule->name, sizeof(desc.name)); |
5793227c VA |
346 | desc.vid = 2; /* todo */ |
347 | desc.pid = 3; /* todo */ | |
d0af1bd5 | 348 | desc.intf_id = gbmodule->dev_id; |
89de9a06 VA |
349 | desc.op_devices = gbmodule->op_devices; |
350 | desc.ip_devices = gbmodule->ip_devices; | |
5793227c VA |
351 | gbmodule->manager_id = gb_audio_manager_add(&desc); |
352 | ||
353 | dev_dbg(dev, "Add GB Audio device:%s\n", gbmodule->name); | |
5793227c | 354 | |
6ba7fad4 DL |
355 | gb_pm_runtime_put_autosuspend(bundle); |
356 | ||
5793227c VA |
357 | return 0; |
358 | ||
359 | disable_data_connection: | |
360 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) | |
361 | gb_connection_disable(dai->connection); | |
5793227c VA |
362 | gbaudio_tplg_release(gbmodule); |
363 | gbmodule->topology = NULL; | |
364 | ||
365 | free_topology: | |
366 | kfree(topology); | |
367 | ||
368 | disable_connection: | |
369 | gb_connection_disable(gbmodule->mgmt_connection); | |
370 | ||
371 | destroy_connections: | |
372 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) { | |
373 | gb_connection_destroy(dai->connection); | |
374 | list_del(&dai->list); | |
375 | devm_kfree(dev, dai); | |
376 | } | |
377 | ||
378 | if (gbmodule->mgmt_connection) | |
379 | gb_connection_destroy(gbmodule->mgmt_connection); | |
380 | ||
381 | devm_kfree(dev, gbmodule); | |
5793227c VA |
382 | |
383 | return ret; | |
384 | } | |
385 | ||
386 | static void gb_audio_disconnect(struct gb_bundle *bundle) | |
387 | { | |
388 | struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle); | |
389 | struct gbaudio_data_connection *dai, *_dai; | |
390 | ||
6ba7fad4 DL |
391 | gb_pm_runtime_get_sync(bundle); |
392 | ||
698282f6 VA |
393 | /* cleanup module related resources first */ |
394 | gbaudio_unregister_module(gbmodule); | |
395 | ||
5793227c VA |
396 | /* inform uevent to above layers */ |
397 | gb_audio_manager_remove(gbmodule->manager_id); | |
398 | ||
399 | gbaudio_tplg_release(gbmodule); | |
5793227c | 400 | kfree(gbmodule->topology); |
37f07c0c | 401 | gbmodule->topology = NULL; |
5793227c VA |
402 | gb_connection_disable(gbmodule->mgmt_connection); |
403 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) { | |
404 | gb_connection_disable(dai->connection); | |
405 | gb_connection_destroy(dai->connection); | |
406 | list_del(&dai->list); | |
407 | devm_kfree(gbmodule->dev, dai); | |
408 | } | |
409 | gb_connection_destroy(gbmodule->mgmt_connection); | |
410 | gbmodule->mgmt_connection = NULL; | |
411 | ||
412 | devm_kfree(&bundle->dev, gbmodule); | |
5793227c VA |
413 | } |
414 | ||
415 | static const struct greybus_bundle_id gb_audio_id_table[] = { | |
416 | { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_AUDIO) }, | |
417 | { } | |
418 | }; | |
419 | MODULE_DEVICE_TABLE(greybus, gb_audio_id_table); | |
420 | ||
948c6227 | 421 | #ifdef CONFIG_PM |
6ba7fad4 DL |
422 | static int gb_audio_suspend(struct device *dev) |
423 | { | |
424 | struct gb_bundle *bundle = to_gb_bundle(dev); | |
425 | struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle); | |
426 | struct gbaudio_data_connection *dai; | |
427 | ||
428 | list_for_each_entry(dai, &gbmodule->data_list, list) | |
429 | gb_connection_disable(dai->connection); | |
430 | ||
431 | gb_connection_disable(gbmodule->mgmt_connection); | |
432 | ||
433 | return 0; | |
434 | } | |
435 | ||
436 | static int gb_audio_resume(struct device *dev) | |
437 | { | |
438 | struct gb_bundle *bundle = to_gb_bundle(dev); | |
439 | struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle); | |
440 | struct gbaudio_data_connection *dai; | |
441 | int ret; | |
442 | ||
443 | ret = gb_connection_enable(gbmodule->mgmt_connection); | |
444 | if (ret) { | |
445 | dev_err(dev, "%d:Error while enabling mgmt connection\n", ret); | |
446 | return ret; | |
447 | } | |
448 | ||
449 | list_for_each_entry(dai, &gbmodule->data_list, list) { | |
450 | ret = gb_connection_enable(dai->connection); | |
451 | if (ret) { | |
452 | dev_err(dev, | |
453 | "%d:Error while enabling %d:data connection\n", | |
50a390ad | 454 | ret, le16_to_cpu(dai->data_cport)); |
6ba7fad4 DL |
455 | return ret; |
456 | } | |
457 | } | |
458 | ||
459 | return 0; | |
460 | } | |
461 | #endif | |
462 | ||
463 | static const struct dev_pm_ops gb_audio_pm_ops = { | |
464 | SET_RUNTIME_PM_OPS(gb_audio_suspend, gb_audio_resume, NULL) | |
465 | }; | |
466 | ||
5793227c VA |
467 | static struct greybus_driver gb_audio_driver = { |
468 | .name = "gb-audio", | |
469 | .probe = gb_audio_probe, | |
470 | .disconnect = gb_audio_disconnect, | |
471 | .id_table = gb_audio_id_table, | |
6ba7fad4 | 472 | .driver.pm = &gb_audio_pm_ops, |
5793227c VA |
473 | }; |
474 | module_greybus_driver(gb_audio_driver); | |
475 | ||
476 | MODULE_DESCRIPTION("Greybus Audio module driver"); | |
477 | MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>"); | |
478 | MODULE_LICENSE("GPL v2"); | |
479 | MODULE_ALIAS("platform:gbaudio-module"); |