diff --git a/Makefile b/Makefile
index 9945cb8..5158d87 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ greybus-y :=	core.o		\
 		protocol.o	\
 		operation.o
 
-gb-phy-y :=	gpb.o		\
+gb-phy-y :=	gpb.o	\
 		sdio.o	\
 		uart.o	\
 		pwm.o	\
@@ -18,7 +18,11 @@ gb-phy-y :=	gpb.o		\
 		hid.o	\
 		i2c.o	\
 		spi.o	\
-		usb.o
+		usb.o	\
+		audio.o		\
+		audio-pcm.o	\
+		audio-dai.o	\
+		audio-gb-cmds.o
 
 # Prefix all modules with gb-
 gb-vibrator-y := vibrator.o
diff --git a/audio-dai.c b/audio-dai.c
new file mode 100644
index 0000000..f2c8ca0
--- /dev/null
+++ b/audio-dai.c
@@ -0,0 +1,103 @@
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/simple_card.h>
+#include "greybus.h"
+#include "gpbridge.h"
+#include "audio.h"
+
+/*
+ * This is the greybus cpu dai logic. It really doesn't do much
+ * other then provide the TRIGGER_START/STOP hooks that start
+ * and stop the timer sending audio data in the pcm logic.
+ */
+
+
+static int gb_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct gb_snd *snd_dev;
+
+
+	snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		gb_pcm_hrtimer_start(snd_dev);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		gb_pcm_hrtimer_stop(snd_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+/*
+ * XXX This is annoying, if we don't have a set_fmt function
+ * the subsystem returns -ENOTSUPP, which causes applications
+ * to fail, so add a dummy function here.
+ */
+static int gb_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	return 0;
+}
+
+static const struct snd_soc_dai_ops gb_dai_ops = {
+	.trigger	= gb_dai_trigger,
+	.set_fmt	= gb_dai_set_fmt,
+};
+
+struct snd_soc_dai_driver gb_cpu_dai = {
+	.name			= "gb-cpu-dai",
+	.playback = {
+		.rates		= GB_RATES,
+		.formats	= GB_FMTS,
+		.channels_min	= 1,
+		.channels_max	= 2,
+	},
+	.ops = &gb_dai_ops,
+};
+
+static const struct snd_soc_component_driver gb_soc_component = {
+	.name		= "gb-component",
+};
+
+static int gb_plat_probe(struct platform_device *pdev)
+{
+	struct gb_snd *snd_dev;
+	int ret;
+
+	snd_dev = (struct gb_snd *)pdev->dev.platform_data;
+	dev_set_drvdata(&pdev->dev, snd_dev);
+
+	ret = snd_soc_register_component(&pdev->dev, &gb_soc_component,
+							&gb_cpu_dai, 1);
+	return ret;
+}
+
+static int gb_plat_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+struct platform_driver gb_audio_plat_driver = {
+	.driver		= {
+		.name	= "gb-dai-audio",
+	},
+	.probe		= gb_plat_probe,
+	.remove		= gb_plat_remove,
+};
diff --git a/audio-gb-cmds.c b/audio-gb-cmds.c
new file mode 100644
index 0000000..ef31916
--- /dev/null
+++ b/audio-gb-cmds.c
@@ -0,0 +1,207 @@
+#include <linux/kernel.h>
+#include "greybus.h"
+#include "gpbridge.h"
+#include "audio.h"
+
+#define GB_I2S_MGMT_VERSION_MAJOR		0x00
+#define GB_I2S_MGMT_VERSION_MINOR		0x01
+
+#define GB_I2S_DATA_VERSION_MAJOR		0x00
+#define GB_I2S_MGMT_VERSION_MINOR		0x01
+
+/***********************************
+ * GB I2S helper functions
+ ***********************************/
+int gb_i2s_mgmt_get_version(struct gb_connection *connection)
+{
+	struct gb_protocol_version_response response;
+
+	memset(&response, 0, sizeof(response));
+	return gb_protocol_get_version(connection,
+				       GB_I2S_MGMT_TYPE_PROTOCOL_VERSION,
+				       NULL, 0, &response,
+				       GB_I2S_MGMT_VERSION_MAJOR);
+}
+
+int gb_i2s_data_get_version(struct gb_connection *connection)
+{
+	struct gb_protocol_version_response response;
+
+	memset(&response, 0, sizeof(response));
+	return gb_protocol_get_version(connection,
+				       GB_I2S_DATA_TYPE_PROTOCOL_VERSION,
+				       NULL, 0, &response,
+				       GB_I2S_DATA_VERSION_MAJOR);
+}
+
+int gb_i2s_mgmt_activate_cport(struct gb_connection *connection,
+				      uint16_t cport)
+{
+	struct gb_i2s_mgmt_activate_cport_request request;
+
+	memset(&request, 0, sizeof(request));
+	request.cport = cport;
+
+	return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_ACTIVATE_CPORT,
+				 &request, sizeof(request), NULL, 0);
+}
+
+int gb_i2s_mgmt_deactivate_cport(struct gb_connection *connection,
+					uint16_t cport)
+{
+	struct gb_i2s_mgmt_deactivate_cport_request request;
+
+	memset(&request, 0, sizeof(request));
+	request.cport = cport;
+
+	return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_DEACTIVATE_CPORT,
+				 &request, sizeof(request), NULL, 0);
+}
+
+int gb_i2s_mgmt_get_supported_configurations(
+	struct gb_connection *connection,
+	struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg,
+	size_t size)
+{
+	return gb_operation_sync(connection,
+				 GB_I2S_MGMT_TYPE_GET_SUPPORTED_CONFIGURATIONS,
+				 NULL, 0, get_cfg, size);
+}
+
+int gb_i2s_mgmt_set_configuration(struct gb_connection *connection,
+			struct gb_i2s_mgmt_set_configuration_request *set_cfg)
+{
+	return gb_operation_sync(connection, GB_I2S_MGMT_TYPE_SET_CONFIGURATION,
+				 set_cfg, sizeof(*set_cfg), NULL, 0);
+}
+
+int gb_i2s_mgmt_set_samples_per_message(
+				struct gb_connection *connection,
+				uint16_t samples_per_message)
+{
+	struct gb_i2s_mgmt_set_samples_per_message_request request;
+
+	memset(&request, 0, sizeof(request));
+	request.samples_per_message = samples_per_message;
+
+	return gb_operation_sync(connection,
+				 GB_I2S_MGMT_TYPE_SET_SAMPLES_PER_MESSAGE,
+				 &request, sizeof(request), NULL, 0);
+}
+
+/*
+ * XXX This is sort of a generic "setup" function which  probably needs
+ * to be broken up, and tied into the constraints.
+ *
+ * I'm on the fence if we should just dictate that we only support
+ * 48k, 16bit, 2 channel, and avoid doign the whole probe for configurations
+ * and then picking one.
+ */
+int gb_i2s_mgmt_setup(struct gb_connection *connection)
+{
+	struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg;
+	struct gb_i2s_mgmt_set_configuration_request set_cfg;
+	struct gb_i2s_mgmt_configuration *cfg;
+	size_t size;
+	int i, ret;
+
+	size = sizeof(*get_cfg) +
+	       (CONFIG_COUNT_MAX * sizeof(get_cfg->config[0]));
+
+	get_cfg = kzalloc(size, GFP_KERNEL);
+	if (!get_cfg)
+		return -ENOMEM;
+
+	ret = gb_i2s_mgmt_get_supported_configurations(connection, get_cfg,
+						       size);
+	if (ret) {
+		pr_err("get_supported_config failed: %d\n", ret);
+		goto free_get_cfg;
+	}
+
+	/* Pick 48KHz 16-bits/channel */
+	for (i = 0, cfg = get_cfg->config; i < CONFIG_COUNT_MAX; i++, cfg++) {
+		if ((cfg->sample_frequency == GB_SAMPLE_RATE) &&
+		    (cfg->num_channels == 2) &&
+		    (cfg->bytes_per_channel == 2) &&
+		    (cfg->byte_order & GB_I2S_MGMT_BYTE_ORDER_LE) &&
+		    (cfg->spatial_locations ==
+			(GB_I2S_MGMT_SPATIAL_LOCATION_FL |
+			 GB_I2S_MGMT_SPATIAL_LOCATION_FR)) &&
+		    (cfg->ll_protocol & GB_I2S_MGMT_PROTOCOL_I2S) &&
+		    (cfg->ll_mclk_role & GB_I2S_MGMT_ROLE_MASTER) &&
+		    (cfg->ll_bclk_role & GB_I2S_MGMT_ROLE_MASTER) &&
+		    (cfg->ll_wclk_role & GB_I2S_MGMT_ROLE_MASTER) &&
+		    (cfg->ll_wclk_polarity & GB_I2S_MGMT_POLARITY_NORMAL) &&
+		    (cfg->ll_wclk_change_edge & GB_I2S_MGMT_EDGE_FALLING) &&
+		    (cfg->ll_wclk_tx_edge & GB_I2S_MGMT_EDGE_FALLING) &&
+		    (cfg->ll_wclk_rx_edge & GB_I2S_MGMT_EDGE_RISING) &&
+		    (cfg->ll_data_offset == 1))
+			break;
+	}
+
+	if (i >= CONFIG_COUNT_MAX) {
+		pr_err("No valid configuration\n");
+		ret = -EINVAL;
+		goto free_get_cfg;
+	}
+
+	memcpy(&set_cfg, cfg, sizeof(set_cfg));
+	set_cfg.config.byte_order = GB_I2S_MGMT_BYTE_ORDER_LE;
+	set_cfg.config.ll_protocol = GB_I2S_MGMT_PROTOCOL_I2S;
+	set_cfg.config.ll_mclk_role = GB_I2S_MGMT_ROLE_MASTER;
+	set_cfg.config.ll_bclk_role = GB_I2S_MGMT_ROLE_MASTER;
+	set_cfg.config.ll_wclk_role = GB_I2S_MGMT_ROLE_MASTER;
+	set_cfg.config.ll_wclk_polarity = GB_I2S_MGMT_POLARITY_NORMAL;
+	set_cfg.config.ll_wclk_change_edge = GB_I2S_MGMT_EDGE_RISING;
+	set_cfg.config.ll_wclk_tx_edge = GB_I2S_MGMT_EDGE_FALLING;
+	set_cfg.config.ll_wclk_rx_edge = GB_I2S_MGMT_EDGE_RISING;
+
+	ret = gb_i2s_mgmt_set_configuration(connection, &set_cfg);
+	if (ret) {
+		pr_err("set_configuration failed: %d\n", ret);
+		goto free_get_cfg;
+	}
+
+	ret = gb_i2s_mgmt_set_samples_per_message(connection,
+						  CONFIG_SAMPLES_PER_MSG);
+	if (ret) {
+		pr_err("set_samples_per_msg failed: %d\n", ret);
+		goto free_get_cfg;
+	}
+
+	/* XXX Add start delay here (probably 1ms) */
+	ret = gb_i2s_mgmt_activate_cport(connection,
+					 CONFIG_I2S_REMOTE_DATA_CPORT);
+	if (ret) {
+		pr_err("activate_cport failed: %d\n", ret);
+		goto free_get_cfg;
+	}
+
+free_get_cfg:
+	kfree(get_cfg);
+	return ret;
+}
+
+int gb_i2s_send_data(struct gb_connection *connection,
+					void *req_buf, void *source_addr,
+					size_t len, int sample_num)
+{
+	struct gb_i2s_send_data_request *gb_req;
+	int ret;
+
+	gb_req = req_buf;
+	gb_req->sample_number = sample_num;
+
+	memcpy((void *)&gb_req->data[0], source_addr, len);
+
+	if (len < MAX_SEND_DATA_LEN)
+		for (; len < MAX_SEND_DATA_LEN; len++)
+			gb_req->data[len] = gb_req->data[len - SAMPLE_SIZE];
+
+	gb_req->size = len;
+
+	ret = gb_operation_sync(connection, GB_I2S_DATA_TYPE_SEND_DATA,
+				(void *) gb_req, SEND_DATA_BUF_LEN, NULL, 0);
+	return ret;
+}
diff --git a/audio-pcm.c b/audio-pcm.c
new file mode 100644
index 0000000..28b5e11
--- /dev/null
+++ b/audio-pcm.c
@@ -0,0 +1,262 @@
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/simple_card.h>
+#include "greybus.h"
+#include "gpbridge.h"
+#include "audio.h"
+
+/*
+ * timer/workqueue logic for pushing pcm data.
+ *
+ * Since when we are playing audio, we don't get any
+ * status or feedback from the codec, we have to use a
+ * hrtimer to trigger sending data to the remote codec.
+ * However since the hrtimer runs in irq context, so we
+ * have to schedule a workqueue to actually send the
+ * greybus data.
+ */
+
+static void gb_pcm_work(struct work_struct *work)
+{
+	struct gb_snd *snd_dev = container_of(work, struct gb_snd, work);
+	struct snd_pcm_substream *substream = snd_dev->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	unsigned int stride, frames, oldptr;
+	int period_elapsed;
+	char *address;
+	long len;
+
+	if (!snd_dev)
+		return;
+
+	if (!atomic_read(&snd_dev->running))
+		return;
+
+	address = runtime->dma_area + snd_dev->hwptr_done;
+
+	len = frames_to_bytes(runtime,
+			      runtime->buffer_size) - snd_dev->hwptr_done;
+	len = min(len, MAX_SEND_DATA_LEN);
+	gb_i2s_send_data(snd_dev->i2s_tx_connection, snd_dev->send_data_req_buf,
+				address, len, snd_dev->send_data_sample_count);
+
+	snd_dev->send_data_sample_count += CONFIG_SAMPLES_PER_MSG;
+
+	stride = runtime->frame_bits >> 3;
+	frames = len/stride;
+
+	snd_pcm_stream_lock(substream);
+	oldptr = snd_dev->hwptr_done;
+	snd_dev->hwptr_done += len;
+	if (snd_dev->hwptr_done >= runtime->buffer_size * stride)
+		snd_dev->hwptr_done -= runtime->buffer_size * stride;
+
+	frames = (len + (oldptr % stride)) / stride;
+
+	snd_dev->transfer_done += frames;
+	if (snd_dev->transfer_done >= runtime->period_size) {
+		snd_dev->transfer_done -= runtime->period_size;
+		period_elapsed = 1;
+	}
+
+	snd_pcm_stream_unlock(substream);
+	if (period_elapsed)
+		snd_pcm_period_elapsed(snd_dev->substream);
+}
+
+static enum hrtimer_restart gb_pcm_timer_function(struct hrtimer *hrtimer)
+{
+	struct gb_snd *snd_dev = container_of(hrtimer, struct gb_snd, timer);
+
+	if (!atomic_read(&snd_dev->running))
+		return HRTIMER_NORESTART;
+	queue_work(snd_dev->workqueue, &snd_dev->work);
+	hrtimer_forward_now(hrtimer, ns_to_ktime(CONFIG_PERIOD_NS));
+	return HRTIMER_RESTART;
+}
+
+void gb_pcm_hrtimer_start(struct gb_snd *snd_dev)
+{
+	atomic_set(&snd_dev->running, 1);
+	hrtimer_start(&snd_dev->timer, ns_to_ktime(CONFIG_PERIOD_NS),
+						HRTIMER_MODE_REL);
+}
+
+void gb_pcm_hrtimer_stop(struct gb_snd *snd_dev)
+{
+	atomic_set(&snd_dev->running, 0);
+	hrtimer_cancel(&snd_dev->timer);
+}
+
+static int gb_pcm_hrtimer_init(struct gb_snd *snd_dev)
+{
+	hrtimer_init(&snd_dev->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+	snd_dev->timer.function = gb_pcm_timer_function;
+	atomic_set(&snd_dev->running, 0);
+	snd_dev->workqueue = alloc_workqueue("gb-audio", WQ_HIGHPRI, 0);
+	if (!snd_dev->workqueue)
+		return -ENOMEM;
+	INIT_WORK(&snd_dev->work, gb_pcm_work);
+	return 0;
+}
+
+
+/*
+ * Core gb pcm structure
+ */
+static struct snd_pcm_hardware gb_plat_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_MMAP        |
+				  SNDRV_PCM_INFO_MMAP_VALID,
+	.formats		= GB_FMTS,
+	.rates			= GB_RATES,
+	.rate_min		= 8000,
+	.rate_max		= GB_SAMPLE_RATE,
+	.channels_min		= 1,
+	.channels_max		= 2,
+	/* XXX - All the values below are junk */
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192,
+	.periods_min		= 2,
+	.periods_max		= 32,
+};
+
+static snd_pcm_uframes_t gb_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct gb_snd *snd_dev;
+
+	snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	return snd_dev->hwptr_done  / (substream->runtime->frame_bits >> 3);
+}
+
+static int gb_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct gb_snd *snd_dev;
+
+	snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+	snd_dev->hwptr_done = 0;
+	snd_dev->transfer_done = 0;
+	return 0;
+}
+
+static unsigned int rates[] = {GB_SAMPLE_RATE};
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static int gb_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct gb_snd *snd_dev;
+	unsigned long flags;
+	int ret;
+
+	snd_dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+
+	spin_lock_irqsave(&snd_dev->lock, flags);
+	runtime->private_data = snd_dev;
+	snd_dev->substream = substream;
+	ret = gb_pcm_hrtimer_init(snd_dev);
+	spin_unlock_irqrestore(&snd_dev->lock, flags);
+
+	if (ret)
+		return ret;
+
+	snd_soc_set_runtime_hwparams(substream, &gb_plat_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_list(substream->runtime, 0,
+					SNDRV_PCM_HW_PARAM_RATE,
+					&constraints_rates);
+	if (ret < 0)
+		return ret;
+
+	return snd_pcm_hw_constraint_integer(runtime,
+					     SNDRV_PCM_HW_PARAM_PERIODS);
+}
+
+static int gb_pcm_close(struct snd_pcm_substream *substream)
+{
+	substream->runtime->private_data = NULL;
+	return 0;
+}
+
+static int gb_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_malloc_pages(substream,
+					params_buffer_bytes(hw_params));
+}
+
+static int gb_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static struct snd_pcm_ops gb_pcm_ops = {
+	.open		= gb_pcm_open,
+	.close		= gb_pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= gb_pcm_hw_params,
+	.hw_free	= gb_pcm_hw_free,
+	.prepare	= gb_pcm_prepare,
+	.pointer	= gb_pcm_pointer,
+};
+
+static void gb_pcm_free(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int gb_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+
+	return snd_pcm_lib_preallocate_pages_for_all(
+			pcm,
+			SNDRV_DMA_TYPE_CONTINUOUS,
+			snd_dma_continuous_data(GFP_KERNEL),
+			PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+}
+
+struct snd_soc_platform_driver gb_soc_platform = {
+	.ops		= &gb_pcm_ops,
+	.pcm_new	= gb_pcm_new,
+	.pcm_free	= gb_pcm_free,
+};
+
+static int gb_soc_platform_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &gb_soc_platform);
+}
+
+static int gb_soc_platform_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+struct platform_driver gb_audio_pcm_driver = {
+	.driver = {
+			.name = "gb-pcm-audio",
+			.owner = THIS_MODULE,
+	},
+	.probe = gb_soc_platform_probe,
+	.remove = gb_soc_platform_remove,
+};
diff --git a/audio.c b/audio.c
new file mode 100644
index 0000000..717e002
--- /dev/null
+++ b/audio.c
@@ -0,0 +1,432 @@
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/simple_card.h>
+#include "greybus.h"
+#include "gpbridge.h"
+#include "audio.h"
+
+
+#define GB_AUDIO_DATA_DRIVER_NAME		"gb_audio_data"
+#define GB_AUDIO_MGMT_DRIVER_NAME		"gb_audio_mgmt"
+
+/*
+ * gb_snd management functions
+ */
+static DEFINE_SPINLOCK(gb_snd_list_lock);
+static LIST_HEAD(gb_snd_list);
+static int device_count;
+
+static struct gb_snd *gb_find_snd(int bundle_id)
+{
+	struct gb_snd *tmp, *ret = NULL;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gb_snd_list_lock, flags);
+	list_for_each_entry(tmp, &gb_snd_list, list)
+		if (tmp->gb_bundle_id == bundle_id) {
+			ret = tmp;
+			break;
+		}
+	spin_unlock_irqrestore(&gb_snd_list_lock, flags);
+	return ret;
+}
+
+static struct gb_snd *gb_get_snd(int bundle_id)
+{
+	struct gb_snd *snd_dev;
+	unsigned long flags;
+
+	snd_dev = gb_find_snd(bundle_id);
+	if (snd_dev)
+		return snd_dev;
+
+	snd_dev = kzalloc(sizeof(*snd_dev), GFP_KERNEL);
+	if (!snd_dev)
+		return NULL;
+
+	spin_lock_init(&snd_dev->lock);
+	snd_dev->device_count = device_count++;
+	snd_dev->gb_bundle_id = bundle_id;
+	spin_lock_irqsave(&gb_snd_list_lock, flags);
+	list_add(&snd_dev->list, &gb_snd_list);
+	spin_unlock_irqrestore(&gb_snd_list_lock, flags);
+	return snd_dev;
+}
+
+static void gb_free_snd(struct gb_snd *snd)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&gb_snd_list_lock, flags);
+	if (!snd->i2s_tx_connection &&
+			!snd->mgmt_connection) {
+		list_del(&snd->list);
+		spin_unlock_irqrestore(&gb_snd_list_lock, flags);
+		kfree(snd);
+	} else {
+		spin_unlock_irqrestore(&gb_snd_list_lock, flags);
+	}
+}
+
+
+
+
+/*
+ * This is the ASoC simple card binds the platform codec,
+ * cpu-dai and codec-dai togheter
+ */
+struct gb_card_info_object {
+	struct asoc_simple_card_info card_info;
+	char codec_name[255];
+	char platform_name[255];
+	char dai_name[255];
+};
+
+
+struct asoc_simple_card_info *setup_card_info(int device_count)
+{
+	struct gb_card_info_object *obj;
+
+	obj = kzalloc(sizeof(struct gb_card_info_object), GFP_KERNEL);
+	if (!obj)
+		return NULL;
+
+	obj->card_info.name		= "Greybus Audio Module";
+	obj->card_info.card		= "gb-card";
+	obj->card_info.codec		= obj->codec_name;
+	obj->card_info.platform		= obj->platform_name;
+	obj->card_info.cpu_dai.name	= obj->dai_name;
+	obj->card_info.cpu_dai.fmt	= GB_FMTS;
+#if USE_RT5645
+	obj->card_info.daifmt		= GB_FMTS;
+	sprintf(obj->codec_name, "rt5645.%s", "6-001b"); /* XXX do i2c bus addr dynamically */
+	obj->card_info.codec_dai.name	= "rt5645-aif1";
+	obj->card_info.codec_dai.fmt	= SND_SOC_DAIFMT_CBM_CFM;
+	obj->card_info.codec_dai.sysclk	= 12288000;
+#else
+	sprintf(obj->codec_name, "spdif-dit");
+	obj->card_info.codec_dai.name = "dit-hifi";
+#endif
+	sprintf(obj->platform_name, "gb-pcm-audio.%i", device_count);
+	sprintf(obj->dai_name, "gb-dai-audio.%i", device_count);
+
+	return &obj->card_info;
+}
+
+void free_card_info(struct asoc_simple_card_info *ci)
+{
+	struct gb_card_info_object *obj;
+
+	obj = container_of(ci, struct gb_card_info_object, card_info);
+	kfree(obj);
+}
+
+
+/*
+ * XXX this is sort of cruddy but I get warnings if
+ * we don't have dev.release handler set.
+ */
+static void default_release(struct device *dev)
+{
+}
+
+/*
+ * GB connection hooks
+ */
+static int gb_i2s_transmitter_connection_init(struct gb_connection *connection)
+{
+	struct gb_snd *snd_dev;
+	struct platform_device *codec, *dai;
+	struct asoc_simple_card_info *simple_card;
+	unsigned long flags;
+	int ret;
+
+	snd_dev = gb_get_snd(connection->bundle->id);
+	if (!snd_dev)
+		return -ENOMEM;
+
+	codec = platform_device_register_simple("spdif-dit", -1, NULL, 0);
+	if (!codec) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	dai = platform_device_register_simple("gb-pcm-audio", snd_dev->device_count, NULL, 0);
+	if (!dai) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	simple_card = setup_card_info(snd_dev->device_count);
+	if (!simple_card) {
+		ret = -ENOMEM;
+		goto out;
+	}
+
+	spin_lock_irqsave(&snd_dev->lock, flags);
+	snd_dev->card.name = "asoc-simple-card";
+	snd_dev->card.id = snd_dev->device_count;
+	snd_dev->card.dev.release = default_release; /* XXX - suspicious */
+
+	snd_dev->cpu_dai.name = "gb-dai-audio";
+	snd_dev->cpu_dai.id = snd_dev->device_count;
+	snd_dev->cpu_dai.dev.release = default_release; /* XXX - suspicious */
+
+
+	snd_dev->simple_card_info = simple_card;
+	snd_dev->card.dev.platform_data = simple_card;
+
+	snd_dev->codec = codec;
+	snd_dev->i2s_tx_connection = connection;
+	snd_dev->cpu_dai.dev.platform_data = snd_dev;
+	snd_dev->i2s_tx_connection->private = snd_dev;
+	spin_unlock_irqrestore(&snd_dev->lock, flags);
+
+	ret = platform_device_register(&snd_dev->cpu_dai);
+	if (ret) {
+		pr_err("cpu_dai platform_device register failed\n");
+		goto out_dai;
+	}
+
+	ret = platform_device_register(&snd_dev->card);
+	if (ret) {
+		pr_err("card platform_device register failed\n");
+		goto out_card;
+	}
+
+	ret = gb_i2s_data_get_version(connection);
+	if (ret) {
+		pr_err("i2s data get_version() failed: %d\n", ret);
+		goto out_get_ver;
+	}
+
+	return 0;
+
+out_get_ver:
+	platform_device_unregister(&snd_dev->card);
+out_card:
+	platform_device_unregister(&snd_dev->cpu_dai);
+out_dai:
+	platform_device_unregister(codec);
+out:
+	gb_free_snd(snd_dev);
+	return ret;
+}
+
+static void gb_i2s_transmitter_connection_exit(struct gb_connection *connection)
+{
+	struct gb_snd *snd_dev;
+
+	snd_dev = (struct gb_snd *)connection->private;
+
+	platform_device_unregister(&snd_dev->card);
+	platform_device_unregister(&snd_dev->cpu_dai);
+	platform_device_unregister(snd_dev->codec);
+
+	free_card_info(snd_dev->simple_card_info);
+	snd_dev->i2s_tx_connection = NULL;
+	gb_free_snd(snd_dev);
+}
+
+static int gb_i2s_mgmt_connection_init(struct gb_connection *connection)
+{
+	struct gb_snd *snd_dev;
+	unsigned long flags;
+	int ret;
+
+	snd_dev = gb_get_snd(connection->bundle->id);
+	if (!snd_dev)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&snd_dev->lock, flags);
+	snd_dev->mgmt_connection = connection;
+	connection->private = snd_dev;
+	spin_unlock_irqrestore(&snd_dev->lock, flags);
+
+	ret = gb_i2s_mgmt_get_version(connection);
+	if (ret) {
+		pr_err("i2s mgmt get_version() failed: %d\n", ret);
+		goto err_free_snd_dev;
+	}
+
+	gb_i2s_mgmt_setup(connection);
+
+	snd_dev->send_data_req_buf = kzalloc(SEND_DATA_BUF_LEN, GFP_KERNEL);
+
+	if (!snd_dev->send_data_req_buf) {
+		ret = -ENOMEM;
+		goto err_deactivate_cport;
+	}
+
+	return 0;
+
+err_deactivate_cport:
+	gb_i2s_mgmt_deactivate_cport(connection, CONFIG_I2S_REMOTE_DATA_CPORT);
+err_free_snd_dev:
+	gb_free_snd(snd_dev);
+	return ret;
+}
+
+static void gb_i2s_mgmt_connection_exit(struct gb_connection *connection)
+{
+	struct gb_snd *snd_dev = (struct gb_snd *)connection->private;
+	int ret;
+
+	ret = gb_i2s_mgmt_deactivate_cport(connection,
+					   CONFIG_I2S_REMOTE_DATA_CPORT);
+	if (ret)
+		pr_err("deactivate_cport failed: %d\n", ret);
+
+	kfree(snd_dev->send_data_req_buf);
+	snd_dev->send_data_req_buf = NULL;
+
+	snd_dev->mgmt_connection = NULL;
+	gb_free_snd(snd_dev);
+}
+
+static int gb_i2s_mgmt_report_event_recv(u8 type, struct gb_operation *op)
+{
+	struct gb_connection *connection = op->connection;
+	struct gb_i2s_mgmt_report_event_request *req = op->request->payload;
+	char *event_name;
+
+	if (type != GB_I2S_MGMT_TYPE_REPORT_EVENT) {
+		dev_err(&connection->dev, "Invalid request type: %d\n",
+			type);
+		return -EINVAL;
+	}
+
+	if (op->request->payload_size < sizeof(*req)) {
+		dev_err(&connection->dev, "Short request received: %d, %d\n",
+			op->request->payload_size, sizeof(*req));
+		return -EINVAL;
+	}
+
+	switch (req->event) {
+	case GB_I2S_MGMT_EVENT_UNSPECIFIED:
+		event_name = "UNSPECIFIED";
+		break;
+	case GB_I2S_MGMT_EVENT_HALT:
+		/* XXX Should stop streaming now */
+		event_name = "HALT";
+		break;
+	case GB_I2S_MGMT_EVENT_INTERNAL_ERROR:
+		event_name = "INTERNAL_ERROR";
+		break;
+	case GB_I2S_MGMT_EVENT_PROTOCOL_ERROR:
+		event_name = "PROTOCOL_ERROR";
+		break;
+	case GB_I2S_MGMT_EVENT_FAILURE:
+		event_name = "FAILURE";
+		break;
+	case GB_I2S_MGMT_EVENT_OUT_OF_SEQUENCE:
+		event_name = "OUT_OF_SEQUENCE";
+		break;
+	case GB_I2S_MGMT_EVENT_UNDERRUN:
+		event_name = "UNDERRUN";
+		break;
+	case GB_I2S_MGMT_EVENT_OVERRUN:
+		event_name = "OVERRUN";
+		break;
+	case GB_I2S_MGMT_EVENT_CLOCKING:
+		event_name = "CLOCKING";
+		break;
+	case GB_I2S_MGMT_EVENT_DATA_LEN:
+		event_name = "DATA_LEN";
+		break;
+	default:
+		dev_warn(&connection->dev, "Unknown I2S Event received: %d\n",
+			 req->event);
+		return -EINVAL;
+	}
+
+	dev_warn(&connection->dev, "I2S Event received: %d - '%s'\n",
+		 req->event, event_name);
+
+	return 0;
+}
+
+static struct gb_protocol gb_i2s_receiver_protocol = {
+	.name			= GB_AUDIO_DATA_DRIVER_NAME,
+	.id			= GREYBUS_PROTOCOL_I2S_RECEIVER,
+	.major			= 0,
+	.minor			= 1,
+	.connection_init	= gb_i2s_transmitter_connection_init,
+	.connection_exit	= gb_i2s_transmitter_connection_exit,
+	.request_recv		= NULL,
+};
+
+static struct gb_protocol gb_i2s_mgmt_protocol = {
+	.name			= GB_AUDIO_MGMT_DRIVER_NAME,
+	.id			= GREYBUS_PROTOCOL_I2S_MGMT,
+	.major			= 0,
+	.minor			= 1,
+	.connection_init	= gb_i2s_mgmt_connection_init,
+	.connection_exit	= gb_i2s_mgmt_connection_exit,
+	.request_recv		= gb_i2s_mgmt_report_event_recv,
+};
+
+
+/*
+ * This is the basic hook get things initialized and registered w/ gb
+ */
+
+int gb_audio_protocol_init(void)
+{
+	int err;
+
+	err = gb_protocol_register(&gb_i2s_mgmt_protocol);
+	if (err) {
+		pr_err("Can't register i2s mgmt protocol driver: %d\n", -err);
+		return err;
+	}
+
+	err = gb_protocol_register(&gb_i2s_receiver_protocol);
+	if (err) {
+		pr_err("Can't register Audio protocol driver: %d\n", -err);
+		goto err_unregister_i2s_mgmt;
+	}
+
+	err = platform_driver_register(&gb_audio_plat_driver);
+	if (err) {
+		pr_err("Can't register platform driver: %d\n", -err);
+		goto err_unregister_plat;
+	}
+
+	err = platform_driver_register(&gb_audio_pcm_driver);
+	if (err) {
+		pr_err("Can't register pcm driver: %d\n", -err);
+		goto err_unregister_pcm;
+	}
+
+	return 0;
+
+err_unregister_pcm:
+	platform_driver_unregister(&gb_audio_plat_driver);
+err_unregister_plat:
+	gb_protocol_deregister(&gb_i2s_receiver_protocol);
+err_unregister_i2s_mgmt:
+	gb_protocol_deregister(&gb_i2s_mgmt_protocol);
+	return err;
+}
+
+void gb_audio_protocol_exit(void)
+{
+	platform_driver_unregister(&gb_audio_pcm_driver);
+	platform_driver_unregister(&gb_audio_plat_driver);
+	gb_protocol_deregister(&gb_i2s_receiver_protocol);
+	gb_protocol_deregister(&gb_i2s_mgmt_protocol);
+}
+
+
+MODULE_LICENSE("GPL");
diff --git a/audio.h b/audio.h
new file mode 100644
index 0000000..6a337d1
--- /dev/null
+++ b/audio.h
@@ -0,0 +1,100 @@
+#ifndef __GB_AUDIO_H
+#define __GB_AUDIO_H
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <sound/soc.h>
+#include "greybus.h"
+#include "gpbridge.h"
+
+
+#define GB_SAMPLE_RATE				48000
+#define GB_RATES				SNDRV_PCM_RATE_48000
+#define GB_FMTS					SNDRV_PCM_FMTBIT_S16_LE
+#define PREALLOC_BUFFER				(32 * 1024)
+#define PREALLOC_BUFFER_MAX			(32 * 1024)
+
+/* assuming 1 ms samples @ 48KHz */
+#define CONFIG_SAMPLES_PER_MSG			48L
+#define CONFIG_PERIOD_NS			1000000 /* send msg every 1ms */
+
+#define CONFIG_COUNT_MAX			32
+#define CONFIG_I2S_REMOTE_DATA_CPORT		7 /* XXX shouldn't be hardcoded...*/
+#define RT5647_SLAVE_ADDR			0x1b /* from toshiba/quanta code */
+
+/* Switch between dummy spdif and jetson rt5645 codec */
+#define USE_RT5645				0
+
+#define SAMPLE_SIZE				4
+#define MAX_SEND_DATA_LEN (CONFIG_SAMPLES_PER_MSG * SAMPLE_SIZE)
+#define SEND_DATA_BUF_LEN (sizeof(struct gb_i2s_send_data_request) + \
+				MAX_SEND_DATA_LEN)
+
+
+/*
+ * This is the gb_snd structure which ties everything together
+ * and fakes DMA interrupts via a timer.
+ */
+struct gb_snd {
+	struct platform_device		card;
+	struct platform_device		cpu_dai;
+	struct platform_device		*codec;
+	struct asoc_simple_card_info	*simple_card_info;
+	struct gb_connection		*mgmt_connection;
+	struct gb_connection		*i2s_tx_connection;
+	struct gb_connection		*i2s_rx_connection;
+	char				*send_data_req_buf;
+	long				send_data_sample_count;
+	int				gb_bundle_id;
+	int				device_count;
+	struct snd_pcm_substream	*substream;
+	struct hrtimer			timer;
+	atomic_t			running;
+	struct workqueue_struct		*workqueue;
+	struct work_struct		work;
+	int				hwptr_done;
+	int				transfer_done;
+	struct list_head		list;
+	spinlock_t			lock;
+};
+
+
+/*
+ * GB I2S cmd functions
+ */
+int gb_i2s_mgmt_get_version(struct gb_connection *connection);
+int gb_i2s_data_get_version(struct gb_connection *connection);
+int gb_i2s_mgmt_activate_cport(struct gb_connection *connection,
+				      uint16_t cport);
+int gb_i2s_mgmt_deactivate_cport(struct gb_connection *connection,
+					uint16_t cport);
+int gb_i2s_mgmt_get_supported_configurations(
+	struct gb_connection *connection,
+	struct gb_i2s_mgmt_get_supported_configurations_response *get_cfg,
+	size_t size);
+int gb_i2s_mgmt_set_configuration(struct gb_connection *connection,
+			struct gb_i2s_mgmt_set_configuration_request *set_cfg);
+int gb_i2s_mgmt_set_samples_per_message(struct gb_connection *connection,
+					uint16_t samples_per_message);
+int gb_i2s_mgmt_setup(struct gb_connection *connection);
+int gb_i2s_send_data(struct gb_connection *connection, void *req_buf,
+				void *source_addr, size_t len, int sample_num);
+
+
+/*
+ * GB PCM hooks
+ */
+void gb_pcm_hrtimer_start(struct gb_snd *snd_dev);
+void gb_pcm_hrtimer_stop(struct gb_snd *snd_dev);
+
+
+/*
+ * Platform drivers
+ */
+extern struct platform_driver gb_audio_pcm_driver;
+extern struct platform_driver gb_audio_plat_driver;
+
+
+#endif /* __GB_AUDIO_H */
diff --git a/gpb.c b/gpb.c
index d5747b2..2324270 100644
--- a/gpb.c
+++ b/gpb.c
@@ -53,8 +53,15 @@ static int __init gpbridge_init(void)
 		pr_err("error initializing hid protocol\n");
 		goto error_hid;
 	}
