summaryrefslogblamecommitdiffstats
path: root/drivers/staging/greybus/audio_codec.c
blob: 25b1042d3c776ed308b0fada7f6c14708f508210 (plain) (tree)
1
2
3
4
5
6
7
8
9






                                 
                         
                         


                                      
 
                        
                            
                          
 


                                        













                                                                          


                


                                                               
                


                                   
                                                                  
 


                                            
                          


                                                     
                                                                         
                                        










                                                                             
                 

                                           
 
                   




                                                                 
                


                                   
                                                                  

                          

                                                     
                                                                         
                               

         
                                   
 






                                                                   











                                                                              
                               











                                                                               

                                
 




                                                           
               





                                                                 
                



                                   
                                                                  
 


                                            
                          


                                                     
                                                                         

                               








                                                                 

                               





                                                               

                               





                                                                 

                               













                                                                                    
                               












                                                                           

                                
                   




                                                               
                

                                   
                                                                  
 


                                            
                          


                                                     
                                                                         

                               












                                                                               
                                       






                                                                                
                                       









                                                                                      
                                       






                                                                                      
                                       





                                                                               

                               




                                                                             

                                





                                                                        
                

                                   
                                                                  
 


                                                  
                               
         
 
                          


                                                     
                                                                         

                               













                                                                  

                               











                                                             

                               




















                                                                            

                                
                   















                                                                         
                                   




                                             


                   

                                                     
                                    












                                                                       
                                                                              


















                                                             
                                                                              













                                                          


                              
 








                                                                        
                                         

                                         
                                                          






                                                                     
                                                  









                                                                             


















                                                                                    



                 
                                                                     
 
                   

                                                                    
                                                           



                                                                 
                                           
 







                                                                            
                                               



                                                                                
 
                                                         


                                                              

                                                                        






                                                                      
                                    



                                     


















                                                                            
 
                             


                                                        
                            
                                                                


                                                                   
                                   


                                                        

                                                                               

                                                                   
                                     
         
                                   
 
                 
 
                
                                      
              

                                      
               
                        

                                                        
                   

 
                                                                        
 
                                  

                                                                            
                                               

                                      
                                                        

 
                                                                 










                                                                            
                                                               







                                                                     
                                                                           



                                                                            
 
                              
                                       









                                                                              

                                              



                 
                                                                           
                                                                            
                                                         

                                         






                                                                   


                                                                              

                                              
                                           
         
 

                                      
                                              


                                                    


                 


                                                                     
 

                                                             
 

                                           
                                                    

                                                       
                   



                                                                        
 
                                        



                                                                             


                                                                  
                               
         
 









                                                                      

                                    
                                                  



                                                    
                                                                               




                                                                   

                                                                               

                                                         

                              
                                                                      



                                                         

         
                                              
                                        
                              
                                                                



                                              
                                              



                                         

                                                            

                                        

         










                                                                            
                                              
                                                      


                                                                

                 
           

                                                                     
 
                                          
                    




                                                                       
 

                                                                
 

                                          

                   
 
 
                                                         
 

                                                                         
 
                                        
                                              

                                                     
 



                                                                     
 






                                                                       
                                 



                                          
 

                                                             
                                                      










                                                

                                                 


                                                              
/*
 * 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");

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");