Re: [alsa-devel] [PATCH v3 05/25] ASoC: qcom: qdsp6: Add support to Q6AFE

Rohit Kumar
2018-02-19T10:30:47Z

On 2/13/2018 10:28 PM, srinivas.kandagatla@linaro.org wrote:
> From: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
>
> This patch adds support to Q6AFE (Audio Front End) module on Q6DSP.
>
> AFE module sits right at the other end of cpu where the codec/audio
> devices are connected.
>
> AFE provides abstraced interfaces to both hardware and virtual devices.
> Each AFE tx/rx port can be configured to connect to one of the hardware
> devices like codec, hdmi, slimbus, i2s and so on. AFE services include
> starting, stopping, and if needed, any configurations of the ports.
>
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
> ---
> include/dt-bindings/sound/qcom,q6afe.h | 9 +
> sound/soc/qcom/Kconfig | 5 +
> sound/soc/qcom/Makefile | 5 +
> sound/soc/qcom/qdsp6/Makefile | 1 +
> sound/soc/qcom/qdsp6/q6afe.c | 520 +++++++++++++++++++++++++++++++++
> sound/soc/qcom/qdsp6/q6afe.h | 37 +++
> 6 files changed, 577 insertions(+)
> create mode 100644 include/dt-bindings/sound/qcom,q6afe.h
> create mode 100644 sound/soc/qcom/qdsp6/q6afe.c
> create mode 100644 sound/soc/qcom/qdsp6/q6afe.h
>
> diff --git a/include/dt-bindings/sound/qcom,q6afe.h b/include/dt-bindings/sound/qcom,q6afe.h
> new file mode 100644
> index 000000000000..b4d82cccdc86
> --- /dev/null
> +++ b/include/dt-bindings/sound/qcom,q6afe.h
> @@ -0,0 +1,9 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#ifndef __DT_BINDINGS_Q6_AFE_H__
> +#define __DT_BINDINGS_Q6_AFE_H__
> +
> +/* Audio Front End (AFE) Ports */
> +#define AFE_PORT_HDMI_RX 8
> +
> +#endif /* __DT_BINDINGS_Q6_AFE_H__ */
> +
> diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
> index b01f347b427d..caeaf8b1b561 100644
> --- a/sound/soc/qcom/Kconfig
> +++ b/sound/soc/qcom/Kconfig
> @@ -48,10 +48,15 @@ config SND_SOC_QDSP6_COMMON
> tristate
> default n
>
> +config SND_SOC_QDSP6_AFE
> + tristate
> + default n
> +
> config SND_SOC_QDSP6
> tristate "SoC ALSA audio driver for QDSP6"
> depends on QCOM_APR && HAS_DMA
> select SND_SOC_QDSP6_COMMON
> + select SND_SOC_QDSP6_AFE
> help
> To add support for MSM QDSP6 Soc Audio.
> This will enable sound soc platform specific
> diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
> index d5280355c24f..748f5e891dcf 100644
> --- a/sound/soc/qcom/Makefile
> +++ b/sound/soc/qcom/Makefile
> @@ -13,6 +13,11 @@ obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o
> # Machine
> snd-soc-storm-objs := storm.o
> snd-soc-apq8016-sbc-objs := apq8016_sbc.o
> +snd-soc-msm8996-objs := apq8096.o
>
This needs to be moved out of this patch
> obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o
> obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o
> +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-msm8996.o
> +
> +#DSP lib
> +obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/
> diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile
> index accebdb49306..9ec951e0833b 100644
> --- a/sound/soc/qcom/qdsp6/Makefile
> +++ b/sound/soc/qcom/qdsp6/Makefile
> @@ -1 +1,2 @@
> obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o
> +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o
> diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c
> new file mode 100644
> index 000000000000..0a5af06bb50e
> --- /dev/null
> +++ b/sound/soc/qcom/qdsp6/q6afe.c
> @@ -0,0 +1,520 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2011-2017, The Linux Foundation
> + * Copyright (c) 2018, Linaro Limited
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/kernel.h>
> +#include <linux/uaccess.h>
> +#include <linux/wait.h>
> +#include <linux/jiffies.h>
> +#include <linux/sched.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/delay.h>
> +#include <linux/soc/qcom/apr.h>
> +#include "q6dsp-errno.h"
> +#include "q6afe.h"
> +
> +/* AFE CMDs */
> +#define AFE_PORT_CMD_DEVICE_START 0x000100E5
> +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6
> +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF
> +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106
> +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210
> +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C
> +
> +/* Port IDs */
> +#define AFE_API_VERSION_HDMI_CONFIG 0x1
> +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E
> +#define TIMEOUT_MS 1000
> +#define AFE_CMD_RESP_AVAIL 0
> +#define AFE_CMD_RESP_NONE 1
> +
> +struct q6afe {
> + struct apr_device *apr;
> + struct device *dev;
> + int state;
> + int status;
> +
> + struct mutex lock;
> + struct list_head port_list;
> + spinlock_t port_list_lock;
> + struct list_head node;
> + void *dai_data;
> +};
> +
> +struct afe_port_cmd_device_start {
> + struct apr_hdr hdr;
> + u16 port_id;
> + u16 reserved;
> +} __packed;
> +
> +struct afe_port_cmd_device_stop {
> + struct apr_hdr hdr;
> + u16 port_id;
> + u16 reserved;
> +/* Reserved for 32-bit alignment. This field must be set to 0.*/
> +} __packed;
> +
> +struct afe_port_param_data_v2 {
> + u32 module_id;
> + u32 param_id;
> + u16 param_size;
> + u16 reserved;
> +} __packed;
> +
> +struct afe_port_cmd_set_param_v2 {
> + u16 port_id;
> + u16 payload_size;
> + u32 payload_address_lsw;
> + u32 payload_address_msw;
> + u32 mem_map_handle;
> +} __packed;
> +
> +struct afe_param_id_hdmi_multi_chan_audio_cfg {
> + u32 hdmi_cfg_minor_version;
> + u16 datatype;
> + u16 channel_allocation;
> + u32 sample_rate;
> + u16 bit_width;
> + u16 reserved;
> +} __packed;
> +
> +union afe_port_config {
> + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch;
> +} __packed;
> +
> +struct q6afe_port {
> + wait_queue_head_t wait;
> + union afe_port_config port_cfg;
> + int token;
> + int id;
> + int cfg_type;
> + struct q6afe *afe;
> + struct list_head node;
> +};
> +
> +struct afe_audioif_config_command {
> + struct apr_hdr hdr;
> + struct afe_port_cmd_set_param_v2 param;
> + struct afe_port_param_data_v2 pdata;
> + union afe_port_config port;
> +} __packed;
> +
> +struct afe_port_map {
> + int port_id;
> + int token;
> + int is_rx;
> + int is_dig_pcm;
> +};
> +
> +/* Port map of index vs real hw port ids */
> +static struct afe_port_map port_maps[AFE_PORT_MAX] = {
> + [AFE_PORT_HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX,
> + AFE_PORT_HDMI_RX, 1, 1},
> +};
> +
> +static struct q6afe_port *afe_find_port(struct q6afe *afe, int token)
> +{
> + struct q6afe_port *p = NULL;
> +
> + spin_lock(&afe->port_list_lock);
> + list_for_each_entry(p, &afe->port_list, node)
> + if (p->token == token)
> + break;
> +
> + spin_unlock(&afe->port_list_lock);
> + return p;
> +}
> +
> +static int afe_callback(struct apr_device *adev,
> + struct apr_client_message *data)
> +{
> + struct q6afe *afe = dev_get_drvdata(&adev->dev);
> + struct aprv2_ibasic_rsp_result_t *res;
> + struct q6afe_port *port;
> +
> + if (!data->payload_size)
> + return 0;
> +
> + res = data->payload;
> + if (data->opcode == APR_BASIC_RSP_RESULT) {
> + if (res->status) {
> + afe->status = res->status;
> + dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n",
> + res->opcode, res->status);
> + }
> +
> + switch (res->opcode) {
> + case AFE_PORT_CMD_SET_PARAM_V2:
> + case AFE_PORT_CMD_DEVICE_STOP:
> + case AFE_PORT_CMD_DEVICE_START:
> + afe->state = AFE_CMD_RESP_AVAIL;
> + port = afe_find_port(afe, data->token);
> + if (port)
> + wake_up(&port->wait);
> +
> + break;
> + default:
> + dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode);
> + break;
> + }
> + }
> +
> + return 0;
> +}
> +/**
> + * q6afe_get_port_id() - Get port id from a given port index
> + *
> + * @index: port index
> + *
> + * Return: Will be an negative on error or valid port_id on success
> + */
> +int q6afe_get_port_id(int index)
> +{
> + if (index < 0 || index > AFE_PORT_MAX)
> + return -EINVAL;
> +
> + return port_maps[index].port_id;
> +}
> +EXPORT_SYMBOL_GPL(q6afe_get_port_id);
> +
> +static int afe_apr_send_pkt(struct q6afe *afe, void *data,
> + wait_queue_head_t *wait)
> +{
> + int ret;
> +
> + mutex_lock(&afe->lock);
> + afe->status = 0;
> + afe->state = AFE_CMD_RESP_NONE;
> +
> + ret = apr_send_pkt(afe->apr, data);
> + if (ret < 0) {
> + dev_err(afe->dev, "packet not transmitted\n");
> + ret = -EINVAL;
> + goto err;
> + }
> +
> + ret = wait_event_timeout(*wait, (afe->state == AFE_CMD_RESP_AVAIL),
> + msecs_to_jiffies(TIMEOUT_MS));
> + if (!ret) {
> + ret = -ETIMEDOUT;
> + } else if (afe->status > 0) {
> + dev_err(afe->dev, "DSP returned error[%s]\n",
> + q6dsp_strerror(afe->status));
> + ret = q6dsp_errno(afe->status);
> + } else {
> + ret = 0;
> + }
> +
> +err:
> + mutex_unlock(&afe->lock);
> +
> + return ret;
> +}
> +
> +static int afe_send_cmd_port_start(struct q6afe_port *port)
> +{
> + u16 port_id = port->id;
> + struct afe_port_cmd_device_start start;
> + struct q6afe *afe = port->afe;
> + int ret;
> +
> + start.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
> + APR_HDR_LEN(APR_HDR_SIZE),
> + APR_PKT_VER);
> + start.hdr.pkt_size = sizeof(start);
> + start.hdr.src_port = 0;
> + start.hdr.dest_port = 0;
> + start.hdr.token = port->token;
> + start.hdr.opcode = AFE_PORT_CMD_DEVICE_START;
> + start.port_id = port_id;
> +
> + ret = afe_apr_send_pkt(afe, &start, &port->wait);
> + if (ret)
> + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
> + port_id, ret);
> +
> + return ret;
> +}
> +
> +static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data,
> + int param_id, int psize)
> +{
> + struct apr_hdr *hdr;
> + struct afe_port_cmd_set_param_v2 *param;
> + struct afe_port_param_data_v2 *pdata;
> + struct q6afe *afe = port->afe;
> + u16 port_id = port->id;
> + int ret;
> +
> + hdr = data;
> + param = data + sizeof(*hdr);
> + pdata = data + sizeof(*hdr) + sizeof(*param);
> +
> + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
> + APR_HDR_LEN(APR_HDR_SIZE),
> + APR_PKT_VER);
> + hdr->pkt_size = sizeof(*hdr) + sizeof(*param) +
> + sizeof(*pdata) + psize;
> + hdr->src_port = 0;
> + hdr->dest_port = 0;
> + hdr->token = port->token;
> + hdr->opcode = AFE_PORT_CMD_SET_PARAM_V2;
> + param->port_id = port_id;
> + param->payload_size = sizeof(*pdata) + psize;
> + param->payload_address_lsw = 0x00;
> + param->payload_address_msw = 0x00;
> + param->mem_map_handle = 0x00;
> + pdata->module_id = AFE_MODULE_AUDIO_DEV_INTERFACE;
> + pdata->param_id = param_id;
> + pdata->param_size = psize;
> +
> + ret = afe_apr_send_pkt(afe, data, &port->wait);
> + if (ret)
> + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
> + port_id, ret);
> +
> +
> + return ret;
> +}
> +
> +static int afe_port_start(struct q6afe_port *port,
> + union afe_port_config *afe_config)
> +{
> + struct afe_audioif_config_command config = {0,};
> + struct q6afe *afe = port->afe;
> + int port_id = port->id;
> + int ret, param_id = port->cfg_type;
> +
> + config.port = *afe_config;
> +
> + ret = q6afe_port_set_param_v2(port, &config, param_id,
> + sizeof(*afe_config));
> + if (ret) {
> + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n",
> + port_id, ret);
> + return ret;
> + }
> + return afe_send_cmd_port_start(port);
> +}
> +
> +/**
> + * q6afe_port_stop() - Stop a afe port
> + *
> + * @port: Instance of port to stop
> + *
> + * Return: Will be an negative on packet size on success.
> + */
> +int q6afe_port_stop(struct q6afe_port *port)
> +{
> + int port_id = port->id;
> + struct afe_port_cmd_device_stop stop;
> + struct q6afe *afe = port->afe;
> + int ret = 0;
> + int index = 0;
> +
> + port_id = port->id;
> + index = port->token;
> + if (index < 0 || index > AFE_PORT_MAX) {
> + dev_err(afe->dev, "AFE port index[%d] invalid!\n", index);
> + return -EINVAL;
> + }
> +
> + stop.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
> + APR_HDR_LEN(APR_HDR_SIZE),
> + APR_PKT_VER);
> + stop.hdr.pkt_size = sizeof(stop);
> + stop.hdr.src_port = 0;
> + stop.hdr.dest_port = 0;
> + stop.hdr.token = index;
> + stop.hdr.opcode = AFE_PORT_CMD_DEVICE_STOP;
> + stop.port_id = port_id;
> + stop.reserved = 0;
> +
> + ret = afe_apr_send_pkt(afe, &stop, &port->wait);
> + if (ret)
> + dev_err(afe->dev, "AFE close failed %d\n", ret);
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(q6afe_port_stop);
> +
> +/**
> + * q6afe_set_dai_data() - set dai private data
> + *
> + * @dev: Pointer to afe device.
> + * @data: dai private data
> + *
> + */
> +void q6afe_set_dai_data(struct device *dev, void *data)
> +{
> + struct q6afe *afe = dev_get_drvdata(dev);
> +
> + afe->dai_data = data;
> +}
> +EXPORT_SYMBOL_GPL(q6afe_set_dai_data);
> +
> +/**
> + * q6afe_get_dai_data() - get dai private data
> + *
> + * @dev: Pointer to afe device.
> + *
> + * Return: pointer to dai private data
> + */
> +void *q6afe_get_dai_data(struct device *dev)
> +{
> + struct q6afe *afe = dev_get_drvdata(dev);
> +
> + return afe->dai_data;
> +}
> +EXPORT_SYMBOL_GPL(q6afe_get_dai_data);
> +
> +/**
> + * q6afe_hdmi_port_prepare() - Prepare hdmi afe port.
> + *
> + * @port: Instance of afe port
> + * @cfg: HDMI configuration for the afe port
> + *
> + */
> +void q6afe_hdmi_port_prepare(struct q6afe_port *port,
> + struct q6afe_hdmi_cfg *cfg)
> +{
> + union afe_port_config *pcfg = &port->port_cfg;
> +
> + pcfg->hdmi_multi_ch.hdmi_cfg_minor_version =
> + AFE_API_VERSION_HDMI_CONFIG;
> + pcfg->hdmi_multi_ch.datatype = cfg->datatype;
> + pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation;
> + pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate;
> + pcfg->hdmi_multi_ch.bit_width = cfg->bit_width;
> +}
> +EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare);
> +
> +/**
> + * q6afe_port_start() - Start a afe port
> + *
> + * @port: Instance of port to start
> + *
> + * Return: Will be an negative on packet size on success.
> + */
> +int q6afe_port_start(struct q6afe_port *port)
> +{
> + return afe_port_start(port, &port->port_cfg);
> +}
> +EXPORT_SYMBOL_GPL(q6afe_port_start);
> +
> +/**
> + * q6afe_port_get_from_id() - Get port instance from a port id
> + *
> + * @dev: Pointer to afe child device.
> + * @id: port id
> + *
> + * Return: Will be an error pointer on error or a valid afe port
> + * on success.
> + */
> +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id)
> +{
> + int port_id;
> + struct q6afe *afe = dev_get_drvdata(dev);
> + struct q6afe_port *port;
> + int cfg_type;
> +
> + if (id < 0 || id > AFE_PORT_MAX) {
> + dev_err(dev, "AFE port token[%d] invalid!\n", id);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + port_id = port_maps[id].port_id;
> +
> + switch (port_id) {
> + case AFE_PORT_ID_MULTICHAN_HDMI_RX:
> + cfg_type = AFE_PARAM_ID_HDMI_CONFIG;
> + break;
> + default:
> + dev_err(dev, "Invalid port id 0x%x\n", port_id);
> + return ERR_PTR(-EINVAL);
> + }
> +
> + port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
> + if (!port)
> + return ERR_PTR(-ENOMEM);
> +
> + init_waitqueue_head(&port->wait);
> +
> + port->token = id;
> + port->id = port_id;
> + port->afe = afe;
> + port->cfg_type = cfg_type;
> +
> + spin_lock(&afe->port_list_lock);
> + list_add_tail(&port->node, &afe->port_list);
> + spin_unlock(&afe->port_list_lock);
> +
> + return port;
> +
> +}
> +EXPORT_SYMBOL_GPL(q6afe_port_get_from_id);
> +
> +/**
> + * q6afe_port_put() - Release port reference
> + *
> + * @port: Instance of port to put
> + */
> +void q6afe_port_put(struct q6afe_port *port)
> +{
> + struct q6afe *afe = port->afe;
> +
> + spin_lock(&afe->port_list_lock);
> + list_del(&port->node);
> + spin_unlock(&afe->port_list_lock);
> +}
> +EXPORT_SYMBOL_GPL(q6afe_port_put);
> +
> +static int q6afev2_probe(struct apr_device *adev)
> +{
> + struct q6afe *afe;
> + struct device *dev = &adev->dev;
> +
> + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL);
> + if (!afe)
> + return -ENOMEM;
> +
> + afe->apr = adev;
> + mutex_init(&afe->lock);
> + afe->dev = dev;
> + INIT_LIST_HEAD(&afe->port_list);
> + spin_lock_init(&afe->port_list_lock);
> +
> + dev_set_drvdata(dev, afe);
> +
> + return q6afe_dai_dev_probe(dev);
> +}
> +
> +static int q6afev2_remove(struct apr_device *adev)
> +{
> + return q6afe_dai_dev_remove(&adev->dev);
> +}
> +
> +static const struct of_device_id q6afe_device_id[] = {
> + { .compatible = "qcom,q6afe" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, q6afe_device_id);
> +
> +static struct apr_driver qcom_q6afe_driver = {
> + .probe = q6afev2_probe,
> + .remove = q6afev2_remove,
> + .callback = afe_callback,
> + .driver = {
> + .name = "qcom-q6afe",
> + .of_match_table = of_match_ptr(q6afe_device_id),
> +
> + },
> +};
> +
> +module_apr_driver(qcom_q6afe_driver);
> +MODULE_DESCRIPTION("Q6 Audio Front End");
> +MODULE_LICENSE("GPL v2");
> diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h
> new file mode 100644
> index 000000000000..43df524f01bb
> --- /dev/null
> +++ b/sound/soc/qcom/qdsp6/q6afe.h
> @@ -0,0 +1,37 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#ifndef __Q6AFE_H__
> +#define __Q6AFE_H__
> +
> +#include <dt-bindings/sound/qcom,q6afe.h>
> +
> +#define AFE_PORT_MAX 9
> +
> +#define MSM_AFE_PORT_TYPE_RX 0
> +#define MSM_AFE_PORT_TYPE_TX 1
> +#define AFE_MAX_PORTS AFE_PORT_MAX
> +
> +struct q6afe_hdmi_cfg {
> + u16 datatype;
> + u16 channel_allocation;
> + u32 sample_rate;
> + u16 bit_width;
> +};
> +
> +struct q6afe_port_config {
> + struct q6afe_hdmi_cfg hdmi;
> +};
> +
> +struct q6afe_port;
> +void q6afe_set_dai_data(struct device *dev, void *data);
> +void *q6afe_get_dai_data(struct device *dev);
> +
> +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id);
> +int q6afe_port_start(struct q6afe_port *port);
> +int q6afe_port_stop(struct q6afe_port *port);
> +void q6afe_port_put(struct q6afe_port *port);
> +int q6afe_get_port_id(int index);
> +void q6afe_hdmi_port_prepare(struct q6afe_port *port,
> + struct q6afe_hdmi_cfg *cfg);
> +
> +#endif /* __Q6AFE_H__ */