+	if (gb_audio_protocol_init()) {
+		pr_err("error initializing audio protocols\n");
+		goto error_audio;
+	}
+
 	return 0;
 
+error_audio:
+	gb_hid_protocol_exit();
 error_hid:
 	gb_spi_protocol_exit();
 error_spi:
@@ -76,6 +83,7 @@ module_init(gpbridge_init);
 
 static void __exit gpbridge_exit(void)
 {
+	gb_audio_protocol_exit();
 	gb_hid_protocol_exit();
 	gb_spi_protocol_exit();
 	gb_i2c_protocol_exit();
diff --git a/gpbridge.h b/gpbridge.h
index 85d8019..85cc385 100644
--- a/gpbridge.h
+++ b/gpbridge.h
@@ -341,6 +341,7 @@ struct gb_i2s_mgmt_configuration {
 	__u8	pad;
 	__le32	spatial_locations;
 	__le32	ll_protocol;
+	__u8	ll_mclk_role;
 	__u8	ll_bclk_role;
 	__u8	ll_wclk_role;
 	__u8	ll_wclk_polarity;
@@ -348,7 +349,6 @@ struct gb_i2s_mgmt_configuration {
 	__u8	ll_wclk_tx_edge;
 	__u8	ll_wclk_rx_edge;
 	__u8	ll_data_offset;
-	__u8	ll_pad;
 };
 
 /* get supported configurations request has no payload */
diff --git a/protocol.h b/protocol.h
index 495d13a..7958802 100644
--- a/protocol.h
+++ b/protocol.h
@@ -87,6 +87,9 @@ extern void gb_spi_protocol_exit(void);
 extern int gb_hid_protocol_init(void);
 extern void gb_hid_protocol_exit(void);
 
+extern int gb_audio_protocol_init(void);
+extern void gb_audio_protocol_exit(void);
+
 #define gb_protocol_driver(__protocol)			\
 static int __init protocol_init(void)			\
 {							\
