/* * drivers/media/m2m1shot-testdev.c * * Copyright (C) 2014 Samsung Electronics Co., Ltd. * * Contact: Cho KyongHo * * 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 #include #include #include #include #include #include #include #include #include #include #include #include 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);