mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-26 22:24:09 +08:00
61e77107fa
create sysfs device symlinks for snd-aoa in /sys/class/sound/controlC0 This allows hald to recognize the device as sound device. Furthermore it allows the desktop user to actually access the sound device nodes. hald and related packages will modify the acl attributes. Fixes https://bugzilla.novell.com/show_bug.cgi?id=106294 Acked-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: Olaf Hering <olaf@aepfle.de> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Jaroslav Kysela <perex@suse.cz>
1130 lines
26 KiB
C
1130 lines
26 KiB
C
/*
|
|
* Apple Onboard Audio driver -- layout fabric
|
|
*
|
|
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
|
*
|
|
* GPL v2, can be found in COPYING.
|
|
*
|
|
*
|
|
* This fabric module looks for sound codecs
|
|
* based on the layout-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;
|
|
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");
|
|
|
|
/* 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,
|
|
},
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct layout *find_layout_by_id(unsigned int id)
|
|
{
|
|
struct layout *l;
|
|
|
|
l = layouts;
|
|
while (l->layout_id) {
|
|
if (l->layout_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 *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;
|
|
|
|
static int control_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
|
|
uinfo->count = 1;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = 1;
|
|
return 0;
|
|
}
|
|
|
|
#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");
|
|
|
|
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)
|
|
{
|
|
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 = (u32*)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);
|
|
|
|
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;
|
|
unsigned int *layout_id;
|
|
struct layout *layout;
|
|
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;
|
|
|
|
layout_id = (unsigned int *) get_property(sound, "layout-id", NULL);
|
|
if (!layout_id)
|
|
goto outnodev;
|
|
printk(KERN_INFO "snd-aoa-fabric-layout: found bus with layout %d\n",
|
|
*layout_id);
|
|
|
|
layout = find_layout_by_id(*layout_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 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;
|
|
sdev->ofdev.dev.driver_data = 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:
|
|
if (sound) of_node_put(sound);
|
|
layout_device = NULL;
|
|
if (ldev) kfree(ldev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
|
|
{
|
|
struct layout_dev *ldev = sdev->ofdev.dev.driver_data;
|
|
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 = sdev->ofdev.dev.driver_data;
|
|
|
|
printk("aoa_fabric_layout_suspend()\n");
|
|
|
|
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 = sdev->ofdev.dev.driver_data;
|
|
|
|
printk("aoa_fabric_layout_resume()\n");
|
|
|
|
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
|
|
};
|
|
|
|
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);
|