diff --git a/drivers/staging/greybus/audio_codec.c b/drivers/staging/greybus/audio_codec.c index 826604ae64df..fd94042d3f33 100644 --- a/drivers/staging/greybus/audio_codec.c +++ b/drivers/staging/greybus/audio_codec.c @@ -5,10 +5,20 @@ * * Released under the GPLv2 only. */ +#include #include +#include +#include +#include #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 ret; - struct gbaudio_codec_info *gbcodec; - char dai_name[NAME_SIZE]; + int i; - gbcodec = devm_kzalloc(&pdev->dev, sizeof(struct gbaudio_codec_info), - GFP_KERNEL); - if (!gbcodec) - return -ENOMEM; - platform_set_drvdata(pdev, gbcodec); + 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; +} - snprintf(dai_name, NAME_SIZE, "%s.%d", "gbcodec_pcm", pdev->id); - gbcodec_dai.name = dai_name; +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; - ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_gbcodec, - &gbcodec_dai, 1); - if (!ret) - gbcodec->registered = 1; + 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; } -static const struct of_device_id gbcodec_of_match[] = { - { .compatible = "greybus,codec", }, - {}, +/* + * 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_remove(struct platform_device *pdev) +static int gbaudio_codec_probe(struct gb_connection *connection) { - snd_soc_unregister_codec(&pdev->dev); + int ret; + struct gbaudio_codec_info *gbcodec; + struct device *dev = &connection->bundle->dev; + int dev_id = connection->bundle->id; + + 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; + + 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; + + 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 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 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 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, }; -module_platform_driver(gbaudio_codec_driver); -MODULE_DESCRIPTION("Greybus Audio virtual codec driver"); +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) +{ + 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 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, +}; + +/* + * 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 "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:gbaudio-codec"); diff --git a/drivers/staging/greybus/audio_codec.h b/drivers/staging/greybus/audio_codec.h index f8597b3de808..39bd995719c2 100644 --- a/drivers/staging/greybus/audio_codec.h +++ b/drivers/staging/greybus/audio_codec.h @@ -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);