greybus: audio: Use greybus connection device for codec registration
authorVaibhav Agarwal <vaibhav.agarwal@linaro.org>
Wed, 13 Jan 2016 21:07:50 +0000 (14:07 -0700)
committerGreg Kroah-Hartman <gregkh@google.com>
Thu, 14 Jan 2016 00:20:20 +0000 (16:20 -0800)
Use GB Audio mgmt, data protocol ids to register codec module with
GB protocol. And in response to mgmt->connection_init(), register
GB codec driver with ASoC.

Now, using msm8994  machine to register DAI link dynamically on
codec insertion.

ToDos:
- snd_soc_register_codec() uses driver->name to identify device id.
  However, for GB device, .driver{} is not yet populated by GB core.
  Thus, defining dummy structure within codec driver. This should
  come from GB core itself.
  Even existing .driver{} may cause problem in case of multiple
  modules inserted or inserted at a different slot.
- Fix logic for gbcodec->dais & gbcodec->dailinks. Current
  implementation contains some hard coded data with assumption of
  count=1.
- Evaluate definition of 'gbaudio_dailink.be_id' in case of multiple
  DAI links.

Signed-off-by: Vaibhav Agarwal <vaibhav.agarwal@linaro.org>
Signed-off-by: Mark Greer <mgreer@animalcreek.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
drivers/staging/greybus/audio_codec.c
drivers/staging/greybus/audio_codec.h

index 826604ae64df5d105e049fea0ea1ac387464aa97..fd94042d3f33363b72ea37a74e921e391e4cff60 100644 (file)
@@ -5,10 +5,20 @@
  *
  * Released under the GPLv2 only.
  */
+#include <linux/kernel.h>
 #include <linux/module.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/msm-dynamic-dailink.h>
 
 #include "audio_codec.h"
 
