This driver implements the Greybus audio protocol.

Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>
---
 drivers/greybus/audio_apbridgea.c       |  207 ++++
 drivers/greybus/audio_apbridgea.h       |  156 +++
 drivers/greybus/audio_codec.c           | 1132 +++++++++++++++++++++++++
 drivers/greybus/audio_codec.h           |  283 ++++++
 drivers/greybus/audio_gb.c              |  228 +++++
 drivers/greybus/audio_manager.c         |  184 ++++
 drivers/greybus/audio_manager.h         |   83 +
 drivers/greybus/audio_manager_module.c  |  258 +++++
 drivers/greybus/audio_manager_private.h |   28 
 drivers/greybus/audio_manager_sysfs.c   |  102 ++
 drivers/greybus/audio_module.c          |  482 ++++++++++
 drivers/greybus/audio_topology.c        | 1442 ++++++++++++++++++++++++++++++++
 12 files changed, 4585 insertions(+)

--- /dev/null
+++ b/drivers/greybus/audio_apbridgea.c
@@ -0,0 +1,207 @@
+/*
+ * Greybus Audio Device Class Protocol helpers
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "greybus.h"
+#include "greybus_protocols.h"
+#include "audio_apbridgea.h"
+#include "audio_codec.h"
+
+int gb_audio_apbridgea_set_config(struct gb_connection *connection,
+                                 __u16 i2s_port, __u32 format, __u32 rate,
+                                 __u32 mclk_freq)
+{
+       struct audio_apbridgea_set_config_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_CONFIG;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.format = cpu_to_le32(format);
+       req.rate = cpu_to_le32(rate);
+       req.mclk_freq = cpu_to_le32(mclk_freq);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_config);
+
+int gb_audio_apbridgea_register_cport(struct gb_connection *connection,
+                                     __u16 i2s_port, __u16 cportid,
+                                     __u8 direction)
+{
+       struct audio_apbridgea_register_cport_request req;
+       int ret;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_REGISTER_CPORT;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.cport = cpu_to_le16(cportid);
+       req.direction = direction;
+
+       ret = gb_pm_runtime_get_sync(connection->bundle);
+       if (ret)
+               return ret;
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_register_cport);
+
+int gb_audio_apbridgea_unregister_cport(struct gb_connection *connection,
+                                       __u16 i2s_port, __u16 cportid,
+                                       __u8 direction)
+{
+       struct audio_apbridgea_unregister_cport_request req;
+       int ret;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_UNREGISTER_CPORT;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.cport = cpu_to_le16(cportid);
+       req.direction = direction;
+
+       ret = gb_hd_output(connection->hd, &req, sizeof(req),
+                          GB_APB_REQUEST_AUDIO_CONTROL, true);
+
+       gb_pm_runtime_put_autosuspend(connection->bundle);
+
+       return ret;
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_unregister_cport);
+
+int gb_audio_apbridgea_set_tx_data_size(struct gb_connection *connection,
+                                       __u16 i2s_port, __u16 size)
+{
+       struct audio_apbridgea_set_tx_data_size_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_TX_DATA_SIZE;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.size = cpu_to_le16(size);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_tx_data_size);
+
+int gb_audio_apbridgea_prepare_tx(struct gb_connection *connection,
+                                 __u16 i2s_port)
+{
+       struct audio_apbridgea_prepare_tx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_TX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_tx);
+
+int gb_audio_apbridgea_start_tx(struct gb_connection *connection,
+                               __u16 i2s_port, __u64 timestamp)
+{
+       struct audio_apbridgea_start_tx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_TX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.timestamp = cpu_to_le64(timestamp);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_tx);
+
+int gb_audio_apbridgea_stop_tx(struct gb_connection *connection, __u16 
i2s_port)
+{
+       struct audio_apbridgea_stop_tx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_TX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_tx);
+
+int gb_audio_apbridgea_shutdown_tx(struct gb_connection *connection,
+                                  __u16 i2s_port)
+{
+       struct audio_apbridgea_shutdown_tx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_TX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_tx);
+
+int gb_audio_apbridgea_set_rx_data_size(struct gb_connection *connection,
+                                       __u16 i2s_port, __u16 size)
+{
+       struct audio_apbridgea_set_rx_data_size_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_SET_RX_DATA_SIZE;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+       req.size = cpu_to_le16(size);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_set_rx_data_size);
+
+int gb_audio_apbridgea_prepare_rx(struct gb_connection *connection,
+                                 __u16 i2s_port)
+{
+       struct audio_apbridgea_prepare_rx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_PREPARE_RX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_prepare_rx);
+
+int gb_audio_apbridgea_start_rx(struct gb_connection *connection,
+                               __u16 i2s_port)
+{
+       struct audio_apbridgea_start_rx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_START_RX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_start_rx);
+
+int gb_audio_apbridgea_stop_rx(struct gb_connection *connection, __u16 
i2s_port)
+{
+       struct audio_apbridgea_stop_rx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_STOP_RX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_stop_rx);
+
+int gb_audio_apbridgea_shutdown_rx(struct gb_connection *connection,
+                                  __u16 i2s_port)
+{
+       struct audio_apbridgea_shutdown_rx_request req;
+
+       req.hdr.type = AUDIO_APBRIDGEA_TYPE_SHUTDOWN_RX;
+       req.hdr.i2s_port = cpu_to_le16(i2s_port);
+
+       return gb_hd_output(connection->hd, &req, sizeof(req),
+                           GB_APB_REQUEST_AUDIO_CONTROL, true);
+}
+EXPORT_SYMBOL_GPL(gb_audio_apbridgea_shutdown_rx);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("greybus:audio-apbridgea");
+MODULE_DESCRIPTION("Greybus Special APBridgeA Audio Protocol library");
+MODULE_AUTHOR("Mark Greer <mgr...@animalcreek.com>");
--- /dev/null
+++ b/drivers/greybus/audio_apbridgea.h
@@ -0,0 +1,156 @@
+/**
+ * Copyright (c) 2015-2016 Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of the copyright holder nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * This is a special protocol for configuring communication over the
+ * I2S bus between the DSP on the MSM8994 and APBridgeA.  Therefore,
+ * we can predefine several low-level attributes of the communication
+ * because we know that they are supported.  In particular, the following
+ * assumptions are made:
+ *     - there are two channels (i.e., stereo)
+ *     - the low-level protocol is I2S as defined by Philips/NXP
+ *     - the DSP on the MSM8994 is the clock master for MCLK, BCLK, and WCLK
+ *     - WCLK changes on the falling edge of BCLK
+ *     - WCLK low for left channel; high for right channel
+ *     - TX data is sent on the falling edge of BCLK
+ *     - RX data is received/latched on the rising edge of BCLK
+ */
+
+#ifndef __AUDIO_APBRIDGEA_H
+#define __AUDIO_APBRIDGEA_H
+
+#define AUDIO_APBRIDGEA_TYPE_SET_CONFIG                        0x01
+#define AUDIO_APBRIDGEA_TYPE_REGISTER_CPORT            0x02
+#define AUDIO_APBRIDGEA_TYPE_UNREGISTER_CPORT          0x03
+#define AUDIO_APBRIDGEA_TYPE_SET_TX_DATA_SIZE          0x04
+                                                       /* 0x05 unused */
+#define AUDIO_APBRIDGEA_TYPE_PREPARE_TX                        0x06
+#define AUDIO_APBRIDGEA_TYPE_START_TX                  0x07
+#define AUDIO_APBRIDGEA_TYPE_STOP_TX                   0x08
+#define AUDIO_APBRIDGEA_TYPE_SHUTDOWN_TX               0x09
+#define AUDIO_APBRIDGEA_TYPE_SET_RX_DATA_SIZE          0x0a
+                                                       /* 0x0b unused */
+#define AUDIO_APBRIDGEA_TYPE_PREPARE_RX                        0x0c
+#define AUDIO_APBRIDGEA_TYPE_START_RX                  0x0d
+#define AUDIO_APBRIDGEA_TYPE_STOP_RX                   0x0e
+#define AUDIO_APBRIDGEA_TYPE_SHUTDOWN_RX               0x0f
+
+#define AUDIO_APBRIDGEA_PCM_FMT_8                      BIT(0)
+#define AUDIO_APBRIDGEA_PCM_FMT_16                     BIT(1)
+#define AUDIO_APBRIDGEA_PCM_FMT_24                     BIT(2)
+#define AUDIO_APBRIDGEA_PCM_FMT_32                     BIT(3)
+#define AUDIO_APBRIDGEA_PCM_FMT_64                     BIT(4)
+
+#define AUDIO_APBRIDGEA_PCM_RATE_5512                  BIT(0)
+#define AUDIO_APBRIDGEA_PCM_RATE_8000                  BIT(1)
+#define AUDIO_APBRIDGEA_PCM_RATE_11025                 BIT(2)
+#define AUDIO_APBRIDGEA_PCM_RATE_16000                 BIT(3)
+#define AUDIO_APBRIDGEA_PCM_RATE_22050                 BIT(4)
+#define AUDIO_APBRIDGEA_PCM_RATE_32000                 BIT(5)
+#define AUDIO_APBRIDGEA_PCM_RATE_44100                 BIT(6)
+#define AUDIO_APBRIDGEA_PCM_RATE_48000                 BIT(7)
+#define AUDIO_APBRIDGEA_PCM_RATE_64000                 BIT(8)
+#define AUDIO_APBRIDGEA_PCM_RATE_88200                 BIT(9)
+#define AUDIO_APBRIDGEA_PCM_RATE_96000                 BIT(10)
+#define AUDIO_APBRIDGEA_PCM_RATE_176400                        BIT(11)
+#define AUDIO_APBRIDGEA_PCM_RATE_192000                        BIT(12)
+
+#define AUDIO_APBRIDGEA_DIRECTION_TX                   BIT(0)
+#define AUDIO_APBRIDGEA_DIRECTION_RX                   BIT(1)
+
+/* The I2S port is passed in the 'index' parameter of the USB request */
+/* The CPort is passed in the 'value' parameter of the USB request */
+
+struct audio_apbridgea_hdr {
+       __u8    type;
+       __le16  i2s_port;
+       __u8    data[0];
+} __packed;
+
+struct audio_apbridgea_set_config_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le32                          format; /* AUDIO_APBRIDGEA_PCM_FMT_* */
+       __le32                          rate;   /* AUDIO_APBRIDGEA_PCM_RATE_* */
+       __le32                          mclk_freq; /* XXX Remove? */
+} __packed;
+
+struct audio_apbridgea_register_cport_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le16                          cport;
+       __u8                            direction;
+} __packed;
+
+struct audio_apbridgea_unregister_cport_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le16                          cport;
+       __u8                            direction;
+} __packed;
+
+struct audio_apbridgea_set_tx_data_size_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le16                          size;
+} __packed;
+
+struct audio_apbridgea_prepare_tx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_start_tx_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le64                          timestamp;
+} __packed;
+
+struct audio_apbridgea_stop_tx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_shutdown_tx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_set_rx_data_size_request {
+       struct audio_apbridgea_hdr      hdr;
+       __le16                          size;
+} __packed;
+
+struct audio_apbridgea_prepare_rx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_start_rx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_stop_rx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+struct audio_apbridgea_shutdown_rx_request {
+       struct audio_apbridgea_hdr      hdr;
+} __packed;
+
+#endif /*__AUDIO_APBRIDGEA_H */
--- /dev/null
+++ b/drivers/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;
+       uint16_t data_cport, i2s_port, cportid;
+       uint8_t 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;
+       uint16_t 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;
+       uint16_t data_cport, i2s_port, cportid;
+       uint8_t 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;
+       uint16_t 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;
+       uint8_t 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)
+{
+       uint16_t 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)
+{
+       uint16_t 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.agar...@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:apb-dummy-codec");
--- /dev/null
+++ b/drivers/greybus/audio_codec.h
@@ -0,0 +1,283 @@
+/*
+ * Greybus audio driver
+ * Copyright 2015 Google Inc.
+ * Copyright 2015 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef __LINUX_GBAUDIO_CODEC_H
+#define __LINUX_GBAUDIO_CODEC_H
+
+#include <sound/soc.h>
+#include <sound/jack.h>
+
+#include "greybus.h"
+#include "greybus_protocols.h"
+
+#define NAME_SIZE      32
+#define MAX_DAIS       2       /* APB1, APB2 */
+
+enum {
+       APB1_PCM = 0,
+       APB2_PCM,
+       NUM_CODEC_DAIS,
+};
+
+enum gbcodec_reg_index {
+       GBCODEC_CTL_REG,
+       GBCODEC_MUTE_REG,
+       GBCODEC_PB_LVOL_REG,
+       GBCODEC_PB_RVOL_REG,
+       GBCODEC_CAP_LVOL_REG,
+       GBCODEC_CAP_RVOL_REG,
+       GBCODEC_APB1_MUX_REG,
+       GBCODEC_APB2_MUX_REG,
+       GBCODEC_REG_COUNT
+};
+
+/* device_type should be same as defined in audio.h (Android media layer) */
+enum {
+       GBAUDIO_DEVICE_NONE                     = 0x0,
+       /* reserved bits */
+       GBAUDIO_DEVICE_BIT_IN                   = 0x80000000,
+       GBAUDIO_DEVICE_BIT_DEFAULT              = 0x40000000,
+       /* output devices */
+       GBAUDIO_DEVICE_OUT_SPEAKER              = 0x2,
+       GBAUDIO_DEVICE_OUT_WIRED_HEADSET        = 0x4,
+       GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE      = 0x8,
+       /* input devices */
+       GBAUDIO_DEVICE_IN_BUILTIN_MIC           = GBAUDIO_DEVICE_BIT_IN | 0x4,
+       GBAUDIO_DEVICE_IN_WIRED_HEADSET         = GBAUDIO_DEVICE_BIT_IN | 0x10,
+};
+
+/* bit 0-SPK, 1-HP, 2-DAC,
+ * 4-MIC, 5-HSMIC, 6-MIC2
+ */
+#define GBCODEC_CTL_REG_DEFAULT                0x00
+
+/* bit 0,1 - APB1-PB-L/R
+ * bit 2,3 - APB2-PB-L/R
+ * bit 4,5 - APB1-Cap-L/R
+ * bit 6,7 - APB2-Cap-L/R
+ */
+#define        GBCODEC_MUTE_REG_DEFAULT        0x00
+
+/* 0-127 steps */
+#define        GBCODEC_PB_VOL_REG_DEFAULT      0x00
+#define        GBCODEC_CAP_VOL_REG_DEFAULT     0x00
+
+/* bit 0,1,2 - PB stereo, left, right
+ * bit 8,9,10 - Cap stereo, left, right
+ */
+#define GBCODEC_APB1_MUX_REG_DEFAULT   0x00
+#define GBCODEC_APB2_MUX_REG_DEFAULT   0x00
+
+#define GBCODEC_JACK_MASK              0x0000FFFF
+#define GBCODEC_JACK_BUTTON_MASK       0xFFFF0000
+
+static const u8 gbcodec_reg_defaults[GBCODEC_REG_COUNT] = {
+       GBCODEC_CTL_REG_DEFAULT,
+       GBCODEC_MUTE_REG_DEFAULT,
+       GBCODEC_PB_VOL_REG_DEFAULT,
+       GBCODEC_PB_VOL_REG_DEFAULT,
+       GBCODEC_CAP_VOL_REG_DEFAULT,
+       GBCODEC_CAP_VOL_REG_DEFAULT,
+       GBCODEC_APB1_MUX_REG_DEFAULT,
+       GBCODEC_APB2_MUX_REG_DEFAULT,
+};
+
+enum gbaudio_codec_state {
+       GBAUDIO_CODEC_SHUTDOWN = 0,
+       GBAUDIO_CODEC_STARTUP,
+       GBAUDIO_CODEC_HWPARAMS,
+       GBAUDIO_CODEC_PREPARE,
+       GBAUDIO_CODEC_START,
+       GBAUDIO_CODEC_STOP,
+};
+
+struct gbaudio_stream_params {
+       int state;
+       uint8_t sig_bits, channels;
+       uint32_t format, rate;
+};
+
+struct gbaudio_codec_dai {
+       int id;
+       /* runtime params for playback/capture streams */
+       struct gbaudio_stream_params params[2];
+       struct list_head list;
+};
+
+struct gbaudio_codec_info {
+       struct device *dev;
+       struct snd_soc_codec *codec;
+       struct list_head module_list;
+       /* to maintain runtime stream params for each DAI */
+       struct list_head dai_list;
+       struct mutex lock;
+       u8 reg[GBCODEC_REG_COUNT];
+};
+
+struct gbaudio_widget {
+       __u8 id;
+       const char *name;
+       struct list_head list;
+};
+
+struct gbaudio_control {
+       __u8 id;
+       char *name;
+       char *wname;
+       const char * const *texts;
+       int items;
+       struct list_head list;
+};
+
+struct gbaudio_data_connection {
+       int id;
+       __le16 data_cport;
+       struct gb_connection *connection;
+       struct list_head list;
+       /* maintain runtime state for playback/capture stream */
+       int state[2];
+};
+
+/* stream direction */
+#define GB_PLAYBACK    BIT(0)
+#define GB_CAPTURE     BIT(1)
+
+enum gbaudio_module_state {
+       GBAUDIO_MODULE_OFF = 0,
+       GBAUDIO_MODULE_ON,
+};
+
+struct gbaudio_module_info {
+       /* module info */
+       struct device *dev;
+       int dev_id;     /* check if it should be bundle_id/hd_cport_id */
+       int vid;
+       int pid;
+       int slot;
+       int type;
+       int set_uevent;
+       char vstr[NAME_SIZE];
+       char pstr[NAME_SIZE];
+       struct list_head list;
+       /* need to share this info to above user space */
+       int manager_id;
+       char name[NAME_SIZE];
+       unsigned int ip_devices;
+       unsigned int op_devices;
+
+       /* jack related */
+       char jack_name[NAME_SIZE];
+       char button_name[NAME_SIZE];
+       int jack_type;
+       int jack_mask;
+       int button_mask;
+       int button_status;
+       struct snd_soc_jack headset_jack;
+       struct snd_soc_jack button_jack;
+
+       /* connection info */
+       struct gb_connection *mgmt_connection;
+       size_t num_data_connections;
+       struct list_head data_list;
+
+       /* topology related */
+       int num_dais;
+       int num_controls;
+       int num_dapm_widgets;
+       int num_dapm_routes;
+       unsigned long dai_offset;
+       unsigned long widget_offset;
+       unsigned long control_offset;
+       unsigned long route_offset;
+       struct snd_kcontrol_new *controls;
+       struct snd_soc_dapm_widget *dapm_widgets;
+       struct snd_soc_dapm_route *dapm_routes;
+       struct snd_soc_dai_driver *dais;
+
+       struct list_head widget_list;
+       struct list_head ctl_list;
+       struct list_head widget_ctl_list;
+
+       struct gb_audio_topology *topology;
+};
+
+int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
+                              struct gb_audio_topology *tplg_data);
+void gbaudio_tplg_release(struct gbaudio_module_info *module);
+
+int gbaudio_module_update(struct gbaudio_codec_info *codec,
+                         struct snd_soc_dapm_widget *w,
+                         struct gbaudio_module_info *module,
+                         int enable);
+int gbaudio_register_module(struct gbaudio_module_info *module);
+void gbaudio_unregister_module(struct gbaudio_module_info *module);
+
+/* protocol related */
+extern int gb_audio_gb_get_topology(struct gb_connection *connection,
+                                   struct gb_audio_topology **topology);
+extern int gb_audio_gb_get_control(struct gb_connection *connection,
+                                  uint8_t control_id, uint8_t index,
+                                  struct gb_audio_ctl_elem_value *value);
+extern int gb_audio_gb_set_control(struct gb_connection *connection,
+                                  uint8_t control_id, uint8_t index,
+                                  struct gb_audio_ctl_elem_value *value);
+extern int gb_audio_gb_enable_widget(struct gb_connection *connection,
+                                    uint8_t widget_id);
+extern int gb_audio_gb_disable_widget(struct gb_connection *connection,
+                                     uint8_t widget_id);
+extern int gb_audio_gb_get_pcm(struct gb_connection *connection,
+                              uint16_t data_cport, uint32_t *format,
+                              uint32_t *rate, uint8_t *channels,
+                              uint8_t *sig_bits);
+extern int gb_audio_gb_set_pcm(struct gb_connection *connection,
+                              uint16_t data_cport, uint32_t format,
+                              uint32_t rate, uint8_t channels,
+                              uint8_t sig_bits);
+extern int gb_audio_gb_set_tx_data_size(struct gb_connection *connection,
+                                       uint16_t data_cport, uint16_t size);
+extern int gb_audio_gb_activate_tx(struct gb_connection *connection,
+                                  uint16_t data_cport);
+extern int gb_audio_gb_deactivate_tx(struct gb_connection *connection,
+                                    uint16_t data_cport);
+extern int gb_audio_gb_set_rx_data_size(struct gb_connection *connection,
+                                       uint16_t data_cport, uint16_t size);
+extern int gb_audio_gb_activate_rx(struct gb_connection *connection,
+                                  uint16_t data_cport);
+extern int gb_audio_gb_deactivate_rx(struct gb_connection *connection,
+                                    uint16_t data_cport);
+extern int gb_audio_apbridgea_set_config(struct gb_connection *connection,
+                                        __u16 i2s_port, __u32 format,
+                                        __u32 rate, __u32 mclk_freq);
+extern int gb_audio_apbridgea_register_cport(struct gb_connection *connection,
+                                            __u16 i2s_port, __u16 cportid,
+                                            __u8 direction);
+extern int gb_audio_apbridgea_unregister_cport(struct gb_connection 
*connection,
+                                              __u16 i2s_port, __u16 cportid,
+                                              __u8 direction);
+extern int gb_audio_apbridgea_set_tx_data_size(struct gb_connection 
*connection,
+                                              __u16 i2s_port, __u16 size);
+extern int gb_audio_apbridgea_prepare_tx(struct gb_connection *connection,
+                                        __u16 i2s_port);
+extern int gb_audio_apbridgea_start_tx(struct gb_connection *connection,
+                                      __u16 i2s_port, __u64 timestamp);
+extern int gb_audio_apbridgea_stop_tx(struct gb_connection *connection,
+                                     __u16 i2s_port);
+extern int gb_audio_apbridgea_shutdown_tx(struct gb_connection *connection,
+                                         __u16 i2s_port);
+extern int gb_audio_apbridgea_set_rx_data_size(struct gb_connection 
*connection,
+                                              __u16 i2s_port, __u16 size);
+extern int gb_audio_apbridgea_prepare_rx(struct gb_connection *connection,
+                                        __u16 i2s_port);
+extern int gb_audio_apbridgea_start_rx(struct gb_connection *connection,
+                                      __u16 i2s_port);
+extern int gb_audio_apbridgea_stop_rx(struct gb_connection *connection,
+                                     __u16 i2s_port);
+extern int gb_audio_apbridgea_shutdown_rx(struct gb_connection *connection,
+                                         __u16 i2s_port);
+
+#endif /* __LINUX_GBAUDIO_CODEC_H */
--- /dev/null
+++ b/drivers/greybus/audio_gb.c
@@ -0,0 +1,228 @@
+/*
+ * Greybus Audio Device Class Protocol helpers
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "greybus.h"
+#include "greybus_protocols.h"
+#include "operation.h"
+#include "audio_codec.h"
+
+/* TODO: Split into separate calls */
+int gb_audio_gb_get_topology(struct gb_connection *connection,
+                            struct gb_audio_topology **topology)
+{
+       struct gb_audio_get_topology_size_response size_resp;
+       struct gb_audio_topology *topo;
+       uint16_t size;
+       int ret;
+
+       ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_TOPOLOGY_SIZE,
+                               NULL, 0, &size_resp, sizeof(size_resp));
+       if (ret)
+               return ret;
+
+       size = le16_to_cpu(size_resp.size);
+       if (size < sizeof(*topo))
+               return -ENODATA;
+
+       topo = kzalloc(size, GFP_KERNEL);
+       if (!topo)
+               return -ENOMEM;
+
+       ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_TOPOLOGY, NULL, 0,
+                               topo, size);
+       if (ret) {
+               kfree(topo);
+               return ret;
+       }
+
+       *topology = topo;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_topology);
+
+int gb_audio_gb_get_control(struct gb_connection *connection,
+                           uint8_t control_id, uint8_t index,
+                           struct gb_audio_ctl_elem_value *value)
+{
+       struct gb_audio_get_control_request req;
+       struct gb_audio_get_control_response resp;
+       int ret;
+
+       req.control_id = control_id;
+       req.index = index;
+
+       ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_CONTROL,
+                               &req, sizeof(req), &resp, sizeof(resp));
+       if (ret)
+               return ret;
+
+       memcpy(value, &resp.value, sizeof(*value));
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_control);
+
+int gb_audio_gb_set_control(struct gb_connection *connection,
+                           uint8_t control_id, uint8_t index,
+                           struct gb_audio_ctl_elem_value *value)
+{
+       struct gb_audio_set_control_request req;
+
+       req.control_id = control_id;
+       req.index = index;
+       memcpy(&req.value, value, sizeof(req.value));
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_CONTROL,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_control);
+
+int gb_audio_gb_enable_widget(struct gb_connection *connection,
+                             uint8_t widget_id)
+{
+       struct gb_audio_enable_widget_request req;
+
+       req.widget_id = widget_id;
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_ENABLE_WIDGET,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_enable_widget);
+
+int gb_audio_gb_disable_widget(struct gb_connection *connection,
+                              uint8_t widget_id)
+{
+       struct gb_audio_disable_widget_request req;
+
+       req.widget_id = widget_id;
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_DISABLE_WIDGET,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_disable_widget);
+
+int gb_audio_gb_get_pcm(struct gb_connection *connection, uint16_t data_cport,
+                       uint32_t *format, uint32_t *rate, uint8_t *channels,
+                       uint8_t *sig_bits)
+{
+       struct gb_audio_get_pcm_request req;
+       struct gb_audio_get_pcm_response resp;
+       int ret;
+
+       req.data_cport = cpu_to_le16(data_cport);
+
+       ret = gb_operation_sync(connection, GB_AUDIO_TYPE_GET_PCM,
+                               &req, sizeof(req), &resp, sizeof(resp));
+       if (ret)
+               return ret;
+
+       *format = le32_to_cpu(resp.format);
+       *rate = le32_to_cpu(resp.rate);
+       *channels = resp.channels;
+       *sig_bits = resp.sig_bits;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_get_pcm);
+
+int gb_audio_gb_set_pcm(struct gb_connection *connection, uint16_t data_cport,
+                       uint32_t format, uint32_t rate, uint8_t channels,
+                       uint8_t sig_bits)
+{
+       struct gb_audio_set_pcm_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+       req.format = cpu_to_le32(format);
+       req.rate = cpu_to_le32(rate);
+       req.channels = channels;
+       req.sig_bits = sig_bits;
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_PCM,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_pcm);
+
+int gb_audio_gb_set_tx_data_size(struct gb_connection *connection,
+                                uint16_t data_cport, uint16_t size)
+{
+       struct gb_audio_set_tx_data_size_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+       req.size = cpu_to_le16(size);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_TX_DATA_SIZE,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_tx_data_size);
+
+int gb_audio_gb_activate_tx(struct gb_connection *connection,
+                           uint16_t data_cport)
+{
+       struct gb_audio_activate_tx_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_ACTIVATE_TX,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_activate_tx);
+
+int gb_audio_gb_deactivate_tx(struct gb_connection *connection,
+                             uint16_t data_cport)
+{
+       struct gb_audio_deactivate_tx_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_DEACTIVATE_TX,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_deactivate_tx);
+
+int gb_audio_gb_set_rx_data_size(struct gb_connection *connection,
+                                uint16_t data_cport, uint16_t size)
+{
+       struct gb_audio_set_rx_data_size_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+       req.size = cpu_to_le16(size);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_SET_RX_DATA_SIZE,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_set_rx_data_size);
+
+int gb_audio_gb_activate_rx(struct gb_connection *connection,
+                           uint16_t data_cport)
+{
+       struct gb_audio_activate_rx_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_ACTIVATE_RX,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_activate_rx);
+
+int gb_audio_gb_deactivate_rx(struct gb_connection *connection,
+                             uint16_t data_cport)
+{
+       struct gb_audio_deactivate_rx_request req;
+
+       req.data_cport = cpu_to_le16(data_cport);
+
+       return gb_operation_sync(connection, GB_AUDIO_TYPE_DEACTIVATE_RX,
+                                &req, sizeof(req), NULL, 0);
+}
+EXPORT_SYMBOL_GPL(gb_audio_gb_deactivate_rx);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("greybus:audio-gb");
+MODULE_DESCRIPTION("Greybus Audio Device Class Protocol library");
+MODULE_AUTHOR("Mark Greer <mgr...@animalcreek.com>");
--- /dev/null
+++ b/drivers/greybus/audio_manager.c
@@ -0,0 +1,184 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/string.h>
+#include <linux/sysfs.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rwlock.h>
+#include <linux/idr.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+static struct kset *manager_kset;
+
+static LIST_HEAD(modules_list);
+static DECLARE_RWSEM(modules_rwsem);
+static DEFINE_IDA(module_id);
+
+/* helpers */
+static struct gb_audio_manager_module *gb_audio_manager_get_locked(int id)
+{
+       struct gb_audio_manager_module *module;
+
+       if (id < 0)
+               return NULL;
+
+       list_for_each_entry(module, &modules_list, list) {
+               if (module->id == id)
+                       return module;
+       }
+
+       return NULL;
+}
+
+/* public API */
+int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc)
+{
+       struct gb_audio_manager_module *module;
+       int id;
+       int err;
+
+       id = ida_simple_get(&module_id, 0, 0, GFP_KERNEL);
+       err = gb_audio_manager_module_create(&module, manager_kset,
+                                            id, desc);
+       if (err) {
+               ida_simple_remove(&module_id, id);
+               return err;
+       }
+
+       /* Add it to the list */
+       down_write(&modules_rwsem);
+       list_add_tail(&module->list, &modules_list);
+       up_write(&modules_rwsem);
+
+       return module->id;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_add);
+
+int gb_audio_manager_remove(int id)
+{
+       struct gb_audio_manager_module *module;
+
+       down_write(&modules_rwsem);
+
+       module = gb_audio_manager_get_locked(id);
+       if (!module) {
+               up_write(&modules_rwsem);
+               return -EINVAL;
+       }
+       list_del(&module->list);
+       kobject_put(&module->kobj);
+       up_write(&modules_rwsem);
+       ida_simple_remove(&module_id, id);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_remove);
+
+void gb_audio_manager_remove_all(void)
+{
+       struct gb_audio_manager_module *module, *next;
+       int is_empty = 1;
+
+       down_write(&modules_rwsem);
+
+       list_for_each_entry_safe(module, next, &modules_list, list) {
+               list_del(&module->list);
+               kobject_put(&module->kobj);
+               ida_simple_remove(&module_id, module->id);
+       }
+
+       is_empty = list_empty(&modules_list);
+
+       up_write(&modules_rwsem);
+
+       if (!is_empty)
+               pr_warn("Not all nodes were deleted\n");
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_remove_all);
+
+struct gb_audio_manager_module *gb_audio_manager_get_module(int id)
+{
+       struct gb_audio_manager_module *module;
+
+       down_read(&modules_rwsem);
+       module = gb_audio_manager_get_locked(id);
+       kobject_get(&module->kobj);
+       up_read(&modules_rwsem);
+       return module;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_get_module);
+
+void gb_audio_manager_put_module(struct gb_audio_manager_module *module)
+{
+       kobject_put(&module->kobj);
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_put_module);
+
+int gb_audio_manager_dump_module(int id)
+{
+       struct gb_audio_manager_module *module;
+
+       down_read(&modules_rwsem);
+       module = gb_audio_manager_get_locked(id);
+       up_read(&modules_rwsem);
+
+       if (!module)
+               return -EINVAL;
+
+       gb_audio_manager_module_dump(module);
+       return 0;
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_dump_module);
+
+void gb_audio_manager_dump_all(void)
+{
+       struct gb_audio_manager_module *module;
+       int count = 0;
+
+       down_read(&modules_rwsem);
+       list_for_each_entry(module, &modules_list, list) {
+               gb_audio_manager_module_dump(module);
+               count++;
+       }
+       up_read(&modules_rwsem);
+
+       pr_info("Number of connected modules: %d\n", count);
+}
+EXPORT_SYMBOL_GPL(gb_audio_manager_dump_all);
+
+/*
+ * module init/deinit
+ */
+static int __init manager_init(void)
+{
+       manager_kset = kset_create_and_add(GB_AUDIO_MANAGER_NAME, NULL,
+                                          kernel_kobj);
+       if (!manager_kset)
+               return -ENOMEM;
+
+#ifdef GB_AUDIO_MANAGER_SYSFS
+       gb_audio_manager_sysfs_init(&manager_kset->kobj);
+#endif
+
+       return 0;
+}
+
+static void __exit manager_exit(void)
+{
+       gb_audio_manager_remove_all();
+       kset_unregister(manager_kset);
+       ida_destroy(&module_id);
+}
+
+module_init(manager_init);
+module_exit(manager_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Svetlin Ankov <ankov_svet...@projectara.com>");
--- /dev/null
+++ b/drivers/greybus/audio_manager.h
@@ -0,0 +1,83 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef _GB_AUDIO_MANAGER_H_
+#define _GB_AUDIO_MANAGER_H_
+
+#include <linux/kobject.h>
+#include <linux/list.h>
+
+#define GB_AUDIO_MANAGER_NAME "gb_audio_manager"
+#define GB_AUDIO_MANAGER_MODULE_NAME_LEN 64
+#define GB_AUDIO_MANAGER_MODULE_NAME_LEN_SSCANF "63"
+
+struct gb_audio_manager_module_descriptor {
+       char name[GB_AUDIO_MANAGER_MODULE_NAME_LEN];
+       int slot;
+       int vid;
+       int pid;
+       int cport;
+       unsigned int ip_devices;
+       unsigned int op_devices;
+};
+
+struct gb_audio_manager_module {
+       struct kobject kobj;
+       struct list_head list;
+       int id;
+       struct gb_audio_manager_module_descriptor desc;
+};
+
+/*
+ * Creates a new gb_audio_manager_module_descriptor, using the specified
+ * descriptor.
+ *
+ * Returns a negative result on error, or the id of the newly created module.
+ *
+ */
+int gb_audio_manager_add(struct gb_audio_manager_module_descriptor *desc);
+
+/*
+ * Removes a connected gb_audio_manager_module_descriptor for the specified ID.
+ *
+ * Returns zero on success, or a negative value on error.
+ */
+int gb_audio_manager_remove(int id);
+
+/*
+ * Removes all connected gb_audio_modules
+ *
+ * Returns zero on success, or a negative value on error.
+ */
+void gb_audio_manager_remove_all(void);
+
+/*
+ * Retrieves a gb_audio_manager_module_descriptor for the specified id.
+ * Returns the gb_audio_manager_module_descriptor structure,
+ * or NULL if there is no module with the specified ID.
+ */
+struct gb_audio_manager_module *gb_audio_manager_get_module(int id);
+
+/*
+ * Decreases the refcount of the module, obtained by the get function.
+ * Modules are removed via gb_audio_manager_remove
+ */
+void gb_audio_manager_put_module(struct gb_audio_manager_module *module);
+
+/*
+ * Dumps the module for the specified id
+ * Return 0 on success
+ */
+int gb_audio_manager_dump_module(int id);
+
+/*
+ * Dumps all connected modules
+ */
+void gb_audio_manager_dump_all(void);
+
+#endif /* _GB_AUDIO_MANAGER_H_ */
--- /dev/null
+++ b/drivers/greybus/audio_manager_module.c
@@ -0,0 +1,258 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/slab.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+#define to_gb_audio_module_attr(x)     \
+               container_of(x, struct gb_audio_manager_module_attribute, attr)
+#define to_gb_audio_module(x)          \
+               container_of(x, struct gb_audio_manager_module, kobj)
+
+struct gb_audio_manager_module_attribute {
+       struct attribute attr;
+       ssize_t (*show)(struct gb_audio_manager_module *module,
+                       struct gb_audio_manager_module_attribute *attr,
+                       char *buf);
+       ssize_t (*store)(struct gb_audio_manager_module *module,
+                        struct gb_audio_manager_module_attribute *attr,
+                        const char *buf, size_t count);
+};
+
+static ssize_t gb_audio_module_attr_show(
+       struct kobject *kobj, struct attribute *attr, char *buf)
+{
+       struct gb_audio_manager_module_attribute *attribute;
+       struct gb_audio_manager_module *module;
+
+       attribute = to_gb_audio_module_attr(attr);
+       module = to_gb_audio_module(kobj);
+
+       if (!attribute->show)
+               return -EIO;
+
+       return attribute->show(module, attribute, buf);
+}
+
+static ssize_t gb_audio_module_attr_store(struct kobject *kobj,
+                                         struct attribute *attr,
+                                         const char *buf, size_t len)
+{
+       struct gb_audio_manager_module_attribute *attribute;
+       struct gb_audio_manager_module *module;
+
+       attribute = to_gb_audio_module_attr(attr);
+       module = to_gb_audio_module(kobj);
+
+       if (!attribute->store)
+               return -EIO;
+
+       return attribute->store(module, attribute, buf, len);
+}
+
+static const struct sysfs_ops gb_audio_module_sysfs_ops = {
+       .show = gb_audio_module_attr_show,
+       .store = gb_audio_module_attr_store,
+};
+
+static void gb_audio_module_release(struct kobject *kobj)
+{
+       struct gb_audio_manager_module *module = to_gb_audio_module(kobj);
+
+       pr_info("Destroying audio module #%d\n", module->id);
+       /* TODO -> delete from list */
+       kfree(module);
+}
+
+static ssize_t gb_audio_module_name_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%s", module->desc.name);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_name_attribute 
=
+       __ATTR(name, 0664, gb_audio_module_name_show, NULL);
+
+static ssize_t gb_audio_module_slot_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d", module->desc.slot);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_slot_attribute 
=
+       __ATTR(slot, 0664, gb_audio_module_slot_show, NULL);
+
+static ssize_t gb_audio_module_vid_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d", module->desc.vid);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_vid_attribute =
+       __ATTR(vid, 0664, gb_audio_module_vid_show, NULL);
+
+static ssize_t gb_audio_module_pid_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d", module->desc.pid);
+}
+
+static struct gb_audio_manager_module_attribute gb_audio_module_pid_attribute =
+       __ATTR(pid, 0664, gb_audio_module_pid_show, NULL);
+
+static ssize_t gb_audio_module_cport_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d", module->desc.cport);
+}
+
+static struct gb_audio_manager_module_attribute
+                                       gb_audio_module_cport_attribute =
+       __ATTR(cport, 0664, gb_audio_module_cport_show, NULL);
+
+static ssize_t gb_audio_module_ip_devices_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "0x%X", module->desc.ip_devices);
+}
+
+static struct gb_audio_manager_module_attribute
+                                       gb_audio_module_ip_devices_attribute =
+       __ATTR(ip_devices, 0664, gb_audio_module_ip_devices_show, NULL);
+
+static ssize_t gb_audio_module_op_devices_show(
+       struct gb_audio_manager_module *module,
+       struct gb_audio_manager_module_attribute *attr, char *buf)
+{
+       return sprintf(buf, "0x%X", module->desc.op_devices);
+}
+
+static struct gb_audio_manager_module_attribute
+                                       gb_audio_module_op_devices_attribute =
+       __ATTR(op_devices, 0664, gb_audio_module_op_devices_show, NULL);
+
+static struct attribute *gb_audio_module_default_attrs[] = {
+       &gb_audio_module_name_attribute.attr,
+       &gb_audio_module_slot_attribute.attr,
+       &gb_audio_module_vid_attribute.attr,
+       &gb_audio_module_pid_attribute.attr,
+       &gb_audio_module_cport_attribute.attr,
+       &gb_audio_module_ip_devices_attribute.attr,
+       &gb_audio_module_op_devices_attribute.attr,
+       NULL,   /* need to NULL terminate the list of attributes */
+};
+
+static struct kobj_type gb_audio_module_type = {
+       .sysfs_ops = &gb_audio_module_sysfs_ops,
+       .release = gb_audio_module_release,
+       .default_attrs = gb_audio_module_default_attrs,
+};
+
+static void send_add_uevent(struct gb_audio_manager_module *module)
+{
+       char name_string[128];
+       char slot_string[64];
+       char vid_string[64];
+       char pid_string[64];
+       char cport_string[64];
+       char ip_devices_string[64];
+       char op_devices_string[64];
+
+       char *envp[] = {
+               name_string,
+               slot_string,
+               vid_string,
+               pid_string,
+               cport_string,
+               ip_devices_string,
+               op_devices_string,
+               NULL
+       };
+
+       snprintf(name_string, 128, "NAME=%s", module->desc.name);
+       snprintf(slot_string, 64, "SLOT=%d", module->desc.slot);
+       snprintf(vid_string, 64, "VID=%d", module->desc.vid);
+       snprintf(pid_string, 64, "PID=%d", module->desc.pid);
+       snprintf(cport_string, 64, "CPORT=%d", module->desc.cport);
+       snprintf(ip_devices_string, 64, "I/P DEVICES=0x%X",
+                module->desc.ip_devices);
+       snprintf(op_devices_string, 64, "O/P DEVICES=0x%X",
+                module->desc.op_devices);
+
+       kobject_uevent_env(&module->kobj, KOBJ_ADD, envp);
+}
+
+int gb_audio_manager_module_create(
+       struct gb_audio_manager_module **module,
+       struct kset *manager_kset,
+       int id, struct gb_audio_manager_module_descriptor *desc)
+{
+       int err;
+       struct gb_audio_manager_module *m;
+
+       m = kzalloc(sizeof(*m), GFP_ATOMIC);
+       if (!m)
+               return -ENOMEM;
+
+       /* Initialize the node */
+       INIT_LIST_HEAD(&m->list);
+
+       /* Set the module id */
+       m->id = id;
+
+       /* Copy the provided descriptor */
+       memcpy(&m->desc, desc, sizeof(*desc));
+
+       /* set the kset */
+       m->kobj.kset = manager_kset;
+
+       /*
+        * Initialize and add the kobject to the kernel.  All the default files
+        * will be created here.  As we have already specified a kset for this
+        * kobject, we don't have to set a parent for the kobject, the kobject
+        * will be placed beneath that kset automatically.
+        */
+       err = kobject_init_and_add(&m->kobj, &gb_audio_module_type, NULL, "%d",
+                                  id);
+       if (err) {
+               pr_err("failed initializing kobject for audio module #%d\n",
+                      id);
+               kobject_put(&m->kobj);
+               return err;
+       }
+
+       /*
+        * Notify the object was created
+        */
+       send_add_uevent(m);
+
+       *module = m;
+       pr_info("Created audio module #%d\n", id);
+       return 0;
+}
+
+void gb_audio_manager_module_dump(struct gb_audio_manager_module *module)
+{
+       pr_info("audio module #%d name=%s slot=%d vid=%d pid=%d cport=%d i/p 
devices=0x%X o/p devices=0x%X\n",
+               module->id,
+               module->desc.name,
+               module->desc.slot,
+               module->desc.vid,
+               module->desc.pid,
+               module->desc.cport,
+               module->desc.ip_devices,
+               module->desc.op_devices);
+}
--- /dev/null
+++ b/drivers/greybus/audio_manager_private.h
@@ -0,0 +1,28 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#ifndef _GB_AUDIO_MANAGER_PRIVATE_H_
+#define _GB_AUDIO_MANAGER_PRIVATE_H_
+
+#include <linux/kobject.h>
+
+#include "audio_manager.h"
+
+int gb_audio_manager_module_create(
+       struct gb_audio_manager_module **module,
+       struct kset *manager_kset,
+       int id, struct gb_audio_manager_module_descriptor *desc);
+
+/* module destroyed via kobject_put */
+
+void gb_audio_manager_module_dump(struct gb_audio_manager_module *module);
+
+/* sysfs control */
+void gb_audio_manager_sysfs_init(struct kobject *kobj);
+
+#endif /* _GB_AUDIO_MANAGER_PRIVATE_H_ */
--- /dev/null
+++ b/drivers/greybus/audio_manager_sysfs.c
@@ -0,0 +1,102 @@
+/*
+ * Greybus operations
+ *
+ * Copyright 2015-2016 Google Inc.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include <linux/string.h>
+#include <linux/sysfs.h>
+
+#include "audio_manager.h"
+#include "audio_manager_private.h"
+
+static ssize_t manager_sysfs_add_store(
+       struct kobject *kobj, struct kobj_attribute *attr,
+       const char *buf, size_t count)
+{
+       struct gb_audio_manager_module_descriptor desc = { {0} };
+
+       int num = sscanf(buf,
+                       "name=%" GB_AUDIO_MANAGER_MODULE_NAME_LEN_SSCANF "s "
+                       "slot=%d vid=%d pid=%d cport=%d i/p devices=0x%X"
+                       "o/p devices=0x%X",
+                       desc.name, &desc.slot, &desc.vid, &desc.pid,
+                       &desc.cport, &desc.ip_devices, &desc.op_devices);
+
+       if (num != 7)
+               return -EINVAL;
+
+       num = gb_audio_manager_add(&desc);
+       if (num < 0)
+               return -EINVAL;
+
+       return count;
+}
+
+static struct kobj_attribute manager_add_attribute =
+       __ATTR(add, 0664, NULL, manager_sysfs_add_store);
+
+static ssize_t manager_sysfs_remove_store(
+       struct kobject *kobj, struct kobj_attribute *attr,
+       const char *buf, size_t count)
+{
+       int id;
+
+       int num = sscanf(buf, "%d", &id);
+
+       if (num != 1)
+               return -EINVAL;
+
+       num = gb_audio_manager_remove(id);
+       if (num)
+               return num;
+
+       return count;
+}
+
+static struct kobj_attribute manager_remove_attribute =
+       __ATTR(remove, 0664, NULL, manager_sysfs_remove_store);
+
+static ssize_t manager_sysfs_dump_store(
+       struct kobject *kobj, struct kobj_attribute *attr,
+       const char *buf, size_t count)
+{
+       int id;
+
+       int num = sscanf(buf, "%d", &id);
+
+       if (num == 1) {
+               num = gb_audio_manager_dump_module(id);
+               if (num)
+                       return num;
+       } else if (!strncmp("all", buf, 3))
+               gb_audio_manager_dump_all();
+       else
+               return -EINVAL;
+
+       return count;
+}
+
+static struct kobj_attribute manager_dump_attribute =
+       __ATTR(dump, 0664, NULL, manager_sysfs_dump_store);
+
+static void manager_sysfs_init_attribute(
+               struct kobject *kobj, struct kobj_attribute *kattr)
+{
+       int err;
+
+       err = sysfs_create_file(kobj, &kattr->attr);
+       if (err) {
+               pr_warn("creating the sysfs entry for %s failed: %d\n",
+                       kattr->attr.name, err);
+       }
+}
+
+void gb_audio_manager_sysfs_init(struct kobject *kobj)
+{
+       manager_sysfs_init_attribute(kobj, &manager_add_attribute);
+       manager_sysfs_init_attribute(kobj, &manager_remove_attribute);
+       manager_sysfs_init_attribute(kobj, &manager_dump_attribute);
+}
--- /dev/null
+++ b/drivers/greybus/audio_module.c
@@ -0,0 +1,482 @@
+/*
+ * 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 "audio_codec.h"
+#include "audio_apbridgea.h"
+#include "audio_manager.h"
+
+/*
+ * gb_snd management functions
+ */
+
+static int gbaudio_request_jack(struct gbaudio_module_info *module,
+                                 struct gb_audio_jack_event_request *req)
+{
+       int report;
+       struct snd_jack *jack = module->headset_jack.jack;
+       struct snd_jack *btn_jack = module->button_jack.jack;
+
+       if (!jack) {
+               dev_err_ratelimited(module->dev,
+                       "Invalid jack event received:type: %u, event: %u\n",
+                       req->jack_attribute, req->event);
+               return -EINVAL;
+       }
+
+       dev_warn_ratelimited(module->dev,
+                            "Jack Event received: type: %u, event: %u\n",
+                            req->jack_attribute, req->event);
+
+       if (req->event == GB_AUDIO_JACK_EVENT_REMOVAL) {
+               module->jack_type = 0;
+               if (btn_jack && module->button_status) {
+                       snd_soc_jack_report(&module->button_jack, 0,
+                                           module->button_mask);
+                       module->button_status = 0;
+               }
+               snd_soc_jack_report(&module->headset_jack, 0,
+                                   module->jack_mask);
+               return 0;
+       }
+
+       report = req->jack_attribute & module->jack_mask;
+       if (!report) {
+               dev_err_ratelimited(module->dev,
+                       "Invalid jack event received:type: %u, event: %u\n",
+                       req->jack_attribute, req->event);
+               return -EINVAL;
+       }
+
+       if (module->jack_type)
+               dev_warn_ratelimited(module->dev,
+                                    "Modifying jack from %d to %d\n",
+                                    module->jack_type, report);
+
+       module->jack_type = report;
+       snd_soc_jack_report(&module->headset_jack, report, module->jack_mask);
+
+       return 0;
+}
+
+static int gbaudio_request_button(struct gbaudio_module_info *module,
+                                 struct gb_audio_button_event_request *req)
+{
+       int soc_button_id, report;
+       struct snd_jack *btn_jack = module->button_jack.jack;
+
+       if (!btn_jack) {
+               dev_err_ratelimited(module->dev,
+                       "Invalid button event received:type: %u, event: %u\n",
+                       req->button_id, req->event);
+               return -EINVAL;
+       }
+
+       dev_warn_ratelimited(module->dev,
+                            "Button Event received: id: %u, event: %u\n",
+                            req->button_id, req->event);
+
+       /* currently supports 4 buttons only */
+       if (!module->jack_type) {
+               dev_err_ratelimited(module->dev,
+                                   "Jack not present. Bogus event!!\n");
+               return -EINVAL;
+       }
+
+       report = module->button_status & module->button_mask;
+       soc_button_id = 0;
+
+       switch (req->button_id) {
+       case 1:
+               soc_button_id = SND_JACK_BTN_0 & module->button_mask;
+               break;
+
+       case 2:
+               soc_button_id = SND_JACK_BTN_1 & module->button_mask;
+               break;
+
+       case 3:
+               soc_button_id = SND_JACK_BTN_2 & module->button_mask;
+               break;
+
+       case 4:
+               soc_button_id = SND_JACK_BTN_3 & module->button_mask;
+               break;
+       }
+
+       if (!soc_button_id) {
+               dev_err_ratelimited(module->dev,
+                                   "Invalid button request received\n");
+               return -EINVAL;
+       }
+
+       if (req->event == GB_AUDIO_BUTTON_EVENT_PRESS)
+               report = report | soc_button_id;
+       else
+               report = report & ~soc_button_id;
+
+       module->button_status = report;
+
+       snd_soc_jack_report(&module->button_jack, report, module->button_mask);
+
+       return 0;
+}
+
+static int gbaudio_request_stream(struct gbaudio_module_info *module,
+                                 struct gb_audio_streaming_event_request *req)
+{
+       dev_warn(module->dev, "Audio Event received: cport: %u, event: %u\n",
+                req->data_cport, req->event);
+
+       return 0;
+}
+
+static int gbaudio_codec_request_handler(struct gb_operation *op)
+{
+       struct gb_connection *connection = op->connection;
+       struct gbaudio_module_info *module =
+               greybus_get_drvdata(connection->bundle);
+       struct gb_operation_msg_hdr *header = op->request->header;
+       struct gb_audio_streaming_event_request *stream_req;
+       struct gb_audio_jack_event_request *jack_req;
+       struct gb_audio_button_event_request *button_req;
+       int ret;
+
+       switch (header->type) {
+       case GB_AUDIO_TYPE_STREAMING_EVENT:
+               stream_req = op->request->payload;
+               ret = gbaudio_request_stream(module, stream_req);
+               break;
+
+       case GB_AUDIO_TYPE_JACK_EVENT:
+               jack_req = op->request->payload;
+               ret = gbaudio_request_jack(module, jack_req);
+               break;
+
+       case GB_AUDIO_TYPE_BUTTON_EVENT:
+               button_req = op->request->payload;
+               ret = gbaudio_request_button(module, button_req);
+               break;
+
+       default:
+               dev_err_ratelimited(&connection->bundle->dev,
+                                   "Invalid Audio Event received\n");
+               return -EINVAL;
+       }
+
+       return ret;
+}
+
+static int gb_audio_add_mgmt_connection(struct gbaudio_module_info *gbmodule,
+                               struct greybus_descriptor_cport *cport_desc,
+                               struct gb_bundle *bundle)
+{
+       struct gb_connection *connection;
+
+       /* Management Cport */
+       if (gbmodule->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);
+
+       greybus_set_drvdata(bundle, gbmodule);
+       gbmodule->mgmt_connection = connection;
+
+       return 0;
+}
+
+static int gb_audio_add_data_connection(struct gbaudio_module_info *gbmodule,
+                               struct greybus_descriptor_cport *cport_desc,
+                               struct gb_bundle *bundle)
+{
+       struct gb_connection *connection;
+       struct gbaudio_data_connection *dai;
+
+       dai = devm_kzalloc(gbmodule->dev, sizeof(*dai), GFP_KERNEL);
+       if (!dai) {
+               dev_err(gbmodule->dev, "DAI Malloc failure\n");
+               return -ENOMEM;
+       }
+
+       connection = gb_connection_create_offloaded(bundle,
+                                       le16_to_cpu(cport_desc->id),
+                                       GB_CONNECTION_FLAG_CSD);
+       if (IS_ERR(connection)) {
+               devm_kfree(gbmodule->dev, dai);
+               return PTR_ERR(connection);
+       }
+
+       greybus_set_drvdata(bundle, gbmodule);
+       dai->id = 0;
+       dai->data_cport = connection->intf_cport_id;
+       dai->connection = connection;
+       list_add(&dai->list, &gbmodule->data_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_module_info *gbmodule;
+       struct greybus_descriptor_cport *cport_desc;
+       struct gb_audio_manager_module_descriptor desc;
+       struct gbaudio_data_connection *dai, *_dai;
+       int ret, i;
+       struct gb_audio_topology *topology;
+
+       /* There should be at least one Management and one Data cport */
+       if (bundle->num_cports < 2)
+               return -ENODEV;
+
+       /*
+        * There can be only one Management connection and any number of data
+        * connections.
+        */
+       gbmodule = devm_kzalloc(dev, sizeof(*gbmodule), GFP_KERNEL);
+       if (!gbmodule)
+               return -ENOMEM;
+
+       gbmodule->num_data_connections = bundle->num_cports - 1;
+       INIT_LIST_HEAD(&gbmodule->data_list);
+       INIT_LIST_HEAD(&gbmodule->widget_list);
+       INIT_LIST_HEAD(&gbmodule->ctl_list);
+       INIT_LIST_HEAD(&gbmodule->widget_ctl_list);
+       gbmodule->dev = dev;
+       snprintf(gbmodule->name, NAME_SIZE, "%s.%s", dev->driver->name,
+                dev_name(dev));
+       greybus_set_drvdata(bundle, gbmodule);
+
+       /* 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(gbmodule, cport_desc,
+                                                          bundle);
+                       if (ret)
+                               goto destroy_connections;
+                       break;
+               case GREYBUS_PROTOCOL_AUDIO_DATA:
+                       ret = gb_audio_add_data_connection(gbmodule, 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 (!gbmodule->mgmt_connection) {
+               ret = -EINVAL;
+               dev_err(dev, "Missing management connection\n");
+               goto destroy_connections;
+       }
+
+       /* Initialize management connection */
+       ret = gb_connection_enable(gbmodule->mgmt_connection);
+       if (ret) {
+               dev_err(dev, "%d: Error while enabling mgmt connection\n", ret);
+               goto destroy_connections;
+       }
+       gbmodule->dev_id = gbmodule->mgmt_connection->intf->interface_id;
+
+       /*
+        * FIXME: malloc for topology happens via audio_gb driver
+        * should be done within codec driver itself
+        */
+       ret = gb_audio_gb_get_topology(gbmodule->mgmt_connection, &topology);
+       if (ret) {
+               dev_err(dev, "%d:Error while fetching topology\n", ret);
+               goto disable_connection;
+       }
+
+       /* process topology data */
+       ret = gbaudio_tplg_parse_data(gbmodule, topology);
+       if (ret) {
+               dev_err(dev, "%d:Error while parsing topology data\n",
+                         ret);
+               goto free_topology;
+       }
+       gbmodule->topology = topology;
+
+       /* Initialize data connections */
+       list_for_each_entry(dai, &gbmodule->data_list, list) {
+               ret = gb_connection_enable(dai->connection);
+               if (ret) {
+                       dev_err(dev,
+                               "%d:Error while enabling %d:data connection\n",
+                               ret, dai->data_cport);
+                       goto disable_data_connection;
+               }
+       }
+
+       /* register module with gbcodec */
+       ret = gbaudio_register_module(gbmodule);
+       if (ret)
+               goto disable_data_connection;
+
+       /* 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, gbmodule->name, GB_AUDIO_MANAGER_MODULE_NAME_LEN);
+       desc.slot = 1; /* todo */
+       desc.vid = 2; /* todo */
+       desc.pid = 3; /* todo */
+       desc.cport = gbmodule->dev_id;
+       desc.op_devices = gbmodule->op_devices;
+       desc.ip_devices = gbmodule->ip_devices;
+       gbmodule->manager_id = gb_audio_manager_add(&desc);
+
+       dev_dbg(dev, "Add GB Audio device:%s\n", gbmodule->name);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       return 0;
+
+disable_data_connection:
+       list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list)
+               gb_connection_disable(dai->connection);
+       gbaudio_tplg_release(gbmodule);
+       gbmodule->topology = NULL;
+
+free_topology:
+       kfree(topology);
+
+disable_connection:
+       gb_connection_disable(gbmodule->mgmt_connection);
+
+destroy_connections:
+       list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) {
+               gb_connection_destroy(dai->connection);
+               list_del(&dai->list);
+               devm_kfree(dev, dai);
+       }
+
+       if (gbmodule->mgmt_connection)
+               gb_connection_destroy(gbmodule->mgmt_connection);
+
+       devm_kfree(dev, gbmodule);
+
+       return ret;
+}
+
+static void gb_audio_disconnect(struct gb_bundle *bundle)
+{
+       struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+       struct gbaudio_data_connection *dai, *_dai;
+
+       gb_pm_runtime_get_sync(bundle);
+
+       /* cleanup module related resources first */
+       gbaudio_unregister_module(gbmodule);
+
+       /* inform uevent to above layers */
+       gb_audio_manager_remove(gbmodule->manager_id);
+
+       gbaudio_tplg_release(gbmodule);
+       kfree(gbmodule->topology);
+       gbmodule->topology = NULL;
+       gb_connection_disable(gbmodule->mgmt_connection);
+       list_for_each_entry_safe(dai, _dai, &gbmodule->data_list, list) {
+               gb_connection_disable(dai->connection);
+               gb_connection_destroy(dai->connection);
+               list_del(&dai->list);
+               devm_kfree(gbmodule->dev, dai);
+       }
+       gb_connection_destroy(gbmodule->mgmt_connection);
+       gbmodule->mgmt_connection = NULL;
+
+       devm_kfree(&bundle->dev, gbmodule);
+}
+
+static const struct greybus_bundle_id gb_audio_id_table[] = {
+       { GREYBUS_DEVICE_CLASS(GREYBUS_CLASS_AUDIO) },
+       { }
+};
+MODULE_DEVICE_TABLE(greybus, gb_audio_id_table);
+
+#ifdef CONFIG_PM
+static int gb_audio_suspend(struct device *dev)
+{
+       struct gb_bundle *bundle = to_gb_bundle(dev);
+       struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+       struct gbaudio_data_connection *dai;
+
+       list_for_each_entry(dai, &gbmodule->data_list, list)
+               gb_connection_disable(dai->connection);
+
+       gb_connection_disable(gbmodule->mgmt_connection);
+
+       return 0;
+}
+
+static int gb_audio_resume(struct device *dev)
+{
+       struct gb_bundle *bundle = to_gb_bundle(dev);
+       struct gbaudio_module_info *gbmodule = greybus_get_drvdata(bundle);
+       struct gbaudio_data_connection *dai;
+       int ret;
+
+       ret = gb_connection_enable(gbmodule->mgmt_connection);
+       if (ret) {
+               dev_err(dev, "%d:Error while enabling mgmt connection\n", ret);
+               return ret;
+       }
+
+       list_for_each_entry(dai, &gbmodule->data_list, list) {
+               ret = gb_connection_enable(dai->connection);
+               if (ret) {
+                       dev_err(dev,
+                               "%d:Error while enabling %d:data connection\n",
+                               ret, dai->data_cport);
+                       return ret;
+               }
+       }
+
+       return 0;
+}
+#endif
+
+static const struct dev_pm_ops gb_audio_pm_ops = {
+       SET_RUNTIME_PM_OPS(gb_audio_suspend, gb_audio_resume, NULL)
+};
+
+static struct greybus_driver gb_audio_driver = {
+       .name           = "gb-audio",
+       .probe          = gb_audio_probe,
+       .disconnect     = gb_audio_disconnect,
+       .id_table       = gb_audio_id_table,
+       .driver.pm      = &gb_audio_pm_ops,
+};
+module_greybus_driver(gb_audio_driver);
+
+MODULE_DESCRIPTION("Greybus Audio module driver");
+MODULE_AUTHOR("Vaibhav Agarwal <vaibhav.agar...@linaro.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:gbaudio-module");
--- /dev/null
+++ b/drivers/greybus/audio_topology.c
@@ -0,0 +1,1442 @@
+/*
+ * Greybus audio driver
+ * Copyright 2015-2016 Google Inc.
+ * Copyright 2015-2016 Linaro Ltd.
+ *
+ * Released under the GPLv2 only.
+ */
+
+#include "audio_codec.h"
+#include "greybus_protocols.h"
+
+#define GBAUDIO_INVALID_ID     0xFF
+
+/* mixer control */
+struct gb_mixer_control {
+       int min, max;
+       unsigned int reg, rreg, shift, rshift, invert;
+};
+
+struct gbaudio_ctl_pvt {
+       unsigned int ctl_id;
+       unsigned int data_cport;
+       unsigned int access;
+       unsigned int vcount;
+       struct gb_audio_ctl_elem_info *info;
+};
+
+static struct gbaudio_module_info *find_gb_module(
+                                       struct gbaudio_codec_info *codec,
+                                       char const *name)
+{
+       int dev_id, ret;
+       char begin[NAME_SIZE];
+       struct gbaudio_module_info *module;
+
+       if (!name)
+               return NULL;
+
+       ret = sscanf(name, "%s %d", begin, &dev_id);
+       dev_dbg(codec->dev, "%s:Find module#%d\n", __func__, dev_id);
+
+       mutex_lock(&codec->lock);
+       list_for_each_entry(module, &codec->module_list, list) {
+               if (module->dev_id == dev_id) {
+                       mutex_unlock(&codec->lock);
+                       return module;
+               }
+       }
+       mutex_unlock(&codec->lock);
+       dev_warn(codec->dev, "%s: module#%d missing in codec list\n", name,
+                dev_id);
+       return NULL;
+}
+
+static const char *gbaudio_map_controlid(struct gbaudio_module_info *module,
+                                        __u8 control_id, __u8 index)
+{
+       struct gbaudio_control *control;
+
+       if (control_id == GBAUDIO_INVALID_ID)
+               return NULL;
+
+       list_for_each_entry(control, &module->ctl_list, list) {
+               if (control->id == control_id) {
+                       if (index == GBAUDIO_INVALID_ID)
+                               return control->name;
+                       if (index >= control->items)
+                               return NULL;
+                       return control->texts[index];
+               }
+       }
+       list_for_each_entry(control, &module->widget_ctl_list, list) {
+               if (control->id == control_id) {
+                       if (index == GBAUDIO_INVALID_ID)
+                               return control->name;
+                       if (index >= control->items)
+                               return NULL;
+                       return control->texts[index];
+               }
+       }
+       return NULL;
+}
+
+static int gbaudio_map_controlname(struct gbaudio_module_info *module,
+                                  const char *name)
+{
+       struct gbaudio_control *control;
+
+       list_for_each_entry(control, &module->ctl_list, list) {
+               if (!strncmp(control->name, name, NAME_SIZE))
+                       return control->id;
+       }
+
+       dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+       return -EINVAL;
+}
+
+static int gbaudio_map_wcontrolname(struct gbaudio_module_info *module,
+                                   const char *name)
+{
+       struct gbaudio_control *control;
+
+       list_for_each_entry(control, &module->widget_ctl_list, list) {
+               if (!strncmp(control->wname, name, NAME_SIZE))
+                       return control->id;
+       }
+       dev_warn(module->dev, "%s: missing in modules controls list\n", name);
+
+       return -EINVAL;
+}
+
+static int gbaudio_map_widgetname(struct gbaudio_module_info *module,
+                                 const char *name)
+{
+       struct gbaudio_widget *widget;
+       list_for_each_entry(widget, &module->widget_list, list) {
+               if (!strncmp(widget->name, name, NAME_SIZE))
+                       return widget->id;
+       }
+       dev_warn(module->dev, "%s: missing in modules widgets list\n", name);
+
+       return -EINVAL;
+}
+
+static const char *gbaudio_map_widgetid(struct gbaudio_module_info *module,
+                                       __u8 widget_id)
+{
+       struct gbaudio_widget *widget;
+
+       list_for_each_entry(widget, &module->widget_list, list) {
+               if (widget->id == widget_id)
+                       return widget->name;
+       }
+       return NULL;
+}
+
+static const char **gb_generate_enum_strings(struct gbaudio_module_info *gb,
+                                            struct gb_audio_enumerated *gbenum)
+{
+       const char **strings;
+       int i;
+       __u8 *data;
+
+       strings = devm_kzalloc(gb->dev, sizeof(char *) * gbenum->items,
+                              GFP_KERNEL);
+       data = gbenum->names;
+
+       for (i = 0; i < gbenum->items; i++) {
+               strings[i] = (const char *)data;
+               while (*data != '\0')
+                       data++;
+               data++;
+       }
+
+       return strings;
+}
+
+static int gbcodec_mixer_ctl_info(struct snd_kcontrol *kcontrol,
+                    struct snd_ctl_elem_info *uinfo)
+{
+       unsigned int max;
+       const char *name;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_info *info;
+       struct gbaudio_module_info *module;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+
+       if (!info) {
+               dev_err(module->dev, "NULL info for %s\n", uinfo->id.name);
+               return -EINVAL;
+       }
+
+       /* update uinfo */
+       uinfo->access = data->access;
+       uinfo->count = data->vcount;
+       uinfo->type = (snd_ctl_elem_type_t)info->type;
+
+       switch (info->type) {
+       case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+       case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+               uinfo->value.integer.min = info->value.integer.min;
+               uinfo->value.integer.max = info->value.integer.max;
+               break;
+       case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+               max = info->value.enumerated.items;
+               uinfo->value.enumerated.items = max;
+               if (uinfo->value.enumerated.item > max - 1)
+                       uinfo->value.enumerated.item = max - 1;
+               module = find_gb_module(gbcodec, kcontrol->id.name);
+               if (!module)
+                       return -EINVAL;
+               name = gbaudio_map_controlid(module, data->ctl_id,
+                                            uinfo->value.enumerated.item);
+               strlcpy(uinfo->value.enumerated.name, name, NAME_SIZE);
+               break;
+       default:
+               dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+                       info->type, kcontrol->id.name);
+               break;
+       }
+       return 0;
+}
+
+static int gbcodec_mixer_ctl_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       int ret;
+       struct gb_audio_ctl_elem_info *info;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+               return ret;
+       }
+
+       /* update ucontrol */
+       switch (info->type) {
+       case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+       case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+               ucontrol->value.integer.value[0] =
+                       gbvalue.value.integer_value[0];
+               if (data->vcount == 2)
+                       ucontrol->value.integer.value[1] =
+                               gbvalue.value.integer_value[1];
+               break;
+       case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+               ucontrol->value.enumerated.item[0] =
+                       gbvalue.value.enumerated_item[0];
+               if (data->vcount == 2)
+                       ucontrol->value.enumerated.item[1] =
+                               gbvalue.value.enumerated_item[1];
+               break;
+       default:
+               dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+                       info->type, kcontrol->id.name);
+               ret = -EINVAL;
+               break;
+       }
+       return ret;
+}
+
+static int gbcodec_mixer_ctl_put(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       int ret = 0;
+       struct gb_audio_ctl_elem_info *info;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+       bundle = to_gb_bundle(module->dev);
+
+       /* update ucontrol */
+       switch (info->type) {
+       case GB_AUDIO_CTL_ELEM_TYPE_BOOLEAN:
+       case GB_AUDIO_CTL_ELEM_TYPE_INTEGER:
+               gbvalue.value.integer_value[0] =
+                       ucontrol->value.integer.value[0];
+               if (data->vcount == 2)
+                       gbvalue.value.integer_value[1] =
+                               ucontrol->value.integer.value[1];
+               break;
+       case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+               gbvalue.value.enumerated_item[0] =
+                       ucontrol->value.enumerated.item[0];
+               if (data->vcount == 2)
+                       gbvalue.value.enumerated_item[1] =
+                               ucontrol->value.enumerated.item[1];
+               break;
+       default:
+               dev_err(codec->dev, "Invalid type: %d for %s:kcontrol\n",
+                       info->type, kcontrol->id.name);
+               ret = -EINVAL;
+               break;
+       }
+
+       if (ret)
+               return ret;
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_set_control(module->mgmt_connection, data->ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+       }
+
+       return ret;
+}
+
+#define SOC_MIXER_GB(xname, kcount, data) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .count = kcount, .info = gbcodec_mixer_ctl_info, \
+       .get = gbcodec_mixer_ctl_get, .put = gbcodec_mixer_ctl_put, \
+       .private_value = (unsigned long)data }
+
+/*
+ * although below callback functions seems redundant to above functions.
+ * same are kept to allow provision for different handling in case
+ * of DAPM related sequencing, etc.
+ */
+static int gbcodec_mixer_dapm_ctl_info(struct snd_kcontrol *kcontrol,
+                    struct snd_ctl_elem_info *uinfo)
+{
+       int platform_max, platform_min;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_info *info;
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct snd_soc_codec *codec = widget->codec;
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+
+       /* update uinfo */
+       platform_max = info->value.integer.max;
+       platform_min = info->value.integer.min;
+
+       if (platform_max == 1 &&
+           !strnstr(kcontrol->id.name, " Volume", NAME_SIZE))
+               uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+       else
+               uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+
+       uinfo->count = data->vcount;
+       uinfo->value.integer.min = 0;
+       if (info->value.integer.min < 0 &&
+           (uinfo->type == SNDRV_CTL_ELEM_TYPE_INTEGER))
+               uinfo->value.integer.max = platform_max - platform_min;
+       else
+               uinfo->value.integer.max = platform_max;
+
+       return 0;
+}
+
+static int gbcodec_mixer_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+       struct snd_ctl_elem_value *ucontrol)
+{
+       int ret;
+       struct gb_audio_ctl_elem_info *info;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct snd_soc_codec *codec = widget->codec;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+       bundle = to_gb_bundle(module->dev);
+
+       if (data->vcount == 2)
+               dev_warn(widget->dapm->dev,
+                        "GB: Control '%s' is stereo, which is not supported\n",
+                        kcontrol->id.name);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_get_control(module->mgmt_connection, data->ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+               return ret;
+       }
+       /* update ucontrol */
+       ucontrol->value.integer.value[0] = gbvalue.value.integer_value[0];
+
+       return ret;
+}
+
+static int gbcodec_mixer_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       int ret, wi, max, connect;
+       unsigned int mask, val;
+       struct gb_audio_ctl_elem_info *info;
+       struct gbaudio_ctl_pvt *data;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct snd_soc_codec *codec = widget->codec;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       dev_dbg(codec->dev, "Entered %s:%s\n", __func__, kcontrol->id.name);
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       data = (struct gbaudio_ctl_pvt *)kcontrol->private_value;
+       info = (struct gb_audio_ctl_elem_info *)data->info;
+       bundle = to_gb_bundle(module->dev);
+
+       if (data->vcount == 2)
+               dev_warn(widget->dapm->dev,
+                        "GB: Control '%s' is stereo, which is not supported\n",
+                        kcontrol->id.name);
+
+       max = info->value.integer.max;
+       mask = (1 << fls(max)) - 1;
+       val = (ucontrol->value.integer.value[0] & mask);
+       connect = !!val;
+
+       /* update ucontrol */
+       if (gbvalue.value.integer_value[0] != val) {
+               for (wi = 0; wi < wlist->num_widgets; wi++) {
+                       widget = wlist->widgets[wi];
+
+                       widget->value = val;
+                       widget->dapm->update = NULL;
+                       snd_soc_dapm_mixer_update_power(widget, kcontrol,
+                                                       connect);
+               }
+               gbvalue.value.integer_value[0] =
+                       ucontrol->value.integer.value[0];
+
+               ret = gb_pm_runtime_get_sync(bundle);
+               if (ret)
+                       return ret;
+
+               ret = gb_audio_gb_set_control(module->mgmt_connection,
+                                             data->ctl_id,
+                                             GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+               gb_pm_runtime_put_autosuspend(bundle);
+
+               if (ret) {
+                       dev_err_ratelimited(codec->dev,
+                                           "%d:Error in %s for %s\n", ret,
+                                           __func__, kcontrol->id.name);
+               }
+       }
+
+       return ret;
+}
+
+#define SOC_DAPM_MIXER_GB(xname, kcount, data) \
+{      .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+       .count = kcount, .info = gbcodec_mixer_dapm_ctl_info, \
+       .get = gbcodec_mixer_dapm_ctl_get, .put = gbcodec_mixer_dapm_ctl_put, \
+       .private_value = (unsigned long)data}
+
+static int gbcodec_event_spk(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB speaker is connected */
+
+       return 0;
+}
+
+static int gbcodec_event_hp(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB module supports jack slot */
+
+       return 0;
+}
+
+static int gbcodec_event_int_mic(struct snd_soc_dapm_widget *w,
+                                       struct snd_kcontrol *k, int event)
+{
+       /* Ensure GB module supports jack slot */
+
+       return 0;
+}
+
+static int gbaudio_validate_kcontrol_count(struct gb_audio_widget *w)
+{
+       int ret = 0;
+
+       switch (w->type) {
+       case snd_soc_dapm_spk:
+       case snd_soc_dapm_hp:
+       case snd_soc_dapm_mic:
+       case snd_soc_dapm_output:
+       case snd_soc_dapm_input:
+               if (w->ncontrols)
+                       ret = -EINVAL;
+               break;
+       case snd_soc_dapm_switch:
+       case snd_soc_dapm_mux:
+               if (w->ncontrols != 1)
+                       ret = -EINVAL;
+               break;
+       default:
+               break;
+       }
+
+       return ret;
+}
+
+static int gbcodec_enum_ctl_get(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       int ret, ctl_id;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+       if (ctl_id < 0)
+               return -EINVAL;
+
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+               return ret;
+       }
+
+       ucontrol->value.enumerated.item[0] = gbvalue.value.enumerated_item[0];
+       if (e->shift_l != e->shift_r)
+               ucontrol->value.enumerated.item[1] =
+                       gbvalue.value.enumerated_item[1];
+
+       return 0;
+}
+
+static int gbcodec_enum_ctl_put(struct snd_kcontrol *kcontrol,
+                               struct snd_ctl_elem_value *ucontrol)
+{
+       int ret, ctl_id;
+       struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct gb_bundle *bundle;
+
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       ctl_id = gbaudio_map_controlname(module, kcontrol->id.name);
+       if (ctl_id < 0)
+               return -EINVAL;
+
+       if (ucontrol->value.enumerated.item[0] > e->max - 1)
+               return -EINVAL;
+       gbvalue.value.enumerated_item[0] = ucontrol->value.enumerated.item[0];
+
+       if (e->shift_l != e->shift_r) {
+               if (ucontrol->value.enumerated.item[1] > e->max - 1)
+                       return -EINVAL;
+               gbvalue.value.enumerated_item[1] =
+                       ucontrol->value.enumerated.item[1];
+       }
+
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+       }
+
+       return ret;
+}
+
+static int gbaudio_tplg_create_enum_kctl(struct gbaudio_module_info *gb,
+                                        struct snd_kcontrol_new *kctl,
+                                        struct gb_audio_control *ctl)
+{
+       struct soc_enum *gbe;
+       struct gb_audio_enumerated *gb_enum;
+       int i;
+
+       gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+       if (!gbe)
+               return -ENOMEM;
+
+       gb_enum = &ctl->info.value.enumerated;
+
+       /* since count=1, and reg is dummy */
+       gbe->max = gb_enum->items;
+       gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+
+       /* debug enum info */
+       dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
+                gb_enum->names_length);
+       for (i = 0; i < gb_enum->items; i++)
+               dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+       *kctl = (struct snd_kcontrol_new)
+               SOC_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_ctl_get,
+                            gbcodec_enum_ctl_put);
+       return 0;
+}
+
+static int gbaudio_tplg_create_kcontrol(struct gbaudio_module_info *gb,
+                                       struct snd_kcontrol_new *kctl,
+                                       struct gb_audio_control *ctl)
+{
+       int ret = 0;
+       struct gbaudio_ctl_pvt *ctldata;
+
+       switch (ctl->iface) {
+       case SNDRV_CTL_ELEM_IFACE_MIXER:
+               switch (ctl->info.type) {
+               case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+                       ret = gbaudio_tplg_create_enum_kctl(gb, kctl, ctl);
+                       break;
+               default:
+                       ctldata = devm_kzalloc(gb->dev,
+                                              sizeof(struct gbaudio_ctl_pvt),
+                                              GFP_KERNEL);
+                       if (!ctldata)
+                               return -ENOMEM;
+                       ctldata->ctl_id = ctl->id;
+                       ctldata->data_cport = ctl->data_cport;
+                       ctldata->access = ctl->access;
+                       ctldata->vcount = ctl->count_values;
+                       ctldata->info = &ctl->info;
+                       *kctl = (struct snd_kcontrol_new)
+                               SOC_MIXER_GB(ctl->name, ctl->count, ctldata);
+                       ctldata = NULL;
+                       break;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       dev_dbg(gb->dev, "%s:%d control created\n", ctl->name, ctl->id);
+       return ret;
+}
+
+static int gbcodec_enum_dapm_ctl_get(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       int ret, ctl_id;
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct gbaudio_module_info *module;
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct snd_soc_codec *codec = widget->codec;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       struct gb_bundle *bundle;
+
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+       if (ctl_id < 0)
+               return -EINVAL;
+
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+               return ret;
+       }
+
+       ucontrol->value.enumerated.item[0] = gbvalue.value.enumerated_item[0];
+       if (e->shift_l != e->shift_r)
+               ucontrol->value.enumerated.item[1] =
+                       gbvalue.value.enumerated_item[1];
+
+       return 0;
+}
+
+static int gbcodec_enum_dapm_ctl_put(struct snd_kcontrol *kcontrol,
+                                    struct snd_ctl_elem_value *ucontrol)
+{
+       int ret, wi, ctl_id;
+       unsigned int val, mux, change;
+       unsigned int mask;
+       struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol);
+       struct snd_soc_dapm_widget *widget = wlist->widgets[0];
+       struct gb_audio_ctl_elem_value gbvalue;
+       struct gbaudio_module_info *module;
+       struct snd_soc_codec *codec = widget->codec;
+       struct gbaudio_codec_info *gb = snd_soc_codec_get_drvdata(codec);
+       struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+       struct gb_bundle *bundle;
+
+       if (ucontrol->value.enumerated.item[0] > e->max - 1)
+               return -EINVAL;
+
+       module = find_gb_module(gb, kcontrol->id.name);
+       if (!module)
+               return -EINVAL;
+
+       ctl_id = gbaudio_map_wcontrolname(module, kcontrol->id.name);
+       if (ctl_id < 0)
+               return -EINVAL;
+
+       change = 0;
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       ret = gb_audio_gb_get_control(module->mgmt_connection, ctl_id,
+                                     GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       if (ret) {
+               dev_err_ratelimited(codec->dev, "%d:Error in %s for %s\n", ret,
+                                   __func__, kcontrol->id.name);
+               return ret;
+       }
+
+       mux = ucontrol->value.enumerated.item[0];
+       val = mux << e->shift_l;
+       mask = e->mask << e->shift_l;
+
+       if (gbvalue.value.enumerated_item[0] !=
+           ucontrol->value.enumerated.item[0]) {
+               change = 1;
+               gbvalue.value.enumerated_item[0] =
+                       ucontrol->value.enumerated.item[0];
+       }
+
+       if (e->shift_l != e->shift_r) {
+               if (ucontrol->value.enumerated.item[1] > e->max - 1)
+                       return -EINVAL;
+               val |= ucontrol->value.enumerated.item[1] << e->shift_r;
+               mask |= e->mask << e->shift_r;
+               if (gbvalue.value.enumerated_item[1] !=
+                   ucontrol->value.enumerated.item[1]) {
+                       change = 1;
+                       gbvalue.value.enumerated_item[1] =
+                               ucontrol->value.enumerated.item[1];
+               }
+       }
+
+       if (change) {
+               ret = gb_pm_runtime_get_sync(bundle);
+               if (ret)
+                       return ret;
+
+               ret = gb_audio_gb_set_control(module->mgmt_connection, ctl_id,
+                                             GB_AUDIO_INVALID_INDEX, &gbvalue);
+
+               gb_pm_runtime_put_autosuspend(bundle);
+
+               if (ret) {
+                       dev_err_ratelimited(codec->dev,
+                                           "%d:Error in %s for %s\n", ret,
+                                           __func__, kcontrol->id.name);
+               }
+               for (wi = 0; wi < wlist->num_widgets; wi++) {
+                       widget = wlist->widgets[wi];
+
+                       widget->value = val;
+                       widget->dapm->update = NULL;
+                       snd_soc_dapm_mux_update_power(widget, kcontrol, mux, e);
+               }
+       }
+
+       return change;
+}
+
+static int gbaudio_tplg_create_enum_ctl(struct gbaudio_module_info *gb,
+                                       struct snd_kcontrol_new *kctl,
+                                       struct gb_audio_control *ctl)
+{
+       struct soc_enum *gbe;
+       struct gb_audio_enumerated *gb_enum;
+       int i;
+
+       gbe = devm_kzalloc(gb->dev, sizeof(*gbe), GFP_KERNEL);
+       if (!gbe)
+               return -ENOMEM;
+
+       gb_enum = &ctl->info.value.enumerated;
+
+       /* since count=1, and reg is dummy */
+       gbe->max = gb_enum->items;
+       gbe->texts = gb_generate_enum_strings(gb, gb_enum);
+
+       /* debug enum info */
+       dev_dbg(gb->dev, "Max:%d, name_length:%d\n", gb_enum->items,
+                gb_enum->names_length);
+       for (i = 0; i < gb_enum->items; i++)
+               dev_dbg(gb->dev, "src[%d]: %s\n", i, gbe->texts[i]);
+
+       *kctl = (struct snd_kcontrol_new)
+               SOC_DAPM_ENUM_EXT(ctl->name, *gbe, gbcodec_enum_dapm_ctl_get,
+                                 gbcodec_enum_dapm_ctl_put);
+       return 0;
+}
+
+static int gbaudio_tplg_create_mixer_ctl(struct gbaudio_module_info *gb,
+                                            struct snd_kcontrol_new *kctl,
+                                            struct gb_audio_control *ctl)
+{
+       struct gbaudio_ctl_pvt *ctldata;
+
+       ctldata = devm_kzalloc(gb->dev, sizeof(struct gbaudio_ctl_pvt),
+                              GFP_KERNEL);
+       if (!ctldata)
+               return -ENOMEM;
+       ctldata->ctl_id = ctl->id;
+       ctldata->data_cport = ctl->data_cport;
+       ctldata->access = ctl->access;
+       ctldata->vcount = ctl->count_values;
+       ctldata->info = &ctl->info;
+       *kctl = (struct snd_kcontrol_new)
+               SOC_DAPM_MIXER_GB(ctl->name, ctl->count, ctldata);
+
+       return 0;
+}
+
+static int gbaudio_tplg_create_wcontrol(struct gbaudio_module_info *gb,
+                                            struct snd_kcontrol_new *kctl,
+                                            struct gb_audio_control *ctl)
+{
+       int ret;
+
+       switch (ctl->iface) {
+       case SNDRV_CTL_ELEM_IFACE_MIXER:
+               switch (ctl->info.type) {
+               case GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED:
+                       ret = gbaudio_tplg_create_enum_ctl(gb, kctl, ctl);
+                       break;
+               default:
+                       ret = gbaudio_tplg_create_mixer_ctl(gb, kctl, ctl);
+                       break;
+               }
+               break;
+       default:
+               return -EINVAL;
+
+       }
+
+       dev_dbg(gb->dev, "%s:%d DAPM control created, ret:%d\n", ctl->name,
+               ctl->id, ret);
+       return ret;
+}
+
+static int gbaudio_widget_event(struct snd_soc_dapm_widget *w,
+                               struct snd_kcontrol *kcontrol, int event)
+{
+       int wid;
+       int ret;
+       struct snd_soc_codec *codec = w->codec;
+       struct gbaudio_codec_info *gbcodec = snd_soc_codec_get_drvdata(codec);
+       struct gbaudio_module_info *module;
+       struct gb_bundle *bundle;
+
+       dev_dbg(codec->dev, "%s %s %d\n", __func__, w->name, event);
+
+       /* Find relevant module */
+       module = find_gb_module(gbcodec, w->name);
+       if (!module)
+               return -EINVAL;
+
+       /* map name to widget id */
+       wid = gbaudio_map_widgetname(module, w->name);
+       if (wid < 0) {
+               dev_err(codec->dev, "Invalid widget name:%s\n", w->name);
+               return -EINVAL;
+       }
+
+       bundle = to_gb_bundle(module->dev);
+
+       ret = gb_pm_runtime_get_sync(bundle);
+       if (ret)
+               return ret;
+
+       switch (event) {
+       case SND_SOC_DAPM_PRE_PMU:
+               ret = gb_audio_gb_enable_widget(module->mgmt_connection, wid);
+               if (!ret)
+                       ret = gbaudio_module_update(gbcodec, w, module, 1);
+               break;
+       case SND_SOC_DAPM_POST_PMD:
+               ret = gb_audio_gb_disable_widget(module->mgmt_connection, wid);
+               if (!ret)
+                       ret = gbaudio_module_update(gbcodec, w, module, 0);
+               break;
+       }
+       if (ret)
+               dev_err_ratelimited(codec->dev,
+                                   "%d: widget, event:%d failed:%d\n", wid,
+                                   event, ret);
+
+       gb_pm_runtime_put_autosuspend(bundle);
+
+       return ret;
+}
+
+static int gbaudio_tplg_create_widget(struct gbaudio_module_info *module,
+                                     struct snd_soc_dapm_widget *dw,
+                                     struct gb_audio_widget *w, int *w_size)
+{
+       int i, ret, csize;
+       struct snd_kcontrol_new *widget_kctls;
+       struct gb_audio_control *curr;
+       struct gbaudio_control *control, *_control;
+       size_t size;
+       char temp_name[NAME_SIZE];
+
+       ret = gbaudio_validate_kcontrol_count(w);
+       if (ret) {
+               dev_err(module->dev, "Inavlid kcontrol count=%d for %s\n",
+                       w->ncontrols, w->name);
+               return ret;
+       }
+
+       /* allocate memory for kcontrol */
+       if (w->ncontrols) {
+               size = sizeof(struct snd_kcontrol_new) * w->ncontrols;
+               widget_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+               if (!widget_kctls)
+                       return -ENOMEM;
+       }
+
+       *w_size = sizeof(struct gb_audio_widget);
+
+       /* create relevant kcontrols */
+       curr = w->ctl;
+       for (i = 0; i < w->ncontrols; i++) {
+               ret = gbaudio_tplg_create_wcontrol(module, &widget_kctls[i],
+                                                  curr);
+               if (ret) {
+                       dev_err(module->dev,
+                               "%s:%d type widget_ctl not supported\n",
+                               curr->name, curr->iface);
+                       goto error;
+               }
+               control = devm_kzalloc(module->dev,
+                                      sizeof(struct gbaudio_control),
+                                      GFP_KERNEL);
+               if (!control) {
+                       ret = -ENOMEM;
+                       goto error;
+               }
+               control->id = curr->id;
+               control->name = curr->name;
+               control->wname = w->name;
+
+               if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+                       struct gb_audio_enumerated *gbenum =
+                               &curr->info.value.enumerated;
+
+                       csize = offsetof(struct gb_audio_control, info);
+                       csize += offsetof(struct gb_audio_ctl_elem_info, value);
+                       csize += offsetof(struct gb_audio_enumerated, names);
+                       csize += gbenum->names_length;
+                       control->texts = (const char * const *)
+                               gb_generate_enum_strings(module, gbenum);
+                       control->items = gbenum->items;
+               } else
+                       csize = sizeof(struct gb_audio_control);
+               *w_size += csize;
+               curr = (void *)curr + csize;
+               list_add(&control->list, &module->widget_ctl_list);
+               dev_dbg(module->dev, "%s: control of type %d created\n",
+                       widget_kctls[i].name, widget_kctls[i].iface);
+       }
+
+       /* Prefix dev_id to widget control_name */
+       strlcpy(temp_name, w->name, NAME_SIZE);
+       snprintf(w->name, NAME_SIZE, "GB %d %s", module->dev_id, temp_name);
+
+       switch (w->type) {
+       case snd_soc_dapm_spk:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_SPK(w->name, gbcodec_event_spk);
+               module->op_devices |= GBAUDIO_DEVICE_OUT_SPEAKER;
+               break;
+       case snd_soc_dapm_hp:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_HP(w->name, gbcodec_event_hp);
+               module->op_devices |= (GBAUDIO_DEVICE_OUT_WIRED_HEADSET
+                                       | GBAUDIO_DEVICE_OUT_WIRED_HEADPHONE);
+               module->ip_devices |= GBAUDIO_DEVICE_IN_WIRED_HEADSET;
+               break;
+       case snd_soc_dapm_mic:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_MIC(w->name, gbcodec_event_int_mic);
+               module->ip_devices |= GBAUDIO_DEVICE_IN_BUILTIN_MIC;
+               break;
+       case snd_soc_dapm_output:
+               *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_OUTPUT(w->name);
+               break;
+       case snd_soc_dapm_input:
+               *dw = (struct snd_soc_dapm_widget)SND_SOC_DAPM_INPUT(w->name);
+               break;
+       case snd_soc_dapm_switch:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_SWITCH_E(w->name, SND_SOC_NOPM, 0, 0,
+                                           widget_kctls, gbaudio_widget_event,
+                                           SND_SOC_DAPM_PRE_PMU |
+                                           SND_SOC_DAPM_POST_PMD);
+               break;
+       case snd_soc_dapm_pga:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_PGA_E(w->name, SND_SOC_NOPM, 0, 0, NULL, 0,
+                                          gbaudio_widget_event,
+                                          SND_SOC_DAPM_PRE_PMU |
+                                          SND_SOC_DAPM_POST_PMD);
+               break;
+       case snd_soc_dapm_mixer:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_MIXER_E(w->name, SND_SOC_NOPM, 0, 0, NULL,
+                                          0, gbaudio_widget_event,
+                                          SND_SOC_DAPM_PRE_PMU |
+                                          SND_SOC_DAPM_POST_PMD);
+               break;
+       case snd_soc_dapm_mux:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_MUX_E(w->name, SND_SOC_NOPM, 0, 0,
+                                        widget_kctls, gbaudio_widget_event,
+                                        SND_SOC_DAPM_PRE_PMU |
+                                        SND_SOC_DAPM_POST_PMD);
+               break;
+       case snd_soc_dapm_aif_in:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_AIF_IN_E(w->name, w->sname, 0,
+                                             SND_SOC_NOPM,
+                                             0, 0, gbaudio_widget_event,
+                                             SND_SOC_DAPM_PRE_PMU |
+                                             SND_SOC_DAPM_POST_PMD);
+               break;
+       case snd_soc_dapm_aif_out:
+               *dw = (struct snd_soc_dapm_widget)
+                       SND_SOC_DAPM_AIF_OUT_E(w->name, w->sname, 0,
+                                              SND_SOC_NOPM,
+                                              0, 0, gbaudio_widget_event,
+                                              SND_SOC_DAPM_PRE_PMU |
+                                              SND_SOC_DAPM_POST_PMD);
+               break;
+       default:
+               ret = -EINVAL;
+               goto error;
+       }
+
+       dev_dbg(module->dev, "%s: widget of type %d created\n", dw->name,
+               dw->id);
+       return 0;
+error:
+       list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+                                list) {
+               list_del(&control->list);
+               devm_kfree(module->dev, control);
+       }
+       return ret;
+}
+
+static int gbaudio_tplg_process_kcontrols(struct gbaudio_module_info *module,
+                                  struct gb_audio_control *controls)
+{
+       int i, csize, ret;
+       struct snd_kcontrol_new *dapm_kctls;
+       struct gb_audio_control *curr;
+       struct gbaudio_control *control, *_control;
+       size_t size;
+       char temp_name[NAME_SIZE];
+
+       size = sizeof(struct snd_kcontrol_new) * module->num_controls;
+       dapm_kctls = devm_kzalloc(module->dev, size, GFP_KERNEL);
+       if (!dapm_kctls)
+               return -ENOMEM;
+
+       curr = controls;
+       for (i = 0; i < module->num_controls; i++) {
+               ret = gbaudio_tplg_create_kcontrol(module, &dapm_kctls[i],
+                                                  curr);
+               if (ret) {
+                       dev_err(module->dev, "%s:%d type not supported\n",
+                               curr->name, curr->iface);
+                       goto error;
+               }
+               control = devm_kzalloc(module->dev, sizeof(struct
+                                                          gbaudio_control),
+                                     GFP_KERNEL);
+               if (!control) {
+                       ret = -ENOMEM;
+                       goto error;
+               }
+               control->id = curr->id;
+               /* Prefix dev_id to widget_name */
+               strlcpy(temp_name, curr->name, NAME_SIZE);
+               snprintf(curr->name, NAME_SIZE, "GB %d %s", module->dev_id,
+                        temp_name);
+               control->name = curr->name;
+               if (curr->info.type == GB_AUDIO_CTL_ELEM_TYPE_ENUMERATED) {
+                       struct gb_audio_enumerated *gbenum =
+                               &curr->info.value.enumerated;
+
+                       csize = offsetof(struct gb_audio_control, info);
+                       csize += offsetof(struct gb_audio_ctl_elem_info, value);
+                       csize += offsetof(struct gb_audio_enumerated, names);
+                       csize += gbenum->names_length;
+                       control->texts = (const char * const *)
+                               gb_generate_enum_strings(module, gbenum);
+                       control->items = gbenum->items;
+               } else
+                       csize = sizeof(struct gb_audio_control);
+
+               list_add(&control->list, &module->ctl_list);
+               dev_dbg(module->dev, "%d:%s created of type %d\n", curr->id,
+                       curr->name, curr->info.type);
+               curr = (void *)curr + csize;
+       }
+       module->controls = dapm_kctls;
+
+       return 0;
+error:
+       list_for_each_entry_safe(control, _control, &module->ctl_list,
+                                list) {
+               list_del(&control->list);
+               devm_kfree(module->dev, control);
+       }
+       devm_kfree(module->dev, dapm_kctls);
+       return ret;
+}
+
+static int gbaudio_tplg_process_widgets(struct gbaudio_module_info *module,
+                                  struct gb_audio_widget *widgets)
+{
+       int i, ret, w_size;
+       struct snd_soc_dapm_widget *dapm_widgets;
+       struct gb_audio_widget *curr;
+       struct gbaudio_widget *widget, *_widget;
+       size_t size;
+
+       size = sizeof(struct snd_soc_dapm_widget) * module->num_dapm_widgets;
+       dapm_widgets = devm_kzalloc(module->dev, size, GFP_KERNEL);
+       if (!dapm_widgets)
+               return -ENOMEM;
+
+       curr = widgets;
+       for (i = 0; i < module->num_dapm_widgets; i++) {
+               ret = gbaudio_tplg_create_widget(module, &dapm_widgets[i],
+                                                curr, &w_size);
+               if (ret) {
+                       dev_err(module->dev, "%s:%d type not supported\n",
+                               curr->name, curr->type);
+                       goto error;
+               }
+               widget = devm_kzalloc(module->dev, sizeof(struct
+                                                          gbaudio_widget),
+                                     GFP_KERNEL);
+               if (!widget) {
+                       ret = -ENOMEM;
+                       goto error;
+               }
+               widget->id = curr->id;
+               widget->name = curr->name;
+               list_add(&widget->list, &module->widget_list);
+               curr = (void *)curr + w_size;
+       }
+       module->dapm_widgets = dapm_widgets;
+
+       return 0;
+
+error:
+       list_for_each_entry_safe(widget, _widget, &module->widget_list,
+                                list) {
+               list_del(&widget->list);
+               devm_kfree(module->dev, widget);
+       }
+       devm_kfree(module->dev, dapm_widgets);
+       return ret;
+}
+
+static int gbaudio_tplg_process_routes(struct gbaudio_module_info *module,
+                                  struct gb_audio_route *routes)
+{
+       int i, ret;
+       struct snd_soc_dapm_route *dapm_routes;
+       struct gb_audio_route *curr;
+       size_t size;
+
+       size = sizeof(struct snd_soc_dapm_route) * module->num_dapm_routes;
+       dapm_routes = devm_kzalloc(module->dev, size, GFP_KERNEL);
+       if (!dapm_routes)
+               return -ENOMEM;
+
+       module->dapm_routes = dapm_routes;
+       curr = routes;
+
+       for (i = 0; i < module->num_dapm_routes; i++) {
+               dapm_routes->sink =
+                       gbaudio_map_widgetid(module, curr->destination_id);
+               if (!dapm_routes->sink) {
+                       dev_err(module->dev, "%d:%d:%d:%d - Invalid sink\n",
+                               curr->source_id, curr->destination_id,
+                               curr->control_id, curr->index);
+                       ret = -EINVAL;
+                       goto error;
+               }
+               dapm_routes->source =
+                       gbaudio_map_widgetid(module, curr->source_id);
+               if (!dapm_routes->source) {
+                       dev_err(module->dev, "%d:%d:%d:%d - Invalid source\n",
+                               curr->source_id, curr->destination_id,
+                               curr->control_id, curr->index);
+                       ret = -EINVAL;
+                       goto error;
+               }
+               dapm_routes->control =
+                       gbaudio_map_controlid(module,
+                                                     curr->control_id,
+                                                     curr->index);
+               if ((curr->control_id !=  GBAUDIO_INVALID_ID) &&
+                   !dapm_routes->control) {
+                       dev_err(module->dev, "%d:%d:%d:%d - Invalid control\n",
+                               curr->source_id, curr->destination_id,
+                               curr->control_id, curr->index);
+                       ret = -EINVAL;
+                       goto error;
+               }
+               dev_dbg(module->dev, "Route {%s, %s, %s}\n", dapm_routes->sink,
+                       (dapm_routes->control) ? dapm_routes->control:"NULL",
+                       dapm_routes->source);
+               dapm_routes++;
+               curr++;
+       }
+
+       return 0;
+
+error:
+       devm_kfree(module->dev, module->dapm_routes);
+       return ret;
+}
+
+static int gbaudio_tplg_process_header(struct gbaudio_module_info *module,
+                                struct gb_audio_topology *tplg_data)
+{
+       /* fetch no. of kcontrols, widgets & routes */
+       module->num_controls = tplg_data->num_controls;
+       module->num_dapm_widgets = tplg_data->num_widgets;
+       module->num_dapm_routes = tplg_data->num_routes;
+
+       /* update block offset */
+       module->dai_offset = (unsigned long)&tplg_data->data;
+       module->control_offset = module->dai_offset + tplg_data->size_dais;
+       module->widget_offset = module->control_offset +
+               tplg_data->size_controls;
+       module->route_offset = module->widget_offset +
+               tplg_data->size_widgets;
+
+       dev_dbg(module->dev, "DAI offset is 0x%lx\n", module->dai_offset);
+       dev_dbg(module->dev, "control offset is %lx\n",
+               module->control_offset);
+       dev_dbg(module->dev, "widget offset is %lx\n", module->widget_offset);
+       dev_dbg(module->dev, "route offset is %lx\n", module->route_offset);
+
+       return 0;
+}
+
+int gbaudio_tplg_parse_data(struct gbaudio_module_info *module,
+                              struct gb_audio_topology *tplg_data)
+{
+       int ret;
+       struct gb_audio_control *controls;
+       struct gb_audio_widget *widgets;
+       struct gb_audio_route *routes;
+
+       if (!tplg_data)
+               return -EINVAL;
+
+       ret = gbaudio_tplg_process_header(module, tplg_data);
+       if (ret) {
+               dev_err(module->dev, "%d: Error in parsing topology header\n",
+                       ret);
+               return ret;
+       }
+
+       /* process control */
+       controls = (struct gb_audio_control *)module->control_offset;
+       ret = gbaudio_tplg_process_kcontrols(module, controls);
+       if (ret) {
+               dev_err(module->dev,
+                       "%d: Error in parsing controls data\n", ret);
+               return ret;
+       }
+       dev_dbg(module->dev, "Control parsing finished\n");
+
+       /* process widgets */
+       widgets = (struct gb_audio_widget *)module->widget_offset;
+       ret = gbaudio_tplg_process_widgets(module, widgets);
+       if (ret) {
+               dev_err(module->dev,
+                       "%d: Error in parsing widgets data\n", ret);
+               return ret;
+       }
+       dev_dbg(module->dev, "Widget parsing finished\n");
+
+       /* process route */
+       routes = (struct gb_audio_route *)module->route_offset;
+       ret = gbaudio_tplg_process_routes(module, routes);
+       if (ret) {
+               dev_err(module->dev,
+                       "%d: Error in parsing routes data\n", ret);
+               return ret;
+       }
+       dev_dbg(module->dev, "Route parsing finished\n");
+
+       /* parse jack capabilities */
+       if (tplg_data->jack_type) {
+               module->jack_mask = tplg_data->jack_type & GBCODEC_JACK_MASK;
+               module->button_mask = tplg_data->jack_type &
+                       GBCODEC_JACK_BUTTON_MASK;
+       }
+
+       return ret;
+}
+
+void gbaudio_tplg_release(struct gbaudio_module_info *module)
+{
+       struct gbaudio_control *control, *_control;
+       struct gbaudio_widget *widget, *_widget;
+
+       if (!module->topology)
+               return;
+
+       /* release kcontrols */
+       list_for_each_entry_safe(control, _control, &module->ctl_list,
+                                list) {
+               list_del(&control->list);
+               devm_kfree(module->dev, control);
+       }
+       if (module->controls)
+               devm_kfree(module->dev, module->controls);
+
+       /* release widget controls */
+       list_for_each_entry_safe(control, _control, &module->widget_ctl_list,
+                                list) {
+               list_del(&control->list);
+               devm_kfree(module->dev, control);
+       }
+
+       /* release widgets */
+       list_for_each_entry_safe(widget, _widget, &module->widget_list,
+                                list) {
+               list_del(&widget->list);
+               devm_kfree(module->dev, widget);
+       }
+       if (module->dapm_widgets)
+               devm_kfree(module->dev, module->dapm_widgets);
+
+       /* release routes */
+       if (module->dapm_routes)
+               devm_kfree(module->dev, module->dapm_routes);
+}


Reply via email to