unplugged-kernel/sound/soc/codecs/fs18xx/fsm_misc.c

295 lines
6.1 KiB
C

// SPDX-License-Identifier: GPL-2.0
/**
* Copyright (C) Fourier Semiconductor Inc. 2016-2020. All rights reserved.
* 2018-10-16 File created.
*/
#if defined(CONFIG_FSM_MISC)
#include "fsm_public.h"
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
/* i2s clock ratio = fbclk/flrclk */
#if defined(CONFIG_FSM_MTK)
#define FSM_BCLK_RATIO (64)
#else
#define FSM_BCLK_RATIO (32)
#endif
#define FSM_I2C_MAX_LEN (8192)
/* ioctl magic number and cmd. */
#define FSM_IOC_MAGIC (0x7C)
#define FSM_IOC_GET_DEVICE _IOR(FSM_IOC_MAGIC, 1, int)
#define FSM_IOC_SET_SRATE _IOW(FSM_IOC_MAGIC, 2, int)
#define FSM_IOC_SET_SCENE _IOW(FSM_IOC_MAGIC, 3, int)
#define FSM_IOC_INIT _IOW(FSM_IOC_MAGIC, 4, int)
#define FSM_IOC_SPEAKER_ON _IOW(FSM_IOC_MAGIC, 5, int)
#define FSM_IOC_SPEAKER_OFF _IOW(FSM_IOC_MAGIC, 6, int)
#define FSM_IOC_CALIBRATE _IOW(FSM_IOC_MAGIC, 7, int)
#define FSM_IOC_F0_TEST _IOW(FSM_IOC_MAGIC, 8, int)
// #define FSM_IOC_SET_SLAVE _IOW(FSM_IOC_MAGIC, 8, int)
#define FSM_IOC_SET_SLAVE (0x0706)
#define FSM_IOC_MAXNR (8)
/*
* misc driver for foursemi devices.
*/
struct fsm_i2c_cfg {
uint8_t addr;
};
static struct fsm_i2c_cfg g_i2c_cfg;
static int fsm_misc_check_params(unsigned int cmd, unsigned long arg)
{
int ret = 0;
if (cmd == FSM_IOC_SET_SLAVE) {
return 0;
}
if (_IOC_TYPE(cmd) != FSM_IOC_MAGIC || _IOC_NR(cmd) > FSM_IOC_MAXNR) {
return -ENOTTY;
}
if (_IOC_DIR(cmd) & _IOC_READ) {
ret = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
} else if (_IOC_DIR(cmd) & _IOC_WRITE) {
ret = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
}
return (ret ? -EFAULT : 0);
}
static int fsm_misc_open(struct inode *inode, struct file *filp)
{
int ret = 0;
pr_debug("enter");
return ret;
}
static int fsm_misc_set_slave(uint8_t slave)
{
struct fsm_i2c_cfg *i2c_cfg = &g_i2c_cfg;
if (i2c_cfg == NULL) {
return -EINVAL;
}
if (slave < FSM_ADDR_BASE || slave >= FSM_ADDR_BASE + FSM_DEV_MAX) {
return -EINVAL;
}
fsm_mutex_lock();
i2c_cfg->addr = slave;
if (fsm_get_fsm_dev(slave) == NULL) {
fsm_mutex_unlock();
return -EINVAL;
}
fsm_mutex_unlock();
return 0;
}
static long fsm_misc_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
int value;
int ret;
ret = fsm_misc_check_params(cmd, arg);
if (ret) {
pr_info("invalid params: %X, %lX", cmd, arg);
return ret;
}
pr_debug("cmd:%X, arg:%lX", cmd, arg);
switch (cmd) {
case FSM_IOC_SET_SLAVE:
ret = fsm_misc_set_slave(arg & 0xFF);
break;
case FSM_IOC_GET_DEVICE:
value = fsm_dev_count();
if (copy_to_user((int *)arg, &value, sizeof(value))) {
return -EFAULT;
}
break;
case FSM_IOC_SET_SRATE:
fsm_set_i2s_clocks(arg, (FSM_BCLK_RATIO * arg));
break;
case FSM_IOC_SET_SCENE:
fsm_set_scene(arg);
break;
case FSM_IOC_SPEAKER_ON:
fsm_speaker_onn();
break;
case FSM_IOC_SPEAKER_OFF:
fsm_speaker_off();
break;
case FSM_IOC_INIT:
fsm_init();
break;
case FSM_IOC_CALIBRATE:
fsm_re25_test((arg != 0));
break;
case FSM_IOC_F0_TEST:
fsm_f0_test();
break;
default:
ret = -EINVAL;
}
return ret;
}
#if defined(CONFIG_COMPAT)
static long fsm_misc_compat_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return fsm_misc_ioctl(filp, cmd, (unsigned long)compat_ptr(arg));
}
#endif
static ssize_t fsm_misc_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
{
struct fsm_i2c_cfg *i2c_cfg = &g_i2c_cfg;
struct fsm_dev *fsm_dev;
int retries = FSM_I2C_RETRY;
uint8_t *tmp;
int ret;
pr_debug("enter");
if (i2c_cfg == NULL) {
return -EINVAL;
}
if (count > FSM_I2C_MAX_LEN) {
count = FSM_I2C_MAX_LEN;
}
tmp = fsm_alloc_mem(count);
if (tmp == NULL) {
return -ENOMEM;
}
fsm_dev = fsm_get_fsm_dev(i2c_cfg->addr);
do {
if (fsm_dev == NULL || fsm_dev->i2c == NULL) {
ret = -EINVAL;
break;
}
mutex_lock(&fsm_dev->i2c_lock);
ret = i2c_master_recv(fsm_dev->i2c, tmp, count);
mutex_unlock(&fsm_dev->i2c_lock);
if (ret != count) {
fsm_delay_ms(5);
}
// pr_debug("data: 0x%02x", tmp[0]);
} while ((ret != count) && (--retries > 0));
if (ret == count) {
ret = (copy_to_user(buf, tmp, count) ? -EFAULT : ret);
} else {
pr_info("%02X: reading %zu bytes failed:%d",
i2c_cfg->addr, count, ret);
}
fsm_free_mem(tmp);
return ret;
}
static ssize_t fsm_misc_write(struct file *filp, const char __user *buf,
size_t count, loff_t *offset)
{
struct fsm_i2c_cfg *i2c_cfg = &g_i2c_cfg;
struct fsm_dev *fsm_dev;
int retries = FSM_I2C_RETRY;
uint8_t *tmp;
int ret;
pr_debug("enter");
if (i2c_cfg == NULL) {
return -EINVAL;
}
if (count > FSM_I2C_MAX_LEN) {
count = FSM_I2C_MAX_LEN;
}
tmp = memdup_user(buf, count);
if (IS_ERR(tmp)) {
return PTR_ERR(tmp);
}
fsm_dev = fsm_get_fsm_dev(i2c_cfg->addr);
do {
if (fsm_dev == NULL || fsm_dev->i2c == NULL) {
ret = -EINVAL;
break;
}
// pr_debug("data: 0x%02x-0x%02x", buf[0], tmp[0]);
mutex_lock(&fsm_dev->i2c_lock);
ret = i2c_master_send(fsm_dev->i2c, tmp, count);
mutex_unlock(&fsm_dev->i2c_lock);
if (ret != count) {
fsm_delay_ms(5);
}
} while ((ret != count) && (--retries > 0));
if (ret != count) {
pr_info("%02X: writing %zu bytes failed:%d",
i2c_cfg->addr, count, ret);
}
fsm_free_mem(tmp);
return ret;
}
static const struct file_operations g_fsm_misc_ops = {
.owner = THIS_MODULE,
.open = fsm_misc_open,
.unlocked_ioctl = fsm_misc_ioctl,
#if defined(CONFIG_COMPAT)
.compat_ioctl = fsm_misc_compat_ioctl,
#endif
.llseek = no_llseek,
.read = fsm_misc_read,
.write = fsm_misc_write,
};
struct miscdevice g_fsm_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = FSM_DRV_NAME,
.fops = &g_fsm_misc_ops,
.this_device = NULL,
};
int fsm_misc_init(void)
{
struct miscdevice *misc = &g_fsm_misc;
int ret;
pr_debug("enter");
if (misc->this_device) {
return 0;
}
ret = misc_register(misc);
if (ret) {
misc->this_device = NULL;
}
FSM_FUNC_EXIT(ret);
return ret;
}
void fsm_misc_deinit(void)
{
struct miscdevice *misc = &g_fsm_misc;
pr_debug("enter");
if (misc->this_device == NULL) {
return;
}
misc_deregister(misc);
}
#endif