/* arch/arm/mach-msm/qdsp5/snd_cad.c * * interface to "snd" service on the baseband cpu * This code also borrows from snd.c, which is * Copyright (C) 2008 HTC Corporation * Copyright (c) 2009, 2012 The Linux Foundation. All rights reserved. * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include struct snd_cad_ctxt { struct mutex lock; int opened; struct msm_rpc_endpoint *ept; struct msm_cad_endpoints *cad_epts; }; struct snd_cad_sys_ctxt { struct mutex lock; struct msm_rpc_endpoint *ept; }; struct snd_curr_dev_info { int rx_dev; int tx_dev; }; static struct snd_cad_sys_ctxt the_snd_cad_sys; static struct snd_cad_ctxt the_snd; static struct snd_curr_dev_info curr_dev; #define RPC_SND_PROG 0x30000002 #define RPC_SND_CB_PROG 0x31000002 #define RPC_SND_VERS 0x00030003 #define SND_CAD_SET_DEVICE_PROC 40 #define SND_CAD_SET_VOLUME_PROC 39 #define MAX_SND_ACTIVE_DEVICE 2 struct rpc_cad_set_device_args { struct cad_devices_type device; uint32_t ear_mute; uint32_t mic_mute; uint32_t cb_func; uint32_t client_data; }; struct rpc_cad_set_volume_args { struct cad_devices_type device; uint32_t method; uint32_t volume; uint32_t cb_func; uint32_t client_data; }; struct snd_cad_set_device_msg { struct rpc_request_hdr hdr; struct rpc_cad_set_device_args args; }; struct snd_cad_set_volume_msg { struct rpc_request_hdr hdr; struct rpc_cad_set_volume_args args; }; struct cad_endpoint *get_cad_endpoints(int *size); #ifdef CONFIG_DEBUG_FS static struct dentry *dentry; static int rtc_getdevice_dbg_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; MM_INFO("debug intf %s\n", (char *) file->private_data); return 0; } static ssize_t rtc_getdevice_dbg_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int n = 0; static char *buffer; static char *swap_buf; const int debug_bufmax = 1024; int swap_count = 0; int rc = 0; int dev_count = 0; int dev_id = 0; struct msm_cad_endpoints *msm_cad_epts = the_snd.cad_epts; struct cad_endpoint *cad_epts; buffer = kmalloc(sizeof(char) * 1024, GFP_KERNEL); if (buffer == NULL) { MM_ERR("Memory allocation failed for buffer failed\n"); return -EFAULT; } swap_buf = kmalloc(sizeof(char) * 1024, GFP_KERNEL); if (swap_buf == NULL) { MM_ERR("Memory allocation failed for swap buffer failed\n"); kfree(buffer); return -EFAULT; } if (msm_cad_epts->num <= 0) { dev_count = 0; n = scnprintf(buffer, debug_bufmax, "DEV_NO:0x%x\n", msm_cad_epts->num); } else { for (dev_id = 0; dev_id < msm_cad_epts->num; dev_id++) { cad_epts = &msm_cad_epts->endpoints[dev_id]; if (IS_ERR(cad_epts)) { MM_ERR("invalid snd endpoint for dev_id %d\n", dev_id); rc = PTR_ERR(cad_epts); continue; } if ((cad_epts->id != curr_dev.tx_dev) && (cad_epts->id != curr_dev.rx_dev)) continue; n += scnprintf(swap_buf + n, debug_bufmax - n, "ACDB_ID:0x%x;CAPB:0x%x\n", cad_epts->id, cad_epts->capability); dev_count++; MM_DBG("RTC Get Device %x Capb %x Dev Count %x\n", dev_id, cad_epts->capability, dev_count); } } swap_count = scnprintf(buffer, debug_bufmax, \ "DEV_NO:0x%x\n", dev_count); memcpy(buffer+swap_count, swap_buf, n*sizeof(char)); n = n+swap_count; buffer[n] = 0; rc = simple_read_from_buffer(buf, count, ppos, buffer, n); kfree(buffer); kfree(swap_buf); return rc; } static const struct file_operations rtc_acdb_debug_fops = { .open = rtc_getdevice_dbg_open, .read = rtc_getdevice_dbg_read }; static int rtc_debugfs_create_entry(void) { int rc = 0; char name[sizeof "rtc_get_device"+1]; snprintf(name, sizeof name, "rtc_get_device"); dentry = debugfs_create_file(name, S_IFREG | S_IRUGO, NULL, NULL, &rtc_acdb_debug_fops); if (IS_ERR(dentry)) { MM_ERR("debugfs_create_file failed\n"); rc = PTR_ERR(dentry); } return rc; } #else static int rtc_debugfs_create_entry() { return 0; } #endif static inline int check_mute(int mute) { return (mute == SND_MUTE_MUTED || mute == SND_MUTE_UNMUTED) ? 0 : -EINVAL; } static int get_endpoint(struct snd_cad_ctxt *snd, unsigned long arg) { int rc = 0, index; struct msm_cad_endpoint ept; if (copy_from_user(&ept, (void __user *)arg, sizeof(ept))) { MM_ERR("cad_ioctl get endpoint: invalid read pointer\n"); return -EFAULT; } index = ept.id; if (index < 0 || index >= snd->cad_epts->num) { MM_ERR("snd_ioctl get endpoint: invalid index!\n"); return -EINVAL; } ept.id = snd->cad_epts->endpoints[index].id; strlcpy(ept.name, snd->cad_epts->endpoints[index].name, sizeof(ept.name)); if (copy_to_user((void __user *)arg, &ept, sizeof(ept))) { MM_ERR("snd_ioctl get endpoint: invalid write pointer\n"); rc = -EFAULT; } return rc; } static long snd_cad_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct snd_cad_set_device_msg dmsg; struct snd_cad_set_volume_msg vmsg; struct msm_cad_device_config dev; struct msm_cad_volume_config vol; struct snd_cad_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))) { MM_ERR("set device: invalid pointer\n"); rc = -EFAULT; break; } dmsg.args.device.rx_device = cpu_to_be32(dev.device.rx_device); dmsg.args.device.tx_device = cpu_to_be32(dev.device.tx_device); dmsg.args.device.pathtype = cpu_to_be32(dev.device.pathtype); 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) { MM_ERR("set device: invalid mute status\n"); rc = -EINVAL; break; } dmsg.args.cb_func = -1; dmsg.args.client_data = 0; curr_dev.tx_dev = dev.device.tx_device; curr_dev.rx_dev = dev.device.rx_device; MM_ERR("snd_cad_set_device %d %d %d %d\n", dev.device.rx_device, dev.device.tx_device, dev.ear_mute, dev.mic_mute); rc = msm_rpc_call(snd->ept, SND_CAD_SET_DEVICE_PROC, &dmsg, sizeof(dmsg), 5 * HZ); break; case SND_SET_VOLUME: if (copy_from_user(&vol, (void __user *) arg, sizeof(vol))) { MM_ERR("set volume: invalid pointer\n"); rc = -EFAULT; break; } vmsg.args.device.rx_device = cpu_to_be32(dev.device.rx_device); vmsg.args.device.tx_device = cpu_to_be32(dev.device.tx_device); vmsg.args.method = cpu_to_be32(vol.method); if (vol.method != SND_METHOD_VOICE && vol.method != SND_METHOD_MIDI) { MM_ERR("set volume: invalid method %d\n", vol.method); rc = -EINVAL; break; } vmsg.args.volume = cpu_to_be32(vol.volume); vmsg.args.cb_func = -1; vmsg.args.client_data = 0; MM_ERR("snd_cad_set_volume %d %d %d %d\n", vol.device.rx_device, vol.device.tx_device, vol.method, vol.volume); rc = msm_rpc_call(snd->ept, SND_CAD_SET_VOLUME_PROC, &vmsg, sizeof(vmsg), 5 * HZ); break; case SND_GET_NUM_ENDPOINTS: if (copy_to_user((void __user *)arg, &snd->cad_epts->num, sizeof(unsigned))) { MM_ERR("get endpoint: invalid pointer\n"); rc = -EFAULT; } break; case SND_GET_ENDPOINT: rc = get_endpoint(snd, arg); break; default: MM_ERR("unknown command\n"); rc = -EINVAL; break; } mutex_unlock(&snd->lock); return rc; } static int snd_cad_release(struct inode *inode, struct file *file) { struct snd_cad_ctxt *snd = file->private_data; int rc; mutex_lock(&snd->lock); rc = msm_rpc_close(snd->ept); if (rc < 0) MM_ERR("msm_rpc_close failed\n"); snd->ept = NULL; snd->opened = 0; mutex_unlock(&snd->lock); return 0; } static int snd_cad_sys_release(void) { struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; int rc = 0; mutex_lock(&snd_cad_sys->lock); rc = msm_rpc_close(snd_cad_sys->ept); if (rc < 0) MM_ERR("msm_rpc_close failed\n"); snd_cad_sys->ept = NULL; mutex_unlock(&snd_cad_sys->lock); return rc; } static int snd_cad_open(struct inode *inode, struct file *file) { struct snd_cad_ctxt *snd = &the_snd; int rc = 0; mutex_lock(&snd->lock); if (snd->opened == 0) { if (snd->ept == NULL) { snd->ept = msm_rpc_connect_compatible(RPC_SND_PROG, RPC_SND_VERS, 0); if (IS_ERR(snd->ept)) { rc = PTR_ERR(snd->ept); snd->ept = NULL; MM_ERR("cad connect failed with VERS %x\n", RPC_SND_VERS); goto err; } } file->private_data = snd; snd->opened = 1; } else { MM_ERR("snd already opened\n"); rc = -EBUSY; } err: mutex_unlock(&snd->lock); return rc; } static int snd_cad_sys_open(void) { struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; int rc = 0; mutex_lock(&snd_cad_sys->lock); if (snd_cad_sys->ept == NULL) { snd_cad_sys->ept = msm_rpc_connect_compatible(RPC_SND_PROG, RPC_SND_VERS, 0); if (IS_ERR(snd_cad_sys->ept)) { rc = PTR_ERR(snd_cad_sys->ept); snd_cad_sys->ept = NULL; MM_ERR("func %s : cad connect failed with VERS %x\n", __func__, RPC_SND_VERS); goto err; } } else MM_DBG("snd already opened\n"); err: mutex_unlock(&snd_cad_sys->lock); return rc; } static const struct file_operations snd_cad_fops = { .owner = THIS_MODULE, .open = snd_cad_open, .release = snd_cad_release, .unlocked_ioctl = snd_cad_ioctl, }; struct miscdevice snd_cad_misc = { .minor = MISC_DYNAMIC_MINOR, .name = "msm_cad", .fops = &snd_cad_fops, }; static long snd_cad_vol_enable(const char *arg) { struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; struct snd_cad_set_volume_msg vmsg; struct msm_cad_volume_config vol; int rc = 0; rc = sscanf(arg, "%d %d %d %d", &vol.device.rx_device, &vol.device.tx_device, &vol.method, &vol.volume); if (rc != 4) { MM_ERR("Invalid arguments. Usage: " \ "method> \n"); rc = -EINVAL; return rc; } vmsg.args.device.rx_device = cpu_to_be32(vol.device.rx_device); vmsg.args.device.tx_device = cpu_to_be32(vol.device.tx_device); vmsg.args.method = cpu_to_be32(vol.method); if (vol.method != SND_METHOD_VOICE && vol.method != SND_METHOD_MIDI) { MM_ERR("snd_cad_ioctl set volume: invalid method\n"); rc = -EINVAL; return rc; } vmsg.args.volume = cpu_to_be32(vol.volume); vmsg.args.cb_func = -1; vmsg.args.client_data = 0; MM_DBG("snd_cad_set_volume %d %d %d %d\n", vol.device.rx_device, vol.device.tx_device, vol.method, vol.volume); rc = msm_rpc_call(snd_cad_sys->ept, SND_CAD_SET_VOLUME_PROC, &vmsg, sizeof(vmsg), 5 * HZ); return rc; } static long snd_cad_dev_enable(const char *arg) { struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; struct snd_cad_set_device_msg dmsg; struct msm_cad_device_config dev; int rc = 0; rc = sscanf(arg, "%d %d %d %d", &dev.device.rx_device, &dev.device.tx_device, &dev.ear_mute, &dev.mic_mute); if (rc != 4) { MM_ERR("Invalid arguments. Usage: "\ " \n"); rc = -EINVAL; return rc; } dmsg.args.device.rx_device = cpu_to_be32(dev.device.rx_device); dmsg.args.device.tx_device = cpu_to_be32(dev.device.tx_device); dmsg.args.device.pathtype = cpu_to_be32(CAD_DEVICE_PATH_RX_TX); 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) { MM_ERR("snd_cad_ioctl set device: invalid mute status\n"); rc = -EINVAL; return rc; } dmsg.args.cb_func = -1; dmsg.args.client_data = 0; curr_dev.tx_dev = dev.device.tx_device; curr_dev.rx_dev = dev.device.rx_device; MM_INFO("snd_cad_set_device %d %d %d %d\n", dev.device.rx_device, dev.device.tx_device, dev.ear_mute, dev.mic_mute); rc = msm_rpc_call(snd_cad_sys->ept, SND_CAD_SET_DEVICE_PROC, &dmsg, sizeof(dmsg), 5 * HZ); return rc; } static ssize_t snd_cad_dev_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { ssize_t status; struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; int rc = 0; rc = snd_cad_sys_open(); if (rc) return rc; mutex_lock(&snd_cad_sys->lock); status = snd_cad_dev_enable(buf); mutex_unlock(&snd_cad_sys->lock); rc = snd_cad_sys_release(); if (rc) return rc; return status ? : size; } static ssize_t snd_cad_vol_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { ssize_t status; struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; int rc = 0; rc = snd_cad_sys_open(); if (rc) return rc; mutex_lock(&snd_cad_sys->lock); status = snd_cad_vol_enable(buf); mutex_unlock(&snd_cad_sys->lock); rc = snd_cad_sys_release(); if (rc) return rc; return status ? : size; } static DEVICE_ATTR(device, S_IWUSR | S_IRUGO, NULL, snd_cad_dev_store); static DEVICE_ATTR(volume, S_IWUSR | S_IRUGO, NULL, snd_cad_vol_store); static int snd_cad_probe(struct platform_device *pdev) { struct snd_cad_ctxt *snd = &the_snd; struct snd_cad_sys_ctxt *snd_cad_sys = &the_snd_cad_sys; int rc = 0; mutex_init(&snd->lock); mutex_init(&snd_cad_sys->lock); snd_cad_sys->ept = NULL; snd->cad_epts = (struct msm_cad_endpoints *)pdev->dev.platform_data; rc = misc_register(&snd_cad_misc); if (rc) return rc; rc = device_create_file(snd_cad_misc.this_device, &dev_attr_device); if (rc) { misc_deregister(&snd_cad_misc); return rc; } rc = device_create_file(snd_cad_misc.this_device, &dev_attr_volume); if (rc) { device_remove_file(snd_cad_misc.this_device, &dev_attr_device); misc_deregister(&snd_cad_misc); } #ifdef CONFIG_DEBUG_FS rc = rtc_debugfs_create_entry(); if (rc) { device_remove_file(snd_cad_misc.this_device, &dev_attr_volume); device_remove_file(snd_cad_misc.this_device, &dev_attr_device); misc_deregister(&snd_cad_misc); } #endif return rc; } static struct platform_driver snd_cad_plat_driver = { .probe = snd_cad_probe, .driver = { .name = "msm_cad", .owner = THIS_MODULE, }, }; static int __init snd_cad_init(void) { return platform_driver_register(&snd_cad_plat_driver); } module_init(snd_cad_init); MODULE_DESCRIPTION("MSM CAD SND driver"); MODULE_LICENSE("GPL v2");