mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-07 08:48:05 -04:00
474 lines
12 KiB
C
474 lines
12 KiB
C
/*
|
|
* Copyright (c) 2013 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/memblock.h>
|
|
#include "exynos-mcomp.h"
|
|
|
|
/* file rw */
|
|
#include <linux/file.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/fcntl.h>
|
|
#include <asm/uaccess.h>
|
|
|
|
#define CHK_SIZE 4096
|
|
|
|
static const char driver_name[] = "exynos-mcomp";
|
|
|
|
/* irq, SFR base, address information and tasklet */
|
|
struct memory_comp mem_comp;
|
|
struct tasklet_struct tasklet;
|
|
static unsigned short *comp_info;
|
|
static struct cout_pool {
|
|
struct list_head node;
|
|
unsigned char *buf;
|
|
bool is_used;
|
|
} couts[10];
|
|
static LIST_HEAD(cout_list);
|
|
DEFINE_SPINLOCK(cout_lock);
|
|
|
|
static int memory_decomp(unsigned char *decout_data, const unsigned char *comp_data,
|
|
unsigned int comp_len, unsigned char* Cout_data)
|
|
{
|
|
register unsigned int Chdr_data;
|
|
register unsigned int Dhdin; // inner header of DAE
|
|
unsigned char* start_comp_data;
|
|
unsigned char* start_Cout_data;
|
|
unsigned char* start_decout_data;
|
|
|
|
start_comp_data = (unsigned char *)comp_data;
|
|
start_Cout_data = Cout_data;
|
|
start_decout_data = decout_data;
|
|
|
|
do {
|
|
Chdr_data = *comp_data;
|
|
comp_data++;
|
|
|
|
if(~Chdr_data & 0x01) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data+=2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
|
|
if(~Chdr_data & 0x02) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data+=2;
|
|
comp_data+=2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x04) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data+=2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x08) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x10) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x20) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x40) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
if(~Chdr_data & 0x80) {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)comp_data);
|
|
Cout_data += 2;
|
|
comp_data += 2;
|
|
} else {
|
|
*((unsigned short*)Cout_data) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
|
|
if((*comp_data) >> 7) {
|
|
*((unsigned short*)(Cout_data)) = *((unsigned short*)(Cout_data - ((*comp_data) & 0x7f)));
|
|
Cout_data += 2;
|
|
}
|
|
comp_data++;
|
|
}
|
|
|
|
} while(comp_data - start_comp_data < comp_len);
|
|
|
|
Cout_data = start_Cout_data;
|
|
|
|
do {
|
|
Dhdin = (*Cout_data);
|
|
Cout_data += ((Dhdin & 0x01));
|
|
*decout_data = ((Dhdin >> 0 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 1 & 0x01));
|
|
*(decout_data + 1) = ((Dhdin >> 1 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 2 & 0x01));
|
|
*(decout_data + 2) = ((Dhdin >> 2 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 3 & 0x01));
|
|
*(decout_data + 3) = ((Dhdin >> 3 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 4 & 0x01));
|
|
*(decout_data + 4) = ((Dhdin >> 4 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 5 & 0x01));
|
|
*(decout_data + 5) = ((Dhdin >> 5 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 6 & 0x01));
|
|
*(decout_data + 6) = ((Dhdin >> 6 & 0x01)) * (*Cout_data);
|
|
Cout_data += ((Dhdin >> 7 & 0x01));
|
|
*(decout_data + 7) = ((Dhdin >> 7 & 0x01)) * (*Cout_data);
|
|
decout_data += 8;
|
|
Cout_data += 1;
|
|
} while(decout_data - start_decout_data < CHK_SIZE);
|
|
|
|
Cout_data = start_Cout_data;
|
|
decout_data = start_decout_data;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* mem_comp_irq - irq clear and scheduling the sswap thread
|
|
*/
|
|
static irqreturn_t mem_comp_irq_handler(int irq, void *dev_id)
|
|
{
|
|
int done_st = 0;
|
|
|
|
/* check the incorrect access */
|
|
done_st = readl(mem_comp.base + ISR) & (0x1);
|
|
|
|
if (!done_st) {
|
|
pr_debug("%s: interrupt does not happen\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* clear interrupt */
|
|
writel(ISR_CLEAR, mem_comp.base + ISR);
|
|
/* scheduling the sswap thread */
|
|
tasklet_hi_schedule(&tasklet);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
/*
|
|
* memory_comp_start_compress - let HW IP for compressing start
|
|
* @disk_num: disk number of sswap disk.
|
|
* @nr_pages: the number of pages which request compressing
|
|
*
|
|
* Write the address of sswap disk, compbuf, compinfo into SFR.
|
|
* and let HW IP start by writing CMD register, or return EBUSY
|
|
* if HW IP is busy compressing another disk.
|
|
*/
|
|
static int memory_comp_start_compress(u32 disk_num, u32 nr_pages)
|
|
{
|
|
struct memory_comp *mc = &mem_comp;
|
|
phys_addr_t page;
|
|
|
|
/* check for device whether or not HW IP is compressing */
|
|
while ((readl(mc->base + CMD) & 0x1) == 0x1);
|
|
|
|
/* if 0, compress the maximum size, 8MB */
|
|
if (!nr_pages)
|
|
nr_pages = SZ_8M >> PAGE_SHIFT;
|
|
|
|
/* input align addr */
|
|
page = mc->sswap_disk_addr[disk_num];
|
|
page = page >> 12;
|
|
pr_debug("disk_addr = %llx\n", page);
|
|
__raw_writel(page, mc->base + DISK_ADDR);
|
|
|
|
page = mc->comp_buf_addr[disk_num];
|
|
page = page >> 12;
|
|
pr_debug("comp_addr = %llx\n", page);
|
|
__raw_writel(page, mc->base + COMPBUF_ADDR);
|
|
|
|
page = mc->comp_info_addr[disk_num];
|
|
page = page >> 12;
|
|
pr_debug("comp_info_addr = %llx\n", page);
|
|
__raw_writel(page, mc->base + COMPINFO_ADDR);
|
|
|
|
/* set cmd of start */
|
|
writel((nr_pages << CMD_PAGES) | CMD_START, mc->base + CMD);
|
|
|
|
while ((readl(mc->base + CMD) & 0x1) == 0x1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int get_comp_len(unsigned short *comp_info, int nr_page)
|
|
{
|
|
int i;
|
|
int len = 0;
|
|
int count = (nr_page % 8 == 0)? nr_page/8 : nr_page/8 + 1;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
len += comp_info[i];
|
|
}
|
|
|
|
pr_debug("comp_info count: %d nr_page = %d comp_len = %d\n",
|
|
count, nr_page, len);
|
|
|
|
return len;
|
|
}
|
|
|
|
int mcomp_compress_page(int disk_num, const unsigned char *in, unsigned char *out)
|
|
{
|
|
int comp_len;
|
|
|
|
pr_debug("%s in=%p out=%p\n", __func__, in, out);
|
|
mem_comp.sswap_disk_addr[disk_num] = virt_to_phys(in);
|
|
mem_comp.comp_buf_addr[disk_num] = virt_to_phys(out);
|
|
mem_comp.comp_info_addr[disk_num] = virt_to_phys(comp_info);
|
|
memory_comp_start_compress(disk_num, 1);
|
|
|
|
comp_len = get_comp_len(comp_info, 1);
|
|
|
|
return comp_len;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mcomp_compress_page);
|
|
|
|
int mcomp_decompress_page(const unsigned char *in, int comp_len, unsigned char *out)
|
|
{
|
|
unsigned long flags;
|
|
struct cout_pool *cout;
|
|
unsigned char *buf;
|
|
|
|
spin_lock_irqsave(&cout_lock, flags);
|
|
list_for_each_entry(cout, &cout_list, node) {
|
|
if (!cout->is_used) {
|
|
buf = cout->buf;
|
|
cout->is_used = true;
|
|
break;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&cout_lock, flags);
|
|
|
|
memory_decomp(out, in, comp_len, buf);
|
|
|
|
spin_lock_irqsave(&cout_lock, flags);
|
|
cout->is_used = false;
|
|
spin_unlock_irqrestore(&cout_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(mcomp_decompress_page);
|
|
|
|
static int memory_compressor_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
int ret = 0;
|
|
int irq, i;
|
|
u32 temp;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
if (!res) {
|
|
pr_debug("Fail to get map register\n");
|
|
return -1;
|
|
}
|
|
mem_comp.base = devm_ioremap_resource(dev, res);
|
|
irq = platform_get_irq(pdev, 0);
|
|
if (irq < 0) {
|
|
pr_debug("Fail to get IRQ resource \n");
|
|
return -1;
|
|
}
|
|
mem_comp.irq = irq;
|
|
|
|
/* interrupt register */
|
|
ret = request_irq(mem_comp.irq, mem_comp_irq_handler, IRQF_DISABLED,
|
|
driver_name, NULL);
|
|
|
|
/* mem_comp struct reset */
|
|
for (i = 0; i < NUM_DISK; i++) {
|
|
mem_comp.comp_info_addr[i] = 0;
|
|
mem_comp.comp_buf_addr[i] = 0;
|
|
mem_comp.sswap_disk_addr[i] = 0;
|
|
}
|
|
|
|
/* control */
|
|
temp = readl(mem_comp.base + CONTROL);
|
|
temp |= (0x1 << CONTROL_ARUSER);
|
|
temp |= (0x1 << CONTROL_AWUSER);
|
|
temp |= (0xFF << CONTROL_THRESHOLD);
|
|
temp |= (0x2 << CONTROL_ARCACHE);
|
|
temp |= (0x2 << CONTROL_AWCACHE);
|
|
writel(temp, mem_comp.base + CONTROL);
|
|
|
|
comp_info = (unsigned short *)get_zeroed_page(GFP_KERNEL);
|
|
if (!comp_info) {
|
|
pr_info("page alloc fail\n");
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(couts); i++) {
|
|
couts[i].buf = (unsigned char *)page_address(alloc_pages(GFP_ATOMIC, 1));
|
|
if (!couts[i].buf) {
|
|
pr_info("page alloc fail: cout\n");
|
|
goto err_cout_alloc;
|
|
}
|
|
couts[i].is_used = false;
|
|
list_add(&couts[i].node, &cout_list);
|
|
}
|
|
|
|
dev_info(&pdev->dev, "Loaded driver for Mcomp\n");
|
|
|
|
return ret;
|
|
|
|
err_cout_alloc:
|
|
for (i = 0; i < ARRAY_SIZE(couts); i++) {
|
|
if (couts[i].buf)
|
|
free_pages((unsigned long)couts[i].buf, 1);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int memory_compressor_remove(struct platform_device *pdev)
|
|
{
|
|
int i;
|
|
|
|
/* unmapping */
|
|
iounmap(mem_comp.base);
|
|
|
|
/* remove an interrupt handler */
|
|
free_irq(mem_comp.irq, NULL);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(couts); i++) {
|
|
if (couts[i].buf)
|
|
free_pages((unsigned long)couts[i].buf, 1);
|
|
}
|
|
|
|
free_page((unsigned long)comp_info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mcomp_dt_match[] = {
|
|
{
|
|
.compatible = "samsung,exynos-mcomp",
|
|
},
|
|
{},
|
|
};
|
|
|
|
static struct platform_driver mem_comp_driver = {
|
|
.probe = memory_compressor_probe,
|
|
.remove = memory_compressor_remove,
|
|
.driver = {
|
|
.name = "exynos-mcomp",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = mcomp_dt_match,
|
|
}
|
|
};
|
|
|
|
static int __init memory_compressor_init(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
ret = platform_driver_register(&mem_comp_driver);
|
|
if (!ret)
|
|
pr_info("%s: init\n",
|
|
mem_comp_driver.driver.name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit memory_compressor_exit(void)
|
|
{
|
|
platform_driver_unregister(&mem_comp_driver);
|
|
}
|
|
late_initcall(memory_compressor_init);
|
|
module_exit(memory_compressor_exit);
|
|
|
|
MODULE_DESCRIPTION("memory_compressor");
|
|
MODULE_AUTHOR("Samsung");
|
|
MODULE_LICENSE("GPL");
|