Logo Search packages:      
Sourcecode: alsa-driver version File versions

layout.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 "../aoa.h"
#include "../soundbus/soundbus.h"

MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Layout-ID fabric for snd-aoa");

#define MAX_CODECS_PER_BUS    2

/* These are the connections the layout fabric
 * knows about. It doesn't really care about the
 * input ones, but I thought I'd separate them
 * to give them proper names. The thing is that
 * Apple usually will distinguish the active output
 * by GPIOs, while the active input is set directly
 * on the codec. Hence we here tell the codec what
 * we think is connected. This information is hard-
 * coded below ... */
#define CC_SPEAKERS     (1<<0)
#define CC_HEADPHONE    (1<<1)
#define CC_LINEOUT      (1<<2)
#define CC_DIGITALOUT   (1<<3)
#define CC_LINEIN (1<<4)
#define CC_MICROPHONE   (1<<5)
#define CC_DIGITALIN    (1<<6)
/* pretty bogus but users complain...
 * This is a flag saying that the LINEOUT
 * should be renamed to HEADPHONE.
 * be careful with input detection! */
#define CC_LINEOUT_LABELLED_HEADPHONE     (1<<7)

struct codec_connection {
      /* CC_ flags from above */
      int connected;
      /* codec dependent bit to be set in the aoa_codec.connected field.
       * This intentionally doesn't have any generic flags because the
       * fabric has to know the codec anyway and all codecs might have
       * different connectors */
      int codec_bit;
};

struct codec_connect_info {
      char *name;
      struct codec_connection *connections;
};

#define LAYOUT_FLAG_COMBO_LINEOUT_SPDIF   (1<<0)

struct layout {
      unsigned int layout_id, device_id;
      struct codec_connect_info codecs[MAX_CODECS_PER_BUS];
      int flags;

      /* if busname is not assigned, we use 'Master' below,
       * so that our layout table doesn't need to be filled
       * too much.
       * We only assign these two if we expect to find more
       * than one soundbus, i.e. on those machines with
       * multiple layout-ids */
      char *busname;
      int pcmid;
};

MODULE_ALIAS("sound-layout-36");
MODULE_ALIAS("sound-layout-41");
MODULE_ALIAS("sound-layout-45");
MODULE_ALIAS("sound-layout-47");
MODULE_ALIAS("sound-layout-48");
MODULE_ALIAS("sound-layout-49");
MODULE_ALIAS("sound-layout-50");
MODULE_ALIAS("sound-layout-51");
MODULE_ALIAS("sound-layout-56");
MODULE_ALIAS("sound-layout-57");
MODULE_ALIAS("sound-layout-58");
MODULE_ALIAS("sound-layout-60");
MODULE_ALIAS("sound-layout-61");
MODULE_ALIAS("sound-layout-62");
MODULE_ALIAS("sound-layout-64");
MODULE_ALIAS("sound-layout-65");
MODULE_ALIAS("sound-layout-66");
MODULE_ALIAS("sound-layout-67");
MODULE_ALIAS("sound-layout-68");
MODULE_ALIAS("sound-layout-69");
MODULE_ALIAS("sound-layout-70");
MODULE_ALIAS("sound-layout-72");
MODULE_ALIAS("sound-layout-76");
MODULE_ALIAS("sound-layout-80");
MODULE_ALIAS("sound-layout-82");
MODULE_ALIAS("sound-layout-84");
MODULE_ALIAS("sound-layout-86");
MODULE_ALIAS("sound-layout-90");
MODULE_ALIAS("sound-layout-92");
MODULE_ALIAS("sound-layout-94");
MODULE_ALIAS("sound-layout-96");
MODULE_ALIAS("sound-layout-98");
MODULE_ALIAS("sound-layout-100");

MODULE_ALIAS("aoa-device-id-14");
MODULE_ALIAS("aoa-device-id-22");
MODULE_ALIAS("aoa-device-id-35");

/* onyx with all but microphone connected */
static struct codec_connection onyx_connections_nomic[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
            .codec_bit = 0,
      },
      {
            .connected = CC_DIGITALOUT,
            .codec_bit = 1,
      },
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {} /* terminate array by .connected == 0 */
};

