aboutsummaryrefslogtreecommitdiffstats
path: root/greybus_audio.patch
diff options
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-09-15 13:54:11 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2016-09-15 13:54:11 +0200
commitc1aa01c96e590714d99c5f17cfae1b14dec8bdee (patch)
tree5811422f65efdff3fd89fdaf9a8c6d7d1cc4d06b /greybus_audio.patch
parent4758ee8bc89a86c2110b9e85878538ced8045ef5 (diff)
downloadpatches-c1aa01c96e590714d99c5f17cfae1b14dec8bdee.tar.gz
greybus patches
Diffstat (limited to 'greybus_audio.patch')
-rw-r--r--greybus_audio.patch4636
1 files changed, 4636 insertions, 0 deletions
diff --git a/greybus_audio.patch b/greybus_audio.patch
new file mode 100644
index 00000000000000..0fa4ec2f8b46c8
--- /dev/null
+++ b/greybus_audio.patch
@@ -0,0 +1,4636 @@
+---
+ 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 <mgreer@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.agarwal@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 <mgreer@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_svetlin@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.agarwal@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);
++}