| /* | 
 |  * Apple Onboard Audio driver -- layout/machine id fabric | 
 |  * | 
 |  * Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net> | 
 |  * | 
 |  * GPL v2, can be found in COPYING. | 
 |  * | 
 |  * | 
 |  * 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 "../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-35"); | 
 |  | 
 | /* 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,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->get_##n)			\ | 
 | 		gpio->methods->set_##n(gpio,				\ | 
 | 			!!ucontrol->value.integer.value[0]);		\ | 
 | 	return 1;							\ | 
 | }									\ | 
 | static 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 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 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 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 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 (codec->node && (strcmp(codec->node->name, "codec") == 0)) { | 
 | 		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->linux_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 */ | 
 | 	while ((sound = of_get_next_child(sdev->ofdev.node, sound))) { | 
 | 		if (sound->type && strcasecmp(sound->type, "soundchip") == 0) | 
 | 			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--; | 
 |  outnodev: | 
 |  	of_node_put(sound); | 
 |  	layout_device = NULL; | 
 |  	kfree(ldev); | 
 | 	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 | 
 | static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state) | 
 | { | 
 | 	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.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 soundbus_dev *sdev) | 
 | { | 
 | 	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev); | 
 |  | 
 | 	if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off) | 
 | 		ldev->gpio.methods->all_amps_restore(&ldev->gpio); | 
 |  | 
 | 	return 0; | 
 | } | 
 | #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, | 
 | #ifdef CONFIG_PM | 
 | 	.suspend = aoa_fabric_layout_suspend, | 
 | 	.resume = aoa_fabric_layout_resume, | 
 | #endif | 
 | 	.driver = { | 
 | 		.owner = THIS_MODULE, | 
 | 	} | 
 | }; | 
 |  | 
 | static int __init aoa_fabric_layout_init(void) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = soundbus_register_driver(&aoa_soundbus_driver); | 
 | 	if (err) | 
 | 		return err; | 
 | 	return 0; | 
 | } | 
 |  | 
 | 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); |