/* onyx on machines without headphone */
static struct codec_connection onyx_connections_noheadphones[] = {
      {
            .connected = CC_SPEAKERS | CC_LINEOUT |
                       CC_LINEOUT_LABELLED_HEADPHONE,
            .codec_bit = 0,
      },
      {
            .connected = CC_DIGITALOUT,
            .codec_bit = 1,
      },
      /* FIXME: are these correct? probably not for all the machines
       * below ... If not this will need separating. */
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {
            .connected = CC_MICROPHONE,
            .codec_bit = 3,
      },
      {} /* terminate array by .connected == 0 */
};

/* onyx on machines with real line-out */
static struct codec_connection onyx_connections_reallineout[] = {
      {
            .connected = CC_SPEAKERS | CC_LINEOUT | CC_HEADPHONE,
            .codec_bit = 0,
      },
      {
            .connected = CC_DIGITALOUT,
            .codec_bit = 1,
      },
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {} /* terminate array by .connected == 0 */
};

/* tas on machines without line out */
static struct codec_connection tas_connections_nolineout[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE,
            .codec_bit = 0,
      },
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {
            .connected = CC_MICROPHONE,
            .codec_bit = 3,
      },
      {} /* terminate array by .connected == 0 */
};

/* tas on machines with neither line out nor line in */
static struct codec_connection tas_connections_noline[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE,
            .codec_bit = 0,
      },
      {
            .connected = CC_MICROPHONE,
            .codec_bit = 3,
      },
      {} /* terminate array by .connected == 0 */
};

/* tas on machines without microphone */
static struct codec_connection tas_connections_nomic[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
            .codec_bit = 0,
      },
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {} /* terminate array by .connected == 0 */
};

/* tas on machines with everything connected */
static struct codec_connection tas_connections_all[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE | CC_LINEOUT,
            .codec_bit = 0,
      },
      {
            .connected = CC_LINEIN,
            .codec_bit = 2,
      },
      {
            .connected = CC_MICROPHONE,
            .codec_bit = 3,
      },
      {} /* terminate array by .connected == 0 */
};

static struct codec_connection toonie_connections[] = {
      {
            .connected = CC_SPEAKERS | CC_HEADPHONE,
            .codec_bit = 0,
      },
      {} /* terminate array by .connected == 0 */
};

static struct codec_connection topaz_input[] = {
      {
            .connected = CC_DIGITALIN,
            .codec_bit = 0,
      },
      {} /* terminate array by .connected == 0 */
};

static struct codec_connection topaz_output[] = {
      {
            .connected = CC_DIGITALOUT,
            .codec_bit = 1,
      },
      {} /* terminate array by .connected == 0 */
};

static struct codec_connection topaz_inout[] = {
      {
            .connected = CC_DIGITALIN,
            .codec_bit = 0,
      },
      {
            .connected = CC_DIGITALOUT,
            .codec_bit = 1,
      },
      {} /* terminate array by .connected == 0 */
};

