389 lines
9.3 KiB
C
389 lines
9.3 KiB
C
|
/*
|
||
|
* 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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/i2c.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <uapi/misc/compat_saf775x_ioctl.h>
|
||
|
#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 <aruns@nvidia.com>");
|
||
|
MODULE_DESCRIPTION("SAF775X Soc Audio driver IO control");
|
||
|
MODULE_LICENSE("GPL");
|