ASoC: Add support for optional auxiliary dailess codecs
authorJarkko Nikula <jhnikula@gmail.com>
Thu, 25 Nov 2010 15:47:38 +0000 (17:47 +0200)
committerMark Brown <broonie@opensource.wolfsonmicro.com>
Tue, 30 Nov 2010 14:39:00 +0000 (14:39 +0000)
This makes possible to register auxiliary dailess codecs in a machine
driver. Term dailess is used here for amplifiers and codecs without DAI or
DAI being unused.

Dailess auxiliary codecs are kept in struct snd_soc_aux_dev and those codecs
are probed after initializing the DAI links. There are no major differences
between DAI link codecs and dailess codecs in ASoC core point of view. DAPM
handles them equally and sysfs and debugfs directories for dailess codecs
are similar except the pmdown_time node is not created.

Only suspend and resume functions are modified to traverse all probed codecs
instead of DAI link codecs.

Example below shows a dailess codec registration.

struct snd_soc_aux_dev foo_aux_dev[] = {
{
.name = "Amp",
.codec_name = "codec.2",
.init = foo_init2,
},
};

static struct snd_soc_card card = {
...
.aux_dev = foo_aux_dev,
.num_aux_devs = ARRAY_SIZE(foo_aux_dev),
};

Signed-off-by: Jarkko Nikula <jhnikula@gmail.com>
Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
include/sound/soc.h
sound/soc/soc-core.c

index 9e593cf1440b91f87571acee39b737cccb47e0bd..4a9195c5ef2d4a534ff050f6696a99d07cb09ac8 100644 (file)
@@ -583,6 +583,14 @@ struct snd_soc_prefix_map {
        const char *name_prefix;
 };
 
