// 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) strscpy(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) strscpy(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) strscpy(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 void 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; } #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);