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