+struct snd_soc_aux_dev {
+       const char *name;               /* Codec name */
+       const char *codec_name;         /* for multi-codec */
+
+       /* codec/machine specific init - e.g. add machine controls */
+       int (*init)(struct snd_soc_dapm_context *dapm);
+};
+
 /* SoC card */
 struct snd_soc_card {
        const char *name;
@@ -624,6 +632,15 @@ struct snd_soc_card {
        struct snd_soc_prefix_map *prefix_map;
        int num_prefixes;
 
+       /*
+        * optional auxiliary devices such as amplifiers or codecs with DAI
+        * link unused
+        */
+       struct snd_soc_aux_dev *aux_dev;
+       int num_aux_devs;
+       struct snd_soc_pcm_runtime *rtd_aux;
+       int num_aux_rtd;
+
        struct work_struct deferred_resume_work;
 
        /* lists of probed devices belonging to this card */
index c8d8dde7f4dde22db733245e7e6a0a6f0cb8befb..a7670d5ac13d29c2735a450d3b176a41817562f4 100644 (file)
@@ -986,6 +986,7 @@ static int soc_suspend(struct device *dev)
 {
        struct platform_device *pdev = to_platform_device(dev);
        struct snd_soc_card *card = platform_get_drvdata(pdev);
+       struct snd_soc_codec *codec;
        int i;
 
        /* If the initialization of this soc device failed, there is no codec
@@ -1064,8 +1065,7 @@ static int soc_suspend(struct device *dev)
        }
 
        /* suspend all CODECs */
-       for (i = 0; i < card->num_rtd; i++) {
-               struct snd_soc_codec *codec = card->rtd[i].codec;
+       list_for_each_entry(codec, &card->codec_dev_list, card_list) {
                /* If there are paths active then the CODEC will be held with
                 * bias _ON and should not be suspended. */
                if (!codec->suspended && codec->driver->suspend) {
@@ -1106,6 +1106,7 @@ static void soc_resume_deferred(struct work_struct *work)
        struct snd_soc_card *card =
                        container_of(work, struct snd_soc_card, deferred_resume_work);
        struct platform_device *pdev = to_platform_device(card->dev);
+       struct snd_soc_codec *codec;
        int i;
 
        /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time,
@@ -1131,8 +1132,7 @@ static void soc_resume_deferred(struct work_struct *work)
                        cpu_dai->driver->resume(cpu_dai);
        }
 
-       for (i = 0; i < card->num_rtd; i++) {
-               struct snd_soc_codec *codec = card->rtd[i].codec;
+       list_for_each_entry(codec, &card->codec_dev_list, card_list) {
                /* If the CODEC was idle over suspend then it will have been
                 * left with bias OFF or STANDBY and suspended so we must now
                 * resume.  Otherwise the suspend was suppressed.
@@ -1603,6 +1603,130 @@ static void soc_unregister_ac97_dai_link(struct snd_soc_codec *codec)
 }
 #endif
 
+static int soc_probe_aux_dev(struct snd_soc_card *card, int num)
+{
+       struct snd_soc_aux_dev *aux_dev = &card->aux_dev[num];
+       struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num];
+       struct snd_soc_codec *codec;
+       const char *temp;
+       int ret = 0;
+
+       /* find CODEC from registered CODECs*/
+       list_for_each_entry(codec, &codec_list, list) {
+               if (!strcmp(codec->name, aux_dev->codec_name)) {
+                       if (codec->probed) {
+                               dev_err(codec->dev,
+                                       "asoc: codec already probed");
+                               ret = -EBUSY;
+                               goto out;
+                       }
+                       break;
+               }
+       }
+
+       if (!try_module_get(codec->dev->driver->owner))
+               return -ENODEV;
+
+       codec->card = card;
+       codec->dapm.card = card;
+
+       soc_set_name_prefix(card, codec);
+       if (codec->driver->probe) {
+               ret = codec->driver->probe(codec);
+               if (ret < 0) {
+                       dev_err(codec->dev, "asoc: failed to probe CODEC");
+                       return ret;
+               }
+       }
+
+       soc_init_codec_debugfs(codec);
+
+       /* mark codec as probed and add to card codec list */
+       codec->probed = 1;
+       list_add(&codec->card_list, &card->codec_dev_list);
+       list_add(&codec->dapm.list, &card->dapm_list);
+
+       /* now that all clients have probed, initialise the DAI link */
+       if (aux_dev->init) {
+               /* machine controls, routes and widgets are not prefixed */
+               temp = codec->name_prefix;
+               codec->name_prefix = NULL;
+               ret = aux_dev->init(&codec->dapm);
+               if (ret < 0) {
+                       dev_err(codec->dev,
+                               "asoc: failed to init %s\n", aux_dev->name);
+                       return ret;
+               }
+               codec->name_prefix = temp;
+       }
+
+       /* Make sure all DAPM widgets are instantiated */
+       snd_soc_dapm_new_widgets(&codec->dapm);
+       snd_soc_dapm_sync(&codec->dapm);
+
+       /* register the rtd device */
+       rtd->codec = codec;
+       rtd->card = card;
+       rtd->dev.parent = card->dev;
+       rtd->dev.release = rtd_release;
+       rtd->dev.init_name = aux_dev->name;
+       ret = device_register(&rtd->dev);
+       if (ret < 0) {
+               dev_err(codec->dev,
+                       "asoc: failed to register aux runtime device %d\n",
+                       ret);
+               return ret;
+       }
+       rtd->dev_registered = 1;
+
+       /* add DAPM sysfs entries for this codec */
+       ret = snd_soc_dapm_sys_add(&rtd->dev);
+       if (ret < 0)
+               dev_err(codec->dev,
+                       "asoc: failed to add codec dapm sysfs entries\n");
+
+       /* add codec sysfs entries */
+       ret = device_create_file(&rtd->dev, &dev_attr_codec_reg);
+       if (ret < 0)
+               dev_err(codec->dev, "asoc: failed to add codec sysfs files\n");
+
+out:
+       return ret;
+}
+
+static void soc_remove_aux_dev(struct snd_soc_card *card, int num)
+{
+       struct snd_soc_pcm_runtime *rtd = &card->rtd_aux[num];
+       struct snd_soc_codec *codec = rtd->codec;
+       int err;
+
+       /* unregister the rtd device */
+       if (rtd->dev_registered) {
+               device_unregister(&rtd->dev);
+               rtd->dev_registered = 0;
+       }
+
+       /* remove the CODEC */
+       if (codec && codec->probed) {
+               if (codec->driver->remove) {
+                       err = codec->driver->remove(codec);
+                       if (err < 0)
+                               dev_err(codec->dev,
+                                       "asoc: failed to remove %s\n",
+                                       codec->name);
+               }
+
+               /* Make sure all DAPM widgets are freed */
+               snd_soc_dapm_free(&codec->dapm);
+
+               soc_cleanup_codec_debugfs(codec);
+               device_remove_file(&rtd->dev, &dev_attr_codec_reg);
+               codec->probed = 0;
+               list_del(&codec->card_list);
+               module_put(codec->dev->driver->owner);
+       }
+}
+
 static void snd_soc_instantiate_card(struct snd_soc_card *card)
 {
        struct platform_device *pdev = to_platform_device(card->dev);
@@ -1657,6 +1781,15 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
                }
        }
 
+       for (i = 0; i < card->num_aux_devs; i++) {
+               ret = soc_probe_aux_dev(card, i);
+               if (ret < 0) {
+                       pr_err("asoc: failed to add auxiliary devices %s: %d\n",
+                              card->name, ret);
+                       goto probe_aux_dev_err;
+               }
+       }
+
        snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname),
                 "%s",  card->name);
        snprintf(card->snd_card->longname, sizeof(card->snd_card->longname),
@@ -1683,6 +1816,10 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card)
        mutex_unlock(&card->mutex);
        return;
 
+probe_aux_dev_err:
+       for (i = 0; i < card->num_aux_devs; i++)
+               soc_remove_aux_dev(card, i);
+
 probe_dai_err:
        for (i = 0; i < card->num_links; i++)
                soc_remove_dai_link(card, i);
@@ -1744,6 +1881,10 @@ static int soc_remove(struct platform_device *pdev)
                        run_delayed_work(&rtd->delayed_work);
                }
 
+               /* remove auxiliary devices */
+               for (i = 0; i < card->num_aux_devs; i++)
+                       soc_remove_aux_dev(card, i);
+
                /* remove and free each DAI */
                for (i = 0; i < card->num_rtd; i++)
                        soc_remove_dai_link(card, i);
@@ -2946,10 +3087,12 @@ static int snd_soc_register_card(struct snd_soc_card *card)
        if (!card->name || !card->dev)
                return -EINVAL;
 
-       card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * card->num_links,
-                       GFP_KERNEL);
+       card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) *
+                           (card->num_links + card->num_aux_devs),
+                           GFP_KERNEL);
        if (card->rtd == NULL)
                return -ENOMEM;
+       card->rtd_aux = &card->rtd[card->num_links];
 
        for (i = 0; i < card->num_links; i++)
                card->rtd[i].dai_link = &card->dai_link[i];