static struct layout layouts[] = {
      /* last PowerBooks (15" Oct 2005) */
      { .layout_id = 82,
        .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* PowerMac9,1 */
      { .layout_id = 60,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_reallineout,
        },
      },
      /* PowerMac9,1 */
      { .layout_id = 61,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* PowerBook5,7 */
      { .layout_id = 64,
        .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      /* PowerBook5,7 */
      { .layout_id = 65,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* PowerBook5,9 [17" Oct 2005] */
      { .layout_id = 84,
        .flags = LAYOUT_FLAG_COMBO_LINEOUT_SPDIF,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* PowerMac8,1 */
      { .layout_id = 45,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* Quad PowerMac (analog in, analog/digital out) */
      { .layout_id = 68,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_nomic,
        },
      },
      /* Quad PowerMac (digital in) */
      { .layout_id = 69,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
        .busname = "digital in", .pcmid = 1 },
      /* Early 2005 PowerBook (PowerBook 5,6) */
      { .layout_id = 70,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nolineout,
        },
      },
      /* PowerBook 5,4 */
      { .layout_id = 51,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nolineout,
        },
      },
      /* PowerBook6,7 */
      { .layout_id = 80,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_noline,
        },
      },
      /* PowerBook6,8 */
      { .layout_id = 72,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nolineout,
        },
      },
      /* PowerMac8,2 */
      { .layout_id = 86,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_nomic,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      /* PowerBook6,7 */
      { .layout_id = 92,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nolineout,
        },
      },
      /* PowerMac10,1 (Mac Mini) */
      { .layout_id = 58,
        .codecs[0] = {
            .name = "toonie",
            .connections = toonie_connections,
        },
      },
      {
        .layout_id = 96,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      /* unknown, untested, but this comes from Apple */
      { .layout_id = 41,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_all,
        },
      },
      { .layout_id = 36,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nomic,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_inout,
        },
      },
      { .layout_id = 47,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      { .layout_id = 48,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      { .layout_id = 49,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_nomic,
        },
      },
      { .layout_id = 50,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      { .layout_id = 56,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      { .layout_id = 57,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      { .layout_id = 62,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_output,
        },
      },
      { .layout_id = 66,
        .codecs[0] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      { .layout_id = 67,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
      },
      { .layout_id = 76,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_nomic,
        },
        .codecs[1] = {
            .name = "topaz",
            .connections = topaz_inout,
        },
      },
      { .layout_id = 90,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_noline,
        },
      },
      { .layout_id = 94,
        .codecs[0] = {
            .name = "onyx",
            /* but it has an external mic?? how to select? */
            .connections = onyx_connections_noheadphones,
        },
      },
      { .layout_id = 98,
        .codecs[0] = {
            .name = "toonie",
            .connections = toonie_connections,
        },
      },
      { .layout_id = 100,
        .codecs[0] = {
            .name = "topaz",
            .connections = topaz_input,
        },
        .codecs[1] = {
            .name = "onyx",
            .connections = onyx_connections_noheadphones,
        },
      },
      /* PowerMac3,4 */
      { .device_id = 14,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_noline,
        },
      },
      /* PowerMac3,6 */
      { .device_id = 22,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_all,
        },
      },
      /* PowerBook5,2 */
      { .device_id = 35,
        .codecs[0] = {
            .name = "tas",
            .connections = tas_connections_all,
        },
      },
      {}
};

static struct layout *find_layout_by_id(unsigned int id)
{
      struct layout *l;

      l = layouts;
      while (l->codecs[0].name) {
            if (l->layout_id == id)
                  return l;
            l++;
      }
      return NULL;
}

static struct layout *find_layout_by_device(unsigned int id)
{
      struct layout *l;

      l = layouts;
      while (l->codecs[0].name) {
            if (l->device_id == id)
                  return l;
            l++;
      }
      return NULL;
}

static void use_layout(struct layout *l)
{
      int i;

      for (i=0; i<MAX_CODECS_PER_BUS; i++) {
            if (l->codecs[i].name) {
                  request_module("snd-aoa-codec-%s", l->codecs[i].name);
            }
      }
      /* now we wait for the codecs to call us back */
}

struct layout_dev;

struct layout_dev_ptr {
      struct layout_dev *ptr;
};

struct layout_dev {
      struct list_head list;
      struct soundbus_dev *sdev;
      struct device_node *sound;
      struct aoa_codec *codecs[MAX_CODECS_PER_BUS];
      struct layout *layout;
      struct gpio_runtime gpio;

      /* we need these for headphone/lineout detection */
      struct snd_kcontrol *headphone_ctrl;
      struct snd_kcontrol *lineout_ctrl;
      struct snd_kcontrol *speaker_ctrl;
      struct snd_kcontrol *master_ctrl;
      struct snd_kcontrol *headphone_detected_ctrl;
      struct snd_kcontrol *lineout_detected_ctrl;

      struct layout_dev_ptr selfptr_headphone;
      struct layout_dev_ptr selfptr_lineout;

      u32 have_lineout_detect:1,
          have_headphone_detect:1,
          switch_on_headphone:1,
          switch_on_lineout:1;
};

static LIST_HEAD(layouts_list);
static int layouts_list_items;
/* this can go away but only if we allow multiple cards,
 * make the fabric handle all the card stuff, etc... */
static struct layout_dev *layout_device;

#define control_info    snd_ctl_boolean_mono_info

