| /* |
| * drivers/media/m2m1shot-testdev.c |
| * |
| * Copyright (C) 2014 Samsung Electronics Co., Ltd. |
| * |
| * Contact: Cho KyongHo <pullip.cho@samsung.com> |
| * |
| * 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. |
| * |
| * 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. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/device.h> |
| #include <linux/uaccess.h> |
| #include <linux/platform_device.h> |
| #include <linux/jiffies.h> |
| #include <linux/videodev2.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/list.h> |
| #include <linux/kthread.h> |
| #include <linux/freezer.h> |
| |
| #include <media/m2m1shot.h> |
| #include <media/m2m1shot-helper.h> |
| |
| struct m2m1shot_testdev_drvdata { |
| struct m2m1shot_device *m21dev; |
| struct task_struct *thread; |
| wait_queue_head_t waitqueue; |
| struct list_head task_list; |
| spinlock_t lock; |
| }; |
| |
| #define M2M1SHOT_TESTDEV_IOC_TIMEOUT _IOW('T', 0, unsigned long) |
| |
| struct m2m1shot_testdev_fmt { |
| __u32 fmt; |
| const char *fmt_name; |
| __u8 mbpp[3]; /* bytes * 2 per pixel: should be divided by 2 */ |
| __u8 planes; |
| __u8 buf_mbpp[3]; /* bytes * 2 per pixel: should be divided by 2 */ |
| __u8 buf_planes; |
| }; |
| |
| #define TO_MBPP(bpp) ((bpp) << 2) |
| #define TO_MBPPDIV(bpp_diven, bpp_diver) (((bpp_diven) << 2) / (bpp_diver)) |
| #define TO_BPP(mbpp) ((mbpp) >> 2) |
| |
| static struct m2m1shot_testdev_fmt m2m1shot_testdev_fmtlist[] = { |
| { |
| .fmt = V4L2_PIX_FMT_RGB565, |
| .fmt_name = "RGB565", |
| .mbpp = { TO_MBPP(2), 0, 0}, |
| .planes = 1, |
| .buf_mbpp = { TO_MBPP(2), 0, 0}, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_BGR32, |
| .fmt_name = "BGR32", |
| .mbpp = { TO_MBPP(4), 0, 0 }, |
| .planes = 1, |
| .buf_mbpp = { TO_MBPP(4), 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_RGB32, |
| .fmt_name = "RGB32", |
| .mbpp = { TO_MBPP(4), 0, 0 }, |
| .planes = 1, |
| .buf_mbpp = { TO_MBPP(4), 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_YUV420, |
| .fmt_name = "YUV4:2:0", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4), TO_MBPPDIV(1, 4) }, |
| .planes = 3, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV12, |
| .fmt_name = "Y/CbCr4:2:0(NV12)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV21, |
| .fmt_name = "Y/CrCb4:2:0(NV21)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV12M, |
| .fmt_name = "Y/CbCr4:2:0(NV12M)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), |
| 0 }, |
| .buf_planes = 2, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV21M, |
| .fmt_name = "Y/CrCb4:2:0(NV21M)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 4) + TO_MBPPDIV(1, 4), |
| 0 }, |
| .buf_planes = 2, |
| }, { |
| .fmt = V4L2_PIX_FMT_YUV422P, |
| .fmt_name = "YUV4:2:2", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 2), TO_MBPPDIV(1, 2) }, |
| .planes = 3, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV16, |
| .fmt_name = "Y/CbCr4:2:2(NV16)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_NV61, |
| .fmt_name = "Y/CrCb4:2:2(NV61)", |
| .mbpp = { TO_MBPP(1), TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), 0 }, |
| .planes = 2, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_YUYV, |
| .fmt_name = "YUV4:2:2(YUYV)", |
| .mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), |
| 0, 0}, |
| .planes = 1, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPPDIV(1, 2) + TO_MBPPDIV(1, 2), |
| 0, 0 }, |
| .buf_planes = 1, |
| }, { |
| .fmt = V4L2_PIX_FMT_YUV444, |
| .fmt_name = "YUV4:4:4", |
| .mbpp = { TO_MBPP(1), TO_MBPP(1), TO_MBPP(1) }, |
| .planes = 3, |
| .buf_mbpp = { TO_MBPP(1) + TO_MBPP(1) + TO_MBPP(1), 0, 0 }, |
| .buf_planes = 1, |
| }, |
| }; |
| |
| static int m2m1shot_testdev_init_context(struct m2m1shot_context *ctx) |
| { |
| ctx->priv = NULL; /* no timeout generated */ |
| |
| return 0; |
| } |
| |
| static int m2m1shot_testdev_free_context(struct m2m1shot_context *ctx) |
| { |
| return 0; |
| } |
| |
| static int m2m1shot_testdev_prepare_format(struct m2m1shot_context *ctx, |
| struct m2m1shot_pix_format *fmt, |
| enum dma_data_direction dir, |
| size_t bytes_used[]) |
| { |
| size_t i, j; |
| for (i = 0; i < ARRAY_SIZE(m2m1shot_testdev_fmtlist); i++) { |
| if (fmt->fmt == m2m1shot_testdev_fmtlist[i].fmt) |
| break; |
| } |
| |
| if (i == ARRAY_SIZE(m2m1shot_testdev_fmtlist)) { |
| dev_err(ctx->m21dev->dev, "Unknown format %#x\n", fmt->fmt); |
| return -EINVAL; |
| } |
| |
| for (j = 0; j < m2m1shot_testdev_fmtlist[i].buf_planes; j++) |
| bytes_used[j] = TO_BPP(m2m1shot_testdev_fmtlist[i].buf_mbpp[j] * |
| fmt->width * fmt->height); |
| return m2m1shot_testdev_fmtlist[i].buf_planes; |
| } |
| |
| static int m2m1shot_testdev_prepare_buffer(struct m2m1shot_context *ctx, |
| struct m2m1shot_buffer_dma *dma_buffer, |
| int plane, |
| enum dma_data_direction dir) |
| { |
| return m2m1shot_map_dma_buf(ctx->m21dev->dev, |
| &dma_buffer->plane[plane], dir); |
| } |
| |
| static void m2m1shot_testdev_finish_buffer(struct m2m1shot_context *ctx, |
| struct m2m1shot_buffer_dma *dma_buffer, |
| int plane, |
| enum dma_data_direction dir) |
| { |
| m2m1shot_unmap_dma_buf(ctx->m21dev->dev, &dma_buffer->plane[plane], dir); |
| } |
| |
| struct m2m1shot_testdev_work { |
| struct list_head node; |
| struct m2m1shot_context *ctx; |
| struct m2m1shot_task *task; |
| }; |
| |
| static int m2m1shot_testdev_worker(void *data) |
| { |
| struct m2m1shot_testdev_drvdata *drvdata = data; |
| |
| while (true) { |
| struct m2m1shot_testdev_work *work; |
| struct m2m1shot_task *task; |
| |
| wait_event_freezable(drvdata->waitqueue, |
| !list_empty(&drvdata->task_list)); |
| |
| spin_lock(&drvdata->lock); |
| BUG_ON(list_empty(&drvdata->task_list)); |
| work = list_first_entry(&drvdata->task_list, |
| struct m2m1shot_testdev_work, node); |
| list_del(&work->node); |
| BUG_ON(!list_empty(&drvdata->task_list)); |
| spin_unlock(&drvdata->lock); |
| |
| msleep(20); |
| |
| task = m2m1shot_get_current_task(drvdata->m21dev); |
| BUG_ON(!task); |
| |
| BUG_ON(task != work->task); |
| BUG_ON(task->ctx != work->ctx); |
| |
| kfree(work); |
| |
| m2m1shot_task_finish(drvdata->m21dev, task, true); |
| } |
| |
| return 0; |
| |
| } |
| |
| static int m2m1shot_testdev_device_run(struct m2m1shot_context *ctx, |
| struct m2m1shot_task *task) |
| { |
| struct m2m1shot_testdev_work *work; |
| struct device *dev = ctx->m21dev->dev; |
| struct m2m1shot_testdev_drvdata *drvdata = dev_get_drvdata(dev); |
| |
| if (ctx->priv) /* timeout generated */ |
| return 0; |
| |
| work = kmalloc(sizeof(*work), GFP_KERNEL); |
| if (!work) { |
| pr_err("%s: Failed to allocate work struct\n", __func__); |
| return -ENOMEM; |
| } |
| |
| INIT_LIST_HEAD(&work->node); |
| work->ctx = ctx; |
| work->task = task; |
| |
| spin_lock(&drvdata->lock); |
| BUG_ON(!list_empty(&drvdata->task_list)); |
| list_add_tail(&work->node, &drvdata->task_list); |
| spin_unlock(&drvdata->lock); |
| |
| if (current != drvdata->thread); |
| wake_up(&drvdata->waitqueue); |
| |
| return 0; |
| } |
| |
| static void m2m1shot_testdev_timeout_task(struct m2m1shot_context *ctx, |
| struct m2m1shot_task *task) |
| { |
| dev_info(ctx->m21dev->dev, "%s: Timeout occurred\n", __func__); |
| } |
| |
| static long m2m1shot_testdev_ioctl(struct m2m1shot_context *ctx, |
| unsigned int cmd, unsigned long arg) |
| { |
| unsigned long timeout; |
| |
| if (cmd != M2M1SHOT_TESTDEV_IOC_TIMEOUT) { |
| dev_err(ctx->m21dev->dev, "%s: Unknown ioctl cmd %#x\n", |
| __func__, cmd); |
| return -ENOSYS; |
| } |
| |
| if (get_user(timeout, (unsigned long __user *)arg)) { |
| dev_err(ctx->m21dev->dev, |
| "%s: Failed to read user data\n", __func__); |
| return -EFAULT; |
| } |
| |
| if (timeout) |
| ctx->priv = (void *)1; /* timeout generated */ |
| else |
| ctx->priv = NULL; /* timeout not generated */ |
| |
| dev_info(ctx->m21dev->dev, "%s: Timeout geration is %s", |
| __func__, timeout ? "set" : "unset"); |
| |
| return 0; |
| } |
| |
| static const struct m2m1shot_devops m2m1shot_testdev_ops = { |
| .init_context = m2m1shot_testdev_init_context, |
| .free_context = m2m1shot_testdev_free_context, |
| .prepare_format = m2m1shot_testdev_prepare_format, |
| .prepare_buffer = m2m1shot_testdev_prepare_buffer, |
| .finish_buffer = m2m1shot_testdev_finish_buffer, |
| .device_run = m2m1shot_testdev_device_run, |
| .timeout_task = m2m1shot_testdev_timeout_task, |
| .custom_ioctl = m2m1shot_testdev_ioctl, |
| }; |
| |
| static struct platform_device m2m1shot_testdev_pdev = { |
| .name = "m2m1shot_testdev", |
| }; |
| |
| static int m2m1shot_testdev_init(void) |
| { |
| int ret = 0; |
| struct m2m1shot_device *m21dev; |
| struct m2m1shot_testdev_drvdata *drvdata; |
| |
| drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); |
| if (!drvdata) { |
| pr_err("%s: Failed allocate drvdata\n", __func__); |
| return -ENOMEM; |
| } |
| ret = platform_device_register(&m2m1shot_testdev_pdev); |
| if (ret) { |
| pr_err("%s: Failed to register platform device\n", __func__); |
| goto err_register; |
| } |
| |
| m21dev = m2m1shot_create_device(&m2m1shot_testdev_pdev.dev, |
| &m2m1shot_testdev_ops, "testdev", -1, msecs_to_jiffies(500)); |
| if (IS_ERR(m21dev)) { |
| pr_err("%s: Failed to create m2m1shot device\n", __func__); |
| ret = PTR_ERR(m21dev); |
| goto err_create; |
| } |
| |
| drvdata->thread = kthread_run(m2m1shot_testdev_worker, drvdata, |
| "%s", "m2m1shot_tesdev_worker"); |
| if (IS_ERR(drvdata->thread)) { |
| pr_err("%s: Failed create worker thread\n", __func__); |
| ret = PTR_ERR(drvdata->thread); |
| goto err_worker; |
| } |
| |
| drvdata->m21dev = m21dev; |
| INIT_LIST_HEAD(&drvdata->task_list); |
| spin_lock_init(&drvdata->lock); |
| init_waitqueue_head(&drvdata->waitqueue); |
| |
| dev_set_drvdata(&m2m1shot_testdev_pdev.dev, drvdata); |
| |
| return 0; |
| |
| err_worker: |
| m2m1shot_destroy_device(m21dev); |
| err_create: |
| platform_device_unregister(&m2m1shot_testdev_pdev); |
| err_register: |
| kfree(drvdata); |
| return ret; |
| } |
| module_init(m2m1shot_testdev_init); |