blob: d820116dd1961ee47c3e59d407c95e4c14a2a5be [file] [log] [blame]
/*
* Greybus audio driver
* Copyright 2015 Google Inc.
* Copyright 2015 Linaro Ltd.
*
* 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"
#include "audio_apbridgea.h"
#include "audio_manager.h"
static DEFINE_MUTEX(gb_codec_list_lock);
static LIST_HEAD(gb_codec_list);
struct gbaudio_dai *gbaudio_find_dai(struct gbaudio_codec_info *gbcodec,
int data_cport, const char *name)
{
struct gbaudio_dai *dai;
list_for_each_entry(dai, &gbcodec->dai_list, list) {
if (name && !strncmp(dai->name, name, NAME_SIZE))
return dai;
if ((data_cport != -1) && (dai->data_cport == data_cport))
return dai;
}
return NULL;
}
/*
* codec DAI ops
*/
static int gbcodec_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
__u16 i2s_port, cportid;
struct gbaudio_dai *gb_dai;
struct gbaudio_codec_info *gb = dev_get_drvdata(dai->dev);
if (!atomic_read(&gb->is_connected))
return -ENODEV;
/* find the dai */
mutex_lock(&gb->lock);
gb_dai = gbaudio_find_dai(gb, -1, dai->name);
if (!gb_dai) {
dev_err(dai->dev, "%s: DAI not registered\n", dai->name);
mutex_unlock(&gb->lock);
return -EINVAL;
}
/* register cport */
i2s_port = 0; /* fixed for now */
cportid = gb_dai->connection->hd_cport_id;
ret = gb_audio_apbridgea_register_cport(gb_dai->connection, i2s_port,
cportid);
dev_dbg(dai->dev, "Register %s:%d DAI, ret:%d\n", dai->name, cportid,
ret);
if (!ret)
atomic_inc(&gb_dai->users);
mutex_unlock(&gb->lock);
return ret;
}
static void gbcodec_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
__u16 i2s_port, cportid;
struct gbaudio_dai *gb_dai;
struct gbaudio_codec_info *gb = dev_get_drvdata(dai->dev);
/* find the dai */
gb_dai = gbaudio_find_dai(gb, -1, dai->name);
if (!gb_dai) {
dev_err(dai->dev, "%s: DAI not registered\n", dai->name);
goto func_exit;
}
atomic_dec(&gb_dai->users);
if (!atomic_read(&gb->is_connected)) {
if (!atomic_read(&gb_dai->users))
wake_up_interruptible(&gb_dai->wait_queue);
return;
}
mutex_lock(&gb->lock);
/* deactivate rx/tx */
cportid = gb_dai->connection->intf_cport_id;
switch (substream->stream) {
case SNDRV_PCM_STREAM_CAPTURE:
ret = gb_audio_gb_deactivate_rx(gb->mgmt_connection, cportid);
break;
case SNDRV_PCM_STREAM_PLAYBACK:
ret = gb_audio_gb_deactivate_tx(gb->mgmt_connection, cportid);
break;
default:
dev_err(dai->dev, "Invalid stream type during shutdown\n");
goto func_exit;
}
if (ret)
dev_err(dai->dev, "%d:Error during deactivate\n", ret);
/* un register cport */
i2s_port = 0; /* fixed for now */
ret = gb_audio_apbridgea_unregister_cport(gb_dai->connection, i2s_port,
gb_dai->connection->hd_cport_id);
dev_dbg(dai->dev, "Unregister %s:%d DAI, ret:%d\n", dai->name,
gb_dai->connection->hd_cport_id, ret);
func_exit:
mutex_unlock(&gb->lock);
/*
if (!atomic_read(&gb_dai->users))
wake_up_interruptible(&gb_dai->wait_queue);
*/
return;
}
static int gbcodec_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hwparams,
struct snd_soc_dai *dai)
{
int ret;
uint8_t sig_bits, channels;
uint32_t format, rate;
uint16_t data_cport;
struct gbaudio_dai *gb_dai;
struct gbaudio_codec_info *gb = dev_get_drvdata(dai->dev);
if (!atomic_read(&gb->is_connected))
return -ENODEV;
/* find the dai */
mutex_lock(&gb->lock);
gb_dai = gbaudio_find_dai(gb, -1, dai->name);
if (!gb_dai) {
dev_err(dai->dev, "%s: DAI not registered\n", dai->name);
ret = -EINVAL;
goto func_exit;
}
/*
* assuming, currently only 48000 Hz, 16BIT_LE, stereo
* is supported, validate params before configuring codec
*/
if (params_channels(hwparams) != 2) {
dev_err(dai->dev, "Invalid channel count:%d\n",
params_channels(hwparams));
ret = -EINVAL;
goto func_exit;
}
channels = params_channels(hwparams);
if (params_rate(hwparams) != 48000) {
dev_err(dai->dev, "Invalid sampling rate:%d\n",
params_rate(hwparams));
ret = -EINVAL;
goto func_exit;
}
rate = GB_AUDIO_PCM_RATE_48000;
if (params_format(hwparams) != SNDRV_PCM_FORMAT_S16_LE) {
dev_err(dai->dev, "Invalid format:%d\n",
params_format(hwparams));
ret = -EINVAL;
goto func_exit;
}
format = GB_AUDIO_PCM_FMT_S16_LE;
data_cport = gb_dai->connection->intf_cport_id;
/* XXX check impact of sig_bit
* it should not change ideally
*/
dev_dbg(dai->dev, "cport:%d, rate:%d, channel %d, format %d, sig_bits:%d\n",
data_cport, rate, channels, format, sig_bits);
ret = gb_audio_gb_set_pcm(gb->mgmt_connection, data_cport, format,
rate, channels, sig_bits);
if (ret) {
dev_err(dai->dev, "%d: Error during set_pcm\n", ret);
goto func_exit;
}
/*
* XXX need to check if
* set config is always required
* check for mclk_freq as well
*/
ret = gb_audio_apbridgea_set_config(gb_dai->connection, 0,
AUDIO_APBRIDGEA_PCM_FMT_16,
AUDIO_APBRIDGEA_PCM_RATE_48000,
6144000);
if (ret)
dev_err(dai->dev, "%d: Error during set_config\n", ret);
func_exit:
mutex_unlock(&gb->lock);
return ret;
}
static int gbcodec_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
int ret;
uint16_t data_cport;
struct gbaudio_dai *gb_dai;
struct gbaudio_codec_info *gb = dev_get_drvdata(dai->dev);
if (!atomic_read(&gb->is_connected))
return -ENODEV;
/* find the dai */
mutex_lock(&gb->lock);
gb_dai = gbaudio_find_dai(gb, -1, dai->name);
if (!gb_dai) {
dev_err(dai->dev, "%s: DAI not registered\n", dai->name);
ret = -EINVAL;
goto func_exit;
}
/* deactivate rx/tx */
data_cport = gb_dai->connection->intf_cport_id;
switch (substream->stream) {
case SNDRV_PCM_STREAM_CAPTURE:
ret = gb_audio_gb_set_rx_data_size(gb->mgmt_connection,
data_cport, 192);
if (ret) {
dev_err(dai->dev,
"%d:Error during set_rx_data_size, cport:%d\n",
ret, data_cport);
goto func_exit;
}
ret = gb_audio_apbridgea_set_rx_data_size(gb_dai->connection, 0,
192);
if (ret) {
dev_err(dai->dev,
"%d:Error during apbridgea_set_rx_data_size\n",
ret);
goto func_exit;
}
ret = gb_audio_gb_activate_rx(gb->mgmt_connection, data_cport);
break;
case SNDRV_PCM_STREAM_PLAYBACK:
ret = gb_audio_gb_set_tx_data_size(gb->mgmt_connection,
data_cport, 192);
if (ret) {
dev_err(dai->dev,
"%d:Error during module set_tx_data_size, cport:%d\n",
ret, data_cport);
goto func_exit;
}
ret = gb_audio_apbridgea_set_tx_data_size(gb_dai->connection, 0,
192);
if (ret) {
dev_err(dai->dev,
"%d:Error during apbridgea set_tx_data_size, cport\n",
ret);
goto func_exit;
}
ret = gb_audio_gb_activate_tx(gb->mgmt_connection, data_cport);
break;
default:
dev_err(dai->dev, "Invalid stream type %d during prepare\n",
substream->stream);
ret = -EINVAL;
goto func_exit;
}
if (ret)
dev_err(dai->dev, "%d: Error during activate stream\n", ret);
func_exit:
mutex_unlock(&gb->lock);
return ret;
}
static int gbcodec_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
int ret;
int tx, rx, start, stop;
struct gbaudio_dai *gb_dai;
struct gbaudio_codec_info *gb = dev_get_drvdata(dai->dev);
if (!atomic_read(&gb->is_connected)) {
if (cmd == SNDRV_PCM_TRIGGER_STOP)
return 0;
return -ENODEV;
}
/* find the dai */
mutex_lock(&gb->lock);
gb_dai = gbaudio_find_dai(gb, -1, dai->name);
if (!gb_dai) {
dev_err(dai->dev, "%s: DAI not registered\n", dai->name);
ret = -EINVAL;
goto func_exit;
}
tx = rx = start = stop = 0;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
start = 1;
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
stop = 1;
break;
default:
dev_err(dai->dev, "Invalid tigger cmd:%d\n", cmd);
ret = -EINVAL;
goto func_exit;
}
switch (substream->stream) {
case SNDRV_PCM_STREAM_CAPTURE:
rx = 1;
break;
case SNDRV_PCM_STREAM_PLAYBACK:
tx = 1;
break;
default:
dev_err(dai->dev, "Invalid stream type:%d\n",
substream->stream);
ret = -EINVAL;
goto func_exit;
}
if (start && tx)
ret = gb_audio_apbridgea_start_tx(gb_dai->connection, 0, 0);
else if (start && rx)
ret = gb_audio_apbridgea_start_rx(gb_dai->connection, 0);
else if (stop && tx)
ret = gb_audio_apbridgea_stop_tx(gb_dai->connection, 0);
else if (stop && rx)
ret = gb_audio_apbridgea_stop_rx(gb_dai->connection, 0);
else
ret = -EINVAL;
if (ret)
dev_err(dai->dev, "%d:Error during %s stream\n", ret,
start ? "Start" : "Stop");
/* in case device removed, return 0 for stop trigger */
if (stop && (ret == -ENODEV))
ret = 0;
func_exit:
mutex_unlock(&gb->lock);
return ret;
}
static int gbcodec_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
return 0;
}
static int gbcodec_digital_mute(struct snd_soc_dai *dai, int mute)
{
return 0;
}
static struct snd_soc_dai_ops gbcodec_dai_ops = {
.startup = gbcodec_startup,
.shutdown = gbcodec_shutdown,
.hw_params = gbcodec_hw_params,
.trigger = gbcodec_trigger,
.prepare = gbcodec_prepare,
.set_fmt = gbcodec_set_dai_fmt,
.digital_mute = gbcodec_digital_mute,
};
/*
* codec driver ops
*/
static int gbcodec_probe(struct snd_soc_codec *codec)
{
/* Empty function for now */
return 0;
}
static int gbcodec_remove(struct snd_soc_codec *codec)
{
/* Empty function for now */
return 0;
}
static int gbcodec_write(struct snd_soc_codec *codec, unsigned int reg,
unsigned int value)
{
int ret = 0;
struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
u8 *gbcodec_reg = gbcodec->reg;
if (reg == SND_SOC_NOPM)
return 0;
if (reg >= GBCODEC_REG_COUNT)
return 0;
gbcodec_reg[reg] = value;
dev_dbg(codec->dev, "reg[%d] = 0x%x\n", reg, value);
return ret;
}
static unsigned int gbcodec_read(struct snd_soc_codec *codec,
unsigned int reg)
{
unsigned int val = 0;
struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
u8 *gbcodec_reg = gbcodec->reg;
if (reg == SND_SOC_NOPM)
return 0;
if (reg >= GBCODEC_REG_COUNT)
return 0;
val = gbcodec_reg[reg];
dev_dbg(codec->dev, "reg[%d] = 0x%x\n", reg, val);
return val;
}
/*
* gb_snd management functions
*/
/* XXX
* since BE DAI path is not yet properly closed from above layer,
* dsp dai.mi2s_dai_data.status_mask is still set to STATUS_PORT_STARTED
* this causes immediate playback/capture to fail in case relevant mixer
* control is not turned OFF
* user need to try once again after failure to recover DSP state.
*/
static void gb_audio_cleanup(struct gbaudio_codec_info *gb)
{
int cportid, ret, timeout_result;
struct gbaudio_dai *gb_dai;
struct gb_connection *connection;
long timeout = msecs_to_jiffies(50); /* 50ms */
struct device *dev = gb->dev;
list_for_each_entry(gb_dai, &gb->dai_list, list) {
/*
* In case of BE dailink, need to deactivate APBridge
* manually
*/
if (atomic_read(&gb_dai->users)) {
/* schedule a wait event */
timeout_result =
wait_event_interruptible_timeout(
gb_dai->wait_queue,
!atomic_read(&gb_dai->users),
timeout);
if (!timeout_result)
dev_warn(dev, "%s:DAI still in use.\n",
gb_dai->name);
connection = gb_dai->connection;
/* PB active */
ret = gb_audio_apbridgea_stop_tx(connection, 0);
if (ret)
dev_info(dev, "%d:Failed during APBridge stop_tx\n",
ret);
cportid = connection->intf_cport_id;
ret = gb_audio_gb_deactivate_tx(gb->mgmt_connection,
cportid);
if (ret)
dev_info(dev,
"%d:Failed during deactivate_tx\n",
ret);
cportid = connection->hd_cport_id;
ret = gb_audio_apbridgea_unregister_cport(connection, 0,
cportid);
if (ret)
dev_info(dev, "%d:Failed during unregister cport\n",
ret);
}
}
}
static int gbaudio_register_codec(struct gbaudio_codec_info *gbcodec)
{
int ret, i;
struct device *dev = gbcodec->dev;
struct gb_connection *connection = gbcodec->mgmt_connection;
struct snd_soc_codec_driver *soc_codec_dev_gbcodec;
/*
* FIXME: malloc for topology happens via audio_gb driver
* should be done within codec driver itself
*/
struct gb_audio_topology *topology;
soc_codec_dev_gbcodec = devm_kzalloc(gbcodec->dev,
sizeof(*soc_codec_dev_gbcodec),
GFP_KERNEL);
if (!soc_codec_dev_gbcodec) {
dev_err(gbcodec->dev, "Malloc failed for codec_driver\n");
return -ENOMEM;
}
ret = gb_connection_enable(connection);
if (ret) {
dev_err(dev, "%d: Error while enabling mgmt connection\n", ret);
return ret;
}
gbcodec->dev_id = connection->intf->interface_id;
/* fetch topology data */
ret = gb_audio_gb_get_topology(connection, &topology);
if (ret) {
dev_err(dev, "%d:Error while fetching topology\n", ret);
goto tplg_fetch_err;
}
/* process topology data */
ret = gbaudio_tplg_parse_data(gbcodec, topology);
if (ret) {
dev_err(dev, "%d:Error while parsing topology data\n",
ret);
goto tplg_parse_err;
}
gbcodec->topology = topology;
/* update codec info */
soc_codec_dev_gbcodec->probe = gbcodec_probe,
soc_codec_dev_gbcodec->remove = gbcodec_remove,
soc_codec_dev_gbcodec->read = gbcodec_read,
soc_codec_dev_gbcodec->write = gbcodec_write,
soc_codec_dev_gbcodec->reg_cache_size = GBCODEC_REG_COUNT,
soc_codec_dev_gbcodec->reg_cache_default = gbcodec_reg_defaults,
soc_codec_dev_gbcodec->reg_word_size = 1,
soc_codec_dev_gbcodec->idle_bias_off = true,
soc_codec_dev_gbcodec->ignore_pmdown_time = 1,
soc_codec_dev_gbcodec->controls = gbcodec->kctls;
soc_codec_dev_gbcodec->num_controls = gbcodec->num_kcontrols;
soc_codec_dev_gbcodec->dapm_widgets = gbcodec->widgets;
soc_codec_dev_gbcodec->num_dapm_widgets = gbcodec->num_dapm_widgets;
soc_codec_dev_gbcodec->dapm_routes = gbcodec->routes;
soc_codec_dev_gbcodec->num_dapm_routes = gbcodec->num_dapm_routes;
/* update DAI info */
for (i = 0; i < gbcodec->num_dais; i++)
gbcodec->dais[i].ops = &gbcodec_dai_ops;
/* 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 codec_reg_err;
}
/* update DAI links in response to this codec */
ret = msm8994_add_dailink("msm8994-tomtom-mtp-snd-card", gbcodec->name,
gbcodec->dais[0].name, 1);
if (ret) {
dev_err(dev, "%d: Failed to add DAI links\n", ret);
goto add_dailink_err;
}
gbcodec->num_dai_links = 1;
return 0;
add_dailink_err:
snd_soc_unregister_codec(dev);
codec_reg_err:
gbaudio_tplg_release(gbcodec);
gbcodec->topology = NULL;
tplg_parse_err:
kfree(topology);
tplg_fetch_err:
gb_connection_disable(gbcodec->mgmt_connection);
return ret;
}
static void gbaudio_unregister_codec(struct gbaudio_codec_info *gbcodec)
{
gb_audio_cleanup(gbcodec);
msm8994_remove_dailink("msm8994-tomtom-mtp-snd-card", gbcodec->name,
gbcodec->dais[0].name, 1);
snd_soc_unregister_codec(gbcodec->dev);
gbaudio_tplg_release(gbcodec);
kfree(gbcodec->topology);
gb_connection_disable(gbcodec->mgmt_connection);
}
static int gbaudio_codec_request_handler(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 int gbaudio_dai_request_handler(struct gb_operation *op)
{
struct gb_connection *connection = op->connection;
dev_warn(&connection->bundle->dev, "Audio Event received\n");
return 0;
}
static int gb_audio_add_mgmt_connection(struct gbaudio_codec_info *gbcodec,
struct greybus_descriptor_cport *cport_desc,
struct gb_bundle *bundle)
{
struct gb_connection *connection;
/* Management Cport */
if (gbcodec->mgmt_connection) {
dev_err(&bundle->dev,
"Can't have multiple Management connections\n");
return -ENODEV;
}
connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
gbaudio_codec_request_handler);
if (IS_ERR(connection))
return PTR_ERR(connection);
connection->private = gbcodec;
gbcodec->mgmt_connection = connection;
return 0;
}
static int gb_audio_add_data_connection(struct gbaudio_codec_info *gbcodec,
struct greybus_descriptor_cport *cport_desc,
struct gb_bundle *bundle)
{
struct gb_connection *connection;
struct gbaudio_dai *dai;
dai = devm_kzalloc(gbcodec->dev, sizeof(*dai), GFP_KERNEL);
if (!dai) {
dev_err(gbcodec->dev, "DAI Malloc failure\n");
return -ENOMEM;
}
connection = gb_connection_create(bundle, le16_to_cpu(cport_desc->id),
gbaudio_dai_request_handler);
if (IS_ERR(connection)) {
devm_kfree(gbcodec->dev, dai);
return PTR_ERR(connection);
}
connection->private = gbcodec;
atomic_set(&dai->users, 0);
init_waitqueue_head(&dai->wait_queue);
dai->data_cport = connection->intf_cport_id;
dai->connection = connection;
list_add(&dai->list, &gbcodec->dai_list);
return 0;
}
/*
* This is the basic hook get things initialized and registered w/ gb
*/
static int gb_audio_probe(struct gb_bundle *bundle,
const struct greybus_bundle_id *id)
{
struct device *dev = &bundle->dev;
struct gbaudio_codec_info *gbcodec;
struct greybus_descriptor_cport *cport_desc;
struct gb_audio_manager_module_descriptor desc;
struct gbaudio_dai *dai, *_dai;
int ret, i;
/* There should be at least one Management and one Data cport */
if (bundle->num_cports < 2)
return -ENODEV;
mutex_lock(&gb_codec_list_lock);
/*
* There can be only one Management connection and any number of data
* connections.
*/
gbcodec = devm_kzalloc(dev, sizeof(*gbcodec), GFP_KERNEL);
if (!gbcodec) {
mutex_unlock(&gb_codec_list_lock);
return -ENOMEM;
}
gbcodec->num_data_connections = bundle->num_cports - 1;
mutex_init(&gbcodec->lock);
INIT_LIST_HEAD(&gbcodec->dai_list);
INIT_LIST_HEAD(&gbcodec->widget_list);
INIT_LIST_HEAD(&gbcodec->codec_ctl_list);
INIT_LIST_HEAD(&gbcodec->widget_ctl_list);
gbcodec->dev = dev;
snprintf(gbcodec->name, NAME_SIZE, "%s.%s", dev->driver->name,
dev_name(dev));
greybus_set_drvdata(bundle, gbcodec);
/* Create all connections */
for (i = 0; i < bundle->num_cports; i++) {
cport_desc = &bundle->cport_desc[i];
switch (cport_desc->protocol_id) {
case GREYBUS_PROTOCOL_AUDIO_MGMT:
ret = gb_audio_add_mgmt_connection(gbcodec, cport_desc,
bundle);
if (ret)
goto destroy_connections;
break;
case GREYBUS_PROTOCOL_AUDIO_DATA:
ret = gb_audio_add_data_connection(gbcodec, cport_desc,
bundle);
if (ret)
goto destroy_connections;
break;
default:
dev_err(dev, "Unsupported protocol: 0x%02x\n",
cport_desc->protocol_id);
ret = -ENODEV;
goto destroy_connections;
}
}
/* There must be a management cport */
if (!gbcodec->mgmt_connection) {
ret = -EINVAL;
dev_err(dev, "Missing management connection\n");
goto destroy_connections;
}
/* Initialize management connection */
ret = gbaudio_register_codec(gbcodec);
if (ret)
goto destroy_connections;
/* Initialize data connections */
list_for_each_entry(dai, &gbcodec->dai_list, list) {
ret = gb_connection_enable(dai->connection);
if (ret)
goto remove_dai;
}
/* inform above layer for uevent */
dev_dbg(dev, "Inform set_event:%d to above layer\n", 1);
/* prepare for the audio manager */
strlcpy(desc.name, gbcodec->name, GB_AUDIO_MANAGER_MODULE_NAME_LEN);
desc.slot = 1; /* todo */
desc.vid = 2; /* todo */
desc.pid = 3; /* todo */
desc.cport = gbcodec->dev_id;
desc.devices = 0x2; /* todo */
gbcodec->manager_id = gb_audio_manager_add(&desc);
atomic_set(&gbcodec->is_connected, 1);
list_add_tail(&gbcodec->list, &gb_codec_list);
dev_dbg(dev, "Add GB Audio device:%s\n", gbcodec->name);
mutex_unlock(&gb_codec_list_lock);
return 0;
remove_dai:
list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list)
gb_connection_disable(dai->connection);
gbaudio_unregister_codec(gbcodec);
destroy_connections:
list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
gb_connection_destroy(dai->connection);
list_del(&dai->list);
devm_kfree(dev, dai);
}
if (gbcodec->mgmt_connection)
gb_connection_destroy(gbcodec->mgmt_connection);
devm_kfree(dev, gbcodec);
mutex_unlock(&gb_codec_list_lock);
return ret;
}
static void gb_audio_disconnect(struct gb_bundle *bundle)
{
struct gbaudio_codec_info *gbcodec = greybus_get_drvdata(bundle);
struct gbaudio_dai *dai, *_dai;
mutex_lock(&gb_codec_list_lock);
atomic_set(&gbcodec->is_connected, 0);
/* inform uevent to above layers */
gb_audio_manager_remove(gbcodec->manager_id);
mutex_lock(&gbcodec->lock);
list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list)
gb_connection_disable(dai->connection);
gbaudio_unregister_codec(gbcodec);
list_for_each_entry_safe(dai, _dai, &gbcodec->dai_list, list) {
gb_connection_destroy(dai->connection);
list_del(&dai->list);
devm_kfree(gbcodec->dev, dai);
}
gb_connection_destroy(gbcodec->mgmt_connection);
gbcodec->mgmt_connection = NULL;
list_del(&gbcodec->list);
mutex_unlock(&gbcodec->lock);
devm_kfree(&bundle->dev, gbcodec);
mutex_unlock(&gb_codec_list_lock);
}
static const struct greybus_bundle_id gb_audio_id_table[] = {
{ GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_AUDIO) },
{ }
};
MODULE_DEVICE_TABLE(greybus, gb_audio_id_table);
static struct greybus_driver gb_audio_driver = {
.name = "gb-audio",
.probe = gb_audio_probe,
.disconnect = gb_audio_disconnect,
.id_table = gb_audio_id_table,
};
module_greybus_driver(gb_audio_driver);
MODULE_DESCRIPTION("Greybus Audio codec driver");
MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:gbaudio-codec");