diff options
author | Sudheer Papothi <spapothi@codeaurora.org> | 2016-03-02 01:56:43 +0530 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-23 20:11:25 -0700 |
commit | be1a516dcb8571becec57f8965ca5abfdf7da092 (patch) | |
tree | 4f4a5fe235b9e5bef10b5aa764f31f6049a88da2 /drivers/soc | |
parent | 26c32e7dad6124d7d726ad17e8c661376cf10d4c (diff) |
ASoC: msm: Add Audio drivers for MSM targets
Add snapshot for audio drivers for MSM targets. The code is
migrated from msm-3.18 kernel at the below commit/AU level -
AU_LINUX_ANDROID_LA.HB.1.3.1.06.00.00.187.056
(e70ad0cd5efdd9dc91a77dcdac31d6132e1315c1)
(Promotion of kernel.lnx.3.18-151201.)
Signed-off-by: Sudheer Papothi <spapothi@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/Makefile | 4 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/adsp-loader.c | 305 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/apr.c | 893 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/apr_tal.c | 287 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/apr_v2.c | 70 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/apr_v3.c | 53 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/msm_audio_ion.c | 920 | ||||
-rw-r--r-- | drivers/soc/qcom/qdsp6v2/voice_svc.c | 759 |
10 files changed, 3293 insertions, 0 deletions
diff --git a/drivers/soc/Makefile b/drivers/soc/Makefile index f2ba2e932ae1..6358d1256bb1 100644 --- a/drivers/soc/Makefile +++ b/drivers/soc/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_ARCH_SUNXI) += sunxi/ obj-$(CONFIG_ARCH_TEGRA) += tegra/ obj-$(CONFIG_SOC_TI) += ti/ obj-$(CONFIG_PLAT_VERSATILE) += versatile/ +obj-$(CONFIG_ARCH_QCOM) += qcom/ diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 36cdfdfab62f..7105f4993394 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_MSM_IPC_ROUTER_SMD_XPRT) += ipc_router_smd_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_HSIC_XPRT) += ipc_router_hsic_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_MHI_XPRT) += ipc_router_mhi_xprt.o obj-$(CONFIG_MSM_IPC_ROUTER_GLINK_XPRT) += ipc_router_glink_xprt.o +obj-y += qdsp6v2/ obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor_v01.o obj-$(CONFIG_MSM_SYSTEM_HEALTH_MONITOR) += system_health_monitor.o obj-$(CONFIG_MSM_GLINK_PKT) += msm_glink_pkt.o diff --git a/drivers/soc/qcom/qdsp6v2/Makefile b/drivers/soc/qcom/qdsp6v2/Makefile new file mode 100644 index 000000000000..e31a72fee03d --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_MSM_QDSP6_APRV2) += apr.o apr_v2.o apr_tal.o voice_svc.o +obj-$(CONFIG_MSM_QDSP6_APRV3) += apr.o apr_v3.o apr_tal.o voice_svc.o +obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += msm_audio_ion.o +obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o diff --git a/drivers/soc/qcom/qdsp6v2/adsp-loader.c b/drivers/soc/qcom/qdsp6v2/adsp-loader.c new file mode 100644 index 000000000000..51539a36a74f --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/adsp-loader.c @@ -0,0 +1,305 @@ +/* + * Copyright (c) 2012-2014, 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/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/of_device.h> +#include <linux/sysfs.h> +#include <soc/qcom/subsystem_restart.h> + +#define Q6_PIL_GET_DELAY_MS 100 +#define BOOT_CMD 1 +#define IMAGE_UNLOAD_CMD 0 + +static ssize_t adsp_boot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count); + +struct adsp_loader_private { + void *pil_h; + struct kobject *boot_adsp_obj; + struct attribute_group *attr_group; +}; + +static struct kobj_attribute adsp_boot_attribute = + __ATTR(boot, 0220, NULL, adsp_boot_store); + +static struct attribute *attrs[] = { + &adsp_boot_attribute.attr, + NULL, +}; + +static struct platform_device *adsp_private; +static void adsp_loader_unload(struct platform_device *pdev); + +static void adsp_loader_do(struct platform_device *pdev) +{ + + struct adsp_loader_private *priv = NULL; + + const char *adsp_dt = "qcom,adsp-state"; + int rc = 0; + u32 adsp_state; + const char *img_name; + + if (!pdev) { + dev_err(&pdev->dev, "%s: Platform device null\n", __func__); + goto fail; + } + + if (!pdev->dev.of_node) { + dev_err(&pdev->dev, + "%s: Device tree information missing\n", __func__); + goto fail; + } + + rc = of_property_read_u32(pdev->dev.of_node, adsp_dt, &adsp_state); + if (rc) { + dev_err(&pdev->dev, + "%s: ADSP state = %x\n", __func__, adsp_state); + goto fail; + } + + rc = of_property_read_string(pdev->dev.of_node, + "qcom,proc-img-to-load", + &img_name); + + if (rc) { + dev_dbg(&pdev->dev, + "%s: loading default image ADSP\n", __func__); + goto load_adsp; + } + if (!strcmp(img_name, "modem")) { + /* adsp_state always returns "0". So load modem image based on + apr_modem_state to prevent loading of image twice */ + adsp_state = apr_get_modem_state(); + if (adsp_state == APR_SUBSYS_DOWN) { + priv = platform_get_drvdata(pdev); + if (!priv) { + dev_err(&pdev->dev, + " %s: Private data get failed\n", __func__); + goto fail; + } + + priv->pil_h = subsystem_get("modem"); + if (IS_ERR(priv->pil_h)) { + dev_err(&pdev->dev, "%s: pil get failed,\n", + __func__); + goto fail; + } + + /* Set the state of the ADSP in APR driver */ + apr_set_modem_state(APR_SUBSYS_LOADED); + } else if (adsp_state == APR_SUBSYS_LOADED) { + dev_dbg(&pdev->dev, + "%s: MDSP state = %x\n", __func__, adsp_state); + } + + dev_dbg(&pdev->dev, "%s: Q6/MDSP image is loaded\n", __func__); + return; + } +load_adsp: + { + adsp_state = apr_get_q6_state(); + if (adsp_state == APR_SUBSYS_DOWN) { + priv = platform_get_drvdata(pdev); + if (!priv) { + dev_err(&pdev->dev, + " %s: Private data get failed\n", __func__); + goto fail; + } + + priv->pil_h = subsystem_get("adsp"); + if (IS_ERR(priv->pil_h)) { + dev_err(&pdev->dev, "%s: pil get failed,\n", + __func__); + goto fail; + } + + /* Set the state of the ADSP in APR driver */ + apr_set_q6_state(APR_SUBSYS_LOADED); + } else if (adsp_state == APR_SUBSYS_LOADED) { + dev_dbg(&pdev->dev, + "%s: ADSP state = %x\n", __func__, adsp_state); + } + + dev_dbg(&pdev->dev, "%s: Q6/ADSP image is loaded\n", __func__); + return; + } +fail: + dev_err(&pdev->dev, "%s: Q6 image loading failed\n", __func__); + return; +} + + +static ssize_t adsp_boot_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, + size_t count) +{ + int boot = 0; + sscanf(buf, "%du", &boot); + + if (boot == BOOT_CMD) { + pr_debug("%s: going to call adsp_loader_do\n", __func__); + adsp_loader_do(adsp_private); + } else if (boot == IMAGE_UNLOAD_CMD) { + pr_debug("%s: going to call adsp_unloader\n", __func__); + adsp_loader_unload(adsp_private); + } + return count; +} + +static void adsp_loader_unload(struct platform_device *pdev) +{ + struct adsp_loader_private *priv = NULL; + + priv = platform_get_drvdata(pdev); + + if (!priv) + return; + + if (priv->pil_h) { + dev_dbg(&pdev->dev, "%s: calling subsystem put\n", __func__); + subsystem_put(priv->pil_h); + priv->pil_h = NULL; + } +} + +static int adsp_loader_init_sysfs(struct platform_device *pdev) +{ + int ret = -EINVAL; + struct adsp_loader_private *priv = NULL; + adsp_private = NULL; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(&pdev->dev, "%s: memory alloc failed\n", __func__); + ret = -ENOMEM; + return ret; + } + + platform_set_drvdata(pdev, priv); + + priv->pil_h = NULL; + priv->boot_adsp_obj = NULL; + priv->attr_group = devm_kzalloc(&pdev->dev, + sizeof(*(priv->attr_group)), + GFP_KERNEL); + if (!priv->attr_group) { + dev_err(&pdev->dev, "%s: malloc attr_group failed\n", + __func__); + ret = -ENOMEM; + goto error_return; + } + + priv->attr_group->attrs = attrs; + + priv->boot_adsp_obj = kobject_create_and_add("boot_adsp", kernel_kobj); + if (!priv->boot_adsp_obj) { + dev_err(&pdev->dev, "%s: sysfs create and add failed\n", + __func__); + ret = -ENOMEM; + goto error_return; + } + + ret = sysfs_create_group(priv->boot_adsp_obj, priv->attr_group); + if (ret) { + dev_err(&pdev->dev, "%s: sysfs create group failed %d\n", + __func__, ret); + goto error_return; + } + + adsp_private = pdev; + + return 0; + +error_return: + + if (priv->boot_adsp_obj) { + kobject_del(priv->boot_adsp_obj); + priv->boot_adsp_obj = NULL; + } + + return ret; +} + +static int adsp_loader_remove(struct platform_device *pdev) +{ + struct adsp_loader_private *priv = NULL; + + priv = platform_get_drvdata(pdev); + + if (!priv) + return 0; + + if (priv->pil_h) { + subsystem_put(priv->pil_h); + priv->pil_h = NULL; + } + + if (priv->boot_adsp_obj) { + sysfs_remove_group(priv->boot_adsp_obj, priv->attr_group); + kobject_del(priv->boot_adsp_obj); + priv->boot_adsp_obj = NULL; + } + + return 0; +} + +static int adsp_loader_probe(struct platform_device *pdev) +{ + int ret = adsp_loader_init_sysfs(pdev); + if (ret != 0) { + dev_err(&pdev->dev, "%s: Error in initing sysfs\n", __func__); + return ret; + } + + return 0; +} + +static const struct of_device_id adsp_loader_dt_match[] = { + { .compatible = "qcom,adsp-loader" }, + { } +}; +MODULE_DEVICE_TABLE(of, adsp_loader_dt_match); + +static struct platform_driver adsp_loader_driver = { + .driver = { + .name = "adsp-loader", + .owner = THIS_MODULE, + .of_match_table = adsp_loader_dt_match, + }, + .probe = adsp_loader_probe, + .remove = adsp_loader_remove, +}; + +static int __init adsp_loader_init(void) +{ + return platform_driver_register(&adsp_loader_driver); +} +module_init(adsp_loader_init); + +static void __exit adsp_loader_exit(void) +{ + platform_driver_unregister(&adsp_loader_driver); +} +module_exit(adsp_loader_exit); + +MODULE_DESCRIPTION("ADSP Loader module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qdsp6v2/apr.c b/drivers/soc/qcom/qdsp6v2/apr.c new file mode 100644 index 000000000000..2da60f96d167 --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr.c @@ -0,0 +1,893 @@ +/* Copyright (c) 2010-2015, 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/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/subsystem_notif.h> +#include <soc/qcom/scm.h> +#include <sound/apr_audio-v2.h> +#include <soc/qcom/smd.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/dsp_debug.h> + +#define SCM_Q6_NMI_CMD 0x1 + +static struct apr_q6 q6; +static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX]; + +static wait_queue_head_t dsp_wait; +static wait_queue_head_t modem_wait; +static bool is_modem_up; +/* Subsystem restart: QDSP6 data, functions */ +static struct workqueue_struct *apr_reset_workqueue; +static void apr_reset_deregister(struct work_struct *work); +struct apr_reset_work { + void *handle; + struct work_struct work; +}; + +struct apr_svc_table { + char name[64]; + int idx; + int id; + int client_id; +}; + +static const struct apr_svc_table svc_tbl_qdsp6[] = { + { + .name = "AFE", + .idx = 0, + .id = APR_SVC_AFE, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "ASM", + .idx = 1, + .id = APR_SVC_ASM, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "ADM", + .idx = 2, + .id = APR_SVC_ADM, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "CORE", + .idx = 3, + .id = APR_SVC_ADSP_CORE, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "TEST", + .idx = 4, + .id = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "MVM", + .idx = 5, + .id = APR_SVC_ADSP_MVM, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "CVS", + .idx = 6, + .id = APR_SVC_ADSP_CVS, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "CVP", + .idx = 7, + .id = APR_SVC_ADSP_CVP, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "USM", + .idx = 8, + .id = APR_SVC_USM, + .client_id = APR_CLIENT_AUDIO, + }, + { + .name = "VIDC", + .idx = 9, + .id = APR_SVC_VIDC, + }, + { + .name = "LSM", + .idx = 10, + .id = APR_SVC_LSM, + .client_id = APR_CLIENT_AUDIO, + }, +}; + +static struct apr_svc_table svc_tbl_voice[] = { + { + .name = "VSM", + .idx = 0, + .id = APR_SVC_VSM, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "VPM", + .idx = 1, + .id = APR_SVC_VPM, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "MVS", + .idx = 2, + .id = APR_SVC_MVS, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "MVM", + .idx = 3, + .id = APR_SVC_MVM, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "CVS", + .idx = 4, + .id = APR_SVC_CVS, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "CVP", + .idx = 5, + .id = APR_SVC_CVP, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "SRD", + .idx = 6, + .id = APR_SVC_SRD, + .client_id = APR_CLIENT_VOICE, + }, + { + .name = "TEST", + .idx = 7, + .id = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_VOICE, + }, +}; + +enum apr_subsys_state apr_get_modem_state(void) +{ + return atomic_read(&q6.modem_state); +} + +void apr_set_modem_state(enum apr_subsys_state state) +{ + atomic_set(&q6.modem_state, state); +} + +enum apr_subsys_state apr_cmpxchg_modem_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.modem_state, prev, new); +} + +enum apr_subsys_state apr_get_q6_state(void) +{ + return atomic_read(&q6.q6_state); +} +EXPORT_SYMBOL_GPL(apr_get_q6_state); + +int apr_set_q6_state(enum apr_subsys_state state) +{ + pr_debug("%s: setting adsp state %d\n", __func__, state); + if (state < APR_SUBSYS_DOWN || state > APR_SUBSYS_LOADED) + return -EINVAL; + atomic_set(&q6.q6_state, state); + return 0; +} +EXPORT_SYMBOL_GPL(apr_set_q6_state); + +enum apr_subsys_state apr_cmpxchg_q6_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.q6_state, prev, new); +} + +int apr_wait_for_device_up(int dest_id) +{ + int rc = -1; + if (dest_id == APR_DEST_MODEM) + rc = wait_event_interruptible_timeout(modem_wait, + (apr_get_modem_state() == APR_SUBSYS_UP), + (1 * HZ)); + else if (dest_id == APR_DEST_QDSP6) + rc = wait_event_interruptible_timeout(dsp_wait, + (apr_get_q6_state() == APR_SUBSYS_UP), + (1 * HZ)); + else + pr_err("%s: unknown dest_id %d\n", __func__, dest_id); + /* returns left time */ + return rc; +} + +int apr_load_adsp_image(void) +{ + int rc = 0; + mutex_lock(&q6.lock); + if (apr_get_q6_state() == APR_SUBSYS_UP) { + q6.pil = subsystem_get("adsp"); + if (IS_ERR(q6.pil)) { + rc = PTR_ERR(q6.pil); + pr_err("APR: Unable to load q6 image, error:%d\n", rc); + } else { + apr_set_q6_state(APR_SUBSYS_LOADED); + pr_debug("APR: Image is loaded, stated\n"); + } + } else if (apr_get_q6_state() == APR_SUBSYS_LOADED) { + pr_debug("APR: q6 image already loaded\n"); + } else { + pr_debug("APR: cannot load state %d\n", apr_get_q6_state()); + } + mutex_unlock(&q6.lock); + return rc; +} + +struct apr_client *apr_get_client(int dest_id, int client_id) +{ + return &client[dest_id][client_id]; +} + +int apr_send_pkt(void *handle, uint32_t *buf) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + struct apr_hdr *hdr; + uint16_t dest_id; + uint16_t client_id; + uint16_t w_len; + unsigned long flags; + + if (!handle || !buf) { + pr_err("APR: Wrong parameters\n"); + return -EINVAL; + } + if (svc->need_reset) { + pr_err("apr: send_pkt service need reset\n"); + return -ENETRESET; + } + + if ((svc->dest_id == APR_DEST_QDSP6) && + (apr_get_q6_state() != APR_SUBSYS_LOADED)) { + pr_err("%s: Still dsp is not Up\n", __func__); + return -ENETRESET; + } else if ((svc->dest_id == APR_DEST_MODEM) && + (apr_get_modem_state() == APR_SUBSYS_DOWN)) { + pr_err("apr: Still Modem is not Up\n"); + return -ENETRESET; + } + + spin_lock_irqsave(&svc->w_lock, flags); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][client_id]; + + if (!client[dest_id][client_id].handle) { + pr_err("APR: Still service is not yet opened\n"); + spin_unlock_irqrestore(&svc->w_lock, flags); + return -EINVAL; + } + 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; + + w_len = apr_tal_write(clnt->handle, buf, hdr->pkt_size); + if (w_len != hdr->pkt_size) + pr_err("Unable to write APR pkt successfully: %d\n", w_len); + spin_unlock_irqrestore(&svc->w_lock, flags); + + return w_len; +} + +struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv) +{ + struct apr_client *clnt; + int client_id = 0; + int svc_idx = 0; + int svc_id = 0; + int dest_id = 0; + int domain_id = 0; + int temp_port = 0; + struct apr_svc *svc = NULL; + int rc = 0; + + if (!dest || !svc_name || !svc_fn) + return NULL; + + if (!strcmp(dest, "ADSP")) + domain_id = APR_DOMAIN_ADSP; + else if (!strcmp(dest, "MODEM")) { + /* Register voice services if destination permits */ + if (!apr_register_voice_svc()) + goto done; + domain_id = APR_DOMAIN_MODEM; + } else { + pr_err("APR: wrong destination\n"); + goto done; + } + + dest_id = apr_get_dest_id(dest); + + if (dest_id == APR_DEST_QDSP6) { + if (apr_get_q6_state() != APR_SUBSYS_LOADED) { + pr_err("%s: adsp not up\n", __func__); + return NULL; + } + pr_debug("%s: adsp Up\n", __func__); + } else if (dest_id == APR_DEST_MODEM) { + if (apr_get_modem_state() == APR_SUBSYS_DOWN) { + if (is_modem_up) { + pr_err("%s: modem shutdown due to SSR, ret", + __func__); + return NULL; + } + pr_debug("%s: Wait for modem to bootup\n", __func__); + rc = apr_wait_for_device_up(APR_DEST_MODEM); + if (rc == 0) { + pr_err("%s: Modem is not Up\n", __func__); + return NULL; + } + } + pr_debug("%s: modem Up\n", __func__); + } + + if (apr_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id)) { + pr_err("%s: apr_get_svc failed\n", __func__); + goto done; + } + + clnt = &client[dest_id][client_id]; + mutex_lock(&clnt->m_lock); + if (!clnt->handle) { + clnt->handle = apr_tal_open(client_id, dest_id, + APR_DL_SMD, apr_cb_func, NULL); + if (!clnt->handle) { + svc = NULL; + pr_err("APR: Unable to open handle\n"); + mutex_unlock(&clnt->m_lock); + goto done; + } + } + mutex_unlock(&clnt->m_lock); + svc = &clnt->svc[svc_idx]; + mutex_lock(&svc->m_lock); + clnt->id = client_id; + if (svc->need_reset) { + mutex_unlock(&svc->m_lock); + pr_err("APR: Service needs reset\n"); + goto done; + } + svc->id = svc_id; + svc->dest_id = dest_id; + svc->client_id = client_id; + svc->dest_domain = domain_id; + if (src_port != 0xFFFFFFFF) { + temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF); + pr_debug("port = %d t_port = %d\n", src_port, temp_port); + if (temp_port >= APR_MAX_PORTS || temp_port < 0) { + pr_err("APR: temp_port out of bounds\n"); + mutex_unlock(&svc->m_lock); + return NULL; + } + if (!svc->port_cnt && !svc->svc_cnt) + clnt->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) + clnt->svc_cnt++; + svc->fn = svc_fn; + if (svc->port_cnt) + svc->svc_cnt++; + svc->priv = priv; + } + } + + mutex_unlock(&svc->m_lock); +done: + return svc; +} + + +void apr_cb_func(void *buf, int len, void *priv) +{ + struct apr_client_data data; + struct apr_client *apr_client; + struct apr_svc *c_svc; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t src; + uint16_t svc; + uint16_t clnt; + int i; + int temp_port = 0; + uint32_t *ptr; + + pr_debug("APR2: len = %d\n", len); + ptr = buf; + pr_debug("\n*****************\n"); + for (i = 0; i < len/4; i++) + pr_debug("%x ", ptr[i]); + pr_debug("\n"); + pr_debug("\n*****************\n"); + + if (!buf || len <= APR_HDR_SIZE) { + pr_err("APR: Improper apr pkt received:%p %d\n", buf, len); + return; + } + hdr = buf; + + ver = hdr->hdr_field; + ver = (ver & 0x000F); + if (ver > APR_PKT_VER + 1) { + pr_err("APR: Wrong version: %d\n", ver); + return; + } + + hdr_size = hdr->hdr_field; + hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4; + if (hdr_size < APR_HDR_SIZE) { + pr_err("APR: Wrong hdr size:%d\n", hdr_size); + return; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + pr_err("APR: Wrong paket size\n"); + return; + } + msg_type = hdr->hdr_field; + msg_type = (msg_type >> 0x08) & 0x0003; + if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) { + pr_err("APR: Wrong message type: %d\n", msg_type); + return; + } + + 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) { + pr_err("APR: Wrong APR header\n"); + return; + } + + svc = hdr->dest_svc; + if (hdr->src_domain == APR_DOMAIN_MODEM) { + if (svc == APR_SVC_MVS || svc == APR_SVC_MVM || + svc == APR_SVC_CVS || svc == APR_SVC_CVP || + svc == APR_SVC_TEST_CLIENT) + clnt = APR_CLIENT_VOICE; + else { + pr_err("APR: Wrong svc :%d\n", svc); + return; + } + } else if (hdr->src_domain == APR_DOMAIN_ADSP) { + if (svc == APR_SVC_AFE || svc == APR_SVC_ASM || + svc == APR_SVC_VSM || svc == APR_SVC_VPM || + svc == APR_SVC_ADM || svc == APR_SVC_ADSP_CORE || + svc == APR_SVC_USM || + svc == APR_SVC_TEST_CLIENT || svc == APR_SVC_ADSP_MVM || + svc == APR_SVC_ADSP_CVS || svc == APR_SVC_ADSP_CVP || + svc == APR_SVC_LSM) + clnt = APR_CLIENT_AUDIO; + else if (svc == APR_SVC_VIDC) + clnt = APR_CLIENT_AUDIO; + else { + pr_err("APR: Wrong svc :%d\n", svc); + return; + } + } else { + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + return; + } + + src = apr_get_data_src(hdr); + if (src == APR_DEST_MAX) + return; + + pr_debug("src =%d clnt = %d\n", src, clnt); + apr_client = &client[src][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) + if (apr_client->svc[i].id == svc) { + pr_debug("%d\n", apr_client->svc[i].id); + c_svc = &apr_client->svc[i]; + break; + } + + if (i == APR_SVC_MAX) { + pr_err("APR: service is not registered\n"); + return; + } + pr_debug("svc_idx = %d\n", i); + pr_debug("%x %x %x %p %p\n", c_svc->id, c_svc->dest_id, + c_svc->client_id, c_svc->fn, c_svc->priv); + 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); + pr_debug("port = %d t_port = %d\n", data.src_port, temp_port); + 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 + pr_err("APR: Rxed a packet for NULL callback\n"); +} + +int apr_get_svc(const char *svc_name, int domain_id, int *client_id, + int *svc_idx, int *svc_id) +{ + int i; + int size; + struct apr_svc_table *tbl; + int ret = 0; + + if ((domain_id == APR_DOMAIN_ADSP)) { + tbl = (struct apr_svc_table *)&svc_tbl_qdsp6; + size = ARRAY_SIZE(svc_tbl_qdsp6); + } else { + tbl = (struct apr_svc_table *)&svc_tbl_voice; + size = ARRAY_SIZE(svc_tbl_voice); + } + + for (i = 0; i < size; i++) { + if (!strcmp(svc_name, tbl[i].name)) { + *client_id = tbl[i].client_id; + *svc_idx = tbl[i].idx; + *svc_id = tbl[i].id; + break; + } + } + + pr_debug("%s: svc_name = %s c_id = %d domain_id = %d\n", + __func__, svc_name, *client_id, domain_id); + if (i == size) { + pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name); + ret = -EINVAL; + } + + return ret; +} + +static void apr_reset_deregister(struct work_struct *work) +{ + struct apr_svc *handle = NULL; + struct apr_reset_work *apr_reset = + container_of(work, struct apr_reset_work, work); + + handle = apr_reset->handle; + pr_debug("%s:handle[%p]\n", __func__, handle); + apr_deregister(handle); + kfree(apr_reset); +} + +int apr_deregister(void *handle) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + uint16_t dest_id; + uint16_t client_id; + + if (!handle) + return -EINVAL; + + mutex_lock(&svc->m_lock); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][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) { + client[dest_id][client_id].svc_cnt--; + svc->need_reset = 0x0; + } + } else if (client[dest_id][client_id].svc_cnt > 0) { + client[dest_id][client_id].svc_cnt--; + if (!client[dest_id][client_id].svc_cnt) { + svc->need_reset = 0x0; + pr_debug("%s: service is reset %p\n", __func__, svc); + } + } + + if (!svc->port_cnt && !svc->svc_cnt) { + svc->priv = NULL; + svc->id = 0; + svc->fn = NULL; + svc->dest_id = 0; + svc->client_id = 0; + svc->need_reset = 0x0; + } + if (client[dest_id][client_id].handle && + !client[dest_id][client_id].svc_cnt) { + apr_tal_close(client[dest_id][client_id].handle); + client[dest_id][client_id].handle = NULL; + } + mutex_unlock(&svc->m_lock); + + return 0; +} + +void apr_reset(void *handle) +{ + struct apr_reset_work *apr_reset_worker = NULL; + + if (!handle) + return; + pr_debug("%s: handle[%p]\n", __func__, handle); + + if (apr_reset_workqueue == NULL) { + pr_err("%s: apr_reset_workqueue is NULL\n", __func__); + return; + } + + apr_reset_worker = kzalloc(sizeof(struct apr_reset_work), + GFP_ATOMIC); + + if (apr_reset_worker == NULL) { + pr_err("%s: mem failure\n", __func__); + return; + } + + apr_reset_worker->handle = handle; + INIT_WORK(&apr_reset_worker->work, apr_reset_deregister); + queue_work(apr_reset_workqueue, &apr_reset_worker->work); +} + +/* Dispatch the Reset events to Modem and audio clients */ +void dispatch_event(unsigned long code, uint16_t proc) +{ + struct apr_client *apr_client; + struct apr_client_data data; + struct apr_svc *svc; + uint16_t clnt; + int i, j; + + data.opcode = RESET_EVENTS; + data.reset_event = code; + + /* Service domain can be different from the processor */ + data.reset_proc = apr_get_reset_domain(proc); + + clnt = APR_CLIENT_AUDIO; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } + + clnt = APR_CLIENT_VOICE; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } +} + +static int modem_notifier_cb(struct notifier_block *this, unsigned long code, + void *_cmd) +{ + static int boot_count = 2; + + if (boot_count) { + boot_count--; + return NOTIFY_OK; + } + + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("M-Notify: Shutdown started\n"); + apr_set_modem_state(APR_SUBSYS_DOWN); + dispatch_event(code, APR_DEST_MODEM); + break; + case SUBSYS_AFTER_SHUTDOWN: + pr_debug("M-Notify: Shutdown Completed\n"); + break; + case SUBSYS_BEFORE_POWERUP: + pr_debug("M-notify: Bootup started\n"); + break; + case SUBSYS_AFTER_POWERUP: + if (apr_cmpxchg_modem_state(APR_SUBSYS_DOWN, APR_SUBSYS_UP) == + APR_SUBSYS_DOWN) + wake_up(&modem_wait); + is_modem_up = 1; + pr_debug("M-Notify: Bootup Completed\n"); + break; + default: + pr_err("M-Notify: General: %lu\n", code); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block mnb = { + .notifier_call = modem_notifier_cb, +}; + +static bool powered_on; + +static int lpass_notifier_cb(struct notifier_block *this, unsigned long code, + void *_cmd) +{ + static int boot_count = 2; + struct notif_data *data = (struct notif_data *)_cmd; + struct scm_desc desc; + + if (boot_count) { + boot_count--; + return NOTIFY_OK; + } + + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + pr_debug("L-Notify: Shutdown started\n"); + apr_set_q6_state(APR_SUBSYS_DOWN); + dispatch_event(code, APR_DEST_QDSP6); + if (data && data->crashed) { + /* Send NMI to QDSP6 via an SCM call. */ + if (!is_scm_armv8()) { + scm_call_atomic1(SCM_SVC_UTIL, + SCM_Q6_NMI_CMD, 0x1); + } else { + desc.args[0] = 0x1; + desc.arginfo = SCM_ARGS(1); + scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_UTIL, + SCM_Q6_NMI_CMD), &desc); + } + /* The write should go through before q6 is shutdown */ + mb(); + pr_debug("L-Notify: Q6 NMI was sent.\n"); + } + break; + case SUBSYS_AFTER_SHUTDOWN: + powered_on = false; + pr_debug("L-Notify: Shutdown Completed\n"); + break; + case SUBSYS_BEFORE_POWERUP: + pr_debug("L-notify: Bootup started\n"); + break; + case SUBSYS_AFTER_POWERUP: + if (apr_cmpxchg_q6_state(APR_SUBSYS_DOWN, + APR_SUBSYS_LOADED) == APR_SUBSYS_DOWN) + wake_up(&dsp_wait); + powered_on = true; + pr_debug("L-Notify: Bootup Completed\n"); + break; + default: + pr_err("L-Notify: Generel: %lu\n", code); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block lnb = { + .notifier_call = lpass_notifier_cb, +}; + +static int panic_handler(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct scm_desc desc; + + if (powered_on) { + /* Send NMI to QDSP6 via an SCM call. */ + if (!is_scm_armv8()) { + scm_call_atomic1(SCM_SVC_UTIL, SCM_Q6_NMI_CMD, 0x1); + } else { + desc.args[0] = 0x1; + desc.arginfo = SCM_ARGS(1); + scm_call2_atomic(SCM_SIP_FNID(SCM_SVC_UTIL, + SCM_Q6_NMI_CMD), &desc); + } + } + return NOTIFY_DONE; +} + +static struct notifier_block panic_nb = { + .notifier_call = panic_handler, +}; + +static int __init apr_init(void) +{ + int i, j, k; + + for (i = 0; i < APR_DEST_MAX; i++) + for (j = 0; j < APR_CLIENT_MAX; j++) { + mutex_init(&client[i][j].m_lock); + for (k = 0; k < APR_SVC_MAX; k++) { + mutex_init(&client[i][j].svc[k].m_lock); + spin_lock_init(&client[i][j].svc[k].w_lock); + } + } + apr_set_subsys_state(); + mutex_init(&q6.lock); + apr_reset_workqueue = create_singlethread_workqueue("apr_driver"); + if (!apr_reset_workqueue) + return -ENOMEM; + atomic_notifier_chain_register(&panic_notifier_list, &panic_nb); + + return 0; +} +device_initcall(apr_init); + +static int __init apr_late_init(void) +{ + int ret = 0; + init_waitqueue_head(&dsp_wait); + init_waitqueue_head(&modem_wait); + subsys_notif_register(&mnb, &lnb); + return ret; +} +late_initcall(apr_late_init); diff --git a/drivers/soc/qcom/qdsp6v2/apr_tal.c b/drivers/soc/qcom/qdsp6v2/apr_tal.c new file mode 100644 index 000000000000..6d93d260cfde --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr_tal.c @@ -0,0 +1,287 @@ +/* Copyright (c) 2010-2011, 2013-2014 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/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <soc/qcom/smd.h> +#include <linux/qdsp6v2/apr_tal.h> + +static char *svc_names[APR_DEST_MAX][APR_CLIENT_MAX] = { + { + "apr_audio_svc", + "apr_voice_svc", + }, + { + "apr_audio_svc", + "apr_voice_svc", + }, +}; + +struct apr_svc_ch_dev apr_svc_ch[APR_DL_MAX][APR_DEST_MAX][APR_CLIENT_MAX]; + +int __apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len) +{ + int w_len; + unsigned long flags; + + + spin_lock_irqsave(&apr_ch->w_lock, flags); + if (smd_write_avail(apr_ch->ch) < len) { + spin_unlock_irqrestore(&apr_ch->w_lock, flags); + return -EAGAIN; + } + + w_len = smd_write(apr_ch->ch, data, len); + spin_unlock_irqrestore(&apr_ch->w_lock, flags); + pr_debug("apr_tal:w_len = %d\n", w_len); + + if (w_len != len) { + pr_err("apr_tal: Error in write\n"); + return -ENETRESET; + } + return w_len; +} + +int apr_tal_write(struct apr_svc_ch_dev *apr_ch, void *data, int len) +{ + int rc = 0, retries = 0; + + if (!apr_ch->ch) + return -EINVAL; + + do { + if (rc == -EAGAIN) + udelay(50); + + rc = __apr_tal_write(apr_ch, data, len); + } while (rc == -EAGAIN && retries++ < 300); + + if (rc == -EAGAIN) + pr_err("apr_tal: TIMEOUT for write\n"); + + return rc; +} + +static void apr_tal_notify(void *priv, unsigned event) +{ + struct apr_svc_ch_dev *apr_ch = priv; + int len, r_len, sz; + int pkt_cnt = 0; + unsigned long flags; + + pr_debug("event = %d\n", event); + switch (event) { + case SMD_EVENT_DATA: + pkt_cnt = 0; + spin_lock_irqsave(&apr_ch->lock, flags); +check_pending: + len = smd_read_avail(apr_ch->ch); + if (len < 0) { + pr_err("apr_tal: Invalid Read Event :%d\n", len); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + sz = smd_cur_packet_size(apr_ch->ch); + if (sz < 0) { + pr_debug("pkt size is zero\n"); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + if (!len && !sz && !pkt_cnt) + goto check_write_avail; + if (!len) { + pr_debug("len = %d pkt_cnt = %d\n", len, pkt_cnt); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + r_len = smd_read_from_cb(apr_ch->ch, apr_ch->data, len); + if (len != r_len) { + pr_err("apr_tal: Invalid Read\n"); + spin_unlock_irqrestore(&apr_ch->lock, flags); + return; + } + pkt_cnt++; + pr_debug("%d %d %d\n", len, sz, pkt_cnt); + if (apr_ch->func) + apr_ch->func(apr_ch->data, r_len, apr_ch->priv); + goto check_pending; +check_write_avail: + if (smd_write_avail(apr_ch->ch)) + wake_up(&apr_ch->wait); + spin_unlock_irqrestore(&apr_ch->lock, flags); + break; + case SMD_EVENT_OPEN: + pr_debug("apr_tal: SMD_EVENT_OPEN\n"); + apr_ch->smd_state = 1; + wake_up(&apr_ch->wait); + break; + case SMD_EVENT_CLOSE: + pr_debug("apr_tal: SMD_EVENT_CLOSE\n"); + break; + } +} + +struct apr_svc_ch_dev *apr_tal_open(uint32_t svc, uint32_t dest, + uint32_t dl, apr_svc_cb_fn func, void *priv) +{ + int rc; + + if ((svc >= APR_CLIENT_MAX) || (dest >= APR_DEST_MAX) || + (dl >= APR_DL_MAX)) { + pr_err("apr_tal: Invalid params\n"); + return NULL; + } + + if (apr_svc_ch[dl][dest][svc].ch) { + pr_err("apr_tal: This channel alreday openend\n"); + return NULL; + } + + mutex_lock(&apr_svc_ch[dl][dest][svc].m_lock); + if (!apr_svc_ch[dl][dest][svc].dest_state) { + rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].dest, + apr_svc_ch[dl][dest][svc].dest_state, + msecs_to_jiffies(APR_OPEN_TIMEOUT_MS)); + if (rc == 0) { + pr_err("apr_tal:open timeout\n"); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + return NULL; + } + pr_debug("apr_tal:Wakeup done\n"); + apr_svc_ch[dl][dest][svc].dest_state = 0; + } + rc = smd_named_open_on_edge(svc_names[dest][svc], dest, + &apr_svc_ch[dl][dest][svc].ch, + &apr_svc_ch[dl][dest][svc], + apr_tal_notify); + if (rc < 0) { + pr_err("apr_tal: smd_open failed %s\n", + svc_names[dest][svc]); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + return NULL; + } + rc = wait_event_timeout(apr_svc_ch[dl][dest][svc].wait, + (apr_svc_ch[dl][dest][svc].smd_state == 1), 5 * HZ); + if (rc == 0) { + pr_err("apr_tal:TIMEOUT for OPEN event\n"); + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + apr_tal_close(&apr_svc_ch[dl][dest][svc]); + return NULL; + } + + smd_disable_read_intr(apr_svc_ch[dl][dest][svc].ch); + + if (!apr_svc_ch[dl][dest][svc].dest_state) { + apr_svc_ch[dl][dest][svc].dest_state = 1; + pr_debug("apr_tal:Waiting for apr svc init\n"); + msleep(200); + pr_debug("apr_tal:apr svc init done\n"); + } + apr_svc_ch[dl][dest][svc].smd_state = 0; + + apr_svc_ch[dl][dest][svc].func = func; + apr_svc_ch[dl][dest][svc].priv = priv; + mutex_unlock(&apr_svc_ch[dl][dest][svc].m_lock); + + return &apr_svc_ch[dl][dest][svc]; +} + +int apr_tal_close(struct apr_svc_ch_dev *apr_ch) +{ + int r; + + if (!apr_ch->ch) + return -EINVAL; + + mutex_lock(&apr_ch->m_lock); + r = smd_close(apr_ch->ch); + apr_ch->ch = NULL; + apr_ch->func = NULL; + apr_ch->priv = NULL; + mutex_unlock(&apr_ch->m_lock); + return r; +} + +static int apr_smd_probe(struct platform_device *pdev) +{ + int dest; + int clnt; + + if (pdev->id == APR_DEST_MODEM) { + pr_info("apr_tal:Modem Is Up\n"); + dest = APR_DEST_MODEM; + if (!strcmp(pdev->name, "apr_audio_svc")) + clnt = APR_CLIENT_AUDIO; + else + clnt = APR_CLIENT_VOICE; + apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1; + wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest); + } else if (pdev->id == APR_DEST_QDSP6) { + pr_info("apr_tal:Q6 Is Up\n"); + dest = APR_DEST_QDSP6; + clnt = APR_CLIENT_AUDIO; + apr_svc_ch[APR_DL_SMD][dest][clnt].dest_state = 1; + wake_up(&apr_svc_ch[APR_DL_SMD][dest][clnt].dest); + } else + pr_err("apr_tal:Invalid Dest Id: %d\n", pdev->id); + + return 0; +} + +static struct platform_driver apr_q6_driver = { + .probe = apr_smd_probe, + .driver = { + .name = "apr_audio_svc", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver apr_modem_driver = { + .probe = apr_smd_probe, + .driver = { + .name = "apr_voice_svc", + .owner = THIS_MODULE, + }, +}; + +static int __init apr_tal_init(void) +{ + int i, j, k; + + for (i = 0; i < APR_DL_MAX; i++) + for (j = 0; j < APR_DEST_MAX; j++) + for (k = 0; k < APR_CLIENT_MAX; k++) { + init_waitqueue_head(&apr_svc_ch[i][j][k].wait); + init_waitqueue_head(&apr_svc_ch[i][j][k].dest); + spin_lock_init(&apr_svc_ch[i][j][k].lock); + spin_lock_init(&apr_svc_ch[i][j][k].w_lock); + mutex_init(&apr_svc_ch[i][j][k].m_lock); + } + platform_driver_register(&apr_q6_driver); + platform_driver_register(&apr_modem_driver); + return 0; +} +device_initcall(apr_tal_init); diff --git a/drivers/soc/qcom/qdsp6v2/apr_v2.c b/drivers/soc/qcom/qdsp6v2/apr_v2.c new file mode 100644 index 000000000000..c6e3b7ee6c10 --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr_v2.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2012-2014 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/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/kernel.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/dsp_debug.h> + +static const char *lpass_subsys_name = "adsp"; + +void apr_set_subsys_state(void) +{ + apr_set_q6_state(APR_SUBSYS_DOWN); + apr_set_modem_state(APR_SUBSYS_UP); +} + +const char *apr_get_lpass_subsys_name(void) +{ + return lpass_subsys_name; +} + +uint16_t apr_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; + else { + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + return APR_DEST_MAX; /*RETURN INVALID VALUE*/ + } +} + +int apr_get_dest_id(char *dest) +{ + if (!strcmp(dest, "ADSP")) + return APR_DEST_QDSP6; + else + return APR_DEST_MODEM; +} + +void subsys_notif_register(struct notifier_block *mod_notif, + struct notifier_block *lp_notif) +{ + subsys_notif_register_notifier("modem", mod_notif); + subsys_notif_register_notifier(apr_get_lpass_subsys_name(), lp_notif); +} + +uint16_t apr_get_reset_domain(uint16_t proc) +{ + return proc; +} + +bool apr_register_voice_svc() +{ + return true; +} diff --git a/drivers/soc/qcom/qdsp6v2/apr_v3.c b/drivers/soc/qcom/qdsp6v2/apr_v3.c new file mode 100644 index 000000000000..899bb2d0aa14 --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr_v3.c @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2013-2014, 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/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/kernel.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/dsp_debug.h> + +#define DEST_ID APR_DEST_MODEM + +void apr_set_subsys_state(void) +{ + apr_set_modem_state(APR_SUBSYS_DOWN); +} + +uint16_t apr_get_data_src(struct apr_hdr *hdr) +{ + return DEST_ID; +} + +int apr_get_dest_id(char *dest) +{ + return DEST_ID; +} + +void subsys_notif_register(struct notifier_block *mod_notif, + struct notifier_block *lp_notif) +{ + subsys_notif_register_notifier("modem", mod_notif); +} + +uint16_t apr_get_reset_domain(uint16_t proc) +{ + return APR_DEST_QDSP6; +} + +bool apr_register_voice_svc() +{ + return false; +} diff --git a/drivers/soc/qcom/qdsp6v2/msm_audio_ion.c b/drivers/soc/qcom/qdsp6v2/msm_audio_ion.c new file mode 100644 index 000000000000..c1d455862db9 --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/msm_audio_ion.c @@ -0,0 +1,920 @@ +/* + * Copyright (c) 2013-2015, 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/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/dma-mapping.h> +#include <linux/dma-buf.h> +#include <linux/iommu.h> +#include <linux/platform_device.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/of_device.h> +#include <linux/msm_audio_ion.h> +#include <linux/export.h> +#include <linux/qcom_iommu.h> +#include <asm/dma-iommu.h> + +#define MSM_AUDIO_ION_PROBED (1 << 0) + +#define MSM_AUDIO_ION_PHYS_ADDR(alloc_data) \ + alloc_data->table->sgl->dma_address + +#define MSM_AUDIO_ION_VA_START 0x10000000 +#define MSM_AUDIO_ION_VA_LEN 0x0FFFFFFF + +#define MSM_AUDIO_SMMU_SID_OFFSET 32 + +struct addr_range { + dma_addr_t start; + size_t size; +}; + +struct context_bank_info { + const char *name; + struct addr_range addr_range; +}; + +struct msm_audio_ion_private { + bool smmu_enabled; + bool audioheap_enabled; + struct device *cb_dev; + struct dma_iommu_mapping *mapping; + u8 device_status; + struct list_head alloc_list; + struct mutex list_mutex; + u64 smmu_sid_bits; + u32 smmu_version; +}; + +struct msm_audio_alloc_data { + struct ion_client *client; + struct ion_handle *handle; + size_t len; + struct dma_buf *dma_buf; + struct dma_buf_attachment *attach; + struct sg_table *table; + struct list_head list; +}; + +static struct msm_audio_ion_private msm_audio_ion_data = {0,}; + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len); + +static int msm_audio_dma_buf_map(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len); + +static int msm_audio_dma_buf_unmap(struct ion_client *client, + struct ion_handle *handle); + +static void msm_audio_ion_add_allocation( + struct msm_audio_ion_private *msm_audio_ion_data, + struct msm_audio_alloc_data *alloc_data) +{ + /* + * Since these APIs can be invoked by multiple + * clients, there is need to make sure the list + * of allocations is always protected + */ + mutex_lock(&(msm_audio_ion_data->list_mutex)); + list_add_tail(&(alloc_data->list), + &(msm_audio_ion_data->alloc_list)); + mutex_unlock(&(msm_audio_ion_data->list_mutex)); +} + +int msm_audio_ion_alloc(const char *name, struct ion_client **client, + struct ion_handle **handle, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = -EINVAL; + unsigned long err_ion_ptr = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + if (!name || !client || !handle || !paddr || !vaddr + || !bufsz || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + goto err; + } + + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID), 0); + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (msm_audio_ion_data.smmu_enabled == true) { + pr_debug("system heap is used"); + msm_audio_ion_data.audioheap_enabled = 0; + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_SYSTEM_HEAP_ID), 0); + } + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (IS_ERR((void *)(*handle))) + err_ion_ptr = PTR_ERR((int *)(*handle)); + pr_err("%s:ION alloc fail err ptr=%ld, smmu_enabled=%d\n", + __func__, err_ion_ptr, msm_audio_ion_data.smmu_enabled); + rc = -ENOMEM; + goto err_ion_client; + } + } else { + pr_debug("audio heap is used"); + msm_audio_ion_data.audioheap_enabled = 1; + } + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_ion_handle; + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + goto err_ion_handle; + } + pr_debug("%s: mapped address = %p, size=%zd\n", __func__, + *vaddr, bufsz); + + if (bufsz != 0) { + pr_debug("%s: memset to 0 %p %zd\n", __func__, *vaddr, bufsz); + memset((void *)*vaddr, 0, bufsz); + } + + return rc; + +err_ion_handle: + ion_free(*client, *handle); +err_ion_client: + msm_audio_ion_client_destroy(*client); + *handle = NULL; + *client = NULL; +err: + return rc; +} +EXPORT_SYMBOL(msm_audio_ion_alloc); + +int msm_audio_ion_import(const char *name, struct ion_client **client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err; + } + + /* name should be audio_acdb_client or Audio_Dec_Client, + bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(*client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%p\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *) (*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err_destroy_client; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(*client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + goto err_ion_handle; + } + } + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_ion_handle; + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -ENOMEM; + goto err_ion_handle; + } + pr_debug("%s: mapped address = %p, size=%zd\n", __func__, + *vaddr, bufsz); + + return 0; + +err_ion_handle: + ion_free(*client, *handle); +err_destroy_client: + msm_audio_ion_client_destroy(*client); + *client = NULL; + *handle = NULL; +err: + return rc; +} + +int msm_audio_ion_free(struct ion_client *client, struct ion_handle *handle) +{ + if (!client || !handle) { + pr_err("%s Invalid params\n", __func__); + return -EINVAL; + } + if (msm_audio_ion_data.smmu_enabled) + msm_audio_dma_buf_unmap(client, handle); + + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + msm_audio_ion_client_destroy(client); + return 0; +} +EXPORT_SYMBOL(msm_audio_ion_free); + +int msm_audio_ion_mmap(struct audio_buffer *ab, + struct vm_area_struct *vma) +{ + struct sg_table *table; + unsigned long addr = vma->vm_start; + unsigned long offset = vma->vm_pgoff * PAGE_SIZE; + struct scatterlist *sg; + unsigned int i; + struct page *page; + int ret; + + pr_debug("%s\n", __func__); + + table = ion_sg_table(ab->client, ab->handle); + + if (IS_ERR(table)) { + pr_err("%s: Unable to get sg_table from ion: %ld\n", + __func__, PTR_ERR(table)); + return PTR_ERR(table); + } else if (!table) { + pr_err("%s: sg_list is NULL\n", __func__); + return -EINVAL; + } + + /* uncached */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + /* We need to check if a page is associated with this sg list because: + * If the allocation came from a carveout we currently don't have + * pages associated with carved out memory. This might change in the + * future and we can remove this check and the else statement. + */ + page = sg_page(table->sgl); + if (page) { + pr_debug("%s: page is NOT null\n", __func__); + for_each_sg(table->sgl, sg, table->nents, i) { + unsigned long remainder = vma->vm_end - addr; + unsigned long len = sg->length; + + page = sg_page(sg); + + if (offset >= len) { + offset -= len; + continue; + } else if (offset) { + page += offset / PAGE_SIZE; + len -= offset; + offset = 0; + } + len = min(len, remainder); + pr_debug("vma=%p, addr=%x len=%ld vm_start=%x vm_end=%x vm_page_prot=%ld\n", + vma, (unsigned int)addr, len, + (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, + (unsigned long int)vma->vm_page_prot); + remap_pfn_range(vma, addr, page_to_pfn(page), len, + vma->vm_page_prot); + addr += len; + if (addr >= vma->vm_end) + return 0; + } + } else { + ion_phys_addr_t phys_addr; + size_t phys_len; + size_t va_len = 0; + pr_debug("%s: page is NULL\n", __func__); + + ret = ion_phys(ab->client, ab->handle, &phys_addr, &phys_len); + if (ret) { + pr_err("%s: Unable to get phys address from ION buffer: %d\n" + , __func__ , ret); + return ret; + } + pr_debug("phys=%pa len=%zd\n", &phys_addr, phys_len); + pr_debug("vma=%p, vm_start=%x vm_end=%x vm_pgoff=%ld vm_page_prot=%ld\n", + vma, (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, vma->vm_pgoff, + (unsigned long int)vma->vm_page_prot); + va_len = vma->vm_end - vma->vm_start; + if ((offset > phys_len) || (va_len > phys_len-offset)) { + pr_err("wrong offset size %ld, lens= %zd, va_len=%zd\n", + offset, phys_len, va_len); + return -EINVAL; + } + ret = remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(phys_addr) + vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + } + return 0; +} + + +bool msm_audio_ion_is_smmu_available(void) +{ + return msm_audio_ion_data.smmu_enabled; +} + +/* move to static section again */ +struct ion_client *msm_audio_ion_client_create(const char *name) +{ + struct ion_client *pclient = NULL; + pclient = msm_ion_client_create(name); + return pclient; +} + + +void msm_audio_ion_client_destroy(struct ion_client *client) +{ + pr_debug("%s: client = %p smmu_enabled = %d\n", __func__, + client, msm_audio_ion_data.smmu_enabled); + + ion_client_destroy(client); +} + +int msm_audio_ion_import_legacy(const char *name, struct ion_client *client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + /* client is already created for legacy and given*/ + /* name should be audio_acdb_client or Audio_Dec_Client, + bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%p\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *)(*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + rc = -EINVAL; + goto err_ion_handle; + } + } + + rc = msm_audio_ion_get_phys(client, *handle, paddr, pa_len); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + rc = -EINVAL; + goto err_ion_handle; + } + + /*Need to add condition SMMU enable or not */ + *vaddr = ion_map_kernel(client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err_ion_handle; + } + + if (bufsz != 0) + memset((void *)*vaddr, 0, bufsz); + + return 0; + +err_ion_handle: + ion_free(client, *handle); +err: + return rc; +} + +int msm_audio_ion_free_legacy(struct ion_client *client, + struct ion_handle *handle) +{ + if (msm_audio_ion_data.smmu_enabled) + msm_audio_dma_buf_unmap(client, handle); + + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + /* no client_destrody in legacy*/ + return 0; +} + +int msm_audio_ion_cache_operations(struct audio_buffer *abuff, int cache_op) +{ + unsigned long ionflag = 0; + int rc = 0; + int msm_cache_ops = 0; + + if (!abuff) { + pr_err("Invalid params: %p, %p\n", __func__, abuff); + return -EINVAL; + } + rc = ion_handle_get_flags(abuff->client, abuff->handle, + &ionflag); + if (rc) { + pr_err("ion_handle_get_flags failed: %d\n", rc); + goto cache_op_failed; + } + + /* has to be CACHED */ + if (ION_IS_CACHED(ionflag)) { + /* ION_IOC_INV_CACHES or ION_IOC_CLEAN_CACHES */ + msm_cache_ops = cache_op; + rc = msm_ion_do_cache_op(abuff->client, + abuff->handle, + (unsigned long *) abuff->data, + (unsigned long)abuff->size, + msm_cache_ops); + if (rc) { + pr_err("cache operation failed %d\n", rc); + goto cache_op_failed; + } + } +cache_op_failed: + return rc; +} + + +static int msm_audio_dma_buf_map(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len) +{ + + struct msm_audio_alloc_data *alloc_data; + struct device *cb_dev; + int rc = 0; + + cb_dev = msm_audio_ion_data.cb_dev; + + /* Data required per buffer mapping */ + alloc_data = kzalloc(sizeof(*alloc_data), GFP_KERNEL); + if (!alloc_data) { + pr_err("%s: No memory for alloc_data\n", __func__); + return -ENOMEM; + } + + /* Get the ION handle size */ + ion_handle_get_size(client, handle, len); + + alloc_data->client = client; + alloc_data->handle = handle; + alloc_data->len = *len; + + /* Get the dma_buf handle from ion_handle */ + alloc_data->dma_buf = ion_share_dma_buf(client, handle); + if (IS_ERR(alloc_data->dma_buf)) { + rc = PTR_ERR(alloc_data->dma_buf); + dev_err(cb_dev, + "%s: Fail to get dma_buf handle, rc = %d\n", + __func__, rc); + goto err_dma_buf; + } + + /* Attach the dma_buf to context bank device */ + alloc_data->attach = dma_buf_attach(alloc_data->dma_buf, + cb_dev); + if (IS_ERR(alloc_data->attach)) { + rc = PTR_ERR(alloc_data->attach); + dev_err(cb_dev, + "%s: Fail to attach dma_buf to CB, rc = %d\n", + __func__, rc); + goto err_attach; + } + + /* + * Get the scatter-gather list. + * There is no info as this is a write buffer or + * read buffer, hence the request is bi-directional + * to accomodate both read and write mappings. + */ + alloc_data->table = dma_buf_map_attachment(alloc_data->attach, + DMA_BIDIRECTIONAL); + if (IS_ERR(alloc_data->table)) { + rc = PTR_ERR(alloc_data->table); + dev_err(cb_dev, + "%s: Fail to map attachment, rc = %d\n", + __func__, rc); + goto err_map_attach; + } + + rc = dma_map_sg(cb_dev, alloc_data->table->sgl, + alloc_data->table->nents, + DMA_BIDIRECTIONAL); + if (rc != alloc_data->table->nents) { + dev_err(cb_dev, + "%s: Fail to map SG, rc = %d, nents = %d\n", + __func__, rc, alloc_data->table->nents); + goto err_map_sg; + } + /* Make sure not to return rc from dma_map_sg, as it can be nonzero */ + rc = 0; + + /* physical address from mapping */ + *addr = MSM_AUDIO_ION_PHYS_ADDR(alloc_data); + + msm_audio_ion_add_allocation(&msm_audio_ion_data, + alloc_data); + return rc; + +err_map_sg: + dma_buf_unmap_attachment(alloc_data->attach, + alloc_data->table, + DMA_BIDIRECTIONAL); +err_map_attach: + dma_buf_detach(alloc_data->dma_buf, + alloc_data->attach); +err_attach: + dma_buf_put(alloc_data->dma_buf); + +err_dma_buf: + kfree(alloc_data); + + return rc; +} + +static int msm_audio_dma_buf_unmap(struct ion_client *client, + struct ion_handle *handle) +{ + int rc = 0; + struct msm_audio_alloc_data *alloc_data = NULL; + struct list_head *ptr, *next; + struct device *cb_dev = msm_audio_ion_data.cb_dev; + bool found = false; + + /* + * Though list_for_each_safe is delete safe, lock + * should be explicitly acquired to avoid race condition + * on adding elements to the list. + */ + mutex_lock(&(msm_audio_ion_data.list_mutex)); + list_for_each_safe(ptr, next, + &(msm_audio_ion_data.alloc_list)) { + + alloc_data = list_entry(ptr, struct msm_audio_alloc_data, + list); + + if (alloc_data->handle == handle && + alloc_data->client == client) { + found = true; + dma_unmap_sg(cb_dev, + alloc_data->table->sgl, + alloc_data->table->nents, + DMA_BIDIRECTIONAL); + + dma_buf_unmap_attachment(alloc_data->attach, + alloc_data->table, + DMA_BIDIRECTIONAL); + + dma_buf_detach(alloc_data->dma_buf, + alloc_data->attach); + + dma_buf_put(alloc_data->dma_buf); + + list_del(&(alloc_data->list)); + kfree(alloc_data); + break; + } + } + mutex_unlock(&(msm_audio_ion_data.list_mutex)); + + if (!found) { + dev_err(cb_dev, + "%s: cannot find allocation, ion_handle %p, ion_client %p", + __func__, handle, client); + rc = -EINVAL; + } + + return rc; +} + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len) +{ + int rc = 0; + + pr_debug("%s: smmu_enabled = %d\n", __func__, + msm_audio_ion_data.smmu_enabled); + + if (msm_audio_ion_data.smmu_enabled) { + rc = msm_audio_dma_buf_map(client, handle, addr, len); + if (rc) { + pr_err("%s: failed to map DMA buf, err = %d\n", + __func__, rc); + goto err; + } + /* Append the SMMU SID information to the IOVA address */ + *addr |= msm_audio_ion_data.smmu_sid_bits; + } else { + rc = ion_phys(client, handle, addr, len); + } + + pr_debug("phys=%pa, len=%zd, rc=%d\n", &(*addr), *len, rc); +err: + return rc; +} + +static int msm_audio_smmu_init_legacy(struct device *dev) +{ + struct dma_iommu_mapping *mapping; + struct device_node *ctx_node = NULL; + struct context_bank_info *cb; + int ret; + u32 read_val[2]; + + cb = devm_kzalloc(dev, sizeof(struct context_bank_info), GFP_KERNEL); + ctx_node = of_parse_phandle(dev->of_node, "iommus", 0); + if (!ctx_node) { + dev_err(dev, "%s Could not find any iommus for audio\n", + __func__); + return -EINVAL; + } + ret = of_property_read_string(ctx_node, "label", &(cb->name)); + if (ret) { + dev_err(dev, "%s Could not find label\n", __func__); + return -EINVAL; + } + pr_debug("label found : %s\n", cb->name); + ret = of_property_read_u32_array(ctx_node, + "qcom,virtual-addr-pool", + read_val, 2); + if (ret) { + dev_err(dev, "%s Could not read addr pool for group : (%d)\n", + __func__, ret); + return -EINVAL; + } + msm_audio_ion_data.cb_dev = msm_iommu_get_ctx(cb->name); + cb->addr_range.start = (dma_addr_t) read_val[0]; + cb->addr_range.size = (size_t) read_val[1]; + dev_dbg(dev, "%s Legacy iommu usage\n", __func__); + mapping = arm_iommu_create_mapping( + msm_iommu_get_bus(msm_audio_ion_data.cb_dev), + cb->addr_range.start, + cb->addr_range.size); + if (IS_ERR(mapping)) + return PTR_ERR(mapping); + + ret = arm_iommu_attach_device(msm_audio_ion_data.cb_dev, mapping); + if (ret) { + dev_err(dev, "%s: Attach failed, err = %d\n", + __func__, ret); + goto fail_attach; + } + + msm_audio_ion_data.mapping = mapping; + INIT_LIST_HEAD(&msm_audio_ion_data.alloc_list); + mutex_init(&(msm_audio_ion_data.list_mutex)); + + return 0; + +fail_attach: + arm_iommu_release_mapping(mapping); + return ret; +} + +static int msm_audio_smmu_init(struct device *dev) +{ + struct dma_iommu_mapping *mapping; + int ret; + int disable_htw = 1; + + mapping = arm_iommu_create_mapping( + msm_iommu_get_bus(dev), + MSM_AUDIO_ION_VA_START, + MSM_AUDIO_ION_VA_LEN); + if (IS_ERR(mapping)) + return PTR_ERR(mapping); + + iommu_domain_set_attr(mapping->domain, + DOMAIN_ATTR_COHERENT_HTW_DISABLE, + &disable_htw); + + ret = arm_iommu_attach_device(dev, mapping); + if (ret) { + dev_err(dev, "%s: Attach failed, err = %d\n", + __func__, ret); + goto fail_attach; + } + + msm_audio_ion_data.cb_dev = dev; + msm_audio_ion_data.mapping = mapping; + INIT_LIST_HEAD(&msm_audio_ion_data.alloc_list); + mutex_init(&(msm_audio_ion_data.list_mutex)); + + return 0; + +fail_attach: + arm_iommu_release_mapping(mapping); + return ret; +} + +static const struct of_device_id msm_audio_ion_dt_match[] = { + { .compatible = "qcom,msm-audio-ion" }, + { } +}; +MODULE_DEVICE_TABLE(of, msm_audio_ion_dt_match); + + +u32 msm_audio_ion_get_smmu_sid_mode32(void) +{ + if (msm_audio_ion_data.smmu_enabled) + return upper_32_bits(msm_audio_ion_data.smmu_sid_bits); + else + return 0; +} + +u32 populate_upper_32_bits(ion_phys_addr_t pa) +{ + if (sizeof(ion_phys_addr_t) == sizeof(u32)) + return msm_audio_ion_get_smmu_sid_mode32(); + else + return upper_32_bits(pa); +} + +static int msm_audio_ion_probe(struct platform_device *pdev) +{ + int rc = 0; + const char *msm_audio_ion_dt = "qcom,smmu-enabled"; + const char *msm_audio_ion_smmu = "qcom,smmu-version"; + bool smmu_enabled; + enum apr_subsys_state q6_state; + struct device *dev = &pdev->dev; + + if (dev->of_node == NULL) { + dev_err(dev, + "%s: device tree is not found\n", + __func__); + msm_audio_ion_data.smmu_enabled = 0; + return 0; + } + + smmu_enabled = of_property_read_bool(dev->of_node, + msm_audio_ion_dt); + msm_audio_ion_data.smmu_enabled = smmu_enabled; + + if (smmu_enabled) { + rc = of_property_read_u32(dev->of_node, + msm_audio_ion_smmu, + &msm_audio_ion_data.smmu_version); + if (rc) { + dev_err(dev, + "%s: qcom,smmu_version missing in DT node\n", + __func__); + return rc; + } + dev_dbg(dev, "%s: SMMU version is (%d)", __func__, + msm_audio_ion_data.smmu_version); + q6_state = apr_get_q6_state(); + if (q6_state == APR_SUBSYS_DOWN) { + dev_dbg(dev, + "defering %s, adsp_state %d\n", + __func__, q6_state); + return -EPROBE_DEFER; + } else { + dev_dbg(dev, "%s: adsp is ready\n", __func__); + } + } + + dev_dbg(dev, "%s: SMMU is %s\n", __func__, + (smmu_enabled) ? "Enabled" : "Disabled"); + + if (smmu_enabled) { + u64 smmu_sid = 0; + struct of_phandle_args iommuspec; + + /* Get SMMU SID information from Devicetree */ + rc = of_parse_phandle_with_args(dev->of_node, "iommus", + "#iommu-cells", 0, &iommuspec); + if (rc) + dev_err(dev, "%s: could not get smmu SID, ret = %d\n", + __func__, rc); + else + smmu_sid = iommuspec.args[0]; + + msm_audio_ion_data.smmu_sid_bits = + smmu_sid << MSM_AUDIO_SMMU_SID_OFFSET; + + if (msm_audio_ion_data.smmu_version == 0x1) { + rc = msm_audio_smmu_init_legacy(dev); + } else if (msm_audio_ion_data.smmu_version == 0x2) { + rc = msm_audio_smmu_init(dev); + } else { + dev_err(dev, "%s: smmu version invalid %d\n", + __func__, msm_audio_ion_data.smmu_version); + rc = -EINVAL; + } + if (rc) + dev_err(dev, "%s: smmu init failed, err = %d\n", + __func__, rc); + } + + if (!rc) + msm_audio_ion_data.device_status |= MSM_AUDIO_ION_PROBED; + + return rc; +} + +static int msm_audio_ion_remove(struct platform_device *pdev) +{ + struct dma_iommu_mapping *mapping; + struct device *audio_cb_dev; + + mapping = msm_audio_ion_data.mapping; + audio_cb_dev = msm_audio_ion_data.cb_dev; + + if (audio_cb_dev && mapping) { + arm_iommu_detach_device(audio_cb_dev); + arm_iommu_release_mapping(mapping); + } + + msm_audio_ion_data.smmu_enabled = 0; + msm_audio_ion_data.device_status = 0; + return 0; +} + +static struct platform_driver msm_audio_ion_driver = { + .driver = { + .name = "msm-audio-ion", + .owner = THIS_MODULE, + .of_match_table = msm_audio_ion_dt_match, + }, + .probe = msm_audio_ion_probe, + .remove = msm_audio_ion_remove, +}; + +static int __init msm_audio_ion_init(void) +{ + return platform_driver_register(&msm_audio_ion_driver); +} +module_init(msm_audio_ion_init); + +static void __exit msm_audio_ion_exit(void) +{ + platform_driver_unregister(&msm_audio_ion_driver); +} +module_exit(msm_audio_ion_exit); + +MODULE_DESCRIPTION("MSM Audio ION module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/qdsp6v2/voice_svc.c b/drivers/soc/qcom/qdsp6v2/voice_svc.c new file mode 100644 index 000000000000..23b8292c8db5 --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/voice_svc.c @@ -0,0 +1,759 @@ +/* Copyright (c) 2014-2015, 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/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/uaccess.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/cdev.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/apr.h> +#include <sound/voice_svc.h> + +#define MINOR_NUMBER 1 +#define APR_MAX_RESPONSE 10 +#define TIMEOUT_MS 1000 + +#define MAX(a, b) ((a) >= (b) ? (a) : (b)) + +struct voice_svc_device { + struct cdev *cdev; + struct device *dev; + int major; +}; + +struct voice_svc_prvt { + void *apr_q6_mvm; + void *apr_q6_cvs; + uint16_t response_count; + struct list_head response_queue; + wait_queue_head_t response_wait; + spinlock_t response_lock; +}; + +struct apr_data { + struct apr_hdr hdr; + __u8 payload[0]; +} __packed; + +struct apr_response_list { + struct list_head list; + struct voice_svc_cmd_response resp; +}; + +static struct voice_svc_device *voice_svc_dev; +static struct class *voice_svc_class; +static bool reg_dummy_sess; +static void *dummy_q6_mvm; +static void *dummy_q6_cvs; +dev_t device_num; + +static int voice_svc_dummy_reg(void); +static int32_t qdsp_dummy_apr_callback(struct apr_client_data *data, + void *priv); + +static int32_t qdsp_apr_callback(struct apr_client_data *data, void *priv) +{ + struct voice_svc_prvt *prtd; + struct apr_response_list *response_list; + unsigned long spin_flags; + + if ((data == NULL) || (priv == NULL)) { + pr_err("%s: data or priv is NULL\n", __func__); + + return -EINVAL; + } + + prtd = (struct voice_svc_prvt *)priv; + if (prtd == NULL) { + pr_err("%s: private data is NULL\n", __func__); + + return -EINVAL; + } + + pr_debug("%s: data->opcode %x\n", __func__, + data->opcode); + + if (data->opcode == RESET_EVENTS) { + if (data->reset_proc == APR_DEST_QDSP6) { + pr_debug("%s: Received ADSP reset event\n", __func__); + + if (prtd->apr_q6_mvm != NULL) { + apr_reset(prtd->apr_q6_mvm); + prtd->apr_q6_mvm = NULL; + } + + if (prtd->apr_q6_cvs != NULL) { + apr_reset(prtd->apr_q6_cvs); + prtd->apr_q6_cvs = NULL; + } + } else if (data->reset_proc == APR_DEST_MODEM) { + pr_debug("%s: Received Modem reset event\n", __func__); + } + /* Set the remaining member variables to default values + for RESET_EVENTS */ + data->payload_size = 0; + data->payload = NULL; + data->src_port = 0; + data->dest_port = 0; + data->token = 0; + } + + spin_lock_irqsave(&prtd->response_lock, spin_flags); + + if (prtd->response_count < APR_MAX_RESPONSE) { + response_list = kmalloc(sizeof(struct apr_response_list) + + data->payload_size, GFP_ATOMIC); + if (response_list == NULL) { + pr_err("%s: kmalloc failed\n", __func__); + + spin_unlock_irqrestore(&prtd->response_lock, + spin_flags); + return -ENOMEM; + } + + response_list->resp.src_port = data->src_port; + + /* Reverting the bit manipulation done in voice_svc_update_hdr + * to the src_port which is returned to us as dest_port. + */ + response_list->resp.dest_port = ((data->dest_port) >> 8); + response_list->resp.token = data->token; + response_list->resp.opcode = data->opcode; + response_list->resp.payload_size = data->payload_size; + if (data->payload != NULL && data->payload_size > 0) { + memcpy(response_list->resp.payload, data->payload, + data->payload_size); + } + + list_add_tail(&response_list->list, &prtd->response_queue); + prtd->response_count++; + spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + + wake_up(&prtd->response_wait); + } else { + spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + pr_err("%s: Response dropped since the queue is full\n", + __func__); + } + + return 0; +} + +static int32_t qdsp_dummy_apr_callback(struct apr_client_data *data, void *priv) +{ + /* Do Nothing */ + return 0; +} + +static void voice_svc_update_hdr(struct voice_svc_cmd_request *apr_req_data, + struct apr_data *aprdata) +{ + + aprdata->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(sizeof(struct apr_hdr)), + APR_PKT_VER); + /* Bit manipulation is done on src_port so that a unique ID is sent. + * This manipulation can be used in the future where the same service + * is tried to open multiple times with the same src_port. At that + * time 0x0001 can be replaced with other values depending on the + * count. + */ + aprdata->hdr.src_port = ((apr_req_data->src_port) << 8 | 0x0001); + aprdata->hdr.dest_port = apr_req_data->dest_port; + aprdata->hdr.token = apr_req_data->token; + aprdata->hdr.opcode = apr_req_data->opcode; + aprdata->hdr.pkt_size = APR_PKT_SIZE(APR_HDR_SIZE, + apr_req_data->payload_size); + memcpy(aprdata->payload, apr_req_data->payload, + apr_req_data->payload_size); +} + +static int voice_svc_send_req(struct voice_svc_cmd_request *apr_request, + struct voice_svc_prvt *prtd) +{ + int ret = 0; + void *apr_handle = NULL; + struct apr_data *aprdata = NULL; + uint32_t user_payload_size = 0; + + pr_debug("%s\n", __func__); + + if (apr_request == NULL) { + pr_err("%s: apr_request is NULL\n", __func__); + + ret = -EINVAL; + goto done; + } + + user_payload_size = apr_request->payload_size; + + aprdata = kmalloc(sizeof(struct apr_data) + user_payload_size, + GFP_KERNEL); + + if (aprdata == NULL) { + pr_err("%s: aprdata kmalloc failed.\n", __func__); + + ret = -ENOMEM; + goto done; + } + + voice_svc_update_hdr(apr_request, aprdata); + + if (!strcmp(apr_request->svc_name, VOICE_SVC_CVS_STR)) { + apr_handle = prtd->apr_q6_cvs; + } else if (!strcmp(apr_request->svc_name, VOICE_SVC_MVM_STR)) { + apr_handle = prtd->apr_q6_mvm; + } else { + pr_err("%s: Invalid service %s\n", __func__, + apr_request->svc_name); + + ret = -EINVAL; + goto done; + } + + ret = apr_send_pkt(apr_handle, (uint32_t *)aprdata); + + if (ret < 0) { + pr_err("%s: Fail in sending request %d\n", + __func__, ret); + ret = -EINVAL; + } else { + pr_debug("%s: apr packet sent successfully %d\n", + __func__, ret); + ret = 0; + } + +done: + kfree(aprdata); + return ret; +} +static int voice_svc_reg(char *svc, uint32_t src_port, + struct voice_svc_prvt *prtd, void **handle) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + if (handle == NULL) { + pr_err("%s: handle is NULL\n", __func__); + ret = -EINVAL; + goto done; + } + + if (*handle != NULL) { + pr_err("%s: svc handle not NULL\n", __func__); + ret = -EINVAL; + goto done; + } + + if (src_port == (APR_MAX_PORTS - 1)) { + pr_err("%s: SRC port reserved for dummy session\n", __func__); + pr_err("%s: Unable to register %s\n", __func__, svc); + ret = -EINVAL; + goto done; + } + + *handle = apr_register("ADSP", + svc, qdsp_apr_callback, + ((src_port) << 8 | 0x0001), + prtd); + + if (*handle == NULL) { + pr_err("%s: Unable to register %s\n", + __func__, svc); + + ret = -EFAULT; + goto done; + } + pr_debug("%s: Register %s successful\n", + __func__, svc); +done: + return ret; +} + +static int voice_svc_dereg(char *svc, void **handle) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + if (handle == NULL) { + pr_err("%s: handle is NULL\n", __func__); + ret = -EINVAL; + goto done; + } + + if (*handle == NULL) { + pr_err("%s: svc handle is NULL\n", __func__); + ret = -EINVAL; + goto done; + } + + ret = apr_deregister(*handle); + if (ret) { + pr_err("%s: Unable to deregister service %s; error: %d\n", + __func__, svc, ret); + + goto done; + } + *handle = NULL; + pr_debug("%s: deregister %s successful\n", __func__, svc); + +done: + return ret; +} + +static int process_reg_cmd(struct voice_svc_register *apr_reg_svc, + struct voice_svc_prvt *prtd) +{ + int ret = 0; + char *svc = NULL; + void **handle = NULL; + + pr_debug("%s\n", __func__); + + if (!strcmp(apr_reg_svc->svc_name, VOICE_SVC_MVM_STR)) { + svc = VOICE_SVC_MVM_STR; + handle = &prtd->apr_q6_mvm; + } else if (!strcmp(apr_reg_svc->svc_name, VOICE_SVC_CVS_STR)) { + svc = VOICE_SVC_CVS_STR; + handle = &prtd->apr_q6_cvs; + } else { + pr_err("%s: Invalid Service: %s\n", __func__, + apr_reg_svc->svc_name); + ret = -EINVAL; + goto done; + } + + if (apr_reg_svc->reg_flag) { + ret = voice_svc_reg(svc, apr_reg_svc->src_port, prtd, + handle); + } else if (!apr_reg_svc->reg_flag) { + ret = voice_svc_dereg(svc, handle); + } + +done: + return ret; +} + +static ssize_t voice_svc_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + struct voice_svc_prvt *prtd; + struct voice_svc_write_msg *data = NULL; + uint32_t cmd; + + pr_debug("%s\n", __func__); + + data = kmalloc(count, GFP_KERNEL); + + if (data == NULL) { + pr_err("%s: data kmalloc failed.\n", __func__); + + ret = -ENOMEM; + goto done; + } + + ret = copy_from_user(data, buf, count); + if (ret) { + pr_err("%s: copy_from_user failed %d\n", __func__, ret); + + ret = -EPERM; + goto done; + } + + cmd = data->msg_type; + prtd = (struct voice_svc_prvt *)file->private_data; + if (prtd == NULL) { + pr_err("%s: prtd is NULL\n", __func__); + + ret = -EINVAL; + goto done; + } + + switch (cmd) { + case MSG_REGISTER: + ret = process_reg_cmd( + (struct voice_svc_register *)data->payload, prtd); + if (!ret) + ret = count; + + break; + case MSG_REQUEST: + ret = voice_svc_send_req( + (struct voice_svc_cmd_request *)data->payload, prtd); + if (!ret) + ret = count; + + break; + default: + pr_debug("%s: Invalid command: %u\n", __func__, cmd); + ret = -EINVAL; + } + +done: + kfree(data); + return ret; +} + +static ssize_t voice_svc_read(struct file *file, char __user *arg, + size_t count, loff_t *ppos) +{ + int ret = 0; + struct voice_svc_prvt *prtd; + struct apr_response_list *resp; + unsigned long spin_flags; + int size; + + pr_debug("%s\n", __func__); + + prtd = (struct voice_svc_prvt *)file->private_data; + if (prtd == NULL) { + pr_err("%s: prtd is NULL\n", __func__); + + ret = -EINVAL; + goto done; + } + + spin_lock_irqsave(&prtd->response_lock, spin_flags); + + if (list_empty(&prtd->response_queue)) { + spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + pr_debug("%s: wait for a response\n", __func__); + + ret = wait_event_interruptible_timeout(prtd->response_wait, + !list_empty(&prtd->response_queue), + msecs_to_jiffies(TIMEOUT_MS)); + if (ret == 0) { + pr_debug("%s: Read timeout\n", __func__); + + ret = -ETIMEDOUT; + goto done; + } else if (ret > 0 && !list_empty(&prtd->response_queue)) { + pr_debug("%s: Interrupt recieved for response\n", + __func__); + } else if (ret < 0) { + pr_debug("%s: Interrupted by SIGNAL %d\n", + __func__, ret); + + goto done; + } + + spin_lock_irqsave(&prtd->response_lock, spin_flags); + } + + resp = list_first_entry(&prtd->response_queue, + struct apr_response_list, list); + + spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + + size = resp->resp.payload_size + + sizeof(struct voice_svc_cmd_response); + + if (count < size) { + pr_err("%s: Invalid payload size %zd, %d\n", + __func__, count, size); + + ret = -ENOMEM; + goto done; + } + + if (!access_ok(VERIFY_WRITE, arg, size)) { + pr_err("%s: Access denied to write\n", + __func__); + + ret = -EPERM; + goto done; + } + + ret = copy_to_user(arg, &resp->resp, + sizeof(struct voice_svc_cmd_response) + + resp->resp.payload_size); + if (ret) { + pr_err("%s: copy_to_user failed %d\n", __func__, ret); + + ret = -EPERM; + goto done; + } + + spin_lock_irqsave(&prtd->response_lock, spin_flags); + + list_del(&resp->list); + prtd->response_count--; + kfree(resp); + + spin_unlock_irqrestore(&prtd->response_lock, + spin_flags); + + ret = count; + +done: + return ret; +} + +static int voice_svc_dummy_reg() +{ + uint32_t src_port = APR_MAX_PORTS - 1; + + pr_debug("%s\n", __func__); + dummy_q6_mvm = apr_register("ADSP", "MVM", + qdsp_dummy_apr_callback, + src_port, + NULL); + if (dummy_q6_mvm == NULL) { + pr_err("%s: Unable to register dummy MVM\n", __func__); + goto err; + } + + dummy_q6_cvs = apr_register("ADSP", "CVS", + qdsp_dummy_apr_callback, + src_port, + NULL); + if (dummy_q6_cvs == NULL) { + pr_err("%s: Unable to register dummy CVS\n", __func__); + goto err; + } + return 0; +err: + if (dummy_q6_mvm != NULL) { + apr_deregister(dummy_q6_mvm); + dummy_q6_mvm = NULL; + } + return -EINVAL; +} + +static int voice_svc_open(struct inode *inode, struct file *file) +{ + struct voice_svc_prvt *prtd = NULL; + + pr_debug("%s\n", __func__); + + prtd = kmalloc(sizeof(struct voice_svc_prvt), GFP_KERNEL); + + if (prtd == NULL) { + pr_err("%s: kmalloc failed\n", __func__); + return -ENOMEM; + } + + memset(prtd, 0, sizeof(struct voice_svc_prvt)); + prtd->apr_q6_cvs = NULL; + prtd->apr_q6_mvm = NULL; + prtd->response_count = 0; + INIT_LIST_HEAD(&prtd->response_queue); + init_waitqueue_head(&prtd->response_wait); + spin_lock_init(&prtd->response_lock); + file->private_data = (void *)prtd; + + /* Current APR implementation doesn't support session based + * multiple service registrations. The apr_deregister() + * function sets the destination and client IDs to zero, if + * deregister is called for a single service instance. + * To avoid this, register for additional services. + */ + if (!reg_dummy_sess) { + voice_svc_dummy_reg(); + reg_dummy_sess = 1; + } + return 0; +} + +static int voice_svc_release(struct inode *inode, struct file *file) +{ + int ret = 0; + struct apr_response_list *resp = NULL; + unsigned long spin_flags; + struct voice_svc_prvt *prtd = NULL; + char *svc_name = NULL; + void **handle = NULL; + + pr_debug("%s\n", __func__); + + prtd = (struct voice_svc_prvt *)file->private_data; + if (prtd == NULL) { + pr_err("%s: prtd is NULL\n", __func__); + + ret = -EINVAL; + goto done; + } + + if (prtd->apr_q6_cvs != NULL) { + svc_name = VOICE_SVC_MVM_STR; + handle = &prtd->apr_q6_cvs; + ret = voice_svc_dereg(svc_name, handle); + if (ret) + pr_err("%s: Failed to dereg CVS %d\n", __func__, ret); + } + + if (prtd->apr_q6_mvm != NULL) { + svc_name = VOICE_SVC_MVM_STR; + handle = &prtd->apr_q6_mvm; + ret = voice_svc_dereg(svc_name, handle); + if (ret) + pr_err("%s: Failed to dereg MVM %d\n", __func__, ret); + } + + spin_lock_irqsave(&prtd->response_lock, spin_flags); + + while (!list_empty(&prtd->response_queue)) { + pr_debug("%s: Remove item from response queue\n", __func__); + + resp = list_first_entry(&prtd->response_queue, + struct apr_response_list, list); + list_del(&resp->list); + prtd->response_count--; + kfree(resp); + } + + spin_unlock_irqrestore(&prtd->response_lock, spin_flags); + + kfree(file->private_data); + file->private_data = NULL; + +done: + return ret; +} + +static const struct file_operations voice_svc_fops = { + .owner = THIS_MODULE, + .open = voice_svc_open, + .read = voice_svc_read, + .write = voice_svc_write, + .release = voice_svc_release, +}; + + +static int voice_svc_probe(struct platform_device *pdev) +{ + int ret = 0; + + pr_debug("%s\n", __func__); + + voice_svc_dev = devm_kzalloc(&pdev->dev, + sizeof(struct voice_svc_device), GFP_KERNEL); + if (!voice_svc_dev) { + pr_err("%s: kzalloc failed\n", __func__); + ret = -ENOMEM; + goto done; + } + + ret = alloc_chrdev_region(&device_num, 0, MINOR_NUMBER, + VOICE_SVC_DRIVER_NAME); + if (ret) { + pr_err("%s: Failed to alloc chrdev\n", __func__); + ret = -ENODEV; + goto chrdev_err; + } + + voice_svc_dev->major = MAJOR(device_num); + voice_svc_class = class_create(THIS_MODULE, VOICE_SVC_DRIVER_NAME); + if (IS_ERR(voice_svc_class)) { + ret = PTR_ERR(voice_svc_class); + pr_err("%s: Failed to create class; err = %d\n", __func__, + ret); + goto class_err; + } + + voice_svc_dev->dev = device_create(voice_svc_class, NULL, device_num, + NULL, VOICE_SVC_DRIVER_NAME); + if (IS_ERR(voice_svc_dev->dev)) { + ret = PTR_ERR(voice_svc_dev->dev); + pr_err("%s: Failed to create device; err = %d\n", __func__, + ret); + goto dev_err; + } + + voice_svc_dev->cdev = cdev_alloc(); + if (!voice_svc_dev->cdev) { + pr_err("%s: Failed to alloc cdev\n", __func__); + ret = -ENOMEM; + goto cdev_alloc_err; + } + + cdev_init(voice_svc_dev->cdev, &voice_svc_fops); + ret = cdev_add(voice_svc_dev->cdev, device_num, MINOR_NUMBER); + if (ret) { + pr_err("%s: Failed to register chrdev; err = %d\n", __func__, + ret); + goto add_err; + } + pr_debug("%s: Device created\n", __func__); + goto done; + +add_err: + cdev_del(voice_svc_dev->cdev); +cdev_alloc_err: + device_destroy(voice_svc_class, device_num); +dev_err: + class_destroy(voice_svc_class); +class_err: + unregister_chrdev_region(0, MINOR_NUMBER); +chrdev_err: + kfree(voice_svc_dev); +done: + return ret; +} + +static int voice_svc_remove(struct platform_device *pdev) +{ + pr_debug("%s\n", __func__); + + cdev_del(voice_svc_dev->cdev); + kfree(voice_svc_dev->cdev); + device_destroy(voice_svc_class, device_num); + class_destroy(voice_svc_class); + unregister_chrdev_region(0, MINOR_NUMBER); + kfree(voice_svc_dev); + + return 0; +} + +static struct of_device_id voice_svc_of_match[] = { + {.compatible = "qcom,msm-voice-svc"}, + { } +}; +MODULE_DEVICE_TABLE(of, voice_svc_of_match); + +static struct platform_driver voice_svc_driver = { + .probe = voice_svc_probe, + .remove = voice_svc_remove, + .driver = { + .name = "msm-voice-svc", + .owner = THIS_MODULE, + .of_match_table = voice_svc_of_match, + }, +}; + +static int __init voice_svc_init(void) +{ + pr_debug("%s\n", __func__); + + return platform_driver_register(&voice_svc_driver); +} + +static void __exit voice_svc_exit(void) +{ + pr_debug("%s\n", __func__); + + platform_driver_unregister(&voice_svc_driver); +} + +module_init(voice_svc_init); +module_exit(voice_svc_exit); + +MODULE_DESCRIPTION("Soc QDSP6v2 Voice Service driver"); +MODULE_LICENSE("GPL v2"); |