mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-12 21:57:43 +00:00
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: Vaibhav Agarwal <vaibhav.agarwal@linaro.org> Signed-off-by: Mark Greer <mgreer@animalcreek.com> Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
parent
86a685dd45
commit
2a70e49f91
2 changed files with 436 additions and 39 deletions
|
@ -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 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 <vaibhav.agarwal@linaro.org>");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:gbaudio-codec");
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue