| /* |
| * 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. |
| */ |
| |
| #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 <linux/highmem.h> |
| |
| #include <media/m2m1shot2.h> |
| |
| #include "../iommu/exynos-iommu.h" |
| |
| |
| struct m2m1shot2_testdev_drvdata { |
| struct m2m1shot2_device *m21dev; |
| struct task_struct *thread; |
| wait_queue_head_t waitqueue; |
| struct list_head task_list; |
| }; |
| |
| struct m2m1shot2_testdev_work { |
| struct list_head node; |
| struct m2m1shot2_context *ctx; |
| }; |
| |
| static int m2m1shot2_testdev_init_context(struct m2m1shot2_context *ctx) |
| { |
| struct m2m1shot2_testdev_work *work; |
| |
| work = kmalloc(sizeof(*work), GFP_KERNEL); |
| if (!work) |
| return -ENOMEM; |
| |
| INIT_LIST_HEAD(&work->node); |
| work->ctx = ctx; |
| |
| ctx->priv = work; |
| return 0; |
| } |
| |
| static int m2m1shot2_testdev_free_context(struct m2m1shot2_context *ctx) |
| { |
| kfree(ctx->priv); |
| return 0; |
| } |
| |
| static int m2m1shot2_testdev_prepare_format( |
| struct m2m1shot2_context_format *fmt, |
| unsigned int index, enum dma_data_direction dir, |
| size_t payload[], unsigned int *num_planes) |
| { |
| payload[0] = fmt->fmt.width * fmt->fmt.height * 4; |
| *num_planes = 1; |
| return 0; |
| } |
| |
| static int m2m1shot2_testdev_prepare_source(struct m2m1shot2_context *ctx, |
| unsigned int index, struct m2m1shot2_source_image *img) |
| { |
| return 0; |
| } |
| |
| static int m2m1shot2_testdev_device_run(struct m2m1shot2_context *ctx) |
| { |
| struct m2m1shot2_testdev_work *work = ctx->priv; |
| struct device *dev = ctx->m21dev->dev; |
| struct m2m1shot2_testdev_drvdata *drvdata = dev_get_drvdata(dev); |
| |
| INIT_LIST_HEAD(&work->node); |
| |
| BUG_ON(!list_empty(&drvdata->task_list)); |
| list_add_tail(&work->node, &drvdata->task_list); |
| |
| /* emulation of situation that device_run is called in IRQ context */ |
| if (current != drvdata->thread) |
| wake_up(&drvdata->waitqueue); |
| |
| return 0; |
| } |
| |
| static const struct m2m1shot2_devops m2m1shot2_testdev_ops = { |
| .init_context = m2m1shot2_testdev_init_context, |
| .free_context = m2m1shot2_testdev_free_context, |
| .prepare_format = m2m1shot2_testdev_prepare_format, |
| .prepare_source = m2m1shot2_testdev_prepare_source, |
| .device_run = m2m1shot2_testdev_device_run, |
| }; |
| |
| static void m2m1shot2_testdev_process(struct m2m1shot2_testdev_drvdata *drvdata) |
| { |
| struct exynos_iommu_owner *owner = drvdata->m21dev->dev->archdata.iommu; |
| struct exynos_iovmm *vmm = owner->vmm_data; |
| struct iommu_domain *domain = vmm->domain; |
| struct m2m1shot2_context *ctx = |
| m2m1shot2_current_context(drvdata->m21dev); |
| dma_addr_t iova; |
| char *addr; |
| struct page *page; |
| unsigned int i; |
| off_t offset; |
| size_t size, len; |
| char val = 0; |
| |
| for (i = 0; i < ctx->num_sources; i++) { |
| iova = m2m1shot2_src_dma_addr(ctx, i, 0); |
| page = phys_to_page(iommu_iova_to_phys(domain, iova)); |
| addr = kmap(page) + offset_in_page(iova); |
| val += addr[0]; |
| kunmap(page); |
| } |
| |
| iova = m2m1shot2_dst_dma_addr(ctx, 0); |
| offset = offset_in_page(iova); |
| iova = iova & PAGE_MASK; |
| size = ctx->target.fmt.fmt.width * ctx->target.fmt.fmt.height * 4; |
| while (size > 0) { |
| len = min(size, PAGE_SIZE - offset); |
| size -= len; |
| |
| page = phys_to_page(iommu_iova_to_phys(domain, iova)); |
| addr = kmap(page) + offset; |
| while (len-- > 0) |
| addr[len] += val; |
| kunmap(page); |
| |
| iova += PAGE_SIZE; |
| offset = 0; |
| } |
| } |
| |
| static void m2m1shot2_testdev_irq(struct m2m1shot2_testdev_drvdata *drvdata) |
| { |
| struct m2m1shot2_testdev_work *work; |
| struct m2m1shot2_context *ctx; |
| |
| /* IRQ handler */ |
| |
| work = list_first_entry(&drvdata->task_list, |
| struct m2m1shot2_testdev_work, node); |
| list_del(&work->node); |
| BUG_ON(!list_empty(&drvdata->task_list)); |
| |
| ctx = m2m1shot2_current_context(drvdata->m21dev); |
| |
| BUG_ON(!ctx); |
| BUG_ON(ctx != work->ctx); |
| |
| m2m1shot2_finish_context(ctx, true); |
| } |
| |
| /* emulates H/W's processing */ |
| static int m2m1shot2_testdev_worker(void *data) |
| { |
| struct m2m1shot2_testdev_drvdata *drvdata = data; |
| |
| while (true) { |
| wait_event_freezable(drvdata->waitqueue, |
| !list_empty(&drvdata->task_list)); |
| |
| BUG_ON(list_empty(&drvdata->task_list)); |
| |
| m2m1shot2_testdev_process(drvdata); |
| |
| m2m1shot2_testdev_irq(drvdata); |
| } |
| |
| return 0; |
| |
| } |
| static struct platform_device m2m1shot2_testdev_pdev = { |
| .name = "m2m1shot2_testdev", |
| }; |
| |
| static int m2m1shot2_init_iovmm(struct device *dev) |
| { |
| struct exynos_iommu_owner *owner = kzalloc(sizeof(*owner), GFP_KERNEL); |
| struct exynos_iommu_domain *priv; |
| struct exynos_iovmm *vmm; |
| |
| if (!owner) { |
| dev_err(dev, "%s: failed to allocate owner data\n", __func__); |
| return -ENOMEM; |
| } |
| |
| vmm = exynos_create_single_iovmm(dev_name(dev)); |
| if (IS_ERR(vmm)) { |
| int ret = PTR_ERR(vmm); |
| |
| kfree(owner); |
| return ret; |
| } |
| |
| priv = (struct exynos_iommu_domain *)vmm->domain->priv; |
| |
| owner->vmm_data = vmm; |
| spin_lock(&priv->lock); |
| spin_unlock(&priv->lock); |
| |
| dev->archdata.iommu = owner; |
| |
| return 0; |
| } |
| |
| static void m2m1shot2_destroy_iovmm(struct device *dev) |
| { |
| pr_err("%s: nothing to do\n", __func__); |
| } |
| |
| static int m2m1shot2_testdev_init(void) |
| { |
| int ret = 0; |
| struct m2m1shot2_device *m21dev; |
| struct m2m1shot2_testdev_drvdata *drvdata; |
| |
| drvdata = kzalloc(sizeof(*drvdata), GFP_KERNEL); |
| if (!drvdata) |
| return -ENOMEM; |
| |
| ret = platform_device_register(&m2m1shot2_testdev_pdev); |
| if (ret) { |
| pr_err("%s: Failed to register platform device\n", __func__); |
| goto err_register; |
| } |
| |
| m21dev = m2m1shot2_create_device(&m2m1shot2_testdev_pdev.dev, |
| &m2m1shot2_testdev_ops, "testdev", -1, |
| M2M1SHOT2_DEVATTR_COHERENT); |
| if (IS_ERR(m21dev)) { |
| pr_err("%s: Failed to create m2m1shot device\n", __func__); |
| ret = PTR_ERR(m21dev); |
| goto err_create; |
| } |
| |
| ret = m2m1shot2_init_iovmm(&m2m1shot2_testdev_pdev.dev); |
| if (ret) { |
| pr_err("%s: Failed to initialize dummy iovmm\n", __func__); |
| goto err_iovmm; |
| } |
| |
| drvdata->thread = kthread_run(m2m1shot2_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); |
| init_waitqueue_head(&drvdata->waitqueue); |
| |
| dev_set_drvdata(&m2m1shot2_testdev_pdev.dev, drvdata); |
| |
| dev_info(&m2m1shot2_testdev_pdev.dev, |
| "%s: m2m1shot2 test device successfully initialized\n", |
| __func__); |
| return 0; |
| |
| err_worker: |
| m2m1shot2_destroy_iovmm(&m2m1shot2_testdev_pdev.dev); |
| err_iovmm: |
| m2m1shot2_destroy_device(m21dev); |
| err_create: |
| platform_device_unregister(&m2m1shot2_testdev_pdev); |
| err_register: |
| kfree(drvdata); |
| |
| pr_err("%s: Failed to initialize m2m1shot2 test device\n", __func__); |
| return ret; |
| } |
| module_init(m2m1shot2_testdev_init); |