| // SPDX-License-Identifier: GPL-2.0-only | 
| /* | 
|  * Apple Onboard Audio driver -- layout/machine id fabric | 
|  * | 
|  * Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net> | 
|  * | 
|  * This fabric module looks for sound codecs based on the | 
|  * layout-id or device-id property in the device tree. | 
|  */ | 
| #include <asm/prom.h> | 
| #include <linux/list.h> | 
| #include <linux/module.h> | 
| #include <linux/slab.h> | 
| #include "../aoa.h" | 
| #include "../soundbus/soundbus.h" | 
|   | 
| MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>"); | 
| MODULE_LICENSE("GPL"); | 
| MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa"); | 
|   | 
| #define MAX_CODECS_PER_BUS    2 | 
|   | 
| /* These are the connections the layout fabric | 
|  * knows about. It doesn't really care about the | 
|  * input ones, but I thought I'd separate them | 
|  * to give them proper names. The thing is that | 
|  * Apple usually will distinguish the active output | 
|  * by GPIOs, while the active input is set directly | 
|  * on the codec. Hence we here tell the codec what | 
|  * we think is connected. This information is hard- | 
|  * coded below ... */ | 
| #define CC_SPEAKERS    (1<<0) | 
| #define CC_HEADPHONE    (1<<1) | 
| #define CC_LINEOUT    (1<<2) | 
| #define CC_DIGITALOUT    (1<<3) | 
| #define CC_LINEIN    (1<<4) | 
| #define CC_MICROPHONE    (1<<5) | 
| #define CC_DIGITALIN    (1<<6) | 
| /* pretty bogus but users complain... | 
|  * This is a flag saying that the LINEOUT | 
|  * should be renamed to HEADPHONE. | 
|  * be careful with input detection! */ | 
| #define CC_LINEOUT_LABELLED_HEADPHONE    (1<<7) | 
|   | 
| struct codec_connection { | 
|     /* CC_ flags from above */ | 
|     int connected; | 
|     /* codec dependent bit to be set in the aoa_codec.connected field. | 
|      * This intentionally doesn't have any generic flags because the | 
|      * fabric has to know the codec anyway and all codecs might have | 
|      * different connectors */ | 
|     int codec_bit; | 
| }; | 
|   | 
| struct codec_connect_info { | 
|     char *name; | 
|     struct codec_connection *connections; | 
| }; | 
|   | 
| #define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF    (1<<0) | 
|   | 
| struct layout { | 
|     unsigned int layout_id, device_id; | 
|     struct codec_connect_info codecs[MAX_CODECS_PER_BUS]; | 
|     int flags; | 
|   | 
|     /* if busname is not assigned, we use 'Master' below, | 
|      * so that our layout table doesn't need to be filled | 
|      * too much. | 
|      * We only assign these two if we expect to find more | 
|      * than one soundbus, i.e. on those machines with | 
|      * multiple layout-ids */ | 
|     char *busname; | 
|     int pcmid; | 
| }; | 
|   | 
| MODULE_ALIAS("sound-layout-36"); | 
| MODULE_ALIAS("sound-layout-41"); | 
| MODULE_ALIAS("sound-layout-45"); | 
| MODULE_ALIAS("sound-layout-47"); | 
| MODULE_ALIAS("sound-layout-48"); | 
| MODULE_ALIAS("sound-layout-49"); | 
| MODULE_ALIAS("sound-layout-50"); | 
| MODULE_ALIAS("sound-layout-51"); | 
| MODULE_ALIAS("sound-layout-56"); | 
| MODULE_ALIAS("sound-layout-57"); | 
| MODULE_ALIAS("sound-layout-58"); | 
| MODULE_ALIAS("sound-layout-60"); | 
| MODULE_ALIAS("sound-layout-61"); | 
| MODULE_ALIAS("sound-layout-62"); | 
| MODULE_ALIAS("sound-layout-64"); | 
| MODULE_ALIAS("sound-layout-65"); | 
| MODULE_ALIAS("sound-layout-66"); | 
| MODULE_ALIAS("sound-layout-67"); | 
| MODULE_ALIAS("sound-layout-68"); | 
| MODULE_ALIAS("sound-layout-69"); | 
| MODULE_ALIAS("sound-layout-70"); | 
| MODULE_ALIAS("sound-layout-72"); | 
| MODULE_ALIAS("sound-layout-76"); | 
| MODULE_ALIAS("sound-layout-80"); | 
| MODULE_ALIAS("sound-layout-82"); | 
| MODULE_ALIAS("sound-layout-84"); | 
| MODULE_ALIAS("sound-layout-86"); | 
| MODULE_ALIAS("sound-layout-90"); | 
| MODULE_ALIAS("sound-layout-92"); | 
| MODULE_ALIAS("sound-layout-94"); | 
| MODULE_ALIAS("sound-layout-96"); | 
| MODULE_ALIAS("sound-layout-98"); | 
| MODULE_ALIAS("sound-layout-100"); | 
|   | 
| MODULE_ALIAS("aoa-device-id-14"); | 
| MODULE_ALIAS("aoa-device-id-22"); | 
| MODULE_ALIAS("aoa-device-id-31"); | 
| MODULE_ALIAS("aoa-device-id-35"); | 
| MODULE_ALIAS("aoa-device-id-44"); | 
|   | 
| /* onyx with all but microphone connected */ | 
| static struct codec_connection onyx_connections_nomic[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_DIGITALOUT, | 
|         .codec_bit = 1, | 
|     }, | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* onyx on machines without headphone */ | 
| static struct codec_connection onyx_connections_noheadphones[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_LINEOUT | | 
|                  CC_LINEOUT_LABELLED_HEADPHONE, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_DIGITALOUT, | 
|         .codec_bit = 1, | 
|     }, | 
|     /* FIXME: are these correct? probably not for all the machines | 
|      * below ... If not this will need separating. */ | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     { | 
|         .connected = CC_MICROPHONE, | 
|         .codec_bit = 3, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* onyx on machines with real line-out */ | 
| static struct codec_connection onyx_connections_reallineout[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_DIGITALOUT, | 
|         .codec_bit = 1, | 
|     }, | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* tas on machines without line out */ | 
| static struct codec_connection tas_connections_nolineout[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     { | 
|         .connected = CC_MICROPHONE, | 
|         .codec_bit = 3, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* tas on machines with neither line out nor line in */ | 
| static struct codec_connection tas_connections_noline[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_MICROPHONE, | 
|         .codec_bit = 3, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* tas on machines without microphone */ | 
| static struct codec_connection tas_connections_nomic[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| /* tas on machines with everything connected */ | 
| static struct codec_connection tas_connections_all[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_LINEIN, | 
|         .codec_bit = 2, | 
|     }, | 
|     { | 
|         .connected = CC_MICROPHONE, | 
|         .codec_bit = 3, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| static struct codec_connection toonie_connections[] = { | 
|     { | 
|         .connected = CC_SPEAKERS | CC_HEADPHONE, | 
|         .codec_bit = 0, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| static struct codec_connection topaz_input[] = { | 
|     { | 
|         .connected = CC_DIGITALIN, | 
|         .codec_bit = 0, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| static struct codec_connection topaz_output[] = { | 
|     { | 
|         .connected = CC_DIGITALOUT, | 
|         .codec_bit = 1, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| static struct codec_connection topaz_inout[] = { | 
|     { | 
|         .connected = CC_DIGITALIN, | 
|         .codec_bit = 0, | 
|     }, | 
|     { | 
|         .connected = CC_DIGITALOUT, | 
|         .codec_bit = 1, | 
|     }, | 
|     {} /* terminate array by .connected == 0 */ | 
| }; | 
|   | 
| static struct layout layouts[] = { | 
|     /* last PowerBooks (15" Oct 2005) */ | 
|     { .layout_id = 82, | 
|       .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* PowerMac9,1 */ | 
|     { .layout_id = 60, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_reallineout, | 
|       }, | 
|     }, | 
|     /* PowerMac9,1 */ | 
|     { .layout_id = 61, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* PowerBook5,7 */ | 
|     { .layout_id = 64, | 
|       .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     /* PowerBook5,7 */ | 
|     { .layout_id = 65, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* PowerBook5,9 [17" Oct 2005] */ | 
|     { .layout_id = 84, | 
|       .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* PowerMac8,1 */ | 
|     { .layout_id = 45, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* Quad PowerMac (analog in, analog/digital out) */ | 
|     { .layout_id = 68, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_nomic, | 
|       }, | 
|     }, | 
|     /* Quad PowerMac (digital in) */ | 
|     { .layout_id = 69, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|       .busname = "digital in", .pcmid = 1 }, | 
|     /* Early 2005 PowerBook (PowerBook 5,6) */ | 
|     { .layout_id = 70, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nolineout, | 
|       }, | 
|     }, | 
|     /* PowerBook 5,4 */ | 
|     { .layout_id = 51, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nolineout, | 
|       }, | 
|     }, | 
|     /* PowerBook6,1 */ | 
|     { .device_id = 31, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nolineout, | 
|       }, | 
|     }, | 
|     /* PowerBook6,5 */ | 
|     { .device_id = 44, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_all, | 
|       }, | 
|     }, | 
|     /* PowerBook6,7 */ | 
|     { .layout_id = 80, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_noline, | 
|       }, | 
|     }, | 
|     /* PowerBook6,8 */ | 
|     { .layout_id = 72, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nolineout, | 
|       }, | 
|     }, | 
|     /* PowerMac8,2 */ | 
|     { .layout_id = 86, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_nomic, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     /* PowerBook6,7 */ | 
|     { .layout_id = 92, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nolineout, | 
|       }, | 
|     }, | 
|     /* PowerMac10,1 (Mac Mini) */ | 
|     { .layout_id = 58, | 
|       .codecs[0] = { | 
|         .name = "toonie", | 
|         .connections = toonie_connections, | 
|       }, | 
|     }, | 
|     { | 
|       .layout_id = 96, | 
|       .codecs[0] = { | 
|           .name = "onyx", | 
|           .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     /* unknown, untested, but this comes from Apple */ | 
|     { .layout_id = 41, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_all, | 
|       }, | 
|     }, | 
|     { .layout_id = 36, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nomic, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_inout, | 
|       }, | 
|     }, | 
|     { .layout_id = 47, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     { .layout_id = 48, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     { .layout_id = 49, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_nomic, | 
|       }, | 
|     }, | 
|     { .layout_id = 50, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     { .layout_id = 56, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     { .layout_id = 57, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     { .layout_id = 62, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_output, | 
|       }, | 
|     }, | 
|     { .layout_id = 66, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     { .layout_id = 67, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|     }, | 
|     { .layout_id = 76, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_nomic, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "topaz", | 
|         .connections = topaz_inout, | 
|       }, | 
|     }, | 
|     { .layout_id = 90, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_noline, | 
|       }, | 
|     }, | 
|     { .layout_id = 94, | 
|       .codecs[0] = { | 
|         .name = "onyx", | 
|         /* but it has an external mic?? how to select? */ | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     { .layout_id = 98, | 
|       .codecs[0] = { | 
|         .name = "toonie", | 
|         .connections = toonie_connections, | 
|       }, | 
|     }, | 
|     { .layout_id = 100, | 
|       .codecs[0] = { | 
|         .name = "topaz", | 
|         .connections = topaz_input, | 
|       }, | 
|       .codecs[1] = { | 
|         .name = "onyx", | 
|         .connections = onyx_connections_noheadphones, | 
|       }, | 
|     }, | 
|     /* PowerMac3,4 */ | 
|     { .device_id = 14, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_noline, | 
|       }, | 
|     }, | 
|     /* PowerMac3,6 */ | 
|     { .device_id = 22, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_all, | 
|       }, | 
|     }, | 
|     /* PowerBook5,2 */ | 
|     { .device_id = 35, | 
|       .codecs[0] = { | 
|         .name = "tas", | 
|         .connections = tas_connections_all, | 
|       }, | 
|     }, | 
|     {} | 
| }; | 
|   | 
| static struct layout *find_layout_by_id(unsigned int id) | 
| { | 
|     struct layout *l; | 
|   | 
|     l = layouts; | 
|     while (l->codecs[0].name) { | 
|         if (l->layout_id == id) | 
|             return l; | 
|         l++; | 
|     } | 
|     return NULL; | 
| } | 
|   | 
| static struct layout *find_layout_by_device(unsigned int id) | 
| { | 
|     struct layout *l; | 
|   | 
|     l = layouts; | 
|     while (l->codecs[0].name) { | 
|         if (l->device_id == id) | 
|             return l; | 
|         l++; | 
|     } | 
|     return NULL; | 
| } | 
|   | 
| static void use_layout(struct layout *l) | 
| { | 
|     int i; | 
|   | 
|     for (i=0; i<MAX_CODECS_PER_BUS; i++) { | 
|         if (l->codecs[i].name) { | 
|             request_module("snd-aoa-codec-%s", l->codecs[i].name); | 
|         } | 
|     } | 
|     /* now we wait for the codecs to call us back */ | 
| } | 
|   | 
| struct layout_dev; | 
|   | 
| struct layout_dev_ptr { | 
|     struct layout_dev *ptr; | 
| }; | 
|   | 
| struct layout_dev { | 
|     struct list_head list; | 
|     struct soundbus_dev *sdev; | 
|     struct device_node *sound; | 
|     struct aoa_codec *codecs[MAX_CODECS_PER_BUS]; | 
|     struct layout *layout; | 
|     struct gpio_runtime gpio; | 
|   | 
|     /* we need these for headphone/lineout detection */ | 
|     struct snd_kcontrol *headphone_ctrl; | 
|     struct snd_kcontrol *lineout_ctrl; | 
|     struct snd_kcontrol *speaker_ctrl; | 
|     struct snd_kcontrol *master_ctrl; | 
|     struct snd_kcontrol *headphone_detected_ctrl; | 
|     struct snd_kcontrol *lineout_detected_ctrl; | 
|   | 
|     struct layout_dev_ptr selfptr_headphone; | 
|     struct layout_dev_ptr selfptr_lineout; | 
|   | 
|     u32 have_lineout_detect:1, | 
|         have_headphone_detect:1, | 
|         switch_on_headphone:1, | 
|         switch_on_lineout:1; | 
| }; | 
|   | 
| static LIST_HEAD(layouts_list); | 
| static int layouts_list_items; | 
| /* this can go away but only if we allow multiple cards, | 
|  * make the fabric handle all the card stuff, etc... */ | 
| static struct layout_dev *layout_device; | 
|   | 
| #define control_info    snd_ctl_boolean_mono_info | 
|   | 
| #define AMP_CONTROL(n, description)                    \ | 
| static int n##_control_get(struct snd_kcontrol *kcontrol,        \ | 
|                struct snd_ctl_elem_value *ucontrol)        \ | 
| {                                    \ | 
|     struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);    \ | 
|     if (gpio->methods && gpio->methods->get_##n)            \ | 
|         ucontrol->value.integer.value[0] =            \ | 
|             gpio->methods->get_##n(gpio);            \ | 
|     return 0;                            \ | 
| }                                    \ | 
| static int n##_control_put(struct snd_kcontrol *kcontrol,        \ | 
|                struct snd_ctl_elem_value *ucontrol)        \ | 
| {                                    \ | 
|     struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);    \ | 
|     if (gpio->methods && gpio->methods->set_##n)            \ | 
|         gpio->methods->set_##n(gpio,                \ | 
|             !!ucontrol->value.integer.value[0]);        \ | 
|     return 1;                            \ | 
| }                                    \ | 
| static const struct snd_kcontrol_new n##_ctl = {            \ | 
|     .iface = SNDRV_CTL_ELEM_IFACE_MIXER,                \ | 
|     .name = description,                        \ | 
|     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \ | 
|     .info = control_info,                        \ | 
|     .get = n##_control_get,                        \ | 
|     .put = n##_control_put,                        \ | 
| } | 
|   | 
| AMP_CONTROL(headphone, "Headphone Switch"); | 
| AMP_CONTROL(speakers, "Speakers Switch"); | 
| AMP_CONTROL(lineout, "Line-Out Switch"); | 
| AMP_CONTROL(master, "Master Switch"); | 
|   | 
| static int detect_choice_get(struct snd_kcontrol *kcontrol, | 
|                  struct snd_ctl_elem_value *ucontrol) | 
| { | 
|     struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | 
|   | 
|     switch (kcontrol->private_value) { | 
|     case 0: | 
|         ucontrol->value.integer.value[0] = ldev->switch_on_headphone; | 
|         break; | 
|     case 1: | 
|         ucontrol->value.integer.value[0] = ldev->switch_on_lineout; | 
|         break; | 
|     default: | 
|         return -ENODEV; | 
|     } | 
|     return 0; | 
| } | 
|   | 
| static int detect_choice_put(struct snd_kcontrol *kcontrol, | 
|                  struct snd_ctl_elem_value *ucontrol) | 
| { | 
|     struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | 
|   | 
|     switch (kcontrol->private_value) { | 
|     case 0: | 
|         ldev->switch_on_headphone = !!ucontrol->value.integer.value[0]; | 
|         break; | 
|     case 1: | 
|         ldev->switch_on_lineout = !!ucontrol->value.integer.value[0]; | 
|         break; | 
|     default: | 
|         return -ENODEV; | 
|     } | 
|     return 1; | 
| } | 
|   | 
| static const struct snd_kcontrol_new headphone_detect_choice = { | 
|     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | 
|     .name = "Headphone Detect Autoswitch", | 
|     .info = control_info, | 
|     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | 
|     .get = detect_choice_get, | 
|     .put = detect_choice_put, | 
|     .private_value = 0, | 
| }; | 
|   | 
| static const struct snd_kcontrol_new lineout_detect_choice = { | 
|     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | 
|     .name = "Line-Out Detect Autoswitch", | 
|     .info = control_info, | 
|     .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | 
|     .get = detect_choice_get, | 
|     .put = detect_choice_put, | 
|     .private_value = 1, | 
| }; | 
|   | 
| static int detected_get(struct snd_kcontrol *kcontrol, | 
|             struct snd_ctl_elem_value *ucontrol) | 
| { | 
|     struct layout_dev *ldev = snd_kcontrol_chip(kcontrol); | 
|     int v; | 
|   | 
|     switch (kcontrol->private_value) { | 
|     case 0: | 
|         v = ldev->gpio.methods->get_detect(&ldev->gpio, | 
|                            AOA_NOTIFY_HEADPHONE); | 
|         break; | 
|     case 1: | 
|         v = ldev->gpio.methods->get_detect(&ldev->gpio, | 
|                            AOA_NOTIFY_LINE_OUT); | 
|         break; | 
|     default: | 
|         return -ENODEV; | 
|     } | 
|     ucontrol->value.integer.value[0] = v; | 
|     return 0; | 
| } | 
|   | 
| static const struct snd_kcontrol_new headphone_detected = { | 
|     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | 
|     .name = "Headphone Detected", | 
|     .info = control_info, | 
|     .access = SNDRV_CTL_ELEM_ACCESS_READ, | 
|     .get = detected_get, | 
|     .private_value = 0, | 
| }; | 
|   | 
| static const struct snd_kcontrol_new lineout_detected = { | 
|     .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | 
|     .name = "Line-Out Detected", | 
|     .info = control_info, | 
|     .access = SNDRV_CTL_ELEM_ACCESS_READ, | 
|     .get = detected_get, | 
|     .private_value = 1, | 
| }; | 
|   | 
| static int check_codec(struct aoa_codec *codec, | 
|                struct layout_dev *ldev, | 
|                struct codec_connect_info *cci) | 
| { | 
|     const u32 *ref; | 
|     char propname[32]; | 
|     struct codec_connection *cc; | 
|   | 
|     /* if the codec has a 'codec' node, we require a reference */ | 
|     if (of_node_name_eq(codec->node, "codec")) { | 
|         snprintf(propname, sizeof(propname), | 
|              "platform-%s-codec-ref", codec->name); | 
|         ref = of_get_property(ldev->sound, propname, NULL); | 
|         if (!ref) { | 
|             printk(KERN_INFO "snd-aoa-fabric-layout: " | 
|                 "required property %s not present\n", propname); | 
|             return -ENODEV; | 
|         } | 
|         if (*ref != codec->node->phandle) { | 
|             printk(KERN_INFO "snd-aoa-fabric-layout: " | 
|                 "%s doesn't match!\n", propname); | 
|             return -ENODEV; | 
|         } | 
|     } else { | 
|         if (layouts_list_items != 1) { | 
|             printk(KERN_INFO "snd-aoa-fabric-layout: " | 
|                 "more than one soundbus, but no references.\n"); | 
|             return -ENODEV; | 
|         } | 
|     } | 
|     codec->soundbus_dev = ldev->sdev; | 
|     codec->gpio = &ldev->gpio; | 
|   | 
|     cc = cci->connections; | 
|     if (!cc) | 
|         return -EINVAL; | 
|   | 
|     printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n"); | 
|   | 
|     codec->connected = 0; | 
|     codec->fabric_data = cc; | 
|   | 
|     while (cc->connected) { | 
|         codec->connected |= 1<<cc->codec_bit; | 
|         cc++; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int layout_found_codec(struct aoa_codec *codec) | 
| { | 
|     struct layout_dev *ldev; | 
|     int i; | 
|   | 
|     list_for_each_entry(ldev, &layouts_list, list) { | 
|         for (i=0; i<MAX_CODECS_PER_BUS; i++) { | 
|             if (!ldev->layout->codecs[i].name) | 
|                 continue; | 
|             if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) { | 
|                 if (check_codec(codec, | 
|                         ldev, | 
|                         &ldev->layout->codecs[i]) == 0) | 
|                     return 0; | 
|             } | 
|         } | 
|     } | 
|     return -ENODEV; | 
| } | 
|   | 
| static void layout_remove_codec(struct aoa_codec *codec) | 
| { | 
|     int i; | 
|     /* here remove the codec from the layout dev's | 
|      * codec reference */ | 
|   | 
|     codec->soundbus_dev = NULL; | 
|     codec->gpio = NULL; | 
|     for (i=0; i<MAX_CODECS_PER_BUS; i++) { | 
|     } | 
| } | 
|   | 
| static void layout_notify(void *data) | 
| { | 
|     struct layout_dev_ptr *dptr = data; | 
|     struct layout_dev *ldev; | 
|     int v, update; | 
|     struct snd_kcontrol *detected, *c; | 
|     struct snd_card *card = aoa_get_card(); | 
|   | 
|     ldev = dptr->ptr; | 
|     if (data == &ldev->selfptr_headphone) { | 
|         v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE); | 
|         detected = ldev->headphone_detected_ctrl; | 
|         update = ldev->switch_on_headphone; | 
|         if (update) { | 
|             ldev->gpio.methods->set_speakers(&ldev->gpio, !v); | 
|             ldev->gpio.methods->set_headphone(&ldev->gpio, v); | 
|             ldev->gpio.methods->set_lineout(&ldev->gpio, 0); | 
|         } | 
|     } else if (data == &ldev->selfptr_lineout) { | 
|         v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT); | 
|         detected = ldev->lineout_detected_ctrl; | 
|         update = ldev->switch_on_lineout; | 
|         if (update) { | 
|             ldev->gpio.methods->set_speakers(&ldev->gpio, !v); | 
|             ldev->gpio.methods->set_headphone(&ldev->gpio, 0); | 
|             ldev->gpio.methods->set_lineout(&ldev->gpio, v); | 
|         } | 
|     } else | 
|         return; | 
|   | 
|     if (detected) | 
|         snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id); | 
|     if (update) { | 
|         c = ldev->headphone_ctrl; | 
|         if (c) | 
|             snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | 
|         c = ldev->speaker_ctrl; | 
|         if (c) | 
|             snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | 
|         c = ldev->lineout_ctrl; | 
|         if (c) | 
|             snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id); | 
|     } | 
| } | 
|   | 
| static void layout_attached_codec(struct aoa_codec *codec) | 
| { | 
|     struct codec_connection *cc; | 
|     struct snd_kcontrol *ctl; | 
|     int headphones, lineout; | 
|     struct layout_dev *ldev = layout_device; | 
|   | 
|     /* need to add this codec to our codec array! */ | 
|   | 
|     cc = codec->fabric_data; | 
|   | 
|     headphones = codec->gpio->methods->get_detect(codec->gpio, | 
|                               AOA_NOTIFY_HEADPHONE); | 
|      lineout = codec->gpio->methods->get_detect(codec->gpio, | 
|                            AOA_NOTIFY_LINE_OUT); | 
|   | 
|     if (codec->gpio->methods->set_master) { | 
|         ctl = snd_ctl_new1(&master_ctl, codec->gpio); | 
|         ldev->master_ctrl = ctl; | 
|         aoa_snd_ctl_add(ctl); | 
|     } | 
|     while (cc->connected) { | 
|         if (cc->connected & CC_SPEAKERS) { | 
|             if (headphones <= 0 && lineout <= 0) | 
|                 ldev->gpio.methods->set_speakers(codec->gpio, 1); | 
|             ctl = snd_ctl_new1(&speakers_ctl, codec->gpio); | 
|             ldev->speaker_ctrl = ctl; | 
|             aoa_snd_ctl_add(ctl); | 
|         } | 
|         if (cc->connected & CC_HEADPHONE) { | 
|             if (headphones == 1) | 
|                 ldev->gpio.methods->set_headphone(codec->gpio, 1); | 
|             ctl = snd_ctl_new1(&headphone_ctl, codec->gpio); | 
|             ldev->headphone_ctrl = ctl; | 
|             aoa_snd_ctl_add(ctl); | 
|             ldev->have_headphone_detect = | 
|                 !ldev->gpio.methods | 
|                     ->set_notify(&ldev->gpio, | 
|                              AOA_NOTIFY_HEADPHONE, | 
|                              layout_notify, | 
|                              &ldev->selfptr_headphone); | 
|             if (ldev->have_headphone_detect) { | 
|                 ctl = snd_ctl_new1(&headphone_detect_choice, | 
|                            ldev); | 
|                 aoa_snd_ctl_add(ctl); | 
|                 ctl = snd_ctl_new1(&headphone_detected, | 
|                            ldev); | 
|                 ldev->headphone_detected_ctrl = ctl; | 
|                 aoa_snd_ctl_add(ctl); | 
|             } | 
|         } | 
|         if (cc->connected & CC_LINEOUT) { | 
|             if (lineout == 1) | 
|                 ldev->gpio.methods->set_lineout(codec->gpio, 1); | 
|             ctl = snd_ctl_new1(&lineout_ctl, codec->gpio); | 
|             if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | 
|                 strlcpy(ctl->id.name, | 
|                     "Headphone Switch", sizeof(ctl->id.name)); | 
|             ldev->lineout_ctrl = ctl; | 
|             aoa_snd_ctl_add(ctl); | 
|             ldev->have_lineout_detect = | 
|                 !ldev->gpio.methods | 
|                     ->set_notify(&ldev->gpio, | 
|                              AOA_NOTIFY_LINE_OUT, | 
|                              layout_notify, | 
|                              &ldev->selfptr_lineout); | 
|             if (ldev->have_lineout_detect) { | 
|                 ctl = snd_ctl_new1(&lineout_detect_choice, | 
|                            ldev); | 
|                 if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | 
|                     strlcpy(ctl->id.name, | 
|                         "Headphone Detect Autoswitch", | 
|                         sizeof(ctl->id.name)); | 
|                 aoa_snd_ctl_add(ctl); | 
|                 ctl = snd_ctl_new1(&lineout_detected, | 
|                            ldev); | 
|                 if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE) | 
|                     strlcpy(ctl->id.name, | 
|                         "Headphone Detected", | 
|                         sizeof(ctl->id.name)); | 
|                 ldev->lineout_detected_ctrl = ctl; | 
|                 aoa_snd_ctl_add(ctl); | 
|             } | 
|         } | 
|         cc++; | 
|     } | 
|     /* now update initial state */ | 
|     if (ldev->have_headphone_detect) | 
|         layout_notify(&ldev->selfptr_headphone); | 
|     if (ldev->have_lineout_detect) | 
|         layout_notify(&ldev->selfptr_lineout); | 
| } | 
|   | 
| static struct aoa_fabric layout_fabric = { | 
|     .name = "SoundByLayout", | 
|     .owner = THIS_MODULE, | 
|     .found_codec = layout_found_codec, | 
|     .remove_codec = layout_remove_codec, | 
|     .attached_codec = layout_attached_codec, | 
| }; | 
|   | 
| static int aoa_fabric_layout_probe(struct soundbus_dev *sdev) | 
| { | 
|     struct device_node *sound = NULL; | 
|     const unsigned int *id; | 
|     struct layout *layout = NULL; | 
|     struct layout_dev *ldev = NULL; | 
|     int err; | 
|   | 
|     /* hm, currently we can only have one ... */ | 
|     if (layout_device) | 
|         return -ENODEV; | 
|   | 
|     /* by breaking out we keep a reference */ | 
|     for_each_child_of_node(sdev->ofdev.dev.of_node, sound) { | 
|         if (of_node_is_type(sound, "soundchip")) | 
|             break; | 
|     } | 
|     if (!sound) | 
|         return -ENODEV; | 
|   | 
|     id = of_get_property(sound, "layout-id", NULL); | 
|     if (id) { | 
|         layout = find_layout_by_id(*id); | 
|     } else { | 
|         id = of_get_property(sound, "device-id", NULL); | 
|         if (id) | 
|             layout = find_layout_by_device(*id); | 
|     } | 
|   | 
|     if (!layout) { | 
|         printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n"); | 
|         goto outnodev; | 
|     } | 
|   | 
|     ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL); | 
|     if (!ldev) | 
|         goto outnodev; | 
|   | 
|     layout_device = ldev; | 
|     ldev->sdev = sdev; | 
|     ldev->sound = sound; | 
|     ldev->layout = layout; | 
|     ldev->gpio.node = sound->parent; | 
|     switch (layout->layout_id) { | 
|     case 0:  /* anything with device_id, not layout_id */ | 
|     case 41: /* that unknown machine no one seems to have */ | 
|     case 51: /* PowerBook5,4 */ | 
|     case 58: /* Mac Mini */ | 
|         ldev->gpio.methods = ftr_gpio_methods; | 
|         printk(KERN_DEBUG | 
|                "snd-aoa-fabric-layout: Using direct GPIOs\n"); | 
|         break; | 
|     default: | 
|         ldev->gpio.methods = pmf_gpio_methods; | 
|         printk(KERN_DEBUG | 
|                "snd-aoa-fabric-layout: Using PMF GPIOs\n"); | 
|     } | 
|     ldev->selfptr_headphone.ptr = ldev; | 
|     ldev->selfptr_lineout.ptr = ldev; | 
|     dev_set_drvdata(&sdev->ofdev.dev, ldev); | 
|     list_add(&ldev->list, &layouts_list); | 
|     layouts_list_items++; | 
|   | 
|     /* assign these before registering ourselves, so | 
|      * callbacks that are done during registration | 
|      * already have the values */ | 
|     sdev->pcmid = ldev->layout->pcmid; | 
|     if (ldev->layout->busname) { | 
|         sdev->pcmname = ldev->layout->busname; | 
|     } else { | 
|         sdev->pcmname = "Master"; | 
|     } | 
|   | 
|     ldev->gpio.methods->init(&ldev->gpio); | 
|   | 
|     err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev); | 
|     if (err && err != -EALREADY) { | 
|         printk(KERN_INFO "snd-aoa-fabric-layout: can't use," | 
|                  " another fabric is active!\n"); | 
|         goto outlistdel; | 
|     } | 
|   | 
|     use_layout(layout); | 
|     ldev->switch_on_headphone = 1; | 
|     ldev->switch_on_lineout = 1; | 
|     return 0; | 
|  outlistdel: | 
|     /* we won't be using these then... */ | 
|     ldev->gpio.methods->exit(&ldev->gpio); | 
|     /* reset if we didn't use it */ | 
|     sdev->pcmname = NULL; | 
|     sdev->pcmid = -1; | 
|     list_del(&ldev->list); | 
|     layouts_list_items--; | 
|     kfree(ldev); | 
|  outnodev: | 
|      of_node_put(sound); | 
|      layout_device = NULL; | 
|     return -ENODEV; | 
| } | 
|   | 
| static int aoa_fabric_layout_remove(struct soundbus_dev *sdev) | 
| { | 
|     struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); | 
|     int i; | 
|   | 
|     for (i=0; i<MAX_CODECS_PER_BUS; i++) { | 
|         if (ldev->codecs[i]) { | 
|             aoa_fabric_unlink_codec(ldev->codecs[i]); | 
|         } | 
|         ldev->codecs[i] = NULL; | 
|     } | 
|     list_del(&ldev->list); | 
|     layouts_list_items--; | 
|     of_node_put(ldev->sound); | 
|   | 
|     ldev->gpio.methods->set_notify(&ldev->gpio, | 
|                        AOA_NOTIFY_HEADPHONE, | 
|                        NULL, | 
|                        NULL); | 
|     ldev->gpio.methods->set_notify(&ldev->gpio, | 
|                        AOA_NOTIFY_LINE_OUT, | 
|                        NULL, | 
|                        NULL); | 
|   | 
|     ldev->gpio.methods->exit(&ldev->gpio); | 
|     layout_device = NULL; | 
|     kfree(ldev); | 
|     sdev->pcmid = -1; | 
|     sdev->pcmname = NULL; | 
|     return 0; | 
| } | 
|   | 
| #ifdef CONFIG_PM_SLEEP | 
| static int aoa_fabric_layout_suspend(struct device *dev) | 
| { | 
|     struct layout_dev *ldev = dev_get_drvdata(dev); | 
|   | 
|     if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) | 
|         ldev->gpio.methods->all_amps_off(&ldev->gpio); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static int aoa_fabric_layout_resume(struct device *dev) | 
| { | 
|     struct layout_dev *ldev = dev_get_drvdata(dev); | 
|   | 
|     if (ldev->gpio.methods && ldev->gpio.methods->all_amps_restore) | 
|         ldev->gpio.methods->all_amps_restore(&ldev->gpio); | 
|   | 
|     return 0; | 
| } | 
|   | 
| static SIMPLE_DEV_PM_OPS(aoa_fabric_layout_pm_ops, | 
|     aoa_fabric_layout_suspend, aoa_fabric_layout_resume); | 
|   | 
| #endif | 
|   | 
| static struct soundbus_driver aoa_soundbus_driver = { | 
|     .name = "snd_aoa_soundbus_drv", | 
|     .owner = THIS_MODULE, | 
|     .probe = aoa_fabric_layout_probe, | 
|     .remove = aoa_fabric_layout_remove, | 
|     .driver = { | 
|         .owner = THIS_MODULE, | 
| #ifdef CONFIG_PM_SLEEP | 
|         .pm = &aoa_fabric_layout_pm_ops, | 
| #endif | 
|     } | 
| }; | 
|   | 
| static int __init aoa_fabric_layout_init(void) | 
| { | 
|     return soundbus_register_driver(&aoa_soundbus_driver); | 
| } | 
|   | 
| static void __exit aoa_fabric_layout_exit(void) | 
| { | 
|     soundbus_unregister_driver(&aoa_soundbus_driver); | 
|     aoa_fabric_unregister(&layout_fabric); | 
| } | 
|   | 
| module_init(aoa_fabric_layout_init); | 
| module_exit(aoa_fabric_layout_exit); |