| /* arch/arm/mach-msm/qdsp5/snd.c |
| * |
| * interface to "snd" service on the baseband cpu |
| * |
| * Copyright (C) 2008 HTC Corporation |
| * |
| * This software is licensed under the terms of the GNU General Public |
| * License version 2, as published by the Free Software Foundation, and |
| * may be copied, distributed, and modified under those terms. |
| * |
| * 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/module.h> |
| #include <linux/fs.h> |
| #include <linux/miscdevice.h> |
| #include <linux/uaccess.h> |
| #include <linux/kthread.h> |
| #include <linux/delay.h> |
| #include <linux/msm_audio.h> |
| |
| #include <asm/atomic.h> |
| #include <asm/ioctls.h> |
| #include <mach/board.h> |
| #include <mach/msm_rpcrouter.h> |
| |
| struct snd_ctxt { |
| struct mutex lock; |
| int opened; |
| struct msm_rpc_endpoint *ept; |
| struct msm_snd_endpoints *snd_epts; |
| }; |
| |
| static struct snd_ctxt the_snd; |
| |
| #define RPC_SND_PROG 0x30000002 |
| #define RPC_SND_CB_PROG 0x31000002 |
| #if CONFIG_MSM_AMSS_VERSION == 6210 |
| #define RPC_SND_VERS 0x94756085 /* 2490720389 */ |
| #elif (CONFIG_MSM_AMSS_VERSION == 6220) || \ |
| (CONFIG_MSM_AMSS_VERSION == 6225) |
| #define RPC_SND_VERS 0xaa2b1a44 /* 2854951492 */ |
| #elif CONFIG_MSM_AMSS_VERSION == 6350 |
| #define RPC_SND_VERS MSM_RPC_VERS(1,0) |
| #endif |
| |
| #define SND_SET_DEVICE_PROC 2 |
| #define SND_SET_VOLUME_PROC 3 |
| |
| struct rpc_snd_set_device_args { |
| uint32_t device; |
| uint32_t ear_mute; |
| uint32_t mic_mute; |
| |
| uint32_t cb_func; |
| uint32_t client_data; |
| }; |
| |
| struct rpc_snd_set_volume_args { |
| uint32_t device; |
| uint32_t method; |
| uint32_t volume; |
| |
| uint32_t cb_func; |
| uint32_t client_data; |
| }; |
| |
| struct snd_set_device_msg { |
| struct rpc_request_hdr hdr; |
| struct rpc_snd_set_device_args args; |
| }; |
| |
| struct snd_set_volume_msg { |
| struct rpc_request_hdr hdr; |
| struct rpc_snd_set_volume_args args; |
| }; |
| |
| struct snd_endpoint *get_snd_endpoints(int *size); |
| |
| static inline int check_mute(int mute) |
| { |
| return (mute == SND_MUTE_MUTED || |
| mute == SND_MUTE_UNMUTED) ? 0 : -EINVAL; |
| } |
| |
| static int get_endpoint(struct snd_ctxt *snd, unsigned long arg) |
| { |
| int rc = 0, index; |
| struct msm_snd_endpoint ept; |
| |
| if (copy_from_user(&ept, (void __user *)arg, sizeof(ept))) { |
| pr_err("snd_ioctl get endpoint: invalid read pointer.\n"); |
| return -EFAULT; |
| } |
| |
| index = ept.id; |
| if (index < 0 || index >= snd->snd_epts->num) { |
| pr_err("snd_ioctl get endpoint: invalid index!\n"); |
| return -EINVAL; |
| } |
| |
| ept.id = snd->snd_epts->endpoints[index].id; |
| strncpy(ept.name, |
| snd->snd_epts->endpoints[index].name, |
| sizeof(ept.name)); |
| |
| if (copy_to_user((void __user *)arg, &ept, sizeof(ept))) { |
| pr_err("snd_ioctl get endpoint: invalid write pointer.\n"); |
| rc = -EFAULT; |
| } |
| |
| return rc; |
| } |
| |
| static long snd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
| { |
| struct snd_set_device_msg dmsg; |
| struct snd_set_volume_msg vmsg; |
| struct msm_snd_device_config dev; |
| struct msm_snd_volume_config vol; |
| struct snd_ctxt *snd = file->private_data; |
| int rc = 0; |
| |
| mutex_lock(&snd->lock); |
| switch (cmd) { |
| case SND_SET_DEVICE: |
| if (copy_from_user(&dev, (void __user *) arg, sizeof(dev))) { |
| pr_err("snd_ioctl set device: invalid pointer.\n"); |
| rc = -EFAULT; |
| break; |
| } |
| |
| dmsg.args.device = cpu_to_be32(dev.device); |
| dmsg.args.ear_mute = cpu_to_be32(dev.ear_mute); |
| dmsg.args.mic_mute = cpu_to_be32(dev.mic_mute); |
| if (check_mute(dev.ear_mute) < 0 || |
| check_mute(dev.mic_mute) < 0) { |
| pr_err("snd_ioctl set device: invalid mute status.\n"); |
| rc = -EINVAL; |
| break; |
| } |
| dmsg.args.cb_func = -1; |
| dmsg.args.client_data = 0; |
| |
| pr_info("snd_set_device %d %d %d\n", dev.device, |
| dev.ear_mute, dev.mic_mute); |
| |
| rc = msm_rpc_call(snd->ept, |
| SND_SET_DEVICE_PROC, |
| &dmsg, sizeof(dmsg), 5 * HZ); |
| break; |
| |
| case SND_SET_VOLUME: |
| if (copy_from_user(&vol, (void __user *) arg, sizeof(vol))) { |
| pr_err("snd_ioctl set volume: invalid pointer.\n"); |
| rc = -EFAULT; |
| break; |
| } |
| |
| vmsg.args.device = cpu_to_be32(vol.device); |
| vmsg.args.method = cpu_to_be32(vol.method); |
| if (vol.method != SND_METHOD_VOICE) { |
| pr_err("snd_ioctl set volume: invalid method.\n"); |
| rc = -EINVAL; |
| break; |
| } |
| |
| vmsg.args.volume = cpu_to_be32(vol.volume); |
| vmsg.args.cb_func = -1; |
| vmsg.args.client_data = 0; |
| |
| pr_info("snd_set_volume %d %d %d\n", vol.device, |
| vol.method, vol.volume); |
| |
| rc = msm_rpc_call(snd->ept, |
| SND_SET_VOLUME_PROC, |
| &vmsg, sizeof(vmsg), 5 * HZ); |
| break; |
| |
| case SND_GET_NUM_ENDPOINTS: |
| if (copy_to_user((void __user *)arg, |
| &snd->snd_epts->num, sizeof(unsigned))) { |
| pr_err("snd_ioctl get endpoint: invalid pointer.\n"); |
| rc = -EFAULT; |
| } |
| break; |
| |
| case SND_GET_ENDPOINT: |
| rc = get_endpoint(snd, arg); |
| break; |
| |
| default: |
| pr_err("snd_ioctl unknown command.\n"); |
| rc = -EINVAL; |
| break; |
| } |
| mutex_unlock(&snd->lock); |
| |
| return rc; |
| } |
| |
| static int snd_release(struct inode *inode, struct file *file) |
| { |
| struct snd_ctxt *snd = file->private_data; |
| |
| mutex_lock(&snd->lock); |
| snd->opened = 0; |
| mutex_unlock(&snd->lock); |
| return 0; |
| } |
| |
| static int snd_open(struct inode *inode, struct file *file) |
| { |
| struct snd_ctxt *snd = &the_snd; |
| int rc = 0; |
| |
| mutex_lock(&snd->lock); |
| if (snd->opened == 0) { |
| if (snd->ept == NULL) { |
| snd->ept = msm_rpc_connect(RPC_SND_PROG, RPC_SND_VERS, |
| MSM_RPC_UNINTERRUPTIBLE); |
| if (IS_ERR(snd->ept)) { |
| rc = PTR_ERR(snd->ept); |
| snd->ept = NULL; |
| pr_err("snd: failed to connect snd svc\n"); |
| goto err; |
| } |
| } |
| file->private_data = snd; |
| snd->opened = 1; |
| } else { |
| pr_err("snd already opened.\n"); |
| rc = -EBUSY; |
| } |
| |
| err: |
| mutex_unlock(&snd->lock); |
| return rc; |
| } |
| |
| static struct file_operations snd_fops = { |
| .owner = THIS_MODULE, |
| .open = snd_open, |
| .release = snd_release, |
| .unlocked_ioctl = snd_ioctl, |
| .llseek = noop_llseek, |
| }; |
| |
| struct miscdevice snd_misc = { |
| .minor = MISC_DYNAMIC_MINOR, |
| .name = "msm_snd", |
| .fops = &snd_fops, |
| }; |
| |
| static int snd_probe(struct platform_device *pdev) |
| { |
| struct snd_ctxt *snd = &the_snd; |
| mutex_init(&snd->lock); |
| snd->snd_epts = (struct msm_snd_endpoints *)pdev->dev.platform_data; |
| return misc_register(&snd_misc); |
| } |
| |
| static struct platform_driver snd_plat_driver = { |
| .probe = snd_probe, |
| .driver = { |
| .name = "msm_snd", |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init snd_init(void) |
| { |
| return platform_driver_register(&snd_plat_driver); |
| } |
| |
| module_init(snd_init); |