SmartAudio/lichee/linux-4.9/drivers/char/sunxi_tr/sunxi_transform.c

1236 lines
28 KiB
C
Executable File

/* linux/drivers/char/sunxi_tr/sunxi_tr.c
*
* Copyright (c) 2014 Allwinnertech Co., Ltd.
* Author: Tyle <tyle@allwinnertech.com>
*
* Transform driver for sunxi platform
*
* 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 "transform.h"
#include <linux/compat.h>
#include <linux/idr.h>
#include <linux/dma-buf.h>
#define TR_CHAN_MAX 32
/* #define TR_POLL */
/* #define TR_CHECK_THREAD */
#if (defined CONFIG_ARCH_SUN50IW3P1) || (defined CONFIG_ARCH_SUN50IW1P1)
#define USE_DMA_BUF
#endif
struct dmabuf_item {
struct list_head list;
int fd;
struct dma_buf *buf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt;
dma_addr_t dma_addr;
unsigned long long id;
};
struct sunxi_transform {
int id; /* chan id */
struct rb_node node;
struct list_head list;
bool requested; /* indicate if have request */
bool busy; /* at busy state when transforming */
bool error;/* indicate some error happens in this channel */
unsigned long start_time; /* the time when starting transform */
unsigned long timeout;/* ms */
tr_info info;
};
struct sunxi_trdev {
struct device *dev;
void __iomem *base;
int irq;
struct clk *clk; /* clock gate for tr */
spinlock_t slock;
struct mutex mlock;
struct list_head trs; /* transform chan list */
unsigned int count; /* transform channel counter */
struct sunxi_transform *cur_tr; /*curent transform channel processing*/
bool busy;
struct task_struct *task;
struct idr idr;
struct rb_node node;
struct rb_root handles;
};
static struct sunxi_trdev *gsunxi_dev;
static struct cdev *tr_cdev;
static dev_t devid;
static struct class *tr_class;
static struct device *tr_dev;
static bool dev_init;
static struct mutex id_lock;
static struct device *dmabuf_dev;
u32 dbg_info;
/* #define struct sunxi_trdev *to_sunxi_trdev(dev)
* container_of(dev, struct sunxi_trdev, dev)
*/
static int sunxi_tr_finish_procss(void);
#if !defined(CONFIG_OF)
static struct resource tr_resource[] = {
[0] = {
.start = (int __force)SUNXI_DE_VBASE,
.end = (int __force)(SUNXI_DE_VBASE + SUNXI_DE_SIZE),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = (int __force)SUNXI_IRQ_DEIRQ1,
.end = (int __force)SUNXI_IRQ_DEIRQ1,
.flags = IORESOURCE_IRQ,
},
};
#endif
static ssize_t tr_debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "debug=%d\n", dbg_info);
}
static ssize_t tr_debug_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
if (strncasecmp(buf, "1", 1) == 0)
dbg_info = 1;
else if (strncasecmp(buf, "0", 1) == 0)
dbg_info = 0;
else
pr_err("Error input!\n");
return count;
}
static DEVICE_ATTR(debug, 0660,
tr_debug_show, tr_debug_store);
static struct attribute *tr_attributes[] = {
&dev_attr_debug.attr,
NULL
};
static struct attribute_group tr_attribute_group = {
.name = "attr",
.attrs = tr_attributes
};
#ifdef USE_DMA_BUF
static int tr_dma_map(int fd, struct dmabuf_item *item)
{
struct dma_buf *dmabuf;
struct dma_buf_attachment *attachment;
struct sg_table *sgt, *sgt_bak;
struct scatterlist *sgl, *sgl_bak;
s32 sg_count = 0;
int ret = -1;
int i;
if (fd < 0) {
pr_err("dma_buf_id(%d) is invalid\n", fd);
goto exit;
}
dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf)) {
pr_err("dma_buf_get failed\n");
goto exit;
}
attachment = dma_buf_attach(dmabuf, dmabuf_dev);
if (IS_ERR(attachment)) {
pr_err("dma_buf_attach failed\n");
goto err_buf_put;
}
sgt = dma_buf_map_attachment(attachment, DMA_FROM_DEVICE);
if (IS_ERR_OR_NULL(sgt)) {
pr_err("dma_buf_map_attachment failed\n");
goto err_buf_detach;
}
/* create a private sgtable base on the given dmabuf */
sgt_bak = kmalloc(sizeof(struct sg_table), GFP_KERNEL | __GFP_ZERO);
if (sgt_bak == NULL) {
pr_err("alloc sgt fail\n");
goto err_buf_unmap;
}
ret = sg_alloc_table(sgt_bak, sgt->nents, GFP_KERNEL);
if (ret != 0) {
pr_err("alloc sgt fail\n");
goto err_kfree;
}
sgl_bak = sgt_bak->sgl;
for_each_sg(sgt->sgl, sgl, sgt->nents, i) {
sg_set_page(sgl_bak, sg_page(sgl), sgl->length, sgl->offset);
sgl_bak = sg_next(sgl_bak);
}
sg_count = dma_map_sg_attrs(dmabuf_dev, sgt_bak->sgl,
sgt_bak->nents, DMA_FROM_DEVICE,
DMA_ATTR_SKIP_CPU_SYNC);
if (sg_count != 1) {
pr_err("dma_map_sg failed:%d\n", sg_count);
goto err_sgt_free;
}
item->fd = fd;
item->buf = dmabuf;
item->sgt = sgt_bak;
item->attachment = attachment;
item->dma_addr = sg_dma_address(sgt_bak->sgl);
ret = 0;
goto exit;
err_sgt_free:
sg_free_table(sgt_bak);
err_kfree:
kfree(sgt_bak);
err_buf_unmap:
/* unmap attachment sgt, not sgt_bak, because it's not alloc yet! */
dma_buf_unmap_attachment(attachment, sgt, DMA_FROM_DEVICE);
err_buf_detach:
dma_buf_detach(dmabuf, attachment);
err_buf_put:
dma_buf_put(dmabuf);
exit:
return ret;
}
static void tr_dma_unmap(struct dmabuf_item *item)
{
dma_unmap_sg_attrs(dmabuf_dev, item->sgt->sgl,
item->sgt->nents, DMA_FROM_DEVICE,
DMA_ATTR_SKIP_CPU_SYNC);
dma_buf_unmap_attachment(item->attachment, item->sgt, DMA_FROM_DEVICE);
sg_free_table(item->sgt);
kfree(item->sgt);
dma_buf_detach(item->buf, item->attachment);
dma_buf_put(item->buf);
}
static struct tr_format_attr fmt_attr_tbl[] = {
/*
*format bits
* hor_rsample(u,v)
* ver_rsample(u,v)
* uvc
* interleave
* factor
* div
*/
{ TR_FORMAT_ARGB_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_ABGR_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_RGBA_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_BGRA_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_XRGB_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_XBGR_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_RGBX_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_BGRX_8888, 8, 1, 1, 1, 1, 0, 1, 4, 1},
{ TR_FORMAT_RGB_888, 8, 1, 1, 1, 1, 0, 1, 3, 1},
{ TR_FORMAT_BGR_888, 8, 1, 1, 1, 1, 0, 1, 3, 1},
{ TR_FORMAT_RGB_565, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_BGR_565, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_ARGB_4444, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_ABGR_4444, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_RGBA_4444, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_BGRA_4444, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_ARGB_1555, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_ABGR_1555, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_RGBA_5551, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_BGRA_5551, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_YUV444_I_AYUV, 8, 1, 1, 1, 1, 0, 1, 3, 1},
{ TR_FORMAT_YUV444_I_VUYA, 8, 1, 1, 1, 1, 0, 1, 3, 1},
{ TR_FORMAT_YUV422_I_YVYU, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_YUV422_I_YUYV, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_YUV422_I_UYVY, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_YUV422_I_VYUY, 8, 1, 1, 1, 1, 0, 1, 2, 1},
{ TR_FORMAT_YUV444_P, 8, 1, 1, 1, 1, 0, 1, 1, 1},
{ TR_FORMAT_YUV422_P, 8, 2, 2, 1, 1, 0, 0, 2, 1},
{ TR_FORMAT_YUV420_P, 8, 2, 2, 2, 2, 0, 0, 3, 2},
{ TR_FORMAT_YUV411_P, 8, 4, 4, 1, 1, 0, 0, 3, 2},
{ TR_FORMAT_YUV422_SP_UVUV, 8, 2, 2, 1, 1, 1, 0, 2, 1},
{ TR_FORMAT_YUV422_SP_VUVU, 8, 2, 2, 1, 1, 1, 0, 2, 1},
{ TR_FORMAT_YUV420_SP_UVUV, 8, 2, 2, 2, 2, 1, 0, 3, 2},
{ TR_FORMAT_YUV420_SP_VUVU, 8, 2, 2, 2, 2, 1, 0, 3, 2},
{ TR_FORMAT_YUV411_SP_UVUV, 8, 4, 4, 1, 1, 1, 0, 3, 2},
{ TR_FORMAT_YUV411_SP_VUVU, 8, 4, 4, 1, 1, 1, 0, 3, 2},
};
s32 tr_set_info(tr_frame *tr_para, struct dmabuf_item *item)
{
s32 ret = -1;
u32 i = 0;
u32 len = ARRAY_SIZE(fmt_attr_tbl);
u32 y_width, y_height, u_width, u_height;
u32 y_pitch, u_pitch;
u32 y_size, u_size;
tr_para->laddr[0] = item->dma_addr;
if (tr_para->fmt >= TR_FORMAT_MAX) {
pr_err("%s, format 0x%x is out of range\n", __func__,
tr_para->fmt);
goto exit;
}
for (i = 0; i < len; ++i) {
if (fmt_attr_tbl[i].format == tr_para->fmt) {
y_width = tr_para->pitch[0];
y_height = tr_para->height[0];
u_width = y_width/fmt_attr_tbl[i].hor_rsample_u;
u_height = y_height/fmt_attr_tbl[i].ver_rsample_u;
y_pitch = y_width;
u_pitch = u_width * (fmt_attr_tbl[i].uvc + 1);
y_size = y_pitch * y_height;
u_size = u_pitch * u_height;
tr_para->laddr[1] = tr_para->laddr[0] + y_size;
tr_para->laddr[2] = tr_para->laddr[0] + y_size + u_size;
if (tr_para->fmt == TR_FORMAT_YUV420_P) {
/* v */
tr_para->laddr[1] = tr_para->laddr[0] + y_size + u_size;
tr_para->laddr[2] = tr_para->laddr[0] + y_size; /* u */
}
ret = 0;
break;
}
}
if (ret != 0)
pr_err("%s, format 0x%x is invalid\n", __func__,
tr_para->fmt);
exit:
return ret;
}
#endif
#if defined(TR_CHECK_THREAD)
static int tr_thread(void *parg)
{
while (1) {
struct sunxi_transform *tr = NULL;
unsigned long timeout = 0;
if (kthread_should_stop())
break;
tr = gsunxi_dev->cur_tr;
if (tr) {
timeout = tr->start_time +
msecs_to_jiffies(tr->timeout);
if (time_after_eq(jiffies, timeout)) {
pr_warn("%s, timeout(%d ms)\n", __func__,
jiffies_to_msecs(jiffies - tr->start_time));
de_tr_reset();
tr->busy = false;
tr->error = true;
sunxi_tr_finish_procss();
}
}
msleep(10);
}
return 0;
}
#endif
static int tr_check_timeout(void)
{
struct sunxi_transform *tr = NULL;
unsigned long timeout = 0;
unsigned long flags;
tr = gsunxi_dev->cur_tr;
if (tr == NULL)
return 0;
spin_lock_irqsave(&gsunxi_dev->slock, flags);
timeout = tr->start_time + msecs_to_jiffies(tr->timeout);
if (tr->busy && time_after_eq(jiffies, timeout)) {
tr->busy = false;
tr->error = true;
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
de_tr_exception();
pr_warn("%s, timeout(%d ms)\n", __func__,
jiffies_to_msecs(jiffies - tr->start_time));
sunxi_tr_finish_procss();
} else
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
return 0;
}
/* find a tr which has request and a longest time to process */
static struct sunxi_transform *tr_find_proper_task(void)
{
struct sunxi_transform *tr = NULL, *proper_tr = NULL;
unsigned long min_time = jiffies;
list_for_each_entry(tr, &gsunxi_dev->trs, list) {
bool condition1 = (true == tr->requested);
bool condition2 = time_after_eq(min_time, tr->start_time);
if (condition1 && condition2) {
min_time = tr->start_time;
proper_tr = tr;
} else {
/* printk("find_task: %d,%d, %ld,%ld\n", condition1,
* condition2, min_time, tr->start_time);
*/
}
}
return proper_tr;
}
/* protect by @slock */
static int tr_process_next_proper_task(u32 from)
{
unsigned long flags;
struct sunxi_transform *tr = NULL;
int ret = -1;
spin_lock_irqsave(&gsunxi_dev->slock, flags);
if (gsunxi_dev->busy) {
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
return -1;
}
/* find a tr which has request */
tr = tr_find_proper_task();
if (tr != NULL) {
/* process request */
gsunxi_dev->busy = true;
tr->busy = true;
tr->error = false;
tr->start_time = jiffies;
tr->requested = false;
gsunxi_dev->cur_tr = tr;
}
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
if (tr != NULL)
ret = de_tr_set_cfg(&tr->info);
return ret;
}
static int sunxi_tr_finish_procss(void)
{
unsigned long flags;
/* correct interrupt */
spin_lock_irqsave(&gsunxi_dev->slock, flags);
if (gsunxi_dev->cur_tr) {
gsunxi_dev->cur_tr->busy = false;
gsunxi_dev->cur_tr = NULL;
gsunxi_dev->busy = false;
}
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
tr_process_next_proper_task(0);
return 0;
}
static struct sunxi_transform *tr_get_by_id(int id)
{
struct sunxi_transform *tr;
mutex_lock(&gsunxi_dev->mlock);
tr = idr_find(&gsunxi_dev->idr, id);
mutex_unlock(&gsunxi_dev->mlock);
return tr ? tr : ERR_PTR(-EINVAL);
}
/*
* sunxi_tr_request - request transform channel
* On success, returns transform handle. On failure, returns 0.
*/
unsigned int sunxi_tr_request(void)
{
struct sunxi_transform *tr = NULL;
unsigned long flags;
unsigned int count = 0;
struct rb_node **p = &gsunxi_dev->handles.rb_node;
struct rb_node *parent = NULL;
struct sunxi_transform *entry;
int id;
if (gsunxi_dev->count > TR_CHAN_MAX) {
pr_warn("%s(), user number have exceed max number %d\n",
__func__, TR_CHAN_MAX);
return 0;
}
tr = kzalloc(sizeof(struct sunxi_transform), GFP_KERNEL);
if (!tr) {
pr_warn("alloc fail\n");
return 0;
}
RB_CLEAR_NODE(&tr->node);
tr->requested = false;
tr->busy = false;
tr->error = false;
tr->timeout = 50; /* default 50ms timeout */
tr->start_time = jiffies;
id = idr_alloc(&gsunxi_dev->idr, tr, 1, 0, GFP_KERNEL);
if (id < 0)
return 0;
tr->id = id;
mutex_lock(&gsunxi_dev->mlock);
while (*p) {
parent = *p;
entry = rb_entry(parent, struct sunxi_transform, node);
if (tr < entry)
p = &(*p)->rb_left;
else if (tr > entry)
p = &(*p)->rb_right;
else
pr_warn("%s: tr already found.\n", __func__);
}
rb_link_node(&tr->node, parent, p);
rb_insert_color(&tr->node, &gsunxi_dev->handles);
mutex_unlock(&gsunxi_dev->mlock);
spin_lock_irqsave(&gsunxi_dev->slock, flags);
list_add_tail(&tr->list, &gsunxi_dev->trs);
gsunxi_dev->count++;
count = gsunxi_dev->count;
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
mutex_lock(&gsunxi_dev->mlock);
if (count == 1) {
if (gsunxi_dev->clk)
clk_prepare_enable(gsunxi_dev->clk);
de_tr_init();
#if defined(TR_CHECK_THREAD)
gsunxi_dev->task = kthread_create(tr_thread,
(void *)0, "tr_thread");
if (IS_ERR(gsunxi_dev->task)) {
pr_warn("Unable to start kernel thread %s.\n",
"tr_thread");
gsunxi_dev->task = NULL;
} else
wake_up_process(gsunxi_dev->task);
#endif
}
mutex_unlock(&gsunxi_dev->mlock);
TR_INFO_MSG("%s, count=%d\n", __func__, count);
return id;
}
EXPORT_SYMBOL_GPL(sunxi_tr_request);
/*
* sunxi_tr_release - release transform channel
* @hdl: transform handle which return by sunxi_tr_request
* On success, returns 0. On failure, returns ERR_PTR(-errno).
*/
int sunxi_tr_release(unsigned int id)
{
struct sunxi_transform *tr = tr_get_by_id(id);
unsigned long flags;
unsigned int count = 0;
if (IS_ERR_OR_NULL(tr)) {
pr_warn("%s, hdl is invalid!\n", __func__);
return -EINVAL;
}
mutex_lock(&gsunxi_dev->mlock);
idr_remove(&gsunxi_dev->idr, tr->id);
if (!RB_EMPTY_NODE(&tr->node))
rb_erase(&tr->node, &gsunxi_dev->handles);
mutex_unlock(&gsunxi_dev->mlock);
spin_lock_irqsave(&gsunxi_dev->slock, flags);
list_del(&tr->list);
gsunxi_dev->count--;
count = gsunxi_dev->count;
kfree((void *)tr);
spin_unlock_irqrestore(&gsunxi_dev->slock, flags);
mutex_lock(&gsunxi_dev->mlock);
if (count == 0) {
#if defined(TR_CHECK_THREAD)
kthread_stop(gsunxi_dev->task);
gsunxi_dev->task = NULL;
#endif
de_tr_exit();
if (gsunxi_dev->clk)
clk_disable(gsunxi_dev->clk);
}
mutex_unlock(&gsunxi_dev->mlock);
TR_INFO_MSG("%s, count=%d\n", __func__, count);
return 0;
}
EXPORT_SYMBOL_GPL(sunxi_tr_release);
/*
* sunxi_tr_commit - commit an transform request
* @hdl: transform handle which return by sunxi_tr_request
* On success, returns 0. On failure, returns ERR_PTR(-errno).
*/
int sunxi_tr_commit(unsigned int id, tr_info *info)
{
int ret = 0;
struct sunxi_transform *tr = tr_get_by_id(id);
#ifdef USE_DMA_BUF
struct dmabuf_item *src_item = NULL;
struct dmabuf_item *dst_item = NULL;
src_item = kmalloc(sizeof(struct dmabuf_item),
GFP_KERNEL | __GFP_ZERO);
if (src_item == NULL) {
pr_err("malloc memory of size %ld fail!\n",
sizeof(struct dmabuf_item));
goto EXIT;
}
dst_item = kmalloc(sizeof(struct dmabuf_item),
GFP_KERNEL | __GFP_ZERO);
if (dst_item == NULL) {
pr_err("malloc memory of size %ld fail!\n",
sizeof(struct dmabuf_item));
goto FREE_SRC;
}
#endif
if (IS_ERR_OR_NULL(tr)) {
pr_warn("%s, hdl is invalid\n", __func__);
return -EINVAL;
}
TR_INFO_MSG("Input info:\n");
TR_INFO_MSG(" Format: 0x%x\n", info->src_frame.fmt);
TR_INFO_MSG(" Pitch: %d,%d,%d\n", info->src_frame.pitch[0],
info->src_frame.pitch[1], info->src_frame.pitch[2]);
TR_INFO_MSG(" RECT X:%d\n", info->src_rect.x);
TR_INFO_MSG(" RECT Y:%d\n", info->src_rect.y);
TR_INFO_MSG(" RECT W:%d\n", info->src_rect.w);
TR_INFO_MSG(" RECT H:%d\n", info->src_rect.h);
TR_INFO_MSG("Output info:\n");
TR_INFO_MSG(" Format: 0x%x\n", info->dst_frame.fmt);
TR_INFO_MSG(" Pitch: %d,%d,%d\n", info->dst_frame.pitch[0],
info->dst_frame.pitch[1], info->dst_frame.pitch[2]);
TR_INFO_MSG(" RECT X:%d\n", info->dst_rect.x);
TR_INFO_MSG(" RECT Y:%d\n", info->dst_rect.y);
TR_INFO_MSG(" RECT W:%d\n", info->dst_rect.w);
TR_INFO_MSG(" RECT H:%d\n", info->dst_rect.h);
if (!tr->requested && !tr->busy) {
memcpy(&tr->info, info, sizeof(tr_info));
tr->requested = true;
#ifdef USE_DMA_BUF
ret = tr_dma_map(tr->info.src_frame.fd, src_item);
if (ret != 0) {
pr_err("map src_item fail!\n");
goto FREE_DST;
}
ret = tr_dma_map(tr->info.dst_frame.fd, dst_item);
if (ret != 0) {
pr_err("map dst_item fail!\n");
goto SRC_DMA_UNMAP;
}
tr_set_info(&tr->info.src_frame, src_item);
tr_set_info(&tr->info.dst_frame, dst_item);
#endif
TR_INFO_MSG("InputAddr: 0x%x,0x%x,0x%x\n",
tr->info.src_frame.laddr[0],
tr->info.src_frame.laddr[1],
tr->info.src_frame.laddr[2]);
TR_INFO_MSG("OutputAddr: 0x%x,0x%x,0x%x\n",
tr->info.dst_frame.laddr[0],
tr->info.dst_frame.laddr[1],
tr->info.dst_frame.laddr[2]);
ret = tr_process_next_proper_task(1);
}
#if defined(TR_POLL)
{
int wait_cnt = 5, delay_ms = 10, i = 0;
while ((i < wait_cnt) && (de_tr_irq_query() != 0)) {
msleep(delay_ms);
i++;
}
if (wait_cnt >= 5)
pr_warn("%s, timeout !!!\n", __func__);
sunxi_tr_finish_procss();
}
#endif
#ifdef USE_DMA_BUF
tr_dma_unmap(dst_item);
SRC_DMA_UNMAP:
tr_dma_unmap(src_item);
FREE_DST:
kfree(dst_item);
FREE_SRC:
kfree(src_item);
EXIT:
return ret;
#else
return ret;
#endif
}
EXPORT_SYMBOL_GPL(sunxi_tr_commit);
/*
* sunxi_tr_query - query transform status
* @hdl: transform handle which return by sunxi_tr_request
* On finish, returns 0. On failure, returns ERR_PTR(-errno).
* On busy,returns 1.
*/
int sunxi_tr_query(unsigned int id)
{
int status = 0;
struct sunxi_transform *tr = tr_get_by_id(id);
if (IS_ERR_OR_NULL(tr)) {
pr_warn("%s, hdl is invalid!\n", __func__);
return 0;
}
#if !defined(TR_CHECK_THREAD)
tr_check_timeout();
#endif
if (tr->requested || tr->busy)
status = 1; /* busy */
else if (tr->error)
status = -1; /*error:timeout */
else
status = 0;/* finish */
return status;
#if 0
timeout = tr->start_time + msecs_to_jiffies(tr->timeout);
if (time_after_eq(jiffies, timeout)) {
pr_warn("%s, timeout(%d ms)\n", __func__,
jiffies_to_msecs(jiffies - tr->start_time));
de_tr_reset();
tr->busy = false;
return -1;
}
return tr->busy?1:0;
#endif
}
EXPORT_SYMBOL_GPL(sunxi_tr_query);
/*
* sunxi_tr_set_timeout - set transform timeout(ms)
* @hdl: transform hdl
* @timeout: time(ms)
* On success, returns 0. On failure, returns ERR_PTR(-errno).
*/
int sunxi_tr_set_timeout(unsigned int id, unsigned long timeout /* ms */)
{
struct sunxi_transform *tr = tr_get_by_id(id);
if (IS_ERR_OR_NULL(tr)) {
pr_warn("%s, hdl is invalid!\n", __func__);
return -EINVAL;
}
if (timeout == 0) {
pr_warn("%s, para error(timeout=0)!\n", __func__);
return -EINVAL;
}
tr->timeout = timeout;
return 0;
}
static irqreturn_t sunxi_tr_interrupt(int irq, void *dev_id)
{
int ret = 0;
/* get irq status */
ret = de_tr_irq_query();
if (ret == 0)
sunxi_tr_finish_procss();
/* clear irq status */
return IRQ_HANDLED;
}
static int tr_open(struct inode *inode, struct file *file)
{
return 0;
}
static int tr_release(struct inode *inode, struct file *file)
{
return 0;
}
static ssize_t tr_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
return 0;
}
static ssize_t tr_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
return 0;
}
static u64 sunxi_tr_dma_mask = DMA_BIT_MASK(32);
/* static int __devinit tr_probe(struct platform_device *pdev) */
static int tr_probe(struct platform_device *pdev)
{
#if !defined(CONFIG_OF)
struct resource *res;
#endif
struct sunxi_trdev *sunxi_dev = NULL;
int ret = 0;
int irq;
pr_info("enter %s\n", __func__);
dmabuf_dev = &pdev->dev;
dmabuf_dev->dma_mask = &sunxi_tr_dma_mask;
dmabuf_dev->coherent_dma_mask = DMA_BIT_MASK(32);
sunxi_dev = kzalloc(sizeof(struct sunxi_trdev), GFP_KERNEL);
if (!sunxi_dev)
return -ENOMEM;
/* register base */
#if !defined(CONFIG_OF)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "get reource MEM fail\n");
ret = -EINVAL;
goto io_err;
}
sunxi_dev->base = (void __iomem *)res->start;
#else
sunxi_dev->base = of_iomap(pdev->dev.of_node, 0);
if (sunxi_dev->base == NULL) {
dev_err(&pdev->dev, "unable to map transform registers\n");
ret = -EINVAL;
goto io_err;
}
#endif
/* irq */
#if !defined(CONFIG_OF)
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
ret = irq;
goto io_err;
}
#else
irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (!irq)
dev_err(&pdev->dev,
"irq_of_parse_and_map irq fail for transform\n");
#endif
sunxi_dev->irq = irq;
ret = request_irq(irq, sunxi_tr_interrupt, IRQF_SHARED,
dev_name(&pdev->dev), sunxi_dev);
if (ret) {
dev_err(&pdev->dev, "NO IRQ found!!!\n");
goto iomap_err;
}
/* clk init */
sunxi_dev->clk = of_clk_get(pdev->dev.of_node, 0);
if (IS_ERR(sunxi_dev->clk))
dev_err(&pdev->dev, "fail to get clk\n");
gsunxi_dev = sunxi_dev;
platform_set_drvdata(pdev, sunxi_dev);
INIT_LIST_HEAD(&sunxi_dev->trs);
spin_lock_init(&sunxi_dev->slock);
mutex_init(&sunxi_dev->mlock);
mutex_init(&id_lock);
idr_init(&sunxi_dev->idr);
sunxi_dev->dev = &pdev->dev;
sunxi_dev->handles = RB_ROOT;
dev_init = true;
ret = sysfs_create_group(&tr_dev->kobj, &tr_attribute_group);
if (ret < 0)
pr_err("sysfs_create_file fail!\n");
/* init hw */
de_tr_set_base((uintptr_t)sunxi_dev->base);
pr_info("exit %s\n", __func__);
return 0;
iomap_err:
iounmap(sunxi_dev->base);
io_err:
kfree(sunxi_dev);
return ret;
}
static int tr_remove(struct platform_device *pdev)
{
struct sunxi_trdev *sunxi_dev = NULL;
pr_info("tr_remove enter\n");
sunxi_dev = (struct sunxi_trdev *)platform_get_drvdata(pdev);
if (sunxi_dev && (sunxi_dev->count != 0)) {
struct sunxi_transform *tr = NULL, *tr_tmp = NULL;
unsigned long flags;
pr_warn("%s(), there are still %d t'users, force release them\n",
__func__, sunxi_dev->count);
spin_lock_irqsave(&sunxi_dev->slock, flags);
list_for_each_entry_safe(tr, tr_tmp, &sunxi_dev->trs, list) {
list_del(&tr->list);
sunxi_dev->count--;
kfree((void *)tr);
}
spin_unlock_irqrestore(&sunxi_dev->slock, flags);
if (sunxi_dev->clk)
clk_disable(sunxi_dev->clk);
}
if (sunxi_dev) {
dev_init = false;
free_irq(sunxi_dev->irq, sunxi_dev);
clk_put(sunxi_dev->clk);
#if defined(CONFIG_OF)
iounmap(sunxi_dev->base);
#endif
kfree(sunxi_dev);
platform_set_drvdata(pdev, NULL);
sysfs_remove_group(&tr_dev->kobj, &tr_attribute_group);
return 0;
}
return -1;
}
static int tr_suspend(struct platform_device *pdev, pm_message_t state)
{
pr_info("enter %s\n", __func__);
pr_info("exit %s\n", __func__);
return 0;
}
static int tr_resume(struct platform_device *pdev)
{
pr_info("%s\n", __func__);
if (gsunxi_dev->count != 0)
de_tr_init();
pr_info("exit %s\n", __func__);
return 0;
}
static void tr_shutdown(struct platform_device *pdev)
{
}
static long tr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
unsigned long karg[4];
unsigned long ubuffer[4] = {0};
long ret = -1;
if (copy_from_user((void *)karg, (void __user *)arg,
4*sizeof(unsigned long))) {
pr_warn("copy_from_user fail\n");
return -EFAULT;
}
ubuffer[0] = *(unsigned long *)karg;
ubuffer[1] = (*(unsigned long *)(karg+1));
ubuffer[2] = (*(unsigned long *)(karg+2));
ubuffer[3] = (*(unsigned long *)(karg+3));
switch (cmd) {
case TR_REQUEST:
{
/* request a chan */
unsigned int id;
id = sunxi_tr_request();
TR_INFO_MSG("TR_REQUEST id:%d\n", id);
if (put_user(id, (unsigned int __user *)ubuffer[0])) {
pr_err("%s: put_user fail\n", __func__);
return -EFAULT;
}
if (id == 0)
ret = -EFAULT;
else
ret = 0;
break;
}
case TR_COMMIT:
{
tr_info info;
if (copy_from_user(&info, (void __user *)ubuffer[1],
sizeof(tr_info))) {
pr_warn("%s, copy_from_user fail\n", __func__);
return -EFAULT;
}
ret = sunxi_tr_commit(ubuffer[0], &info);
break;
}
case TR_RELEASE:
{
/* release a chan */
ret = sunxi_tr_release(ubuffer[0]);
break;
}
case TR_QUERY:
{
/* query status */
ret = sunxi_tr_query(ubuffer[0]);
break;
}
case TR_SET_TIMEOUT:
{
/* set timeout */
ret = sunxi_tr_set_timeout(ubuffer[0], ubuffer[1]);
break;
}
default:
break;
}
return ret;
}
#ifdef CONFIG_COMPAT
static long tr_compat_ioctl32(struct file *file, unsigned int cmd,
unsigned long arg)
{
compat_uptr_t karg[4];
unsigned long __user *ubuffer;
int ret = 0;
if (copy_from_user((void *)karg, (void __user *)arg,
4 * sizeof(compat_uptr_t))) {
pr_err("copy_from_user fail\n");
return -EFAULT;
}
ubuffer = compat_alloc_user_space(4 * sizeof(unsigned long));
if (!access_ok(VERIFY_WRITE, ubuffer, 4 * sizeof(unsigned long)))
return -EFAULT;
if (put_user(karg[0], &ubuffer[0])
|| put_user(karg[1], &ubuffer[1])
|| put_user(karg[2], &ubuffer[2])
|| put_user(karg[3], &ubuffer[3])) {
pr_err("put_user fail\n");
return -EFAULT;
}
switch (cmd) {
case TR_REQUEST:
{
/* request a chan */
compat_uint_t id = 0;
id = sunxi_tr_request();
if (put_user(id, (compat_uint_t __user *)ubuffer[0])) {
pr_err("%s, put tr user failed.", __func__);
ret = -EFAULT;
} else {
ret = 0;
}
break;
}
case TR_COMMIT:
{
tr_info info;
compat_uint_t id = karg[0];
ret = copy_from_user(&info, (void __user *)ubuffer[1],
sizeof(tr_info));
if (ret) {
pr_warn("%s, tr copy_from_user fail\n", __func__);
return -EFAULT;
}
ret = sunxi_tr_commit(id, &info);
break;
}
case TR_RELEASE:
{
/* release a chan */
compat_uint_t id = karg[0];
ret = sunxi_tr_release(id);
break;
}
case TR_QUERY:
{
/* query status */
compat_uint_t id = karg[0];
ret = sunxi_tr_query((unsigned long)id);
break;
}
case TR_SET_TIMEOUT:
{
/* set timeout */
compat_uint_t id = karg[0];
compat_uptr_t timeout = karg[1];
ret = sunxi_tr_set_timeout(id, (unsigned long)timeout);
break;
}
default:
break;
}
return ret;
}
#endif
static const struct file_operations tr_fops = {
.owner = THIS_MODULE,
.open = tr_open,
.release = tr_release,
.write = tr_write,
.read = tr_read,
.unlocked_ioctl = tr_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = tr_compat_ioctl32,
#endif
};
#if !defined(CONFIG_OF)
static struct platform_device tr_device = {
.name = "transform",
.id = -1,
.num_resources = ARRAY_SIZE(tr_resource),
.resource = tr_resource,
.dev = {
},
};
#else
static const struct of_device_id sunxi_tr_match[] = {
{ .compatible = "allwinner,sun50i-tr", },
{ .compatible = "allwinner,sun8iw11-tr", },
{},
};
#endif
static struct platform_driver tr_driver = {
.probe = tr_probe,
.remove = tr_remove,
.suspend = tr_suspend,
.resume = tr_resume,
.shutdown = tr_shutdown,
.driver = {
.name = "transform",
.owner = THIS_MODULE,
.of_match_table = sunxi_tr_match,
},
};
static int __init tr_module_init(void)
{
int ret = 0, err;
alloc_chrdev_region(&devid, 0, 1, "transform");
tr_cdev = cdev_alloc();
cdev_init(tr_cdev, &tr_fops);
tr_cdev->owner = THIS_MODULE;
err = cdev_add(tr_cdev, devid, 1);
if (err) {
pr_warn("cdev_add fail\n");
return -1;
}
tr_class = class_create(THIS_MODULE, "transform");
if (IS_ERR(tr_class)) {
pr_warn("class_create fail\n");
return -1;
}
tr_dev = device_create(tr_class, NULL, devid, NULL, "transform");
#if !defined(CONFIG_OF)
ret = platform_device_register(&tr_device);
#endif
if (ret == 0)
ret = platform_driver_register(&tr_driver);
return ret;
}
static void __exit tr_module_exit(void)
{
platform_driver_unregister(&tr_driver);
#if !defined(CONFIG_OF)
platform_device_unregister(&tr_device);
#endif
device_destroy(tr_class, devid);
class_destroy(tr_class);
cdev_del(tr_cdev);
}
module_init(tr_module_init);
module_exit(tr_module_exit);
MODULE_AUTHOR("tyle");
MODULE_DESCRIPTION("transform driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:transform");