diff options
Diffstat (limited to 'drivers/media/video/tiler/tmm-pat.c')
-rw-r--r-- | drivers/media/video/tiler/tmm-pat.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/drivers/media/video/tiler/tmm-pat.c b/drivers/media/video/tiler/tmm-pat.c new file mode 100644 index 0000000..2d902f9 --- /dev/null +++ b/drivers/media/video/tiler/tmm-pat.c @@ -0,0 +1,326 @@ +/* + * tmm-pat.c + * + * DMM driver support functions for TI TILER hardware block. + * + * Author: Lajos Molnar <molnar@ti.com>, David Sin <dsin@ti.com> + * + * Copyright (C) 2009-2010 Texas Instruments, Inc. + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/mmzone.h> +#include <asm/cacheflush.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/slab.h> + +#include "tmm.h" + +static int param_set_mem(const char *val, struct kernel_param *kp); + +/* Memory limit to cache free pages. TILER will eventually use this much */ +static u32 cache_limit = CONFIG_TILER_CACHE_LIMIT << 20; + +param_check_uint(cache, &cache_limit); +module_param_call(cache, param_set_mem, param_get_uint, &cache_limit, 0644); +__MODULE_PARM_TYPE(cache, "uint"); +MODULE_PARM_DESC(cache, "Cache free pages if total memory is under this limit"); + +/* global state - statically initialized */ +static LIST_HEAD(free_list); /* page cache: list of free pages */ +static u32 total_mem; /* total memory allocated (free & used) */ +static u32 refs; /* number of tmm_pat instances */ +static DEFINE_MUTEX(mtx); /* global mutex */ + +/* The page struct pointer and physical address of each page.*/ +struct mem { + struct list_head list; + struct page *pg; /* page struct */ + u32 pa; /* physical address */ +}; + +/* Used to keep track of mem per tmm_pat_get_pages call */ +struct fast { + struct list_head list; + struct mem **mem; /* array of page info */ + u32 *pa; /* array of physical addresses */ + u32 num; /* number of pages */ +}; + +/* TMM PAT private structure */ +struct dmm_mem { + struct list_head fast_list; + struct dmm *dmm; + u32 *dmac_va; /* coherent memory */ + u32 dmac_pa; /* phys.addr of coherent memory */ + struct page *dummy_pg; /* dummy page */ + u32 dummy_pa; /* phys.addr of dummy page */ +}; + +/* read mem values for a param */ +static int param_set_mem(const char *val, struct kernel_param *kp) +{ + u32 a; + char *p; + + /* must specify memory */ + if (!val) + return -EINVAL; + + /* parse value */ + a = memparse(val, &p); + if (p == val || *p) + return -EINVAL; + + /* store parsed value */ + *(uint *)kp->arg = a; + return 0; +} + +/** + * Frees pages in a fast structure. Moves pages to the free list if there + * are less pages used than max_to_keep. Otherwise, it frees the pages + */ +static void free_fast(struct fast *f) +{ + s32 i = 0; + + /* mutex is locked */ + for (i = 0; i < f->num; i++) { + if (total_mem < cache_limit) { + /* cache free page if under the limit */ + list_add(&f->mem[i]->list, &free_list); + } else { + /* otherwise, free */ + total_mem -= PAGE_SIZE; + __free_page(f->mem[i]->pg); + kfree(f->mem[i]); + } + } + kfree(f->pa); + kfree(f->mem); + /* remove only if element was added */ + if (f->list.next) + list_del(&f->list); + kfree(f); +} + +/* allocate and flush a page */ +static struct mem *alloc_mem(void) +{ + struct mem *m = kmalloc(sizeof(*m), GFP_KERNEL); + if (!m) + return NULL; + memset(m, 0, sizeof(*m)); + + m->pg = alloc_page(GFP_KERNEL | GFP_DMA); + if (!m->pg) { + kfree(m); + return NULL; + } + + m->pa = page_to_phys(m->pg); + + /* flush the cache entry for each page we allocate. */ + dmac_flush_range(page_address(m->pg), + page_address(m->pg) + PAGE_SIZE); + outer_flush_range(m->pa, m->pa + PAGE_SIZE); + + return m; +} + +static void free_page_cache(void) +{ + struct mem *m, *m_; + + /* mutex is locked */ + list_for_each_entry_safe(m, m_, &free_list, list) { + __free_page(m->pg); + total_mem -= PAGE_SIZE; + list_del(&m->list); + kfree(m); + } +} + +static void tmm_pat_deinit(struct tmm *tmm) +{ + struct fast *f, *f_; + struct dmm_mem *pvt = (struct dmm_mem *) tmm->pvt; + + mutex_lock(&mtx); + + /* free all outstanding used memory */ + list_for_each_entry_safe(f, f_, &pvt->fast_list, list) + free_fast(f); + + /* if this is the last tmm_pat, free all memory */ + if (--refs == 0) + free_page_cache(); + + __free_page(pvt->dummy_pg); + + mutex_unlock(&mtx); +} + +static u32 *tmm_pat_get_pages(struct tmm *tmm, u32 n) +{ + struct mem *m; + struct fast *f; + struct dmm_mem *pvt = (struct dmm_mem *) tmm->pvt; + + f = kmalloc(sizeof(*f), GFP_KERNEL); + if (!f) + return NULL; + memset(f, 0, sizeof(*f)); + + /* array of mem struct pointers */ + f->mem = kmalloc(n * sizeof(*f->mem), GFP_KERNEL); + + /* array of physical addresses */ + f->pa = kmalloc(n * sizeof(*f->pa), GFP_KERNEL); + + /* no pages have been allocated yet (needed for cleanup) */ + f->num = 0; + + if (!f->mem || !f->pa) + goto cleanup; + + memset(f->mem, 0, n * sizeof(*f->mem)); + memset(f->pa, 0, n * sizeof(*f->pa)); + + /* fill out fast struct mem array with free pages */ + mutex_lock(&mtx); + while (f->num < n) { + /* if there is a free cached page use it */ + if (!list_empty(&free_list)) { + /* unbind first element from list */ + m = list_first_entry(&free_list, typeof(*m), list); + list_del(&m->list); + } else { + mutex_unlock(&mtx); + + /** + * Unlock mutex during allocation and cache flushing. + */ + m = alloc_mem(); + if (!m) + goto cleanup; + + mutex_lock(&mtx); + total_mem += PAGE_SIZE; + } + + f->mem[f->num] = m; + f->pa[f->num++] = m->pa; + } + + list_add(&f->list, &pvt->fast_list); + mutex_unlock(&mtx); + return f->pa; + +cleanup: + free_fast(f); + return NULL; +} + +static void tmm_pat_free_pages(struct tmm *tmm, u32 *page_list) +{ + struct dmm_mem *pvt = (struct dmm_mem *) tmm->pvt; + struct fast *f, *f_; + + mutex_lock(&mtx); + /* find fast struct based on 1st page */ + list_for_each_entry_safe(f, f_, &pvt->fast_list, list) { + if (f->pa[0] == page_list[0]) { + free_fast(f); + break; + } + } + mutex_unlock(&mtx); +} + +static s32 tmm_pat_pin(struct tmm *tmm, struct pat_area area, u32 page_pa) +{ + struct dmm_mem *pvt = (struct dmm_mem *) tmm->pvt; + struct pat pat_desc = {0}; + + /* send pat descriptor to dmm driver */ + pat_desc.ctrl.dir = 0; + pat_desc.ctrl.ini = 0; + pat_desc.ctrl.lut_id = 0; + pat_desc.ctrl.start = 1; + pat_desc.ctrl.sync = 0; + pat_desc.area = area; + pat_desc.next = NULL; + + /* must be a 16-byte aligned physical address */ + pat_desc.data = page_pa; + return dmm_pat_refill(pvt->dmm, &pat_desc, MANUAL); +} + +static void tmm_pat_unpin(struct tmm *tmm, struct pat_area area) +{ + u16 w = (u8) area.x1 - (u8) area.x0; + u16 h = (u8) area.y1 - (u8) area.y0; + u16 i = (w + 1) * (h + 1); + struct dmm_mem *pvt = (struct dmm_mem *) tmm->pvt; + + while (i--) + pvt->dmac_va[i] = pvt->dummy_pa; + + tmm_pat_pin(tmm, area, pvt->dmac_pa); +} + +struct tmm *tmm_pat_init(u32 pat_id, u32 *dmac_va, u32 dmac_pa) +{ + struct tmm *tmm = NULL; + struct dmm_mem *pvt = NULL; + + struct dmm *dmm = dmm_pat_init(pat_id); + if (dmm) + tmm = kmalloc(sizeof(*tmm), GFP_KERNEL); + if (tmm) + pvt = kmalloc(sizeof(*pvt), GFP_KERNEL); + if (pvt) + pvt->dummy_pg = alloc_page(GFP_KERNEL | GFP_DMA); + if (pvt->dummy_pg) { + /* private data */ + pvt->dmm = dmm; + pvt->dmac_pa = dmac_pa; + pvt->dmac_va = dmac_va; + pvt->dummy_pa = page_to_phys(pvt->dummy_pg); + + INIT_LIST_HEAD(&pvt->fast_list); + + /* increate tmm_pat references */ + mutex_lock(&mtx); + refs++; + mutex_unlock(&mtx); + + /* public data */ + tmm->pvt = pvt; + tmm->deinit = tmm_pat_deinit; + tmm->get = tmm_pat_get_pages; + tmm->free = tmm_pat_free_pages; + tmm->pin = tmm_pat_pin; + tmm->unpin = tmm_pat_unpin; + + return tmm; + } + + kfree(pvt); + kfree(tmm); + dmm_pat_release(dmm); + return NULL; +} +EXPORT_SYMBOL(tmm_pat_init); |