#define AMP_CONTROL(n, description)                         \
static int n##_control_get(struct snd_kcontrol *kcontrol,         \
                     struct snd_ctl_elem_value *ucontrol)         \
{                                                     \
      struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);    \
      if (gpio->methods && gpio->methods->get_##n)                \
            ucontrol->value.integer.value[0] =              \
                  gpio->methods->get_##n(gpio);             \
      return 0;                                       \
}                                                     \
static int n##_control_put(struct snd_kcontrol *kcontrol,         \
                     struct snd_ctl_elem_value *ucontrol)         \
{                                                     \
      struct gpio_runtime *gpio = snd_kcontrol_chip(kcontrol);    \
      if (gpio->methods && gpio->methods->get_##n)                \
            gpio->methods->set_##n(gpio,                    \
                  !!ucontrol->value.integer.value[0]);            \
      return 1;                                       \
}                                                     \
static struct snd_kcontrol_new n##_ctl = {                        \
      .iface = SNDRV_CTL_ELEM_IFACE_MIXER,                        \
      .name = description,                                  \
      .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,                      \
      .info = control_info,                                 \
      .get = n##_control_get,                               \
      .put = n##_control_put,                               \
}

AMP_CONTROL(headphone, "Headphone Switch");
AMP_CONTROL(speakers, "Speakers Switch");
AMP_CONTROL(lineout, "Line-Out Switch");
AMP_CONTROL(master, "Master Switch");

static int detect_choice_get(struct snd_kcontrol *kcontrol,
                       struct snd_ctl_elem_value *ucontrol)
{
      struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);

      switch (kcontrol->private_value) {
      case 0:
            ucontrol->value.integer.value[0] = ldev->switch_on_headphone;
            break;
      case 1:
            ucontrol->value.integer.value[0] = ldev->switch_on_lineout;
            break;
      default:
            return -ENODEV;
      }
      return 0;
}

static int detect_choice_put(struct snd_kcontrol *kcontrol,
                       struct snd_ctl_elem_value *ucontrol)
{
      struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);

      switch (kcontrol->private_value) {
      case 0:
            ldev->switch_on_headphone = !!ucontrol->value.integer.value[0];
            break;
      case 1:
            ldev->switch_on_lineout = !!ucontrol->value.integer.value[0];
            break;
      default:
            return -ENODEV;
      }
      return 1;
}

static struct snd_kcontrol_new headphone_detect_choice = {
      .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
      .name = "Headphone Detect Autoswitch",
      .info = control_info,
      .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
      .get = detect_choice_get,
      .put = detect_choice_put,
      .private_value = 0,
};

static struct snd_kcontrol_new lineout_detect_choice = {
      .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
      .name = "Line-Out Detect Autoswitch",
      .info = control_info,
      .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
      .get = detect_choice_get,
      .put = detect_choice_put,
      .private_value = 1,
};

static int detected_get(struct snd_kcontrol *kcontrol,
                  struct snd_ctl_elem_value *ucontrol)
{
      struct layout_dev *ldev = snd_kcontrol_chip(kcontrol);
      int v;

      switch (kcontrol->private_value) {
      case 0:
            v = ldev->gpio.methods->get_detect(&ldev->gpio,
                                       AOA_NOTIFY_HEADPHONE);
            break;
      case 1:
            v = ldev->gpio.methods->get_detect(&ldev->gpio,
                                       AOA_NOTIFY_LINE_OUT);
            break;
      default:
            return -ENODEV;
      }
      ucontrol->value.integer.value[0] = v;
      return 0;
}

static struct snd_kcontrol_new headphone_detected = {
      .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
      .name = "Headphone Detected",
      .info = control_info,
      .access = SNDRV_CTL_ELEM_ACCESS_READ,
      .get = detected_get,
      .private_value = 0,
};

static struct snd_kcontrol_new lineout_detected = {
      .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
      .name = "Line-Out Detected",
      .info = control_info,
      .access = SNDRV_CTL_ELEM_ACCESS_READ,
      .get = detected_get,
      .private_value = 1,
};

static int check_codec(struct aoa_codec *codec,
                   struct layout_dev *ldev,
                   struct codec_connect_info *cci)
{
      const u32 *ref;
      char propname[32];
      struct codec_connection *cc;

      /* if the codec has a 'codec' node, we require a reference */
      if (codec->node && (strcmp(codec->node->name, "codec") == 0)) {
            snprintf(propname, sizeof(propname),
                   "platform-%s-codec-ref", codec->name);
            ref = of_get_property(ldev->sound, propname, NULL);
            if (!ref) {
                  printk(KERN_INFO "snd-aoa-fabric-layout: "
                        "required property %s not present\n", propname);
                  return -ENODEV;
            }
            if (*ref != codec->node->linux_phandle) {
                  printk(KERN_INFO "snd-aoa-fabric-layout: "
                        "%s doesn't match!\n", propname);
                  return -ENODEV;
            }
      } else {
            if (layouts_list_items != 1) {
                  printk(KERN_INFO "snd-aoa-fabric-layout: "
                        "more than one soundbus, but no references.\n");
                  return -ENODEV;
            }
      }
      codec->soundbus_dev = ldev->sdev;
      codec->gpio = &ldev->gpio;

