226 lines
5.7 KiB
C
226 lines
5.7 KiB
C
|
/*
|
||
|
* DSPG DBMDX codec driver character device interface
|
||
|
*
|
||
|
* Copyright (C) 2014 DSP Group
|
||
|
*
|
||
|
* 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/device.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/cdev.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/kfifo.h>
|
||
|
#include <linux/delay.h>
|
||
|
|
||
|
#include "dbmdx-interface.h"
|
||
|
|
||
|
static struct dbmdx_private *dbmdx_p;
|
||
|
|
||
|
static atomic_t cdev_opened = ATOMIC_INIT(0);
|
||
|
|
||
|
/* Access to the audio buffer is controlled through "audio_owner". Either the
|
||
|
* character device or the ALSA-capture device can be opened.
|
||
|
*/
|
||
|
static int dbmdx_record_open(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
file->private_data = dbmdx_p;
|
||
|
|
||
|
if (!atomic_add_unless(&cdev_opened, 1, 1))
|
||
|
return -EBUSY;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int dbmdx_record_release(struct inode *inode, struct file *file)
|
||
|
{
|
||
|
dbmdx_p->lock(dbmdx_p);
|
||
|
dbmdx_p->va_flags.buffering = 0;
|
||
|
dbmdx_p->unlock(dbmdx_p);
|
||
|
|
||
|
flush_work(&dbmdx_p->sv_work);
|
||
|
|
||
|
atomic_dec(&cdev_opened);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* read out of the kfifo as much as was requested or if requested more
|
||
|
* as much as is in the FIFO
|
||
|
*/
|
||
|
static ssize_t dbmdx_record_read(struct file *file, char __user *buf,
|
||
|
size_t count_want, loff_t *f_pos)
|
||
|
{
|
||
|
struct dbmdx_private *p = (struct dbmdx_private *)file->private_data;
|
||
|
size_t not_copied;
|
||
|
ssize_t to_copy = count_want;
|
||
|
int avail;
|
||
|
unsigned int copied;
|
||
|
int ret;
|
||
|
|
||
|
dev_dbg(p->dbmdx_dev, "%s: count_want:%zu f_pos:%lld\n",
|
||
|
__func__, count_want, *f_pos);
|
||
|
|
||
|
avail = kfifo_len(&p->detection_samples_kfifo);
|
||
|
|
||
|
if (avail == 0)
|
||
|
return 0;
|
||
|
|
||
|
if (count_want > avail)
|
||
|
to_copy = avail;
|
||
|
|
||
|
ret = kfifo_to_user(&p->detection_samples_kfifo,
|
||
|
buf, to_copy, &copied);
|
||
|
if (ret)
|
||
|
return -EIO;
|
||
|
|
||
|
not_copied = count_want - copied;
|
||
|
*f_pos = *f_pos + (count_want - not_copied);
|
||
|
|
||
|
return count_want - not_copied;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations dbmdx_cdev_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = dbmdx_record_open,
|
||
|
.release = dbmdx_record_release,
|
||
|
.read = dbmdx_record_read,
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* read out of the kfifo as much as was requested and block until all
|
||
|
* data is available or a timeout occurs
|
||
|
*/
|
||
|
static ssize_t dbmdx_record_read_blocking(struct file *file, char __user *buf,
|
||
|
size_t count_want, loff_t *f_pos)
|
||
|
{
|
||
|
struct dbmdx_private *p = (struct dbmdx_private *)file->private_data;
|
||
|
|
||
|
size_t not_copied;
|
||
|
ssize_t to_copy = count_want;
|
||
|
int avail;
|
||
|
unsigned int copied, total_copied = 0;
|
||
|
int ret;
|
||
|
unsigned long timeout = jiffies + msecs_to_jiffies(500);
|
||
|
|
||
|
dev_dbg(p->dbmdx_dev, "%s: count_want:%zu f_pos:%lld\n",
|
||
|
__func__, count_want, *f_pos);
|
||
|
|
||
|
avail = kfifo_len(&p->detection_samples_kfifo);
|
||
|
|
||
|
while ((total_copied < count_want) &&
|
||
|
time_before(jiffies, timeout) && avail) {
|
||
|
to_copy = avail;
|
||
|
if (count_want - total_copied < avail)
|
||
|
to_copy = count_want - total_copied;
|
||
|
|
||
|
ret = kfifo_to_user(&p->detection_samples_kfifo,
|
||
|
buf + total_copied, to_copy, &copied);
|
||
|
if (ret)
|
||
|
return -EIO;
|
||
|
|
||
|
total_copied += copied;
|
||
|
|
||
|
avail = kfifo_len(&p->detection_samples_kfifo);
|
||
|
if (avail == 0 && p->va_flags.buffering)
|
||
|
usleep_range(100000, 110000);
|
||
|
}
|
||
|
|
||
|
if (avail && (total_copied < count_want))
|
||
|
dev_err(p->dev, "dbmdx: timeout during reading\n");
|
||
|
|
||
|
not_copied = count_want - total_copied;
|
||
|
*f_pos = *f_pos + (count_want - not_copied);
|
||
|
|
||
|
return count_want - not_copied;
|
||
|
}
|
||
|
|
||
|
static const struct file_operations dbmdx_cdev_block_fops = {
|
||
|
.owner = THIS_MODULE,
|
||
|
.open = dbmdx_record_open,
|
||
|
.release = dbmdx_record_release,
|
||
|
.read = dbmdx_record_read_blocking,
|
||
|
};
|
||
|
|
||
|
int dbmdx_register_cdev(struct dbmdx_private *p)
|
||
|
{
|
||
|
int ret;
|
||
|
|
||
|
dbmdx_p = p;
|
||
|
|
||
|
ret = alloc_chrdev_region(&p->record_chrdev, 0, 2, "dbmdx");
|
||
|
if (ret) {
|
||
|
dev_err(p->dbmdx_dev, "failed to allocate character device\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
cdev_init(&p->record_cdev, &dbmdx_cdev_fops);
|
||
|
cdev_init(&p->record_cdev_block, &dbmdx_cdev_block_fops);
|
||
|
|
||
|
p->record_cdev.owner = THIS_MODULE;
|
||
|
p->record_cdev_block.owner = THIS_MODULE;
|
||
|
|
||
|
ret = cdev_add(&p->record_cdev, p->record_chrdev, 1);
|
||
|
if (ret) {
|
||
|
dev_err(p->dbmdx_dev, "failed to add character device\n");
|
||
|
unregister_chrdev_region(p->record_chrdev, 1);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = cdev_add(&p->record_cdev_block, p->record_chrdev + 1, 1);
|
||
|
if (ret) {
|
||
|
dev_err(p->dbmdx_dev,
|
||
|
"failed to add blocking character device\n");
|
||
|
unregister_chrdev_region(p->record_chrdev, 1);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
p->record_dev = device_create(p->ns_class, NULL,
|
||
|
MKDEV(MAJOR(p->record_chrdev), 0),
|
||
|
p, "dbmdx-%d", 0);
|
||
|
if (IS_ERR(p->record_dev)) {
|
||
|
dev_err(p->dev, "could not create device\n");
|
||
|
unregister_chrdev_region(p->record_chrdev, 1);
|
||
|
cdev_del(&p->record_cdev);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
p->record_dev_block = device_create(p->ns_class, NULL,
|
||
|
MKDEV(MAJOR(p->record_chrdev), 1),
|
||
|
p, "dbmdx-%d", 1);
|
||
|
if (IS_ERR(p->record_dev_block)) {
|
||
|
dev_err(p->dev, "could not create device\n");
|
||
|
unregister_chrdev_region(p->record_chrdev, 1);
|
||
|
cdev_del(&p->record_cdev_block);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL(dbmdx_register_cdev);
|
||
|
|
||
|
void dbmdx_deregister_cdev(struct dbmdx_private *p)
|
||
|
{
|
||
|
if (p->record_dev) {
|
||
|
device_unregister(p->record_dev);
|
||
|
cdev_del(&p->record_cdev);
|
||
|
}
|
||
|
if (p->record_dev_block) {
|
||
|
device_unregister(p->record_dev_block);
|
||
|
cdev_del(&p->record_cdev_block);
|
||
|
}
|
||
|
unregister_chrdev_region(p->record_chrdev, 2);
|
||
|
|
||
|
dbmdx_p = NULL;
|
||
|
}
|
||
|
EXPORT_SYMBOL(dbmdx_deregister_cdev);
|