+#define GB_AUDIO_MGMT_DRIVER_NAME      "gb_audio_mgmt"
+#define GB_AUDIO_DATA_DRIVER_NAME      "gb_audio_data"
+
+static DEFINE_MUTEX(gb_codec_list_lock);
+static LIST_HEAD(gb_codec_list);
+
 static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
                                        struct snd_kcontrol *k, int event)
 {
@@ -190,10 +200,7 @@ static struct snd_soc_dai_driver gbcodec_dai = {
 
 static int gbcodec_probe(struct snd_soc_codec *codec)
 {
-       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
-
-       gbcodec->codec = codec;
-
+       /* Empty function for now */
        return 0;
 }
 
@@ -263,53 +270,412 @@ static struct snd_soc_codec_driver soc_codec_dev_gbcodec = {
        .num_dapm_routes = ARRAY_SIZE(gbcodec_dapm_routes),
 };
 
-static int gbaudio_codec_probe(struct platform_device *pdev)
+/*
+ * GB codec DAI link related
+ */
+static struct snd_soc_dai_link gbaudio_dailink = {
+       .name = "PRI_MI2S_RX",
+       .stream_name = "Primary MI2S Playback",
+       .platform_name = "msm-pcm-routing",
+       .cpu_dai_name = "msm-dai-q6-mi2s.0",
+       .no_pcm = 1,
+       .be_id = 34,
+};
+
+static void gbaudio_remove_dailinks(struct gbaudio_codec_info *gbcodec)
+{
+       int i;
+
+       for (i = 0; i < gbcodec->num_dai_links; i++) {
+               dev_dbg(gbcodec->dev, "Remove %s: DAI link\n",
+                       gbcodec->dailink_name[i]);
+               devm_kfree(gbcodec->dev, gbcodec->dailink_name[i]);
+               gbcodec->dailink_name[i] = NULL;
+       }
+       gbcodec->num_dai_links = 0;
+}
+
+static int gbaudio_add_dailinks(struct gbaudio_codec_info *gbcodec)
+{
+       int ret, i;
+       char *dai_link_name;
+       struct snd_soc_dai_link *dai;
+       struct device *dev = gbcodec->dev;
+
+       dai = &gbaudio_dailink;
+       dai->codec_name = gbcodec->name;
+
+       /* FIXME
+        * allocate memory for DAI links based on count.
+        * currently num_dai_links=1, so using static struct
+        */
+       gbcodec->num_dai_links = 1;
+
+       for (i = 0; i < gbcodec->num_dai_links; i++) {
+               gbcodec->dailink_name[i] = dai_link_name =
+                       devm_kzalloc(dev, NAME_SIZE, GFP_KERNEL);
+               snprintf(dai_link_name, NAME_SIZE, "GB %d.%d PRI_MI2S_RX",
+                        gbcodec->dev_id, i);
+               dai->name = dai_link_name;
+               dai->codec_dai_name = gbcodec->dais[i].name;
+       }
+
+       ret = msm8994_add_dailink("msm8994-tomtom-mtp-snd-card", dai, 1);
+       if (ret) {
+               dev_err(dev, "%d:Error while adding DAI link\n", ret);
+               goto err_dai_link;
+       }
+
+       return ret;
+
+err_dai_link:
+       gbcodec->num_dai_links = i;
+       gbaudio_remove_dailinks(gbcodec);
+       return ret;
+}
+
+/*
+ * gb_snd management functions
+ */
+static struct gbaudio_codec_info *gbaudio_find_codec(struct device *dev,
+                                                    int dev_id)
+{
+       struct gbaudio_codec_info *tmp, *ret;
+
+       mutex_lock(&gb_codec_list_lock);
+       list_for_each_entry_safe(ret, tmp, &gb_codec_list, list) {
+               dev_dbg(dev, "%d:device found\n", ret->dev_id);
+               if (ret->dev_id == dev_id) {
+                       mutex_unlock(&gb_codec_list_lock);
+                       return ret;
+               }
+       }
+       mutex_unlock(&gb_codec_list_lock);
+       return NULL;
+}
+
+static struct gbaudio_codec_info *gbaudio_get_codec(struct device *dev,
+                                                   int dev_id)
+{
+       struct gbaudio_codec_info *gbcodec;
+
+       gbcodec = gbaudio_find_codec(dev, dev_id);
+       if (gbcodec)
+               return gbcodec;
+
+       gbcodec = devm_kzalloc(dev, sizeof(*gbcodec), GFP_KERNEL);
+       if (!gbcodec)
+               return NULL;
+
+       mutex_init(&gbcodec->lock);
+       INIT_LIST_HEAD(&gbcodec->dai_list);
+       gbcodec->dev_id = dev_id;
+       dev_set_drvdata(dev, gbcodec);
+       gbcodec->dev = dev;
+       strlcpy(gbcodec->name, dev_name(dev), NAME_SIZE);
+
+       mutex_lock(&gb_codec_list_lock);
+       list_add(&gbcodec->list, &gb_codec_list);
+       mutex_unlock(&gb_codec_list_lock);
+       dev_dbg(dev, "%d:%s Added to codec list\n", gbcodec->dev_id,
+               gbcodec->name);
+
+       return gbcodec;
+}
+
+static void gbaudio_free_codec(struct device *dev,
+                              struct gbaudio_codec_info *gbcodec)
+{
+       mutex_lock(&gb_codec_list_lock);
+       if (!gbcodec->mgmt_connection &&
+                       list_empty(&gbcodec->dai_list)) {
+               list_del(&gbcodec->list);
+               mutex_unlock(&gb_codec_list_lock);
+               dev_set_drvdata(dev, NULL);
+               devm_kfree(dev, gbcodec);
+       } else {
+               mutex_unlock(&gb_codec_list_lock);
+       }
+}
+
+/*
+ * This is the basic hook get things initialized and registered w/ gb
+ */
+
+/*
+ * GB codec module driver ops
+ */
+struct device_driver gb_codec_driver = {
+       .name = "1-8",
+       .owner = THIS_MODULE,
+};
+
+static int gbaudio_codec_probe(struct gb_connection *connection)
 {
        int ret;
        struct gbaudio_codec_info *gbcodec;
-       char dai_name[NAME_SIZE];
+       struct device *dev = &connection->bundle->dev;
+       int dev_id = connection->bundle->id;
 
-       gbcodec = devm_kzalloc(&pdev->dev, sizeof(struct gbaudio_codec_info),
-                              GFP_KERNEL);
+       dev_dbg(dev, "Add device:%d:%s\n", dev_id, dev_name(dev));
+       /* get gbcodec data */
+       gbcodec = gbaudio_get_codec(dev, dev_id);
        if (!gbcodec)
                return -ENOMEM;
-       platform_set_drvdata(pdev, gbcodec);
 
-       snprintf(dai_name, NAME_SIZE, "%s.%d", "gbcodec_pcm", pdev->id);
-       gbcodec_dai.name = dai_name;
+       gbcodec->mgmt_connection = connection;
+
+       /* update DAI info */
+       gbcodec->dais = &gbcodec_dai;
+       /* FIXME */
+       dev->driver = &gb_codec_driver;
+
+       /* register codec */
+       ret = snd_soc_register_codec(dev, &soc_codec_dev_gbcodec,
+                                    gbcodec->dais, 1);
+       if (ret) {
+               dev_err(dev, "%d:Failed to register codec\n", ret);
+               goto base_error;
+       }
+
+       /* update DAI links in response to this codec */
+       ret = gbaudio_add_dailinks(gbcodec);
+       if (ret) {
+               dev_err(dev, "%d: Failed to add DAI links\n", ret);
+               goto codec_reg_error;
+       }
+
+       /* set registered flag */
+       mutex_lock(&gbcodec->lock);
+       gbcodec->codec_registered = 1;
 
-       ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_gbcodec,
-                                    &gbcodec_dai, 1);
-       if (!ret)
-               gbcodec->registered = 1;
+       mutex_unlock(&gbcodec->lock);
 
        return ret;
+
+codec_reg_error:
+       snd_soc_unregister_codec(dev);
+base_error:
+       dev->driver = NULL;
+       gbcodec->mgmt_connection = NULL;
+       return ret;
 }
 
-static const struct of_device_id gbcodec_of_match[] = {
-       { .compatible = "greybus,codec", },
-       {},
+static void gbaudio_codec_remove(struct gb_connection *connection)
+{
+       struct gbaudio_codec_info *gbcodec;
+       struct device *dev = &connection->bundle->dev;
+       int dev_id = connection->bundle->id;
+
+       dev_dbg(dev, "Remove device:%d:%s\n", dev_id, dev_name(dev));
+
+       /* get gbcodec data */
+       gbcodec = gbaudio_find_codec(dev, dev_id);
+       if (!gbcodec)
+               return;
+
+       msm8994_remove_dailink("msm8994-tomtom-mtp-snd-card", &gbaudio_dailink,
+                              1);
+       gbaudio_remove_dailinks(gbcodec);
+
+       snd_soc_unregister_codec(dev);
+       dev->driver = NULL;
+       gbcodec->mgmt_connection = NULL;
+       mutex_lock(&gbcodec->lock);
+       gbcodec->codec_registered = 0;
+       mutex_unlock(&gbcodec->lock);
+       gbaudio_free_codec(dev, gbcodec);
+}
+
+static int gbaudio_codec_report_event_recv(u8 type, struct gb_operation *op)
+{
+       struct gb_connection *connection = op->connection;
+       struct gb_audio_streaming_event_request *req = op->request->payload;
+
+       dev_warn(&connection->bundle->dev,
+                "Audio Event received: cport: %u, event: %u\n",
+                req->data_cport, req->event);
+
+       return 0;
+}
+
+static struct gb_protocol gb_audio_mgmt_protocol = {
+       .name                   = GB_AUDIO_MGMT_DRIVER_NAME,
+       .id                     = GREYBUS_PROTOCOL_AUDIO_MGMT,
+       .major                  = 0,
+       .minor                  = 1,
+       .connection_init        = gbaudio_codec_probe,
+       .connection_exit        = gbaudio_codec_remove,
+       .request_recv           = gbaudio_codec_report_event_recv,
 };
 
-static int gbaudio_codec_remove(struct platform_device *pdev)
+static struct gbaudio_dai *gbaudio_allocate_dai(struct gbaudio_codec_info *gb,
+                                        int data_cport,
+                                        struct gb_connection *connection,
+                                        const char *name)
+{
+       struct gbaudio_dai *dai;
+
+       mutex_lock(&gb->lock);
+       dai = devm_kzalloc(gb->dev, sizeof(*dai), GFP_KERNEL);
+       if (!dai) {
+               dev_err(gb->dev, "%s:DAI Malloc failure\n", name);
+               mutex_unlock(&gb->lock);
+               return NULL;
+       }
+
+       dai->data_cport = data_cport;
+       dai->connection = connection;
+
+       /* update name */
+       if (name)
+               strlcpy(dai->name, name, NAME_SIZE);
+       list_add(&dai->list, &gb->dai_list);
+       dev_dbg(gb->dev, "%d:%s: DAI added\n", data_cport, dai->name);
+       mutex_unlock(&gb->lock);
+
+       return dai;
+}
+
+struct gbaudio_dai *gbaudio_add_dai(struct gbaudio_codec_info *gbcodec,
+                                   int data_cport,
+                                   struct gb_connection *connection,
+                                   const char *name)
+{
+       struct gbaudio_dai *dai, *_dai;
+
+       /* FIXME need to take care for multiple DAIs */
+       mutex_lock(&gbcodec->lock);
+       if (list_empty(&gbcodec->dai_list)) {
+               mutex_unlock(&gbcodec->lock);
+               return gbaudio_allocate_dai(gbcodec, data_cport, connection,
+                                           name);
+       }
+
+       list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
+               if (dai->data_cport == data_cport) {
+                       if (connection)
+                               dai->connection = connection;
+
+                       if (name)
+                               strlcpy(dai->name, name, NAME_SIZE);
+                       dev_dbg(gbcodec->dev, "%d:%s: DAI updated\n",
+                               data_cport, dai->name);
+                       mutex_unlock(&gbcodec->lock);
+                       return dai;
+               }
+       }
+
+       dev_err(gbcodec->dev, "%s:DAI not found\n", name);
+       mutex_unlock(&gbcodec->lock);
+       return NULL;
+}
+
+static int gbaudio_dai_probe(struct gb_connection *connection)
 {
-       snd_soc_unregister_codec(&pdev->dev);
+       struct gbaudio_dai *dai;
+       struct device *dev = &connection->bundle->dev;
+       int dev_id = connection->bundle->id;
+       struct gbaudio_codec_info *gbcodec = dev_get_drvdata(dev);
+
+       dev_dbg(dev, "Add DAI device:%d:%s\n", dev_id, dev_name(dev));
+
+       /* get gbcodec data */
+       gbcodec = gbaudio_get_codec(dev, dev_id);
+       if (!gbcodec)
+               return -ENOMEM;
+
+       /* add/update dai_list*/
+       dai = gbaudio_add_dai(gbcodec, connection->intf_cport_id, connection,
+                              NULL);
+       if (!dai)
+               return -ENOMEM;
+
+       /* update dai_added count */
+       mutex_lock(&gbcodec->lock);
+       gbcodec->dai_added++;
+       mutex_unlock(&gbcodec->lock);
 
        return 0;
 }
 
-static struct platform_driver gbaudio_codec_driver = {
-       .driver = {
-               .name = "gbaudio-codec",
-               .owner = THIS_MODULE,
-               .of_match_table = gbcodec_of_match,
-       },
-       .probe = gbaudio_codec_probe,
-       .remove   = gbaudio_codec_remove,
+static void gbaudio_dai_remove(struct gb_connection *connection)
+{
+       struct device *dev = &connection->bundle->dev;
+       int dev_id = connection->bundle->id;
+       struct gbaudio_codec_info *gbcodec;
+
+       dev_dbg(dev, "Remove DAI device:%d:%s\n", dev_id, dev_name(dev));
+
+       /* get gbcodec data */
+       gbcodec = gbaudio_find_codec(dev, dev_id);
+       if (!gbcodec)
+               return;
+
+       /* inform uevent to above layers */
+       mutex_lock(&gbcodec->lock);
+       /* update dai_added count */
+       gbcodec->dai_added--;
+       mutex_unlock(&gbcodec->lock);
+
+       gbaudio_free_codec(dev, gbcodec);
+}
+
+static int gbaudio_dai_report_event_recv(u8 type, struct gb_operation *op)
+{
+       struct gb_connection *connection = op->connection;
+
+       dev_warn(&connection->bundle->dev, "Audio Event received\n");
+
+       return 0;
+}
+
+static struct gb_protocol gb_audio_data_protocol = {
+       .name                   = GB_AUDIO_DATA_DRIVER_NAME,
+       .id                     = GREYBUS_PROTOCOL_AUDIO_DATA,
+       .major                  = 0,
+       .minor                  = 1,
+       .connection_init        = gbaudio_dai_probe,
+       .connection_exit        = gbaudio_dai_remove,
+       .request_recv           = gbaudio_dai_report_event_recv,
 };
-module_platform_driver(gbaudio_codec_driver);
 
-MODULE_DESCRIPTION("Greybus Audio virtual codec driver");
+/*
+ * This is the basic hook get things initialized and registered w/ gb
+ */
+
+static int __init gb_audio_protocol_init(void)
+{
+       int err;
+
+       err = gb_protocol_register(&gb_audio_mgmt_protocol);
+       if (err) {
+               pr_err("Can't register i2s mgmt protocol driver: %d\n", -err);
+               return err;
+       }
+
+       err = gb_protocol_register(&gb_audio_data_protocol);
+       if (err) {
+               pr_err("Can't register Audio protocol driver: %d\n", -err);
+               goto err_unregister_audio_mgmt;
+       }
+
+       return 0;
+
+err_unregister_audio_mgmt:
+       gb_protocol_deregister(&gb_audio_mgmt_protocol);
+       return err;
+}
+module_init(gb_audio_protocol_init);
+
+static void __exit gb_audio_protocol_exit(void)
+{
+       gb_protocol_deregister(&gb_audio_data_protocol);
+       gb_protocol_deregister(&gb_audio_mgmt_protocol);
+}
+module_exit(gb_audio_protocol_exit);
+
+MODULE_DESCRIPTION("Greybus Audio codec driver");
 MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("platform:gbaudio-codec");
index f8597b3de808843420f05cb770a6da657db1ebf5..39bd995719c276cad2cedd645f20b346d27f142b 100644 (file)
@@ -15,6 +15,7 @@
 #include "greybus_protocols.h"
 
 #define NAME_SIZE      32
+#define MAX_DAIS       2       /* APB1, APB2 */
 
 enum {
        APB1_PCM = 0,
@@ -67,24 +68,54 @@ static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = {
        GBCODEC_APB2_MUX_REG_DEFAULT,
 };
 
+struct gbaudio_dai {
+       __le16 data_cport;
+       char name[NAME_SIZE];
+       struct gb_connection *connection;
+       struct list_head list;
+};
+
 struct gbaudio_codec_info {
+       /* module info */
+       int dev_id;     /* check if it should be bundle_id/hd_cport_id */
+       int vid;
+       int pid;
+       int slot;
+       int type;
+       int dai_added;
+       int codec_registered;
+       char vstr[NAME_SIZE];
+       char pstr[NAME_SIZE];
+       struct list_head list;
+       char name[NAME_SIZE];
+
+       /* soc related data */
        struct snd_soc_codec *codec;
-
-       bool usable;
+       struct device *dev;
        u8 reg[GBCODEC_REG_COUNT];
-       int registered;
 
+       /* dai_link related */
+       char card_name[NAME_SIZE];
+       char *dailink_name[MAX_DAIS];
+       int num_dai_links;
+
+       /* topology related */
+       struct gb_connection *mgmt_connection;
+       int num_dais;
        int num_kcontrols;
        int num_dapm_widgets;
        int num_dapm_routes;
        struct snd_kcontrol_new *kctls;
        struct snd_soc_dapm_widget *widgets;
        struct snd_soc_dapm_route *routes;
+       struct snd_soc_dai_driver *dais;
+
+       /* lists */
+       struct list_head dai_list;
        struct mutex lock;
 };
 
-extern int gb_audio_gb_get_topology(struct gb_connection *connection,
-                                   struct gb_audio_topology **topology);
+/* protocol related */
 extern int gb_audio_gb_get_control(struct gb_connection *connection,
                                   uint8_t control_id, uint8_t index,
                                   struct gb_audio_ctl_elem_value *value);