      cc = cci->connections;
      if (!cc)
            return -EINVAL;

      printk(KERN_INFO "snd-aoa-fabric-layout: can use this codec\n");

      codec->connected = 0;
      codec->fabric_data = cc;

      while (cc->connected) {
            codec->connected |= 1<<cc->codec_bit;
            cc++;
      }

      return 0;
}

static int layout_found_codec(struct aoa_codec *codec)
{
      struct layout_dev *ldev;
      int i;

      list_for_each_entry(ldev, &layouts_list, list) {
            for (i=0; i<MAX_CODECS_PER_BUS; i++) {
                  if (!ldev->layout->codecs[i].name)
                        continue;
                  if (strcmp(ldev->layout->codecs[i].name, codec->name) == 0) {
                        if (check_codec(codec,
                                    ldev,
                                    &ldev->layout->codecs[i]) == 0)
                              return 0;
                  }
            }
      }
      return -ENODEV;
}

static void layout_remove_codec(struct aoa_codec *codec)
{
      int i;
      /* here remove the codec from the layout dev's
       * codec reference */

      codec->soundbus_dev = NULL;
      codec->gpio = NULL;
      for (i=0; i<MAX_CODECS_PER_BUS; i++) {
      }
}

static void layout_notify(void *data)
{
      struct layout_dev_ptr *dptr = data;
      struct layout_dev *ldev;
      int v, update;
      struct snd_kcontrol *detected, *c;
      struct snd_card *card = aoa_get_card();

      ldev = dptr->ptr;
      if (data == &ldev->selfptr_headphone) {
            v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_HEADPHONE);
            detected = ldev->headphone_detected_ctrl;
            update = ldev->switch_on_headphone;
            if (update) {
                  ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
                  ldev->gpio.methods->set_headphone(&ldev->gpio, v);
                  ldev->gpio.methods->set_lineout(&ldev->gpio, 0);
            }
      } else if (data == &ldev->selfptr_lineout) {
            v = ldev->gpio.methods->get_detect(&ldev->gpio, AOA_NOTIFY_LINE_OUT);
            detected = ldev->lineout_detected_ctrl;
            update = ldev->switch_on_lineout;
            if (update) {
                  ldev->gpio.methods->set_speakers(&ldev->gpio, !v);
                  ldev->gpio.methods->set_headphone(&ldev->gpio, 0);
                  ldev->gpio.methods->set_lineout(&ldev->gpio, v);
            }
      } else
            return;

      if (detected)
            snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &detected->id);
      if (update) {
            c = ldev->headphone_ctrl;
            if (c)
                  snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
            c = ldev->speaker_ctrl;
            if (c)
                  snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
            c = ldev->lineout_ctrl;
            if (c)
                  snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_VALUE, &c->id);
      }
}

