summaryrefslogtreecommitdiff
path: root/drivers/staging/greybus/audio_codec.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/greybus/audio_codec.c')
-rw-r--r--drivers/staging/greybus/audio_codec.c1132
1 files changed, 1132 insertions, 0 deletions
diff --git a/drivers/staging/greybus/audio_codec.c b/drivers/staging/greybus/audio_codec.c
new file mode 100644
index 000000000000..8a0744b58a32
--- /dev/null
+++ b/drivers/staging/greybus/audio_codec.c
@@ -0,0 +1,1132 @@
+/*
+ * APBridge ALSA SoC dummy codec driver
+ * Copyright 2016 Google Inc.
+ * Copyright 2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <uapi/linux/input.h>
+
+#include "audio_codec.h"
+#include "audio_apbridgea.h"
+#include "audio_manager.h"
+
+static struct gbaudio_codec_info *gbcodec;
+
+static struct gbaudio_data_connection *
+find_data(struct gbaudio_module_info *module, int id)
+{
+ struct gbaudio_data_connection *data;
+
+ list_for_each_entry(data, &module->data_list, list) {
+ if (id == data->id)
+ return data;
+ }
+ return NULL;
+}
+
+static struct gbaudio_stream_params *
+find_dai_stream_params(struct gbaudio_codec_info *codec, int id, int stream)
+{
+ struct gbaudio_codec_dai *dai;
+
+ list_for_each_entry(dai, &codec->dai_list, list) {
+ if (dai->id == id)
+ return &dai->params[stream];
+ }
+ return NULL;
+}
+
+static int gbaudio_module_enable_tx(struct gbaudio_codec_info *codec,
+ struct gbaudio_module_info *module, int id)
+{
+ int module_state, ret = 0;
+ u16 data_cport, i2s_port, cportid;
+ u8 sig_bits, channels;
+ uint32_t format, rate;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_stream_params *params;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
+
+ params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_PLAYBACK);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ return -EINVAL;
+ }
+
+ /* register cport */
+ if (module_state < GBAUDIO_CODEC_STARTUP) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_register_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "reg_cport failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] =
+ GBAUDIO_CODEC_STARTUP;
+ dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
+ }
+
+ /* hw_params */
+ if (module_state < GBAUDIO_CODEC_HWPARAMS) {
+ format = params->format;
+ channels = params->channels;
+ rate = params->rate;
+ sig_bits = params->sig_bits;
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
+ format, rate, channels, sig_bits);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "set_pcm failed:%d\n",
+ ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] =
+ GBAUDIO_CODEC_HWPARAMS;
+ dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
+ }
+
+ /* prepare */
+ if (module_state < GBAUDIO_CODEC_PREPARE) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_tx_data_size(module->mgmt_connection,
+ data_cport, 192);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "set_tx_data_size failed:%d\n",
+ ret);
+ return ret;
+ }
+ ret = gb_audio_gb_activate_tx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "activate_tx failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] =
+ GBAUDIO_CODEC_PREPARE;
+ dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_disable_tx(struct gbaudio_module_info *module, int id)
+{
+ int ret;
+ u16 data_cport, cportid, i2s_port;
+ int module_state;
+ struct gbaudio_data_connection *data;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_PLAYBACK];
+
+ if (module_state > GBAUDIO_CODEC_HWPARAMS) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_deactivate_tx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "deactivate_tx failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] =
+ GBAUDIO_CODEC_HWPARAMS;
+ }
+
+ if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "unregister_cport failed:%d\n",
+ ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
+ data->state[SNDRV_PCM_STREAM_PLAYBACK] =
+ GBAUDIO_CODEC_SHUTDOWN;
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_enable_rx(struct gbaudio_codec_info *codec,
+ struct gbaudio_module_info *module, int id)
+{
+ int module_state, ret = 0;
+ u16 data_cport, i2s_port, cportid;
+ u8 sig_bits, channels;
+ uint32_t format, rate;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_stream_params *params;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
+
+ params = find_dai_stream_params(codec, id, SNDRV_PCM_STREAM_CAPTURE);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ return -EINVAL;
+ }
+
+ /* register cport */
+ if (module_state < GBAUDIO_CODEC_STARTUP) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_register_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "reg_cport failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] =
+ GBAUDIO_CODEC_STARTUP;
+ dev_dbg(module->dev, "Dynamic Register %d DAI\n", cportid);
+ }
+
+ /* hw_params */
+ if (module_state < GBAUDIO_CODEC_HWPARAMS) {
+ format = params->format;
+ channels = params->channels;
+ rate = params->rate;
+ sig_bits = params->sig_bits;
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_pcm(module->mgmt_connection, data_cport,
+ format, rate, channels, sig_bits);
+ if (ret) {
+ dev_err_ratelimited(module->dev, "set_pcm failed:%d\n",
+ ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] =
+ GBAUDIO_CODEC_HWPARAMS;
+ dev_dbg(module->dev, "Dynamic hw_params %d DAI\n", data_cport);
+ }
+
+ /* prepare */
+ if (module_state < GBAUDIO_CODEC_PREPARE) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_set_rx_data_size(module->mgmt_connection,
+ data_cport, 192);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "set_rx_data_size failed:%d\n",
+ ret);
+ return ret;
+ }
+ ret = gb_audio_gb_activate_rx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "activate_rx failed:%d\n", ret);
+ return ret;
+ }
+ data->state[SNDRV_PCM_STREAM_CAPTURE] =
+ GBAUDIO_CODEC_PREPARE;
+ dev_dbg(module->dev, "Dynamic prepare %d DAI\n", data_cport);
+ }
+
+ return 0;
+}
+
+static int gbaudio_module_disable_rx(struct gbaudio_module_info *module, int id)
+{
+ int ret;
+ u16 data_cport, cportid, i2s_port;
+ int module_state;
+ struct gbaudio_data_connection *data;
+
+ /* find the dai */
+ data = find_data(module, id);
+ if (!data) {
+ dev_err(module->dev, "%d:DATA connection missing\n", id);
+ return -ENODEV;
+ }
+ module_state = data->state[SNDRV_PCM_STREAM_CAPTURE];
+
+ if (module_state > GBAUDIO_CODEC_HWPARAMS) {
+ data_cport = data->connection->intf_cport_id;
+ ret = gb_audio_gb_deactivate_rx(module->mgmt_connection,
+ data_cport);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "deactivate_rx failed:%d\n", ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic deactivate %d DAI\n", data_cport);
+ data->state[SNDRV_PCM_STREAM_CAPTURE] =
+ GBAUDIO_CODEC_HWPARAMS;
+ }
+
+ if (module_state > GBAUDIO_CODEC_SHUTDOWN) {
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ if (ret) {
+ dev_err_ratelimited(module->dev,
+ "unregister_cport failed:%d\n",
+ ret);
+ return ret;
+ }
+ dev_dbg(module->dev, "Dynamic Unregister %d DAI\n", cportid);
+ data->state[SNDRV_PCM_STREAM_CAPTURE] =
+ GBAUDIO_CODEC_SHUTDOWN;
+ }
+
+ return 0;
+}
+
+int gbaudio_module_update(struct gbaudio_codec_info *codec,
+ struct snd_soc_dapm_widget *w,
+ struct gbaudio_module_info *module, int enable)
+{
+ int dai_id, ret;
+ char intf_name[NAME_SIZE], dir[NAME_SIZE];
+
+ dev_dbg(module->dev, "%s:Module update %s sequence\n", w->name,
+ enable ? "Enable":"Disable");
+
+ if ((w->id != snd_soc_dapm_aif_in) && (w->id != snd_soc_dapm_aif_out)) {
+ dev_dbg(codec->dev, "No action required for %s\n", w->name);
+ return 0;
+ }
+
+ /* parse dai_id from AIF widget's stream_name */
+ ret = sscanf(w->sname, "%s %d %s", intf_name, &dai_id, dir);
+ if (ret < 3) {
+ dev_err(codec->dev, "Error while parsing dai_id for %s\n",
+ w->name);
+ return -EINVAL;
+ }
+
+ mutex_lock(&codec->lock);
+ if (w->id == snd_soc_dapm_aif_in) {
+ if (enable)
+ ret = gbaudio_module_enable_tx(codec, module, dai_id);
+ else
+ ret = gbaudio_module_disable_tx(module, dai_id);
+ } else if (w->id == snd_soc_dapm_aif_out) {
+ if (enable)
+ ret = gbaudio_module_enable_rx(codec, module, dai_id);
+ else
+ ret = gbaudio_module_disable_rx(module, dai_id);
+ }
+
+ mutex_unlock(&codec->lock);
+
+ return ret;
+}
+EXPORT_SYMBOL(gbaudio_module_update);
+
+/*
+ * codec DAI ops
+ */
+static int gbcodec_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ params->state = GBAUDIO_CODEC_STARTUP;
+ mutex_unlock(&codec->lock);
+ /* to prevent suspend in case of active audio */
+ pm_stay_awake(dai->dev);
+
+ return 0;
+}
+
+static void gbcodec_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list))
+ dev_info(codec->dev, "No codec module available during shutdown\n");
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return;
+ }
+ params->state = GBAUDIO_CODEC_SHUTDOWN;
+ mutex_unlock(&codec->lock);
+ pm_relax(dai->dev);
+ return;
+}
+
+static int gbcodec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hwparams,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ u8 sig_bits, channels;
+ uint32_t format, rate;
+ struct gbaudio_module_info *module;
+ struct gbaudio_data_connection *data;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ /*
+ * 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));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ channels = params_channels(hwparams);
+
+ if (params_rate(hwparams) != 48000) {
+ dev_err(dai->dev, "Invalid sampling rate:%d\n",
+ params_rate(hwparams));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ 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));
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+ format = GB_AUDIO_PCM_FMT_S16_LE;
+
+ /* find the data connection */
+ list_for_each_entry(module, &codec->module_list, list) {
+ data = find_data(module, dai->id);
+ if (data)
+ break;
+ }
+
+ if (!data) {
+ dev_err(dai->dev, "DATA connection missing\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ ret = gb_audio_apbridgea_set_config(data->connection, 0,
+ AUDIO_APBRIDGEA_PCM_FMT_16,
+ AUDIO_APBRIDGEA_PCM_RATE_48000,
+ 6144000);
+ if (ret) {
+ dev_err_ratelimited(dai->dev, "%d: Error during set_config\n",
+ ret);
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ gb_pm_runtime_put_noidle(bundle);
+
+ params->state = GBAUDIO_CODEC_HWPARAMS;
+ params->format = format;
+ params->rate = rate;
+ params->channels = channels;
+ params->sig_bits = sig_bits;
+
+ mutex_unlock(&codec->lock);
+ return 0;
+}
+
+static int gbcodec_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct gbaudio_module_info *module;
+ struct gbaudio_data_connection *data;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+ mutex_lock(&codec->lock);
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ list_for_each_entry(module, &codec->module_list, list) {
+ /* find the dai */
+ data = find_data(module, dai->id);
+ if (data)
+ break;
+ }
+ if (!data) {
+ dev_err(dai->dev, "DATA connection missing\n");
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ params = find_dai_stream_params(codec, dai->id, substream->stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ switch (substream->stream) {
+ case SNDRV_PCM_STREAM_PLAYBACK:
+ ret = gb_audio_apbridgea_set_tx_data_size(data->connection, 0,
+ 192);
+ break;
+ case SNDRV_PCM_STREAM_CAPTURE:
+ ret = gb_audio_apbridgea_set_rx_data_size(data->connection, 0,
+ 192);
+ break;
+ }
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ dev_err_ratelimited(dai->dev, "set_data_size failed:%d\n",
+ ret);
+ return ret;
+ }
+
+ gb_pm_runtime_put_noidle(bundle);
+
+ params->state = GBAUDIO_CODEC_PREPARE;
+ mutex_unlock(&codec->lock);
+ return 0;
+}
+
+static int gbcodec_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+ int ret;
+ struct gbaudio_data_connection *data;
+ struct gbaudio_module_info *module;
+ struct gb_bundle *bundle;
+ struct gbaudio_codec_info *codec = dev_get_drvdata(dai->dev);
+ struct gbaudio_stream_params *params;
+
+
+ dev_dbg(dai->dev, "Mute:%d, Direction:%s\n", mute,
+ stream ? "CAPTURE":"PLAYBACK");
+
+ mutex_lock(&codec->lock);
+
+ params = find_dai_stream_params(codec, dai->id, stream);
+ if (!params) {
+ dev_err(codec->dev, "Failed to fetch dai_stream pointer\n");
+ mutex_unlock(&codec->lock);
+ return -EINVAL;
+ }
+
+ if (list_empty(&codec->module_list)) {
+ dev_err(codec->dev, "No codec module available\n");
+ if (mute) {
+ params->state = GBAUDIO_CODEC_STOP;
+ ret = 0;
+ } else {
+ ret = -ENODEV;
+ }
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ list_for_each_entry(module, &codec->module_list, list) {
+ /* find the dai */
+ data = find_data(module, dai->id);
+ if (data)
+ break;
+ }
+ if (!data) {
+ dev_err(dai->dev, "%s:%s DATA connection missing\n",
+ dai->name, module->name);
+ mutex_unlock(&codec->lock);
+ return -ENODEV;
+ }
+
+ bundle = to_gb_bundle(module->dev);
+ ret = gb_pm_runtime_get_sync(bundle);
+ if (ret) {
+ mutex_unlock(&codec->lock);
+ return ret;
+ }
+
+ if (!mute && !stream) {/* start playback */
+ ret = gb_audio_apbridgea_prepare_tx(data->connection,
+ 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_start_tx(data->connection,
+ 0, 0);
+ params->state = GBAUDIO_CODEC_START;
+ } else if (!mute && stream) {/* start capture */
+ ret = gb_audio_apbridgea_prepare_rx(data->connection,
+ 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_start_rx(data->connection,
+ 0);
+ params->state = GBAUDIO_CODEC_START;
+ } else if (mute && !stream) {/* stop playback */
+ ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_shutdown_tx(data->connection,
+ 0);
+ params->state = GBAUDIO_CODEC_STOP;
+ } else if (mute && stream) {/* stop capture */
+ ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
+ if (!ret)
+ ret = gb_audio_apbridgea_shutdown_rx(data->connection,
+ 0);
+ params->state = GBAUDIO_CODEC_STOP;
+ } else
+ ret = -EINVAL;
+ if (ret)
+ dev_err_ratelimited(dai->dev,
+ "%s:Error during %s %s stream:%d\n",
+ module->name, mute ? "Mute" : "Unmute",
+ stream ? "Capture" : "Playback", ret);
+
+ gb_pm_runtime_put_noidle(bundle);
+ mutex_unlock(&codec->lock);
+ return ret;
+}
+
+static struct snd_soc_dai_ops gbcodec_dai_ops = {
+ .startup = gbcodec_startup,
+ .shutdown = gbcodec_shutdown,
+ .hw_params = gbcodec_hw_params,
+ .prepare = gbcodec_prepare,
+ .mute_stream = gbcodec_mute_stream,
+};
+
+static struct snd_soc_dai_driver gbaudio_dai[] = {
+ {
+ .name = "apb-i2s0",
+ .id = 0,
+ .playback = {
+ .stream_name = "I2S 0 Playback",
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FORMAT_S16_LE,
+ .rate_max = 48000,
+ .rate_min = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+ .capture = {
+ .stream_name = "I2S 0 Capture",
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FORMAT_S16_LE,
+ .rate_max = 48000,
+ .rate_min = 48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ },
+ .ops = &gbcodec_dai_ops,
+ },
+};
+
+static int gbaudio_init_jack(struct gbaudio_module_info *module,
+ struct snd_soc_codec *codec)
+{
+ int ret;
+
+ if (!module->jack_mask)
+ return 0;
+
+ snprintf(module->jack_name, NAME_SIZE, "GB %d Headset Jack",
+ module->dev_id);
+ ret = snd_soc_jack_new(codec, module->jack_name, module->jack_mask,
+ &module->headset_jack);
+ if (ret) {
+ dev_err(module->dev, "Failed to create new jack\n");
+ return ret;
+ }
+
+ if (!module->button_mask)
+ return 0;
+
+ snprintf(module->button_name, NAME_SIZE, "GB %d Button Jack",
+ module->dev_id);
+ ret = snd_soc_jack_new(codec, module->button_name, module->button_mask,
+ &module->button_jack);
+ if (ret) {
+ dev_err(module->dev, "Failed to create button jack\n");
+ return ret;
+ }
+
+ /*
+ * Currently, max 4 buttons are supported with following key mapping
+ * BTN_0 = KEY_MEDIA
+ * BTN_1 = KEY_VOICECOMMAND
+ * BTN_2 = KEY_VOLUMEUP
+ * BTN_3 = KEY_VOLUMEDOWN
+ */
+
+ if (module->button_mask & SND_JACK_BTN_0) {
+ ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_0,
+ KEY_MEDIA);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_0\n");
+ return ret;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_1) {
+ ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_1,
+ KEY_VOICECOMMAND);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_1\n");
+ return ret;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_2) {
+ ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_2,
+ KEY_VOLUMEUP);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_2\n");
+ return ret;
+ }
+ }
+
+ if (module->button_mask & SND_JACK_BTN_3) {
+ ret = snd_jack_set_key(module->button_jack.jack, SND_JACK_BTN_3,
+ KEY_VOLUMEDOWN);
+ if (ret) {
+ dev_err(module->dev, "Failed to set BTN_0\n");
+ return ret;
+ }
+ }
+
+ /* FIXME
+ * verify if this is really required
+ set_bit(INPUT_PROP_NO_DUMMY_RELEASE,
+ module->button_jack.jack->input_dev->propbit);
+ */
+
+ return 0;
+}
+
+int gbaudio_register_module(struct gbaudio_module_info *module)
+{
+ int ret;
+ struct snd_soc_codec *codec;
+ struct snd_card *card;
+ struct snd_soc_jack *jack = NULL;
+
+ if (!gbcodec) {
+ dev_err(module->dev, "GB Codec not yet probed\n");
+ return -EAGAIN;
+ }
+
+ codec = gbcodec->codec;
+ card = codec->card->snd_card;
+
+ down_write(&card->controls_rwsem);
+
+ if (module->num_dais) {
+ dev_err(gbcodec->dev,
+ "%d:DAIs not supported via gbcodec driver\n",
+ module->num_dais);
+ up_write(&card->controls_rwsem);
+ return -EINVAL;
+ }
+
+ ret = gbaudio_init_jack(module, codec);
+ if (ret) {
+ up_write(&card->controls_rwsem);
+ return ret;
+ }
+
+ if (module->dapm_widgets)
+ snd_soc_dapm_new_controls(&codec->dapm, module->dapm_widgets,
+ module->num_dapm_widgets);
+ if (module->controls)
+ snd_soc_add_codec_controls(codec, module->controls,
+ module->num_controls);
+ if (module->dapm_routes)
+ snd_soc_dapm_add_routes(&codec->dapm, module->dapm_routes,
+ module->num_dapm_routes);
+
+ /* card already instantiated, create widgets here only */
+ if (codec->card->instantiated) {
+ snd_soc_dapm_link_component_dai_widgets(codec->card,
+ &codec->dapm);
+#ifdef CONFIG_SND_JACK
+ /* register jack devices for this module from codec->jack_list */
+ list_for_each_entry(jack, &codec->jack_list, list) {
+ if ((jack == &module->headset_jack)
+ || (jack == &module->button_jack))
+ snd_device_register(codec->card->snd_card,
+ jack->jack);
+ }
+#endif
+ }
+
+ mutex_lock(&gbcodec->lock);
+ list_add(&module->list, &gbcodec->module_list);
+ mutex_unlock(&gbcodec->lock);
+
+ if (codec->card->instantiated)
+ ret = snd_soc_dapm_new_widgets(&codec->dapm);
+ dev_dbg(codec->dev, "Registered %s module\n", module->name);
+
+ up_write(&card->controls_rwsem);
+ return ret;
+}
+EXPORT_SYMBOL(gbaudio_register_module);
+
+static void gbaudio_codec_clean_data_tx(struct gbaudio_data_connection *data)
+{
+ u16 i2s_port, cportid;
+ int ret;
+
+ if (list_is_singular(&gbcodec->module_list)) {
+ ret = gb_audio_apbridgea_stop_tx(data->connection, 0);
+ if (ret)
+ return;
+ ret = gb_audio_apbridgea_shutdown_tx(data->connection,
+ 0);
+ if (ret)
+ return;
+ }
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_TX);
+ data->state[0] = GBAUDIO_CODEC_SHUTDOWN;
+}
+
+static void gbaudio_codec_clean_data_rx(struct gbaudio_data_connection *data)
+{
+ u16 i2s_port, cportid;
+ int ret;
+
+ if (list_is_singular(&gbcodec->module_list)) {
+ ret = gb_audio_apbridgea_stop_rx(data->connection, 0);
+ if (ret)
+ return;
+ ret = gb_audio_apbridgea_shutdown_rx(data->connection,
+ 0);
+ if (ret)
+ return;
+ }
+ i2s_port = 0; /* fixed for now */
+ cportid = data->connection->hd_cport_id;
+ ret = gb_audio_apbridgea_unregister_cport(data->connection,
+ i2s_port, cportid,
+ AUDIO_APBRIDGEA_DIRECTION_RX);
+ data->state[1] = GBAUDIO_CODEC_SHUTDOWN;
+}
+
+
+static void gbaudio_codec_cleanup(struct gbaudio_module_info *module)
+{
+ struct gbaudio_data_connection *data;
+ int pb_state, cap_state;
+
+ dev_dbg(gbcodec->dev, "%s: removed, cleanup APBridge\n", module->name);
+ list_for_each_entry(data, &module->data_list, list) {
+ pb_state = data->state[0];
+ cap_state = data->state[1];
+
+ if (pb_state > GBAUDIO_CODEC_SHUTDOWN)
+ gbaudio_codec_clean_data_tx(data);
+
+ if (cap_state > GBAUDIO_CODEC_SHUTDOWN)
+ gbaudio_codec_clean_data_rx(data);
+
+ }
+}
+
+void gbaudio_unregister_module(struct gbaudio_module_info *module)
+{
+ struct snd_soc_codec *codec = gbcodec->codec;
+ struct snd_card *card = codec->card->snd_card;
+ struct snd_soc_jack *jack, *next_j;
+ int mask;
+
+ dev_dbg(codec->dev, "Unregister %s module\n", module->name);
+
+ down_write(&card->controls_rwsem);
+ mutex_lock(&gbcodec->lock);
+ gbaudio_codec_cleanup(module);
+ list_del(&module->list);
+ dev_dbg(codec->dev, "Process Unregister %s module\n", module->name);
+ mutex_unlock(&gbcodec->lock);
+
+#ifdef CONFIG_SND_JACK
+ /* free jack devices for this module from codec->jack_list */
+ list_for_each_entry_safe(jack, next_j, &codec->jack_list, list) {
+ if (jack == &module->headset_jack)
+ mask = GBCODEC_JACK_MASK;
+ else if (jack == &module->button_jack)
+ mask = GBCODEC_JACK_BUTTON_MASK;
+ else
+ mask = 0;
+ if (mask) {
+ dev_dbg(module->dev, "Report %s removal\n",
+ jack->jack->id);
+ snd_soc_jack_report(jack, 0, mask);
+ snd_device_free(codec->card->snd_card, jack->jack);
+ list_del(&jack->list);
+ }
+ }
+#endif
+
+ if (module->dapm_routes) {
+ dev_dbg(codec->dev, "Removing %d routes\n",
+ module->num_dapm_routes);
+ snd_soc_dapm_del_routes(&codec->dapm, module->dapm_routes,
+ module->num_dapm_routes);
+ }
+ if (module->controls) {
+ dev_dbg(codec->dev, "Removing %d controls\n",
+ module->num_controls);
+ snd_soc_remove_codec_controls(codec, module->controls,
+ module->num_controls);
+ }
+ if (module->dapm_widgets) {
+ dev_dbg(codec->dev, "Removing %d widgets\n",
+ module->num_dapm_widgets);
+ snd_soc_dapm_free_controls(&codec->dapm, module->dapm_widgets,
+ module->num_dapm_widgets);
+ }
+
+ dev_dbg(codec->dev, "Unregistered %s module\n", module->name);
+
+ up_write(&card->controls_rwsem);
+}
+EXPORT_SYMBOL(gbaudio_unregister_module);
+
+/*
+ * codec driver ops
+ */
+static int gbcodec_probe(struct snd_soc_codec *codec)
+{
+ int i;
+ struct gbaudio_codec_info *info;
+ struct gbaudio_codec_dai *dai;
+
+ info = devm_kzalloc(codec->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->dev = codec->dev;
+ INIT_LIST_HEAD(&info->module_list);
+ mutex_init(&info->lock);
+ INIT_LIST_HEAD(&info->dai_list);
+
+ /* init dai_list used to maintain runtime stream info */
+ for (i = 0; i < ARRAY_SIZE(gbaudio_dai); i++) {
+ dai = devm_kzalloc(codec->dev, sizeof(*dai), GFP_KERNEL);
+ if (!dai)
+ return -ENOMEM;
+ dai->id = gbaudio_dai[i].id;
+ list_add(&dai->list, &info->dai_list);
+ }
+
+ info->codec = codec;
+ snd_soc_codec_set_drvdata(codec, info);
+ gbcodec = info;
+
+ device_init_wakeup(codec->dev, 1);
+ return 0;
+}
+
+static int gbcodec_remove(struct snd_soc_codec *codec)
+{
+ /* Empty function for now */
+ return 0;
+}
+
+static u8 gbcodec_reg[GBCODEC_REG_COUNT] = {
+ [GBCODEC_CTL_REG] = GBCODEC_CTL_REG_DEFAULT,
+ [GBCODEC_MUTE_REG] = GBCODEC_MUTE_REG_DEFAULT,
+ [GBCODEC_PB_LVOL_REG] = GBCODEC_PB_VOL_REG_DEFAULT,
+ [GBCODEC_PB_RVOL_REG] = GBCODEC_PB_VOL_REG_DEFAULT,
+ [GBCODEC_CAP_LVOL_REG] = GBCODEC_CAP_VOL_REG_DEFAULT,
+ [GBCODEC_CAP_RVOL_REG] = GBCODEC_CAP_VOL_REG_DEFAULT,
+ [GBCODEC_APB1_MUX_REG] = GBCODEC_APB1_MUX_REG_DEFAULT,
+ [GBCODEC_APB2_MUX_REG] = GBCODEC_APB2_MUX_REG_DEFAULT,
+};
+
+static int gbcodec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ int ret = 0;
+
+ if (reg == SND_SOC_NOPM)
+ return 0;
+
+ BUG_ON(reg >= GBCODEC_REG_COUNT);
+
+ 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;
+
+ if (reg == SND_SOC_NOPM)
+ return 0;
+
+ BUG_ON(reg >= GBCODEC_REG_COUNT);
+
+ val = gbcodec_reg[reg];
+ dev_dbg(codec->dev, "reg[%d] = 0x%x\n", reg, val);
+
+ return val;
+}
+
+static struct snd_soc_codec_driver soc_codec_dev_gbaudio = {
+ .probe = gbcodec_probe,
+ .remove = gbcodec_remove,
+
+ .read = gbcodec_read,
+ .write = gbcodec_write,
+
+ .reg_cache_size = GBCODEC_REG_COUNT,
+ .reg_cache_default = gbcodec_reg_defaults,
+ .reg_word_size = 1,
+
+ .idle_bias_off = true,
+ .ignore_pmdown_time = 1,
+};
+
+#ifdef CONFIG_PM
+static int gbaudio_codec_suspend(struct device *dev)
+{
+ dev_dbg(dev, "%s: suspend\n", __func__);
+ return 0;
+}
+
+static int gbaudio_codec_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s: resume\n", __func__);
+ return 0;
+}
+
+static const struct dev_pm_ops gbaudio_codec_pm_ops = {
+ .suspend = gbaudio_codec_suspend,
+ .resume = gbaudio_codec_resume,
+};
+#endif
+
+static int gbaudio_codec_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_gbaudio,
+ gbaudio_dai, ARRAY_SIZE(gbaudio_dai));
+}
+
+static int gbaudio_codec_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_codec(&pdev->dev);
+ return 0;
+}
+
+static const struct of_device_id greybus_asoc_machine_of_match[] = {
+ { .compatible = "toshiba,apb-dummy-codec", },
+ {},
+};
+
+static struct platform_driver gbaudio_codec_driver = {
+ .driver = {
+ .name = "apb-dummy-codec",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &gbaudio_codec_pm_ops,
+#endif
+ .of_match_table = greybus_asoc_machine_of_match,
+ },
+ .probe = gbaudio_codec_probe,
+ .remove = gbaudio_codec_remove,
+};
+module_platform_driver(gbaudio_codec_driver);
+
+MODULE_DESCRIPTION("APBridge ALSA SoC dummy codec driver");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agarwal@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:apb-dummy-codec");