blob: 9d786142af9b879f0c6d6e6c818229c159568d7b [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/****************************************************************************
*
* The MIT License (MIT)
*
* COPYRIGHT (C) 2014 VERISILICON ALL RIGHTS RESERVED
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*****************************************************************************
*
* The GPL License (GPL)
*
* COPYRIGHT (C) 2014 VERISILICON ALL RIGHTS RESERVED
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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.
*
*****************************************************************************
*
* Note: This software is released under dual MIT and GPL licenses. A
* recipient may use this file under the terms of either the MIT license or
* GPL License. If you wish to use only one license not the other, you can
* indicate your decision by deleting one of the above license notices in your
* version of this file.
*
*****************************************************************************
*/
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/version.h>
/* Our header */
#include "memalloc.h"
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/dma-mapping.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/moduleparam.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include <linux/compat.h>
#ifndef HLINA_START_ADDRESS
#define HLINA_START_ADDRESS 0x02000000
#endif
#ifndef HLINA_SIZE
#define HLINA_SIZE 96
#endif
#ifndef HLINA_TRANSL_OFFSET
#define HLINA_TRANSL_OFFSET 0x0
#endif
/* the size of chunk in MEMALLOC_DYNAMIC */
#define CHUNK_SIZE (PAGE_SIZE * 4)
static struct attribute *vencmem_class_attrs[] = {
NULL
};
ATTRIBUTE_GROUPS(vencmem_class);
#define CLASS_NAME "vencmem"
#define DEVICE_NAME "memalloc"
static struct class vencmem_class = {
.name = CLASS_NAME,
.class_groups = vencmem_class_groups,
};
static struct device* device;
/* memory size in MBs for MEMALLOC_DYNAMIC */
static unsigned int alloc_size = 96;
static unsigned long alloc_base = HLINA_START_ADDRESS;
/* user space SW will subtract HLINA_TRANSL_OFFSET from the bus address
* and decoder HW will use the result as the address translated base
* address. The SW needs the original host memory bus address for memory
* mapping to virtual address.
*/
static unsigned long addr_transl = HLINA_TRANSL_OFFSET;
static int memalloc_major; /* dynamic */
/* module_param(name, type, perm) */
module_param(alloc_size, uint, 0);
module_param(alloc_base, ulong, 0);
module_param(addr_transl, ulong, 0);
static DEFINE_SPINLOCK(mem_lock);
typedef struct hlinc {
unsigned long bus_address;
u16 chunks_reserved;
const struct file *filp; /* Client that allocated this chunk */
} hlina_chunk;
static hlina_chunk *hlina_chunks;
static size_t chunks;
static int AllocMemory(unsigned int *busaddr, unsigned int size, const struct file *filp);
static int FreeMemory(unsigned int busaddr, const struct file *filp);
static void ResetMems(void);
static int venc_mem_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret = 0;
unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start);
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
ret = remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot);
return ret;
}
static long memalloc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
MemallocParams memparams;
unsigned long busaddr;
//spin_lock(&mem_lock);
switch (cmd) {
case MEMALLOC_IOCGMEMBASE:
__put_user(alloc_base, (unsigned long __user *)arg);
break;
case MEMALLOC_IOCHARDRESET:
ResetMems();
break;
case MEMALLOC_IOCXGETBUFFER:
ret = copy_from_user(&memparams, (MemallocParams __user *)arg, sizeof(MemallocParams));
if (ret)
break;
ret = AllocMemory(&memparams.bus_address, memparams.size, filp);
memparams.translation_offset = addr_transl;
ret |= copy_to_user((MemallocParams __user *)arg, &memparams, sizeof(MemallocParams));
break;
case MEMALLOC_IOCSFREEBUFFER:
__get_user(busaddr, (unsigned long __user *)arg);
ret = FreeMemory(busaddr, filp);
break;
default:
break;
}
//spin_unlock(&mem_lock);
return ret ? -EFAULT : 0;
}
static int memalloc_open(struct inode *inode, struct file *filp)
{
PDEBUG("dev opened\n");
return 0;
}
static int memalloc_release(struct inode *inode, struct file *filp)
{
int i = 0;
for (i = 0; i < chunks; i++) {
spin_lock(&mem_lock);
if (hlina_chunks[i].filp == filp) {
pr_warn("memalloc: Found unfreed memory at release time!\n");
hlina_chunks[i].filp = NULL;
hlina_chunks[i].chunks_reserved = 0;
}
spin_unlock(&mem_lock);
}
PDEBUG("dev closed\n");
return 0;
}
#ifdef CONFIG_COMPAT
static long memalloc_compat_ioctl(struct file *filp,
unsigned int cmd, unsigned long args)
{
unsigned long ret;
args = (unsigned long)compat_ptr(args);
ret = memalloc_ioctl(filp, cmd, args);
return ret;
}
#endif
static dma_addr_t paddr = 0;
static void *vaddr = NULL;
static void memalloc_cleanup(struct platform_device *pf_dev)
{
if (hlina_chunks)
vfree(hlina_chunks);
dma_free_coherent(&pf_dev->dev, alloc_size * SZ_1M, vaddr, paddr);
unregister_chrdev(memalloc_major, "memalloc");
if (device)
device_destroy(&vencmem_class, MKDEV(memalloc_major, 0));
class_destroy(&vencmem_class);
PDEBUG("module removed\n");
}
/* VFS methods */
static struct file_operations memalloc_fops = {.owner = THIS_MODULE,
.open = memalloc_open,
.release = memalloc_release,
.unlocked_ioctl = memalloc_ioctl,
.mmap = venc_mem_mmap,
#ifdef CONFIG_COMPAT
.compat_ioctl = memalloc_compat_ioctl,
#endif
};
static int memalloc_init(struct platform_device *pf_dev)
{
int result;
int ret = 0;
PDEBUG("module init\n");
pr_info("memalloc: Linear Memory Allocator\n");
pr_info("============== memalloc_init this is probe func\n");
ret = of_reserved_mem_device_init(&pf_dev->dev);
if (ret) {
pr_info("reserve memory init fail:%d\n", ret);
return ret;
}
vaddr = dma_alloc_coherent(&pf_dev->dev, alloc_size*SZ_1M, &paddr, GFP_KERNEL);
//vaddr = codec_mm_vmap(paddr, 32 * SZ_1M);
pr_info("------- vaddr: %lpx, paddr: %lx\n", vaddr, paddr);
alloc_base = paddr;
pr_info("memalloc: alloc_size = 0x%x,Linear memory base = %px\n", alloc_size,
(void *)alloc_base);
chunks = (alloc_size * 1024 * 1024) / CHUNK_SIZE;
pr_info("memalloc: Total size %d MB; %d chunks of size %lu\n", alloc_size, (int)chunks,
CHUNK_SIZE);
hlina_chunks = vmalloc(chunks * sizeof(hlina_chunk));
if (!hlina_chunks) {
pr_err("memalloc: cannot allocate hlina_chunks\n");
result = -ENOMEM;
goto err;
}
result = register_chrdev(memalloc_major, "memalloc", &memalloc_fops);
if (result < 0) {
PDEBUG("memalloc: unable to get major %d\n", memalloc_major);
goto err;
} else if (result != 0) { /* this is for dynamic major */
memalloc_major = result;
}
ret = class_register(&vencmem_class);
if (ret < 0) {
pr_info("hantro: error create venc class!");
return ret;
}
device = device_create(&vencmem_class, NULL, MKDEV(memalloc_major, 0), NULL, DEVICE_NAME);
if (IS_ERR(device)) {
class_destroy(&vencmem_class);
unregister_chrdev(memalloc_major, DEVICE_NAME);
ret = PTR_ERR(device);
goto err;
}
ResetMems();
return 0;
err:
if (hlina_chunks)
vfree(hlina_chunks);
return result;
}
/* Cycle through the buffers we have, give the first free one */
static int AllocMemory(unsigned int *busaddr, unsigned int size, const struct file *filp)
{
int i = 0;
int j = 0;
unsigned int skip_chunks = 0;
/* calculate how many chunks we need; round up to chunk boundary */
unsigned int alloc_chunks = (size + CHUNK_SIZE - 1) / CHUNK_SIZE;
*busaddr = 0;
/* run through the chunk table */
for (i = 0; i < chunks;) {
skip_chunks = 0;
/* if this chunk is available */
if (!hlina_chunks[i].chunks_reserved) {
/* check that there is enough memory left */
if (i + alloc_chunks > chunks)
break;
/* check that there is enough consecutive chunks available */
for (j = i; j < i + alloc_chunks; j++) {
if (hlina_chunks[j].chunks_reserved) {
skip_chunks = 1;
/* skip the used chunks */
i = j + hlina_chunks[j].chunks_reserved;
break;
}
}
/* if enough free memory found */
if (!skip_chunks) {
*busaddr = hlina_chunks[i].bus_address;
hlina_chunks[i].filp = filp;
hlina_chunks[i].chunks_reserved = alloc_chunks;
break;
}
} else {
/* skip the used chunks */
i += hlina_chunks[i].chunks_reserved;
}
}
if (*busaddr == 0) {
pr_info("memalloc: Allocation FAILED: size = %d\n", size);
return -EFAULT;
} else {
PDEBUG("MEMALLOC OK: size: %d, reserved: %ld\n", size, alloc_chunks * CHUNK_SIZE);
}
return 0;
}
/* Free a buffer based on bus address */
static int FreeMemory(unsigned int busaddr, const struct file *filp)
{
int i = 0;
for (i = 0; i < chunks; i++) {
/* user space SW has stored the translated bus address, add addr_transl to
* translate back to our address space
*/
if (hlina_chunks[i].bus_address == busaddr + addr_transl) {
if (hlina_chunks[i].filp == filp) {
hlina_chunks[i].filp = NULL;
hlina_chunks[i].chunks_reserved = 0;
} else {
pr_warn("memalloc: Owner mismatch while freeing memory!\n");
}
break;
}
}
return 0;
}
/* Reset "used" status */
static void ResetMems(void)
{
int i = 0;
unsigned long ba = alloc_base;
for (i = 0; i < chunks; i++) {
hlina_chunks[i].bus_address = ba;
hlina_chunks[i].filp = NULL;
hlina_chunks[i].chunks_reserved = 0;
ba += CHUNK_SIZE;
}
}
static int encmem_vce_probe(struct platform_device *pf_dev)
{
pr_info("encmem_vce_probe\n");
memalloc_init(pf_dev);
return 0;
}
static int encmem_vce_remove(struct platform_device *pf_dev)
{
pr_info("encmem_vce_remove:\n");
memalloc_cleanup(pf_dev);
return 0;
}
static const struct of_device_id amlogic_venc_mem_match[] = {{
.compatible = "encmem_rev",
},
{}};
static struct platform_driver venc_mem_driver = {.probe = encmem_vce_probe,
.remove = encmem_vce_remove,
.driver = {
.name = "encmem_rev",
.owner = THIS_MODULE,
.of_match_table = amlogic_venc_mem_match,
}};
int __init enc_memallc_init(void)
{
pr_info("enc_mem_init: enc_mem_init\n");
platform_driver_register(&venc_mem_driver);
return 0;
}
void __exit enc_memallc_exit(void)
{
pr_info("enc_mem_init: enc_mem_exit\n");
platform_driver_unregister(&venc_mem_driver);
}
module_init(enc_memallc_init);
module_exit(enc_memallc_exit);
/* module description */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Amlogic Inc.");
MODULE_DESCRIPTION("Linear RAM allocation");