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