[Meego-kernel] [MFLD Camera - PATCH v6 6/9] Medfield Camera Image ISP driver memory management.
Wang, Wen W
wen.w.wang at intel.com
Thu Dec 23 06:39:09 PST 2010
>From 62e903e745ba1cc635fe15f1ff4e79728199eef0 Mon Sep 17 00:00:00 2001
From: Wen Wang <wen.w.wang at intel.com>
Date: Fri, 24 Dec 2010 02:14:10 +0800
Subject: [PATCH] MFLD ISP memory management
This patch includes memory and MMU management for Medfield camera image
ISP driver. It provides functions to control MMU block in ISP and memory
allocation/free/access from ISP point of view.
Signed-off-by: Wen Wang <wen.w.wang at intel.com>
Signed-off-by: Xiaolin Zhang <xiaolin.zhang at intel.com>
---
drivers/media/video/atomisp/hmm/hmm.c | 375 ++++++
drivers/media/video/atomisp/hmm/hmm_bo.c | 1190 ++++++++++++++++++++
drivers/media/video/atomisp/hmm/hmm_bo_dev.c | 295 +++++
drivers/media/video/atomisp/hmm/hmm_vm.c | 210 ++++
drivers/media/video/atomisp/include/hmm/hmm.h | 71 ++
drivers/media/video/atomisp/include/hmm/hmm_bo.h | 313 +++++
.../media/video/atomisp/include/hmm/hmm_bo_dev.h | 124 ++
.../media/video/atomisp/include/hmm/hmm_common.h | 97 ++
drivers/media/video/atomisp/include/hmm/hmm_vm.h | 67 ++
drivers/media/video/atomisp/include/mmu/isp_mmu.h | 168 +++
drivers/media/video/atomisp/include/mmu/sh_mmu.h | 74 ++
drivers/media/video/atomisp/mmu/isp_mmu.c | 515 +++++++++
12 files changed, 3499 insertions(+), 0 deletions(-)
create mode 100644 drivers/media/video/atomisp/hmm/hmm.c
create mode 100644 drivers/media/video/atomisp/hmm/hmm_bo.c
create mode 100644 drivers/media/video/atomisp/hmm/hmm_bo_dev.c
create mode 100644 drivers/media/video/atomisp/hmm/hmm_vm.c
create mode 100644 drivers/media/video/atomisp/include/hmm/hmm.h
create mode 100644 drivers/media/video/atomisp/include/hmm/hmm_bo.h
create mode 100644 drivers/media/video/atomisp/include/hmm/hmm_bo_dev.h
create mode 100644 drivers/media/video/atomisp/include/hmm/hmm_common.h
create mode 100644 drivers/media/video/atomisp/include/hmm/hmm_vm.h
create mode 100644 drivers/media/video/atomisp/include/mmu/isp_mmu.h
create mode 100644 drivers/media/video/atomisp/include/mmu/sh_mmu.h
create mode 100644 drivers/media/video/atomisp/mmu/isp_mmu.c
diff --git a/drivers/media/video/atomisp/hmm/hmm.c b/drivers/media/video/atomisp/hmm/hmm.c
new file mode 100644
index 0000000..7f4d0de
--- /dev/null
+++ b/drivers/media/video/atomisp/hmm/hmm.c
@@ -0,0 +1,375 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * This file contains entry functions for memory management of ISP driver
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/highmem.h> /* for kmap */
+
+#include <linux/io.h> /* for page_to_phys */
+
+#include "hmm/hmm.h"
+#include "hmm/hmm_bo.h"
+#include "hmm/hmm_bo_dev.h"
+
+#include "mmu/isp_mmu.h"
+#include "mmu/sh_mmu.h"
+#include "mfldisp_internal.h"
+
+static struct hmm_bo_device bo_device;
+static void *dummy_ptr;
+
+int hmm_init()
+{
+ int ret;
+
+ ret = hmm_bo_device_init(&bo_device, &sh_mmu_driver,
+ ISP_VM_START, ISP_VM_SIZE);
+ if (ret)
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "hmm_bo_device_init failed.\n");
+
+ /*
+ * As hmm use NULL to indicate invalid ISP virtual address,
+ * and ISP_VM_START is defined to 0 too, so we allocate
+ * one piece of dummy memory, which should return value 0,
+ * at the beginning, to avoid hmm_alloc return 0 in the
+ * further allocation.
+ */
+ dummy_ptr = hmm_alloc(1, HMM_BO_PRIVATE, 0, 0);
+ return ret;
+}
+
+void hmm_cleanup()
+{
+ /*
+ * free dummy memory first
+ */
+ hmm_free(dummy_ptr);
+ dummy_ptr = NULL;
+
+ hmm_bo_device_exit(&bo_device);
+}
+
+void *hmm_alloc(size_t bytes, enum hmm_bo_type type,
+ int from_highmem, unsigned int userptr)
+{
+ unsigned int pgnr;
+ struct hmm_buffer_object *bo;
+ int ret;
+
+ /*Get page number from size*/
+ pgnr = size_to_pgnr_ceil(bytes);
+
+ /*Buffer object structure init*/
+ bo = hmm_bo_create(&bo_device, pgnr);
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev, "hmm_bo_create failed.\n");
+ goto create_bo_err;
+ }
+
+ /*Allocate virtual address in ISP virtual space*/
+ ret = hmm_bo_alloc_vm(bo);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "hmm_bo_alloc_vm failed.\n");
+ goto alloc_vm_err;
+ }
+
+ /*Allocate pages for memory*/
+ ret = hmm_bo_alloc_pages(bo, type, from_highmem, userptr);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "hmm_bo_alloc_pages failed.\n");
+ goto alloc_page_err;
+ }
+
+ /*Combind the virtual address and pages togather*/
+ ret = hmm_bo_bind(bo);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev, "hmm_bo_bind failed.\n");
+ goto bind_err;
+ }
+
+ return (void *)bo->vm_node->start;
+
+bind_err:
+ hmm_bo_free_pages(bo);
+alloc_page_err:
+ hmm_bo_free_vm(bo);
+alloc_vm_err:
+ hmm_bo_unref(bo);
+create_bo_err:
+ return NULL;
+}
+
+void hmm_free(void *virt)
+{
+ struct hmm_buffer_object *bo;
+
+ bo = hmm_bo_device_search_start(&bo_device, (unsigned int)virt);
+
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "can not find buffer object start with "
+ "address 0x%x\n", (unsigned int)virt);
+ return;
+ }
+
+ hmm_bo_unbind(bo);
+
+ hmm_bo_free_pages(bo);
+
+ hmm_bo_free_vm(bo);
+
+ hmm_bo_unref(bo);
+}
+
+static inline int hmm_check_bo(struct hmm_buffer_object *bo, unsigned int ptr)
+{
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "can not find buffer object contains "
+ "address 0x%x\n", ptr);
+ return -EINVAL;
+ }
+
+ if (!hmm_bo_page_allocated(bo)) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object has no page allocated.\n");
+ return -EINVAL;
+ }
+
+ if (!hmm_bo_vm_allocated(bo)) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object has no virtual address"
+ " space allocated.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*Read function in ISP memory management*/
+int hmm_load(void *virt, void *data, unsigned int bytes)
+{
+ unsigned int ptr;
+ struct hmm_buffer_object *bo;
+ unsigned int idx, offset, len;
+ char *src, *des;
+ int ret;
+
+ ptr = (unsigned int)virt;
+
+ bo = hmm_bo_device_search_in_range(&bo_device, ptr);
+ ret = hmm_check_bo(bo, ptr);
+ if (ret)
+ return ret;
+
+ des = (char *)data;
+ while (bytes) {
+ idx = (ptr - bo->vm_node->start) >> PAGE_SHIFT;
+ offset = (ptr - bo->vm_node->start) - (idx << PAGE_SHIFT);
+
+ src = (char *)kmap(bo->pages[idx]);
+ if (!src) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "kmap buffer object page failed: "
+ "pg_idx = %d\n", idx);
+ return -EINVAL;
+ }
+
+ src += offset;
+
+ if ((bytes + offset) >= PAGE_SIZE) {
+ len = PAGE_SIZE - offset;
+ bytes -= len;
+ } else {
+ len = bytes;
+ bytes = 0;
+ }
+
+ ptr += len; /* update ptr for next loop */
+
+ while (len--)
+ *des++ = *src++;
+
+ kunmap(bo->pages[idx]);
+ }
+
+ return 0;
+}
+
+/*Write function in ISP memory management*/
+int hmm_store(void *virt, const void *data, unsigned int bytes)
+{
+ unsigned int ptr;
+ struct hmm_buffer_object *bo;
+ unsigned int idx, offset, len;
+ char *src, *des;
+ int ret;
+
+ ptr = (unsigned int)virt;
+
+ bo = hmm_bo_device_search_in_range(&bo_device, ptr);
+ ret = hmm_check_bo(bo, ptr);
+ if (ret)
+ return ret;
+
+ src = (char *)data;
+ while (bytes) {
+ idx = (ptr - bo->vm_node->start) >> PAGE_SHIFT;
+ offset = (ptr - bo->vm_node->start) - (idx << PAGE_SHIFT);
+
+ des = (char *)kmap(bo->pages[idx]);
+ if (!des) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "kmap buffer object page failed: "
+ "pg_idx = %d\n", idx);
+ return -EINVAL;
+ }
+
+ des += offset;
+
+ if ((bytes + offset) >= PAGE_SIZE) {
+ len = PAGE_SIZE - offset;
+ bytes -= len;
+ } else {
+ len = bytes;
+ bytes = 0;
+ }
+
+ ptr += len;
+
+ while (len--)
+ *des++ = *src++;
+
+ kunmap(bo->pages[idx]);
+ }
+
+ return 0;
+}
+
+/*memset function in ISP memory management*/
+int hmm_set(void *virt, char c, unsigned int bytes)
+{
+ unsigned int ptr;
+ struct hmm_buffer_object *bo;
+ unsigned int idx, offset, len;
+ char *des;
+ int ret;
+
+ ptr = (unsigned int)virt;
+
+ bo = hmm_bo_device_search_in_range(&bo_device, ptr);
+ ret = hmm_check_bo(bo, ptr);
+ if (ret)
+ return ret;
+
+ while (bytes) {
+ idx = (ptr - bo->vm_node->start) >> PAGE_SHIFT;
+ offset = (ptr - bo->vm_node->start) - (idx << PAGE_SHIFT);
+
+ des = (char *)kmap(bo->pages[idx]);
+ if (!des) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "kmap buffer object page failed: "
+ "pg_idx = %d\n", idx);
+ return -EINVAL;
+ }
+ des += offset;
+
+ if ((bytes + offset) >= PAGE_SIZE) {
+ len = PAGE_SIZE - offset;
+ bytes -= len;
+ } else {
+ len = bytes;
+ bytes = 0;
+ }
+
+ ptr += len;
+
+ while (len--)
+ *des++ = c;
+
+ kunmap(bo->pages[idx]);
+ }
+
+ return 0;
+}
+
+/*Virtual address to physical address convert*/
+unsigned int hmm_virt_to_phys(void *virt)
+{
+ unsigned int ptr = (unsigned int)virt;
+ unsigned int idx, offset;
+ struct hmm_buffer_object *bo;
+
+ bo = hmm_bo_device_search_in_range(&bo_device, ptr);
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "can not find buffer object contains "
+ "address 0x%x\n", ptr);
+ return -1;
+ }
+
+ idx = (ptr - bo->vm_node->start) >> PAGE_SHIFT;
+ offset = (ptr - bo->vm_node->start) - (idx << PAGE_SHIFT);
+
+ return page_to_phys(bo->pages[idx]) + offset;
+}
+
+int hmm_mmap(struct vm_area_struct *vma, void *virt)
+{
+ unsigned int ptr = (unsigned int)virt;
+ struct hmm_buffer_object *bo;
+
+ bo = hmm_bo_device_search_start(&bo_device, ptr);
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "can not find buffer object start with "
+ "address 0x%x\n", (unsigned int)virt);
+ return -EINVAL;
+ }
+
+ return hmm_bo_mmap(vma, bo);
+}
+
+/*Map ISP virtual address into IA virtual address*/
+void *hmm_vmap(void *virt)
+{
+ unsigned int ptr = (unsigned int)virt;
+ struct hmm_buffer_object *bo;
+
+ bo = hmm_bo_device_search_start(&bo_device, ptr);
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "can not find buffer object start with "
+ "address 0x%x\n", (unsigned int)virt);
+ return NULL;
+ }
+
+ return hmm_bo_vmap(bo);
+}
diff --git a/drivers/media/video/atomisp/hmm/hmm_bo.c b/drivers/media/video/atomisp/hmm/hmm_bo.c
new file mode 100644
index 0000000..f1f2935
--- /dev/null
+++ b/drivers/media/video/atomisp/hmm/hmm_bo.c
@@ -0,0 +1,1190 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * This file contains functions for buffer object structure management
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/gfp.h> /* for GFP_ATOMIC */
+#include <linux/mm.h>
+#include <linux/mm_types.h>
+#include <linux/slab.h> /* for kmalloc */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <asm/cacheflush.h>
+#include <linux/io.h>
+#include <asm/current.h>
+#include <linux/sched.h>
+
+#ifdef USE_DRM_MM
+#include <drm/drm_mm.h>
+#else
+#include "hmm/hmm_vm.h"
+#endif
+
+#include "hmm/hmm_bo.h"
+#include "hmm/hmm_bo_dev.h"
+#include "hmm/hmm_common.h"
+#include "mfldisp_internal.h"
+
+#define check_bo_status_yes_goto(bo, _status, label) \
+ var_not_equal_goto((bo->status & (_status)), (_status), \
+ label, \
+ "HMM buffer status not contain %s.\n", \
+ #_status)
+
+#define check_bo_status_no_goto(bo, _status, label) \
+ var_equal_goto((bo->status & (_status)), (_status), \
+ label, \
+ "HMM buffer status contains %s.\n", \
+ #_status)
+#define HMM_MAX_ORDER 10
+
+static inline unsigned int order_to_nr(unsigned int order)
+{
+ return 1U << order;
+}
+
+static inline unsigned int nr_to_order_ceil(unsigned int nr)
+{
+ unsigned int order = 0;
+
+ for (; nr / 2; nr = nr / 2 + nr % 2)
+ order++;
+
+ return order;
+}
+
+static inline unsigned int nr_to_order_bottom(unsigned int nr)
+{
+ unsigned int order = 0;
+
+ while (nr /= 2)
+ order++;
+
+ return order;
+}
+
+static void free_bo_internal(struct hmm_buffer_object *bo)
+{
+ kfree(bo);
+}
+
+/*
+ * use these functions to dynamically alloc hmm_buffer_object.
+ * hmm_bo_init will called for that allocated buffer object, and
+ * the release callback is set to kfree.
+ */
+struct hmm_buffer_object *hmm_bo_create(struct hmm_bo_device *bdev, int pgnr)
+{
+ struct hmm_buffer_object *bo;
+ int ret;
+
+ bo = kmalloc(sizeof(*bo), GFP_KERNEL);
+ if (!bo) {
+ v4l2_printk(KERN_ERR, &isp_dev, "out of memory.\n");
+ return NULL;
+ }
+
+ ret = hmm_bo_init(bdev, bo, pgnr, free_bo_internal);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev, "hmm_bo_init failed\n");
+ kfree(bo);
+ return NULL;
+ }
+
+ return bo;
+}
+
+/*
+ * use this function to initialize pre-allocated hmm_buffer_object.
+ * as hmm_buffer_object may be used as an embedded object in an upper
+ * level object, a release callback must be provided. if it is
+ * embedded in upper level object, set release call back to release
+ * function of that object. if no upper level object, set release
+ * callback to NULL.
+ *
+ * bo->kref is inited to 1.
+ */
+int hmm_bo_init(struct hmm_bo_device *bdev,
+ struct hmm_buffer_object *bo,
+ unsigned int pgnr, void (*release) (struct hmm_buffer_object *))
+{
+ if (bdev == NULL) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "NULL hmm_bo_device.\n");
+ return -EINVAL;
+ }
+
+ /* hmm_bo_device must be already inited */
+ var_equal_return(hmm_bo_device_inited(bdev), 0, -EINVAL,
+ "hmm_bo_device not inited yet.\n");
+
+ /* prevent zero size buffer object */
+ if (pgnr == 0) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "0 size buffer is not allowed.\n");
+ return -EINVAL;
+ }
+
+ memset(bo, 0, sizeof(*bo));
+
+ kref_init(&bo->kref);
+
+ mutex_init(&bo->mutex);
+
+ INIT_LIST_HEAD(&bo->list);
+
+ bo->pgnr = pgnr;
+ bo->bdev = bdev;
+
+ INIT_LIST_HEAD(&bo->pgblocks);
+
+ bo->release = release;
+
+ if (!bo->release)
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "no release callback specified.\n");
+
+ /*
+ * add to active_bo_list
+ */
+ mutex_lock(&bdev->ablist_mutex);
+ list_add_tail(&bo->list, &bdev->active_bo_list);
+ bo->status |= HMM_BO_ACTIVE;
+ mutex_unlock(&bdev->ablist_mutex);
+
+ return 0;
+}
+
+static void hmm_bo_release(struct hmm_buffer_object *bo)
+{
+ struct hmm_bo_device *bdev;
+
+ check_bo_null_return(bo, (void)0);
+
+ bdev = bo->bdev;
+
+ /*
+ * FIX ME:
+ *
+ * how to destroy the bo when it is stilled MMAPED?
+ *
+ * ideally, this will not happened as hmm_bo_release
+ * will only be called when kref reaches 0, and in mmap
+ * operation the hmm_bo_ref will eventually be called.
+ * so, if this happened, something goes wrong.
+ */
+ if (bo->status & HMM_BO_MMAPED) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "destroy bo which is MMAPED, do nothing\n");
+ goto err;
+ }
+#ifdef HMM_BO_VMAP_SUPPORT
+ if (bo->status & HMM_BO_VMAPED) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "the bo is still vmapped, unmap it first...\n");
+ hmm_bo_vunmap(bo);
+ }
+#endif
+
+ if (bo->status & HMM_BO_BINDED) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "the bo is still binded, unbind it first...\n");
+ hmm_bo_unbind(bo);
+ }
+ if (bo->status & HMM_BO_PAGE_ALLOCED) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "the pages is not freed, free pages first\n");
+ hmm_bo_free_pages(bo);
+ }
+ if (bo->status & HMM_BO_VM_ALLOCED) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "the vm is still not freed, free vm first...\n");
+ hmm_bo_free_vm(bo);
+ }
+
+ /*
+ * remove it from buffer device's buffer object list.
+ */
+ if (hmm_bo_activated(bo)) {
+ mutex_lock(&bdev->ablist_mutex);
+ list_del(&bo->list);
+ mutex_unlock(&bdev->ablist_mutex);
+ } else {
+ mutex_lock(&bdev->fblist_mutex);
+ list_del(&bo->list);
+ mutex_unlock(&bdev->fblist_mutex);
+ }
+
+ if (bo->release)
+ bo->release(bo);
+err:
+ return;
+}
+
+int hmm_bo_activated(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, 0);
+
+ return bo->status & HMM_BO_ACTIVE;
+}
+
+void hmm_bo_unactivate(struct hmm_buffer_object *bo)
+{
+ struct hmm_bo_device *bdev;
+
+ check_bo_null_return(bo, (void)0);
+
+ check_bo_status_no_goto(bo, HMM_BO_ACTIVE, status_err);
+
+ bdev = bo->bdev;
+
+ mutex_lock(&bdev->ablist_mutex);
+ list_del(&bo->list);
+ mutex_unlock(&bdev->ablist_mutex);
+
+ mutex_lock(&bdev->fblist_mutex);
+ list_add_tail(&bo->list, &bdev->free_bo_list);
+ bo->status &= (~HMM_BO_ACTIVE);
+ mutex_unlock(&bdev->fblist_mutex);
+
+ return;
+
+status_err:
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object already unactivated.\n");
+ return;
+}
+
+int hmm_bo_alloc_vm(struct hmm_buffer_object *bo)
+{
+ struct hmm_bo_device *bdev;
+
+ check_bo_null_return(bo, -EINVAL);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_no_goto(bo, HMM_BO_VM_ALLOCED, status_err);
+
+ bdev = bo->bdev;
+
+#ifdef USE_DRM_MM
+retry:
+ ret = drm_mm_pre_get(&bdev->vaddr_space);
+ if (unlikely(ret != 0)) {
+ ret = -ENOMEM;
+ goto out_of_vm;
+ }
+
+ write_lock(&bdev->vm_lock);
+ /*
+ * unit of size parameter is page number or bytes?
+ * how to use alignment?
+ */
+ bo->vm_node = drm_mm_search_free(&bdev->vaddr_space, bo->pgnr, 0, 0);
+
+ if (unlikely(ret != 0)) {
+ ret = -ENOMEM;
+ goto out_of_vm_lock;
+ }
+
+ bo->vm_node = drm_mm_get_block_atomic(bo->vm_node, bo->pgnr, 0);
+
+ if (unlikely(ret != 0)) {
+ write_unlock(&bdev->vm_lock);
+ goto retry;
+ }
+
+ bo->status |= HMM_BO_VM_ALLOCED;
+
+ write_unlock(&bdev->vm_lock);
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+
+out_of_vm_lock:
+ write_unlock(&bdev->vm_lock);
+out_of_vm:
+ mutex_unlock(&bo->mutex);
+ return ret;
+
+#else
+ bo->vm_node = hmm_vm_alloc_node(&bdev->vaddr_space, bo->pgnr);
+ if (unlikely(!bo->vm_node)) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "hmm_vm_alloc_node err.\n");
+ goto null_vm;
+ }
+
+ bo->status |= HMM_BO_VM_ALLOCED;
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+null_vm:
+ mutex_unlock(&bo->mutex);
+ return -ENOMEM;
+#endif
+
+status_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object already has vm allocated.\n");
+ return -EINVAL;
+}
+
+void hmm_bo_free_vm(struct hmm_buffer_object *bo)
+{
+ struct hmm_bo_device *bdev;
+
+ check_bo_null_return(bo, (void)0);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo, HMM_BO_VM_ALLOCED, status_err);
+
+ bdev = bo->bdev;
+
+#ifdef USE_DRM_MM
+ write_lock(&bdev->vm_lock);
+
+ drm_mm_put_block(bo->vm_node);
+ bo->vm_node = NULL;
+
+ bo->status &= (~HMM_BO_VM_ALLOCED);
+
+ write_unlock(&bdev->vm_lock);
+#else
+ hmm_vm_free_node(bo->vm_node);
+ bo->vm_node = NULL;
+ bo->status &= (~HMM_BO_VM_ALLOCED);
+#endif
+ mutex_unlock(&bo->mutex);
+
+ return;
+
+status_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object has no vm allocated.\n");
+}
+
+int hmm_bo_vm_allocated(struct hmm_buffer_object *bo)
+{
+ int ret;
+
+ check_bo_null_return(bo, 0);
+
+ mutex_lock(&bo->mutex);
+
+ ret = (bo->status & HMM_BO_VM_ALLOCED);
+
+ mutex_unlock(&bo->mutex);
+
+ return ret;
+}
+
+#ifdef CI_VA_SHARE_BUFFER
+static int alloc_share_pages(struct hmm_buffer_object *bo, int from_highmem)
+{
+ int ret;
+
+ /*
+ * FIX ME:
+ *
+ * how to control ttm to alloc memory from/not from highmem.
+ */
+
+ ret = drm_bo_create_external(0, (bo->pgnr) << PAGE_SHIFT, &bo->handle);
+
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "unable to alloc pages: %d\n", pgnr);
+ return ret;
+ }
+
+ /*
+ * Will bo->pgnr be larger than pgnr that we want to
+ * allocate?
+ */
+ drm_bo_external_get_pages(bo->handle, &bo->pages, &bo->pgnr);
+}
+
+static void free_share_pages(struct hmm_buffer_object *bo)
+{
+ return drm_bo_external_destroy(bo->handle);
+}
+#endif
+
+/*Allocate pages which will be used only by ISP*/
+static int alloc_private_pages(struct hmm_buffer_object *bo, int from_highmem)
+{
+ int ret;
+ unsigned int pgnr, order, blk_pgnr;
+ struct page *pages;
+ struct page_block *pgblk;
+ gfp_t gfp;
+ int i, j;
+
+ gfp = GFP_KERNEL;
+ if (from_highmem)
+ gfp |= __GFP_HIGHMEM;
+
+ pgnr = bo->pgnr;
+
+ bo->pages = kmalloc(sizeof(struct page *) * pgnr, GFP_KERNEL);
+ if (unlikely(!bo->pages))
+ goto out_of_mem;
+
+ i = 0;
+ while (pgnr) {
+ order = nr_to_order_bottom(pgnr);
+ if (order > HMM_MAX_ORDER)
+ order = HMM_MAX_ORDER;
+retry:
+ pages = alloc_pages(gfp, order);
+ if (unlikely(!pages)) {
+ order--;
+ if (order < 0)
+ goto out_of_mem;
+ else
+ goto retry;
+ } else {
+ blk_pgnr = order_to_nr(order);
+
+ pgblk = kmalloc(sizeof(*pgblk), GFP_KERNEL);
+ if (unlikely(!pgblk))
+ goto out_of_mem;
+
+ INIT_LIST_HEAD(&pgblk->list);
+ pgblk->pages = pages;
+ pgblk->order = order;
+
+ list_add_tail(&pgblk->list, &bo->pgblocks);
+
+ for (j = 0; j < blk_pgnr; j++)
+ bo->pages[i++] = pages + j;
+
+ pgnr -= blk_pgnr;
+
+ /*
+ * set memory to uncacheable -- UC_MINUS
+ */
+ ret = set_pages_uc(pages, blk_pgnr);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page uncacheable failed.\n");
+ goto set_uc_mem_fail;
+ }
+ }
+ }
+
+ return 0;
+set_uc_mem_fail:
+ /* FIX ME: select one better */
+ ret = -ENOMEM;
+ goto cleanup;
+out_of_mem:
+ ret = -ENOMEM;
+ goto cleanup;
+cleanup:
+ while (!list_empty(&bo->pgblocks)) {
+ pgblk = list_first_entry(&bo->pgblocks,
+ struct page_block, list);
+
+ list_del(&pgblk->list);
+
+ ret = set_pages_wb(pgblk->pages, order_to_nr(pgblk->order));
+ if (ret)
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page to WB err...\n");
+
+ __free_pages(pgblk->pages, pgblk->order);
+ kfree(pgblk);
+ }
+
+ return ret;
+}
+
+static void free_private_pages(struct hmm_buffer_object *bo)
+{
+ struct page_block *pgblk;
+ int ret;
+
+ while (!list_empty(&bo->pgblocks)) {
+ pgblk = list_first_entry(&bo->pgblocks,
+ struct page_block, list);
+
+ list_del(&pgblk->list);
+
+ ret = set_pages_wb(pgblk->pages, order_to_nr(pgblk->order));
+ if (ret)
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page to WB err...\n");
+
+ __free_pages(pgblk->pages, pgblk->order);
+ kfree(pgblk);
+ }
+
+ kfree(bo->pages);
+}
+
+/*
+ * Convert user space virtual address into page list and update into pages
+ * tables
+ */
+static int alloc_user_pages(struct hmm_buffer_object *bo,
+ unsigned int userptr)
+{
+ unsigned int page_nr;
+ unsigned int i;
+ struct page_block *pgblk;
+ int ret;
+
+ bo->pages = kmalloc(sizeof(struct page *) * bo->pgnr, GFP_KERNEL);
+ if (unlikely(!bo->pages)) {
+ v4l2_printk(KERN_ERR, &isp_dev, "out of memory...\n");
+ return -ENOMEM;
+ }
+
+ page_nr = get_user_pages(current, current->mm, (unsigned long)userptr,
+ (int)(bo->pgnr), 1, 0, bo->pages, NULL);
+ /* can be written by caller, not forced */
+ if (page_nr != bo->pgnr) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "get_user_pages err: bo->pgnr = %d, "
+ "pgnr actually pinned = %d.\n",
+ bo->pgnr, page_nr);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < bo->pgnr; i++) {
+ pgblk = kmalloc(sizeof(*pgblk), GFP_KERNEL);
+ if (unlikely(!pgblk))
+ goto out_of_mem;
+
+ INIT_LIST_HEAD(&pgblk->list);
+ pgblk->pages = bo->pages[i];
+ pgblk->order = 0;
+
+ list_add_tail(&pgblk->list, &bo->pgblocks);
+ }
+ /* fixe me: need to set pages to uncacheable */
+
+ for (i = 0; i < bo->pgnr; i++) {
+#ifdef CONFIG_ARCH_USES_PG_UNCACHED
+ SetPageUncached(bo->pages[i]);
+#else
+ ret = set_pages_uc(bo->pages[i], 1);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page to uncacheable failed %d.\n",
+ i);
+ goto cleanup;
+ }
+#endif
+ }
+
+ return 0;
+
+out_of_mem:
+ ret = -ENOMEM;
+ goto cleanup;
+cleanup:
+ while (!list_empty(&bo->pgblocks)) {
+ pgblk = list_first_entry(&bo->pgblocks,
+ struct page_block, list);
+
+ list_del(&pgblk->list);
+
+ kfree(pgblk);
+ }
+
+ return ret;
+}
+
+static void free_user_pages(struct hmm_buffer_object *bo)
+{
+ unsigned int i;
+ struct page_block *pgblk;
+
+ for (i = 0; i < bo->pgnr; i++) {
+#ifdef CONFIG_ARCH_USES_PG_UNCACHED
+ ClearPageUncached(bo->pages[i]);
+#else
+ if (set_pages_wb(bo->pages[i], 1))
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page to WB again failed.\n");
+#endif
+ }
+
+ while (!list_empty(&bo->pgblocks)) {
+ pgblk = list_first_entry(&bo->pgblocks,
+ struct page_block, list);
+
+ list_del(&pgblk->list);
+
+ kfree(pgblk);
+ }
+
+ kfree(bo->pages);
+}
+
+/*
+ * allocate/free physical pages for the bo.
+ *
+ * type indicate where are the pages from. currently we have 3 types
+ * of memory: HMM_BO_PRIVATE, HMM_BO_USER, HMM_BO_SHARE.
+ *
+ * from_highmem is only valid when type is HMM_BO_PRIVATE, it will
+ * try to alloc memory from highmem if from_highmem is set.
+ *
+ * userptr is only valid when type is HMM_BO_USER, it indicates
+ * the start address from user space task.
+ *
+ * from_highmem and userptr will both be ignored when type is
+ * HMM_BO_SHARE.
+ */
+int hmm_bo_alloc_pages(struct hmm_buffer_object *bo,
+ enum hmm_bo_type type, int from_highmem,
+ unsigned int userptr)
+{
+ int ret;
+
+ check_bo_null_return(bo, -EINVAL);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_no_goto(bo, HMM_BO_PAGE_ALLOCED, status_err);
+
+ /*
+ * TO DO:
+ * add HMM_BO_USER type
+ */
+ if (type == HMM_BO_PRIVATE)
+ ret = alloc_private_pages(bo, from_highmem);
+ else if (type == HMM_BO_USER)
+ ret = alloc_user_pages(bo, userptr);
+#ifdef CI_VA_SHARE_BUFFER
+ else if (type == HMM_BO_SHARE)
+ ret = alloc_share_pages(bo, from_highmem);
+#endif
+ else {
+ v4l2_printk(KERN_ERR, &isp_dev, "invalid buffer type.\n");
+ ret = -EINVAL;
+ }
+
+ if (ret)
+ goto alloc_err;
+
+ bo->type = type;
+
+ bo->status |= HMM_BO_PAGE_ALLOCED;
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+
+alloc_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev, "alloc pages err...\n");
+ return ret;
+status_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object has already page allocated.\n");
+ return -EINVAL;
+}
+
+/*
+ * free physical pages of the bo.
+ */
+void hmm_bo_free_pages(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, (void)0);
+
+ mutex_lock(&bo->mutex);
+
+#ifdef HMM_BO_VMAP_SUPPORT
+ check_bo_status_no_goto(bo, HMM_BO_VMAPED, status_err1);
+#endif
+
+ check_bo_status_yes_goto(bo, HMM_BO_PAGE_ALLOCED, status_err2);
+
+ if (bo->type == HMM_BO_PRIVATE)
+ free_private_pages(bo);
+ else if (bo->type == HMM_BO_USER)
+ free_user_pages(bo);
+#ifdef CI_VA_SHARE_BUFFER
+ else if free_share_pages
+ (bo);
+#endif
+ else
+ v4l2_printk(KERN_ERR, &isp_dev, "invalid buffer type.\n");
+ /* clear the flag anyway. */
+ bo->status &= (~HMM_BO_PAGE_ALLOCED);
+
+ mutex_unlock(&bo->mutex);
+
+ return;
+
+status_err2:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object not page allocated yet.\n");
+
+#ifdef HMM_BO_VMAP_SUPPORT
+status_err1:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object still vmapped, dont free pages here.\n");
+#endif
+}
+
+int hmm_bo_page_allocated(struct hmm_buffer_object *bo)
+{
+ int ret;
+
+ check_bo_null_return(bo, 0);
+
+ mutex_lock(&bo->mutex);
+
+ ret = bo->status & HMM_BO_PAGE_ALLOCED;
+
+ mutex_unlock(&bo->mutex);
+
+ return ret;
+}
+
+/*
+ * get physical page info of the bo.
+ */
+int hmm_bo_get_page_info(struct hmm_buffer_object *bo,
+ struct page ***pages, int *pgnr)
+{
+ check_bo_null_return(bo, -EINVAL);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo, HMM_BO_PAGE_ALLOCED, status_err);
+
+ *pages = bo->pages;
+ *pgnr = bo->pgnr;
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+
+status_err:
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object not page allocated yet.\n");
+ mutex_unlock(&bo->mutex);
+ return -EINVAL;
+}
+
+/*
+ * bind the physical pages to a virtual address space.
+ */
+int hmm_bo_bind(struct hmm_buffer_object *bo)
+{
+ int ret;
+ unsigned int virt;
+ struct hmm_bo_device *bdev;
+#ifdef __MMU_MAP_BLOCK
+ struct list_head *pos;
+ struct page_block *pgblk;
+ unsigned int blk_pgnr;
+#else
+ unsigned int i;
+#endif
+
+ check_bo_null_return(bo, -EINVAL);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo,
+ HMM_BO_PAGE_ALLOCED | HMM_BO_VM_ALLOCED,
+ status_err1);
+
+ check_bo_status_no_goto(bo, HMM_BO_BINDED, status_err2);
+
+ bdev = bo->bdev;
+
+ virt = bo->vm_node->start;
+
+#ifdef __MMU_MAP_BLOCK
+ /*
+ * FIXME:
+ *
+ * bo->pgblocks is empty in case of HMM_BO_USER, so CANNOT use this way
+ * to setup address mapping.
+ * DONOT define __MMU_MAP_BLOCK macro currently.
+ */
+ list_for_each(pos, &bo->pgblocks) {
+ pgblk = list_entry(pos, struct page_block, list);
+ blk_pgnr = order_to_nr(pgblk->order);
+ ret = isp_mmu_map(&bdev->mmu, virt, page_to_phys(pgblk->pages),
+ blk_pgnr);
+ if (ret)
+ goto map_err;
+ virt += (blk_pgnr << PAGE_SHIFT);
+ }
+#else
+ for (i = 0; i < bo->pgnr; i++) {
+ ret =
+ isp_mmu_map(&bdev->mmu, virt, page_to_phys(bo->pages[i]),
+ 1);
+ if (ret)
+ goto map_err;
+ virt += (1 << PAGE_SHIFT);
+ }
+#endif
+ /*
+ * flush TBL here.
+ *
+ * theoretically, we donot need to flush TLB as we didnot change
+ * any existed address mappings, but for Silicon Hive's MMU, its
+ * really a bug here. I guess when fetching PTEs (page table entity)
+ * to TLB, its MMU will fetch additional INVALID PTEs automatically
+ * for performance issue. EX, we only set up 1 page address mapping,
+ * meaning updating 1 PTE, but the MMU fetches 4 PTE at one time,
+ * so the additional 3 PTEs are invalid.
+ */
+ isp_mmu_flush_tlb_range(&bdev->mmu, bo->vm_node->start,
+ (bo->pgnr << PAGE_SHIFT));
+
+ bo->status |= HMM_BO_BINDED;
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+
+map_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "setup MMU address mapping failed.\n");
+ return ret;
+
+status_err2:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev, "buffer object already binded.\n");
+ return -EINVAL;
+status_err1:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object vm_node or page not allocated.\n");
+ return -EINVAL;
+}
+
+/*
+ * unbind the physical pages with related virtual address space.
+ */
+void hmm_bo_unbind(struct hmm_buffer_object *bo)
+{
+ unsigned int virt;
+ struct hmm_bo_device *bdev;
+#ifdef __MMU_MAP_BLOCK
+ struct list_head *pos;
+ struct page_block *pgblk;
+ unsigned int blk_pgnr;
+#else
+ unsigned int i;
+#endif
+
+ check_bo_null_return(bo, (void)0);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo,
+ HMM_BO_PAGE_ALLOCED |
+ HMM_BO_VM_ALLOCED |
+ HMM_BO_BINDED, status_err);
+
+ bdev = bo->bdev;
+
+ virt = bo->vm_node->start;
+
+#ifdef __MMU_MAP_BLOCK
+ list_for_each(pos, &bo->pgblocks) {
+ pgblk = list_entry(pos, struct page_block, list);
+ blk_pgnr = order_to_nr(pgblk->order);
+ isp_mmu_unmap(&bdev->mmu, virt, blk_pgnr);
+ virt += pgnr_to_size(blk_pgnr);
+ }
+#else
+ for (i = 0; i < bo->pgnr; i++) {
+ isp_mmu_unmap(&bdev->mmu, virt, 1);
+ virt += pgnr_to_size(1);
+ }
+#endif
+ /*
+ * flush TLB as the address mapping has been removed and
+ * related TLBs should be invalidated.
+ */
+ isp_mmu_flush_tlb_range(&bdev->mmu, bo->vm_node->start,
+ (bo->pgnr << PAGE_SHIFT));
+
+ bo->status &= (~HMM_BO_BINDED);
+
+ mutex_unlock(&bo->mutex);
+
+ return;
+
+status_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer vm or page not allocated or not binded yet.\n");
+}
+
+int hmm_bo_binded(struct hmm_buffer_object *bo)
+{
+ int ret;
+
+ check_bo_null_return(bo, 0);
+
+ mutex_lock(&bo->mutex);
+
+ ret = bo->status & HMM_BO_BINDED;
+
+ mutex_unlock(&bo->mutex);
+
+ return ret;
+}
+
+#ifdef HMM_BO_VMAP_SUPPORT
+
+int hmm_bo_vmap(struct hmm_buffer_object *bo)
+{
+ int ret;
+
+ check_bo_null_return(bo, -EINVAL);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo, HMM_BO_PAGE_ALLOCED, status_err1);
+
+ check_bo_status_no_goto(bo, HMM_BO_VMAPED, status_err2);
+
+ bo->virt = vmap(bo->pages, bo->pgnr, VM_MAP, PAGE_KERNEL_NOCACHE);
+
+ if (bo->virt)
+ goto vmap_err;
+
+ bo->status |= HMM_BO_VMAPED;
+
+ mutex_unlock(&bo->mutex);
+
+ return 0;
+vmap_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev, "buffer object vmap failed.\n");
+ return -ENOMEM;
+status_err2:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev, "buffer object already vmapped.\n");
+ return -EINVAL;
+status_err1:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer object has no pages allocated.\n");
+ return -EINVAL;
+}
+
+void hmm_bo_vunmap(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, (void)0);
+
+ mutex_lock(&bo->mutex);
+
+ check_bo_status_yes_goto(bo, HMM_BO_VMAPED, status_err);
+
+ vunmap(bo->virt);
+
+ bo->status &= (~HMM_BO_VMAPED);
+
+ mutex_unlock(&bo->mutex);
+
+status_err:
+ mutex_unlock(&bo->mutex);
+ v4l2_printk(KERN_ERR, &isp_dev, "buffer object not vmapped.\n");
+}
+
+int hmm_bo_vmapped(struct hmm_buffer_object *bo)
+{
+ int ret;
+
+ check_bo_null_return(bo, 0);
+
+ mutex_lock(&bo->mutex);
+
+ ret = bo->status & HMM_BO_VMAPED;
+
+ mutex_unlock(&bo->mutex);
+
+ return ret;
+}
+
+#else
+
+void *hmm_bo_vmap(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, NULL);
+
+ return vmap(bo->pages, bo->pgnr, VM_MAP, PAGE_KERNEL_NOCACHE);
+}
+
+#endif
+
+void hmm_bo_ref(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, (void)0);
+
+ kref_get(&bo->kref);
+}
+
+#define kref_to_hmm_bo(kref_ptr) \
+ list_entry((kref_ptr), struct hmm_buffer_object, kref)
+
+static void kref_hmm_bo_release(struct kref *kref)
+{
+ if (!kref)
+ return;
+
+ hmm_bo_release(kref_to_hmm_bo(kref));
+}
+
+void hmm_bo_unref(struct hmm_buffer_object *bo)
+{
+ check_bo_null_return(bo, (void)0);
+
+ kref_put(&bo->kref, kref_hmm_bo_release);
+}
+
+static void hmm_bo_vm_open(struct vm_area_struct *vma)
+{
+ struct hmm_buffer_object *bo =
+ (struct hmm_buffer_object *)vma->vm_private_data;
+
+ check_bo_null_return(bo, (void)0);
+
+ hmm_bo_ref(bo);
+
+ mutex_lock(&bo->mutex);
+
+ bo->status |= HMM_BO_MMAPED;
+
+ bo->mmap_count++;
+
+ mutex_unlock(&bo->mutex);
+}
+
+static void hmm_bo_vm_close(struct vm_area_struct *vma)
+{
+ struct hmm_buffer_object *bo =
+ (struct hmm_buffer_object *)vma->vm_private_data;
+
+ check_bo_null_return(bo, (void)0);
+
+ hmm_bo_unref(bo);
+
+ mutex_lock(&bo->mutex);
+
+ bo->mmap_count--;
+
+ if (!bo->mmap_count) {
+ bo->status &= (~HMM_BO_MMAPED);
+ vma->vm_private_data = NULL;
+ }
+
+ mutex_unlock(&bo->mutex);
+}
+
+static const struct vm_operations_struct hmm_bo_vm_ops = {
+ .open = hmm_bo_vm_open,
+ .close = hmm_bo_vm_close,
+};
+
+/*
+ * mmap the bo to user space.
+ */
+int hmm_bo_mmap(struct vm_area_struct *vma, struct hmm_buffer_object *bo)
+{
+ unsigned int start, end;
+ unsigned int virt;
+ unsigned int pgnr, i;
+ unsigned int pfn;
+
+ check_bo_null_return(bo, -EINVAL);
+
+ check_bo_status_yes_goto(bo, HMM_BO_PAGE_ALLOCED, status_err);
+
+ pgnr = bo->pgnr;
+ start = vma->vm_start;
+ end = vma->vm_end;
+
+ /*
+ * check vma's virtual address space size and buffer object's size.
+ * must be the same.
+ */
+ if ((start + pgnr_to_size(pgnr)) != end) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "vma's address space size not equal"
+ " to buffer object's size");
+ return -EINVAL;
+ }
+
+ virt = vma->vm_start;
+ for (i = 0; i < pgnr; i++) {
+ pfn = page_to_pfn(bo->pages[i]);
+ if (remap_pfn_range(vma, virt, pfn, PAGE_SIZE, PAGE_SHARED)) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "remap_pfn_range failed:"
+ " virt = 0x%x, pfn = 0x%x,"
+ " mapped_pgnr = %d\n", virt, pfn, 1);
+ return -EINVAL;
+ }
+ virt += PAGE_SIZE;
+ }
+
+ vma->vm_private_data = bo;
+
+ vma->vm_ops = &hmm_bo_vm_ops;
+ vma->vm_flags |= (VM_RESERVED | VM_IO);
+
+ /*
+ * call hmm_bo_vm_open explictly.
+ */
+ hmm_bo_vm_open(vma);
+
+ return 0;
+
+status_err:
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "buffer page not allocated yet.\n");
+ return -EINVAL;
+}
diff --git a/drivers/media/video/atomisp/hmm/hmm_bo_dev.c b/drivers/media/video/atomisp/hmm/hmm_bo_dev.c
new file mode 100644
index 0000000..5fed475
--- /dev/null
+++ b/drivers/media/video/atomisp/hmm/hmm_bo_dev.c
@@ -0,0 +1,295 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/gfp.h>
+#include <linux/mm.h> /* for GFP_ATOMIC */
+#include <linux/slab.h> /* for kmalloc */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+
+#include "hmm/hmm_common.h"
+#include "hmm/hmm_bo_dev.h"
+#include "hmm/hmm_bo.h"
+#include "mfldisp_internal.h"
+
+/*
+ * hmm_bo_device functions.
+ */
+int hmm_bo_device_init(struct hmm_bo_device *bdev,
+ struct isp_mmu_driver *mmu_driver,
+ unsigned int vaddr_start, unsigned int size)
+{
+ int ret;
+
+ check_bodev_null_return(bdev, -EINVAL);
+
+ ret = isp_mmu_init(&bdev->mmu, mmu_driver);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev, "isp_mmu_init failed.\n");
+ goto isp_mmu_init_err;
+ }
+
+#ifdef USE_DRM_MM
+ ret = drm_mm_init(&bdev->vaddr_space, vaddr_start, size);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev, "drm_mm_init falied. "
+ "vaddr_start = 0x%x, size = %d\n", vaddr_start,
+ size);
+ goto vm_init_err;
+ }
+ rwlock_init(&bdev->vm_lock);
+#else
+ ret = hmm_vm_init(&bdev->vaddr_space, vaddr_start, size);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev, "hmm_vm_init falied. "
+ "vaddr_start = 0x%x, size = %d\n", vaddr_start,
+ size);
+ goto vm_init_err;
+ }
+#endif
+
+ INIT_LIST_HEAD(&bdev->free_bo_list);
+ INIT_LIST_HEAD(&bdev->active_bo_list);
+
+ mutex_init(&bdev->fblist_mutex);
+ mutex_init(&bdev->ablist_mutex);
+
+ bdev->flag = HMM_BO_DEVICE_INITED;
+
+ return 0;
+
+vm_init_err:
+ isp_mmu_exit(&bdev->mmu);
+isp_mmu_init_err:
+ return ret;
+}
+
+void hmm_bo_device_exit(struct hmm_bo_device *bdev)
+{
+ check_bodev_null_return(bdev, (void)0);
+
+ /*
+ * destroy all bos in the bo list, even they are in use.
+ */
+ if (!list_empty(&bdev->active_bo_list))
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "there're still activated bo in use. "
+ "force to free them.\n");
+
+ while (!list_empty(&bdev->active_bo_list))
+ hmm_bo_unref(list_to_hmm_bo(bdev->active_bo_list.next));
+
+ if (!list_empty(&bdev->free_bo_list))
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "there're still bo in free_bo_list. "
+ "force to free them.\n");
+
+ while (!list_empty(&bdev->free_bo_list))
+ hmm_bo_unref(list_to_hmm_bo(bdev->free_bo_list.next));
+
+ isp_mmu_exit(&bdev->mmu);
+
+#ifdef USE_DRM_MM
+ drm_mm_takedown(&bdev->vaddr_space);
+#else
+ hmm_vm_clean(&bdev->vaddr_space);
+#endif
+}
+
+int hmm_bo_device_inited(struct hmm_bo_device *bdev)
+{
+ check_bodev_null_return(bdev, -EINVAL);
+
+ return bdev->flag == HMM_BO_DEVICE_INITED;
+}
+
+/*
+ * find the buffer object with virtual address vaddr.
+ * return NULL if no such buffer object found.
+ */
+struct hmm_buffer_object *hmm_bo_device_search_start(struct hmm_bo_device *bdev,
+ unsigned int vaddr)
+{
+ struct list_head *pos;
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, NULL);
+
+ mutex_lock(&bdev->ablist_mutex);
+ list_for_each(pos, &bdev->active_bo_list) {
+ bo = list_to_hmm_bo(pos);
+ /* pass bo which has no vm_node allocated */
+ if (!hmm_bo_vm_allocated(bo))
+ continue;
+ if (bo->vm_node->start == vaddr)
+ goto found;
+ }
+ mutex_unlock(&bdev->ablist_mutex);
+ return NULL;
+found:
+ mutex_unlock(&bdev->ablist_mutex);
+ return bo;
+}
+
+static int in_range(unsigned int start, unsigned int size, unsigned int addr)
+{
+ return (start <= addr) && (start + size > addr);
+}
+
+struct hmm_buffer_object *hmm_bo_device_search_in_range(struct hmm_bo_device
+ *bdev,
+ unsigned int vaddr)
+{
+ struct list_head *pos;
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, NULL);
+
+ mutex_lock(&bdev->fblist_mutex);
+ list_for_each(pos, &bdev->active_bo_list) {
+ bo = list_to_hmm_bo(pos);
+ /* pass bo which has no vm_node allocated */
+ if (!hmm_bo_vm_allocated(bo))
+ continue;
+ if (in_range(bo->vm_node->start, bo->vm_node->size, vaddr))
+ goto found;
+ }
+ mutex_unlock(&bdev->fblist_mutex);
+ return NULL;
+found:
+ mutex_unlock(&bdev->fblist_mutex);
+ return bo;
+}
+
+/*
+ * find a buffer object with pgnr pages from free_bo_list and
+ * activate it (remove from free_bo_list and add to
+ * active_bo_list)
+ *
+ * return NULL if no such buffer object found.
+ */
+struct hmm_buffer_object *hmm_bo_device_get_bo(struct hmm_bo_device *bdev,
+ unsigned int pgnr)
+{
+ struct list_head *pos;
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, NULL);
+
+ mutex_lock(&bdev->fblist_mutex);
+ list_for_each(pos, &bdev->free_bo_list) {
+ bo = list_to_hmm_bo(pos);
+ if (bo->pgnr == pgnr)
+ goto found;
+ }
+ mutex_unlock(&bdev->fblist_mutex);
+ return NULL;
+found:
+ list_del(&bo->list);
+ mutex_unlock(&bdev->fblist_mutex);
+
+ mutex_lock(&bdev->ablist_mutex);
+ list_add(&bo->list, &bdev->active_bo_list);
+ mutex_unlock(&bdev->ablist_mutex);
+
+ return bo;
+}
+
+/*
+ * destroy all buffer objects in the free_bo_list.
+ */
+void hmm_bo_device_destroy_free_bo_list(struct hmm_bo_device *bdev)
+{
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, (void)0);
+
+ mutex_lock(&bdev->fblist_mutex);
+ while (!list_empty(&bdev->free_bo_list)) {
+ bo = list_first_entry(&bdev->free_bo_list,
+ struct hmm_buffer_object, list);
+
+ list_del(&bo->list);
+ hmm_bo_unref(bo);
+ }
+ mutex_unlock(&bdev->fblist_mutex);
+}
+
+/*
+ * destroy buffer object with start virtual address vaddr.
+ */
+void hmm_bo_device_destroy_free_bo_addr(struct hmm_bo_device *bdev,
+ unsigned int vaddr)
+{
+ struct list_head *pos;
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, (void)0);
+
+ mutex_lock(&bdev->fblist_mutex);
+ list_for_each(pos, &bdev->free_bo_list) {
+ bo = list_to_hmm_bo(pos);
+ /* pass bo which has no vm_node allocated */
+ if (!hmm_bo_vm_allocated(bo))
+ continue;
+ if (bo->vm_node->start == vaddr)
+ goto found;
+ }
+ mutex_unlock(&bdev->fblist_mutex);
+ return;
+found:
+ list_del(&bo->list);
+ mutex_unlock(&bdev->fblist_mutex);
+ hmm_bo_unref(bo);
+}
+
+/*
+ * destroy all buffer objects with pgnr pages.
+ */
+void hmm_bo_device_destroy_free_bo_size(struct hmm_bo_device *bdev,
+ unsigned int pgnr)
+{
+ struct list_head *pos;
+ struct hmm_buffer_object *bo;
+
+ check_bodev_null_return(bdev, (void)0);
+
+retry:
+ mutex_lock(&bdev->fblist_mutex);
+ list_for_each(pos, &bdev->free_bo_list) {
+ bo = list_to_hmm_bo(pos);
+ if (bo->pgnr == pgnr)
+ goto found;
+ }
+ mutex_unlock(&bdev->fblist_mutex);
+ return;
+found:
+ list_del(&bo->list);
+ mutex_unlock(&bdev->fblist_mutex);
+ hmm_bo_unref(bo);
+ goto retry;
+}
diff --git a/drivers/media/video/atomisp/hmm/hmm_vm.c b/drivers/media/video/atomisp/hmm/hmm_vm.c
new file mode 100644
index 0000000..336b1cb
--- /dev/null
+++ b/drivers/media/video/atomisp/hmm/hmm_vm.c
@@ -0,0 +1,210 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * This file contains function for ISP virtual address management in ISP driver
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <asm/page.h>
+
+#include "mmu/isp_mmu.h"
+#include "hmm/hmm_vm.h"
+#include "hmm/hmm_common.h"
+#include "mfldisp_internal.h"
+
+static unsigned int vm_node_end(unsigned int start, unsigned int pgnr)
+{
+ return start + pgnr_to_size(pgnr);
+}
+
+static int addr_in_vm_node(unsigned int addr,
+ struct hmm_vm_node *node)
+{
+ return (addr >= node->start) && (addr < (node->start + node->size));
+}
+
+int hmm_vm_init(struct hmm_vm *vm, unsigned int start,
+ unsigned int size)
+{
+ if (!vm)
+ return -1;
+
+ vm->start = start;
+ vm->pgnr = size_to_pgnr_ceil(size);
+ vm->size = pgnr_to_size(vm->pgnr);
+
+ INIT_LIST_HEAD(&vm->vm_node_list);
+ spin_lock_init(&vm->lock);
+
+ return 0;
+}
+
+void hmm_vm_clean(struct hmm_vm *vm)
+{
+ if (!vm)
+ return;
+
+ while (!list_empty(&vm->vm_node_list))
+ hmm_vm_free_node(hmm_vm_node(vm->vm_node_list.next));
+}
+
+static struct hmm_vm_node *alloc_hmm_vm_node(unsigned int start,
+ unsigned int pgnr,
+ struct hmm_vm *vm)
+{
+ struct hmm_vm_node *node;
+
+ node = kmalloc(sizeof(struct hmm_vm_node), GFP_KERNEL);
+ if (!node) {
+ v4l2_printk(KERN_ERR, &isp_dev, "out of memory.\n");
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&node->list);
+ node->start = start;
+ node->pgnr = pgnr;
+ node->size = pgnr_to_size(pgnr);
+ node->vm = vm;
+
+ return node;
+}
+
+struct hmm_vm_node *hmm_vm_alloc_node(struct hmm_vm *vm, unsigned int pgnr)
+{
+ struct list_head *head, *p, *pos;
+ struct hmm_vm_node *node, *cur, *next;
+ unsigned int vm_start, vm_end;
+ unsigned int addr;
+ unsigned int size;
+
+ if (!vm)
+ return NULL;
+
+ vm_start = vm->start;
+ vm_end = vm_node_end(vm->start, vm->pgnr);
+ size = pgnr_to_size(pgnr);
+
+ addr = vm_start;
+ pos = head = &vm->vm_node_list;
+
+ spin_lock(&vm->lock);
+
+ /*
+ * if list is empty, the loop code will not be executed.
+ */
+ list_for_each(p, head) {
+ pos = p;
+ cur = hmm_vm_node(pos);
+ addr = vm_node_end(cur->start, cur->pgnr);
+ if (pos->next != head) {
+ next = hmm_vm_node(pos->next);
+ if ((next->start - addr) >= size)
+ goto found;
+ }
+ }
+
+ if (addr + size > vm_end) {
+ v4l2_printk(KERN_INFO, &isp_dev,
+ "no enough virtual address space.\n");
+ goto failed;
+ }
+
+found:
+
+ spin_unlock(&vm->lock);
+ node = alloc_hmm_vm_node(addr, pgnr, vm);
+ if (!node)
+ goto failed;
+
+ spin_lock(&vm->lock);
+ list_add(&node->list, pos);
+
+ spin_unlock(&vm->lock);
+
+ return node;
+failed:
+ v4l2_printk(KERN_ERR, &isp_dev, "failed...\n");
+ spin_unlock(&vm->lock);
+
+ return NULL;
+}
+
+void hmm_vm_free_node(struct hmm_vm_node *node)
+{
+ if (node) {
+ struct hmm_vm *vm = node->vm;
+ spin_lock(&vm->lock);
+ list_del(&node->list);
+ spin_unlock(&vm->lock);
+ kfree(node);
+ }
+}
+
+struct hmm_vm_node *hmm_vm_find_node_start(struct hmm_vm *vm, unsigned int addr)
+{
+ struct list_head *pos;
+ struct hmm_vm_node *node;
+
+ if (!vm)
+ return NULL;
+
+ spin_lock(&vm->lock);
+
+ list_for_each(pos, &vm->vm_node_list) {
+ node = hmm_vm_node(pos);
+ if (node->start == addr)
+ goto found;
+ }
+
+ spin_unlock(&vm->lock);
+ return NULL;
+found:
+ spin_unlock(&vm->lock);
+ return node;
+}
+
+struct hmm_vm_node *hmm_vm_find_node_in_range(struct hmm_vm *vm,
+ unsigned int addr)
+{
+ struct list_head *pos;
+ struct hmm_vm_node *node;
+
+ if (!vm)
+ return NULL;
+
+ spin_lock(&vm->lock);
+
+ list_for_each(pos, &vm->vm_node_list) {
+ node = hmm_vm_node(pos);
+ if (addr_in_vm_node(addr, node))
+ goto found;
+ }
+
+ spin_unlock(&vm->lock);
+ return NULL;
+found:
+ spin_unlock(&vm->lock);
+ return node;
+}
diff --git a/drivers/media/video/atomisp/include/hmm/hmm.h b/drivers/media/video/atomisp/include/hmm/hmm.h
new file mode 100644
index 0000000..05c5e35
--- /dev/null
+++ b/drivers/media/video/atomisp/include/hmm/hmm.h
@@ -0,0 +1,71 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __HMM_H__
+#define __HMM_H__
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+#include "hmm/hmm_bo.h"
+
+int hmm_init(void);
+void hmm_cleanup(void);
+
+void *hmm_alloc(size_t bytes, enum hmm_bo_type type,
+ int from_highmem, unsigned int userptr);
+void hmm_free(void *ptr);
+int hmm_load(void *virt, void *data, unsigned int bytes);
+int hmm_store(void *virt, const void *data, unsigned int bytes);
+int hmm_set(void *virt, char c, unsigned int bytes);
+
+/*
+ * get kernel memory physical address from ISP virtual address.
+ */
+unsigned int hmm_virt_to_phys(void *virt);
+
+/*
+ * map ISP memory starts with virt to kernel virtual address
+ * by using vmap. return NULL if failed.
+ *
+ * !! user needs to use vunmap to unmap it manually before calling
+ * hmm_free to free the memory.
+ *
+ * virt must be the start address of ISP memory (return by hmm_alloc),
+ * do not pass any other address.
+ */
+void *hmm_vmap(void *virt);
+
+/*
+ * map ISP memory starts with virt to specific vma.
+ *
+ * used for mmap operation.
+ *
+ * virt must be the start address of ISP memory (return by hmm_alloc),
+ * do not pass any other address.
+ */
+int hmm_mmap(struct vm_area_struct *vma, void *virt);
+
+#endif
diff --git a/drivers/media/video/atomisp/include/hmm/hmm_bo.h b/drivers/media/video/atomisp/include/hmm/hmm_bo.h
new file mode 100644
index 0000000..30b4bf7
--- /dev/null
+++ b/drivers/media/video/atomisp/include/hmm/hmm_bo.h
@@ -0,0 +1,313 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __HMM_BO_H__
+#define __HMM_BO_H__
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+#include <linux/kref.h>
+
+#include "hmm_common.h"
+
+#ifdef USE_DRM_MM
+#include <drm/drm_mm.h>
+#else
+#include "hmm/hmm_vm.h"
+#endif
+
+#define check_bo_null_return(bo, exp) \
+ check_null_return(bo, exp, "NULL hmm buffer object.\n")
+
+struct hmm_bo_device;
+
+/*
+ * buffer object type.
+ *
+ * HMM_BO_PRIVATE:
+ * pages are allocated by driver itself.
+ * HMM_BO_SHARE:
+ * pages are allocated by other component. currently: video driver.
+ * HMM_BO_USER:
+ * pages are allocated in user space process.
+ *
+ */
+enum hmm_bo_type {
+ HMM_BO_PRIVATE,
+ HMM_BO_SHARE,
+ HMM_BO_USER,
+};
+
+#define HMM_BO_VM_ALLOCED 0x1
+#define HMM_BO_PAGE_ALLOCED 0x2
+#define HMM_BO_BINDED 0x4
+#define HMM_BO_MMAPED 0x8
+
+#ifdef HMM_BO_VMAP_SUPPORT
+#define HMM_BO_VMAPED 0x10
+#endif
+
+#define HMM_BO_ACTIVE 0x1000
+
+struct page_block {
+ struct list_head list;
+ struct page *pages;
+ int order;
+};
+
+struct hmm_buffer_object {
+ struct hmm_bo_device *bdev;
+ struct list_head list;
+ struct kref kref;
+
+ /* mutex protecting this BO */
+ struct mutex mutex;
+
+ enum hmm_bo_type type;
+
+#ifdef HMM_BO_VMAP_SUPPORT
+ /*
+ * kernel virtual address got by vmap, used by kernel
+ * to access the buffer object's pages contiguously.
+ */
+ void *virt;
+#endif
+
+ struct list_head pgblocks;
+ struct page **pages; /* physical pages */
+ unsigned int pgnr; /* page number */
+ int from_highmem;
+#ifdef CI_VA_SHARE_BUFFER
+ /* for share buffer created by video driver */
+ unsigned int handle;
+#endif
+ int mmap_count;
+#ifdef USE_DRM_MM
+ /* protected by bdev->vm_lock */
+ struct drm_mm_node *vm_node;
+#else
+ struct hmm_vm_node *vm_node;
+#endif
+ int status;
+
+ /*
+ * release callback for releasing buffer object.
+ *
+ * usually set to the release function to release the
+ * upper level buffer object which has hmm_buffer_object
+ * embedded in. if the hmm_buffer_object is dynamically
+ * created by hmm_bo_create, release will set to kfree.
+ *
+ */
+ void (*release)(struct hmm_buffer_object *bo);
+};
+
+#define list_to_hmm_bo(list_ptr) \
+ list_entry((list_ptr), struct hmm_buffer_object, list)
+
+/*
+ * use this function to initialize pre-allocated hmm_buffer_object.
+ *
+ * the hmm_buffer_object use reference count to manage its life cycle.
+ *
+ * bo->kref is inited to 1.
+ *
+ * use hmm_bo_ref/hmm_bo_unref increase/decrease the reference count,
+ * and hmm_bo_unref will free resource of buffer object (but not the
+ * buffer object itself as it can be both pre-allocated or dynamically
+ * allocated) when reference reaches 0.
+ *
+ * see detailed description of hmm_bo_ref/hmm_bo_unref below.
+ *
+ * as hmm_buffer_object may be used as an embedded object in an upper
+ * level object, a release callback must be provided. if it is
+ * embedded in upper level object, set release call back to release
+ * function of that object. if no upper level object, set release
+ * callback to NULL.
+ *
+ * ex:
+ * struct hmm_buffer_object bo;
+ * hmm_bo_init(bdev, &bo, pgnr, NULL);
+ *
+ * or
+ * struct my_buffer_object {
+ * struct hmm_buffer_object bo;
+ * ...
+ * };
+ *
+ * void my_buffer_release(struct hmm_buffer_object *bo)
+ * {
+ * struct my_buffer_object *my_bo =
+ * container_of(bo, struct my_buffer_object, bo);
+ *
+ * ... // release resource in my_buffer_object
+ *
+ * kfree(my_bo);
+ * }
+ *
+ * struct my_buffer_object *my_bo =
+ * kmalloc(sizeof(*my_bo), GFP_KERNEL);
+ *
+ * hmm_bo_init(bdev, &my_bo->bo, pgnr, my_buffer_release);
+ * ...
+ *
+ * hmm_bo_unref(&my_bo->bo);
+ */
+int hmm_bo_init(struct hmm_bo_device *bdev,
+ struct hmm_buffer_object *bo,
+ unsigned int pgnr,
+ void (*release)(struct hmm_buffer_object *));
+
+/*
+ * use these functions to dynamically alloc hmm_buffer_object.
+ *
+ * hmm_bo_init will called for that allocated buffer object, and
+ * the release callback is set to kfree.
+ *
+ * ex:
+ * hmm_buffer_object *bo = hmm_bo_create(bdev, pgnr);
+ * ...
+ * hmm_bo_unref(bo);
+ */
+struct hmm_buffer_object *hmm_bo_create(struct hmm_bo_device *bdev,
+ int pgnr);
+
+/*
+ * increse buffer object reference.
+ */
+void hmm_bo_ref(struct hmm_buffer_object *bo);
+
+/*
+ * decrese buffer object reference. if reference reaches 0,
+ * release function of the buffer object will be called.
+ *
+ * this call is also used to release hmm_buffer_object or its
+ * upper level object with it embedded in. you need to call
+ * this function when it is no longer used.
+ *
+ * Note:
+ *
+ * user dont need to care about internal resource release of
+ * the buffer object in the release callback, it will be
+ * handled internally.
+ *
+ * this call will only release internal resource of the buffer
+ * object but will not free the buffer object itself, as the
+ * buffer object can be both pre-allocated statically or
+ * dynamically allocated. so user need to deal with the release
+ * of the buffer object itself manually. below example shows
+ * the normal case of using the buffer object.
+ *
+ * struct hmm_buffer_object *bo = hmm_bo_create(bdev, pgnr);
+ * ......
+ * hmm_bo_unref(bo);
+ *
+ * or:
+ *
+ * struct hmm_buffer_object bo;
+ *
+ * hmm_bo_init(bdev, &bo, pgnr, NULL);
+ * ...
+ * hmm_bo_unref(&bo);
+ */
+void hmm_bo_unref(struct hmm_buffer_object *bo);
+
+
+/*
+ * put buffer object to unactivated status, meaning put it into
+ * bo->bdev->free_bo_list, but not destroy it.
+ *
+ * this can be used to instead of hmm_bo_destroy if there are
+ * lots of petential hmm_bo_init/hmm_bo_destroy operations with
+ * the same buffer object size. using this with hmm_bo_device_get_bo
+ * can improve performace as lots of memory allocation/free are
+ * avoided..
+ */
+void hmm_bo_unactivate(struct hmm_buffer_object *bo);
+int hmm_bo_activated(struct hmm_buffer_object *bo);
+
+/*
+ * allocate/free virtual address space for the bo.
+ */
+int hmm_bo_alloc_vm(struct hmm_buffer_object *bo);
+void hmm_bo_free_vm(struct hmm_buffer_object *bo);
+int hmm_bo_vm_allocated(struct hmm_buffer_object *bo);
+
+/*
+ * allocate/free physical pages for the bo. will try to alloc mem
+ * from highmem if from_highmem is set, and type indicate that the
+ * pages will be allocated by using video driver (for share buffer)
+ * or by ISP driver itself.
+ */
+int hmm_bo_alloc_pages(struct hmm_buffer_object *bo,
+ enum hmm_bo_type type, int from_highmem,
+ unsigned int userptr);
+void hmm_bo_free_pages(struct hmm_buffer_object *bo);
+int hmm_bo_page_allocated(struct hmm_buffer_object *bo);
+
+/*
+ * get physical page info of the bo.
+ */
+int hmm_bo_get_page_info(struct hmm_buffer_object *bo,
+ struct page ***pages, int *pgnr);
+
+/*
+ * bind/unbind the physical pages to a virtual address space.
+ */
+int hmm_bo_bind(struct hmm_buffer_object *bo);
+void hmm_bo_unbind(struct hmm_buffer_object *bo);
+int hmm_bo_binded(struct hmm_buffer_object *bo);
+
+#ifdef HMM_BO_VMAP_SUPPORT
+/*
+ * map/unmap buffer object's physical pages to contiguous kernel
+ * virtual address. the mapped virtual address is kept in bo->virt.
+ *
+ * return non-zero if failed.
+ */
+int hmm_bo_vmap(struct hmm_buffer_object *bo);
+void hmm_bo_vunmap(struct hmm_buffer_object *bo);
+int hmm_bo_vmapped(struct hmm_buffer_object *bo);
+#else
+/*
+ * vmap buffer object's pages to contiguous kernel virtual address.
+ * user needs to call vunmap manually to unmap it.
+ */
+void *hmm_bo_vmap(struct hmm_buffer_object *bo);
+#endif
+
+/*
+ * mmap the bo's physical pages to specific vma.
+ *
+ * vma's address space size must be the same as bo's size,
+ * otherwise it will return -EINVAL.
+ *
+ * vma->vm_flags will be set to (VM_RESERVED | VM_IO).
+ */
+int hmm_bo_mmap(struct vm_area_struct *vma,
+ struct hmm_buffer_object *bo);
+
+#endif
diff --git a/drivers/media/video/atomisp/include/hmm/hmm_bo_dev.h b/drivers/media/video/atomisp/include/hmm/hmm_bo_dev.h
new file mode 100644
index 0000000..9762143
--- /dev/null
+++ b/drivers/media/video/atomisp/include/hmm/hmm_bo_dev.h
@@ -0,0 +1,124 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __HMM_BO_DEV_H__
+#define __HMM_BO_DEV_H__
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/mutex.h>
+
+#include "mmu/isp_mmu.h"
+#include "hmm/hmm_common.h"
+
+#ifdef USE_DRM_MM
+#include <drm/drm_mm.h>
+#else
+#include "hmm/hmm_vm.h"
+#endif
+
+#define check_bodev_null_return(bdev, exp) \
+ check_null_return(bdev, exp, \
+ "NULL hmm_bo_device.\n")
+
+#define HMM_BO_DEVICE_INITED 0x1
+
+struct hmm_buffer_object;
+
+struct hmm_bo_device {
+ /* isp_mmu provides lock itself */
+ struct isp_mmu mmu;
+
+#ifdef USE_DRM_MM
+ /* drm_mm does not support lock itself */
+ struct drm_mm vaddr_space;
+ rwlock_t vm_lock;
+#else
+ /* hmm_vm provides lock itself */
+ struct hmm_vm vaddr_space;
+#endif
+
+ struct list_head free_bo_list;
+ struct mutex fblist_mutex;
+ struct list_head active_bo_list;
+ struct mutex ablist_mutex;
+
+ int flag;
+};
+
+int hmm_bo_device_init(struct hmm_bo_device *bdev,
+ struct isp_mmu_driver *mmu_driver,
+ unsigned int vaddr_start, unsigned int size);
+
+/*
+ * clean up all hmm_bo_device related things.
+ */
+void hmm_bo_device_exit(struct hmm_bo_device *bdev);
+
+/*
+ * whether the bo device is inited or not.
+ */
+int hmm_bo_device_inited(struct hmm_bo_device *bdev);
+
+/*
+ * find the buffer object with virtual address vaddr.
+ * return NULL if no such buffer object found.
+ */
+struct hmm_buffer_object *hmm_bo_device_search_start(
+ struct hmm_bo_device *bdev, unsigned int vaddr);
+
+/*
+ * find the buffer object with virtual address vaddr.
+ * return NULL if no such buffer object found.
+ */
+struct hmm_buffer_object *hmm_bo_device_search_in_range(
+ struct hmm_bo_device *bdev, unsigned int vaddr);
+
+/*
+ * find a buffer object with pgnr pages from free_bo_list and
+ * activate it (remove from free_bo_list and add to
+ * active_bo_list)
+ *
+ * return NULL if no such buffer object found.
+ */
+struct hmm_buffer_object *hmm_bo_device_get_bo(
+ struct hmm_bo_device *bdev, unsigned int pgnr);
+
+/*
+ * destroy all buffer objects in the free_bo_list.
+ */
+void hmm_bo_device_destroy_free_bo_list(struct hmm_bo_device *bdev);
+/*
+ * destroy buffer object with start virtual address vaddr.
+ */
+void hmm_bo_device_destroy_free_bo_addr(struct hmm_bo_device *bdev,
+ unsigned int vaddr);
+/*
+ * destroy all buffer objects with pgnr pages.
+ */
+void hmm_bo_device_destroy_free_bo_size(struct hmm_bo_device *bdev,
+ unsigned int pgnr);
+
+#endif
diff --git a/drivers/media/video/atomisp/include/hmm/hmm_common.h b/drivers/media/video/atomisp/include/hmm/hmm_common.h
new file mode 100644
index 0000000..2358979
--- /dev/null
+++ b/drivers/media/video/atomisp/include/hmm/hmm_common.h
@@ -0,0 +1,97 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __HMM_BO_COMMON_H__
+#define __HMM_BO_COMMON_H__
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+
+#define HMM_BO_NAME "HMM"
+
+/*
+ * some common use micros
+ */
+#define var_equal(var1, var2, fmt, arg ...) \
+ do { \
+ if ((var1) == (var2))\
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ } while (0)
+
+#define var_equal_return(var1, var2, exp, fmt, arg ...) \
+ do { \
+ if ((var1) == (var2)) { \
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ return exp;\
+ } \
+ } while (0)
+
+#define var_equal_goto(var1, var2, label, fmt, arg ...) \
+ do { \
+ if ((var1) == (var2)) { \
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ goto label;\
+ } \
+ } while (0)
+
+
+#define var_not_equal(var1, var2, fmt, arg ...) \
+ do { \
+ if ((var1) != (var2))\
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ } while (0)
+
+#define var_not_equal_return(var1, var2, exp, fmt, arg ...) \
+ do { \
+ if ((var1) != (var2)) { \
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ return exp;\
+ } \
+ } while (0)
+
+#define var_not_equal_goto(var1, var2, label, fmt, arg ...) \
+ do { \
+ if ((var1) != (var2)) { \
+ v4l2_printk(KERN_ERR, &isp_dev, \
+ fmt, ## arg); \
+ goto label;\
+ } \
+ } while (0)
+
+#define check_null(ptr, fmt, arg...) \
+ var_equal(ptr, NULL, fmt, ## arg)
+
+#define check_null_return(ptr, exp, fmt, arg ...) \
+ var_equal_return(ptr, NULL, exp, fmt, ## arg)
+
+#define check_null_goto(ptr, label, fmt, arg...) \
+ var_equal_goto(ptr, NULL, label, fmt, ## arg)
+
+#endif
diff --git a/drivers/media/video/atomisp/include/hmm/hmm_vm.h b/drivers/media/video/atomisp/include/hmm/hmm_vm.h
new file mode 100644
index 0000000..260cb90
--- /dev/null
+++ b/drivers/media/video/atomisp/include/hmm/hmm_vm.h
@@ -0,0 +1,67 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#ifndef __HMM_VM_H__
+#define __HMM_VM_H__
+
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/mutex.h>
+#include <linux/list.h>
+
+struct hmm_vm {
+ unsigned int start;
+ unsigned int pgnr;
+ unsigned int size;
+ struct list_head vm_node_list;
+ spinlock_t lock;
+};
+
+struct hmm_vm_node {
+ struct list_head list;
+ unsigned int start;
+ unsigned int pgnr;
+ unsigned int size;
+ struct hmm_vm *vm;
+};
+
+#define hmm_vm_node(list_ptr) \
+ list_entry((list_ptr), struct hmm_vm_node, list)
+
+int hmm_vm_init(struct hmm_vm *vm, unsigned int start,
+ unsigned int size);
+
+void hmm_vm_clean(struct hmm_vm *vm);
+
+struct hmm_vm_node *hmm_vm_alloc_node(struct hmm_vm *vm,
+ unsigned int pgnr);
+
+void hmm_vm_free_node(struct hmm_vm_node *node);
+
+struct hmm_vm_node *hmm_vm_find_node_start(struct hmm_vm *vm,
+ unsigned int addr);
+
+struct hmm_vm_node *hmm_vm_find_node_in_range(struct hmm_vm *vm,
+ unsigned int addr);
+
+#endif
diff --git a/drivers/media/video/atomisp/include/mmu/isp_mmu.h b/drivers/media/video/atomisp/include/mmu/isp_mmu.h
new file mode 100644
index 0000000..a01c19b
--- /dev/null
+++ b/drivers/media/video/atomisp/include/mmu/isp_mmu.h
@@ -0,0 +1,168 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * ISP MMU driver for classic two-level page tables
+ */
+#ifndef __ISP_MMU_H__
+#define __ISP_MMU_H__
+
+#include <linux/mutex.h>
+
+/*
+ * do not change these values, the page size for ISP must be the
+ * same as kernel's page size.
+ */
+#define ISP_PAGE_OFFSET 12
+#define ISP_PAGE_SIZE (1U << ISP_PAGE_OFFSET)
+#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1))
+
+#define ISP_L1PT_OFFSET 22
+#define ISP_L1PT_MASK (~((1U << ISP_L1PT_OFFSET) - 1))
+
+#define ISP_L2PT_OFFSET 12
+#define ISP_L2PT_MASK (~(ISP_L1PT_MASK|(~(ISP_PAGE_MASK))))
+
+#define ISP_L1PT_PTES 1024
+#define ISP_L2PT_PTES 1024
+
+#define ISP_PTR_TO_L1_IDX(x) ((((x) & ISP_L1PT_MASK)) \
+ >> ISP_L1PT_OFFSET)
+
+#define ISP_PTR_TO_L2_IDX(x) ((((x) & ISP_L2PT_MASK)) \
+ >> ISP_L2PT_OFFSET)
+
+#define ISP_PAGE_ALIGN(x) (((x) + (ISP_PAGE_SIZE-1)) \
+ & ISP_PAGE_MASK)
+
+#define ISP_PT_TO_VIRT(l1_idx, l2_idx, offset) do {\
+ ((l1_idx) << ISP_L1PT_OFFSET) | \
+ ((l2_idx) << ISP_L2PT_OFFSET) | \
+ (offset)\
+} while (0)
+
+#define pgnr_to_size(pgnr) ((pgnr) << ISP_PAGE_OFFSET)
+#define size_to_pgnr_ceil(size) (((size) + (1 << ISP_PAGE_OFFSET) - 1)\
+ >> ISP_PAGE_OFFSET)
+#define size_to_pgnr_bottom(size) ((size) >> ISP_PAGE_OFFSET)
+
+struct isp_mmu;
+
+struct isp_mmu_driver {
+ /*
+ * const value
+ *
+ * @name:
+ * driver name
+ * @pte_valid_mask:
+ * should be 1 bit valid data, meaning the value should
+ * be power of 2.
+ */
+ char *name;
+ unsigned int pte_valid_mask;
+
+ /*
+ * set page directory base address (physical address).
+ *
+ * must be provided.
+ */
+ int (*set_pd_base) (struct isp_mmu *mmu,
+ unsigned int pd_base);
+ /*
+ * callback to flush tlb.
+ *
+ * tlb_flush_range will at least flush TLBs containing
+ * address mapping from addr to addr + size.
+ *
+ * tlb_flush_all will flush all TLBs.
+ *
+ * tlb_flush_all is must be provided. if tlb_flush_range is
+ * not valid, it will set to tlb_flush_all by default.
+ */
+ void (*tlb_flush_range) (struct isp_mmu *mmu,
+ unsigned int addr, unsigned int size);
+ void (*tlb_flush_all) (struct isp_mmu *mmu);
+
+};
+
+struct isp_mmu {
+ struct isp_mmu_driver *driver;
+ unsigned int l1_pte;
+
+ struct mutex pt_mutex;
+};
+
+/* flags for PDE and PTE */
+#define ISP_PTE_VALID_MASK(mmu) \
+ ((mmu)->driver->pte_valid_mask)
+
+#define ISP_PTE_VALID(mmu, pte) \
+ ((pte) & ISP_PTE_VALID_MASK(mmu))
+
+#define UNVALID_PHYS 0xffffffff
+
+#define NULL_PAGE (UNVALID_PHYS & ISP_PAGE_MASK)
+#define NULL_PTE NULL_PAGE
+
+#define PAGE_VALID(page) ((page) != NULL_PAGE)
+
+/*
+ * init mmu with specific mmu driver.
+ */
+int isp_mmu_init(struct isp_mmu *mmu, struct isp_mmu_driver *driver);
+/*
+ * cleanup all mmu related things.
+ */
+void isp_mmu_exit(struct isp_mmu *mmu);
+
+/*
+ * setup/remove address mapping for pgnr continous physical pages
+ * and isp_virt.
+ *
+ * map/unmap is mutex lock protected, and caller does not have
+ * to do lock/unlock operation.
+ *
+ * map/unmap will not flush tlb, and caller needs to deal with
+ * this itself.
+ */
+int isp_mmu_map(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int phys, unsigned int pgnr);
+
+void isp_mmu_unmap(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int pgnr);
+
+static inline void isp_mmu_flush_tlb_all(struct isp_mmu *mmu)
+{
+ if (mmu->driver && mmu->driver->tlb_flush_all)
+ mmu->driver->tlb_flush_all(mmu);
+}
+
+#define isp_mmu_flush_tlb isp_mmu_flush_tlb_all
+
+static inline void isp_mmu_flush_tlb_range(struct isp_mmu *mmu,
+ unsigned int start, unsigned int size)
+{
+ if (mmu->driver && mmu->driver->tlb_flush_range)
+ mmu->driver->tlb_flush_range(mmu, start, size);
+}
+
+#endif /* ISP_MMU_H_ */
diff --git a/drivers/media/video/atomisp/include/mmu/sh_mmu.h b/drivers/media/video/atomisp/include/mmu/sh_mmu.h
new file mode 100644
index 0000000..d05aa3b
--- /dev/null
+++ b/drivers/media/video/atomisp/include/mmu/sh_mmu.h
@@ -0,0 +1,74 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+#ifndef SH_MMU_H_
+#define SH_MMU_H_
+
+#include <css/sh_css.h>
+#include "mmu/isp_mmu.h"
+
+
+/*
+ * include SH header file here
+ */
+
+/*
+ * set page directory base address (physical address).
+ *
+ * must be provided.
+ */
+static int sh_set_pd_base(struct isp_mmu *mmu,
+ unsigned int phys)
+{
+ sh_css_mmu_set_page_table_base_address((void *)phys);
+ return 0;
+}
+
+/*
+ * callback to flush tlb.
+ *
+ * tlb_flush_range will at least flush TLBs containing
+ * address mapping from addr to addr + size.
+ *
+ * tlb_flush_all will flush all TLBs.
+ *
+ * tlb_flush_all is must be provided. if tlb_flush_range is
+ * not valid, it will set to tlb_flush_all by default.
+ */
+static void sh_tlb_flush(struct isp_mmu *mmu)
+{
+ sh_css_mmu_invalidate_cache();
+}
+
+static struct isp_mmu_driver sh_mmu_driver = {
+ .name = "Silicon Hive ISP3000 MMU",
+ .pte_valid_mask = 0x1,
+ .set_pd_base = sh_set_pd_base,
+ .tlb_flush_all = sh_tlb_flush,
+};
+
+#define ISP_VM_START 0x0
+#define ISP_VM_SIZE (1 << 30) /* 1G address space */
+#define ISP_PTR_NULL NULL
+
+#endif /* SH_MMU_H_ */
+
diff --git a/drivers/media/video/atomisp/mmu/isp_mmu.c b/drivers/media/video/atomisp/mmu/isp_mmu.c
new file mode 100644
index 0000000..3c892d6
--- /dev/null
+++ b/drivers/media/video/atomisp/mmu/isp_mmu.c
@@ -0,0 +1,515 @@
+/*
+ * Support for Medifield PNW Camera Imaging ISP subsystem.
+ *
+ * Copyright (c) 2010 Intel Corporation. All Rights Reserved.
+ *
+ * Copyright (c) 2010 Silicon Hive www.siliconhive.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 Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+/*
+ * ISP MMU management wrap code
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/gfp.h>
+#include <linux/mm.h> /* for GFP_ATOMIC */
+#include <linux/slab.h> /* for kmalloc */
+#include <linux/list.h>
+#include <linux/io.h>
+#include <asm/cacheflush.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+
+#include "mmu/isp_mmu.h"
+#include "mfldisp_internal.h"
+
+static unsigned int mfld_isp_get_pte(unsigned int pt, unsigned int idx)
+{
+ unsigned int pt_virt = (unsigned int)phys_to_virt(pt);
+ return *(((unsigned int *) pt_virt) + idx);
+}
+
+static void mfld_isp_set_pte(unsigned int pt,
+ unsigned int idx, unsigned int pte)
+{
+ unsigned int pt_virt = (unsigned int)phys_to_virt(pt);
+ (*(((unsigned int *) pt_virt) + idx)) = pte;
+}
+
+static void *isp_pt_phys_to_virt(unsigned int phys)
+{
+ return phys_to_virt(phys);
+}
+
+static unsigned int isp_pte_to_pgaddr(unsigned int pte)
+{
+ return (unsigned int)(pte & ISP_PAGE_MASK);
+}
+
+static unsigned int isp_pgaddr_to_pte_valid(struct isp_mmu *mmu,
+ unsigned int phys)
+{
+ return (unsigned int) (phys | ISP_PTE_VALID_MASK(mmu));
+}
+
+/*
+ * allocate a uncacheable page table.
+ * return physical address.
+ */
+static unsigned int alloc_page_table(void)
+{
+ int i;
+ unsigned int page;
+
+ void *virt = (void *)__get_free_page(GFP_KERNEL);
+ if (!virt)
+ return NULL_PAGE;
+
+ /*
+ * we need a uncacheable page table.
+ */
+#ifdef CONFIG_X86
+ set_memory_uc((unsigned int)virt, 1);
+#endif
+
+ page = virt_to_phys(virt);
+
+ for (i = 0; i < 1024; i++) {
+ /* NEED CHECK */
+ mfld_isp_set_pte(page, i, NULL_PAGE);
+ }
+
+ return page;
+}
+
+static void free_page_table(unsigned int page)
+{
+ unsigned int virt;
+ page &= ISP_PAGE_MASK;
+ /*
+ * reset the page to write back before free
+ */
+#ifdef CONFIG_X86
+ virt = (unsigned int)phys_to_virt(page);
+ set_memory_wb(virt, 1);
+#endif
+ free_page((unsigned long)phys_to_virt(page));
+}
+
+static void mmu_remap_error(struct isp_mmu *mmu,
+ unsigned int l1_pt, unsigned int l1_idx,
+ unsigned int l2_pt, unsigned int l2_idx,
+ unsigned int isp_virt, unsigned int old_phys,
+ unsigned int new_phys)
+{
+ v4l2_printk(KERN_ERR, &isp_dev, "address remap:\n\n"
+ "\tL1 PT: virt = 0x%x, phys = 0x%x, "
+ "idx = %d\n"
+ "\tL2 PT: virt = 0x%x, phys = 0x%x, "
+ "idx = %d\n"
+ "\told: isp_virt = 0x%x, phys = 0x%x\n"
+ "\tnew: isp_virt = 0x%x, phys = 0x%x\n",
+ (unsigned int)isp_pt_phys_to_virt(l1_pt),
+ (unsigned int)(l1_pt), l1_idx,
+ (unsigned int)isp_pt_phys_to_virt(l2_pt),
+ (unsigned int)(l2_pt), l2_idx, (unsigned int)isp_virt,
+ (unsigned int)old_phys, (unsigned int)isp_virt,
+ (unsigned int)new_phys);
+}
+
+static void mmu_unmap_l2_pte_error(struct isp_mmu *mmu,
+ unsigned int l1_pt, unsigned int l1_idx,
+ unsigned int l2_pt, unsigned int l2_idx,
+ unsigned int isp_virt, unsigned int pte)
+{
+ v4l2_printk(KERN_ERR, &isp_dev, "unmap unvalid L2 pte:\n\n"
+ "\tL1 PT: virt = 0x%x, phys = 0x%x, "
+ "idx = %d\n"
+ "\tL2 PT: virt = 0x%x, phys = 0x%x, "
+ "idx = %d\n"
+ "\tisp_virt = 0x%x, pte(page phys) = 0x%x\n",
+ (unsigned int)isp_pt_phys_to_virt(l1_pt),
+ (unsigned int)(l1_pt), l1_idx,
+ (unsigned int)isp_pt_phys_to_virt(l2_pt),
+ (unsigned int)(l2_pt), l2_idx, (unsigned int)isp_virt,
+ (unsigned int)pte);
+}
+
+static void mmu_unmap_l1_pte_error(struct isp_mmu *mmu,
+ unsigned int l1_pt, unsigned int l1_idx,
+ unsigned int isp_virt, unsigned int pte)
+{
+ v4l2_printk(KERN_ERR, &isp_dev, "unmap unvalid L1 pte (L2 PT):\n\n"
+ "\tL1 PT: virt = 0x%x, phys = 0x%x, "
+ "idx = %d\n"
+ "\tisp_virt = 0x%x, l1_pte(L2 PT) = 0x%x\n",
+ (unsigned int)isp_pt_phys_to_virt(l1_pt),
+ (unsigned int)(l1_pt), l1_idx, (unsigned int)isp_virt,
+ (unsigned int)pte);
+}
+
+static void mmu_unmap_l1_pt_error(struct isp_mmu *mmu, unsigned int pte)
+{
+ v4l2_printk(KERN_ERR, &isp_dev, "unmap unvalid L1PT:\n\n"
+ "L1PT = 0x%x\n", (unsigned int)pte);
+}
+
+/*
+ * Update L2 page table according to isp virtual address and page physical
+ * address
+ */
+static int mmu_l2_map(struct isp_mmu *mmu, unsigned int l1_pt,
+ unsigned int l1_idx, unsigned int l2_pt,
+ unsigned int start, unsigned int end, unsigned int phys)
+{
+ unsigned int ptr;
+ unsigned int idx;
+ unsigned int pte;
+
+ l2_pt &= ISP_PAGE_MASK;
+
+ start = start & ISP_PAGE_MASK;
+ end = ISP_PAGE_ALIGN(end);
+ phys &= ISP_PAGE_MASK;
+
+ ptr = start;
+ do {
+ idx = ISP_PTR_TO_L2_IDX(ptr);
+
+ pte = mfld_isp_get_pte(l2_pt, idx);
+
+ if (ISP_PTE_VALID(mmu, pte))
+ mmu_remap_error(mmu, l1_pt, l1_idx,
+ l2_pt, idx, ptr, pte, phys);
+
+ pte = isp_pgaddr_to_pte_valid(mmu, phys);
+
+ mfld_isp_set_pte(l2_pt, idx, pte);
+ ptr += (1U << ISP_L2PT_OFFSET);
+ phys += (1U << ISP_L2PT_OFFSET);
+ } while (ptr < end && idx < ISP_L2PT_PTES - 1);
+
+ return 0;
+}
+
+/*
+ * Update L1 page table according to isp virtual address and page physical
+ * address
+ */
+static int mmu_l1_map(struct isp_mmu *mmu, unsigned int l1_pt,
+ unsigned int start, unsigned int end, unsigned int phys)
+{
+ unsigned int l2_pt, ptr, l1_aligned;
+ unsigned int idx;
+ unsigned int l2_pte;
+ int ret;
+
+ l1_pt &= ISP_PAGE_MASK;
+
+ start = start & ISP_PAGE_MASK;
+ end = ISP_PAGE_ALIGN(end);
+ phys &= ISP_PAGE_MASK;
+
+ ptr = start;
+ do {
+ idx = ISP_PTR_TO_L1_IDX(ptr);
+
+ l2_pte = mfld_isp_get_pte(l1_pt, idx);
+
+ if (!ISP_PTE_VALID(mmu, l2_pte)) {
+ l2_pt = alloc_page_table();
+ if (l2_pt == NULL_PAGE) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "alloc page table fail.\n");
+ return -ENOMEM;
+ }
+
+ l2_pte = isp_pgaddr_to_pte_valid(mmu, l2_pt);
+
+ mfld_isp_set_pte(l1_pt, idx, l2_pte);
+ }
+
+ l2_pt = isp_pte_to_pgaddr(l2_pte);
+
+ l1_aligned = (ptr & ISP_PAGE_MASK) + (1U << ISP_L1PT_OFFSET);
+
+ if (l1_aligned < end) {
+ ret = mmu_l2_map(mmu, l1_pt, idx,
+ l2_pt, ptr, l1_aligned, phys);
+ phys += (l1_aligned - ptr);
+ ptr = l1_aligned;
+ } else {
+ ret = mmu_l2_map(mmu, l1_pt, idx,
+ l2_pt, ptr, end, phys);
+ phys += (end - ptr);
+ ptr = end;
+ }
+
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "setup mapping in L2PT fail.\n");
+ return ret;
+ }
+ } while (ptr < end && idx < ISP_L1PT_PTES - 1);
+
+ return 0;
+}
+
+/*
+ * Update page table according to isp virtual address and page physical
+ * address
+ */
+static int mmu_map(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int phys, unsigned int pgnr)
+{
+ unsigned int start, end;
+ unsigned int l1_pt;
+ int ret;
+
+ if (!ISP_PTE_VALID(mmu, mmu->l1_pte)) {
+ /*
+ * allocate 1 new page for L1 page table
+ */
+ l1_pt = alloc_page_table();
+ if (l1_pt == NULL_PAGE) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "alloc page table fail.\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * setup L1 page table physical addr to MMU
+ */
+ ret = mmu->driver->set_pd_base(mmu, l1_pt);
+ if (ret) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set page directory base address "
+ "fail.\n");
+ return ret;
+ }
+
+ mmu->l1_pte = isp_pgaddr_to_pte_valid(mmu, l1_pt);
+
+ isp_mmu_flush_tlb(mmu);
+ }
+
+ l1_pt = isp_pte_to_pgaddr(mmu->l1_pte);
+
+ start = (isp_virt) & ISP_PAGE_MASK;
+ end = start + (pgnr << ISP_PAGE_OFFSET);
+ phys &= ISP_PAGE_MASK;
+
+ ret = mmu_l1_map(mmu, l1_pt, start, end, phys);
+
+ if (ret)
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "setup mapping in L1PT fail.\n");
+
+ return ret;
+}
+
+/*
+ * Free L2 page table according to isp virtual address and page physical
+ * address
+ */
+static void mmu_l2_unmap(struct isp_mmu *mmu, unsigned int l1_pt,
+ unsigned int l1_idx, unsigned int l2_pt,
+ unsigned int start, unsigned int end)
+{
+
+ unsigned int ptr;
+ unsigned int idx;
+ unsigned int pte;
+
+ l2_pt &= ISP_PAGE_MASK;
+
+ start = start & ISP_PAGE_MASK;
+ end = ISP_PAGE_ALIGN(end);
+
+ ptr = start;
+ do {
+ idx = ISP_PTR_TO_L2_IDX(ptr);
+
+ pte = mfld_isp_get_pte(l2_pt, idx);
+
+ if (!ISP_PTE_VALID(mmu, pte))
+ mmu_unmap_l2_pte_error(mmu, l1_pt, l1_idx,
+ l2_pt, idx, ptr, pte);
+
+ mfld_isp_set_pte(l2_pt, idx, NULL_PTE);
+
+ ptr += (1U << ISP_L2PT_OFFSET);
+ } while (ptr < end && idx < ISP_L2PT_PTES - 1);
+}
+
+/*
+ * Free L1 page table according to isp virtual address and page physical
+ * address
+ */
+static void mmu_l1_unmap(struct isp_mmu *mmu, unsigned int l1_pt,
+ unsigned int start, unsigned int end)
+{
+ unsigned int l2_pt, ptr, l1_aligned;
+ unsigned int idx;
+ unsigned int l2_pte;
+
+ l1_pt &= ISP_PAGE_MASK;
+
+ start = start & ISP_PAGE_MASK;
+ end = ISP_PAGE_ALIGN(end);
+
+ ptr = start;
+ do {
+ idx = ISP_PTR_TO_L1_IDX(ptr);
+
+ l2_pte = mfld_isp_get_pte(l1_pt, idx);
+
+ if (!ISP_PTE_VALID(mmu, l2_pte)) {
+ mmu_unmap_l1_pte_error(mmu, l1_pt, idx, ptr, l2_pte);
+ continue;
+ }
+
+ l2_pt = isp_pte_to_pgaddr(l2_pte);
+
+ l1_aligned = (ptr & ISP_PAGE_MASK) + (1U << ISP_L1PT_OFFSET);
+
+ if (l1_aligned < end) {
+ mmu_l2_unmap(mmu, l1_pt, idx, l2_pt, ptr, l1_aligned);
+ ptr = l1_aligned;
+ } else {
+ mmu_l2_unmap(mmu, l1_pt, idx, l2_pt, ptr, end);
+ ptr = end;
+ }
+ /*
+ * use the same L2 page next time, so we dont
+ * need to invalidate and free this PT.
+ */
+ /* mfld_isp_set_pte(l1_pt, idx, NULL_PTE); */
+ } while (ptr < end && idx < ISP_L1PT_PTES - 1);
+}
+
+/*
+ * Free page table according to isp virtual address and page physical
+ * address
+ */
+static void mmu_unmap(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int pgnr)
+{
+ unsigned int start, end;
+ unsigned int l1_pt;
+
+ if (!ISP_PTE_VALID(mmu, mmu->l1_pte)) {
+ mmu_unmap_l1_pt_error(mmu, mmu->l1_pte);
+ return;
+ }
+
+ l1_pt = isp_pte_to_pgaddr(mmu->l1_pte);
+
+ start = (isp_virt) & ISP_PAGE_MASK;
+ end = start + (pgnr << ISP_PAGE_OFFSET);
+
+ mmu_l1_unmap(mmu, l1_pt, start, end);
+}
+
+int isp_mmu_map(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int phys, unsigned int pgnr)
+{
+ return mmu_map(mmu, isp_virt, phys, pgnr);
+}
+
+void isp_mmu_unmap(struct isp_mmu *mmu, unsigned int isp_virt,
+ unsigned int pgnr)
+{
+ mmu_unmap(mmu, isp_virt, pgnr);
+}
+
+static void isp_mmu_flush_tlb_range_default(struct isp_mmu *mmu,
+ unsigned int start,
+ unsigned int size)
+{
+ isp_mmu_flush_tlb(mmu);
+}
+
+/*MMU init for internal structure*/
+int isp_mmu_init(struct isp_mmu *mmu, struct isp_mmu_driver *driver)
+{
+ if (!mmu) /* error */
+ return -EINVAL;
+ if (!driver) /* error */
+ return -EINVAL;
+
+ if (!driver->name)
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "NULL name for MMU driver...\n");
+
+ mmu->driver = driver;
+
+ if (!driver->set_pd_base || !driver->tlb_flush_all) {
+ v4l2_printk(KERN_ERR, &isp_dev,
+ "set_pd_base or tlb_flush_all operation "
+ "not provided.\n");
+ return -EINVAL;
+ }
+
+ if (!driver->tlb_flush_range)
+ driver->tlb_flush_range = isp_mmu_flush_tlb_range_default;
+
+ if (!driver->pte_valid_mask)
+ driver->pte_valid_mask = 0x1;
+
+ mmu->l1_pte = NULL_PTE;
+
+ mutex_init(&mmu->pt_mutex);
+
+ isp_mmu_flush_tlb(mmu);
+
+ return 0;
+}
+
+/*Free L1 and L2 page table*/
+void isp_mmu_exit(struct isp_mmu *mmu)
+{
+ unsigned int idx;
+ unsigned int pte;
+ unsigned int l1_pt, l2_pt;
+
+ if (!mmu)
+ return;
+
+ if (!ISP_PTE_VALID(mmu, mmu->l1_pte)) {
+ v4l2_printk(KERN_WARNING, &isp_dev,
+ "invalid L1PT: pte = 0x%x\n",
+ (unsigned int)mmu->l1_pte);
+ return;
+ }
+
+ l1_pt = isp_pte_to_pgaddr(mmu->l1_pte);
+
+ for (idx = 0; idx < ISP_L1PT_PTES; idx++) {
+ pte = mfld_isp_get_pte(l1_pt, idx);
+
+ if (ISP_PTE_VALID(mmu, pte)) {
+ l2_pt = isp_pte_to_pgaddr(pte);
+
+ free_page_table(l2_pt);
+ }
+ }
+
+ free_page_table(l1_pt);
+}
--
1.5.4.3
-------------- next part --------------
A non-text attachment was scrubbed...
Name: 0001-Medfiled-Camera-Image-ISP-driver-memory-management.patch
Type: application/octet-stream
Size: 90006 bytes
Desc: 0001-Medfiled-Camera-Image-ISP-driver-memory-management.patch
URL: <http://lists.meego.com/pipermail/meego-kernel/attachments/20101223/53a59683/attachment-0001.obj>
More information about the MeeGo-kernel
mailing list