1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Greybus audio driver |
4 | * Copyright 2015 Google Inc. |
5 | * Copyright 2015 Linaro Ltd. |
6 | */ |
7 | #include <linux/kernel.h> |
8 | #include <linux/module.h> |
9 | #include <sound/soc.h> |
10 | #include <sound/pcm_params.h> |
11 | |
12 | #include "audio_codec.h" |
13 | #include "audio_apbridgea.h" |
14 | #include "audio_manager.h" |
15 | |
16 | /* |
17 | * gb_snd management functions |
18 | */ |
19 | |
20 | static int gbaudio_request_jack(struct gbaudio_module_info *module, |
21 | struct gb_audio_jack_event_request *req) |
22 | { |
23 | int report; |
24 | struct snd_jack *jack = module->headset.jack.jack; |
25 | struct snd_jack *btn_jack = module->button.jack.jack; |
26 | |
27 | if (!jack) { |
28 | dev_err_ratelimited(module->dev, |
29 | "Invalid jack event received:type: %u, event: %u\n" , |
30 | req->jack_attribute, req->event); |
31 | return -EINVAL; |
32 | } |
33 | |
34 | dev_warn_ratelimited(module->dev, |
35 | "Jack Event received: type: %u, event: %u\n" , |
36 | req->jack_attribute, req->event); |
37 | |
38 | if (req->event == GB_AUDIO_JACK_EVENT_REMOVAL) { |
39 | module->jack_type = 0; |
40 | if (btn_jack && module->button_status) { |
41 | snd_soc_jack_report(jack: &module->button.jack, status: 0, |
42 | mask: module->button_mask); |
43 | module->button_status = 0; |
44 | } |
45 | snd_soc_jack_report(jack: &module->headset.jack, status: 0, |
46 | mask: module->jack_mask); |
47 | return 0; |
48 | } |
49 | |
50 | report = req->jack_attribute & module->jack_mask; |
51 | if (!report) { |
52 | dev_err_ratelimited(module->dev, |
53 | "Invalid jack event received:type: %u, event: %u\n" , |
54 | req->jack_attribute, req->event); |
55 | return -EINVAL; |
56 | } |
57 | |
58 | if (module->jack_type) |
59 | dev_warn_ratelimited(module->dev, |
60 | "Modifying jack from %d to %d\n" , |
61 | module->jack_type, report); |
62 | |
63 | module->jack_type = report; |
64 | snd_soc_jack_report(jack: &module->headset.jack, status: report, mask: module->jack_mask); |
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; |
73 | struct snd_jack *btn_jack = module->button.jack.jack; |
74 | |
75 | if (!btn_jack) { |
76 | dev_err_ratelimited(module->dev, |
77 | "Invalid button event received:type: %u, event: %u\n" , |
78 | req->button_id, req->event); |
79 | return -EINVAL; |
80 | } |
81 | |
82 | dev_warn_ratelimited(module->dev, |
83 | "Button Event received: id: %u, event: %u\n" , |
84 | req->button_id, req->event); |
85 | |
86 | /* currently supports 4 buttons only */ |
87 | if (!module->jack_type) { |
88 | dev_err_ratelimited(module->dev, |
89 | "Jack not present. Bogus event!!\n" ); |
90 | return -EINVAL; |
91 | } |
92 | |
93 | report = module->button_status & module->button_mask; |
94 | soc_button_id = 0; |
95 | |
96 | switch (req->button_id) { |
97 | case 1: |
98 | soc_button_id = SND_JACK_BTN_0 & module->button_mask; |
99 | break; |
100 | |
101 | case 2: |
102 | soc_button_id = SND_JACK_BTN_1 & module->button_mask; |
103 | break; |
104 | |
105 | case 3: |
106 | soc_button_id = SND_JACK_BTN_2 & module->button_mask; |
107 | break; |
108 | |
109 | case 4: |
110 | soc_button_id = SND_JACK_BTN_3 & module->button_mask; |
111 | break; |
112 | } |
113 | |
114 | if (!soc_button_id) { |
115 | dev_err_ratelimited(module->dev, |
116 | "Invalid button request received\n" ); |
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 | |
127 | snd_soc_jack_report(jack: &module->button.jack, status: report, mask: module->button_mask); |
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" , |
136 | le16_to_cpu(req->data_cport), req->event); |
137 | |
138 | return 0; |
139 | } |
140 | |
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(bundle: connection->bundle); |
146 | struct gb_operation_msg_hdr * = 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, req: stream_req); |
156 | break; |
157 | |
158 | case GB_AUDIO_TYPE_JACK_EVENT: |
159 | jack_req = op->request->payload; |
160 | ret = gbaudio_request_jack(module, req: jack_req); |
161 | break; |
162 | |
163 | case GB_AUDIO_TYPE_BUTTON_EVENT: |
164 | button_req = op->request->payload; |
165 | ret = gbaudio_request_button(module, req: button_req); |
166 | break; |
167 | |
168 | default: |
169 | dev_err_ratelimited(&connection->bundle->dev, |
170 | "Invalid Audio Event received\n" ); |
171 | return -EINVAL; |
172 | } |
173 | |
174 | return ret; |
175 | } |
176 | |
177 | static int gb_audio_add_mgmt_connection(struct gbaudio_module_info *gbmodule, |
178 | struct greybus_descriptor_cport *cport_desc, |
179 | struct gb_bundle *bundle) |
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 | handler: gbaudio_codec_request_handler); |
192 | if (IS_ERR(ptr: connection)) |
193 | return PTR_ERR(ptr: connection); |
194 | |
195 | greybus_set_drvdata(bundle, data: gbmodule); |
196 | gbmodule->mgmt_connection = connection; |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | static int gb_audio_add_data_connection(struct gbaudio_module_info *gbmodule, |
202 | struct greybus_descriptor_cport *cport_desc, |
203 | struct gb_bundle *bundle) |
204 | { |
205 | struct gb_connection *connection; |
206 | struct gbaudio_data_connection *dai; |
207 | |
208 | dai = devm_kzalloc(dev: gbmodule->dev, size: sizeof(*dai), GFP_KERNEL); |
209 | if (!dai) |
210 | return -ENOMEM; |
211 | |
212 | connection = gb_connection_create_offloaded(bundle, |
213 | le16_to_cpu(cport_desc->id), |
214 | GB_CONNECTION_FLAG_CSD); |
215 | if (IS_ERR(ptr: connection)) { |
216 | devm_kfree(dev: gbmodule->dev, p: dai); |
217 | return PTR_ERR(ptr: connection); |
218 | } |
219 | |
220 | greybus_set_drvdata(bundle, data: gbmodule); |
221 | dai->id = 0; |
222 | dai->data_cport = cpu_to_le16(connection->intf_cport_id); |
223 | dai->connection = connection; |
224 | list_add(new: &dai->list, head: &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 | |
244 | /* There should be at least one Management and one Data cport */ |
245 | if (bundle->num_cports < 2) |
246 | return -ENODEV; |
247 | |
248 | /* |
249 | * There can be only one Management connection and any number of data |
250 | * connections. |
251 | */ |
252 | gbmodule = devm_kzalloc(dev, size: sizeof(*gbmodule), GFP_KERNEL); |
253 | if (!gbmodule) |
254 | return -ENOMEM; |
255 | |
256 | gbmodule->num_data_connections = bundle->num_cports - 1; |
257 | INIT_LIST_HEAD(list: &gbmodule->data_list); |
258 | INIT_LIST_HEAD(list: &gbmodule->widget_list); |
259 | INIT_LIST_HEAD(list: &gbmodule->ctl_list); |
260 | INIT_LIST_HEAD(list: &gbmodule->widget_ctl_list); |
261 | INIT_LIST_HEAD(list: &gbmodule->jack_list); |
262 | gbmodule->dev = dev; |
263 | snprintf(buf: gbmodule->name, size: sizeof(gbmodule->name), fmt: "%s.%s" , dev->driver->name, |
264 | dev_name(dev)); |
265 | greybus_set_drvdata(bundle, data: 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(connection: 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(connection: gbmodule->mgmt_connection, topology: &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(module: gbmodule, tplg_data: topology); |
319 | if (ret) { |
320 | dev_err(dev, "%d:Error while parsing topology data\n" , |
321 | ret); |
322 | goto free_topology; |
323 | } |
324 | gbmodule->topology = topology; |
325 | |
326 | /* Initialize data connections */ |
327 | list_for_each_entry(dai, &gbmodule->data_list, list) { |
328 | ret = gb_connection_enable(connection: dai->connection); |
329 | if (ret) { |
330 | dev_err(dev, |
331 | "%d:Error while enabling %d:data connection\n" , |
332 | ret, le16_to_cpu(dai->data_cport)); |
333 | goto disable_data_connection; |
334 | } |
335 | } |
336 | |
337 | /* register module with gbcodec */ |
338 | ret = gbaudio_register_module(module: gbmodule); |
339 | if (ret) |
340 | goto disable_data_connection; |
341 | |
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 */ |
345 | strscpy(desc.name, gbmodule->name, sizeof(desc.name)); |
346 | desc.vid = 2; /* todo */ |
347 | desc.pid = 3; /* todo */ |
348 | desc.intf_id = gbmodule->dev_id; |
349 | desc.op_devices = gbmodule->op_devices; |
350 | desc.ip_devices = gbmodule->ip_devices; |
351 | gbmodule->manager_id = gb_audio_manager_add(desc: &desc); |
352 | |
353 | dev_dbg(dev, "Add GB Audio device:%s\n" , gbmodule->name); |
354 | |
355 | gb_pm_runtime_put_autosuspend(bundle); |
356 | |
357 | return 0; |
358 | |
359 | disable_data_connection: |
360 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) |
361 | gb_connection_disable(connection: dai->connection); |
362 | gbaudio_tplg_release(module: gbmodule); |
363 | gbmodule->topology = NULL; |
364 | |
365 | free_topology: |
366 | kfree(objp: topology); |
367 | |
368 | disable_connection: |
369 | gb_connection_disable(connection: gbmodule->mgmt_connection); |
370 | |
371 | destroy_connections: |
372 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) { |
373 | gb_connection_destroy(connection: dai->connection); |
374 | list_del(entry: &dai->list); |
375 | devm_kfree(dev, p: dai); |
376 | } |
377 | |
378 | if (gbmodule->mgmt_connection) |
379 | gb_connection_destroy(connection: gbmodule->mgmt_connection); |
380 | |
381 | devm_kfree(dev, p: gbmodule); |
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 | |
391 | gb_pm_runtime_get_sync(bundle); |
392 | |
393 | /* cleanup module related resources first */ |
394 | gbaudio_unregister_module(module: gbmodule); |
395 | |
396 | /* inform uevent to above layers */ |
397 | gb_audio_manager_remove(id: gbmodule->manager_id); |
398 | |
399 | gbaudio_tplg_release(module: gbmodule); |
400 | kfree(objp: gbmodule->topology); |
401 | gbmodule->topology = NULL; |
402 | gb_connection_disable(connection: gbmodule->mgmt_connection); |
403 | list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) { |
404 | gb_connection_disable(connection: dai->connection); |
405 | gb_connection_destroy(connection: dai->connection); |
406 | list_del(entry: &dai->list); |
407 | devm_kfree(dev: gbmodule->dev, p: dai); |
408 | } |
409 | gb_connection_destroy(connection: gbmodule->mgmt_connection); |
410 | gbmodule->mgmt_connection = NULL; |
411 | |
412 | devm_kfree(dev: &bundle->dev, p: gbmodule); |
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 | |
421 | #ifdef CONFIG_PM |
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(connection: dai->connection); |
430 | |
431 | gb_connection_disable(connection: 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(connection: 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(connection: dai->connection); |
451 | if (ret) { |
452 | dev_err(dev, |
453 | "%d:Error while enabling %d:data connection\n" , |
454 | ret, le16_to_cpu(dai->data_cport)); |
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 | |
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, |
472 | .driver.pm = &gb_audio_pm_ops, |
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" ); |
480 | |