/* * Copyright (c) 2007-2018 Allwinnertech Co., Ltd. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * 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. */ #include "di_client.h" #include "di_dev.h" #include "di_utils.h" #include "di_debug.h" #include "sunxi_di.h" #include #include #define DI_TNR_BUF_ALIGN_LEN 16 #define DI_MD_BUF_ALIGN_LEN 32 extern int di_drv_get_version(struct di_version *version); extern int di_drv_client_inc(struct di_client *c); extern int di_drv_client_dec(struct di_client *c); extern int di_drv_is_valid_client(struct di_client *c); extern int di_drv_process_fb(struct di_client *c); static int di_client_alloc_mbuf(struct di_mapped_buf **mbuf, u32 size) { struct di_mapped_buf *p = *mbuf; if (p != NULL) { u32 size_alloced = PAGE_ALIGN(size); if (p->size_alloced != size_alloced) { di_dma_buf_unmap_free(p); p = NULL; *mbuf = NULL; } else { p->size_request = size; } } if (p == NULL) { p = di_dma_buf_alloc_map(size); if (p == NULL) return -ENOMEM; } memset((void *)p->vir_addr, 0, p->size_alloced); *mbuf = p; return 0; } static int di_client_setup_md_buf(struct di_client *c) { int ret = 0; u32 w_bit = c->video_size.width * 2; u32 w_stride = ALIGN(w_bit, DI_MD_BUF_ALIGN_LEN * 8) / 8; u32 h = c->video_size.height; u32 size = h * w_stride; ret = di_client_alloc_mbuf(&(c->md_buf.mbuf[0]), size); if (ret) return ret; ret = di_client_alloc_mbuf(&(c->md_buf.mbuf[1]), size); if (ret) { if (c->md_buf.mbuf[0]) { di_dma_buf_unmap_free(c->md_buf.mbuf[0]); c->md_buf.mbuf[0] = NULL; } return ret; } c->md_buf.dir = 0; c->md_buf.w_bit = w_bit; c->md_buf.w_stride = w_stride; c->md_buf.h = h; return 0; } int di_client_reset(struct di_client *c, void *data) { u32 i; DI_DEBUG("%s: %s\n", c->name, __func__); c->para_checked = false; c->unreset = false; c->proc_fb_seqno = 0; c->apply_fixed_para = true; atomic_set(&c->wait_con, DI_PROC_STATE_IDLE); c->in_fb0 = c->in_fb1 = c->in_fb2 = c->in_fb0_nf = c->in_fb1_nf = c->in_fb2_nf = c->out_dit_fb0 = c->out_dit_fb1 = c->out_tnr_fb0 = c->dma_di = c->dma_p = c->dma_c = c->di_w0 = c->di_w1 = c->tnr_w = NULL; for (i = 0; i < ARRAY_SIZE(c->fb_pool); i++) { if (c->fb_pool[i].dma_item) { di_dma_buf_self_unmap(c->fb_pool[i].dma_item); c->fb_pool[i].dma_item = NULL; } } for (i = 0; i < ARRAY_SIZE(c->md_buf.mbuf); i++) { if (c->md_buf.mbuf[i]) { di_dma_buf_unmap_free(c->md_buf.mbuf[i]); c->md_buf.mbuf[i] = NULL; } } di_dev_reset_cdata((void *)c->dev_cdata); return 0; } /*checking and correcting di_client para before running proccess_fb*/ int di_client_check_para(struct di_client *c, void *data) { DI_DEBUG("%s: %s\n", c->name, __func__); if (c->para_checked == true) return 0; if (c->unreset == true) { DI_ERR("%s: do reset before setting, then check\n", c->name); return -EINVAL; } c->proc_fb_seqno = 0; c->apply_fixed_para = true; if ((c->video_size.height == 0) || ((c->video_size.height & 0x1) != 0) || (c->video_size.width == 0) || ((c->video_size.width & 0x1) != 0)) { DI_ERR("%s: invalid size W(%d)xH(%d)\n", c->name, c->video_size.height, c->video_size.width); goto err_out; } if ((c->dit_mode.intp_mode == DI_DIT_INTP_MODE_MOTION) && (c->dit_mode.out_frame_mode == DI_DIT_OUT_2FRAME) /*&& (c->tnr_mode.mode != DI_TNR_MODE_INVALID) && (c->fmd_en.en != 0)*/) { DI_DEBUG("%s: this is 60hz mode\n", c->name); c->mode = DI_MODE_60HZ; c->md_en = true; if (c->tnr_mode.mode) c->tnr_en = true; else c->tnr_en = false; c->vof_buf_en = true; c->dma_di = c->in_fb0 = &c->fb_pool[0]; c->dma_di_nf = c->in_fb0_nf = &c->fb_pool[1]; c->dma_p = c->in_fb1 = &c->fb_pool[2]; c->dma_p_nf = c->in_fb1_nf = &c->fb_pool[3]; c->dma_c = c->in_fb2 = &c->fb_pool[4]; c->dma_c_nf = c->in_fb2_nf = &c->fb_pool[5]; c->di_w0 = c->out_dit_fb0 = &c->fb_pool[6]; c->di_w1 = c->out_dit_fb1 = &c->fb_pool[7]; c->tnr_w = c->out_tnr_fb0 = &c->fb_pool[8]; } else if ((c->dit_mode.intp_mode == DI_DIT_INTP_MODE_MOTION) && (c->dit_mode.out_frame_mode == DI_DIT_OUT_1FRAME) /*&& (c->tnr_mode.mode != DI_TNR_MODE_INVALID) && (c->fmd_en.en != 0)*/) { DI_DEBUG("%s: this is 30hz mode\n", c->name); c->mode = DI_MODE_30HZ; c->md_en = true; if (c->tnr_mode.mode) c->tnr_en = true; else c->tnr_en = false; c->vof_buf_en = false; c->dma_p = c->in_fb0 = &c->fb_pool[0]; c->dma_p_nf = c->in_fb0_nf = &c->fb_pool[1]; c->dma_c = c->in_fb1 = &c->fb_pool[2]; c->dma_c_nf = c->in_fb1_nf = &c->fb_pool[3]; c->di_w1 = c->out_dit_fb0 = &c->fb_pool[4]; c->tnr_w = c->out_tnr_fb0 = &c->fb_pool[5]; } else if ((c->dit_mode.intp_mode == DI_DIT_INTP_MODE_BOB) && (c->dit_mode.out_frame_mode == DI_DIT_OUT_1FRAME) && (c->tnr_mode.mode == DI_TNR_MODE_INVALID) && (c->fmd_en.en == 0)) { DI_DEBUG("%s: this is bob mode\n", c->name); c->mode = DI_MODE_BOB; c->md_en = false; c->tnr_en = false; c->vof_buf_en = false; c->dma_p = c->in_fb0 = &c->fb_pool[0]; c->di_w1 = c->out_dit_fb0 = &c->fb_pool[1]; } else if ((c->dit_mode.intp_mode == DI_DIT_INTP_MODE_WEAVE) && (c->dit_mode.out_frame_mode == DI_DIT_OUT_1FRAME) && (c->tnr_mode.mode == DI_TNR_MODE_INVALID) && (c->fmd_en.en == 0)) { DI_DEBUG("%s: this is weave mode\n", c->name); c->mode = DI_MODE_WEAVE; c->md_en = false; c->tnr_en = false; c->vof_buf_en = false; c->dma_p = c->in_fb0 = &c->fb_pool[0]; c->di_w1 = c->out_dit_fb0 = &c->fb_pool[1]; } else if ((c->dit_mode.intp_mode == DI_DIT_INTP_MODE_INVALID) && (c->dit_mode.out_frame_mode == DI_DIT_OUT_0FRAME) && (c->tnr_mode.mode != DI_TNR_MODE_INVALID) && (c->fmd_en.en == 0)) { DI_DEBUG("%s: this is only-tnr mode\n", c->name); c->mode = DI_MODE_TNR; c->md_en = true; c->tnr_en = true; c->vof_buf_en = false; c->dma_p = c->in_fb0 = &c->fb_pool[0]; c->dma_c = c->in_fb1 = &c->fb_pool[1]; c->tnr_w = c->out_tnr_fb0 = &c->fb_pool[2]; } else { DI_ERR("%s: wrong paras:\n " "dit_mode:%d,%d; tnr:%d,%d; fmd_en:%d\n", c->name, c->dit_mode.intp_mode, c->dit_mode.out_frame_mode, c->tnr_mode.mode, c->tnr_mode.level, c->fmd_en.en); goto err_out; } if (c->md_en) if (di_client_setup_md_buf(c)) goto err_out; atomic_set(&c->wait_con, DI_PROC_STATE_IDLE); di_dev_reset_cdata((void *)c->dev_cdata); c->para_checked = true; c->unreset = true; return 0; err_out: c->para_checked = false; return -EINVAL; } static bool di_client_check_fb_arg(struct di_client *c, struct di_process_fb_arg *fb_arg) { DI_DEBUG("%s pulldown[%s] topFieldFirst[%s] baseFiled[%s]\n", fb_arg->is_interlace ? "Interlace" : "P", fb_arg->is_pulldown ? "Y" : "N", fb_arg->top_field_first ? "Y" : "N", fb_arg->base_field ? "TOP" : "BOTTOM"); /* TODO: add more check ? */ return true; } static int di_client_get_fb(struct di_client *c, struct di_dma_fb *dma_fb, struct di_fb *fb, enum dma_data_direction dir) { DI_DEBUG("%s: type:%s buf_addr[0x%llx,0x%llx,0x%llx]," "buf_stride[%d,%d],fmt=%s,fd=%d,size[%d,%d]\n", c->name, fb->field_type ? (fb->field_type == DI_FIELD_TYPE_TOP_FIELD ? "top field" : "bottom field") : "combine field", fb->buf.y_addr, fb->buf.cb_addr, fb->buf.cr_addr, fb->buf.ystride, fb->buf.cstride, di_format_to_string(fb->format), fb->dma_buf_fd, fb->size.width, fb->size.height); if (dma_fb->dma_item != NULL) { di_dma_buf_self_unmap(dma_fb->dma_item); dma_fb->dma_item = NULL; } if (fb->dma_buf_fd >= 0) { dma_fb->dma_item = di_dma_buf_self_map(fb->dma_buf_fd, dir); if (dma_fb->dma_item == NULL) { DI_ERR("%s: %s,%d\n", c->name, __func__, __LINE__); return -EINVAL; } fb->buf.y_addr += (u64)(dma_fb->dma_item->dma_addr); if (fb->buf.cb_addr) fb->buf.cb_addr += (u64)(dma_fb->dma_item->dma_addr); if (fb->buf.cr_addr) fb->buf.cr_addr += (u64)(dma_fb->dma_item->dma_addr); DI_DEBUG("%s:dma_addr=0x%llx,yuv[0x%llx,0x%llx,0x%llx]\n", c->name, (u64)(dma_fb->dma_item->dma_addr), fb->buf.y_addr, fb->buf.cb_addr, fb->buf.cr_addr); } else { DI_DEBUG("%s: On phy_addr_buf method\n", c->name); } dma_fb->fb = fb; return 0; } static int di_client_get_fbs(struct di_client *c) { struct di_process_fb_arg *fb_arg = &c->fb_arg; if ((c->in_fb0 != NULL) && di_client_get_fb(c, c->in_fb0, &fb_arg->in_fb0, DMA_TO_DEVICE)) return -EINVAL; if ((c->in_fb0_nf != NULL) && c->in_fb0->fb && c->in_fb0->fb->field_type && di_client_get_fb(c, c->in_fb0_nf, &fb_arg->in_fb0_nf, DMA_TO_DEVICE)) return -EINVAL; if ((c->in_fb1 != NULL) && di_client_get_fb(c, c->in_fb1, &fb_arg->in_fb1, DMA_TO_DEVICE)) return -EINVAL; if ((c->in_fb1_nf != NULL) && c->in_fb1->fb && c->in_fb1->fb->field_type && di_client_get_fb(c, c->in_fb1_nf, &fb_arg->in_fb1_nf, DMA_TO_DEVICE)) return -EINVAL; if ((c->in_fb2 != NULL) && di_client_get_fb(c, c->in_fb2, &fb_arg->in_fb2, DMA_TO_DEVICE)) return -EINVAL; if ((c->in_fb2_nf != NULL) && c->in_fb2->fb && c->in_fb2->fb->field_type && di_client_get_fb(c, c->in_fb2_nf, &fb_arg->in_fb2_nf, DMA_TO_DEVICE)) return -EINVAL; if ((c->out_dit_fb0 != NULL) && di_client_get_fb(c, c->out_dit_fb0, &fb_arg->out_dit_fb0, DMA_FROM_DEVICE)) return -EINVAL; if ((c->out_dit_fb1 != NULL) && di_client_get_fb(c, c->out_dit_fb1, &fb_arg->out_dit_fb1, DMA_FROM_DEVICE)) return -EINVAL; if ((c->out_tnr_fb0 != NULL) && (c->tnr_mode.mode > 0)) { if (di_client_get_fb(c, c->out_tnr_fb0, &fb_arg->out_tnr_fb0, DMA_FROM_DEVICE)) return -EINVAL; /*NOTE: when use tnr, the output format must be planner*/ if (di_format_get_planar_num(c->out_tnr_fb0->fb->format) != 3) { DI_ERR("%s: invalid %s(%d) for out_tnr_fb0\n", c->name, di_format_to_string(c->out_tnr_fb0->fb->format), c->out_tnr_fb0->fb->format); return -EINVAL; } /*NOTE: when use tnr, the output format must be planner*/ if ((c->out_dit_fb0 != NULL) && (di_format_get_planar_num( c->out_dit_fb0->fb->format) != 3)) { DI_ERR("%s: invalid %s(%d) for out_dit_fb0\n", c->name, di_format_to_string(c->out_dit_fb0->fb->format), c->out_dit_fb0->fb->format); return -EINVAL; } /*NOTE: when use tnr, the output format must be planner*/ if ((c->out_dit_fb1 != NULL) && (di_format_get_planar_num( c->out_dit_fb1->fb->format) != 3)) { DI_ERR("%s: invalid %s(%d) for out_dit_fb1\n", c->name, di_format_to_string(c->out_dit_fb1->fb->format), c->out_dit_fb1->fb->format); return -EINVAL; } } return 0; } static int di_client_put_fbs(struct di_client *c) { u32 i; for (i = 0; i < ARRAY_SIZE(c->fb_pool); i++) { struct di_dma_fb *fb = &(c->fb_pool[i]); fb->fb = NULL; if (fb->dma_item != NULL) { di_dma_buf_self_unmap(fb->dma_item); fb->dma_item = NULL; } } return 0; } int di_client_process_fb(struct di_client *c, struct di_process_fb_arg *fb_arg) { int ret = 0; ktime_t time; unsigned long long t0 = 0, t1 = 0, t2 = 0, t3 = 0; time = ktime_get(); t0 = ktime_to_us(time); if (c->para_checked == false) { DI_ERR("%s: para unchecked\n", c->name); return -EINVAL; } if (!di_client_check_fb_arg(c, fb_arg)) { DI_ERR("%s: check_fb_arg fail\n", c->name); return -EINVAL; } memcpy((void *)&c->fb_arg, fb_arg, sizeof(c->fb_arg)); ret = di_client_get_fbs(c); time = ktime_get(); t1 = ktime_to_us(time); if (!ret) ret = di_drv_process_fb(c); time = ktime_get(); t2 = ktime_to_us(time); di_client_put_fbs(c); time = ktime_get(); t3 = ktime_to_us(time); DI_TEST("total:%lluus t0~t1:%lluus t1~t2:%lluus t2~t3:%lluus\n", (t3 - t0), (t1 - t0), (t2 - t1), (t3 - t2)); return ret; } int di_client_set_video_size(struct di_client *c, struct di_size *size) { DI_DEBUG("%s: video_size[%d x %d]\n", c->name, size->width, size->height); c->video_size.width = size->width; c->video_size.height = size->height; return 0; } int di_client_set_video_crop(struct di_client *c, struct di_rect *rect) { DI_DEBUG("%s: video_crop:(%u, %u) (%u, %u)\n", c->name, rect->left, rect->top, rect->right, rect->bottom); c->dit_out_crop.left = rect->left; c->dit_out_crop.top = rect->top; c->dit_out_crop.right = rect->right; c->dit_out_crop.bottom = rect->bottom; memcpy((void *)&c->md_out_crop, (void *)&c->dit_out_crop, sizeof(c->md_out_crop)); memcpy((void *)&c->fmd_out_crop, (void *)&c->dit_out_crop, sizeof(c->fmd_out_crop)); memcpy((void *)&c->tnr_out_crop, (void *)&c->dit_out_crop, sizeof(c->tnr_out_crop)); memcpy((void *)&c->dit_demo_crop, (void *)&c->dit_out_crop, sizeof(c->dit_demo_crop)); memcpy((void *)&c->tnr_demo_crop, (void *)&c->dit_out_crop, sizeof(c->tnr_demo_crop)); return 0; } int di_client_set_demo_crop(struct di_client *c, struct di_demo_crop_arg *demo_arg) { DI_DEBUG("%s: demo crop: dit:(%u, %u) (%u, %u) " "tnr: (%u, %u) (%u, %u)\n", c->name, demo_arg->dit_demo.left, demo_arg->dit_demo.top, demo_arg->dit_demo.right, demo_arg->dit_demo.bottom, demo_arg->tnr_demo.left, demo_arg->tnr_demo.top, demo_arg->tnr_demo.right, demo_arg->tnr_demo.bottom); demo_arg->dit_demo.left = demo_arg->dit_demo.left - (demo_arg->dit_demo.left % 4); demo_arg->dit_demo.top = demo_arg->dit_demo.top - (demo_arg->dit_demo.top % 4); demo_arg->dit_demo.right = demo_arg->dit_demo.right - (demo_arg->dit_demo.right % 4); demo_arg->dit_demo.bottom = demo_arg->dit_demo.bottom - (demo_arg->dit_demo.bottom % 4); demo_arg->tnr_demo.left = demo_arg->tnr_demo.left - (demo_arg->tnr_demo.left % 4); demo_arg->tnr_demo.top = demo_arg->tnr_demo.top - (demo_arg->tnr_demo.top % 4); demo_arg->tnr_demo.right = demo_arg->tnr_demo.right - (demo_arg->tnr_demo.right % 4); demo_arg->tnr_demo.bottom = demo_arg->tnr_demo.bottom - (demo_arg->tnr_demo.bottom % 4); memcpy((void *)&c->dit_demo_crop, (void *)&demo_arg->dit_demo, sizeof(c->dit_demo_crop)); memcpy((void *)&c->tnr_demo_crop, (void *)&demo_arg->tnr_demo, sizeof(c->tnr_demo_crop)); return 0; } int di_client_set_dit_mode(struct di_client *c, struct di_dit_mode *mode) { DI_DEBUG("%s: dit_mode: intp_mode=%d, out_frame_mode=%d\n", c->name, mode->intp_mode, mode->out_frame_mode); c->dit_mode.intp_mode = mode->intp_mode; c->dit_mode.out_frame_mode = mode->out_frame_mode; return 0; } int di_client_set_tnr_mode(struct di_client *c, struct di_tnr_mode *mode) { DI_DEBUG("%s: tnr_mode: mode=%d, level=%d\n", c->name, mode->mode, mode->level); c->tnr_mode.mode = mode->mode; c->tnr_mode.level = mode->level; return 0; } int di_client_set_fmd_enable(struct di_client *c, struct di_fmd_enable *en) { DI_DEBUG("%s: fmd_en: en=%d\n", c->name, en->en); c->fmd_en.en = en->en; return 0; } int di_client_get_version(struct di_client *c, struct di_version *version) { return di_drv_get_version(version); } int di_client_set_timeout(struct di_client *c, struct di_timeout_ns *timeout) { DI_DEBUG("%s:wait4start=%llu,wait4finish=%llu\n", c->name, timeout->wait4start, timeout->wait4finish); if (timeout->wait4start > 0) c->timeout.wait4start = timeout->wait4start; if (timeout->wait4finish > 0) c->timeout.wait4finish = timeout->wait4finish; return 0; } void *di_client_create(const char *name) { struct di_client *client; if (!name) { DI_ERR("%s: Name cannot be null\n", __func__); return NULL; } client = kzalloc(sizeof(*client) + di_dev_get_cdata_size(), GFP_KERNEL); if (client == NULL) { DI_ERR("kzalloc for client%s fail\n", name); return NULL; } client->name = kstrdup(name, GFP_KERNEL); if (client->name == NULL) { kfree(client); DI_ERR("kstrdup for name(%s) fail\n", name); return NULL; } INIT_LIST_HEAD(&client->node); client->timeout.wait4start = 3 * 1000000000UL; client->timeout.wait4finish = 3 * 1000000000UL; init_waitqueue_head(&client->wait); atomic_set(&client->wait_con, DI_PROC_STATE_IDLE); client->apply_fixed_para = true; client->dev_cdata = (uintptr_t)( (char *)&client->dev_cdata + sizeof(client->dev_cdata)); if (di_drv_client_inc(client)) { kfree(client); return NULL; } return client; } EXPORT_SYMBOL_GPL(di_client_create); void di_client_destroy(void *client) { struct di_client *c = (struct di_client *)client; u32 i; if (!di_drv_is_valid_client(c)) { DI_ERR("%s, invalid client(%p)\n", __func__, c); return; } di_drv_client_dec(c); for (i = 0; i < ARRAY_SIZE(c->fb_pool); i++) if (c->fb_pool[i].dma_item) di_dma_buf_self_unmap(c->fb_pool[i].dma_item); for (i = 0; i < ARRAY_SIZE(c->md_buf.mbuf); i++) if (c->md_buf.mbuf[i]) di_dma_buf_unmap_free(c->md_buf.mbuf[i]); di_dev_reset_cdata((void *)c->dev_cdata); kfree(c->name); kfree(c); } EXPORT_SYMBOL_GPL(di_client_destroy); int di_client_mem_request(struct di_client *c, void *data) { struct di_mem_arg *mem = (struct di_mem_arg *)data; mem->handle = di_mem_request(mem->size, &mem->phys_addr); if (mem->handle < 0) return -1; return 0; } EXPORT_SYMBOL_GPL(di_client_mem_request); int di_client_mem_release(struct di_client *c, void *data) { struct di_mem_arg *mem = (struct di_mem_arg *)data; return mem->handle = di_mem_release(mem->handle); } EXPORT_SYMBOL_GPL(di_client_mem_release);