linux/sound/aoa/fabrics/layout.c

1169 lines
27 KiB
C
Raw Normal View History

/*
* 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 cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/slab.h>
#include "../aoa.h"
#include "../soundbus/soundbus.h"
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");
#define MAX_CODECS_PER_BUS 2
/* These are the connections the layout fabric
* knows about. It doesn't really care about the
* input ones, but I thought I'd separate them
* to give them proper names. The thing is that
* Apple usually will distinguish the active output
* by GPIOs, while the active input is set directly
* on the codec. Hence we here tell the codec what
* we think is connected. This information is hard-
* coded below ... */
#define CC_SPEAKERS (1<<0)
#define CC_HEADPHONE (1<<1)
#define CC_LINEOUT (1<<2)
#define CC_DIGITALOUT (1<<3)
#define CC_LINEIN (1<<4)
#define CC_MICROPHONE (1<<5)
#define CC_DIGITALIN (1<<6)
/* pretty bogus but users complain...
* This is a flag saying that the LINEOUT
* should be renamed to HEADPHONE.
* be careful with input detection! */
#define CC_LINEOUT_LABELLED_HEADPHONE (1<<7)
struct codec_connection {
/* CC_ flags from above */
int connected;
/* codec dependent bit to be set in the aoa_codec.connected field.
* This intentionally doesn't have any generic flags because the
* fabric has to know the codec anyway and all codecs might have
* different connectors */
int codec_bit;
};
struct codec_connect_info {
char *name;
struct codec_connection *connections;
};
#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF (1<<0)
struct layout {
unsigned int layout_id, device_id;
struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
int flags;
/* if busname is not assigned, we use 'Master' below,
* so that our layout table doesn't need to be filled
* too much.
* We only assign these two if we expect to find more
* than one soundbus, i.e. on those machines with
* multiple layout-ids */
char *busname;
int pcmid;
};
MODULE_ALIAS("sound-layout-36");
MODULE_ALIAS("sound-layout-41");
MODULE_ALIAS("sound-layout-45");
MODULE_ALIAS("sound-layout-47");
MODULE_ALIAS("sound-layout-48");
MODULE_ALIAS("sound-layout-49");
MODULE_ALIAS("sound-layout-50");
MODULE_ALIAS("sound-layout-51");
MODULE_ALIAS("sound-layout-56");
MODULE_ALIAS("sound-layout-57");
MODULE_ALIAS("sound-layout-58");
MODULE_ALIAS("sound-layout-60");
MODULE_ALIAS("sound-layout-61");
MODULE_ALIAS("sound-layout-62");
MODULE_ALIAS("sound-layout-64");
MODULE_ALIAS("sound-layout-65");
MODULE_ALIAS("sound-layout-66");
MODULE_ALIAS("sound-layout-67");
MODULE_ALIAS("sound-layout-68");
MODULE_ALIAS("sound-layout-69");
MODULE_ALIAS("sound-layout-70");
MODULE_ALIAS("sound-layout-72");
MODULE_ALIAS("sound-layout-76");
MODULE_ALIAS("sound-layout-80");
MODULE_ALIAS("sound-layout-82");
MODULE_ALIAS("sound-layout-84");
MODULE_ALIAS("sound-layout-86");
MODULE_ALIAS("sound-layout-90");
MODULE_ALIAS("sound-layout-92");
MODULE_ALIAS("sound-layout-94");
MODULE_ALIAS("sound-layout-96");
MODULE_ALIAS("sound-layout-98");
MODULE_ALIAS("sound-layout-100");
MODULE_ALIAS("aoa-device-id-14");
MODULE_ALIAS("aoa-device-id-22");
MODULE_ALIAS("aoa-device-id-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->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.dev.of_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);