| /* |
| * Copyright (C) 2010 Imagination Technologies Ltd. |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/spinlock.h> |
| #include <linux/stddef.h> |
| #include <linux/genalloc.h> |
| #include <linux/string.h> |
| #include <linux/list.h> |
| #include <linux/slab.h> |
| #include <asm/page.h> |
| #include <asm/tcm.h> |
| |
| struct tcm_pool { |
| struct list_head list; |
| unsigned int tag; |
| unsigned long start; |
| unsigned long end; |
| struct gen_pool *pool; |
| }; |
| |
| static LIST_HEAD(pool_list); |
| |
| static struct tcm_pool *find_pool(unsigned int tag) |
| { |
| struct list_head *lh; |
| struct tcm_pool *pool; |
| |
| list_for_each(lh, &pool_list) { |
| pool = list_entry(lh, struct tcm_pool, list); |
| if (pool->tag == tag) |
| return pool; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * tcm_alloc - allocate memory from a TCM pool |
| * @tag: tag of the pool to allocate memory from |
| * @len: number of bytes to be allocated |
| * |
| * Allocate the requested number of bytes from the pool matching |
| * the specified tag. Returns the address of the allocated memory |
| * or zero on failure. |
| */ |
| unsigned long tcm_alloc(unsigned int tag, size_t len) |
| { |
| unsigned long vaddr; |
| struct tcm_pool *pool; |
| |
| pool = find_pool(tag); |
| if (!pool) |
| return 0; |
| |
| vaddr = gen_pool_alloc(pool->pool, len); |
| if (!vaddr) |
| return 0; |
| |
| return vaddr; |
| } |
| |
| /** |
| * tcm_free - free a block of memory to a TCM pool |
| * @tag: tag of the pool to free memory to |
| * @addr: address of the memory to be freed |
| * @len: number of bytes to be freed |
| * |
| * Free the requested number of bytes at a specific address to the |
| * pool matching the specified tag. |
| */ |
| void tcm_free(unsigned int tag, unsigned long addr, size_t len) |
| { |
| struct tcm_pool *pool; |
| |
| pool = find_pool(tag); |
| if (!pool) |
| return; |
| gen_pool_free(pool->pool, addr, len); |
| } |
| |
| /** |
| * tcm_lookup_tag - find the tag matching an address |
| * @p: memory address to lookup the tag for |
| * |
| * Find the tag of the tcm memory region that contains the |
| * specified address. Returns %TCM_INVALID_TAG if no such |
| * memory region could be found. |
| */ |
| unsigned int tcm_lookup_tag(unsigned long p) |
| { |
| struct list_head *lh; |
| struct tcm_pool *pool; |
| unsigned long addr = (unsigned long) p; |
| |
| list_for_each(lh, &pool_list) { |
| pool = list_entry(lh, struct tcm_pool, list); |
| if (addr >= pool->start && addr < pool->end) |
| return pool->tag; |
| } |
| |
| return TCM_INVALID_TAG; |
| } |
| |
| /** |
| * tcm_add_region - add a memory region to TCM pool list |
| * @reg: descriptor of region to be added |
| * |
| * Add a region of memory to the TCM pool list. Returns 0 on success. |
| */ |
| int __init tcm_add_region(struct tcm_region *reg) |
| { |
| struct tcm_pool *pool; |
| |
| pool = kmalloc(sizeof(*pool), GFP_KERNEL); |
| if (!pool) { |
| pr_err("Failed to alloc memory for TCM pool!\n"); |
| return -ENOMEM; |
| } |
| |
| pool->tag = reg->tag; |
| pool->start = reg->res.start; |
| pool->end = reg->res.end; |
| |
| /* |
| * 2^3 = 8 bytes granularity to allow for 64bit access alignment. |
| * -1 = NUMA node specifier. |
| */ |
| pool->pool = gen_pool_create(3, -1); |
| |
| if (!pool->pool) { |
| pr_err("Failed to create TCM pool!\n"); |
| kfree(pool); |
| return -ENOMEM; |
| } |
| |
| if (gen_pool_add(pool->pool, reg->res.start, |
| reg->res.end - reg->res.start + 1, -1)) { |
| pr_err("Failed to add memory to TCM pool!\n"); |
| return -ENOMEM; |
| } |
| pr_info("Added %s TCM pool (%08x bytes @ %08x)\n", |
| reg->res.name, reg->res.end - reg->res.start + 1, |
| reg->res.start); |
| |
| list_add_tail(&pool->list, &pool_list); |
| |
| return 0; |
| } |