Bug: 156978017 Change-Id: Ie8a7b18bfb62b7fc4345dfff9cd286b5a810a90c Signed-off-by: Super Liu <supercjliu@google.com>
408 lines
11 KiB
C
408 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/input.h>
|
|
#include <linux/input/heatmap.h>
|
|
#include <media/videobuf2-vmalloc.h>
|
|
|
|
static const int FIRST_FREE_NODE = -1;
|
|
|
|
/**
|
|
* Optimization: keep track of how many consecutive frames
|
|
* have been dropped due to not having any buffers available.
|
|
* If too many have been recently dropped, and still no free buffers
|
|
* are available, then skip the bus read.
|
|
* This situation could happen if an app have opened the video device,
|
|
* but went into paused state, and did not close the video device in
|
|
* onPause. With this optimization, we would avoid the wasteful bus reads
|
|
* when no one is likely to consume the buffers.
|
|
*/
|
|
static const unsigned int NUM_BUFFERS_BEFORE_DROP = 3;
|
|
static unsigned int consecutive_frames_dropped;
|
|
|
|
struct heatmap_vb2_buffer {
|
|
struct vb2_v4l2_buffer v4l2_vb;
|
|
struct list_head list;
|
|
};
|
|
|
|
static int heatmap_set_input(
|
|
struct v4l2_heatmap *v4l2, unsigned int input_index)
|
|
{
|
|
struct v4l2_pix_format *fmt = &v4l2->format;
|
|
|
|
if (input_index != 0)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Changing the input implies a format change, which is not allowed
|
|
* while buffers for use with streaming have already been allocated.
|
|
*/
|
|
if (vb2_is_busy(&v4l2->queue))
|
|
return -EBUSY;
|
|
|
|
v4l2->input_index = input_index;
|
|
|
|
fmt->width = v4l2->width;
|
|
fmt->height = v4l2->height;
|
|
fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
|
|
fmt->field = V4L2_FIELD_NONE;
|
|
fmt->colorspace = V4L2_COLORSPACE_RAW;
|
|
fmt->bytesperline = fmt->width * sizeof(strength_t);
|
|
fmt->sizeimage = fmt->width * fmt->height * sizeof(strength_t);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline struct heatmap_vb2_buffer *to_heatmap_vb2_buffer(
|
|
struct vb2_buffer *vb2)
|
|
{
|
|
return container_of(to_vb2_v4l2_buffer(vb2), struct heatmap_vb2_buffer,
|
|
v4l2_vb);
|
|
}
|
|
|
|
static const struct v4l2_file_operations heatmap_video_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = v4l2_fh_open,
|
|
.release = vb2_fop_release,
|
|
.unlocked_ioctl = video_ioctl2,
|
|
.read = vb2_fop_read,
|
|
.mmap = vb2_fop_mmap,
|
|
.poll = vb2_fop_poll,
|
|
};
|
|
|
|
void heatmap_read(struct v4l2_heatmap *v4l2, uint64_t timestamp)
|
|
{
|
|
struct heatmap_vb2_buffer *new_buf;
|
|
struct vb2_buffer *vb2_buf;
|
|
strength_t *data;
|
|
int total_bytes = v4l2->format.sizeimage;
|
|
bool read_success;
|
|
|
|
if (!vb2_is_streaming(&v4l2->queue)) {
|
|
/* No need to read, no one is viewing the video */
|
|
return;
|
|
}
|
|
|
|
/* Optimization */
|
|
if (consecutive_frames_dropped >= NUM_BUFFERS_BEFORE_DROP) {
|
|
spin_lock(&v4l2->heatmap_lock);
|
|
if (list_empty(&v4l2->heatmap_buffer_list)) {
|
|
/*
|
|
* Already dropped some frames, and still don't have
|
|
* any free buffers. A buffer could become available
|
|
* during read_frame(..), but given that we already
|
|
* dropped some frames, this is unlikely.
|
|
*/
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
return; /* Drop the frame */
|
|
}
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
}
|
|
|
|
/* This is a potentially slow operation */
|
|
read_success = v4l2->read_frame(v4l2);
|
|
if (!read_success)
|
|
return;
|
|
|
|
/* Copy the data into the buffer */
|
|
spin_lock(&v4l2->heatmap_lock);
|
|
if (list_empty(&v4l2->heatmap_buffer_list)) {
|
|
/*
|
|
* If streaming is off, then there would
|
|
* be no queued buffers. This is expected.
|
|
* On the other hand, if there is a consumer, but there
|
|
* aren't any available buffers, then this indicates
|
|
* slowness in the userspace for reading or
|
|
* processing buffers.
|
|
*/
|
|
dev_warn(v4l2->parent_dev, "heatmap: No buffers available, dropping frame\n");
|
|
consecutive_frames_dropped++;
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
return;
|
|
}
|
|
consecutive_frames_dropped = 0;
|
|
new_buf = list_entry(v4l2->heatmap_buffer_list.next,
|
|
struct heatmap_vb2_buffer, list);
|
|
list_del(&new_buf->list);
|
|
|
|
vb2_buf = &new_buf->v4l2_vb.vb2_buf;
|
|
data = vb2_plane_vaddr(vb2_buf, 0);
|
|
if (!data) {
|
|
dev_err(v4l2->parent_dev, "heatmap: Error acquiring frame pointer\n");
|
|
vb2_buffer_done(vb2_buf, VB2_BUF_STATE_ERROR);
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
return;
|
|
}
|
|
|
|
memcpy(data, v4l2->frame, total_bytes);
|
|
vb2_set_plane_payload(vb2_buf, /* plane number */ 0, total_bytes);
|
|
vb2_buf->timestamp = timestamp;
|
|
vb2_buffer_done(vb2_buf, VB2_BUF_STATE_DONE);
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
}
|
|
EXPORT_SYMBOL(heatmap_read);
|
|
|
|
static void heatmap_buffer_queue(struct vb2_buffer *vb)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = vb2_get_drv_priv(vb->vb2_queue);
|
|
struct heatmap_vb2_buffer *heatmap_buffer = to_heatmap_vb2_buffer(vb);
|
|
|
|
spin_lock(&v4l2->heatmap_lock);
|
|
list_add_tail(&heatmap_buffer->list, &v4l2->heatmap_buffer_list);
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
}
|
|
|
|
static int heatmap_queue_setup(struct vb2_queue *vq,
|
|
unsigned int *num_buffers, unsigned int *num_planes,
|
|
unsigned int sizes[], struct device *alloc_devs[])
|
|
{
|
|
struct v4l2_heatmap *v4l2 = vb2_get_drv_priv(vq);
|
|
size_t size = v4l2->format.sizeimage;
|
|
|
|
if (*num_planes != 0)
|
|
return sizes[0] < size ? -EINVAL : 0;
|
|
|
|
*num_planes = 1;
|
|
sizes[0] = size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void return_all_buffers(struct v4l2_heatmap *v4l2,
|
|
enum vb2_buffer_state state)
|
|
{
|
|
struct heatmap_vb2_buffer *buf, *node;
|
|
|
|
spin_lock(&v4l2->heatmap_lock);
|
|
list_for_each_entry_safe(buf, node, &v4l2->heatmap_buffer_list, list) {
|
|
vb2_buffer_done(&buf->v4l2_vb.vb2_buf, state);
|
|
list_del(&buf->list);
|
|
}
|
|
spin_unlock(&v4l2->heatmap_lock);
|
|
}
|
|
|
|
/*
|
|
* Stop the DMA engine. Any remaining buffers in the DMA queue are dequeued
|
|
* and passed on to the vb2 framework marked as STATE_ERROR.
|
|
*/
|
|
static void stop_streaming(struct vb2_queue *vq)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = vb2_get_drv_priv(vq);
|
|
/* Release all active buffers */
|
|
return_all_buffers(v4l2, VB2_BUF_STATE_ERROR);
|
|
}
|
|
|
|
/* V4L2 structures */
|
|
static const struct vb2_ops heatmap_queue_ops = {
|
|
.queue_setup = heatmap_queue_setup,
|
|
.buf_queue = heatmap_buffer_queue,
|
|
.stop_streaming = stop_streaming,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
.wait_finish = vb2_ops_wait_finish,
|
|
};
|
|
|
|
static const struct vb2_queue heatmap_queue = {
|
|
.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
|
.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ,
|
|
.buf_struct_size = sizeof(struct heatmap_vb2_buffer),
|
|
.ops = &heatmap_queue_ops,
|
|
.mem_ops = &vb2_vmalloc_memops,
|
|
.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC,
|
|
.min_buffers_needed = 1,
|
|
};
|
|
|
|
static int heatmap_vidioc_querycap(struct file *file, void *priv,
|
|
struct v4l2_capability *cap)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = video_drvdata(file);
|
|
strlcpy(cap->driver, v4l2->parent_dev->driver->name,
|
|
sizeof(cap->driver));
|
|
if (v4l2->input_dev != NULL) {
|
|
strlcpy(cap->card, v4l2->input_dev->name, sizeof(cap->card));
|
|
} else {
|
|
strlcpy(cap->card, KBUILD_MODNAME, sizeof(cap->card));
|
|
}
|
|
|
|
strlcpy(cap->bus_info, dev_name(v4l2->parent_dev),
|
|
sizeof(cap->bus_info));
|
|
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH |
|
|
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
|
|
return 0;
|
|
}
|
|
|
|
static int heatmap_vidioc_enum_input(struct file *file, void *priv,
|
|
struct v4l2_input *video_input)
|
|
{
|
|
if (video_input->index != 0)
|
|
return -EINVAL;
|
|
|
|
video_input->type = V4L2_INPUT_TYPE_TOUCH;
|
|
strlcpy(video_input->name, "strength", sizeof(video_input->name));
|
|
return 0;
|
|
}
|
|
|
|
static int heatmap_vidioc_s_input(
|
|
struct file *file, void *priv, unsigned int input_index)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = video_drvdata(file);
|
|
return heatmap_set_input(v4l2, input_index);
|
|
}
|
|
|
|
static int heatmap_vidioc_g_input(struct file *file, void *priv,
|
|
unsigned int *input_index)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = video_drvdata(file);
|
|
*input_index = v4l2->input_index;
|
|
return 0;
|
|
}
|
|
|
|
static int heatmap_vidioc_fmt(struct file *file, void *priv,
|
|
struct v4l2_format *fmt)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = video_drvdata(file);
|
|
|
|
fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
fmt->fmt.pix = v4l2->format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int heatmap_vidioc_enum_fmt(struct file *file, void *priv,
|
|
struct v4l2_fmtdesc *fmt)
|
|
{
|
|
if (fmt->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
if (fmt->index != 0)
|
|
return -EINVAL;
|
|
|
|
fmt->pixelformat = V4L2_TCH_FMT_DELTA_TD16;
|
|
return 0;
|
|
}
|
|
|
|
static int heatmap_vidioc_g_parm(struct file *file, void *fh,
|
|
struct v4l2_streamparm *streamparm)
|
|
{
|
|
struct v4l2_heatmap *v4l2 = video_drvdata(file);
|
|
|
|
if (streamparm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
|
return -EINVAL;
|
|
|
|
streamparm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
|
|
streamparm->parm.capture.readbuffers = 1;
|
|
streamparm->parm.capture.timeperframe.numerator =
|
|
v4l2->timeperframe.numerator;
|
|
streamparm->parm.capture.timeperframe.denominator =
|
|
v4l2->timeperframe.denominator;
|
|
return 0;
|
|
}
|
|
|
|
static const struct v4l2_ioctl_ops heatmap_video_ioctl_ops = {
|
|
.vidioc_querycap = heatmap_vidioc_querycap,
|
|
|
|
.vidioc_enum_fmt_vid_cap = heatmap_vidioc_enum_fmt,
|
|
.vidioc_s_fmt_vid_cap = heatmap_vidioc_fmt,
|
|
.vidioc_g_fmt_vid_cap = heatmap_vidioc_fmt,
|
|
.vidioc_try_fmt_vid_cap = heatmap_vidioc_fmt,
|
|
.vidioc_g_parm = heatmap_vidioc_g_parm,
|
|
|
|
.vidioc_enum_input = heatmap_vidioc_enum_input,
|
|
.vidioc_g_input = heatmap_vidioc_g_input,
|
|
.vidioc_s_input = heatmap_vidioc_s_input,
|
|
|
|
.vidioc_reqbufs = vb2_ioctl_reqbufs,
|
|
.vidioc_create_bufs = vb2_ioctl_create_bufs,
|
|
.vidioc_querybuf = vb2_ioctl_querybuf,
|
|
.vidioc_qbuf = vb2_ioctl_qbuf,
|
|
.vidioc_dqbuf = vb2_ioctl_dqbuf,
|
|
.vidioc_expbuf = vb2_ioctl_expbuf,
|
|
|
|
.vidioc_streamon = vb2_ioctl_streamon,
|
|
.vidioc_streamoff = vb2_ioctl_streamoff,
|
|
};
|
|
|
|
static const struct video_device heatmap_video_device = {
|
|
.name = "heatmap",
|
|
.fops = &heatmap_video_fops,
|
|
.ioctl_ops = &heatmap_video_ioctl_ops,
|
|
.release = video_device_release_empty,
|
|
.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TOUCH |
|
|
V4L2_CAP_READWRITE | V4L2_CAP_STREAMING,
|
|
};
|
|
|
|
int heatmap_probe(struct v4l2_heatmap *v4l2)
|
|
{
|
|
int error;
|
|
|
|
/* init channel to zero */
|
|
heatmap_set_input(v4l2, 0);
|
|
|
|
v4l2->frame = devm_kzalloc(v4l2->parent_dev,
|
|
v4l2->format.sizeimage, GFP_KERNEL);
|
|
if(!v4l2->frame) {
|
|
error = -ENOMEM;
|
|
goto err_probe;
|
|
}
|
|
|
|
/* register video device */
|
|
strlcpy(v4l2->device.name, dev_name(v4l2->parent_dev),
|
|
V4L2_DEVICE_NAME_SIZE);
|
|
error = v4l2_device_register(v4l2->parent_dev, &v4l2->device);
|
|
if (error)
|
|
goto err_free_frame_storage;
|
|
|
|
INIT_LIST_HEAD(&v4l2->heatmap_buffer_list);
|
|
|
|
/* initialize the queue */
|
|
spin_lock_init(&v4l2->heatmap_lock);
|
|
mutex_init(&v4l2->lock);
|
|
|
|
v4l2->queue = heatmap_queue;
|
|
v4l2->queue.drv_priv = v4l2;
|
|
v4l2->queue.lock = &v4l2->lock;
|
|
|
|
error = vb2_queue_init(&v4l2->queue);
|
|
if (error)
|
|
goto err_unreg_v4l2;
|
|
|
|
v4l2->vdev = heatmap_video_device;
|
|
|
|
v4l2->vdev.v4l2_dev = &v4l2->device;
|
|
v4l2->vdev.lock = &v4l2->lock;
|
|
v4l2->vdev.vfl_dir = VFL_DIR_RX;
|
|
v4l2->vdev.queue = &v4l2->queue;
|
|
video_set_drvdata(&v4l2->vdev, v4l2);
|
|
|
|
error = video_register_device(&v4l2->vdev, VFL_TYPE_TOUCH,
|
|
FIRST_FREE_NODE);
|
|
if (error)
|
|
goto err_video_device_release;
|
|
return 0;
|
|
|
|
err_video_device_release:
|
|
video_device_release(&v4l2->vdev);
|
|
|
|
err_unreg_v4l2:
|
|
v4l2_device_unregister(&v4l2->device);
|
|
err_free_frame_storage:
|
|
kfree(v4l2->frame);
|
|
err_probe:
|
|
return error;
|
|
}
|
|
EXPORT_SYMBOL(heatmap_probe);
|
|
|
|
void heatmap_remove(struct v4l2_heatmap *v4l2)
|
|
{
|
|
if (v4l2->frame) {
|
|
video_unregister_device(&v4l2->vdev);
|
|
v4l2_device_unregister(&v4l2->device);
|
|
devm_kfree(v4l2->parent_dev, v4l2->frame);
|
|
v4l2->frame = NULL;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(heatmap_remove);
|
|
|
|
MODULE_DESCRIPTION("Touchscreen heatmap video device");
|
|
MODULE_AUTHOR("Siarhei Vishniakou <svv@google.com>");
|
|
MODULE_LICENSE("GPL v2");
|