static void layout_attached_codec(struct aoa_codec *codec)
{
      struct codec_connection *cc;
      struct snd_kcontrol *ctl;
      int headphones, lineout;
      struct layout_dev *ldev = layout_device;

      /* need to add this codec to our codec array! */

      cc = codec->fabric_data;

      headphones = codec->gpio->methods->get_detect(codec->gpio,
                                          AOA_NOTIFY_HEADPHONE);
      lineout = codec->gpio->methods->get_detect(codec->gpio,
                                       AOA_NOTIFY_LINE_OUT);

      if (codec->gpio->methods->set_master) {
            ctl = snd_ctl_new1(&master_ctl, codec->gpio);
            ldev->master_ctrl = ctl;
            aoa_snd_ctl_add(ctl);
      }
      while (cc->connected) {
            if (cc->connected & CC_SPEAKERS) {
                  if (headphones <= 0 && lineout <= 0)
                        ldev->gpio.methods->set_speakers(codec->gpio, 1);
                  ctl = snd_ctl_new1(&speakers_ctl, codec->gpio);
                  ldev->speaker_ctrl = ctl;
                  aoa_snd_ctl_add(ctl);
            }
            if (cc->connected & CC_HEADPHONE) {
                  if (headphones == 1)
                        ldev->gpio.methods->set_headphone(codec->gpio, 1);
                  ctl = snd_ctl_new1(&headphone_ctl, codec->gpio);
                  ldev->headphone_ctrl = ctl;
                  aoa_snd_ctl_add(ctl);
                  ldev->have_headphone_detect =
                        !ldev->gpio.methods
                              ->set_notify(&ldev->gpio,
                                         AOA_NOTIFY_HEADPHONE,
                                         layout_notify,
                                         &ldev->selfptr_headphone);
                  if (ldev->have_headphone_detect) {
                        ctl = snd_ctl_new1(&headphone_detect_choice,
                                       ldev);
                        aoa_snd_ctl_add(ctl);
                        ctl = snd_ctl_new1(&headphone_detected,
                                       ldev);
                        ldev->headphone_detected_ctrl = ctl;
                        aoa_snd_ctl_add(ctl);
                  }
            }
            if (cc->connected & CC_LINEOUT) {
                  if (lineout == 1)
                        ldev->gpio.methods->set_lineout(codec->gpio, 1);
                  ctl = snd_ctl_new1(&lineout_ctl, codec->gpio);
                  if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
                        strlcpy(ctl->id.name,
                              "Headphone Switch", sizeof(ctl->id.name));
                  ldev->lineout_ctrl = ctl;
                  aoa_snd_ctl_add(ctl);
                  ldev->have_lineout_detect =
                        !ldev->gpio.methods
                              ->set_notify(&ldev->gpio,
                                         AOA_NOTIFY_LINE_OUT,
                                         layout_notify,
                                         &ldev->selfptr_lineout);
                  if (ldev->have_lineout_detect) {
                        ctl = snd_ctl_new1(&lineout_detect_choice,
                                       ldev);
                        if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
                              strlcpy(ctl->id.name,
                                    "Headphone Detect Autoswitch",
                                    sizeof(ctl->id.name));
                        aoa_snd_ctl_add(ctl);
                        ctl = snd_ctl_new1(&lineout_detected,
                                       ldev);
                        if (cc->connected & CC_LINEOUT_LABELLED_HEADPHONE)
                              strlcpy(ctl->id.name,
                                    "Headphone Detected",
                                    sizeof(ctl->id.name));
                        ldev->lineout_detected_ctrl = ctl;
                        aoa_snd_ctl_add(ctl);
                  }
            }
            cc++;
      }
      /* now update initial state */
      if (ldev->have_headphone_detect)
            layout_notify(&ldev->selfptr_headphone);
      if (ldev->have_lineout_detect)
            layout_notify(&ldev->selfptr_lineout);
}

static struct aoa_fabric layout_fabric = {
      .name = "SoundByLayout",
      .owner = THIS_MODULE,
      .found_codec = layout_found_codec,
      .remove_codec = layout_remove_codec,
      .attached_codec = layout_attached_codec,
};

