mirror of
https://github.com/torvalds/linux.git
synced 2024-12-29 14:21:47 +00:00
f6af5df0c7
Update the aoa-soundbus framework to use dev_pm_ops rather than the deprecated legacy suspend and resume callbacks. Since there isn't anything special to do at the bus level the bus driver does not have to implement any callbacks. The device driver core will automatically pick up and execute the device's PM ops. As there is only a single aoa-soundbus driver implementing suspend and resume, update both the core and driver at the same time to avoid unnecessary code churn. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Signed-off-by: Takashi Iwai <tiwai@suse.de>
1180 lines
27 KiB
C
1180 lines
27 KiB
C
/*
|
|
* 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 <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");
|
|
MODULE_ALIAS("aoa-device-id-44");
|
|
|
|
/* onyx with all but microphone connected */
|
|
static struct codec_connection onyx_connections_nomic[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_DIGITALOUT,
|
|
.codec_bit = 1,
|
|
},
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* onyx on machines without headphone */
|
|
static struct codec_connection onyx_connections_noheadphones[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_LINEOUT |
|
|
CC_LINEOUT_LABELLED_HEADPHONE,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_DIGITALOUT,
|
|
.codec_bit = 1,
|
|
},
|
|
/* FIXME: are these correct? probably not for all the machines
|
|
* below ... If not this will need separating. */
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{
|
|
.connected = CC_MICROPHONE,
|
|
.codec_bit = 3,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* onyx on machines with real line-out */
|
|
static struct codec_connection onyx_connections_reallineout[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_DIGITALOUT,
|
|
.codec_bit = 1,
|
|
},
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* tas on machines without line out */
|
|
static struct codec_connection tas_connections_nolineout[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{
|
|
.connected = CC_MICROPHONE,
|
|
.codec_bit = 3,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* tas on machines with neither line out nor line in */
|
|
static struct codec_connection tas_connections_noline[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_MICROPHONE,
|
|
.codec_bit = 3,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* tas on machines without microphone */
|
|
static struct codec_connection tas_connections_nomic[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
/* tas on machines with everything connected */
|
|
static struct codec_connection tas_connections_all[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_LINEIN,
|
|
.codec_bit = 2,
|
|
},
|
|
{
|
|
.connected = CC_MICROPHONE,
|
|
.codec_bit = 3,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
static struct codec_connection toonie_connections[] = {
|
|
{
|
|
.connected = CC_SPEAKERS | CC_HEADPHONE,
|
|
.codec_bit = 0,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
static struct codec_connection topaz_input[] = {
|
|
{
|
|
.connected = CC_DIGITALIN,
|
|
.codec_bit = 0,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
static struct codec_connection topaz_output[] = {
|
|
{
|
|
.connected = CC_DIGITALOUT,
|
|
.codec_bit = 1,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
static struct codec_connection topaz_inout[] = {
|
|
{
|
|
.connected = CC_DIGITALIN,
|
|
.codec_bit = 0,
|
|
},
|
|
{
|
|
.connected = CC_DIGITALOUT,
|
|
.codec_bit = 1,
|
|
},
|
|
{} /* terminate array by .connected == 0 */
|
|
};
|
|
|
|
static struct layout layouts[] = {
|
|
/* last PowerBooks (15" Oct 2005) */
|
|
{ .layout_id = 82,
|
|
.flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* PowerMac9,1 */
|
|
{ .layout_id = 60,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_reallineout,
|
|
},
|
|
},
|
|
/* PowerMac9,1 */
|
|
{ .layout_id = 61,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* PowerBook5,7 */
|
|
{ .layout_id = 64,
|
|
.flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
/* PowerBook5,7 */
|
|
{ .layout_id = 65,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* PowerBook5,9 [17" Oct 2005] */
|
|
{ .layout_id = 84,
|
|
.flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* PowerMac8,1 */
|
|
{ .layout_id = 45,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* Quad PowerMac (analog in, analog/digital out) */
|
|
{ .layout_id = 68,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_nomic,
|
|
},
|
|
},
|
|
/* Quad PowerMac (digital in) */
|
|
{ .layout_id = 69,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
.busname = "digital in", .pcmid = 1 },
|
|
/* Early 2005 PowerBook (PowerBook 5,6) */
|
|
{ .layout_id = 70,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nolineout,
|
|
},
|
|
},
|
|
/* PowerBook 5,4 */
|
|
{ .layout_id = 51,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nolineout,
|
|
},
|
|
},
|
|
/* PowerBook6,5 */
|
|
{ .device_id = 44,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_all,
|
|
},
|
|
},
|
|
/* PowerBook6,7 */
|
|
{ .layout_id = 80,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_noline,
|
|
},
|
|
},
|
|
/* PowerBook6,8 */
|
|
{ .layout_id = 72,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nolineout,
|
|
},
|
|
},
|
|
/* PowerMac8,2 */
|
|
{ .layout_id = 86,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_nomic,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
/* PowerBook6,7 */
|
|
{ .layout_id = 92,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nolineout,
|
|
},
|
|
},
|
|
/* PowerMac10,1 (Mac Mini) */
|
|
{ .layout_id = 58,
|
|
.codecs[0] = {
|
|
.name = "toonie",
|
|
.connections = toonie_connections,
|
|
},
|
|
},
|
|
{
|
|
.layout_id = 96,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
/* unknown, untested, but this comes from Apple */
|
|
{ .layout_id = 41,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_all,
|
|
},
|
|
},
|
|
{ .layout_id = 36,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nomic,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_inout,
|
|
},
|
|
},
|
|
{ .layout_id = 47,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
{ .layout_id = 48,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
{ .layout_id = 49,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_nomic,
|
|
},
|
|
},
|
|
{ .layout_id = 50,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
{ .layout_id = 56,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
{ .layout_id = 57,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
{ .layout_id = 62,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_output,
|
|
},
|
|
},
|
|
{ .layout_id = 66,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
{ .layout_id = 67,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
},
|
|
{ .layout_id = 76,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_nomic,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "topaz",
|
|
.connections = topaz_inout,
|
|
},
|
|
},
|
|
{ .layout_id = 90,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_noline,
|
|
},
|
|
},
|
|
{ .layout_id = 94,
|
|
.codecs[0] = {
|
|
.name = "onyx",
|
|
/* but it has an external mic?? how to select? */
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
{ .layout_id = 98,
|
|
.codecs[0] = {
|
|
.name = "toonie",
|
|
.connections = toonie_connections,
|
|
},
|
|
},
|
|
{ .layout_id = 100,
|
|
.codecs[0] = {
|
|
.name = "topaz",
|
|
.connections = topaz_input,
|
|
},
|
|
.codecs[1] = {
|
|
.name = "onyx",
|
|
.connections = onyx_connections_noheadphones,
|
|
},
|
|
},
|
|
/* PowerMac3,4 */
|
|
{ .device_id = 14,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_noline,
|
|
},
|
|
},
|
|
/* PowerMac3,6 */
|
|
{ .device_id = 22,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_all,
|
|
},
|
|
},
|
|
/* PowerBook5,2 */
|
|
{ .device_id = 35,
|
|
.codecs[0] = {
|
|
.name = "tas",
|
|
.connections = tas_connections_all,
|
|
},
|
|
},
|
|
{}
|
|
};
|
|
|
|
static struct layout *find_layout_by_id(unsigned int id)
|
|
{
|
|
struct layout *l;
|
|
|
|
l = layouts;
|
|
while (l->codecs[0].name) {
|
|
if (l->layout_id == id)
|
|
return l;
|
|
l++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static struct layout *find_layout_by_device(unsigned int id)
|
|
{
|
|
struct layout *l;
|
|
|
|
l = layouts;
|
|
while (l->codecs[0].name) {
|
|
if (l->device_id == id)
|
|
return l;
|
|
l++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void use_layout(struct layout *l)
|
|
{
|
|
int i;
|
|
|
|
for (i=0; i<MAX_CODECS_PER_BUS; i++) {
|
|
if (l->codecs[i].name) {
|
|
request_module("snd-aoa-codec-%s", l->codecs[i].name);
|
|
}
|
|
}
|
|
/* now we wait for the codecs to call us back */
|
|
}
|
|
|
|
struct layout_dev;
|
|
|
|
struct layout_dev_ptr {
|
|
struct layout_dev *ptr;
|
|
};
|
|
|
|
struct layout_dev {
|
|
struct list_head list;
|
|
struct soundbus_dev *sdev;
|
|
struct device_node *sound;
|
|
struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
|
|
struct layout *layout;
|
|
struct gpio_runtime gpio;
|
|
|
|
/* we need these for headphone/lineout detection */
|
|
struct snd_kcontrol *headphone_ctrl;
|
|
struct snd_kcontrol *lineout_ctrl;
|
|
struct snd_kcontrol *speaker_ctrl;
|
|
struct snd_kcontrol *master_ctrl;
|
|
struct snd_kcontrol *headphone_detected_ctrl;
|
|
struct snd_kcontrol *lineout_detected_ctrl;
|
|
|
|
struct layout_dev_ptr selfptr_headphone;
|
|
struct layout_dev_ptr selfptr_lineout;
|
|
|
|
u32 have_lineout_detect:1,
|
|
have_headphone_detect:1,
|
|
switch_on_headphone:1,
|
|
switch_on_lineout:1;
|
|
};
|
|
|
|
static LIST_HEAD(layouts_list);
|
|
static int layouts_list_items;
|
|
/* this can go away but only if we allow multiple cards,
|
|
* make the fabric handle all the card stuff, etc... */
|
|
static struct layout_dev *layout_device;
|
|
|
|
#define control_info snd_ctl_boolean_mono_info
|
|
|
|
#define AMP_CONTROL(n, description) \
|
|
static int n##_control_get(struct snd_kcontrol *kcontrol, \
|
|
struct snd_ctl_elem_value *ucontrol) \
|
|
{ \
|
|
struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \
|
|
if (gpio->methods && gpio->methods->get_##n) \
|
|
ucontrol->value.integer.value[0] = \
|
|
gpio->methods->get_##n(gpio); \
|
|
return 0; \
|
|
} \
|
|
static int n##_control_put(struct snd_kcontrol *kcontrol, \
|
|
struct snd_ctl_elem_value *ucontrol) \
|
|
{ \
|
|
struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol); \
|
|
if (gpio->methods && gpio->methods->set_##n) \
|
|
gpio->methods->set_##n(gpio, \
|
|
!!ucontrol->value.integer.value[0]); \
|
|
return 1; \
|
|
} \
|
|
static 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--;
|
|
kfree(ldev);
|
|
outnodev:
|
|
of_node_put(sound);
|
|
layout_device = NULL;
|
|
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_SLEEP
|
|
static int aoa_fabric_layout_suspend(struct device *dev)
|
|
{
|
|
struct layout_dev *ldev = dev_get_drvdata(dev);
|
|
|
|
if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
|
|
ldev->gpio.methods->all_amps_off(&ldev->gpio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int aoa_fabric_layout_resume(struct device *dev)
|
|
{
|
|
struct layout_dev *ldev = dev_get_drvdata(dev);
|
|
|
|
if (ldev->gpio.methods && ldev->gpio.methods->all_amps_restore)
|
|
ldev->gpio.methods->all_amps_restore(&ldev->gpio);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(aoa_fabric_layout_pm_ops,
|
|
aoa_fabric_layout_suspend, aoa_fabric_layout_resume);
|
|
|
|
#endif
|
|
|
|
static struct soundbus_driver aoa_soundbus_driver = {
|
|
.name = "snd_aoa_soundbus_drv",
|
|
.owner = THIS_MODULE,
|
|
.probe = aoa_fabric_layout_probe,
|
|
.remove = aoa_fabric_layout_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &aoa_fabric_layout_pm_ops,
|
|
#endif
|
|
}
|
|
};
|
|
|
|
static int __init aoa_fabric_layout_init(void)
|
|
{
|
|
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);
|