kernel_samsung_a53x/sound/soc/samsung/vts/vts_pcm_dump.c
2024-06-15 16:02:09 -03:00

287 lines
7.2 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0-or-later
/* sound/soc/samsung/vts/vts_pcm_dump.c
*
* ALSA SoC Audio Layer - Samsung VTS Internal Buffer Dumping driver
*
* Copyright (c) 2019 Samsung Electronics Co. Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/debugfs.h>
#include <linux/spinlock.h>
#include <linux/vmalloc.h>
#include <linux/version.h>
#include <sound/samsung/abox.h>
#include <sound/samsung/vts.h>
#include <linux/pm_runtime.h>
#include "vts.h"
#include "vts_log.h"
#include "vts_dbg.h"
#include "vts_proc.h"
#include "vts_pcm_dump.h"
#define BUFFER_MAX (SZ_64)
#define NAME_LENGTH (SZ_32)
struct vts_pcm_dump_info {
struct device *dev;
struct list_head list;
int id;
char name[NAME_LENGTH];
struct mutex lock;
struct snd_dma_buffer buffer;
size_t pointer;
struct proc_dir_entry *file;
bool file_started;
size_t file_pointer;
wait_queue_head_t file_waitqueue;
bool file_created;
struct file *filp;
};
static struct proc_dir_entry *dir_dump;
static struct device *vts_pcm_dump_dev_vts;
static struct vts_pcm_dump_info vts_pcm_dump_list[BUFFER_MAX];
static LIST_HEAD(vts_pcm_dump_list_head);
static struct vts_pcm_dump_info *vts_pcm_dump_get_info(int id)
{
struct vts_pcm_dump_info *info;
list_for_each_entry(info, &vts_pcm_dump_list_head, list) {
if (info->id == id)
return info;
}
return NULL;
}
static void vts_pcm_dump_request_dump(int id, bool enable)
{
dev_dbg(vts_pcm_dump_dev_vts, "%s(%d)\n", __func__, id);
}
static ssize_t vts_pcm_dump_file_read(struct file *file, char __user *data,
size_t count, loff_t *ppos)
{
struct vts_pcm_dump_info *info = file->private_data;
struct device *dev = info->dev;
struct vts_data *vts_data = dev_get_drvdata(dev);
size_t end, pointer;
ssize_t size;
int ret;
dev_dbg(dev, "%s(%#zx)\n", __func__, count);
do {
pointer = READ_ONCE(info->pointer);
end = (info->file_pointer <= pointer) ? pointer :
info->buffer.bytes;
if (end > info->buffer.bytes) {
dev_err(dev, "pointer Error=%#zx file_pointer=%#zx end=%#zx buffer.bytes=%#zx\n",
pointer, info->file_pointer,
end, info->buffer.bytes);
end = info->buffer.bytes;
}
size = min(end - info->file_pointer, count);
dev_dbg(dev, "pointer=%#zx file_pointer=%#zx size=%#zx\n",
pointer, info->file_pointer, size);
if (!size) {
if (file->f_flags & O_NONBLOCK) {
dev_err(dev, "%d size is 0", info->id);
return -EAGAIN;
}
ret = wait_event_interruptible(info->file_waitqueue,
pointer != READ_ONCE(info->pointer));
if (ret < 0)
return ret;
}
} while (!size);
if (vts_data->running) {
if (copy_to_user(data,
info->buffer.area + info->file_pointer, size))
return -EFAULT;
info->file_pointer += size;
info->file_pointer %= info->buffer.bytes;
} else {
dev_err(dev, "%s (%d) : Wrong VTS status RESET", __func__, info->id);
info->file_pointer = 0;
info->pointer = 0;
}
return size;
}
static int vts_pcm_dump_file_open(struct inode *i, struct file *f)
{
struct vts_pcm_dump_info *info = vts_dump_get_data(f);
struct device *dev = info->dev;
dev_info(dev, "%s : %d\n", __func__, info->id);
f->private_data = info;
info->file_started = true;
info->file_pointer = 0;
info->pointer = 0;
vts_pcm_dump_request_dump(info->id, true);
return 0;
}
static int vts_pcm_dump_file_release(struct inode *i, struct file *f)
{
struct vts_pcm_dump_info *info = vts_dump_get_data(f);
struct device *dev = info->dev;
dev_info(dev, "%s : %d\n", __func__, info->id);
info->file_started = false;
info->pointer = 1;
wake_up_interruptible(&info->file_waitqueue);
vts_pcm_dump_request_dump(info->id, false);
return 0;
}
static unsigned int vts_pcm_dump_file_poll(struct file *file, poll_table *wait)
{
struct vts_pcm_dump_info *info = file->private_data;
dev_dbg(info->dev, "%s\n", __func__);
poll_wait(file, &info->file_waitqueue, wait);
return POLLIN | POLLRDNORM;
}
static const struct proc_ops vts_pcm_dump_fops = {
.proc_lseek = generic_file_llseek,
.proc_read = vts_pcm_dump_file_read,
.proc_poll = vts_pcm_dump_file_poll,
.proc_open = vts_pcm_dump_file_open,
.proc_release = vts_pcm_dump_file_release,
};
void vts_pcm_dump_period_elapsed(int id, size_t pointer)
{
struct vts_pcm_dump_info *info = vts_pcm_dump_get_info(id);
struct device *dev = info->dev;
dev_dbg(dev, "%s[%d](%zx)\n", __func__, id, pointer);
info->pointer = pointer;
wake_up_interruptible(&info->file_waitqueue);
}
static void vts_pcm_dump_check_buffer(struct snd_dma_buffer *buffer)
{
if (!buffer->bytes)
buffer->bytes = SZ_64K * 5;
if (!buffer->area) {
/* area will be used in kernel only.
* kmalloc and virt_to_phys are just enough.
*/
buffer->area = devm_kmalloc(vts_pcm_dump_dev_vts, buffer->bytes,
GFP_KERNEL);
buffer->addr = virt_to_phys(buffer->area);
}
}
struct proc_dir_entry *vts_pcm_dump_register_file(const char *name, void *data,
const struct proc_ops *fops)
{
if (IS_ERR_OR_NULL(dir_dump))
dir_dump = vts_proc_mkdir("dump", NULL);
return vts_proc_create_file(name, 0664, dir_dump, fops, data, 0);
}
void vts_pcm_dump_unregister_file(struct proc_dir_entry *file)
{
vts_proc_remove_file(file);
}
void *vts_dump_get_data(const struct file *file)
{
return vts_proc_data(file);
}
void vts_pcm_dump_register_work_func(struct work_struct *work)
{
int id;
struct vts_pcm_dump_info *info;
for (info = &vts_pcm_dump_list[0]; (info - &vts_pcm_dump_list[0]) <
ARRAY_SIZE(vts_pcm_dump_list); info++) {
id = info->id;
if (info->dev && !vts_pcm_dump_get_info(id)) {
vts_pcm_dump_check_buffer(&info->buffer);
dev_dbg(info->dev, "%s(%d, %s, %#zx)\n", __func__,
id, info->name, info->buffer.bytes);
info->file = vts_pcm_dump_register_file(info->name,
info, &vts_pcm_dump_fops);
init_waitqueue_head(&info->file_waitqueue);
list_add_tail(&info->list, &vts_pcm_dump_list_head);
}
}
}
static DECLARE_WORK(vts_pcm_dump_register_work,
vts_pcm_dump_register_work_func);
int vts_pcm_dump_register(struct device *dev, int id, const char *name,
void *area, phys_addr_t addr, size_t bytes)
{
struct vts_pcm_dump_info *info;
dev_info(dev, "%s[%d](%s, %#zx)\n", __func__, id, name, bytes);
if (id < 0 || id >= ARRAY_SIZE(vts_pcm_dump_list)) {
dev_err(dev, "invalid id: %d\n", id);
return -EINVAL;
}
info = &vts_pcm_dump_list[id];
if (!strcmp(info->name, name)) {
dev_dbg(dev, "already registered dump: %d\n", id);
return 0;
}
mutex_init(&info->lock);
info->id = id;
strncpy(info->name, name, sizeof(info->name) - 1);
info->buffer.area = area;
info->buffer.addr = addr;
info->buffer.bytes = bytes;
vts_pcm_dump_dev_vts = info->dev = dev;
schedule_work(&vts_pcm_dump_register_work);
return 0;
}
void vts_pcm_dump_init(struct device *dev_vts)
{
dev_dbg(dev_vts, "%s\n", __func__);
vts_pcm_dump_dev_vts = dev_vts;
}
bool vts_pcm_dump_get_file_started(int id)
{
struct vts_pcm_dump_info *info = vts_pcm_dump_get_info(id);
return info->file_started;
}
void vts_pcm_dump_reset(int id)
{
struct vts_pcm_dump_info *info = vts_pcm_dump_get_info(id);
dev_info(vts_pcm_dump_dev_vts, "%s by %d", __func__, id);
info->file_pointer = 0;
info->pointer = 0;
}