From: Srinivas Kandagatla srinivas.kandagatla@linaro.org
This patch adds support to APR (Asynchronous Packet Router) driver, which is used communication between application processor and QDSP to use services on QDSP like Audio and others.
CC: Andy Gross andy.gross@linaro.org Signed-off-by: Srinivas Kandagatla srinivas.kandagatla@linaro.org --- .../devicetree/bindings/soc/qcom/qcom,apr.txt | 66 ++++ drivers/soc/qcom/Kconfig | 8 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 406 +++++++++++++++++++++ include/linux/soc/qcom/apr.h | 163 +++++++++ 5 files changed, 644 insertions(+) create mode 100644 Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt create mode 100644 drivers/soc/qcom/apr.c create mode 100644 include/linux/soc/qcom/apr.h
diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt new file mode 100644 index 0000000..6fdb8d9 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -0,0 +1,66 @@ +Qualcomm APR (Asynchronous Packet Router) binding + +This binding describes the Qualcomm APR. APR is a IPC protocol for +communication between Application processor and QDSP. APR is mainly +used for audio/voice services on the QDSP. + +- compatible: + Usage: required + Value type: <stringlist> + Definition: must be "qcom,apr-<SOC-NAME>" example: "qcom,apr-msm8996" + + +- qcom,smd-channel: + Usage: required + Value type: <string> + Definition: standard SMD property specifying the SMD channel used for + communication with the APR on QDSP. + Should be "apr_audio_svc". += APR DEVICES +Each subnode of APR node represents services/devices that are only available +when APR is active. + += EXAMPLE +The following example represents a QDSP based sound card on a MSM8996 device +which uses apr as communication between Apps and QDSP. + + apr { + compatible = "qcom,apr-msm8996"; + qcom,smd-channels = "apr_audio_svc"; + + pcm: pcm0 { + compatible = "qcom,msm-pcm-dsp"; + ... + }; + + routing:routing { + compatible = "qcom,msm-pcm-routing"; + #sound-dai-cells = <0>; + ... + }; + + hdmi_dai: dai_hdmi { + compatible = "qcom,msm-dai-q6-hdmi"; + #sound-dai-cells = <0>; + ... + }; + + snd { + compatible = "qcom,snd-apq8096"; + qcom,model = "DB820c"; + ... + }; + + adm { + compatible = "qcom,q6adm"; + }; + + asm { + compatible = "qcom,q6asm"; + }; + + afe: afe { + compatible = "qcom,q6afe-v2"; + }; + + }; diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 6c5ba05..1c0e64a 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -115,3 +115,11 @@ config BUS_TOPOLOGY_ADHOC directionality of connections by explicitly listing device connections thus avoiding illegal routes.
+config QCOM_APR + tristate "Qualcomm APR (Asynchronous Packet Router)" + depends on (RPMSG_QCOM_SMD || RPMSG_QCOM_GLINK_RPM) + help + Enable APR IPC protocol support between + application processor and QDSP6. APR is + used by audio driver to configure QDSP6 + ASM, ADM and AFE modules. diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index a946e41..78fa1d8 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_MSM_BUS_SCALING) += msm_bus/ obj-$(CONFIG_BUS_TOPOLOGY_ADHOC) += msm_bus/ +obj-$(CONFIG_QCOM_APR) += apr.o diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c new file mode 100644 index 0000000..0f10cf2 --- /dev/null +++ b/drivers/soc/qcom/apr.c @@ -0,0 +1,406 @@ +/* Copyright (c) 2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/soc/qcom/apr.h> +#include <linux/rpmsg.h> +#include <linux/of.h> + +struct apr_ops { + int (*get_data_src)(struct apr_hdr *hdr); +}; + +struct apr { + struct rpmsg_endpoint *ch; + struct device *dev; + struct mutex svcs_lock; + struct list_head svcs; + int svc_cnt; + int dest_id; + int client_id; + const struct apr_ops *ops; +}; + +struct apr_svc_table { + char name[64]; + int id; + int client_id; +}; + +static const struct apr_svc_table svc_tbl_qdsp6[] = { + { "AFE", APR_SVC_AFE, APR_CLIENT_AUDIO, }, + { "ASM", APR_SVC_ASM, APR_CLIENT_AUDIO, }, + { "ADM", APR_SVC_ADM, APR_CLIENT_AUDIO, }, + { "CORE", APR_SVC_ADSP_CORE, APR_CLIENT_AUDIO, }, + { "TEST", APR_SVC_TEST_CLIENT, APR_CLIENT_AUDIO, }, + { "MVM", APR_SVC_ADSP_MVM, APR_CLIENT_AUDIO, }, + { "CVS", APR_SVC_ADSP_CVS, APR_CLIENT_AUDIO, }, + { "CVP", APR_SVC_ADSP_CVP, APR_CLIENT_AUDIO, }, + { "USM", APR_SVC_USM, APR_CLIENT_AUDIO, }, + { "VIDC", APR_SVC_VIDC, }, + { "LSM", APR_SVC_LSM, APR_CLIENT_AUDIO, }, +}; + +int apr_send_pkt(void *handle, uint32_t *buf) +{ + struct apr_svc *svc = handle; + struct apr *apr = dev_get_drvdata(svc->dev->parent); + struct apr_hdr *hdr; + unsigned long flags; + int ret; + + if (!handle || !buf) { + dev_err(svc->dev, "APR: Wrong parameters\n"); + return -EINVAL; + } + + spin_lock_irqsave(&svc->w_lock, flags); + + hdr = (struct apr_hdr *)buf; + hdr->src_domain = APR_DOMAIN_APPS; + hdr->src_svc = svc->id; + hdr->dest_domain = svc->dest_domain; + hdr->dest_svc = svc->id; + + ret = rpmsg_send(apr->ch, buf, hdr->pkt_size); + if (ret) { + dev_err(svc->dev, "Unable to send APR pkt %d\n", + hdr->pkt_size); + } else { + ret = hdr->pkt_size; + } + + spin_unlock_irqrestore(&svc->w_lock, flags); + + return ret; +} +EXPORT_SYMBOL_GPL(apr_send_pkt); + +static int apr_find_svc(const char *svc_name, int domain_id, int *client_id, + int *svc_id) +{ + struct apr_svc_table *tbl = (struct apr_svc_table *)&svc_tbl_qdsp6; + int i, size = ARRAY_SIZE(svc_tbl_qdsp6); + + for (i = 0; i < size; i++) { + if (!strcmp(svc_name, tbl[i].name)) { + *client_id = tbl[i].client_id; + *svc_id = tbl[i].id; + return 0; + } + } + + pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name); + return -EINVAL; +} + +struct apr_svc *apr_register(struct device *dev, char *dest, char *svc_name, + apr_fn svc_fn, uint32_t src_port, void *priv) +{ + int client_id = 0; + int svc_id = 0; + int domain_id = 0; + int temp_port = 0; + struct apr_svc *p, *svc = NULL; + struct apr *apr = dev_get_drvdata(dev->parent); + + if (!apr || !dest || !svc_name || !svc_fn) + return NULL; + + if (!strcmp(dest, "ADSP")) { + domain_id = APR_DOMAIN_ADSP; + } else { + dev_err(dev, "APR: wrong destination\n"); + goto done; + } + + if (apr_find_svc(svc_name, domain_id, &client_id, &svc_id)) { + dev_err(dev, "%s: apr_find_svc failed\n", __func__); + goto done; + } + + list_for_each_entry(p, &apr->svcs, node) { + if (svc_id == p->id) { + svc = p; + break; + } + } + + if (!svc) { + svc = kzalloc(sizeof(*svc), GFP_KERNEL); + if (!svc) + return NULL; + + mutex_init(&svc->m_lock); + spin_lock_init(&svc->w_lock); + } + + mutex_lock(&svc->m_lock); + + svc->priv = priv; + svc->id = svc_id; + svc->dest_id = apr->dest_id; + svc->client_id = client_id; + svc->dest_domain = domain_id; + svc->dev = dev; + if (src_port != 0xFFFFFFFF) { + temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF); + if (temp_port >= APR_MAX_PORTS || temp_port < 0) { + mutex_unlock(&svc->m_lock); + return NULL; + } + if (!svc->port_cnt && !svc->svc_cnt) + apr->svc_cnt++; + svc->port_cnt++; + svc->port_fn[temp_port] = svc_fn; + svc->port_priv[temp_port] = priv; + } else { + if (!svc->fn) { + if (!svc->port_cnt && !svc->svc_cnt) + apr->svc_cnt++; + svc->fn = svc_fn; + if (svc->port_cnt) + svc->svc_cnt++; + } + } + + mutex_unlock(&svc->m_lock); + mutex_lock(&apr->svcs_lock); + list_add_tail(&svc->node, &apr->svcs); + mutex_unlock(&apr->svcs_lock); +done: + return svc; +} +EXPORT_SYMBOL_GPL(apr_register); + +static int qcom_rpmsg_q6_callback(struct rpmsg_device *rpdev, void *buf, + int len, void *priv, u32 addr) +{ + struct apr *apr = dev_get_drvdata(&rpdev->dev); + struct apr_client_data data; + struct apr_svc *p, *c_svc = NULL; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t src; + uint16_t svc; + int temp_port = 0; + + if (!buf || len <= APR_HDR_SIZE) { + pr_info("APR: Improper apr pkt received:%p %d\n", buf, len); + return -EINVAL; + } + + hdr = buf; + ver = (hdr->hdr_field) & 0x000F; + if (ver > APR_PKT_VER + 1) { + pr_info("APR: Wrong version: %d\n", ver); + return -EINVAL; + } + + hdr_size = hdr->hdr_field; + hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4; + if (hdr_size < APR_HDR_SIZE) { + dev_err(apr->dev, "APR: Wrong hdr size:%d\n", hdr_size); + return -EINVAL; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + dev_err(apr->dev, "APR: Wrong paket size\n"); + return -EINVAL; + } + msg_type = hdr->hdr_field; + msg_type = (msg_type >> 0x08) & 0x0003; + if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) { + dev_err(apr->dev, "APR: Wrong message type: %d\n", msg_type); + return -EINVAL; + } + + if (hdr->src_domain >= APR_DOMAIN_MAX || + hdr->dest_domain >= APR_DOMAIN_MAX || + hdr->src_svc >= APR_SVC_MAX || hdr->dest_svc >= APR_SVC_MAX) { + dev_err(apr->dev, "APR: Wrong APR header\n"); + return -EINVAL; + } + + svc = hdr->dest_svc; + src = apr->ops->get_data_src(hdr); + if (src == APR_DEST_MAX) + return -EINVAL; + + list_for_each_entry(p, &apr->svcs, node) { + if (svc == p->id) { + c_svc = p; + break; + } + } + + if (!c_svc) { + dev_err(apr->dev, "APR: service is not registered\n"); + return -EINVAL; + } + + data.payload_size = hdr->pkt_size - hdr_size; + data.opcode = hdr->opcode; + data.src = src; + data.src_port = hdr->src_port; + data.dest_port = hdr->dest_port; + data.token = hdr->token; + data.msg_type = msg_type; + + if (data.payload_size > 0) + data.payload = (char *)hdr + hdr_size; + + temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF); + if (c_svc->port_cnt && c_svc->port_fn[temp_port]) + c_svc->port_fn[temp_port] (&data, c_svc->port_priv[temp_port]); + else if (c_svc->fn) + c_svc->fn(&data, c_svc->priv); + else + dev_err(apr->dev, "APR: Rxed a packet for NULL callback\n"); + + return 0; +} + +int apr_deregister(void *handle) +{ + struct apr_svc *svc = handle; + struct apr *apr = dev_get_drvdata(svc->dev->parent); + uint16_t client_id; + + if (!handle) + return -EINVAL; + + mutex_lock(&svc->m_lock); + client_id = svc->client_id; + + if (svc->port_cnt > 0 || svc->svc_cnt > 0) { + if (svc->port_cnt) + svc->port_cnt--; + else if (svc->svc_cnt) + svc->svc_cnt--; + if (!svc->port_cnt && !svc->svc_cnt) + apr->svc_cnt--; + } else if (apr->svc_cnt > 0) { + apr->svc_cnt--; + } + + if (!svc->port_cnt && !svc->svc_cnt) { + mutex_unlock(&svc->m_lock); + + mutex_lock(&apr->svcs_lock); + list_del(&svc->node); + mutex_unlock(&apr->svcs_lock); + kfree(svc); + return 0; + } + + mutex_unlock(&svc->m_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(apr_deregister); + +static int qcom_rpmsg_q6_probe(struct rpmsg_device *rpdev) +{ + struct device *dev = &rpdev->dev; + const char *name; + struct apr *apr; + int ret; + + apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL); + if (!apr) + return -ENOMEM; + + apr->ops = of_device_get_match_data(dev); + if (!apr->ops) + return -ENODEV; + + ret = of_property_read_string(dev->of_node, "qcom,smd-channels", &name); + if (ret) { + dev_err(dev, "qcom,smd-channels name not found\n"); + return -EINVAL; + } + + if (!strcmp(name, "apr_audio_svc")) { + apr->client_id = APR_CLIENT_AUDIO; + } else { + dev_err(dev, "Unsupported srv name\n"); + return -EINVAL; + } + + ret = of_property_read_u32(dev->parent->of_node, "qcom,smd-edge", + &apr->dest_id); + if (ret) { + dev_err(dev, "qcom,smd-edge not found\n"); + return -EINVAL; + } + + dev_set_drvdata(dev, apr); + apr->ch = rpdev->ept; + apr->dev = dev; + INIT_LIST_HEAD(&apr->svcs); + + dev_info(dev, "APR service up for apr id %d dest id %d\n", + apr->client_id, apr->dest_id); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static void qcom_rpmsg_q6_remove(struct rpmsg_device *rpdev) +{ + of_platform_depopulate(&rpdev->dev); +} + +static int apr_v2_get_data_src(struct apr_hdr *hdr) +{ + if (hdr->src_domain == APR_DOMAIN_MODEM) + return APR_DEST_MODEM; + else if (hdr->src_domain == APR_DOMAIN_ADSP) + return APR_DEST_QDSP6; + + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + + return APR_DEST_MAX; +} + +static const struct apr_ops apr_v2_ops = { + .get_data_src = apr_v2_get_data_src, +}; + +static const struct of_device_id qcom_rpmsg_q6_of_match[] = { + { .compatible = "qcom,apr-msm8996", .data = &apr_v2_ops}, + {} +}; + +static struct rpmsg_driver qcom_rpmsg_q6_driver = { + .probe = qcom_rpmsg_q6_probe, + .remove = qcom_rpmsg_q6_remove, + .callback = qcom_rpmsg_q6_callback, + .drv = { + .name = "qcom_rpmsg_q6", + .owner = THIS_MODULE, + .of_match_table = qcom_rpmsg_q6_of_match, + }, +}; + +module_rpmsg_driver(qcom_rpmsg_q6_driver); + +MODULE_DESCRIPTION("Qualcomm rpmsg backed apr driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h new file mode 100644 index 0000000..02175e6 --- /dev/null +++ b/include/linux/soc/qcom/apr.h @@ -0,0 +1,163 @@ +/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __APR_H_ +#define __APR_H_ + +#include <linux/mutex.h> + +/* APR Client IDs */ +#define APR_CLIENT_AUDIO 0x0 +#define APR_CLIENT_VOICE 0x1 +#define APR_CLIENT_MAX 0x2 + +#define APR_DL_SMD 0 +#define APR_DL_MAX 1 + +#define APR_DEST_MODEM 0 +#define APR_DEST_QDSP6 1 +#define APR_DEST_MAX 2 +#define APR_MAX_BUF 8192 + +#define APR_HDR_LEN(hdr_len) ((hdr_len)/4) +#define APR_PKT_SIZE(hdr_len, payload_len) ((hdr_len) + (payload_len)) +#define APR_HDR_FIELD(msg_type, hdr_len, ver)\ + (((msg_type & 0x3) << 8) | ((hdr_len & 0xF) << 4) | (ver & 0xF)) + +#define APR_HDR_SIZE sizeof(struct apr_hdr) +#define APR_SEQ_CMD_HDR_FIELD APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, \ + APR_HDR_LEN(APR_HDR_SIZE), \ + APR_PKT_VER) + +/* Version */ +#define APR_PKT_VER 0x0 + +/* Command and Response Types */ +#define APR_MSG_TYPE_EVENT 0x0 +#define APR_MSG_TYPE_CMD_RSP 0x1 +#define APR_MSG_TYPE_SEQ_CMD 0x2 +#define APR_MSG_TYPE_NSEQ_CMD 0x3 +#define APR_MSG_TYPE_MAX 0x04 + +/* APR Basic Response Message */ +#define APR_BASIC_RSP_RESULT 0x000110E8 +#define APR_RSP_ACCEPTED 0x000100BE + +/* Domain IDs */ +#define APR_DOMAIN_SIM 0x1 +#define APR_DOMAIN_PC 0x2 +#define APR_DOMAIN_MODEM 0x3 +#define APR_DOMAIN_ADSP 0x4 +#define APR_DOMAIN_APPS 0x5 +#define APR_DOMAIN_MAX 0x6 + +/* ADSP service IDs */ +#define APR_SVC_TEST_CLIENT 0x2 +#define APR_SVC_ADSP_CORE 0x3 +#define APR_SVC_AFE 0x4 +#define APR_SVC_VSM 0x5 +#define APR_SVC_VPM 0x6 +#define APR_SVC_ASM 0x7 +#define APR_SVC_ADM 0x8 +#define APR_SVC_ADSP_MVM 0x09 +#define APR_SVC_ADSP_CVS 0x0A +#define APR_SVC_ADSP_CVP 0x0B +#define APR_SVC_USM 0x0C +#define APR_SVC_LSM 0x0D +#define APR_SVC_VIDC 0x16 +#define APR_SVC_MAX 0x17 + +/* Modem Service IDs */ +#define APR_SVC_MVS 0x3 +#define APR_SVC_MVM 0x4 +#define APR_SVC_CVS 0x5 +#define APR_SVC_CVP 0x6 +#define APR_SVC_SRD 0x7 + +/* APR Port IDs */ +#define APR_MAX_PORTS 0x80 +#define APR_NAME_MAX 0x40 +#define RESET_EVENTS 0x000130D7 + +struct apr_hdr { + uint16_t hdr_field; + uint16_t pkt_size; + uint8_t src_svc; + uint8_t src_domain; + uint16_t src_port; + uint8_t dest_svc; + uint8_t dest_domain; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; +}; + +struct apr_client_data { + uint16_t payload_size; + uint16_t hdr_len; + uint16_t msg_type; + uint16_t src; + uint16_t dest_svc; + uint16_t src_port; + uint16_t dest_port; + uint32_t token; + uint32_t opcode; + void *payload; +}; + +typedef int32_t (*apr_fn) (struct apr_client_data *data, void *priv); +struct apr_svc { + uint16_t id; + uint16_t dest_id; + uint16_t client_id; + uint16_t dest_domain; + uint8_t rvd; + uint8_t port_cnt; + uint8_t svc_cnt; + + apr_fn port_fn[APR_MAX_PORTS]; + void *port_priv[APR_MAX_PORTS]; + apr_fn fn; + void *priv; + struct mutex m_lock; + spinlock_t w_lock; + struct device *dev; + struct list_head node; +}; + +#if IS_ENABLED(CONFIG_QCOM_APR) +struct apr_svc *apr_register(struct device *dev, char *dest, char *svc_name, + apr_fn svc_fn, uint32_t src_port, void *priv); +int apr_send_pkt(void *handle, uint32_t *buf); +int apr_deregister(void *handle); + +#else + +static inline struct apr_svc *apr_register(struct device *dev, char *dest, + char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv) +{ + return ERR_PTR(-ENOSYS); +} + +static inline int apr_send_pkt(void *handle, uint32_t *buf) +{ + return -ENOSYS; +} + +static inline int apr_deregister(void *handle) +{ + return -ENOSYS; +} + +#endif /* CONFIG_QCOM_APR */ +#endif /* __APR_H_ */