/* * saf775x_ioctl.c -- SAF775X Soc Audio driver IO control * * Copyright (c) 2014-2019 NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include "saf775x_ioctl.h" #define MAX_LEN 24 #define CHECK_MAX_AND_ADJUST(__val_len__)\ do { if (__val_len__ > MAX_LEN) __val_len__ = MAX_LEN; } while (0) static struct saf775x_ioctl_ops saf775x_ops; static struct saf775x_device_interfaces saf775x_devifs; unsigned int saf775x_curr_if = SPI; static long saf775x_hwdep_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct saf775x_cmd __user *_saf775x = (struct saf775x_cmd *)arg; struct saf775x_control_param __user *_param = (struct saf775x_control_param *)arg; struct saf775x_control_info __user *_info = (struct saf775x_control_info *)arg; struct saf775x_control_param param = {{0}}; struct saf775x_cmd saf775x = {0}; struct saf775x_control_info info; int ret = 0; unsigned char *buf; void *codec_priv; struct device *dev; unsigned long __user val; if (saf775x_curr_if == SPI) { codec_priv = saf775x_devifs.spi; dev = &(saf775x_devifs.spi)->dev; } else { codec_priv = saf775x_devifs.client; dev = &(saf775x_devifs.client)->dev; } if ((!dev) && (cmd != SAF775X_CONTROL_SETIF)) { pr_err("Device not initialised\n"); return -EFAULT; } switch (cmd) { case SAF775X_CONTROL_SET_IOCTL: if (arg && copy_from_user(&saf775x, _saf775x, sizeof(saf775x))) return -EFAULT; CHECK_MAX_AND_ADJUST(saf775x.val_len); if (saf775x_ops.codec_write) ret = saf775x_ops.codec_write(codec_priv, saf775x.reg, saf775x.val, saf775x.reg_len, saf775x.val_len); else ret = -EFAULT; break; case SAF775x_CODEC_RESET_IOCTL: saf775x_ops.codec_reset(); break; case SAF775X_CONTROL_GET_IOCTL: if (arg && copy_from_user(&saf775x, _saf775x, sizeof(saf775x))) return -EFAULT; /* safe way to get userspace address */ if (get_user(val, &_saf775x->val)) return -EFAULT; if (saf775x_curr_if == SPI) saf775x.val_len++; CHECK_MAX_AND_ADJUST(saf775x.val_len); buf = devm_kzalloc(dev, sizeof(*buf) * saf775x.val_len, GFP_KERNEL); if (!buf) { dev_err(dev, "Failed to allocate memory for codec read buffer"); return -ENOMEM; } if (saf775x_ops.codec_read) { ret = saf775x_ops.codec_read(codec_priv, buf, saf775x.val_len); if (saf775x_curr_if == I2C) { if (copy_to_user((void __user *)val, buf, sizeof(*buf) * saf775x.val_len)) { devm_kfree(dev, buf); return -EFAULT; } } else { if (copy_to_user((void __user *)val, buf+1, sizeof(*buf) * (saf775x.val_len-1))) { devm_kfree(dev, buf); return -EFAULT; } } } else ret = -EFAULT; devm_kfree(dev, buf); break; case SAF775X_CONTROL_GET_MIXER: if (arg && copy_from_user(&info, _info, sizeof(info))) return -EFAULT; if (saf775x_ops.codec_get_ctrl) { ret = saf775x_ops.codec_get_ctrl(codec_priv, &info); if (ret) break; if (copy_to_user((struct saf775x_control_info *)_info, &info, sizeof(info))) return -EFAULT; } else ret = -EFAULT; break; case SAF775X_CONTROL_SET_MIXER: if (arg && copy_from_user(¶m, _param, sizeof(param))) return -EFAULT; ret = saf775x_ops.codec_set_ctrl(codec_priv, param.name, param.reg, param.val, param.num_reg); break; case SAF775X_CONTROL_SETIF: if (saf775x_set_active_if(arg) < 0) return -EFAULT; break; case SAF775X_CONTROL_GETIF: return saf775x_curr_if; case SAF775X_CONTROL_KEYCODE: if (arg && copy_from_user(&saf775x, _saf775x, sizeof(saf775x))) return -EFAULT; /* safe way to get userspace address */ if (get_user(val, &_saf775x->val)) return -EFAULT; if (saf775x.val_len == 0) { dev_err(dev, "Failed to wrong length value"); return -EFAULT; } CHECK_MAX_AND_ADJUST(saf775x.val_len); buf = devm_kzalloc(dev, sizeof(*buf) * saf775x.val_len, GFP_KERNEL); if (!buf) { dev_err(dev, "Failed to allocate memory for codec read buffer"); return -ENOMEM; } if (copy_from_user(buf, (void __user *)val, sizeof(*buf) * saf775x.val_len)) { devm_kfree(dev, buf); return -EFAULT; } if (saf775x_ops.codec_write_keycode) { ret = saf775x_ops.codec_write_keycode(codec_priv, saf775x.reg, buf, saf775x.reg_len, saf775x.val_len); } else ret = -EFAULT; devm_kfree(dev, buf); break; default: return -EFAULT; } return ret; } #if defined(CONFIG_SPI_MASTER) static ssize_t saf775x_hwdep_write(struct file *filp, const char __user *_buf, size_t size, loff_t *off) { char *data; int ret; struct spi_device *codec_priv = saf775x_devifs.spi; data = devm_kzalloc(&codec_priv->dev, sizeof(*data) * size, GFP_KERNEL); if (!data) { dev_err(&codec_priv->dev, "Failed to allocate memory for flash image buffer"); return -ENOMEM; } if (saf775x_ops.codec_flash) { if (copy_from_user(data, _buf, sizeof(*data) * size)) { devm_kfree(&codec_priv->dev, data); return -EFAULT; } ret = saf775x_ops.codec_flash(codec_priv, data, size); devm_kfree(&codec_priv->dev, data); if (ret < 0) return ret; return size; } else { return -EFAULT; } } static ssize_t saf775x_hwdep_read(struct file *filp, char __user *_buf, size_t size, loff_t *off) { char *data; int ret; struct spi_device *codec_priv = saf775x_devifs.spi; data = devm_kzalloc(&codec_priv->dev, sizeof(*data) * size, GFP_KERNEL); if (!data) { dev_err(&codec_priv->dev, "Failed to allocate memory for status read buffer"); return -ENOMEM; } if (saf775x_ops.codec_read_status) { ret = saf775x_ops.codec_read_status(codec_priv, data, size); if (ret < 0) { devm_kfree(&codec_priv->dev, data); return ret; } if (copy_to_user(_buf, data, sizeof(*data) * size)) { devm_kfree(&codec_priv->dev, data); return -EFAULT; } devm_kfree(&codec_priv->dev, data); return size; } else { return -EFAULT; } } #endif static int saf775x_ioctl_open(struct inode *inp, struct file *filep) { return 0; } static int saf775x_ioctl_release(struct inode *inp, struct file *filep) { return 0; } static int saf775x_ioctl_major; static struct cdev saf775x_ioctl_cdev; static struct class *saf775x_ioctl_class; static const struct file_operations saf775x_ioctl_fops = { .owner = THIS_MODULE, .open = saf775x_ioctl_open, .release = saf775x_ioctl_release, .unlocked_ioctl = saf775x_hwdep_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = compat_saf775x_hwdep_ioctl, #endif #if defined(CONFIG_SPI_MASTER) .write = saf775x_hwdep_write, .read = saf775x_hwdep_read, #endif }; static void saf775x_ioctl_cleanup(void) { cdev_del(&saf775x_ioctl_cdev); device_destroy(saf775x_ioctl_class, MKDEV(saf775x_ioctl_major, 0)); if (saf775x_ioctl_class) class_destroy(saf775x_ioctl_class); unregister_chrdev_region(MKDEV(saf775x_ioctl_major, 0), 1); } int saf775x_hwdep_create(void) { int result; int ret = -ENODEV; dev_t saf775x_ioctl_dev; result = alloc_chrdev_region(&saf775x_ioctl_dev, 0, 1, "saf775x_hwdep"); if (result < 0) goto fail_err; saf775x_ioctl_major = MAJOR(saf775x_ioctl_dev); cdev_init(&saf775x_ioctl_cdev, &saf775x_ioctl_fops); saf775x_ioctl_cdev.owner = THIS_MODULE; saf775x_ioctl_cdev.ops = &saf775x_ioctl_fops; result = cdev_add(&saf775x_ioctl_cdev, saf775x_ioctl_dev, 1); if (result < 0) goto fail_chrdev; saf775x_ioctl_class = class_create(THIS_MODULE, "saf775x_hwdep"); if (IS_ERR(saf775x_ioctl_class)) { pr_err(KERN_ERR "saf775x_hwdep: device class file already in use.\n"); saf775x_ioctl_cleanup(); return PTR_ERR(saf775x_ioctl_class); } device_create(saf775x_ioctl_class, NULL, MKDEV(saf775x_ioctl_major, 0), NULL, "%s", "saf775x_hwdep"); return 0; fail_chrdev: unregister_chrdev_region(saf775x_ioctl_dev, 1); fail_err: return ret; } EXPORT_SYMBOL_GPL(saf775x_hwdep_create); int saf775x_hwdep_cleanup(void) { saf775x_ioctl_cleanup(); return 0; } EXPORT_SYMBOL_GPL(saf775x_hwdep_cleanup); struct saf775x_ioctl_ops *saf775x_get_ioctl_ops(void) { return &saf775x_ops; } EXPORT_SYMBOL_GPL(saf775x_get_ioctl_ops); struct saf775x_device_interfaces *saf775x_get_devifs(void) { return &saf775x_devifs; } EXPORT_SYMBOL_GPL(saf775x_get_devifs); unsigned int saf775x_get_active_if(void) { return saf775x_curr_if; } EXPORT_SYMBOL_GPL(saf775x_get_active_if); int saf775x_set_active_if(unsigned int id) { if ((id != SPI) && (id != I2C)) return -1; #if !defined(CONFIG_SPI_MASTER) if (id == SPI) return -1; #endif #if !defined(CONFIG_I2C) && !defined(CONFIG_I2C_MODULE) if (id == I2C) return -1; #endif saf775x_curr_if = id; return 0; } MODULE_AUTHOR("Arun S L "); MODULE_DESCRIPTION("SAF775X Soc Audio driver IO control"); MODULE_LICENSE("GPL");