*
* 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)
{
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;
}
.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");