diff options
| author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-09-15 13:54:11 +0200 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2016-09-15 13:54:11 +0200 |
| commit | c1aa01c96e590714d99c5f17cfae1b14dec8bdee (patch) | |
| tree | 5811422f65efdff3fd89fdaf9a8c6d7d1cc4d06b /greybus_audio.patch | |
| parent | 4758ee8bc89a86c2110b9e85878538ced8045ef5 (diff) | |
| download | patches-c1aa01c96e590714d99c5f17cfae1b14dec8bdee.tar.gz | |
greybus patches
Diffstat (limited to 'greybus_audio.patch')
| -rw-r--r-- | greybus_audio.patch | 4636 |
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); ++} |