static int aoa_fabric_layout_probe(struct soundbus_dev *sdev)
{
      struct device_node *sound = NULL;
      const unsigned int *id;
      struct layout *layout = NULL;
      struct layout_dev *ldev = NULL;
      int err;

      /* hm, currently we can only have one ... */
      if (layout_device)
            return -ENODEV;

      /* by breaking out we keep a reference */
      while ((sound = of_get_next_child(sdev->ofdev.node, sound))) {
            if (sound->type && strcasecmp(sound->type, "soundchip") == 0)
                  break;
      }
      if (!sound)
            return -ENODEV;

      id = of_get_property(sound, "layout-id", NULL);
      if (id) {
            layout = find_layout_by_id(*id);
      } else {
            id = of_get_property(sound, "device-id", NULL);
            if (id)
                  layout = find_layout_by_device(*id);
      }

      if (!layout) {
            printk(KERN_ERR "snd-aoa-fabric-layout: unknown layout\n");
            goto outnodev;
      }

      ldev = kzalloc(sizeof(struct layout_dev), GFP_KERNEL);
      if (!ldev)
            goto outnodev;

      layout_device = ldev;
      ldev->sdev = sdev;
      ldev->sound = sound;
      ldev->layout = layout;
      ldev->gpio.node = sound->parent;
      switch (layout->layout_id) {
      case 0:  /* anything with device_id, not layout_id */
      case 41: /* that unknown machine no one seems to have */
      case 51: /* PowerBook5,4 */
      case 58: /* Mac Mini */
            ldev->gpio.methods = ftr_gpio_methods;
            printk(KERN_DEBUG
                   "snd-aoa-fabric-layout: Using direct GPIOs\n");
            break;
      default:
            ldev->gpio.methods = pmf_gpio_methods;
            printk(KERN_DEBUG
                   "snd-aoa-fabric-layout: Using PMF GPIOs\n");
      }
      ldev->selfptr_headphone.ptr = ldev;
      ldev->selfptr_lineout.ptr = ldev;
      dev_set_drvdata(&sdev->ofdev.dev, ldev);
      list_add(&ldev->list, &layouts_list);
      layouts_list_items++;

      /* assign these before registering ourselves, so
       * callbacks that are done during registration
       * already have the values */
      sdev->pcmid = ldev->layout->pcmid;
      if (ldev->layout->busname) {
            sdev->pcmname = ldev->layout->busname;
      } else {
            sdev->pcmname = "Master";
      }

      ldev->gpio.methods->init(&ldev->gpio);

      err = aoa_fabric_register(&layout_fabric, &sdev->ofdev.dev);
      if (err && err != -EALREADY) {
            printk(KERN_INFO "snd-aoa-fabric-layout: can't use,"
                         " another fabric is active!\n");
            goto outlistdel;
      }

      use_layout(layout);
      ldev->switch_on_headphone = 1;
      ldev->switch_on_lineout = 1;
      return 0;
 outlistdel:
      /* we won't be using these then... */
      ldev->gpio.methods->exit(&ldev->gpio);
      /* reset if we didn't use it */
      sdev->pcmname = NULL;
      sdev->pcmid = -1;
      list_del(&ldev->list);
      layouts_list_items--;
 outnodev:
      of_node_put(sound);
      layout_device = NULL;
      kfree(ldev);
      return -ENODEV;
}

static int aoa_fabric_layout_remove(struct soundbus_dev *sdev)
{
      struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);
      int i;

      for (i=0; i<MAX_CODECS_PER_BUS; i++) {
            if (ldev->codecs[i]) {
                  aoa_fabric_unlink_codec(ldev->codecs[i]);
            }
            ldev->codecs[i] = NULL;
      }
      list_del(&ldev->list);
      layouts_list_items--;
      of_node_put(ldev->sound);

      ldev->gpio.methods->set_notify(&ldev->gpio,
                               AOA_NOTIFY_HEADPHONE,
                               NULL,
                               NULL);
      ldev->gpio.methods->set_notify(&ldev->gpio,
                               AOA_NOTIFY_LINE_OUT,
                               NULL,
                               NULL);

      ldev->gpio.methods->exit(&ldev->gpio);
      layout_device = NULL;
      kfree(ldev);
      sdev->pcmid = -1;
      sdev->pcmname = NULL;
      return 0;
}

#ifdef CONFIG_PM
static int aoa_fabric_layout_suspend(struct soundbus_dev *sdev, pm_message_t state)
{
      struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);

      if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
            ldev->gpio.methods->all_amps_off(&ldev->gpio);

      return 0;
}

static int aoa_fabric_layout_resume(struct soundbus_dev *sdev)
{
      struct layout_dev *ldev = dev_get_drvdata(&sdev->ofdev.dev);

      if (ldev->gpio.methods && ldev->gpio.methods->all_amps_off)
            ldev->gpio.methods->all_amps_restore(&ldev->gpio);

      return 0;
}
#endif

static struct soundbus_driver aoa_soundbus_driver = {
      .name = "snd_aoa_soundbus_drv",
      .owner = THIS_MODULE,
      .probe = aoa_fabric_layout_probe,
      .remove = aoa_fabric_layout_remove,
#ifdef CONFIG_PM
      .suspend = aoa_fabric_layout_suspend,
      .resume = aoa_fabric_layout_resume,
#endif
      .driver = {
            .owner = THIS_MODULE,
      }
};

static int __init aoa_fabric_layout_init(void)
{
      int err;

      err = soundbus_register_driver(&aoa_soundbus_driver);
      if (err)
            return err;
      return 0;
}

static void __exit aoa_fabric_layout_exit(void)
{
      soundbus_unregister_driver(&aoa_soundbus_driver);
      aoa_fabric_unregister(&layout_fabric);
}

module_init(aoa_fabric_layout_init);
module_exit(aoa_fabric_layout_exit);

Generated by  Doxygen 1.6.0   Back to index