18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Apple Onboard Audio driver -- layout/machine id fabric
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright 2006-2008 Johannes Berg <johannes@sipsolutions.net>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * This fabric module looks for sound codecs based on the
88c2ecf20Sopenharmony_ci * layout-id or device-id property in the device tree.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <asm/prom.h>
118c2ecf20Sopenharmony_ci#include <linux/list.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include "../aoa.h"
158c2ecf20Sopenharmony_ci#include "../soundbus/soundbus.h"
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ciMODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
188c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define MAX_CODECS_PER_BUS	2
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci/* These are the connections the layout fabric
248c2ecf20Sopenharmony_ci * knows about. It doesn't really care about the
258c2ecf20Sopenharmony_ci * input ones, but I thought I'd separate them
268c2ecf20Sopenharmony_ci * to give them proper names. The thing is that
278c2ecf20Sopenharmony_ci * Apple usually will distinguish the active output
288c2ecf20Sopenharmony_ci * by GPIOs, while the active input is set directly
298c2ecf20Sopenharmony_ci * on the codec. Hence we here tell the codec what
308c2ecf20Sopenharmony_ci * we think is connected. This information is hard-
318c2ecf20Sopenharmony_ci * coded below ... */
328c2ecf20Sopenharmony_ci#define CC_SPEAKERS	(1<<0)
338c2ecf20Sopenharmony_ci#define CC_HEADPHONE	(1<<1)
348c2ecf20Sopenharmony_ci#define CC_LINEOUT	(1<<2)
358c2ecf20Sopenharmony_ci#define CC_DIGITALOUT	(1<<3)
368c2ecf20Sopenharmony_ci#define CC_LINEIN	(1<<4)
378c2ecf20Sopenharmony_ci#define CC_MICROPHONE	(1<<5)
388c2ecf20Sopenharmony_ci#define CC_DIGITALIN	(1<<6)
398c2ecf20Sopenharmony_ci/* pretty bogus but users complain...
408c2ecf20Sopenharmony_ci * This is a flag saying that the LINEOUT
418c2ecf20Sopenharmony_ci * should be renamed to HEADPHONE.
428c2ecf20Sopenharmony_ci * be careful with input detection! */
438c2ecf20Sopenharmony_ci#define CC_LINEOUT_LABELLED_HEADPHONE	(1<<7)
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistruct codec_connection {
468c2ecf20Sopenharmony_ci	/* CC_ flags from above */
478c2ecf20Sopenharmony_ci	int connected;
488c2ecf20Sopenharmony_ci	/* codec dependent bit to be set in the aoa_codec.connected field.
498c2ecf20Sopenharmony_ci	 * This intentionally doesn't have any generic flags because the
508c2ecf20Sopenharmony_ci	 * fabric has to know the codec anyway and all codecs might have
518c2ecf20Sopenharmony_ci	 * different connectors */
528c2ecf20Sopenharmony_ci	int codec_bit;
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_cistruct codec_connect_info {
568c2ecf20Sopenharmony_ci	char *name;
578c2ecf20Sopenharmony_ci	struct codec_connection *connections;
588c2ecf20Sopenharmony_ci};
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF	(1<<0)
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistruct layout {
638c2ecf20Sopenharmony_ci	unsigned int layout_id, device_id;
648c2ecf20Sopenharmony_ci	struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
658c2ecf20Sopenharmony_ci	int flags;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	/* if busname is not assigned, we use 'Master' below,
688c2ecf20Sopenharmony_ci	 * so that our layout table doesn't need to be filled
698c2ecf20Sopenharmony_ci	 * too much.
708c2ecf20Sopenharmony_ci	 * We only assign these two if we expect to find more
718c2ecf20Sopenharmony_ci	 * than one soundbus, i.e. on those machines with
728c2ecf20Sopenharmony_ci	 * multiple layout-ids */
738c2ecf20Sopenharmony_ci	char *busname;
748c2ecf20Sopenharmony_ci	int pcmid;
758c2ecf20Sopenharmony_ci};
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-36");
788c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-41");
798c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-45");
808c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-47");
818c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-48");
828c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-49");
838c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-50");
848c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-51");
858c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-56");
868c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-57");
878c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-58");
888c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-60");
898c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-61");
908c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-62");
918c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-64");
928c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-65");
938c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-66");
948c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-67");
958c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-68");
968c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-69");
978c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-70");
988c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-72");
998c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-76");
1008c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-80");
1018c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-82");
1028c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-84");
1038c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-86");
1048c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-90");
1058c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-92");
1068c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-94");
1078c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-96");
1088c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-98");
1098c2ecf20Sopenharmony_ciMODULE_ALIAS("sound-layout-100");
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ciMODULE_ALIAS("aoa-device-id-14");
1128c2ecf20Sopenharmony_ciMODULE_ALIAS("aoa-device-id-22");
1138c2ecf20Sopenharmony_ciMODULE_ALIAS("aoa-device-id-31");
1148c2ecf20Sopenharmony_ciMODULE_ALIAS("aoa-device-id-35");
1158c2ecf20Sopenharmony_ciMODULE_ALIAS("aoa-device-id-44");
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci/* onyx with all but microphone connected */
1188c2ecf20Sopenharmony_cistatic struct codec_connection onyx_connections_nomic[] = {
1198c2ecf20Sopenharmony_ci	{
1208c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
1218c2ecf20Sopenharmony_ci		.codec_bit = 0,
1228c2ecf20Sopenharmony_ci	},
1238c2ecf20Sopenharmony_ci	{
1248c2ecf20Sopenharmony_ci		.connected = CC_DIGITALOUT,
1258c2ecf20Sopenharmony_ci		.codec_bit = 1,
1268c2ecf20Sopenharmony_ci	},
1278c2ecf20Sopenharmony_ci	{
1288c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
1298c2ecf20Sopenharmony_ci		.codec_bit = 2,
1308c2ecf20Sopenharmony_ci	},
1318c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
1328c2ecf20Sopenharmony_ci};
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci/* onyx on machines without headphone */
1358c2ecf20Sopenharmony_cistatic struct codec_connection onyx_connections_noheadphones[] = {
1368c2ecf20Sopenharmony_ci	{
1378c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_LINEOUT |
1388c2ecf20Sopenharmony_ci			     CC_LINEOUT_LABELLED_HEADPHONE,
1398c2ecf20Sopenharmony_ci		.codec_bit = 0,
1408c2ecf20Sopenharmony_ci	},
1418c2ecf20Sopenharmony_ci	{
1428c2ecf20Sopenharmony_ci		.connected = CC_DIGITALOUT,
1438c2ecf20Sopenharmony_ci		.codec_bit = 1,
1448c2ecf20Sopenharmony_ci	},
1458c2ecf20Sopenharmony_ci	/* FIXME: are these correct? probably not for all the machines
1468c2ecf20Sopenharmony_ci	 * below ... If not this will need separating. */
1478c2ecf20Sopenharmony_ci	{
1488c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
1498c2ecf20Sopenharmony_ci		.codec_bit = 2,
1508c2ecf20Sopenharmony_ci	},
1518c2ecf20Sopenharmony_ci	{
1528c2ecf20Sopenharmony_ci		.connected = CC_MICROPHONE,
1538c2ecf20Sopenharmony_ci		.codec_bit = 3,
1548c2ecf20Sopenharmony_ci	},
1558c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
1568c2ecf20Sopenharmony_ci};
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci/* onyx on machines with real line-out */
1598c2ecf20Sopenharmony_cistatic struct codec_connection onyx_connections_reallineout[] = {
1608c2ecf20Sopenharmony_ci	{
1618c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
1628c2ecf20Sopenharmony_ci		.codec_bit = 0,
1638c2ecf20Sopenharmony_ci	},
1648c2ecf20Sopenharmony_ci	{
1658c2ecf20Sopenharmony_ci		.connected = CC_DIGITALOUT,
1668c2ecf20Sopenharmony_ci		.codec_bit = 1,
1678c2ecf20Sopenharmony_ci	},
1688c2ecf20Sopenharmony_ci	{
1698c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
1708c2ecf20Sopenharmony_ci		.codec_bit = 2,
1718c2ecf20Sopenharmony_ci	},
1728c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
1738c2ecf20Sopenharmony_ci};
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci/* tas on machines without line out */
1768c2ecf20Sopenharmony_cistatic struct codec_connection tas_connections_nolineout[] = {
1778c2ecf20Sopenharmony_ci	{
1788c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE,
1798c2ecf20Sopenharmony_ci		.codec_bit = 0,
1808c2ecf20Sopenharmony_ci	},
1818c2ecf20Sopenharmony_ci	{
1828c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
1838c2ecf20Sopenharmony_ci		.codec_bit = 2,
1848c2ecf20Sopenharmony_ci	},
1858c2ecf20Sopenharmony_ci	{
1868c2ecf20Sopenharmony_ci		.connected = CC_MICROPHONE,
1878c2ecf20Sopenharmony_ci		.codec_bit = 3,
1888c2ecf20Sopenharmony_ci	},
1898c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
1908c2ecf20Sopenharmony_ci};
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci/* tas on machines with neither line out nor line in */
1938c2ecf20Sopenharmony_cistatic struct codec_connection tas_connections_noline[] = {
1948c2ecf20Sopenharmony_ci	{
1958c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE,
1968c2ecf20Sopenharmony_ci		.codec_bit = 0,
1978c2ecf20Sopenharmony_ci	},
1988c2ecf20Sopenharmony_ci	{
1998c2ecf20Sopenharmony_ci		.connected = CC_MICROPHONE,
2008c2ecf20Sopenharmony_ci		.codec_bit = 3,
2018c2ecf20Sopenharmony_ci	},
2028c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2038c2ecf20Sopenharmony_ci};
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci/* tas on machines without microphone */
2068c2ecf20Sopenharmony_cistatic struct codec_connection tas_connections_nomic[] = {
2078c2ecf20Sopenharmony_ci	{
2088c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
2098c2ecf20Sopenharmony_ci		.codec_bit = 0,
2108c2ecf20Sopenharmony_ci	},
2118c2ecf20Sopenharmony_ci	{
2128c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
2138c2ecf20Sopenharmony_ci		.codec_bit = 2,
2148c2ecf20Sopenharmony_ci	},
2158c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2168c2ecf20Sopenharmony_ci};
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci/* tas on machines with everything connected */
2198c2ecf20Sopenharmony_cistatic struct codec_connection tas_connections_all[] = {
2208c2ecf20Sopenharmony_ci	{
2218c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
2228c2ecf20Sopenharmony_ci		.codec_bit = 0,
2238c2ecf20Sopenharmony_ci	},
2248c2ecf20Sopenharmony_ci	{
2258c2ecf20Sopenharmony_ci		.connected = CC_LINEIN,
2268c2ecf20Sopenharmony_ci		.codec_bit = 2,
2278c2ecf20Sopenharmony_ci	},
2288c2ecf20Sopenharmony_ci	{
2298c2ecf20Sopenharmony_ci		.connected = CC_MICROPHONE,
2308c2ecf20Sopenharmony_ci		.codec_bit = 3,
2318c2ecf20Sopenharmony_ci	},
2328c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2338c2ecf20Sopenharmony_ci};
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_cistatic struct codec_connection toonie_connections[] = {
2368c2ecf20Sopenharmony_ci	{
2378c2ecf20Sopenharmony_ci		.connected = CC_SPEAKERS | CC_HEADPHONE,
2388c2ecf20Sopenharmony_ci		.codec_bit = 0,
2398c2ecf20Sopenharmony_ci	},
2408c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2418c2ecf20Sopenharmony_ci};
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_cistatic struct codec_connection topaz_input[] = {
2448c2ecf20Sopenharmony_ci	{
2458c2ecf20Sopenharmony_ci		.connected = CC_DIGITALIN,
2468c2ecf20Sopenharmony_ci		.codec_bit = 0,
2478c2ecf20Sopenharmony_ci	},
2488c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2498c2ecf20Sopenharmony_ci};
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_cistatic struct codec_connection topaz_output[] = {
2528c2ecf20Sopenharmony_ci	{
2538c2ecf20Sopenharmony_ci		.connected = CC_DIGITALOUT,
2548c2ecf20Sopenharmony_ci		.codec_bit = 1,
2558c2ecf20Sopenharmony_ci	},
2568c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2578c2ecf20Sopenharmony_ci};
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_cistatic struct codec_connection topaz_inout[] = {
2608c2ecf20Sopenharmony_ci	{
2618c2ecf20Sopenharmony_ci		.connected = CC_DIGITALIN,
2628c2ecf20Sopenharmony_ci		.codec_bit = 0,
2638c2ecf20Sopenharmony_ci	},
2648c2ecf20Sopenharmony_ci	{
2658c2ecf20Sopenharmony_ci		.connected = CC_DIGITALOUT,
2668c2ecf20Sopenharmony_ci		.codec_bit = 1,
2678c2ecf20Sopenharmony_ci	},
2688c2ecf20Sopenharmony_ci	{} /* terminate array by .connected == 0 */
2698c2ecf20Sopenharmony_ci};
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_cistatic struct layout layouts[] = {
2728c2ecf20Sopenharmony_ci	/* last PowerBooks (15" Oct 2005) */
2738c2ecf20Sopenharmony_ci	{ .layout_id = 82,
2748c2ecf20Sopenharmony_ci	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
2758c2ecf20Sopenharmony_ci	  .codecs[0] = {
2768c2ecf20Sopenharmony_ci		.name = "onyx",
2778c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
2788c2ecf20Sopenharmony_ci	  },
2798c2ecf20Sopenharmony_ci	  .codecs[1] = {
2808c2ecf20Sopenharmony_ci		.name = "topaz",
2818c2ecf20Sopenharmony_ci		.connections = topaz_input,
2828c2ecf20Sopenharmony_ci	  },
2838c2ecf20Sopenharmony_ci	},
2848c2ecf20Sopenharmony_ci	/* PowerMac9,1 */
2858c2ecf20Sopenharmony_ci	{ .layout_id = 60,
2868c2ecf20Sopenharmony_ci	  .codecs[0] = {
2878c2ecf20Sopenharmony_ci		.name = "onyx",
2888c2ecf20Sopenharmony_ci		.connections = onyx_connections_reallineout,
2898c2ecf20Sopenharmony_ci	  },
2908c2ecf20Sopenharmony_ci	},
2918c2ecf20Sopenharmony_ci	/* PowerMac9,1 */
2928c2ecf20Sopenharmony_ci	{ .layout_id = 61,
2938c2ecf20Sopenharmony_ci	  .codecs[0] = {
2948c2ecf20Sopenharmony_ci		.name = "topaz",
2958c2ecf20Sopenharmony_ci		.connections = topaz_input,
2968c2ecf20Sopenharmony_ci	  },
2978c2ecf20Sopenharmony_ci	},
2988c2ecf20Sopenharmony_ci	/* PowerBook5,7 */
2998c2ecf20Sopenharmony_ci	{ .layout_id = 64,
3008c2ecf20Sopenharmony_ci	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
3018c2ecf20Sopenharmony_ci	  .codecs[0] = {
3028c2ecf20Sopenharmony_ci		.name = "onyx",
3038c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
3048c2ecf20Sopenharmony_ci	  },
3058c2ecf20Sopenharmony_ci	},
3068c2ecf20Sopenharmony_ci	/* PowerBook5,7 */
3078c2ecf20Sopenharmony_ci	{ .layout_id = 65,
3088c2ecf20Sopenharmony_ci	  .codecs[0] = {
3098c2ecf20Sopenharmony_ci		.name = "topaz",
3108c2ecf20Sopenharmony_ci		.connections = topaz_input,
3118c2ecf20Sopenharmony_ci	  },
3128c2ecf20Sopenharmony_ci	},
3138c2ecf20Sopenharmony_ci	/* PowerBook5,9 [17" Oct 2005] */
3148c2ecf20Sopenharmony_ci	{ .layout_id = 84,
3158c2ecf20Sopenharmony_ci	  .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
3168c2ecf20Sopenharmony_ci	  .codecs[0] = {
3178c2ecf20Sopenharmony_ci		.name = "onyx",
3188c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
3198c2ecf20Sopenharmony_ci	  },
3208c2ecf20Sopenharmony_ci	  .codecs[1] = {
3218c2ecf20Sopenharmony_ci		.name = "topaz",
3228c2ecf20Sopenharmony_ci		.connections = topaz_input,
3238c2ecf20Sopenharmony_ci	  },
3248c2ecf20Sopenharmony_ci	},
3258c2ecf20Sopenharmony_ci	/* PowerMac8,1 */
3268c2ecf20Sopenharmony_ci	{ .layout_id = 45,
3278c2ecf20Sopenharmony_ci	  .codecs[0] = {
3288c2ecf20Sopenharmony_ci		.name = "onyx",
3298c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
3308c2ecf20Sopenharmony_ci	  },
3318c2ecf20Sopenharmony_ci	  .codecs[1] = {
3328c2ecf20Sopenharmony_ci		.name = "topaz",
3338c2ecf20Sopenharmony_ci		.connections = topaz_input,
3348c2ecf20Sopenharmony_ci	  },
3358c2ecf20Sopenharmony_ci	},
3368c2ecf20Sopenharmony_ci	/* Quad PowerMac (analog in, analog/digital out) */
3378c2ecf20Sopenharmony_ci	{ .layout_id = 68,
3388c2ecf20Sopenharmony_ci	  .codecs[0] = {
3398c2ecf20Sopenharmony_ci		.name = "onyx",
3408c2ecf20Sopenharmony_ci		.connections = onyx_connections_nomic,
3418c2ecf20Sopenharmony_ci	  },
3428c2ecf20Sopenharmony_ci	},
3438c2ecf20Sopenharmony_ci	/* Quad PowerMac (digital in) */
3448c2ecf20Sopenharmony_ci	{ .layout_id = 69,
3458c2ecf20Sopenharmony_ci	  .codecs[0] = {
3468c2ecf20Sopenharmony_ci		.name = "topaz",
3478c2ecf20Sopenharmony_ci		.connections = topaz_input,
3488c2ecf20Sopenharmony_ci	  },
3498c2ecf20Sopenharmony_ci	  .busname = "digital in", .pcmid = 1 },
3508c2ecf20Sopenharmony_ci	/* Early 2005 PowerBook (PowerBook 5,6) */
3518c2ecf20Sopenharmony_ci	{ .layout_id = 70,
3528c2ecf20Sopenharmony_ci	  .codecs[0] = {
3538c2ecf20Sopenharmony_ci		.name = "tas",
3548c2ecf20Sopenharmony_ci		.connections = tas_connections_nolineout,
3558c2ecf20Sopenharmony_ci	  },
3568c2ecf20Sopenharmony_ci	},
3578c2ecf20Sopenharmony_ci	/* PowerBook 5,4 */
3588c2ecf20Sopenharmony_ci	{ .layout_id = 51,
3598c2ecf20Sopenharmony_ci	  .codecs[0] = {
3608c2ecf20Sopenharmony_ci		.name = "tas",
3618c2ecf20Sopenharmony_ci		.connections = tas_connections_nolineout,
3628c2ecf20Sopenharmony_ci	  },
3638c2ecf20Sopenharmony_ci	},
3648c2ecf20Sopenharmony_ci	/* PowerBook6,1 */
3658c2ecf20Sopenharmony_ci	{ .device_id = 31,
3668c2ecf20Sopenharmony_ci	  .codecs[0] = {
3678c2ecf20Sopenharmony_ci		.name = "tas",
3688c2ecf20Sopenharmony_ci		.connections = tas_connections_nolineout,
3698c2ecf20Sopenharmony_ci	  },
3708c2ecf20Sopenharmony_ci	},
3718c2ecf20Sopenharmony_ci	/* PowerBook6,5 */
3728c2ecf20Sopenharmony_ci	{ .device_id = 44,
3738c2ecf20Sopenharmony_ci	  .codecs[0] = {
3748c2ecf20Sopenharmony_ci		.name = "tas",
3758c2ecf20Sopenharmony_ci		.connections = tas_connections_all,
3768c2ecf20Sopenharmony_ci	  },
3778c2ecf20Sopenharmony_ci	},
3788c2ecf20Sopenharmony_ci	/* PowerBook6,7 */
3798c2ecf20Sopenharmony_ci	{ .layout_id = 80,
3808c2ecf20Sopenharmony_ci	  .codecs[0] = {
3818c2ecf20Sopenharmony_ci		.name = "tas",
3828c2ecf20Sopenharmony_ci		.connections = tas_connections_noline,
3838c2ecf20Sopenharmony_ci	  },
3848c2ecf20Sopenharmony_ci	},
3858c2ecf20Sopenharmony_ci	/* PowerBook6,8 */
3868c2ecf20Sopenharmony_ci	{ .layout_id = 72,
3878c2ecf20Sopenharmony_ci	  .codecs[0] = {
3888c2ecf20Sopenharmony_ci		.name = "tas",
3898c2ecf20Sopenharmony_ci		.connections = tas_connections_nolineout,
3908c2ecf20Sopenharmony_ci	  },
3918c2ecf20Sopenharmony_ci	},
3928c2ecf20Sopenharmony_ci	/* PowerMac8,2 */
3938c2ecf20Sopenharmony_ci	{ .layout_id = 86,
3948c2ecf20Sopenharmony_ci	  .codecs[0] = {
3958c2ecf20Sopenharmony_ci		.name = "onyx",
3968c2ecf20Sopenharmony_ci		.connections = onyx_connections_nomic,
3978c2ecf20Sopenharmony_ci	  },
3988c2ecf20Sopenharmony_ci	  .codecs[1] = {
3998c2ecf20Sopenharmony_ci		.name = "topaz",
4008c2ecf20Sopenharmony_ci		.connections = topaz_input,
4018c2ecf20Sopenharmony_ci	  },
4028c2ecf20Sopenharmony_ci	},
4038c2ecf20Sopenharmony_ci	/* PowerBook6,7 */
4048c2ecf20Sopenharmony_ci	{ .layout_id = 92,
4058c2ecf20Sopenharmony_ci	  .codecs[0] = {
4068c2ecf20Sopenharmony_ci		.name = "tas",
4078c2ecf20Sopenharmony_ci		.connections = tas_connections_nolineout,
4088c2ecf20Sopenharmony_ci	  },
4098c2ecf20Sopenharmony_ci	},
4108c2ecf20Sopenharmony_ci	/* PowerMac10,1 (Mac Mini) */
4118c2ecf20Sopenharmony_ci	{ .layout_id = 58,
4128c2ecf20Sopenharmony_ci	  .codecs[0] = {
4138c2ecf20Sopenharmony_ci		.name = "toonie",
4148c2ecf20Sopenharmony_ci		.connections = toonie_connections,
4158c2ecf20Sopenharmony_ci	  },
4168c2ecf20Sopenharmony_ci	},
4178c2ecf20Sopenharmony_ci	{
4188c2ecf20Sopenharmony_ci	  .layout_id = 96,
4198c2ecf20Sopenharmony_ci	  .codecs[0] = {
4208c2ecf20Sopenharmony_ci	  	.name = "onyx",
4218c2ecf20Sopenharmony_ci	  	.connections = onyx_connections_noheadphones,
4228c2ecf20Sopenharmony_ci	  },
4238c2ecf20Sopenharmony_ci	},
4248c2ecf20Sopenharmony_ci	/* unknown, untested, but this comes from Apple */
4258c2ecf20Sopenharmony_ci	{ .layout_id = 41,
4268c2ecf20Sopenharmony_ci	  .codecs[0] = {
4278c2ecf20Sopenharmony_ci		.name = "tas",
4288c2ecf20Sopenharmony_ci		.connections = tas_connections_all,
4298c2ecf20Sopenharmony_ci	  },
4308c2ecf20Sopenharmony_ci	},
4318c2ecf20Sopenharmony_ci	{ .layout_id = 36,
4328c2ecf20Sopenharmony_ci	  .codecs[0] = {
4338c2ecf20Sopenharmony_ci		.name = "tas",
4348c2ecf20Sopenharmony_ci		.connections = tas_connections_nomic,
4358c2ecf20Sopenharmony_ci	  },
4368c2ecf20Sopenharmony_ci	  .codecs[1] = {
4378c2ecf20Sopenharmony_ci		.name = "topaz",
4388c2ecf20Sopenharmony_ci		.connections = topaz_inout,
4398c2ecf20Sopenharmony_ci	  },
4408c2ecf20Sopenharmony_ci	},
4418c2ecf20Sopenharmony_ci	{ .layout_id = 47,
4428c2ecf20Sopenharmony_ci	  .codecs[0] = {
4438c2ecf20Sopenharmony_ci		.name = "onyx",
4448c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
4458c2ecf20Sopenharmony_ci	  },
4468c2ecf20Sopenharmony_ci	},
4478c2ecf20Sopenharmony_ci	{ .layout_id = 48,
4488c2ecf20Sopenharmony_ci	  .codecs[0] = {
4498c2ecf20Sopenharmony_ci		.name = "topaz",
4508c2ecf20Sopenharmony_ci		.connections = topaz_input,
4518c2ecf20Sopenharmony_ci	  },
4528c2ecf20Sopenharmony_ci	},
4538c2ecf20Sopenharmony_ci	{ .layout_id = 49,
4548c2ecf20Sopenharmony_ci	  .codecs[0] = {
4558c2ecf20Sopenharmony_ci		.name = "onyx",
4568c2ecf20Sopenharmony_ci		.connections = onyx_connections_nomic,
4578c2ecf20Sopenharmony_ci	  },
4588c2ecf20Sopenharmony_ci	},
4598c2ecf20Sopenharmony_ci	{ .layout_id = 50,
4608c2ecf20Sopenharmony_ci	  .codecs[0] = {
4618c2ecf20Sopenharmony_ci		.name = "topaz",
4628c2ecf20Sopenharmony_ci		.connections = topaz_input,
4638c2ecf20Sopenharmony_ci	  },
4648c2ecf20Sopenharmony_ci	},
4658c2ecf20Sopenharmony_ci	{ .layout_id = 56,
4668c2ecf20Sopenharmony_ci	  .codecs[0] = {
4678c2ecf20Sopenharmony_ci		.name = "onyx",
4688c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
4698c2ecf20Sopenharmony_ci	  },
4708c2ecf20Sopenharmony_ci	},
4718c2ecf20Sopenharmony_ci	{ .layout_id = 57,
4728c2ecf20Sopenharmony_ci	  .codecs[0] = {
4738c2ecf20Sopenharmony_ci		.name = "topaz",
4748c2ecf20Sopenharmony_ci		.connections = topaz_input,
4758c2ecf20Sopenharmony_ci	  },
4768c2ecf20Sopenharmony_ci	},
4778c2ecf20Sopenharmony_ci	{ .layout_id = 62,
4788c2ecf20Sopenharmony_ci	  .codecs[0] = {
4798c2ecf20Sopenharmony_ci		.name = "onyx",
4808c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
4818c2ecf20Sopenharmony_ci	  },
4828c2ecf20Sopenharmony_ci	  .codecs[1] = {
4838c2ecf20Sopenharmony_ci		.name = "topaz",
4848c2ecf20Sopenharmony_ci		.connections = topaz_output,
4858c2ecf20Sopenharmony_ci	  },
4868c2ecf20Sopenharmony_ci	},
4878c2ecf20Sopenharmony_ci	{ .layout_id = 66,
4888c2ecf20Sopenharmony_ci	  .codecs[0] = {
4898c2ecf20Sopenharmony_ci		.name = "onyx",
4908c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
4918c2ecf20Sopenharmony_ci	  },
4928c2ecf20Sopenharmony_ci	},
4938c2ecf20Sopenharmony_ci	{ .layout_id = 67,
4948c2ecf20Sopenharmony_ci	  .codecs[0] = {
4958c2ecf20Sopenharmony_ci		.name = "topaz",
4968c2ecf20Sopenharmony_ci		.connections = topaz_input,
4978c2ecf20Sopenharmony_ci	  },
4988c2ecf20Sopenharmony_ci	},
4998c2ecf20Sopenharmony_ci	{ .layout_id = 76,
5008c2ecf20Sopenharmony_ci	  .codecs[0] = {
5018c2ecf20Sopenharmony_ci		.name = "tas",
5028c2ecf20Sopenharmony_ci		.connections = tas_connections_nomic,
5038c2ecf20Sopenharmony_ci	  },
5048c2ecf20Sopenharmony_ci	  .codecs[1] = {
5058c2ecf20Sopenharmony_ci		.name = "topaz",
5068c2ecf20Sopenharmony_ci		.connections = topaz_inout,
5078c2ecf20Sopenharmony_ci	  },
5088c2ecf20Sopenharmony_ci	},
5098c2ecf20Sopenharmony_ci	{ .layout_id = 90,
5108c2ecf20Sopenharmony_ci	  .codecs[0] = {
5118c2ecf20Sopenharmony_ci		.name = "tas",
5128c2ecf20Sopenharmony_ci		.connections = tas_connections_noline,
5138c2ecf20Sopenharmony_ci	  },
5148c2ecf20Sopenharmony_ci	},
5158c2ecf20Sopenharmony_ci	{ .layout_id = 94,
5168c2ecf20Sopenharmony_ci	  .codecs[0] = {
5178c2ecf20Sopenharmony_ci		.name = "onyx",
5188c2ecf20Sopenharmony_ci		/* but it has an external mic?? how to select? */
5198c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
5208c2ecf20Sopenharmony_ci	  },
5218c2ecf20Sopenharmony_ci	},
5228c2ecf20Sopenharmony_ci	{ .layout_id = 98,
5238c2ecf20Sopenharmony_ci	  .codecs[0] = {
5248c2ecf20Sopenharmony_ci		.name = "toonie",
5258c2ecf20Sopenharmony_ci		.connections = toonie_connections,
5268c2ecf20Sopenharmony_ci	  },
5278c2ecf20Sopenharmony_ci	},
5288c2ecf20Sopenharmony_ci	{ .layout_id = 100,
5298c2ecf20Sopenharmony_ci	  .codecs[0] = {
5308c2ecf20Sopenharmony_ci		.name = "topaz",
5318c2ecf20Sopenharmony_ci		.connections = topaz_input,
5328c2ecf20Sopenharmony_ci	  },
5338c2ecf20Sopenharmony_ci	  .codecs[1] = {
5348c2ecf20Sopenharmony_ci		.name = "onyx",
5358c2ecf20Sopenharmony_ci		.connections = onyx_connections_noheadphones,
5368c2ecf20Sopenharmony_ci	  },
5378c2ecf20Sopenharmony_ci	},
5388c2ecf20Sopenharmony_ci	/* PowerMac3,4 */
5398c2ecf20Sopenharmony_ci	{ .device_id = 14,
5408c2ecf20Sopenharmony_ci	  .codecs[0] = {
5418c2ecf20Sopenharmony_ci		.name = "tas",
5428c2ecf20Sopenharmony_ci		.connections = tas_connections_noline,
5438c2ecf20Sopenharmony_ci	  },
5448c2ecf20Sopenharmony_ci	},
5458c2ecf20Sopenharmony_ci	/* PowerMac3,6 */
5468c2ecf20Sopenharmony_ci	{ .device_id = 22,
5478c2ecf20Sopenharmony_ci	  .codecs[0] = {
5488c2ecf20Sopenharmony_ci		.name = "tas",
5498c2ecf20Sopenharmony_ci		.connections = tas_connections_all,
5508c2ecf20Sopenharmony_ci	  },
5518c2ecf20Sopenharmony_ci	},
5528c2ecf20Sopenharmony_ci	/* PowerBook5,2 */
5538c2ecf20Sopenharmony_ci	{ .device_id = 35,
5548c2ecf20Sopenharmony_ci	  .codecs[0] = {
5558c2ecf20Sopenharmony_ci		.name = "tas",
5568c2ecf20Sopenharmony_ci		.connections = tas_connections_all,
5578c2ecf20Sopenharmony_ci	  },
5588c2ecf20Sopenharmony_ci	},
5598c2ecf20Sopenharmony_ci	{}
5608c2ecf20Sopenharmony_ci};
5618c2ecf20Sopenharmony_ci
5628c2ecf20Sopenharmony_cistatic struct layout *find_layout_by_id(unsigned int id)
5638c2ecf20Sopenharmony_ci{
5648c2ecf20Sopenharmony_ci	struct layout *l;
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	l = layouts;
5678c2ecf20Sopenharmony_ci	while (l->codecs[0].name) {
5688c2ecf20Sopenharmony_ci		if (l->layout_id == id)
5698c2ecf20Sopenharmony_ci			return l;
5708c2ecf20Sopenharmony_ci		l++;
5718c2ecf20Sopenharmony_ci	}
5728c2ecf20Sopenharmony_ci	return NULL;
5738c2ecf20Sopenharmony_ci}
5748c2ecf20Sopenharmony_ci
5758c2ecf20Sopenharmony_cistatic struct layout *find_layout_by_device(unsigned int id)
5768c2ecf20Sopenharmony_ci{
5778c2ecf20Sopenharmony_ci	struct layout *l;
5788c2ecf20Sopenharmony_ci
5798c2ecf20Sopenharmony_ci	l = layouts;
5808c2ecf20Sopenharmony_ci	while (l->codecs[0].name) {
5818c2ecf20Sopenharmony_ci		if (l->device_id == id)
5828c2ecf20Sopenharmony_ci			return l;
5838c2ecf20Sopenharmony_ci		l++;
5848c2ecf20Sopenharmony_ci	}
5858c2ecf20Sopenharmony_ci	return NULL;
5868c2ecf20Sopenharmony_ci}
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_cistatic void use_layout(struct layout *l)
5898c2ecf20Sopenharmony_ci{
5908c2ecf20Sopenharmony_ci	int i;
5918c2ecf20Sopenharmony_ci
5928c2ecf20Sopenharmony_ci	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
5938c2ecf20Sopenharmony_ci		if (l->codecs[i].name) {
5948c2ecf20Sopenharmony_ci			request_module("snd-aoa-codec-%s", l->codecs[i].name);
5958c2ecf20Sopenharmony_ci		}
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci	/* now we wait for the codecs to call us back */
5988c2ecf20Sopenharmony_ci}
5998c2ecf20Sopenharmony_ci
6008c2ecf20Sopenharmony_cistruct layout_dev;
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_cistruct layout_dev_ptr {
6038c2ecf20Sopenharmony_ci	struct layout_dev *ptr;
6048c2ecf20Sopenharmony_ci};
6058c2ecf20Sopenharmony_ci
6068c2ecf20Sopenharmony_cistruct layout_dev {
6078c2ecf20Sopenharmony_ci	struct list_head list;
6088c2ecf20Sopenharmony_ci	struct soundbus_dev *sdev;
6098c2ecf20Sopenharmony_ci	struct device_node *sound;
6108c2ecf20Sopenharmony_ci	struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
6118c2ecf20Sopenharmony_ci	struct layout *layout;
6128c2ecf20Sopenharmony_ci	struct gpio_runtime gpio;
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_ci	/* we need these for headphone/lineout detection */
6158c2ecf20Sopenharmony_ci	struct snd_kcontrol *headphone_ctrl;
6168c2ecf20Sopenharmony_ci	struct snd_kcontrol *lineout_ctrl;
6178c2ecf20Sopenharmony_ci	struct snd_kcontrol *speaker_ctrl;
6188c2ecf20Sopenharmony_ci	struct snd_kcontrol *master_ctrl;
6198c2ecf20Sopenharmony_ci	struct snd_kcontrol *headphone_detected_ctrl;
6208c2ecf20Sopenharmony_ci	struct snd_kcontrol *lineout_detected_ctrl;
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_ci	struct layout_dev_ptr selfptr_headphone;
6238c2ecf20Sopenharmony_ci	struct layout_dev_ptr selfptr_lineout;
6248c2ecf20Sopenharmony_ci
6258c2ecf20Sopenharmony_ci	u32 have_lineout_detect:1,
6268c2ecf20Sopenharmony_ci	    have_headphone_detect:1,
6278c2ecf20Sopenharmony_ci	    switch_on_headphone:1,
6288c2ecf20Sopenharmony_ci	    switch_on_lineout:1;
6298c2ecf20Sopenharmony_ci};
6308c2ecf20Sopenharmony_ci
6318c2ecf20Sopenharmony_cistatic LIST_HEAD(layouts_list);
6328c2ecf20Sopenharmony_cistatic int layouts_list_items;
6338c2ecf20Sopenharmony_ci/* this can go away but only if we allow multiple cards,
6348c2ecf20Sopenharmony_ci * make the fabric handle all the card stuff, etc... */
6358c2ecf20Sopenharmony_cistatic struct layout_dev *layout_device;
6368c2ecf20Sopenharmony_ci
6378c2ecf20Sopenharmony_ci#define control_info	snd_ctl_boolean_mono_info
6388c2ecf20Sopenharmony_ci
6398c2ecf20Sopenharmony_ci#define AMP_CONTROL(n, description)					\
6408c2ecf20Sopenharmony_cistatic int n##_control_get(struct snd_kcontrol *kcontrol,		\
6418c2ecf20Sopenharmony_ci			   struct snd_ctl_elem_value *ucontrol)		\
6428c2ecf20Sopenharmony_ci{									\
6438c2ecf20Sopenharmony_ci	struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);	\
6448c2ecf20Sopenharmony_ci	if (gpio->methods && gpio->methods->get_##n)			\
6458c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[0] =			\
6468c2ecf20Sopenharmony_ci			gpio->methods->get_##n(gpio);			\
6478c2ecf20Sopenharmony_ci	return 0;							\
6488c2ecf20Sopenharmony_ci}									\
6498c2ecf20Sopenharmony_cistatic int n##_control_put(struct snd_kcontrol *kcontrol,		\
6508c2ecf20Sopenharmony_ci			   struct snd_ctl_elem_value *ucontrol)		\
6518c2ecf20Sopenharmony_ci{									\
6528c2ecf20Sopenharmony_ci	struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);	\
6538c2ecf20Sopenharmony_ci	if (gpio->methods && gpio->methods->set_##n)			\
6548c2ecf20Sopenharmony_ci		gpio->methods->set_##n(gpio,				\
6558c2ecf20Sopenharmony_ci			!!ucontrol->value.integer.value[0]);		\
6568c2ecf20Sopenharmony_ci	return 1;							\
6578c2ecf20Sopenharmony_ci}									\
6588c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new n##_ctl = {			\
6598c2ecf20Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,				\
6608c2ecf20Sopenharmony_ci	.name = description,						\
6618c2ecf20Sopenharmony_ci	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
6628c2ecf20Sopenharmony_ci	.info = control_info,						\
6638c2ecf20Sopenharmony_ci	.get = n##_control_get,						\
6648c2ecf20Sopenharmony_ci	.put = n##_control_put,						\
6658c2ecf20Sopenharmony_ci}
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ciAMP_CONTROL(headphone, "Headphone Switch");
6688c2ecf20Sopenharmony_ciAMP_CONTROL(speakers, "Speakers Switch");
6698c2ecf20Sopenharmony_ciAMP_CONTROL(lineout, "Line-Out Switch");
6708c2ecf20Sopenharmony_ciAMP_CONTROL(master, "Master Switch");
6718c2ecf20Sopenharmony_ci
6728c2ecf20Sopenharmony_cistatic int detect_choice_get(struct snd_kcontrol *kcontrol,
6738c2ecf20Sopenharmony_ci			     struct snd_ctl_elem_value *ucontrol)
6748c2ecf20Sopenharmony_ci{
6758c2ecf20Sopenharmony_ci	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
6768c2ecf20Sopenharmony_ci
6778c2ecf20Sopenharmony_ci	switch (kcontrol->private_value) {
6788c2ecf20Sopenharmony_ci	case 0:
6798c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[0] = ldev->switch_on_headphone;
6808c2ecf20Sopenharmony_ci		break;
6818c2ecf20Sopenharmony_ci	case 1:
6828c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[0] = ldev->switch_on_lineout;
6838c2ecf20Sopenharmony_ci		break;
6848c2ecf20Sopenharmony_ci	default:
6858c2ecf20Sopenharmony_ci		return -ENODEV;
6868c2ecf20Sopenharmony_ci	}
6878c2ecf20Sopenharmony_ci	return 0;
6888c2ecf20Sopenharmony_ci}
6898c2ecf20Sopenharmony_ci
6908c2ecf20Sopenharmony_cistatic int detect_choice_put(struct snd_kcontrol *kcontrol,
6918c2ecf20Sopenharmony_ci			     struct snd_ctl_elem_value *ucontrol)
6928c2ecf20Sopenharmony_ci{
6938c2ecf20Sopenharmony_ci	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
6948c2ecf20Sopenharmony_ci
6958c2ecf20Sopenharmony_ci	switch (kcontrol->private_value) {
6968c2ecf20Sopenharmony_ci	case 0:
6978c2ecf20Sopenharmony_ci		ldev->switch_on_headphone = !!ucontrol->value.integer.value[0];
6988c2ecf20Sopenharmony_ci		break;
6998c2ecf20Sopenharmony_ci	case 1:
7008c2ecf20Sopenharmony_ci		ldev->switch_on_lineout = !!ucontrol->value.integer.value[0];
7018c2ecf20Sopenharmony_ci		break;
7028c2ecf20Sopenharmony_ci	default:
7038c2ecf20Sopenharmony_ci		return -ENODEV;
7048c2ecf20Sopenharmony_ci	}
7058c2ecf20Sopenharmony_ci	return 1;
7068c2ecf20Sopenharmony_ci}
7078c2ecf20Sopenharmony_ci
7088c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new headphone_detect_choice = {
7098c2ecf20Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
7108c2ecf20Sopenharmony_ci	.name = "Headphone Detect Autoswitch",
7118c2ecf20Sopenharmony_ci	.info = control_info,
7128c2ecf20Sopenharmony_ci	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
7138c2ecf20Sopenharmony_ci	.get = detect_choice_get,
7148c2ecf20Sopenharmony_ci	.put = detect_choice_put,
7158c2ecf20Sopenharmony_ci	.private_value = 0,
7168c2ecf20Sopenharmony_ci};
7178c2ecf20Sopenharmony_ci
7188c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new lineout_detect_choice = {
7198c2ecf20Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
7208c2ecf20Sopenharmony_ci	.name = "Line-Out Detect Autoswitch",
7218c2ecf20Sopenharmony_ci	.info = control_info,
7228c2ecf20Sopenharmony_ci	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
7238c2ecf20Sopenharmony_ci	.get = detect_choice_get,
7248c2ecf20Sopenharmony_ci	.put = detect_choice_put,
7258c2ecf20Sopenharmony_ci	.private_value = 1,
7268c2ecf20Sopenharmony_ci};
7278c2ecf20Sopenharmony_ci
7288c2ecf20Sopenharmony_cistatic int detected_get(struct snd_kcontrol *kcontrol,
7298c2ecf20Sopenharmony_ci			struct snd_ctl_elem_value *ucontrol)
7308c2ecf20Sopenharmony_ci{
7318c2ecf20Sopenharmony_ci	struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
7328c2ecf20Sopenharmony_ci	int v;
7338c2ecf20Sopenharmony_ci
7348c2ecf20Sopenharmony_ci	switch (kcontrol->private_value) {
7358c2ecf20Sopenharmony_ci	case 0:
7368c2ecf20Sopenharmony_ci		v = ldev->gpio.methods->get_detect(&ldev->gpio,
7378c2ecf20Sopenharmony_ci						   AOA_NOTIFY_HEADPHONE);
7388c2ecf20Sopenharmony_ci		break;
7398c2ecf20Sopenharmony_ci	case 1:
7408c2ecf20Sopenharmony_ci		v = ldev->gpio.methods->get_detect(&ldev->gpio,
7418c2ecf20Sopenharmony_ci						   AOA_NOTIFY_LINE_OUT);
7428c2ecf20Sopenharmony_ci		break;
7438c2ecf20Sopenharmony_ci	default:
7448c2ecf20Sopenharmony_ci		return -ENODEV;
7458c2ecf20Sopenharmony_ci	}
7468c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = v;
7478c2ecf20Sopenharmony_ci	return 0;
7488c2ecf20Sopenharmony_ci}
7498c2ecf20Sopenharmony_ci
7508c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new headphone_detected = {
7518c2ecf20Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
7528c2ecf20Sopenharmony_ci	.name = "Headphone Detected",
7538c2ecf20Sopenharmony_ci	.info = control_info,
7548c2ecf20Sopenharmony_ci	.access = SNDRV_CTL_ELEM_ACCESS_READ,
7558c2ecf20Sopenharmony_ci	.get = detected_get,
7568c2ecf20Sopenharmony_ci	.private_value = 0,
7578c2ecf20Sopenharmony_ci};
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_cistatic const struct snd_kcontrol_new lineout_detected = {
7608c2ecf20Sopenharmony_ci	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
7618c2ecf20Sopenharmony_ci	.name = "Line-Out Detected",
7628c2ecf20Sopenharmony_ci	.info = control_info,
7638c2ecf20Sopenharmony_ci	.access = SNDRV_CTL_ELEM_ACCESS_READ,
7648c2ecf20Sopenharmony_ci	.get = detected_get,
7658c2ecf20Sopenharmony_ci	.private_value = 1,
7668c2ecf20Sopenharmony_ci};
7678c2ecf20Sopenharmony_ci
7688c2ecf20Sopenharmony_cistatic int check_codec(struct aoa_codec *codec,
7698c2ecf20Sopenharmony_ci		       struct layout_dev *ldev,
7708c2ecf20Sopenharmony_ci		       struct codec_connect_info *cci)
7718c2ecf20Sopenharmony_ci{
7728c2ecf20Sopenharmony_ci	const u32 *ref;
7738c2ecf20Sopenharmony_ci	char propname[32];
7748c2ecf20Sopenharmony_ci	struct codec_connection *cc;
7758c2ecf20Sopenharmony_ci
7768c2ecf20Sopenharmony_ci	/* if the codec has a 'codec' node, we require a reference */
7778c2ecf20Sopenharmony_ci	if (of_node_name_eq(codec->node, "codec")) {
7788c2ecf20Sopenharmony_ci		snprintf(propname, sizeof(propname),
7798c2ecf20Sopenharmony_ci			 "platform-%s-codec-ref", codec->name);
7808c2ecf20Sopenharmony_ci		ref = of_get_property(ldev->sound, propname, NULL);
7818c2ecf20Sopenharmony_ci		if (!ref) {
7828c2ecf20Sopenharmony_ci			printk(KERN_INFO "snd-aoa-fabric-layout: "
7838c2ecf20Sopenharmony_ci				"required property %s not present\n", propname);
7848c2ecf20Sopenharmony_ci			return -ENODEV;
7858c2ecf20Sopenharmony_ci		}
7868c2ecf20Sopenharmony_ci		if (*ref != codec->node->phandle) {
7878c2ecf20Sopenharmony_ci			printk(KERN_INFO "snd-aoa-fabric-layout: "
7888c2ecf20Sopenharmony_ci				"%s doesn't match!\n", propname);
7898c2ecf20Sopenharmony_ci			return -ENODEV;
7908c2ecf20Sopenharmony_ci		}
7918c2ecf20Sopenharmony_ci	} else {
7928c2ecf20Sopenharmony_ci		if (layouts_list_items != 1) {
7938c2ecf20Sopenharmony_ci			printk(KERN_INFO "snd-aoa-fabric-layout: "
7948c2ecf20Sopenharmony_ci				"more than one soundbus, but no references.\n");
7958c2ecf20Sopenharmony_ci			return -ENODEV;
7968c2ecf20Sopenharmony_ci		}
7978c2ecf20Sopenharmony_ci	}
7988c2ecf20Sopenharmony_ci	codec->soundbus_dev = ldev->sdev;
7998c2ecf20Sopenharmony_ci	codec->gpio = &ldev->gpio;
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci	cc = cci->connections;
8028c2ecf20Sopenharmony_ci	if (!cc)
8038c2ecf20Sopenharmony_ci		return -EINVAL;
8048c2ecf20Sopenharmony_ci
8058c2ecf20Sopenharmony_ci	printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n");
8068c2ecf20Sopenharmony_ci
8078c2ecf20Sopenharmony_ci	codec->connected = 0;
8088c2ecf20Sopenharmony_ci	codec->fabric_data = cc;
8098c2ecf20Sopenharmony_ci
8108c2ecf20Sopenharmony_ci	while (cc->connected) {
8118c2ecf20Sopenharmony_ci		codec->connected |= 1<<cc->codec_bit;
8128c2ecf20Sopenharmony_ci		cc++;
8138c2ecf20Sopenharmony_ci	}
8148c2ecf20Sopenharmony_ci
8158c2ecf20Sopenharmony_ci	return 0;
8168c2ecf20Sopenharmony_ci}
8178c2ecf20Sopenharmony_ci
8188c2ecf20Sopenharmony_cistatic int layout_found_codec(struct aoa_codec *codec)
8198c2ecf20Sopenharmony_ci{
8208c2ecf20Sopenharmony_ci	struct layout_dev *ldev;
8218c2ecf20Sopenharmony_ci	int i;
8228c2ecf20Sopenharmony_ci
8238c2ecf20Sopenharmony_ci	list_for_each_entry(ldev, &layouts_list, list) {
8248c2ecf20Sopenharmony_ci		for (i=0; i<MAX_CODECS_PER_BUS; i++) {
8258c2ecf20Sopenharmony_ci			if (!ldev->layout->codecs[i].name)
8268c2ecf20Sopenharmony_ci				continue;
8278c2ecf20Sopenharmony_ci			if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) {
8288c2ecf20Sopenharmony_ci				if (check_codec(codec,
8298c2ecf20Sopenharmony_ci						ldev,
8308c2ecf20Sopenharmony_ci						&ldev->layout->codecs[i]) == 0)
8318c2ecf20Sopenharmony_ci					return 0;
8328c2ecf20Sopenharmony_ci			}
8338c2ecf20Sopenharmony_ci		}
8348c2ecf20Sopenharmony_ci	}
8358c2ecf20Sopenharmony_ci	return -ENODEV;
8368c2ecf20Sopenharmony_ci}
8378c2ecf20Sopenharmony_ci
8388c2ecf20Sopenharmony_cistatic void layout_remove_codec(struct aoa_codec *codec)
8398c2ecf20Sopenharmony_ci{
8408c2ecf20Sopenharmony_ci	int i;
8418c2ecf20Sopenharmony_ci	/* here remove the codec from the layout dev's
8428c2ecf20Sopenharmony_ci	 * codec reference */
8438c2ecf20Sopenharmony_ci
8448c2ecf20Sopenharmony_ci	codec->soundbus_dev = NULL;
8458c2ecf20Sopenharmony_ci	codec->gpio = NULL;
8468c2ecf20Sopenharmony_ci	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
8478c2ecf20Sopenharmony_ci	}
8488c2ecf20Sopenharmony_ci}
8498c2ecf20Sopenharmony_ci
8508c2ecf20Sopenharmony_cistatic void layout_notify(void *data)
8518c2ecf20Sopenharmony_ci{
8528c2ecf20Sopenharmony_ci	struct layout_dev_ptr *dptr = data;
8538c2ecf20Sopenharmony_ci	struct layout_dev *ldev;
8548c2ecf20Sopenharmony_ci	int v, update;
8558c2ecf20Sopenharmony_ci	struct snd_kcontrol *detected, *c;
8568c2ecf20Sopenharmony_ci	struct snd_card *card = aoa_get_card();
8578c2ecf20Sopenharmony_ci
8588c2ecf20Sopenharmony_ci	ldev = dptr->ptr;
8598c2ecf20Sopenharmony_ci	if (data == &ldev->selfptr_headphone) {
8608c2ecf20Sopenharmony_ci		v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE);
8618c2ecf20Sopenharmony_ci		detected = ldev->headphone_detected_ctrl;
8628c2ecf20Sopenharmony_ci		update = ldev->switch_on_headphone;
8638c2ecf20Sopenharmony_ci		if (update) {
8648c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
8658c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_headphone(&ldev->gpio, v);
8668c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_lineout(&ldev->gpio, 0);
8678c2ecf20Sopenharmony_ci		}
8688c2ecf20Sopenharmony_ci	} else if (data == &ldev->selfptr_lineout) {
8698c2ecf20Sopenharmony_ci		v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT);
8708c2ecf20Sopenharmony_ci		detected = ldev->lineout_detected_ctrl;
8718c2ecf20Sopenharmony_ci		update = ldev->switch_on_lineout;
8728c2ecf20Sopenharmony_ci		if (update) {
8738c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
8748c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_headphone(&ldev->gpio, 0);
8758c2ecf20Sopenharmony_ci			ldev->gpio.methods->set_lineout(&ldev->gpio, v);
8768c2ecf20Sopenharmony_ci		}
8778c2ecf20Sopenharmony_ci	} else
8788c2ecf20Sopenharmony_ci		return;
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_ci	if (detected)
8818c2ecf20Sopenharmony_ci		snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id);
8828c2ecf20Sopenharmony_ci	if (update) {
8838c2ecf20Sopenharmony_ci		c = ldev->headphone_ctrl;
8848c2ecf20Sopenharmony_ci		if (c)
8858c2ecf20Sopenharmony_ci			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
8868c2ecf20Sopenharmony_ci		c = ldev->speaker_ctrl;
8878c2ecf20Sopenharmony_ci		if (c)
8888c2ecf20Sopenharmony_ci			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
8898c2ecf20Sopenharmony_ci		c = ldev->lineout_ctrl;
8908c2ecf20Sopenharmony_ci		if (c)
8918c2ecf20Sopenharmony_ci			snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
8928c2ecf20Sopenharmony_ci	}
8938c2ecf20Sopenharmony_ci}
8948c2ecf20Sopenharmony_ci
8958c2ecf20Sopenharmony_cistatic void layout_attached_codec(struct aoa_codec *codec)
8968c2ecf20Sopenharmony_ci{
8978c2ecf20Sopenharmony_ci	struct codec_connection *cc;
8988c2ecf20Sopenharmony_ci	struct snd_kcontrol *ctl;
8998c2ecf20Sopenharmony_ci	int headphones, lineout;
9008c2ecf20Sopenharmony_ci	struct layout_dev *ldev = layout_device;
9018c2ecf20Sopenharmony_ci
9028c2ecf20Sopenharmony_ci	/* need to add this codec to our codec array! */
9038c2ecf20Sopenharmony_ci
9048c2ecf20Sopenharmony_ci	cc = codec->fabric_data;
9058c2ecf20Sopenharmony_ci
9068c2ecf20Sopenharmony_ci	headphones = codec->gpio->methods->get_detect(codec->gpio,
9078c2ecf20Sopenharmony_ci						      AOA_NOTIFY_HEADPHONE);
9088c2ecf20Sopenharmony_ci 	lineout = codec->gpio->methods->get_detect(codec->gpio,
9098c2ecf20Sopenharmony_ci						   AOA_NOTIFY_LINE_OUT);
9108c2ecf20Sopenharmony_ci
9118c2ecf20Sopenharmony_ci	if (codec->gpio->methods->set_master) {
9128c2ecf20Sopenharmony_ci		ctl = snd_ctl_new1(&master_ctl, codec->gpio);
9138c2ecf20Sopenharmony_ci		ldev->master_ctrl = ctl;
9148c2ecf20Sopenharmony_ci		aoa_snd_ctl_add(ctl);
9158c2ecf20Sopenharmony_ci	}
9168c2ecf20Sopenharmony_ci	while (cc->connected) {
9178c2ecf20Sopenharmony_ci		if (cc->connected & CC_SPEAKERS) {
9188c2ecf20Sopenharmony_ci			if (headphones <= 0 && lineout <= 0)
9198c2ecf20Sopenharmony_ci				ldev->gpio.methods->set_speakers(codec->gpio, 1);
9208c2ecf20Sopenharmony_ci			ctl = snd_ctl_new1(&speakers_ctl, codec->gpio);
9218c2ecf20Sopenharmony_ci			ldev->speaker_ctrl = ctl;
9228c2ecf20Sopenharmony_ci			aoa_snd_ctl_add(ctl);
9238c2ecf20Sopenharmony_ci		}
9248c2ecf20Sopenharmony_ci		if (cc->connected & CC_HEADPHONE) {
9258c2ecf20Sopenharmony_ci			if (headphones == 1)
9268c2ecf20Sopenharmony_ci				ldev->gpio.methods->set_headphone(codec->gpio, 1);
9278c2ecf20Sopenharmony_ci			ctl = snd_ctl_new1(&headphone_ctl, codec->gpio);
9288c2ecf20Sopenharmony_ci			ldev->headphone_ctrl = ctl;
9298c2ecf20Sopenharmony_ci			aoa_snd_ctl_add(ctl);
9308c2ecf20Sopenharmony_ci			ldev->have_headphone_detect =
9318c2ecf20Sopenharmony_ci				!ldev->gpio.methods
9328c2ecf20Sopenharmony_ci					->set_notify(&ldev->gpio,
9338c2ecf20Sopenharmony_ci						     AOA_NOTIFY_HEADPHONE,
9348c2ecf20Sopenharmony_ci						     layout_notify,
9358c2ecf20Sopenharmony_ci						     &ldev->selfptr_headphone);
9368c2ecf20Sopenharmony_ci			if (ldev->have_headphone_detect) {
9378c2ecf20Sopenharmony_ci				ctl = snd_ctl_new1(&headphone_detect_choice,
9388c2ecf20Sopenharmony_ci						   ldev);
9398c2ecf20Sopenharmony_ci				aoa_snd_ctl_add(ctl);
9408c2ecf20Sopenharmony_ci				ctl = snd_ctl_new1(&headphone_detected,
9418c2ecf20Sopenharmony_ci						   ldev);
9428c2ecf20Sopenharmony_ci				ldev->headphone_detected_ctrl = ctl;
9438c2ecf20Sopenharmony_ci				aoa_snd_ctl_add(ctl);
9448c2ecf20Sopenharmony_ci			}
9458c2ecf20Sopenharmony_ci		}
9468c2ecf20Sopenharmony_ci		if (cc->connected & CC_LINEOUT) {
9478c2ecf20Sopenharmony_ci			if (lineout == 1)
9488c2ecf20Sopenharmony_ci				ldev->gpio.methods->set_lineout(codec->gpio, 1);
9498c2ecf20Sopenharmony_ci			ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
9508c2ecf20Sopenharmony_ci			if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
9518c2ecf20Sopenharmony_ci				strlcpy(ctl->id.name,
9528c2ecf20Sopenharmony_ci					"Headphone Switch", sizeof(ctl->id.name));
9538c2ecf20Sopenharmony_ci			ldev->lineout_ctrl = ctl;
9548c2ecf20Sopenharmony_ci			aoa_snd_ctl_add(ctl);
9558c2ecf20Sopenharmony_ci			ldev->have_lineout_detect =
9568c2ecf20Sopenharmony_ci				!ldev->gpio.methods
9578c2ecf20Sopenharmony_ci					->set_notify(&ldev->gpio,
9588c2ecf20Sopenharmony_ci						     AOA_NOTIFY_LINE_OUT,
9598c2ecf20Sopenharmony_ci						     layout_notify,
9608c2ecf20Sopenharmony_ci						     &ldev->selfptr_lineout);
9618c2ecf20Sopenharmony_ci			if (ldev->have_lineout_detect) {
9628c2ecf20Sopenharmony_ci				ctl = snd_ctl_new1(&lineout_detect_choice,
9638c2ecf20Sopenharmony_ci						   ldev);
9648c2ecf20Sopenharmony_ci				if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
9658c2ecf20Sopenharmony_ci					strlcpy(ctl->id.name,
9668c2ecf20Sopenharmony_ci						"Headphone Detect Autoswitch",
9678c2ecf20Sopenharmony_ci						sizeof(ctl->id.name));
9688c2ecf20Sopenharmony_ci				aoa_snd_ctl_add(ctl);
9698c2ecf20Sopenharmony_ci				ctl = snd_ctl_new1(&lineout_detected,
9708c2ecf20Sopenharmony_ci						   ldev);
9718c2ecf20Sopenharmony_ci				if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
9728c2ecf20Sopenharmony_ci					strlcpy(ctl->id.name,
9738c2ecf20Sopenharmony_ci						"Headphone Detected",
9748c2ecf20Sopenharmony_ci						sizeof(ctl->id.name));
9758c2ecf20Sopenharmony_ci				ldev->lineout_detected_ctrl = ctl;
9768c2ecf20Sopenharmony_ci				aoa_snd_ctl_add(ctl);
9778c2ecf20Sopenharmony_ci			}
9788c2ecf20Sopenharmony_ci		}
9798c2ecf20Sopenharmony_ci		cc++;
9808c2ecf20Sopenharmony_ci	}
9818c2ecf20Sopenharmony_ci	/* now update initial state */
9828c2ecf20Sopenharmony_ci	if (ldev->have_headphone_detect)
9838c2ecf20Sopenharmony_ci		layout_notify(&ldev->selfptr_headphone);
9848c2ecf20Sopenharmony_ci	if (ldev->have_lineout_detect)
9858c2ecf20Sopenharmony_ci		layout_notify(&ldev->selfptr_lineout);
9868c2ecf20Sopenharmony_ci}
9878c2ecf20Sopenharmony_ci
9888c2ecf20Sopenharmony_cistatic struct aoa_fabric layout_fabric = {
9898c2ecf20Sopenharmony_ci	.name = "SoundByLayout",
9908c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
9918c2ecf20Sopenharmony_ci	.found_codec = layout_found_codec,
9928c2ecf20Sopenharmony_ci	.remove_codec = layout_remove_codec,
9938c2ecf20Sopenharmony_ci	.attached_codec = layout_attached_codec,
9948c2ecf20Sopenharmony_ci};
9958c2ecf20Sopenharmony_ci
9968c2ecf20Sopenharmony_cistatic int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
9978c2ecf20Sopenharmony_ci{
9988c2ecf20Sopenharmony_ci	struct device_node *sound = NULL;
9998c2ecf20Sopenharmony_ci	const unsigned int *id;
10008c2ecf20Sopenharmony_ci	struct layout *layout = NULL;
10018c2ecf20Sopenharmony_ci	struct layout_dev *ldev = NULL;
10028c2ecf20Sopenharmony_ci	int err;
10038c2ecf20Sopenharmony_ci
10048c2ecf20Sopenharmony_ci	/* hm, currently we can only have one ... */
10058c2ecf20Sopenharmony_ci	if (layout_device)
10068c2ecf20Sopenharmony_ci		return -ENODEV;
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_ci	/* by breaking out we keep a reference */
10098c2ecf20Sopenharmony_ci	for_each_child_of_node(sdev->ofdev.dev.of_node, sound) {
10108c2ecf20Sopenharmony_ci		if (of_node_is_type(sound, "soundchip"))
10118c2ecf20Sopenharmony_ci			break;
10128c2ecf20Sopenharmony_ci	}
10138c2ecf20Sopenharmony_ci	if (!sound)
10148c2ecf20Sopenharmony_ci		return -ENODEV;
10158c2ecf20Sopenharmony_ci
10168c2ecf20Sopenharmony_ci	id = of_get_property(sound, "layout-id", NULL);
10178c2ecf20Sopenharmony_ci	if (id) {
10188c2ecf20Sopenharmony_ci		layout = find_layout_by_id(*id);
10198c2ecf20Sopenharmony_ci	} else {
10208c2ecf20Sopenharmony_ci		id = of_get_property(sound, "device-id", NULL);
10218c2ecf20Sopenharmony_ci		if (id)
10228c2ecf20Sopenharmony_ci			layout = find_layout_by_device(*id);
10238c2ecf20Sopenharmony_ci	}
10248c2ecf20Sopenharmony_ci
10258c2ecf20Sopenharmony_ci	if (!layout) {
10268c2ecf20Sopenharmony_ci		printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n");
10278c2ecf20Sopenharmony_ci		goto outnodev;
10288c2ecf20Sopenharmony_ci	}
10298c2ecf20Sopenharmony_ci
10308c2ecf20Sopenharmony_ci	ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL);
10318c2ecf20Sopenharmony_ci	if (!ldev)
10328c2ecf20Sopenharmony_ci		goto outnodev;
10338c2ecf20Sopenharmony_ci
10348c2ecf20Sopenharmony_ci	layout_device = ldev;
10358c2ecf20Sopenharmony_ci	ldev->sdev = sdev;
10368c2ecf20Sopenharmony_ci	ldev->sound = sound;
10378c2ecf20Sopenharmony_ci	ldev->layout = layout;
10388c2ecf20Sopenharmony_ci	ldev->gpio.node = sound->parent;
10398c2ecf20Sopenharmony_ci	switch (layout->layout_id) {
10408c2ecf20Sopenharmony_ci	case 0:  /* anything with device_id, not layout_id */
10418c2ecf20Sopenharmony_ci	case 41: /* that unknown machine no one seems to have */
10428c2ecf20Sopenharmony_ci	case 51: /* PowerBook5,4 */
10438c2ecf20Sopenharmony_ci	case 58: /* Mac Mini */
10448c2ecf20Sopenharmony_ci		ldev->gpio.methods = ftr_gpio_methods;
10458c2ecf20Sopenharmony_ci		printk(KERN_DEBUG
10468c2ecf20Sopenharmony_ci		       "snd-aoa-fabric-layout: Using direct GPIOs\n");
10478c2ecf20Sopenharmony_ci		break;
10488c2ecf20Sopenharmony_ci	default:
10498c2ecf20Sopenharmony_ci		ldev->gpio.methods = pmf_gpio_methods;
10508c2ecf20Sopenharmony_ci		printk(KERN_DEBUG
10518c2ecf20Sopenharmony_ci		       "snd-aoa-fabric-layout: Using PMF GPIOs\n");
10528c2ecf20Sopenharmony_ci	}
10538c2ecf20Sopenharmony_ci	ldev->selfptr_headphone.ptr = ldev;
10548c2ecf20Sopenharmony_ci	ldev->selfptr_lineout.ptr = ldev;
10558c2ecf20Sopenharmony_ci	dev_set_drvdata(&sdev->ofdev.dev, ldev);
10568c2ecf20Sopenharmony_ci	list_add(&ldev->list, &layouts_list);
10578c2ecf20Sopenharmony_ci	layouts_list_items++;
10588c2ecf20Sopenharmony_ci
10598c2ecf20Sopenharmony_ci	/* assign these before registering ourselves, so
10608c2ecf20Sopenharmony_ci	 * callbacks that are done during registration
10618c2ecf20Sopenharmony_ci	 * already have the values */
10628c2ecf20Sopenharmony_ci	sdev->pcmid = ldev->layout->pcmid;
10638c2ecf20Sopenharmony_ci	if (ldev->layout->busname) {
10648c2ecf20Sopenharmony_ci		sdev->pcmname = ldev->layout->busname;
10658c2ecf20Sopenharmony_ci	} else {
10668c2ecf20Sopenharmony_ci		sdev->pcmname = "Master";
10678c2ecf20Sopenharmony_ci	}
10688c2ecf20Sopenharmony_ci
10698c2ecf20Sopenharmony_ci	ldev->gpio.methods->init(&ldev->gpio);
10708c2ecf20Sopenharmony_ci
10718c2ecf20Sopenharmony_ci	err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev);
10728c2ecf20Sopenharmony_ci	if (err && err != -EALREADY) {
10738c2ecf20Sopenharmony_ci		printk(KERN_INFO "snd-aoa-fabric-layout: can't use,"
10748c2ecf20Sopenharmony_ci				 " another fabric is active!\n");
10758c2ecf20Sopenharmony_ci		goto outlistdel;
10768c2ecf20Sopenharmony_ci	}
10778c2ecf20Sopenharmony_ci
10788c2ecf20Sopenharmony_ci	use_layout(layout);
10798c2ecf20Sopenharmony_ci	ldev->switch_on_headphone = 1;
10808c2ecf20Sopenharmony_ci	ldev->switch_on_lineout = 1;
10818c2ecf20Sopenharmony_ci	return 0;
10828c2ecf20Sopenharmony_ci outlistdel:
10838c2ecf20Sopenharmony_ci	/* we won't be using these then... */
10848c2ecf20Sopenharmony_ci	ldev->gpio.methods->exit(&ldev->gpio);
10858c2ecf20Sopenharmony_ci	/* reset if we didn't use it */
10868c2ecf20Sopenharmony_ci	sdev->pcmname = NULL;
10878c2ecf20Sopenharmony_ci	sdev->pcmid = -1;
10888c2ecf20Sopenharmony_ci	list_del(&ldev->list);
10898c2ecf20Sopenharmony_ci	layouts_list_items--;
10908c2ecf20Sopenharmony_ci	kfree(ldev);
10918c2ecf20Sopenharmony_ci outnodev:
10928c2ecf20Sopenharmony_ci 	of_node_put(sound);
10938c2ecf20Sopenharmony_ci 	layout_device = NULL;
10948c2ecf20Sopenharmony_ci	return -ENODEV;
10958c2ecf20Sopenharmony_ci}
10968c2ecf20Sopenharmony_ci
10978c2ecf20Sopenharmony_cistatic int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
10988c2ecf20Sopenharmony_ci{
10998c2ecf20Sopenharmony_ci	struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
11008c2ecf20Sopenharmony_ci	int i;
11018c2ecf20Sopenharmony_ci
11028c2ecf20Sopenharmony_ci	for (i=0; i<MAX_CODECS_PER_BUS; i++) {
11038c2ecf20Sopenharmony_ci		if (ldev->codecs[i]) {
11048c2ecf20Sopenharmony_ci			aoa_fabric_unlink_codec(ldev->codecs[i]);
11058c2ecf20Sopenharmony_ci		}
11068c2ecf20Sopenharmony_ci		ldev->codecs[i] = NULL;
11078c2ecf20Sopenharmony_ci	}
11088c2ecf20Sopenharmony_ci	list_del(&ldev->list);
11098c2ecf20Sopenharmony_ci	layouts_list_items--;
11108c2ecf20Sopenharmony_ci	of_node_put(ldev->sound);
11118c2ecf20Sopenharmony_ci
11128c2ecf20Sopenharmony_ci	ldev->gpio.methods->set_notify(&ldev->gpio,
11138c2ecf20Sopenharmony_ci				       AOA_NOTIFY_HEADPHONE,
11148c2ecf20Sopenharmony_ci				       NULL,
11158c2ecf20Sopenharmony_ci				       NULL);
11168c2ecf20Sopenharmony_ci	ldev->gpio.methods->set_notify(&ldev->gpio,
11178c2ecf20Sopenharmony_ci				       AOA_NOTIFY_LINE_OUT,
11188c2ecf20Sopenharmony_ci				       NULL,
11198c2ecf20Sopenharmony_ci				       NULL);
11208c2ecf20Sopenharmony_ci
11218c2ecf20Sopenharmony_ci	ldev->gpio.methods->exit(&ldev->gpio);
11228c2ecf20Sopenharmony_ci	layout_device = NULL;
11238c2ecf20Sopenharmony_ci	kfree(ldev);
11248c2ecf20Sopenharmony_ci	sdev->pcmid = -1;
11258c2ecf20Sopenharmony_ci	sdev->pcmname = NULL;
11268c2ecf20Sopenharmony_ci	return 0;
11278c2ecf20Sopenharmony_ci}
11288c2ecf20Sopenharmony_ci
11298c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
11308c2ecf20Sopenharmony_cistatic int aoa_fabric_layout_suspend(struct device *dev)
11318c2ecf20Sopenharmony_ci{
11328c2ecf20Sopenharmony_ci	struct layout_dev *ldev = dev_get_drvdata(dev);
11338c2ecf20Sopenharmony_ci
11348c2ecf20Sopenharmony_ci	if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
11358c2ecf20Sopenharmony_ci		ldev->gpio.methods->all_amps_off(&ldev->gpio);
11368c2ecf20Sopenharmony_ci
11378c2ecf20Sopenharmony_ci	return 0;
11388c2ecf20Sopenharmony_ci}
11398c2ecf20Sopenharmony_ci
11408c2ecf20Sopenharmony_cistatic int aoa_fabric_layout_resume(struct device *dev)
11418c2ecf20Sopenharmony_ci{
11428c2ecf20Sopenharmony_ci	struct layout_dev *ldev = dev_get_drvdata(dev);
11438c2ecf20Sopenharmony_ci
11448c2ecf20Sopenharmony_ci	if (ldev->gpio.methods && ldev->gpio.methods->all_amps_restore)
11458c2ecf20Sopenharmony_ci		ldev->gpio.methods->all_amps_restore(&ldev->gpio);
11468c2ecf20Sopenharmony_ci
11478c2ecf20Sopenharmony_ci	return 0;
11488c2ecf20Sopenharmony_ci}
11498c2ecf20Sopenharmony_ci
11508c2ecf20Sopenharmony_cistatic SIMPLE_DEV_PM_OPS(aoa_fabric_layout_pm_ops,
11518c2ecf20Sopenharmony_ci	aoa_fabric_layout_suspend, aoa_fabric_layout_resume);
11528c2ecf20Sopenharmony_ci
11538c2ecf20Sopenharmony_ci#endif
11548c2ecf20Sopenharmony_ci
11558c2ecf20Sopenharmony_cistatic struct soundbus_driver aoa_soundbus_driver = {
11568c2ecf20Sopenharmony_ci	.name = "snd_aoa_soundbus_drv",
11578c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
11588c2ecf20Sopenharmony_ci	.probe = aoa_fabric_layout_probe,
11598c2ecf20Sopenharmony_ci	.remove = aoa_fabric_layout_remove,
11608c2ecf20Sopenharmony_ci	.driver = {
11618c2ecf20Sopenharmony_ci		.owner = THIS_MODULE,
11628c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
11638c2ecf20Sopenharmony_ci		.pm = &aoa_fabric_layout_pm_ops,
11648c2ecf20Sopenharmony_ci#endif
11658c2ecf20Sopenharmony_ci	}
11668c2ecf20Sopenharmony_ci};
11678c2ecf20Sopenharmony_ci
11688c2ecf20Sopenharmony_cistatic int __init aoa_fabric_layout_init(void)
11698c2ecf20Sopenharmony_ci{
11708c2ecf20Sopenharmony_ci	return soundbus_register_driver(&aoa_soundbus_driver);
11718c2ecf20Sopenharmony_ci}
11728c2ecf20Sopenharmony_ci
11738c2ecf20Sopenharmony_cistatic void __exit aoa_fabric_layout_exit(void)
11748c2ecf20Sopenharmony_ci{
11758c2ecf20Sopenharmony_ci	soundbus_unregister_driver(&aoa_soundbus_driver);
11768c2ecf20Sopenharmony_ci	aoa_fabric_unregister(&layout_fabric);
11778c2ecf20Sopenharmony_ci}
11788c2ecf20Sopenharmony_ci
11798c2ecf20Sopenharmony_cimodule_init(aoa_fabric_layout_init);
11808c2ecf20Sopenharmony_cimodule_exit(aoa_fabric_layout_exit);
1181