Commit | Line | Data |
---|---|---|
f3d9478b JB |
1 | /* |
2 | * Apple Onboard Audio driver -- layout fabric | |
3 | * | |
4 | * Copyright 2006 Johannes Berg <johannes@sipsolutions.net> | |
5 | * | |
6 | * GPL v2, can be found in COPYING. | |
7 | * | |
8 | * | |
9 | * This fabric module looks for sound codecs | |
10 | * based on the layout-id property in the device tree. | |
11 | * | |
12 | */ | |
13 | ||
14 | #include <asm/prom.h> | |
15 | #include <linux/list.h> | |
16 | #include <linux/module.h> | |
17 | #include "../aoa.h" | |
18 | #include "../soundbus/soundbus.h" | |
19 | ||
20 | MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | |
21 | MODULE_LICENSE("GPL"); | |
22 | MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa"); | |
23 | ||
24 | #define MAX_CODECS_PER_BUS 2 | |
25 | ||
26 | /* These are the connections the layout fabric | |
27 | * knows about. It doesn't really care about the | |
28 | * input ones, but I thought I'd separate them | |
29 | * to give them proper names. The thing is that | |
30 | * Apple usually will distinguish the active output | |
31 | * by GPIOs, while the active input is set directly | |
32 | * on the codec. Hence we here tell the codec what | |
33 | * we think is connected. This information is hard- | |
34 | * coded below ... */ | |
35 | #define CC_SPEAKERS (1<<0) | |
36 | #define CC_HEADPHONE (1<<1) | |
37 | #define CC_LINEOUT (1<<2) | |
38 | #define CC_DIGITALOUT (1<<3) | |
39 | #define CC_LINEIN (1<<4) | |
40 | #define CC_MICROPHONE (1<<5) | |
41 | #define CC_DIGITALIN (1<<6) | |
42 | /* pretty bogus but users complain... | |
43 | * This is a flag saying that the LINEOUT | |
44 | * should be renamed to HEADPHONE. | |
45 | * be careful with input detection! */ | |
46 | #define CC_LINEOUT_LABELLED_HEADPHONE (1<<7) | |
47 | ||
48 | struct codec_connection { | |
49 | /* CC_ flags from above */ | |
50 | int connected; | |
51 | /* codec dependent bit to be set in the aoa_codec.connected field. | |
52 | * This intentionally doesn't have any generic flags because the | |
53 | * fabric has to know the codec anyway and all codecs might have | |
54 | * different connectors */ | |
55 | int codec_bit; | |
56 | }; | |
57 | ||
58 | struct codec_connect_info { | |
59 | char *name; | |
60 | struct codec_connection *connections; | |
61 | }; | |
62 | ||
63 | #define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0) | |
64 | ||
65 | struct layout { | |
66 | unsigned int layout_id; | |
67 | struct codec_connect_info codecs[MAX_CODECS_PER_BUS]; | |
68 | int flags; | |
69 | ||
70 | /* if busname is not assigned, we use 'Master' below, | |
71 | * so that our layout table doesn't need to be filled | |
72 | * too much. | |
73 | * We only assign these two if we expect to find more | |
74 | * than one soundbus, i.e. on those machines with | |
75 | * multiple layout-ids */ | |
76 | char *busname; | |
77 | int pcmid; | |
78 | }; | |
79 | ||
3e5102ad | 80 | MODULE_ALIAS("sound-layout-36"); |
f3d9478b JB |
81 | MODULE_ALIAS("sound-layout-41"); |
82 | MODULE_ALIAS("sound-layout-45"); | |
3e5102ad JB |
83 | MODULE_ALIAS("sound-layout-47"); |
84 | MODULE_ALIAS("sound-layout-48"); | |
85 | MODULE_ALIAS("sound-layout-49"); | |
86 | MODULE_ALIAS("sound-layout-50"); | |
f3d9478b | 87 | MODULE_ALIAS("sound-layout-51"); |
3e5102ad JB |
88 | MODULE_ALIAS("sound-layout-56"); |
89 | MODULE_ALIAS("sound-layout-57"); | |
f3d9478b JB |
90 | MODULE_ALIAS("sound-layout-58"); |
91 | MODULE_ALIAS("sound-layout-60"); | |
92 | MODULE_ALIAS("sound-layout-61"); | |
3e5102ad | 93 | MODULE_ALIAS("sound-layout-62"); |
f3d9478b JB |
94 | MODULE_ALIAS("sound-layout-64"); |
95 | MODULE_ALIAS("sound-layout-65"); | |
3e5102ad JB |
96 | MODULE_ALIAS("sound-layout-66"); |
97 | MODULE_ALIAS("sound-layout-67"); | |
f3d9478b JB |
98 | MODULE_ALIAS("sound-layout-68"); |
99 | MODULE_ALIAS("sound-layout-69"); | |
100 | MODULE_ALIAS("sound-layout-70"); | |
101 | MODULE_ALIAS("sound-layout-72"); | |
3e5102ad | 102 | MODULE_ALIAS("sound-layout-76"); |
f3d9478b JB |
103 | MODULE_ALIAS("sound-layout-80"); |
104 | MODULE_ALIAS("sound-layout-82"); | |
105 | MODULE_ALIAS("sound-layout-84"); | |
106 | MODULE_ALIAS("sound-layout-86"); | |
3e5102ad | 107 | MODULE_ALIAS("sound-layout-90"); |
f3d9478b | 108 | MODULE_ALIAS("sound-layout-92"); |
3e5102ad | 109 | MODULE_ALIAS("sound-layout-94"); |
c6feefd0 | 110 | MODULE_ALIAS("sound-layout-96"); |
3e5102ad JB |
111 | MODULE_ALIAS("sound-layout-98"); |
112 | MODULE_ALIAS("sound-layout-100"); | |
f3d9478b JB |
113 | |
114 | /* onyx with all but microphone connected */ | |
115 | static struct codec_connection onyx_connections_nomic[] = { | |
116 | { | |
117 | .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | |
118 | .codec_bit = 0, | |
119 | }, | |
120 | { | |
121 | .connected = CC_DIGITALOUT, | |
122 | .codec_bit = 1, | |
123 | }, | |
124 | { | |
125 | .connected = CC_LINEIN, | |
126 | .codec_bit = 2, | |
127 | }, | |
128 | {} /* terminate array by .connected == 0 */ | |
129 | }; | |
130 | ||
131 | /* onyx on machines without headphone */ | |
132 | static struct codec_connection onyx_connections_noheadphones[] = { | |
133 | { | |
134 | .connected = CC_SPEAKERS | CC_LINEOUT | | |
135 | CC_LINEOUT_LABELLED_HEADPHONE, | |
136 | .codec_bit = 0, | |
137 | }, | |
138 | { | |
139 | .connected = CC_DIGITALOUT, | |
140 | .codec_bit = 1, | |
141 | }, | |
142 | /* FIXME: are these correct? probably not for all the machines | |
143 | * below ... If not this will need separating. */ | |
144 | { | |
145 | .connected = CC_LINEIN, | |
146 | .codec_bit = 2, | |
147 | }, | |
148 | { | |
149 | .connected = CC_MICROPHONE, | |
150 | .codec_bit = 3, | |
151 | }, | |
152 | {} /* terminate array by .connected == 0 */ | |
153 | }; | |
154 | ||
155 | /* onyx on machines with real line-out */ | |
156 | static struct codec_connection onyx_connections_reallineout[] = { | |
157 | { | |
158 | .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE, | |
159 | .codec_bit = 0, | |
160 | }, | |
161 | { | |
162 | .connected = CC_DIGITALOUT, | |
163 | .codec_bit = 1, | |
164 | }, | |
165 | { | |
166 | .connected = CC_LINEIN, | |
167 | .codec_bit = 2, | |
168 | }, | |
169 | {} /* terminate array by .connected == 0 */ | |
170 | }; | |
171 | ||
172 | /* tas on machines without line out */ | |
173 | static struct codec_connection tas_connections_nolineout[] = { | |
174 | { | |
175 | .connected = CC_SPEAKERS | CC_HEADPHONE, | |
176 | .codec_bit = 0, | |
177 | }, | |
178 | { | |
179 | .connected = CC_LINEIN, | |
180 | .codec_bit = 2, | |
181 | }, | |
182 | { | |
183 | .connected = CC_MICROPHONE, | |
184 | .codec_bit = 3, | |
185 | }, | |
186 | {} /* terminate array by .connected == 0 */ | |
187 | }; | |
188 | ||
189 | /* tas on machines with neither line out nor line in */ | |
190 | static struct codec_connection tas_connections_noline[] = { | |
191 | { | |
192 | .connected = CC_SPEAKERS | CC_HEADPHONE, | |
193 | .codec_bit = 0, | |
194 | }, | |
195 | { | |
196 | .connected = CC_MICROPHONE, | |
197 | .codec_bit = 3, | |
198 | }, | |
199 | {} /* terminate array by .connected == 0 */ | |
200 | }; | |
201 | ||
202 | /* tas on machines without microphone */ | |
203 | static struct codec_connection tas_connections_nomic[] = { | |
204 | { | |
205 | .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | |
206 | .codec_bit = 0, | |
207 | }, | |
208 | { | |
209 | .connected = CC_LINEIN, | |
210 | .codec_bit = 2, | |
211 | }, | |
212 | {} /* terminate array by .connected == 0 */ | |
213 | }; | |
214 | ||
215 | /* tas on machines with everything connected */ | |
216 | static struct codec_connection tas_connections_all[] = { | |
217 | { | |
218 | .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | |
219 | .codec_bit = 0, | |
220 | }, | |
221 | { | |
222 | .connected = CC_LINEIN, | |
223 | .codec_bit = 2, | |
224 | }, | |
225 | { | |
226 | .connected = CC_MICROPHONE, | |
227 | .codec_bit = 3, | |
228 | }, | |
229 | {} /* terminate array by .connected == 0 */ | |
230 | }; | |
231 | ||
232 | static struct codec_connection toonie_connections[] = { | |
233 | { | |
234 | .connected = CC_SPEAKERS | CC_HEADPHONE, | |
235 | .codec_bit = 0, | |
236 | }, | |
237 | {} /* terminate array by .connected == 0 */ | |
238 | }; | |
239 | ||
240 | static struct codec_connection topaz_input[] = { | |
241 | { | |
242 | .connected = CC_DIGITALIN, | |
243 | .codec_bit = 0, | |
244 | }, | |
245 | {} /* terminate array by .connected == 0 */ | |
246 | }; | |
247 | ||
248 | static struct codec_connection topaz_output[] = { | |
249 | { | |
250 | .connected = CC_DIGITALOUT, | |
251 | .codec_bit = 1, | |
252 | }, | |
253 | {} /* terminate array by .connected == 0 */ | |
254 | }; | |
255 | ||
256 | static struct codec_connection topaz_inout[] = { | |
257 | { | |
258 | .connected = CC_DIGITALIN, | |
259 | .codec_bit = 0, | |
260 | }, | |
261 | { | |
262 | .connected = CC_DIGITALOUT, | |
263 | .codec_bit = 1, | |
264 | }, | |
265 | {} /* terminate array by .connected == 0 */ | |
266 | }; | |
267 | ||
268 | static struct layout layouts[] = { | |
269 | /* last PowerBooks (15" Oct 2005) */ | |
270 | { .layout_id = 82, | |
271 | .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | |
272 | .codecs[0] = { | |
273 | .name = "onyx", | |
274 | .connections = onyx_connections_noheadphones, | |
275 | }, | |
276 | .codecs[1] = { | |
277 | .name = "topaz", | |
278 | .connections = topaz_input, | |
279 | }, | |
280 | }, | |
281 | /* PowerMac9,1 */ | |
282 | { .layout_id = 60, | |
283 | .codecs[0] = { | |
284 | .name = "onyx", | |
285 | .connections = onyx_connections_reallineout, | |
286 | }, | |
287 | }, | |
288 | /* PowerMac9,1 */ | |
289 | { .layout_id = 61, | |
290 | .codecs[0] = { | |
291 | .name = "topaz", | |
292 | .connections = topaz_input, | |
293 | }, | |
294 | }, | |
295 | /* PowerBook5,7 */ | |
296 | { .layout_id = 64, | |
297 | .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | |
298 | .codecs[0] = { | |
299 | .name = "onyx", | |
300 | .connections = onyx_connections_noheadphones, | |
301 | }, | |
302 | }, | |
303 | /* PowerBook5,7 */ | |
304 | { .layout_id = 65, | |
305 | .codecs[0] = { | |
306 | .name = "topaz", | |
307 | .connections = topaz_input, | |
308 | }, | |
309 | }, | |
310 | /* PowerBook5,9 [17" Oct 2005] */ | |
311 | { .layout_id = 84, | |
312 | .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | |
313 | .codecs[0] = { | |
314 | .name = "onyx", | |
315 | .connections = onyx_connections_noheadphones, | |
316 | }, | |
317 | .codecs[1] = { | |
318 | .name = "topaz", | |
319 | .connections = topaz_input, | |
320 | }, | |
321 | }, | |
322 | /* PowerMac8,1 */ | |
323 | { .layout_id = 45, | |
324 | .codecs[0] = { | |
325 | .name = "onyx", | |
326 | .connections = onyx_connections_noheadphones, | |
327 | }, | |
328 | .codecs[1] = { | |
329 | .name = "topaz", | |
330 | .connections = topaz_input, | |
331 | }, | |
332 | }, | |
333 | /* Quad PowerMac (analog in, analog/digital out) */ | |
334 | { .layout_id = 68, | |
335 | .codecs[0] = { | |
336 | .name = "onyx", | |
337 | .connections = onyx_connections_nomic, | |
338 | }, | |
339 | }, | |
340 | /* Quad PowerMac (digital in) */ | |
341 | { .layout_id = 69, | |
342 | .codecs[0] = { | |
343 | .name = "topaz", | |
344 | .connections = topaz_input, | |
345 | }, | |
346 | .busname = "digital in", .pcmid = 1 }, | |
347 | /* Early 2005 PowerBook (PowerBook 5,6) */ | |
348 | { .layout_id = 70, | |
349 | .codecs[0] = { | |
350 | .name = "tas", | |
351 | .connections = tas_connections_nolineout, | |
352 | }, | |
353 | }, | |
354 | /* PowerBook 5,4 */ | |
355 | { .layout_id = 51, | |
356 | .codecs[0] = { | |
357 | .name = "tas", | |
358 | .connections = tas_connections_nolineout, | |
359 | }, | |
360 | }, | |
361 | /* PowerBook6,7 */ | |
362 | { .layout_id = 80, | |
363 | .codecs[0] = { | |
364 | .name = "tas", | |
365 | .connections = tas_connections_noline, | |
366 | }, | |
367 | }, | |
368 | /* PowerBook6,8 */ | |
369 | { .layout_id = 72, | |
370 | .codecs[0] = { | |
371 | .name = "tas", | |
372 | .connections = tas_connections_nolineout, | |
373 | }, | |
374 | }, | |
375 | /* PowerMac8,2 */ | |
376 | { .layout_id = 86, | |
377 | .codecs[0] = { | |
378 | .name = "onyx", | |
379 | .connections = onyx_connections_nomic, | |
380 | }, | |
381 | .codecs[1] = { | |
382 | .name = "topaz", | |
383 | .connections = topaz_input, | |
384 | }, | |
385 | }, | |
386 | /* PowerBook6,7 */ | |
387 | { .layout_id = 92, | |
388 | .codecs[0] = { | |
389 | .name = "tas", | |
390 | .connections = tas_connections_nolineout, | |
391 | }, | |
392 | }, | |
393 | /* PowerMac10,1 (Mac Mini) */ | |
394 | { .layout_id = 58, | |
395 | .codecs[0] = { | |
396 | .name = "toonie", | |
397 | .connections = toonie_connections, | |
398 | }, | |
399 | }, | |
c6feefd0 JB |
400 | { |
401 | .layout_id = 96, | |
402 | .codecs[0] = { | |
403 | .name = "onyx", | |
404 | .connections = onyx_connections_noheadphones, | |
405 | }, | |
406 | }, | |
f3d9478b JB |
407 | /* unknown, untested, but this comes from Apple */ |
408 | { .layout_id = 41, | |
409 | .codecs[0] = { | |
410 | .name = "tas", | |
411 | .connections = tas_connections_all, | |
412 | }, | |
413 | }, | |
414 | { .layout_id = 36, | |
415 | .codecs[0] = { | |
416 | .name = "tas", | |
417 | .connections = tas_connections_nomic, | |
418 | }, | |
419 | .codecs[1] = { | |
420 | .name = "topaz", | |
421 | .connections = topaz_inout, | |
422 | }, | |
423 | }, | |
424 | { .layout_id = 47, | |
425 | .codecs[0] = { | |
426 | .name = "onyx", | |
427 | .connections = onyx_connections_noheadphones, | |
428 | }, | |
429 | }, | |
430 | { .layout_id = 48, | |
431 | .codecs[0] = { | |
432 | .name = "topaz", | |
433 | .connections = topaz_input, | |
434 | }, | |
435 | }, | |
436 | { .layout_id = 49, | |
437 | .codecs[0] = { | |
438 | .name = "onyx", | |
439 | .connections = onyx_connections_nomic, | |
440 | }, | |
441 | }, | |
442 | { .layout_id = 50, | |
443 | .codecs[0] = { | |
444 | .name = "topaz", | |
445 | .connections = topaz_input, | |
446 | }, | |
447 | }, | |
448 | { .layout_id = 56, | |
449 | .codecs[0] = { | |
450 | .name = "onyx", | |
451 | .connections = onyx_connections_noheadphones, | |
452 | }, | |
453 | }, | |
454 | { .layout_id = 57, | |
455 | .codecs[0] = { | |
456 | .name = "topaz", | |
457 | .connections = topaz_input, | |
458 | }, | |
459 | }, | |
460 | { .layout_id = 62, | |
461 | .codecs[0] = { | |
462 | .name = "onyx", | |
463 | .connections = onyx_connections_noheadphones, | |
464 | }, | |
465 | .codecs[1] = { | |
466 | .name = "topaz", | |
467 | .connections = topaz_output, | |
468 | }, | |
469 | }, | |
470 | { .layout_id = 66, | |
471 | .codecs[0] = { | |
472 | .name = "onyx", | |
473 | .connections = onyx_connections_noheadphones, | |
474 | }, | |
475 | }, | |
476 | { .layout_id = 67, | |
477 | .codecs[0] = { | |
478 | .name = "topaz", | |
479 | .connections = topaz_input, | |
480 | }, | |
481 | }, | |
482 | { .layout_id = 76, | |
483 | .codecs[0] = { | |
484 | .name = "tas", | |
485 | .connections = tas_connections_nomic, | |
486 | }, | |
487 | .codecs[1] = { | |
488 | .name = "topaz", | |
489 | .connections = topaz_inout, | |
490 | }, | |
491 | }, | |
492 | { .layout_id = 90, | |
493 | .codecs[0] = { | |
494 | .name = "tas", | |
495 | .connections = tas_connections_noline, | |
496 | }, | |
497 | }, | |
498 | { .layout_id = 94, | |
499 | .codecs[0] = { | |
500 | .name = "onyx", | |
501 | /* but it has an external mic?? how to select? */ | |
502 | .connections = onyx_connections_noheadphones, | |
503 | }, | |
504 | }, | |
f3d9478b JB |
505 | { .layout_id = 98, |
506 | .codecs[0] = { | |
507 | .name = "toonie", | |
508 | .connections = toonie_connections, | |
509 | }, | |
510 | }, | |
511 | { .layout_id = 100, | |
512 | .codecs[0] = { | |
513 | .name = "topaz", | |
514 | .connections = topaz_input, | |
515 | }, | |
516 | .codecs[1] = { | |
517 | .name = "onyx", | |
518 | .connections = onyx_connections_noheadphones, | |
519 | }, | |
520 | }, | |
521 | {} | |
522 | }; | |
523 | ||
524 | static struct layout *find_layout_by_id(unsigned int id) | |
525 | { | |
526 | struct layout *l; | |
527 | ||
528 | l = layouts; | |
529 | while (l->layout_id) { | |
530 | if (l->layout_id == id) | |
531 | return l; | |
532 | l++; | |
533 | } | |
534 | return NULL; | |
535 | } | |
536 | ||
537 | static void use_layout(struct layout *l) | |
538 | { | |
539 | int i; | |
540 | ||
541 | for (i=0; i<MAX_CODECS_PER_BUS; i++) { | |
542 | if (l->codecs[i].name) { | |
543 | request_module("snd-aoa-codec-%s", l->codecs[i].name); | |
544 | } | |
545 | } | |
546 | /* now we wait for the codecs to call us back */ | |
547 | } | |
548 | ||
549 | struct layout_dev; | |
550 | ||
551 | struct layout_dev_ptr { | |
552 | struct layout_dev *ptr; | |
553 | }; | |
554 | ||
555 | struct layout_dev { | |
556 | struct list_head list; | |
557 | struct soundbus_dev *sdev; | |
558 | struct device_node *sound; | |
559 | struct aoa_codec *codecs[MAX_CODECS_PER_BUS]; | |
560 | struct layout *layout; | |
561 | struct gpio_runtime gpio; | |
562 | ||
563 | /* we need these for headphone/lineout detection */ | |
564 | struct snd_kcontrol *headphone_ctrl; | |
565 | struct snd_kcontrol *lineout_ctrl; | |
566 | struct snd_kcontrol *speaker_ctrl; | |
567 | struct snd_kcontrol *headphone_detected_ctrl; | |
568 | struct snd_kcontrol *lineout_detected_ctrl; | |
569 | ||
570 | struct layout_dev_ptr selfptr_headphone; | |
571 | struct layout_dev_ptr selfptr_lineout; | |
572 | ||
573 | u32 have_lineout_detect:1, | |
574 | have_headphone_detect:1, | |
575 | switch_on_headphone:1, | |
576 | switch_on_lineout:1; | |
577 | }; | |
578 | ||
579 | static LIST_HEAD(layouts_list); | |
580 | static int layouts_list_items; | |
581 | /* this can go away but only if we allow multiple cards, | |
582 | * make the fabric handle all the card stuff, etc... */ | |
583 | static struct layout_dev *layout_device; | |
584 | ||
a5ce8890 | 585 | #define control_info snd_ctl_boolean_mono_info |
f3d9478b JB |
586 | |
587 | #define AMP_CONTROL(n, description) \ | |
588 | static int n##_control_get(struct snd_kcontrol *kcontrol, \ | |
589 | struct snd_ctl_elem_value *ucontrol) \ | |
590 | { \ | |
591 | struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \ | |
592 | if (gpio->methods && gpio->methods->get_##n) \ | |
593 | ucontrol->value.integer.value[0] = \ | |
594 | gpio->methods->get_##n(gpio); \ | |
595 | return 0; \ | |
596 | } \ | |
597 | static int n##_control_put(struct snd_kcontrol *kcontrol, \ | |
598 | struct snd_ctl_elem_value *ucontrol) \ | |
599 | { \ | |
600 | struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \ | |
601 | if (gpio->methods && gpio->methods->get_##n) \ | |
602 | gpio->methods->set_##n(gpio, \ | |
603 | ucontrol->value.integer.value[0]); \ | |
604 | return 1; \ | |
605 | } \ | |
606 | static struct snd_kcontrol_new n##_ctl = { \ | |
607 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ | |
608 | .name = description, \ | |
609 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ | |
610 | .info = control_info, \ | |
611 | .get = n##_control_get, \ | |
612 | .put = n##_control_put, \ | |
613 | } | |
614 | ||
615 | AMP_CONTROL(headphone, "Headphone Switch"); | |
616 | AMP_CONTROL(speakers, "Speakers Switch"); | |
617 | AMP_CONTROL(lineout, "Line-Out Switch"); | |
618 | ||
619 | static int detect_choice_get(struct snd_kcontrol *kcontrol, | |
620 | struct snd_ctl_elem_value *ucontrol) | |
621 | { | |
622 | struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | |
623 | ||
624 | switch (kcontrol->private_value) { | |
625 | case 0: | |
626 | ucontrol->value.integer.value[0] = ldev->switch_on_headphone; | |
627 | break; | |
628 | case 1: | |
629 | ucontrol->value.integer.value[0] = ldev->switch_on_lineout; | |
630 | break; | |
631 | default: | |
632 | return -ENODEV; | |
633 | } | |
634 | return 0; | |
635 | } | |
636 | ||
637 | static int detect_choice_put(struct snd_kcontrol *kcontrol, | |
638 | struct snd_ctl_elem_value *ucontrol) | |
639 | { | |
640 | struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | |
641 | ||
642 | switch (kcontrol->private_value) { | |
643 | case 0: | |
644 | ldev->switch_on_headphone = !!ucontrol->value.integer.value[0]; | |
645 | break; | |
646 | case 1: | |
647 | ldev->switch_on_lineout = !!ucontrol->value.integer.value[0]; | |
648 | break; | |
649 | default: | |
650 | return -ENODEV; | |
651 | } | |
652 | return 1; | |
653 | } | |
654 | ||
655 | static struct snd_kcontrol_new headphone_detect_choice = { | |
656 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
657 | .name = "Headphone Detect Autoswitch", | |
658 | .info = control_info, | |
659 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | |
660 | .get = detect_choice_get, | |
661 | .put = detect_choice_put, | |
662 | .private_value = 0, | |
663 | }; | |
664 | ||
665 | static struct snd_kcontrol_new lineout_detect_choice = { | |
666 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
667 | .name = "Line-Out Detect Autoswitch", | |
668 | .info = control_info, | |
669 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | |
670 | .get = detect_choice_get, | |
671 | .put = detect_choice_put, | |
672 | .private_value = 1, | |
673 | }; | |
674 | ||
675 | static int detected_get(struct snd_kcontrol *kcontrol, | |
676 | struct snd_ctl_elem_value *ucontrol) | |
677 | { | |
678 | struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | |
679 | int v; | |
680 | ||
681 | switch (kcontrol->private_value) { | |
682 | case 0: | |
683 | v = ldev->gpio.methods->get_detect(&ldev->gpio, | |
684 | AOA_NOTIFY_HEADPHONE); | |
685 | break; | |
686 | case 1: | |
687 | v = ldev->gpio.methods->get_detect(&ldev->gpio, | |
688 | AOA_NOTIFY_LINE_OUT); | |
689 | break; | |
690 | default: | |
691 | return -ENODEV; | |
692 | } | |
693 | ucontrol->value.integer.value[0] = v; | |
694 | return 0; | |
695 | } | |
696 | ||
697 | static struct snd_kcontrol_new headphone_detected = { | |
698 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
699 | .name = "Headphone Detected", | |
700 | .info = control_info, | |
701 | .access = SNDRV_CTL_ELEM_ACCESS_READ, | |
702 | .get = detected_get, | |
703 | .private_value = 0, | |
704 | }; | |
705 | ||
706 | static struct snd_kcontrol_new lineout_detected = { | |
707 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | |
708 | .name = "Line-Out Detected", | |
709 | .info = control_info, | |
710 | .access = SNDRV_CTL_ELEM_ACCESS_READ, | |
711 | .get = detected_get, | |
712 | .private_value = 1, | |
713 | }; | |
714 | ||
715 | static int check_codec(struct aoa_codec *codec, | |
716 | struct layout_dev *ldev, | |
717 | struct codec_connect_info *cci) | |
718 | { | |
a7edd0e6 | 719 | const u32 *ref; |
f3d9478b JB |
720 | char propname[32]; |
721 | struct codec_connection *cc; | |
722 | ||
723 | /* if the codec has a 'codec' node, we require a reference */ | |
724 | if (codec->node && (strcmp(codec->node->name, "codec") == 0)) { | |
725 | snprintf(propname, sizeof(propname), | |
726 | "platform-%s-codec-ref", codec->name); | |
c4f55b39 | 727 | ref = of_get_property(ldev->sound, propname, NULL); |
f3d9478b JB |
728 | if (!ref) { |
729 | printk(KERN_INFO "snd-aoa-fabric-layout: " | |
730 | "required property %s not present\n", propname); | |
731 | return -ENODEV; | |
732 | } | |
733 | if (*ref != codec->node->linux_phandle) { | |
734 | printk(KERN_INFO "snd-aoa-fabric-layout: " | |
735 | "%s doesn't match!\n", propname); | |
736 | return -ENODEV; | |
737 | } | |
738 | } else { | |
739 | if (layouts_list_items != 1) { | |
740 | printk(KERN_INFO "snd-aoa-fabric-layout: " | |
741 | "more than one soundbus, but no references.\n"); | |
742 | return -ENODEV; | |
743 | } | |
744 | } | |
745 | codec->soundbus_dev = ldev->sdev; | |
746 | codec->gpio = &ldev->gpio; | |
747 | ||
748 | cc = cci->connections; | |
749 | if (!cc) | |
750 | return -EINVAL; | |
751 | ||
752 | printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n"); | |
753 | ||
754 | codec->connected = 0; | |
755 | codec->fabric_data = cc; | |
756 | ||
757 | while (cc->connected) { | |
758 | codec->connected |= 1<<cc->codec_bit; | |
759 | cc++; | |
760 | } | |
761 | ||
762 | return 0; | |
763 | } | |
764 | ||
765 | static int layout_found_codec(struct aoa_codec *codec) | |
766 | { | |
767 | struct layout_dev *ldev; | |
768 | int i; | |
769 | ||
770 | list_for_each_entry(ldev, &layouts_list, list) { | |
771 | for (i=0; i<MAX_CODECS_PER_BUS; i++) { | |
772 | if (!ldev->layout->codecs[i].name) | |
773 | continue; | |
774 | if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) { | |
775 | if (check_codec(codec, | |
776 | ldev, | |
777 | &ldev->layout->codecs[i]) == 0) | |
778 | return 0; | |
779 | } | |
780 | } | |
781 | } | |
782 | return -ENODEV; | |
783 | } | |
784 | ||
785 | static void layout_remove_codec(struct aoa_codec *codec) | |
786 | { | |
787 | int i; | |
788 | /* here remove the codec from the layout dev's | |
789 | * codec reference */ | |
790 | ||
791 | codec->soundbus_dev = NULL; | |
792 | codec->gpio = NULL; | |
793 | for (i=0; i<MAX_CODECS_PER_BUS; i++) { | |
794 | } | |
795 | } | |
796 | ||
797 | static void layout_notify(void *data) | |
798 | { | |
799 | struct layout_dev_ptr *dptr = data; | |
800 | struct layout_dev *ldev; | |
801 | int v, update; | |
802 | struct snd_kcontrol *detected, *c; | |
803 | struct snd_card *card = aoa_get_card(); | |
804 | ||
805 | ldev = dptr->ptr; | |
806 | if (data == &ldev->selfptr_headphone) { | |
807 | v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE); | |
808 | detected = ldev->headphone_detected_ctrl; | |
809 | update = ldev->switch_on_headphone; | |
810 | if (update) { | |
811 | ldev->gpio.methods->set_speakers(&ldev->gpio, !v); | |
812 | ldev->gpio.methods->set_headphone(&ldev->gpio, v); | |
813 | ldev->gpio.methods->set_lineout(&ldev->gpio, 0); | |
814 | } | |
815 | } else if (data == &ldev->selfptr_lineout) { | |
816 | v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT); | |
817 | detected = ldev->lineout_detected_ctrl; | |
818 | update = ldev->switch_on_lineout; | |
819 | if (update) { | |
820 | ldev->gpio.methods->set_speakers(&ldev->gpio, !v); | |
821 | ldev->gpio.methods->set_headphone(&ldev->gpio, 0); | |
822 | ldev->gpio.methods->set_lineout(&ldev->gpio, v); | |
823 | } | |
824 | } else | |
825 | return; | |
826 | ||
827 | if (detected) | |
828 | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id); | |
829 | if (update) { | |
830 | c = ldev->headphone_ctrl; | |
831 | if (c) | |
832 | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | |
833 | c = ldev->speaker_ctrl; | |
834 | if (c) | |
835 | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | |
836 | c = ldev->lineout_ctrl; | |
837 | if (c) | |
838 | snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | |
839 | } | |
840 | } | |
841 | ||
842 | static void layout_attached_codec(struct aoa_codec *codec) | |
843 | { | |
844 | struct codec_connection *cc; | |
845 | struct snd_kcontrol *ctl; | |
846 | int headphones, lineout; | |
847 | struct layout_dev *ldev = layout_device; | |
848 | ||
849 | /* need to add this codec to our codec array! */ | |
850 | ||
851 | cc = codec->fabric_data; | |
852 | ||
853 | headphones = codec->gpio->methods->get_detect(codec->gpio, | |
854 | AOA_NOTIFY_HEADPHONE); | |
855 | lineout = codec->gpio->methods->get_detect(codec->gpio, | |
856 | AOA_NOTIFY_LINE_OUT); | |
857 | ||
858 | while (cc->connected) { | |
859 | if (cc->connected & CC_SPEAKERS) { | |
860 | if (headphones <= 0 && lineout <= 0) | |
861 | ldev->gpio.methods->set_speakers(codec->gpio, 1); | |
862 | ctl = snd_ctl_new1(&speakers_ctl, codec->gpio); | |
863 | ldev->speaker_ctrl = ctl; | |
864 | aoa_snd_ctl_add(ctl); | |
865 | } | |
866 | if (cc->connected & CC_HEADPHONE) { | |
867 | if (headphones == 1) | |
868 | ldev->gpio.methods->set_headphone(codec->gpio, 1); | |
869 | ctl = snd_ctl_new1(&headphone_ctl, codec->gpio); | |
870 | ldev->headphone_ctrl = ctl; | |
871 | aoa_snd_ctl_add(ctl); | |
872 | ldev->have_headphone_detect = | |
873 | !ldev->gpio.methods | |
874 | ->set_notify(&ldev->gpio, | |
875 | AOA_NOTIFY_HEADPHONE, | |
876 | layout_notify, | |
877 | &ldev->selfptr_headphone); | |
878 | if (ldev->have_headphone_detect) { | |
879 | ctl = snd_ctl_new1(&headphone_detect_choice, | |
880 | ldev); | |
881 | aoa_snd_ctl_add(ctl); | |
882 | ctl = snd_ctl_new1(&headphone_detected, | |
883 | ldev); | |
884 | ldev->headphone_detected_ctrl = ctl; | |
885 | aoa_snd_ctl_add(ctl); | |
886 | } | |
887 | } | |
888 | if (cc->connected & CC_LINEOUT) { | |
889 | if (lineout == 1) | |
890 | ldev->gpio.methods->set_lineout(codec->gpio, 1); | |
891 | ctl = snd_ctl_new1(&lineout_ctl, codec->gpio); | |
892 | if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | |
893 | strlcpy(ctl->id.name, | |
894 | "Headphone Switch", sizeof(ctl->id.name)); | |
895 | ldev->lineout_ctrl = ctl; | |
896 | aoa_snd_ctl_add(ctl); | |
897 | ldev->have_lineout_detect = | |
898 | !ldev->gpio.methods | |
899 | ->set_notify(&ldev->gpio, | |
900 | AOA_NOTIFY_LINE_OUT, | |
901 | layout_notify, | |
902 | &ldev->selfptr_lineout); | |
903 | if (ldev->have_lineout_detect) { | |
904 | ctl = snd_ctl_new1(&lineout_detect_choice, | |
905 | ldev); | |
906 | if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | |
907 | strlcpy(ctl->id.name, | |
908 | "Headphone Detect Autoswitch", | |
909 | sizeof(ctl->id.name)); | |
910 | aoa_snd_ctl_add(ctl); | |
911 | ctl = snd_ctl_new1(&lineout_detected, | |
912 | ldev); | |
913 | if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | |
914 | strlcpy(ctl->id.name, | |
915 | "Headphone Detected", | |
916 | sizeof(ctl->id.name)); | |
917 | ldev->lineout_detected_ctrl = ctl; | |
918 | aoa_snd_ctl_add(ctl); | |
919 | } | |
920 | } | |
921 | cc++; | |
922 | } | |
923 | /* now update initial state */ | |
924 | if (ldev->have_headphone_detect) | |
925 | layout_notify(&ldev->selfptr_headphone); | |
926 | if (ldev->have_lineout_detect) | |
927 | layout_notify(&ldev->selfptr_lineout); | |
928 | } | |
929 | ||
930 | static struct aoa_fabric layout_fabric = { | |
931 | .name = "SoundByLayout", | |
932 | .owner = THIS_MODULE, | |
933 | .found_codec = layout_found_codec, | |
934 | .remove_codec = layout_remove_codec, | |
935 | .attached_codec = layout_attached_codec, | |
936 | }; | |
937 | ||
938 | static int aoa_fabric_layout_probe(struct soundbus_dev *sdev) | |
939 | { | |
940 | struct device_node *sound = NULL; | |
c4f55b39 | 941 | const unsigned int *layout_id; |
f3d9478b JB |
942 | struct layout *layout; |
943 | struct layout_dev *ldev = NULL; | |
944 | int err; | |
945 | ||
946 | /* hm, currently we can only have one ... */ | |
947 | if (layout_device) | |
948 | return -ENODEV; | |
949 | ||
950 | /* by breaking out we keep a reference */ | |
951 | while ((sound = of_get_next_child(sdev->ofdev.node, sound))) { | |
952 | if (sound->type && strcasecmp(sound->type, "soundchip") == 0) | |
953 | break; | |
954 | } | |
955 | if (!sound) return -ENODEV; | |
956 | ||
c4f55b39 | 957 | layout_id = of_get_property(sound, "layout-id", NULL); |
f3d9478b JB |
958 | if (!layout_id) |
959 | goto outnodev; | |
a677c8fb BH |
960 | printk(KERN_INFO "snd-aoa-fabric-layout: found bus with layout %d\n", |
961 | *layout_id); | |
f3d9478b JB |
962 | |
963 | layout = find_layout_by_id(*layout_id); | |
964 | if (!layout) { | |
a677c8fb | 965 | printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n"); |
f3d9478b JB |
966 | goto outnodev; |
967 | } | |
968 | ||
969 | ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL); | |
970 | if (!ldev) | |
971 | goto outnodev; | |
972 | ||
973 | layout_device = ldev; | |
974 | ldev->sdev = sdev; | |
975 | ldev->sound = sound; | |
976 | ldev->layout = layout; | |
977 | ldev->gpio.node = sound->parent; | |
978 | switch (layout->layout_id) { | |
979 | case 41: /* that unknown machine no one seems to have */ | |
980 | case 51: /* PowerBook5,4 */ | |
981 | case 58: /* Mac Mini */ | |
982 | ldev->gpio.methods = ftr_gpio_methods; | |
a677c8fb BH |
983 | printk(KERN_DEBUG |
984 | "snd-aoa-fabric-layout: Using direct GPIOs\n"); | |
f3d9478b JB |
985 | break; |
986 | default: | |
987 | ldev->gpio.methods = pmf_gpio_methods; | |
a677c8fb BH |
988 | printk(KERN_DEBUG |
989 | "snd-aoa-fabric-layout: Using PMF GPIOs\n"); | |
f3d9478b JB |
990 | } |
991 | ldev->selfptr_headphone.ptr = ldev; | |
992 | ldev->selfptr_lineout.ptr = ldev; | |
993 | sdev->ofdev.dev.driver_data = ldev; | |
f3d9478b JB |
994 | list_add(&ldev->list, &layouts_list); |
995 | layouts_list_items++; | |
996 | ||
997 | /* assign these before registering ourselves, so | |
998 | * callbacks that are done during registration | |
999 | * already have the values */ | |
1000 | sdev->pcmid = ldev->layout->pcmid; | |
1001 | if (ldev->layout->busname) { | |
1002 | sdev->pcmname = ldev->layout->busname; | |
1003 | } else { | |
1004 | sdev->pcmname = "Master"; | |
1005 | } | |
1006 | ||
1007 | ldev->gpio.methods->init(&ldev->gpio); | |
1008 | ||
61e77107 | 1009 | err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev); |
f3d9478b JB |
1010 | if (err && err != -EALREADY) { |
1011 | printk(KERN_INFO "snd-aoa-fabric-layout: can't use," | |
1012 | " another fabric is active!\n"); | |
1013 | goto outlistdel; | |
1014 | } | |
1015 | ||
1016 | use_layout(layout); | |
1017 | ldev->switch_on_headphone = 1; | |
1018 | ldev->switch_on_lineout = 1; | |
1019 | return 0; | |
1020 | outlistdel: | |
1021 | /* we won't be using these then... */ | |
1022 | ldev->gpio.methods->exit(&ldev->gpio); | |
1023 | /* reset if we didn't use it */ | |
1024 | sdev->pcmname = NULL; | |
1025 | sdev->pcmid = -1; | |
1026 | list_del(&ldev->list); | |
1027 | layouts_list_items--; | |
1028 | outnodev: | |
c6848715 | 1029 | of_node_put(sound); |
f3d9478b | 1030 | layout_device = NULL; |
c6848715 | 1031 | kfree(ldev); |
f3d9478b JB |
1032 | return -ENODEV; |
1033 | } | |
1034 | ||
1035 | static int aoa_fabric_layout_remove(struct soundbus_dev *sdev) | |
1036 | { | |
1037 | struct layout_dev *ldev = sdev->ofdev.dev.driver_data; | |
1038 | int i; | |
1039 | ||
1040 | for (i=0; i<MAX_CODECS_PER_BUS; i++) { | |
1041 | if (ldev->codecs[i]) { | |
1042 | aoa_fabric_unlink_codec(ldev->codecs[i]); | |
1043 | } | |
1044 | ldev->codecs[i] = NULL; | |
1045 | } | |
1046 | list_del(&ldev->list); | |
1047 | layouts_list_items--; | |
1048 | of_node_put(ldev->sound); | |
1049 | ||
1050 | ldev->gpio.methods->set_notify(&ldev->gpio, | |
1051 | AOA_NOTIFY_HEADPHONE, | |
1052 | NULL, | |
1053 | NULL); | |
1054 | ldev->gpio.methods->set_notify(&ldev->gpio, | |
1055 | AOA_NOTIFY_LINE_OUT, | |
1056 | NULL, | |
1057 | NULL); | |
1058 | ||
1059 | ldev->gpio.methods->exit(&ldev->gpio); | |
1060 | layout_device = NULL; | |
1061 | kfree(ldev); | |
1062 | sdev->pcmid = -1; | |
1063 | sdev->pcmname = NULL; | |
1064 | return 0; | |
1065 | } | |
1066 | ||
1067 | #ifdef CONFIG_PM | |
1068 | static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state) | |
1069 | { | |
1070 | struct layout_dev *ldev = sdev->ofdev.dev.driver_data; | |
1071 | ||
f3d9478b JB |
1072 | if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) |
1073 | ldev->gpio.methods->all_amps_off(&ldev->gpio); | |
1074 | ||
1075 | return 0; | |
1076 | } | |
1077 | ||
1078 | static int aoa_fabric_layout_resume(struct soundbus_dev *sdev) | |
1079 | { | |
1080 | struct layout_dev *ldev = sdev->ofdev.dev.driver_data; | |
1081 | ||
f3d9478b JB |
1082 | if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) |
1083 | ldev->gpio.methods->all_amps_restore(&ldev->gpio); | |
1084 | ||
1085 | return 0; | |
1086 | } | |
1087 | #endif | |
1088 | ||
1089 | static struct soundbus_driver aoa_soundbus_driver = { | |
1090 | .name = "snd_aoa_soundbus_drv", | |
1091 | .owner = THIS_MODULE, | |
1092 | .probe = aoa_fabric_layout_probe, | |
1093 | .remove = aoa_fabric_layout_remove, | |
1094 | #ifdef CONFIG_PM | |
1095 | .suspend = aoa_fabric_layout_suspend, | |
1096 | .resume = aoa_fabric_layout_resume, | |
1097 | #endif | |
c17d6fd9 OH |
1098 | .driver = { |
1099 | .owner = THIS_MODULE, | |
1100 | } | |
f3d9478b JB |
1101 | }; |
1102 | ||
1103 | static int __init aoa_fabric_layout_init(void) | |
1104 | { | |
1105 | int err; | |
1106 | ||
1107 | err = soundbus_register_driver(&aoa_soundbus_driver); | |
1108 | if (err) | |
1109 | return err; | |
1110 | return 0; | |
1111 | } | |
1112 | ||
1113 | static void __exit aoa_fabric_layout_exit(void) | |
1114 | { | |
1115 | soundbus_unregister_driver(&aoa_soundbus_driver); | |
1116 | aoa_fabric_unregister(&layout_fabric); | |
1117 | } | |
1118 | ||
1119 | module_init(aoa_fabric_layout_init); | |
1120 | module_exit(aoa_fabric_layout_exit); |