Commit 2a70e49f authored by Vaibhav Agarwal's avatar Vaibhav Agarwal Committed by Greg Kroah-Hartman
Browse files

greybus: audio: Use greybus connection device for codec registration



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: default avatarVaibhav Agarwal <vaibhav.agarwal@linaro.org>
Signed-off-by: default avatarMark Greer <mgreer@animalcreek.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 86a685dd
Loading
Loading
Loading
Loading
+396 −30
Original line number Diff line number Diff line
@@ -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 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 const struct of_device_id gbcodec_of_match[] = {
	{ .compatible = "greybus,codec", },
	{},
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)
{
	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)
{
	snd_soc_unregister_codec(&pdev->dev);
	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 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_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");
+36 −5
Original line number Diff line number Diff line
@@ -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);