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