new file mode 100644
@@ -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";
+ };
+
+ };
@@ -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.
@@ -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
new file mode 100644
@@ -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");
new file mode 100644
@@ -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_ */