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/qcom/qdsp6v2/voice_svc.c | |
| 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/qcom/qdsp6v2/voice_svc.c')
| -rw-r--r-- | drivers/soc/qcom/qdsp6v2/voice_svc.c | 759 |
1 files changed, 759 insertions, 0 deletions
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"); |
