/* * Copyright 2005-2015 Freescale Semiconductor, Inc. All Rights Reserved. * Copyright 2019 NXP */ /* * The code contained herein is licensed under the GNU General Public * License. You may obtain a copy of the GNU General Public License * Version 2 or later at the following locations: * * http://www.opensource.org/licenses/gpl-license.html * http://www.gnu.org/copyleft/gpl.html */ /*! * @file ipu_device.c * * @brief This file contains the IPUv3 driver device interface and fops functions. * * @ingroup IPU */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipu_param_mem.h" #include "ipu_regs.h" #include "vdoa.h" #define CHECK_RETCODE(cont, str, err, label, ret) \ do { \ if (cont) { \ dev_err(t->dev, "ERR:[0x%p]-no:0x%x "#str" ret:%d," \ "line:%d\n", t, t->task_no, ret, __LINE__);\ if (ret != -EACCES) { \ t->state = err; \ goto label; \ } \ } \ } while (0) #define CHECK_RETCODE_CONT(cont, str, err, ret) \ do { \ if (cont) { \ dev_err(t->dev, "ERR:[0x%p]-no:0x%x"#str" ret:%d," \ "line:%d\n", t, t->task_no, ret, __LINE__);\ if (ret != -EACCES) { \ if (t->state == STATE_OK) \ t->state = err; \ } \ } \ } while (0) #undef DBG_IPU_PERF #ifdef DBG_IPU_PERF #define CHECK_PERF(ts) \ do { \ getnstimeofday(ts); \ } while (0) #define DECLARE_PERF_VAR \ struct timespec ts_queue; \ struct timespec ts_dotask; \ struct timespec ts_waitirq; \ struct timespec ts_sche; \ struct timespec ts_rel; \ struct timespec ts_frame #define PRINT_TASK_STATISTICS \ do { \ ts_queue = timespec_sub(tsk->ts_dotask, tsk->ts_queue); \ ts_dotask = timespec_sub(tsk->ts_waitirq, tsk->ts_dotask); \ ts_waitirq = timespec_sub(tsk->ts_inirq, tsk->ts_waitirq); \ ts_sche = timespec_sub(tsk->ts_wakeup, tsk->ts_inirq); \ ts_rel = timespec_sub(tsk->ts_rel, tsk->ts_wakeup); \ ts_frame = timespec_sub(tsk->ts_rel, tsk->ts_queue); \ dev_dbg(tsk->dev, "[0x%p] no-0x%x, ts_q:%ldus, ts_do:%ldus," \ "ts_waitirq:%ldus,ts_sche:%ldus, ts_rel:%ldus," \ "ts_frame: %ldus\n", tsk, tsk->task_no, \ ts_queue.tv_nsec / NSEC_PER_USEC + ts_queue.tv_sec * USEC_PER_SEC,\ ts_dotask.tv_nsec / NSEC_PER_USEC + ts_dotask.tv_sec * USEC_PER_SEC,\ ts_waitirq.tv_nsec / NSEC_PER_USEC + ts_waitirq.tv_sec * USEC_PER_SEC,\ ts_sche.tv_nsec / NSEC_PER_USEC + ts_sche.tv_sec * USEC_PER_SEC,\ ts_rel.tv_nsec / NSEC_PER_USEC + ts_rel.tv_sec * USEC_PER_SEC,\ ts_frame.tv_nsec / NSEC_PER_USEC + ts_frame.tv_sec * USEC_PER_SEC); \ if ((ts_frame.tv_nsec/NSEC_PER_USEC + ts_frame.tv_sec*USEC_PER_SEC) > \ 80000) \ dev_dbg(tsk->dev, "ts_frame larger than 80ms [0x%p] no-0x%x.\n"\ , tsk, tsk->task_no); \ } while (0) #else #define CHECK_PERF(ts) #define DECLARE_PERF_VAR #define PRINT_TASK_STATISTICS #endif #define IPU_PP_CH_VF (IPU_TASK_ID_VF - 1) #define IPU_PP_CH_PP (IPU_TASK_ID_PP - 1) #define MAX_PP_CH (IPU_TASK_ID_MAX - 1) #define VDOA_DEF_TIMEOUT_MS (HZ/2) /* Strucutures and variables for exporting MXC IPU as device*/ typedef enum { STATE_OK = 0, STATE_QUEUE, STATE_IN_PROGRESS, STATE_ERR, STATE_TIMEOUT, STATE_RES_TIMEOUT, STATE_NO_IPU, STATE_NO_IRQ, STATE_IPU_BUSY, STATE_IRQ_FAIL, STATE_IRQ_TIMEOUT, STATE_ENABLE_CHAN_FAIL, STATE_DISABLE_CHAN_FAIL, STATE_SEL_BUF_FAIL, STATE_INIT_CHAN_FAIL, STATE_LINK_CHAN_FAIL, STATE_UNLINK_CHAN_FAIL, STATE_INIT_CHAN_BUF_FAIL, STATE_INIT_CHAN_BAND_FAIL, STATE_SYS_NO_MEM, STATE_VDOA_IRQ_TIMEOUT, STATE_VDOA_IRQ_FAIL, STATE_VDOA_TASK_FAIL, } ipu_state_t; enum { INPUT_CHAN_VDI_P = 1, INPUT_CHAN, INPUT_CHAN_VDI_N, }; struct ipu_state_msg { int state; char *msg; } state_msg[] = { {STATE_OK, "ok"}, {STATE_QUEUE, "split queue"}, {STATE_IN_PROGRESS, "split in progress"}, {STATE_ERR, "error"}, {STATE_TIMEOUT, "split task timeout"}, {STATE_RES_TIMEOUT, "wait resource timeout"}, {STATE_NO_IPU, "no ipu found"}, {STATE_NO_IRQ, "no irq found for task"}, {STATE_IPU_BUSY, "ipu busy"}, {STATE_IRQ_FAIL, "request irq failed"}, {STATE_IRQ_TIMEOUT, "wait for irq timeout"}, {STATE_ENABLE_CHAN_FAIL, "ipu enable channel fail"}, {STATE_DISABLE_CHAN_FAIL, "ipu disable channel fail"}, {STATE_SEL_BUF_FAIL, "ipu select buf fail"}, {STATE_INIT_CHAN_FAIL, "ipu init channel fail"}, {STATE_LINK_CHAN_FAIL, "ipu link channel fail"}, {STATE_UNLINK_CHAN_FAIL, "ipu unlink channel fail"}, {STATE_INIT_CHAN_BUF_FAIL, "ipu init channel buffer fail"}, {STATE_INIT_CHAN_BAND_FAIL, "ipu init channel band mode fail"}, {STATE_SYS_NO_MEM, "sys no mem: -ENOMEM"}, {STATE_VDOA_IRQ_TIMEOUT, "wait for vdoa irq timeout"}, {STATE_VDOA_IRQ_FAIL, "vdoa irq fail"}, {STATE_VDOA_TASK_FAIL, "vdoa task fail"}, }; struct stripe_setting { u32 iw; u32 ih; u32 ow; u32 oh; u32 outh_resize_ratio; u32 outv_resize_ratio; u32 i_left_pos; u32 i_right_pos; u32 i_top_pos; u32 i_bottom_pos; u32 o_left_pos; u32 o_right_pos; u32 o_top_pos; u32 o_bottom_pos; u32 rl_split_line; u32 ud_split_line; }; struct task_set { #define NULL_MODE 0x0 #define IC_MODE 0x1 #define ROT_MODE 0x2 #define VDI_MODE 0x4 #define IPU_PREPROCESS_MODE_MASK (IC_MODE | ROT_MODE | VDI_MODE) /* VDOA_MODE means this task use vdoa, and VDOA has two modes: * BAND MODE and non-BAND MODE. Non-band mode will do transfer data * to memory. BAND mode needs hareware sync with IPU, it is used default * if connected to VDIC. */ #define VDOA_MODE 0x8 #define VDOA_BAND_MODE 0x10 u8 mode; #define IC_VF 0x1 #define IC_PP 0x2 #define ROT_VF 0x4 #define ROT_PP 0x8 #define VDI_VF 0x10 #define VDOA_ONLY 0x20 u8 task; #define NO_SPLIT 0x0 #define RL_SPLIT 0x1 #define UD_SPLIT 0x2 #define LEFT_STRIPE 0x1 #define RIGHT_STRIPE 0x2 #define UP_STRIPE 0x4 #define DOWN_STRIPE 0x8 #define SPLIT_MASK 0xF u8 split_mode; u8 band_lines; ipu_channel_t ic_chan; ipu_channel_t rot_chan; ipu_channel_t vdi_ic_p_chan; ipu_channel_t vdi_ic_n_chan; u32 i_off; u32 i_uoff; u32 i_voff; u32 istride; u32 ov_off; u32 ov_uoff; u32 ov_voff; u32 ovstride; u32 ov_alpha_off; u32 ov_alpha_stride; u32 o_off; u32 o_uoff; u32 o_voff; u32 ostride; u32 r_fmt; u32 r_width; u32 r_height; u32 r_stride; dma_addr_t r_paddr; struct stripe_setting sp_setting; }; struct ipu_split_task { struct ipu_task task; struct ipu_task_entry *parent_task; struct ipu_task_entry *child_task; u32 task_no; }; struct ipu_task_entry { struct ipu_input input; struct ipu_output output; bool overlay_en; struct ipu_overlay overlay; #define DEF_TIMEOUT_MS 1000 #define DEF_DELAY_MS 20 int timeout; int irq; u8 task_id; u8 ipu_id; u8 task_in_list; u8 split_done; struct mutex split_lock; struct mutex vdic_lock; wait_queue_head_t split_waitq; struct list_head node; struct list_head split_list; struct ipu_soc *ipu; struct device *dev; struct task_set set; wait_queue_head_t task_waitq; struct completion irq_comp; struct kref refcount; ipu_state_t state; u32 task_no; atomic_t done; atomic_t res_free; atomic_t res_get; struct ipu_task_entry *parent; char *vditmpbuf[2]; u32 old_save_lines; u32 old_size; bool buf1filled; bool buf0filled; vdoa_handle_t vdoa_handle; struct vdoa_output_mem { void *vaddr; dma_addr_t paddr; int size; } vdoa_dma; #ifdef DBG_IPU_PERF struct timespec ts_queue; struct timespec ts_dotask; struct timespec ts_waitirq; struct timespec ts_inirq; struct timespec ts_wakeup; struct timespec ts_rel; #endif }; struct ipu_channel_tabel { struct mutex lock; u8 used[MXC_IPU_MAX_NUM][MAX_PP_CH]; u8 vdoa_used; }; struct ipu_thread_data { struct ipu_soc *ipu; u32 id; u32 is_vdoa; }; struct ipu_alloc_list { struct list_head list; dma_addr_t phy_addr; void *cpu_addr; u32 size; void *file_index; }; static LIST_HEAD(ipu_alloc_list); static DEFINE_MUTEX(ipu_alloc_lock); static struct ipu_channel_tabel ipu_ch_tbl; static LIST_HEAD(ipu_task_list); static DEFINE_SPINLOCK(ipu_task_list_lock); static DECLARE_WAIT_QUEUE_HEAD(thread_waitq); static DECLARE_WAIT_QUEUE_HEAD(res_waitq); static atomic_t req_cnt; static atomic_t file_index = ATOMIC_INIT(1); static int major; static int max_ipu_no; static int thread_id; static atomic_t frame_no; static struct class *ipu_class; static struct device *ipu_dev; static int debug; module_param(debug, int, 0600); #ifdef DBG_IPU_PERF static struct timespec ts_frame_max; static u32 ts_frame_avg; static atomic_t frame_cnt; #endif static bool deinterlace_3_field(struct ipu_task_entry *t) { return ((t->set.mode & VDI_MODE) && (t->input.deinterlace.motion != HIGH_MOTION)); } static u32 tiled_filed_size(struct ipu_task_entry *t) { u32 field_size; /* note: page_align is required by VPU hw ouput buffer */ field_size = TILED_NV12_FRAME_SIZE(t->input.width, t->input.height/2); return field_size; } static bool only_ic(u8 mode) { mode = mode & IPU_PREPROCESS_MODE_MASK; return ((mode == IC_MODE) || (mode == VDI_MODE)); } static bool only_rot(u8 mode) { mode = mode & IPU_PREPROCESS_MODE_MASK; return (mode == ROT_MODE); } static bool ic_and_rot(u8 mode) { mode = mode & IPU_PREPROCESS_MODE_MASK; return ((mode == (IC_MODE | ROT_MODE)) || (mode == (VDI_MODE | ROT_MODE))); } static bool need_split(struct ipu_task_entry *t) { return ((t->set.split_mode != NO_SPLIT) || (t->task_no & SPLIT_MASK)); } unsigned int fmt_to_bpp(unsigned int pixelformat) { u32 bpp; switch (pixelformat) { case IPU_PIX_FMT_RGB565: /*interleaved 422*/ case IPU_PIX_FMT_YUYV: case IPU_PIX_FMT_UYVY: /*non-interleaved 422*/ case IPU_PIX_FMT_YUV422P: case IPU_PIX_FMT_YVU422P: bpp = 16; break; case IPU_PIX_FMT_BGR24: case IPU_PIX_FMT_RGB24: case IPU_PIX_FMT_YUV444: case IPU_PIX_FMT_YUV444P: bpp = 24; break; case IPU_PIX_FMT_BGR32: case IPU_PIX_FMT_BGRA32: case IPU_PIX_FMT_RGB32: case IPU_PIX_FMT_RGBA32: case IPU_PIX_FMT_ABGR32: bpp = 32; break; /*non-interleaved 420*/ case IPU_PIX_FMT_YUV420P: case IPU_PIX_FMT_YVU420P: case IPU_PIX_FMT_YUV420P2: case IPU_PIX_FMT_NV12: bpp = 12; break; default: bpp = 8; break; } return bpp; } EXPORT_SYMBOL_GPL(fmt_to_bpp); cs_t colorspaceofpixel(int fmt) { switch (fmt) { case IPU_PIX_FMT_RGB565: case IPU_PIX_FMT_RGB666: case IPU_PIX_FMT_BGR24: case IPU_PIX_FMT_RGB24: case IPU_PIX_FMT_BGRA32: case IPU_PIX_FMT_BGR32: case IPU_PIX_FMT_RGBA32: case IPU_PIX_FMT_RGB32: case IPU_PIX_FMT_ABGR32: return RGB_CS; break; case IPU_PIX_FMT_UYVY: case IPU_PIX_FMT_YUYV: case IPU_PIX_FMT_YUV420P2: case IPU_PIX_FMT_YUV420P: case IPU_PIX_FMT_YVU420P: case IPU_PIX_FMT_YVU422P: case IPU_PIX_FMT_YUV422P: case IPU_PIX_FMT_YUV444: case IPU_PIX_FMT_YUV444P: case IPU_PIX_FMT_NV12: case IPU_PIX_FMT_TILED_NV12: case IPU_PIX_FMT_TILED_NV12F: return YUV_CS; break; default: return NULL_CS; } } EXPORT_SYMBOL_GPL(colorspaceofpixel); int need_csc(int ifmt, int ofmt) { cs_t ics, ocs; ics = colorspaceofpixel(ifmt); ocs = colorspaceofpixel(ofmt); if ((ics == NULL_CS) || (ocs == NULL_CS)) return -1; else if (ics != ocs) return 1; return 0; } EXPORT_SYMBOL_GPL(need_csc); static int soc_max_in_width(u32 is_vdoa) { return is_vdoa ? 8192 : 4096; } static int soc_max_vdi_in_width(struct ipu_soc *ipu) { int i; if (!ipu) { for (i = 0; i < max_ipu_no; i++) { ipu = ipu_get_soc(i); if (!IS_ERR_OR_NULL(ipu)) break; } if (i == max_ipu_no) return 720; } return IPU_MAX_VDI_IN_WIDTH(ipu->devtype); } static int soc_max_in_height(void) { return 4096; } static int soc_max_out_width(void) { /* mx51/mx53/mx6q is 1024*/ return 1024; } static int soc_max_out_height(void) { /* mx51/mx53/mx6q is 1024*/ return 1024; } static void dump_task_info(struct ipu_task_entry *t) { if (!debug) return; dev_dbg(t->dev, "[0x%p]input:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tformat = 0x%x\n", (void *)t, t->input.format); dev_dbg(t->dev, "[0x%p]\twidth = %d\n", (void *)t, t->input.width); dev_dbg(t->dev, "[0x%p]\theight = %d\n", (void *)t, t->input.height); dev_dbg(t->dev, "[0x%p]\tcrop.w = %d\n", (void *)t, t->input.crop.w); dev_dbg(t->dev, "[0x%p]\tcrop.h = %d\n", (void *)t, t->input.crop.h); dev_dbg(t->dev, "[0x%p]\tcrop.pos.x = %d\n", (void *)t, t->input.crop.pos.x); dev_dbg(t->dev, "[0x%p]\tcrop.pos.y = %d\n", (void *)t, t->input.crop.pos.y); dev_dbg(t->dev, "[0x%p]input buffer:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tpaddr = 0x%x\n", (void *)t, t->input.paddr); dev_dbg(t->dev, "[0x%p]\ti_off = 0x%x\n", (void *)t, t->set.i_off); dev_dbg(t->dev, "[0x%p]\ti_uoff = 0x%x\n", (void *)t, t->set.i_uoff); dev_dbg(t->dev, "[0x%p]\ti_voff = 0x%x\n", (void *)t, t->set.i_voff); dev_dbg(t->dev, "[0x%p]\tistride = %d\n", (void *)t, t->set.istride); if (t->input.deinterlace.enable) { dev_dbg(t->dev, "[0x%p]deinterlace enabled with:\n", (void *)t); if (t->input.deinterlace.motion != HIGH_MOTION) { dev_dbg(t->dev, "[0x%p]\tlow/medium motion\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tpaddr_n = 0x%x\n", (void *)t, t->input.paddr_n); } else dev_dbg(t->dev, "[0x%p]\thigh motion\n", (void *)t); } dev_dbg(t->dev, "[0x%p]output:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tformat = 0x%x\n", (void *)t, t->output.format); dev_dbg(t->dev, "[0x%p]\twidth = %d\n", (void *)t, t->output.width); dev_dbg(t->dev, "[0x%p]\theight = %d\n", (void *)t, t->output.height); dev_dbg(t->dev, "[0x%p]\tcrop.w = %d\n", (void *)t, t->output.crop.w); dev_dbg(t->dev, "[0x%p]\tcrop.h = %d\n", (void *)t, t->output.crop.h); dev_dbg(t->dev, "[0x%p]\tcrop.pos.x = %d\n", (void *)t, t->output.crop.pos.x); dev_dbg(t->dev, "[0x%p]\tcrop.pos.y = %d\n", (void *)t, t->output.crop.pos.y); dev_dbg(t->dev, "[0x%p]\trotate = %d\n", (void *)t, t->output.rotate); dev_dbg(t->dev, "[0x%p]output buffer:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tpaddr = 0x%x\n", (void *)t, t->output.paddr); dev_dbg(t->dev, "[0x%p]\to_off = 0x%x\n", (void *)t, t->set.o_off); dev_dbg(t->dev, "[0x%p]\to_uoff = 0x%x\n", (void *)t, t->set.o_uoff); dev_dbg(t->dev, "[0x%p]\to_voff = 0x%x\n", (void *)t, t->set.o_voff); dev_dbg(t->dev, "[0x%p]\tostride = %d\n", (void *)t, t->set.ostride); if (t->overlay_en) { dev_dbg(t->dev, "[0x%p]overlay:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tformat = 0x%x\n", (void *)t, t->overlay.format); dev_dbg(t->dev, "[0x%p]\twidth = %d\n", (void *)t, t->overlay.width); dev_dbg(t->dev, "[0x%p]\theight = %d\n", (void *)t, t->overlay.height); dev_dbg(t->dev, "[0x%p]\tcrop.w = %d\n", (void *)t, t->overlay.crop.w); dev_dbg(t->dev, "[0x%p]\tcrop.h = %d\n", (void *)t, t->overlay.crop.h); dev_dbg(t->dev, "[0x%p]\tcrop.pos.x = %d\n", (void *)t, t->overlay.crop.pos.x); dev_dbg(t->dev, "[0x%p]\tcrop.pos.y = %d\n", (void *)t, t->overlay.crop.pos.y); dev_dbg(t->dev, "[0x%p]overlay buffer:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tpaddr = 0x%x\n", (void *)t, t->overlay.paddr); dev_dbg(t->dev, "[0x%p]\tov_off = 0x%x\n", (void *)t, t->set.ov_off); dev_dbg(t->dev, "[0x%p]\tov_uoff = 0x%x\n", (void *)t, t->set.ov_uoff); dev_dbg(t->dev, "[0x%p]\tov_voff = 0x%x\n", (void *)t, t->set.ov_voff); dev_dbg(t->dev, "[0x%p]\tovstride = %d\n", (void *)t, t->set.ovstride); if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) { dev_dbg(t->dev, "[0x%p]local alpha enabled with:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tpaddr = 0x%x\n", (void *)t, t->overlay.alpha.loc_alp_paddr); dev_dbg(t->dev, "[0x%p]\tov_alpha_off = 0x%x\n", (void *)t, t->set.ov_alpha_off); dev_dbg(t->dev, "[0x%p]\tov_alpha_stride = %d\n", (void *)t, t->set.ov_alpha_stride); } else dev_dbg(t->dev, "[0x%p]globle alpha enabled with value 0x%x\n", (void *)t, t->overlay.alpha.gvalue); if (t->overlay.colorkey.enable) dev_dbg(t->dev, "[0x%p]colorkey enabled with value 0x%x\n", (void *)t, t->overlay.colorkey.value); } dev_dbg(t->dev, "[0x%p]want task_id = %d\n", (void *)t, t->task_id); dev_dbg(t->dev, "[0x%p]want task mode is 0x%x\n", (void *)t, t->set.mode); dev_dbg(t->dev, "[0x%p]\tIC_MODE = 0x%x\n", (void *)t, IC_MODE); dev_dbg(t->dev, "[0x%p]\tROT_MODE = 0x%x\n", (void *)t, ROT_MODE); dev_dbg(t->dev, "[0x%p]\tVDI_MODE = 0x%x\n", (void *)t, VDI_MODE); dev_dbg(t->dev, "[0x%p]\tTask_no = 0x%x\n\n\n", (void *)t, t->task_no); } static void dump_check_err(struct device *dev, int err) { switch (err) { case IPU_CHECK_ERR_INPUT_CROP: dev_err(dev, "input crop setting error\n"); break; case IPU_CHECK_ERR_OUTPUT_CROP: dev_err(dev, "output crop setting error\n"); break; case IPU_CHECK_ERR_OVERLAY_CROP: dev_err(dev, "overlay crop setting error\n"); break; case IPU_CHECK_ERR_INPUT_OVER_LIMIT: dev_err(dev, "input over limitation\n"); break; case IPU_CHECK_ERR_OVERLAY_WITH_VDI: dev_err(dev, "do not support overlay with deinterlace\n"); break; case IPU_CHECK_ERR_OV_OUT_NO_FIT: dev_err(dev, "width/height of overlay and ic output should be same\n"); break; case IPU_CHECK_ERR_PROC_NO_NEED: dev_err(dev, "no ipu processing need\n"); break; case IPU_CHECK_ERR_SPLIT_INPUTW_OVER: dev_err(dev, "split mode input width overflow\n"); break; case IPU_CHECK_ERR_SPLIT_INPUTH_OVER: dev_err(dev, "split mode input height overflow\n"); break; case IPU_CHECK_ERR_SPLIT_OUTPUTW_OVER: dev_err(dev, "split mode output width overflow\n"); break; case IPU_CHECK_ERR_SPLIT_OUTPUTH_OVER: dev_err(dev, "split mode output height overflow\n"); break; case IPU_CHECK_ERR_SPLIT_WITH_ROT: dev_err(dev, "not support split mode with rotation\n"); break; case IPU_CHECK_ERR_W_DOWNSIZE_OVER: dev_err(dev, "horizontal downsizing ratio overflow\n"); break; case IPU_CHECK_ERR_H_DOWNSIZE_OVER: dev_err(dev, "vertical downsizing ratio overflow\n"); break; default: break; } } static void dump_check_warn(struct device *dev, int warn) { if (warn & IPU_CHECK_WARN_INPUT_OFFS_NOT8ALIGN) dev_warn(dev, "input u/v offset not 8 align\n"); if (warn & IPU_CHECK_WARN_OUTPUT_OFFS_NOT8ALIGN) dev_warn(dev, "output u/v offset not 8 align\n"); if (warn & IPU_CHECK_WARN_OVERLAY_OFFS_NOT8ALIGN) dev_warn(dev, "overlay u/v offset not 8 align\n"); } static int set_crop(struct ipu_crop *crop, int width, int height, int fmt) { if ((width == 0) || (height == 0)) { pr_err("Invalid param: width=%d, height=%d\n", width, height); return -EINVAL; } if ((IPU_PIX_FMT_TILED_NV12 == fmt) || (IPU_PIX_FMT_TILED_NV12F == fmt)) { if (crop->w || crop->h) { if (((crop->w + crop->pos.x) > width) || ((crop->h + crop->pos.y) > height) || (0 != (crop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN)) || (0 != (crop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN)) || (0 != (crop->pos.x % IPU_PIX_FMT_TILED_NV12_MBALIGN)) || (0 != (crop->pos.y % IPU_PIX_FMT_TILED_NV12_MBALIGN)) ) { pr_err("set_crop error MB align.\n"); return -EINVAL; } } else { crop->pos.x = 0; crop->pos.y = 0; crop->w = width; crop->h = height; if ((0 != (crop->w % IPU_PIX_FMT_TILED_NV12_MBALIGN)) || (0 != (crop->h % IPU_PIX_FMT_TILED_NV12_MBALIGN))) { pr_err("set_crop error w/h MB align.\n"); return -EINVAL; } } } else { if (crop->w || crop->h) { if (((crop->w + crop->pos.x) > (width + 16)) || ((crop->h + crop->pos.y) > height + 16)) { pr_err("set_crop error exceeds width/height.\n"); return -EINVAL; } } else { crop->pos.x = 0; crop->pos.y = 0; crop->w = width; crop->h = height; } crop->w -= crop->w%8; crop->h -= crop->h%8; } if ((crop->w == 0) || (crop->h == 0)) { pr_err("Invalid crop param: crop.w=%d, crop.h=%d\n", crop->w, crop->h); return -EINVAL; } return 0; } static void update_offset(unsigned int fmt, unsigned int width, unsigned int height, unsigned int pos_x, unsigned int pos_y, int *off, int *uoff, int *voff, int *stride) { /* NOTE: u v offset should based on start point of off*/ switch (fmt) { case IPU_PIX_FMT_YUV420P2: case IPU_PIX_FMT_YUV420P: *off = pos_y * width + pos_x; *uoff = (width * (height - pos_y) - pos_x) + (width/2) * (pos_y/2) + pos_x/2; /* In case height is odd, round up to even */ *voff = *uoff + (width/2) * ((height+1)/2); break; case IPU_PIX_FMT_YVU420P: *off = pos_y * width + pos_x; *voff = (width * (height - pos_y) - pos_x) + (width/2) * (pos_y/2) + pos_x/2; /* In case height is odd, round up to even */ *uoff = *voff + (width/2) * ((height+1)/2); break; case IPU_PIX_FMT_YVU422P: *off = pos_y * width + pos_x; *voff = (width * (height - pos_y) - pos_x) + (width/2) * pos_y + pos_x/2; *uoff = *voff + (width/2) * height; break; case IPU_PIX_FMT_YUV422P: *off = pos_y * width + pos_x; *uoff = (width * (height - pos_y) - pos_x) + (width/2) * pos_y + pos_x/2; *voff = *uoff + (width/2) * height; break; case IPU_PIX_FMT_YUV444P: *off = pos_y * width + pos_x; *uoff = width * height; *voff = width * height * 2; break; case IPU_PIX_FMT_NV12: *off = pos_y * width + pos_x; *uoff = (width * (height - pos_y) - pos_x) + width * (pos_y/2) + pos_x; break; case IPU_PIX_FMT_TILED_NV12: /* * tiled format, progressive: * assuming that line is aligned with MB height (aligned to 16) * offset = line * stride + (pixel / MB_width) * pixels_in_MB * = line * stride + (pixel / 16) * 256 * = line * stride + pixel * 16 */ *off = pos_y * width + (pos_x << 4); *uoff = ALIGN(width * height, SZ_4K) + (*off >> 1) - *off; break; case IPU_PIX_FMT_TILED_NV12F: /* * tiled format, interlaced: * same as above, only number of pixels in MB is 128, * instead of 256 */ *off = (pos_y >> 1) * width + (pos_x << 3); *uoff = ALIGN(width * height/2, SZ_4K) + (*off >> 1) - *off; break; default: *off = (pos_y * width + pos_x) * fmt_to_bpp(fmt)/8; break; } *stride = width * bytes_per_pixel(fmt); } static int update_split_setting(struct ipu_task_entry *t, bool vdi_split) { struct stripe_param left_stripe; struct stripe_param right_stripe; struct stripe_param up_stripe; struct stripe_param down_stripe; u32 iw, ih, ow, oh; u32 max_width; int ret; if (t->output.rotate >= IPU_ROTATE_90_RIGHT) return IPU_CHECK_ERR_SPLIT_WITH_ROT; iw = t->input.crop.w; ih = t->input.crop.h; ow = t->output.crop.w; oh = t->output.crop.h; memset(&left_stripe, 0, sizeof(left_stripe)); memset(&right_stripe, 0, sizeof(right_stripe)); memset(&up_stripe, 0, sizeof(up_stripe)); memset(&down_stripe, 0, sizeof(down_stripe)); if (t->set.split_mode & RL_SPLIT) { /* * We do want equal strips: initialize stripes in case * calc_stripes returns before actually doing the calculation */ left_stripe.input_width = iw / 2; left_stripe.output_width = ow / 2; right_stripe.input_column = iw / 2; right_stripe.output_column = ow / 2; if (vdi_split) max_width = soc_max_vdi_in_width(t->ipu); else max_width = soc_max_out_width(); ret = ipu_calc_stripes_sizes(iw, ow, max_width, (((unsigned long long)1) << 32), /* 32bit for fractional*/ 1, /* equal stripes */ t->input.format, t->output.format, &left_stripe, &right_stripe); if (ret < 0) return IPU_CHECK_ERR_W_DOWNSIZE_OVER; else if (ret) dev_dbg(t->dev, "Warn: no:0x%x,calc_stripes ret:%d\n", t->task_no, ret); t->set.sp_setting.iw = left_stripe.input_width; t->set.sp_setting.ow = left_stripe.output_width; t->set.sp_setting.outh_resize_ratio = left_stripe.irr; t->set.sp_setting.i_left_pos = left_stripe.input_column; t->set.sp_setting.o_left_pos = left_stripe.output_column; t->set.sp_setting.i_right_pos = right_stripe.input_column; t->set.sp_setting.o_right_pos = right_stripe.output_column; } else { t->set.sp_setting.iw = iw; t->set.sp_setting.ow = ow; t->set.sp_setting.outh_resize_ratio = 0; t->set.sp_setting.i_left_pos = 0; t->set.sp_setting.o_left_pos = 0; t->set.sp_setting.i_right_pos = 0; t->set.sp_setting.o_right_pos = 0; } if ((t->set.sp_setting.iw + t->set.sp_setting.i_right_pos) > (iw+16)) return IPU_CHECK_ERR_SPLIT_INPUTW_OVER; if (((t->set.sp_setting.ow + t->set.sp_setting.o_right_pos) > ow) || (t->set.sp_setting.ow > soc_max_out_width())) return IPU_CHECK_ERR_SPLIT_OUTPUTW_OVER; if (rounddown(t->set.sp_setting.ow, 8) * 8 <= rounddown(t->set.sp_setting.iw, 8)) return IPU_CHECK_ERR_W_DOWNSIZE_OVER; if (t->set.split_mode & UD_SPLIT) { /* * We do want equal strips: initialize stripes in case * calc_stripes returns before actually doing the calculation */ up_stripe.input_width = ih / 2; up_stripe.output_width = oh / 2; down_stripe.input_column = ih / 2; down_stripe.output_column = oh / 2; ret = ipu_calc_stripes_sizes(ih, oh, soc_max_out_height(), (((unsigned long long)1) << 32), /* 32bit for fractional*/ 0x1 | 0x2, /* equal stripes and vertical */ t->input.format, t->output.format, &up_stripe, &down_stripe); if (ret < 0) return IPU_CHECK_ERR_H_DOWNSIZE_OVER; else if (ret) dev_err(t->dev, "Warn: no:0x%x,calc_stripes ret:%d\n", t->task_no, ret); t->set.sp_setting.ih = up_stripe.input_width; t->set.sp_setting.oh = up_stripe.output_width; t->set.sp_setting.outv_resize_ratio = up_stripe.irr; t->set.sp_setting.i_top_pos = up_stripe.input_column; t->set.sp_setting.o_top_pos = up_stripe.output_column; t->set.sp_setting.i_bottom_pos = down_stripe.input_column; t->set.sp_setting.o_bottom_pos = down_stripe.output_column; } else { t->set.sp_setting.ih = ih; t->set.sp_setting.oh = oh; t->set.sp_setting.outv_resize_ratio = 0; t->set.sp_setting.i_top_pos = 0; t->set.sp_setting.o_top_pos = 0; t->set.sp_setting.i_bottom_pos = 0; t->set.sp_setting.o_bottom_pos = 0; } /* downscale case: enforce limits */ if (((t->set.sp_setting.ih + t->set.sp_setting.i_bottom_pos) > (ih)) && (t->set.sp_setting.ih >= t->set.sp_setting.oh)) return IPU_CHECK_ERR_SPLIT_INPUTH_OVER; /* upscale case: relax limits because ipu_calc_stripes_sizes() may create input stripe that falls just outside of the input window */ else if ((t->set.sp_setting.ih + t->set.sp_setting.i_bottom_pos) > (ih+16)) return IPU_CHECK_ERR_SPLIT_INPUTH_OVER; if (((t->set.sp_setting.oh + t->set.sp_setting.o_bottom_pos) > oh) || (t->set.sp_setting.oh > soc_max_out_height())) return IPU_CHECK_ERR_SPLIT_OUTPUTH_OVER; if (rounddown(t->set.sp_setting.oh, 8) * 8 <= rounddown(t->set.sp_setting.ih, 8)) return IPU_CHECK_ERR_H_DOWNSIZE_OVER; return IPU_CHECK_OK; } static int check_task(struct ipu_task_entry *t) { int tmp; int ret = IPU_CHECK_OK; int timeout; bool vdi_split = false; int ocw, och; if ((IPU_PIX_FMT_TILED_NV12 == t->overlay.format) || (IPU_PIX_FMT_TILED_NV12F == t->overlay.format) || (IPU_PIX_FMT_TILED_NV12 == t->output.format) || (IPU_PIX_FMT_TILED_NV12F == t->output.format) || ((IPU_PIX_FMT_TILED_NV12F == t->input.format) && !t->input.deinterlace.enable)) { ret = IPU_CHECK_ERR_NOT_SUPPORT; goto done; } /* check input */ ret = set_crop(&t->input.crop, t->input.width, t->input.height, t->input.format); if (ret < 0) { ret = IPU_CHECK_ERR_INPUT_CROP; goto done; } else update_offset(t->input.format, t->input.width, t->input.height, t->input.crop.pos.x, t->input.crop.pos.y, &t->set.i_off, &t->set.i_uoff, &t->set.i_voff, &t->set.istride); /* check output */ ret = set_crop(&t->output.crop, t->output.width, t->output.height, t->output.format); if (ret < 0) { ret = IPU_CHECK_ERR_OUTPUT_CROP; goto done; } else update_offset(t->output.format, t->output.width, t->output.height, t->output.crop.pos.x, t->output.crop.pos.y, &t->set.o_off, &t->set.o_uoff, &t->set.o_voff, &t->set.ostride); if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { /* * Cache output width and height and * swap them so that we may check * downsize overflow correctly. */ ocw = t->output.crop.h; och = t->output.crop.w; } else { ocw = t->output.crop.w; och = t->output.crop.h; } if (ocw * 8 <= t->input.crop.w) { ret = IPU_CHECK_ERR_W_DOWNSIZE_OVER; goto done; } if (och * 8 <= t->input.crop.h) { ret = IPU_CHECK_ERR_H_DOWNSIZE_OVER; goto done; } if ((IPU_PIX_FMT_TILED_NV12 == t->input.format) || (IPU_PIX_FMT_TILED_NV12F == t->input.format)) { if ((t->input.crop.w > soc_max_in_width(1)) || (t->input.crop.h > soc_max_in_height())) { ret = IPU_CHECK_ERR_INPUT_OVER_LIMIT; goto done; } /* output fmt: NV12 and YUYV, now don't support resize */ if (((IPU_PIX_FMT_NV12 != t->output.format) && (IPU_PIX_FMT_YUYV != t->output.format)) || (t->input.crop.w != t->output.crop.w) || (t->input.crop.h != t->output.crop.h)) { ret = IPU_CHECK_ERR_NOT_SUPPORT; goto done; } } /* check overlay if there is */ if (t->overlay_en) { if (t->input.deinterlace.enable) { ret = IPU_CHECK_ERR_OVERLAY_WITH_VDI; goto done; } ret = set_crop(&t->overlay.crop, t->overlay.width, t->overlay.height, t->overlay.format); if (ret < 0) { ret = IPU_CHECK_ERR_OVERLAY_CROP; goto done; } else { ocw = t->output.crop.w; och = t->output.crop.h; if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { ocw = t->output.crop.h; och = t->output.crop.w; } if ((t->overlay.crop.w != ocw) || (t->overlay.crop.h != och)) { ret = IPU_CHECK_ERR_OV_OUT_NO_FIT; goto done; } update_offset(t->overlay.format, t->overlay.width, t->overlay.height, t->overlay.crop.pos.x, t->overlay.crop.pos.y, &t->set.ov_off, &t->set.ov_uoff, &t->set.ov_voff, &t->set.ovstride); if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) { t->set.ov_alpha_stride = t->overlay.width; t->set.ov_alpha_off = t->overlay.crop.pos.y * t->overlay.width + t->overlay.crop.pos.x; } } } /* input overflow? */ if (!((IPU_PIX_FMT_TILED_NV12 == t->input.format) || (IPU_PIX_FMT_TILED_NV12F == t->input.format))) { if ((t->input.crop.w > soc_max_in_width(0)) || (t->input.crop.h > soc_max_in_height())) { ret = IPU_CHECK_ERR_INPUT_OVER_LIMIT; goto done; } } /* check task mode */ t->set.mode = NULL_MODE; t->set.split_mode = NO_SPLIT; if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { /*output swap*/ tmp = t->output.crop.w; t->output.crop.w = t->output.crop.h; t->output.crop.h = tmp; } if (t->output.rotate >= IPU_ROTATE_90_RIGHT) t->set.mode |= ROT_MODE; /*need resize or CSC?*/ if ((t->input.crop.w != t->output.crop.w) || (t->input.crop.h != t->output.crop.h) || need_csc(t->input.format, t->output.format)) t->set.mode |= IC_MODE; /*need cropping?*/ if ((t->input.crop.w != t->input.width) || (t->input.crop.h != t->input.height) || (t->output.crop.w != t->output.width) || (t->output.crop.h != t->output.height)) t->set.mode |= IC_MODE; /*need flip?*/ if ((t->set.mode == NULL_MODE) && (t->output.rotate > IPU_ROTATE_NONE)) t->set.mode |= IC_MODE; /*need IDMAC do format(same color space)?*/ if ((t->set.mode == NULL_MODE) && (t->input.format != t->output.format)) t->set.mode |= IC_MODE; /*overlay support*/ if (t->overlay_en) t->set.mode |= IC_MODE; /*deinterlace*/ if (t->input.deinterlace.enable) { t->set.mode &= ~IC_MODE; t->set.mode |= VDI_MODE; } if ((IPU_PIX_FMT_TILED_NV12 == t->input.format) || (IPU_PIX_FMT_TILED_NV12F == t->input.format)) { if (t->set.mode & ROT_MODE) { ret = IPU_CHECK_ERR_NOT_SUPPORT; goto done; } t->set.mode |= VDOA_MODE; if (IPU_PIX_FMT_TILED_NV12F == t->input.format) t->set.mode |= VDOA_BAND_MODE; t->set.mode &= ~IC_MODE; } if ((t->set.mode & (IC_MODE | VDI_MODE)) && (IPU_PIX_FMT_TILED_NV12F != t->input.format)) { if (t->output.crop.w > soc_max_out_width()) t->set.split_mode |= RL_SPLIT; if (t->output.crop.h > soc_max_out_height()) t->set.split_mode |= UD_SPLIT; if (!t->set.split_mode && (t->set.mode & VDI_MODE) && (t->input.crop.w > soc_max_vdi_in_width(t->ipu))) { t->set.split_mode |= RL_SPLIT; vdi_split = true; } if (t->set.split_mode) { if ((t->set.split_mode == RL_SPLIT) || (t->set.split_mode == UD_SPLIT)) timeout = DEF_TIMEOUT_MS * 2 + DEF_DELAY_MS; else timeout = DEF_TIMEOUT_MS * 4 + DEF_DELAY_MS; if (t->timeout < timeout) t->timeout = timeout; ret = update_split_setting(t, vdi_split); if (ret > IPU_CHECK_ERR_MIN) goto done; } } if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { /*output swap*/ tmp = t->output.crop.w; t->output.crop.w = t->output.crop.h; t->output.crop.h = tmp; } if (t->set.mode == NULL_MODE) { ret = IPU_CHECK_ERR_PROC_NO_NEED; goto done; } if ((t->set.i_uoff % 8) || (t->set.i_voff % 8)) ret |= IPU_CHECK_WARN_INPUT_OFFS_NOT8ALIGN; if ((t->set.o_uoff % 8) || (t->set.o_voff % 8)) ret |= IPU_CHECK_WARN_OUTPUT_OFFS_NOT8ALIGN; if (t->overlay_en && ((t->set.ov_uoff % 8) || (t->set.ov_voff % 8))) ret |= IPU_CHECK_WARN_OVERLAY_OFFS_NOT8ALIGN; done: /* dump msg */ if (debug) { if (ret > IPU_CHECK_ERR_MIN) dump_check_err(t->dev, ret); else if (ret != IPU_CHECK_OK) dump_check_warn(t->dev, ret); } return ret; } static int prepare_task(struct ipu_task_entry *t) { int ret = 0; ret = check_task(t); if (ret > IPU_CHECK_ERR_MIN) return -EINVAL; if (t->set.mode & VDI_MODE) { t->task_id = IPU_TASK_ID_VF; t->set.task = VDI_VF; if (t->set.mode & ROT_MODE) t->set.task |= ROT_VF; } if (VDOA_MODE == t->set.mode) { if (t->set.task != 0) { dev_err(t->dev, "ERR: vdoa only task:0x%x, [0x%p].\n", t->set.task, t); return -EINVAL; } t->set.task |= VDOA_ONLY; } if (VDOA_BAND_MODE & t->set.mode) { /* to save band size: 1<<3 = 8 lines */ t->set.band_lines = 3; } dump_task_info(t); return ret; } static uint32_t ic_vf_pp_is_busy(struct ipu_soc *ipu, bool is_vf) { uint32_t status; uint32_t status_vf; uint32_t status_rot; if (is_vf) { status = ipu_channel_status(ipu, MEM_VDI_PRP_VF_MEM); status_vf = ipu_channel_status(ipu, MEM_PRP_VF_MEM); status_rot = ipu_channel_status(ipu, MEM_ROT_VF_MEM); return status || status_vf || status_rot; } else { status = ipu_channel_status(ipu, MEM_PP_MEM); status_rot = ipu_channel_status(ipu, MEM_ROT_PP_MEM); return status || status_rot; } } static int _get_vdoa_ipu_res(struct ipu_task_entry *t) { int i; struct ipu_soc *ipu; u8 *used; uint32_t found_ipu = 0; uint32_t found_vdoa = 0; struct ipu_channel_tabel *tbl = &ipu_ch_tbl; mutex_lock(&tbl->lock); if (t->set.mode & VDOA_MODE) { if (NULL != t->vdoa_handle) found_vdoa = 1; else { found_vdoa = tbl->vdoa_used ? 0 : 1; if (found_vdoa) { tbl->vdoa_used = 1; vdoa_get_handle(&t->vdoa_handle); } else /* first get vdoa->ipu resource sequence */ goto out; if (t->set.task & VDOA_ONLY) goto out; } } for (i = 0; i < max_ipu_no; i++) { ipu = ipu_get_soc(i); if (IS_ERR(ipu)) dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n", t->task_no, found_vdoa, i); used = &tbl->used[i][IPU_PP_CH_VF]; if (t->set.mode & VDI_MODE) { if (0 == *used) { *used = 1; found_ipu = 1; break; } } else if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) { if (0 == *used) { t->task_id = IPU_TASK_ID_VF; if (t->set.mode & IC_MODE) t->set.task |= IC_VF; if (t->set.mode & ROT_MODE) t->set.task |= ROT_VF; *used = 1; found_ipu = 1; break; } } else dev_err(t->dev, "no:0x%x,found_vdoa:%d, mode:0x%x\n", t->task_no, found_vdoa, t->set.mode); } if (found_ipu) goto next; for (i = 0; i < max_ipu_no; i++) { ipu = ipu_get_soc(i); if (IS_ERR(ipu)) dev_err(t->dev, "no:0x%x,found_vdoa:%d, ipu:%d\n", t->task_no, found_vdoa, i); if ((t->set.mode & IC_MODE) || only_rot(t->set.mode)) { used = &tbl->used[i][IPU_PP_CH_PP]; if (0 == *used) { t->task_id = IPU_TASK_ID_PP; if (t->set.mode & IC_MODE) t->set.task |= IC_PP; if (t->set.mode & ROT_MODE) t->set.task |= ROT_PP; *used = 1; found_ipu = 1; break; } } } next: if (found_ipu) { t->ipu = ipu; t->ipu_id = i; t->dev = ipu->dev; if (atomic_inc_return(&t->res_get) == 2) dev_err(t->dev, "ERR no:0x%x,found_vdoa:%d,get ipu twice\n", t->task_no, found_vdoa); } out: dev_dbg(t->dev, "%s:no:0x%x,found_vdoa:%d, found_ipu:%d\n", __func__, t->task_no, found_vdoa, found_ipu); mutex_unlock(&tbl->lock); if (t->set.task & VDOA_ONLY) return found_vdoa; else if (t->set.mode & VDOA_MODE) return found_vdoa && found_ipu; else return found_ipu; } static void put_vdoa_ipu_res(struct ipu_task_entry *tsk, int vdoa_only) { int ret; int rel_vdoa = 0, rel_ipu = 0; struct ipu_channel_tabel *tbl = &ipu_ch_tbl; mutex_lock(&tbl->lock); if (tsk->set.mode & VDOA_MODE) { if (!tbl->vdoa_used && tsk->vdoa_handle) dev_err(tsk->dev, "ERR no:0x%x,vdoa not used,mode:0x%x\n", tsk->task_no, tsk->set.mode); if (tbl->vdoa_used && tsk->vdoa_handle) { tbl->vdoa_used = 0; vdoa_put_handle(&tsk->vdoa_handle); if (tsk->ipu) tsk->ipu->vdoa_en = 0; rel_vdoa = 1; if (vdoa_only || (tsk->set.task & VDOA_ONLY)) goto out; } } if (tsk->ipu_id != 0 && tsk->ipu_id != 1) { dev_err(tsk->dev, "%s:invalid ipu id, no:0x%x, rel_vdoa:%d, rel_ipu:%d\n", __func__, tsk->task_no, rel_vdoa, rel_ipu); goto out; } tbl->used[tsk->ipu_id][tsk->task_id - 1] = 0; rel_ipu = 1; ret = atomic_inc_return(&tsk->res_free); if (ret == 2) dev_err(tsk->dev, "ERR no:0x%x,rel_vdoa:%d,put ipu twice\n", tsk->task_no, rel_vdoa); out: dev_dbg(tsk->dev, "%s:no:0x%x,rel_vdoa:%d, rel_ipu:%d\n", __func__, tsk->task_no, rel_vdoa, rel_ipu); mutex_unlock(&tbl->lock); } static int get_vdoa_ipu_res(struct ipu_task_entry *t) { int ret; uint32_t found = 0; found = _get_vdoa_ipu_res(t); if (!found) { t->ipu_id = -1; t->ipu = NULL; /* blocking to get resource */ ret = atomic_inc_return(&req_cnt); dev_dbg(t->dev, "wait_res:no:0x%x,req_cnt:%d\n", t->task_no, ret); ret = wait_event_timeout(res_waitq, _get_vdoa_ipu_res(t), msecs_to_jiffies(t->timeout - DEF_DELAY_MS)); if (ret == 0) { dev_err(t->dev, "ERR[0x%p,no-0x%x] wait_res timeout:%dms!\n", t, t->task_no, t->timeout - DEF_DELAY_MS); ret = -ETIMEDOUT; t->state = STATE_RES_TIMEOUT; goto out; } else { if (!(t->set.task & VDOA_ONLY) && (!t->ipu)) dev_err(t->dev, "ERR[no-0x%x] can not get ipu!\n", t->task_no); ret = atomic_read(&req_cnt); if (ret > 0) ret = atomic_dec_return(&req_cnt); else dev_err(t->dev, "ERR[no-0x%x] req_cnt:%d mismatch!\n", t->task_no, ret); dev_dbg(t->dev, "no-0x%x,[0x%p],req_cnt:%d, got_res!\n", t->task_no, t, ret); found = 1; } } out: return found; } static struct ipu_task_entry *create_task_entry(struct ipu_task *task) { struct ipu_task_entry *tsk; tsk = kzalloc(sizeof(struct ipu_task_entry), GFP_KERNEL); if (!tsk) return ERR_PTR(-ENOMEM); kref_init(&tsk->refcount); tsk->state = -EINVAL; tsk->ipu_id = -1; tsk->dev = ipu_dev; tsk->input = task->input; tsk->output = task->output; tsk->overlay_en = task->overlay_en; if (tsk->overlay_en) tsk->overlay = task->overlay; if (task->timeout > DEF_TIMEOUT_MS) tsk->timeout = task->timeout; else tsk->timeout = DEF_TIMEOUT_MS; return tsk; } static void task_mem_free(struct kref *ref) { struct ipu_task_entry *tsk = container_of(ref, struct ipu_task_entry, refcount); kfree(tsk); } int create_split_child_task(struct ipu_split_task *sp_task) { int ret = 0; struct ipu_task_entry *tsk; tsk = create_task_entry(&sp_task->task); if (IS_ERR(tsk)) return PTR_ERR(tsk); sp_task->child_task = tsk; tsk->task_no = sp_task->task_no; ret = prepare_task(tsk); if (ret < 0) goto err; tsk->parent = sp_task->parent_task; tsk->set.sp_setting = sp_task->parent_task->set.sp_setting; list_add(&tsk->node, &tsk->parent->split_list); dev_dbg(tsk->dev, "[0x%p] sp_tsk Q list,no-0x%x\n", tsk, tsk->task_no); tsk->state = STATE_QUEUE; CHECK_PERF(&tsk->ts_queue); err: return ret; } static inline int sp_task_check_done(struct ipu_split_task *sp_task, struct ipu_task_entry *parent, int num, int *idx) { int i; int ret = 0; struct ipu_task_entry *tsk; struct mutex *lock = &parent->split_lock; *idx = -EINVAL; mutex_lock(lock); for (i = 0; i < num; i++) { tsk = sp_task[i].child_task; if (tsk && tsk->split_done) { *idx = i; ret = 1; goto out; } } out: mutex_unlock(lock); return ret; } static int create_split_task( int stripe, struct ipu_split_task *sp_task) { struct ipu_task *task = &(sp_task->task); struct ipu_task_entry *t = sp_task->parent_task; int ret; sp_task->task_no |= stripe; task->input = t->input; task->output = t->output; task->overlay_en = t->overlay_en; if (task->overlay_en) task->overlay = t->overlay; task->task_id = t->task_id; if ((t->set.split_mode == RL_SPLIT) || (t->set.split_mode == UD_SPLIT)) task->timeout = t->timeout / 2; else task->timeout = t->timeout / 4; task->input.crop.w = t->set.sp_setting.iw; task->input.crop.h = t->set.sp_setting.ih; if (task->overlay_en) { task->overlay.crop.w = t->set.sp_setting.ow; task->overlay.crop.h = t->set.sp_setting.oh; } if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { task->output.crop.w = t->set.sp_setting.oh; task->output.crop.h = t->set.sp_setting.ow; t->set.sp_setting.rl_split_line = t->set.sp_setting.o_bottom_pos; t->set.sp_setting.ud_split_line = t->set.sp_setting.o_right_pos; } else { task->output.crop.w = t->set.sp_setting.ow; task->output.crop.h = t->set.sp_setting.oh; t->set.sp_setting.rl_split_line = t->set.sp_setting.o_right_pos; t->set.sp_setting.ud_split_line = t->set.sp_setting.o_bottom_pos; } if (stripe & LEFT_STRIPE) task->input.crop.pos.x += t->set.sp_setting.i_left_pos; else if (stripe & RIGHT_STRIPE) task->input.crop.pos.x += t->set.sp_setting.i_right_pos; if (stripe & UP_STRIPE) task->input.crop.pos.y += t->set.sp_setting.i_top_pos; else if (stripe & DOWN_STRIPE) task->input.crop.pos.y += t->set.sp_setting.i_bottom_pos; if (task->overlay_en) { if (stripe & LEFT_STRIPE) task->overlay.crop.pos.x += t->set.sp_setting.o_left_pos; else if (stripe & RIGHT_STRIPE) task->overlay.crop.pos.x += t->set.sp_setting.o_right_pos; if (stripe & UP_STRIPE) task->overlay.crop.pos.y += t->set.sp_setting.o_top_pos; else if (stripe & DOWN_STRIPE) task->overlay.crop.pos.y += t->set.sp_setting.o_bottom_pos; } switch (t->output.rotate) { case IPU_ROTATE_NONE: if (stripe & LEFT_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_left_pos; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_right_pos; if (stripe & UP_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_top_pos; else if (stripe & DOWN_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_bottom_pos; break; case IPU_ROTATE_VERT_FLIP: if (stripe & LEFT_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_left_pos; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_right_pos; if (stripe & UP_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_top_pos - t->set.sp_setting.oh; else if (stripe & DOWN_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_bottom_pos - t->set.sp_setting.oh; break; case IPU_ROTATE_HORIZ_FLIP: if (stripe & LEFT_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_left_pos - t->set.sp_setting.ow; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_right_pos - t->set.sp_setting.ow; if (stripe & UP_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_top_pos; else if (stripe & DOWN_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_bottom_pos; break; case IPU_ROTATE_180: if (stripe & LEFT_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_left_pos - t->set.sp_setting.ow; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_right_pos - t->set.sp_setting.ow; if (stripe & UP_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_top_pos - t->set.sp_setting.oh; else if (stripe & DOWN_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_bottom_pos - t->set.sp_setting.oh; break; case IPU_ROTATE_90_RIGHT: if (stripe & UP_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_top_pos - t->set.sp_setting.oh; else if (stripe & DOWN_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_bottom_pos - t->set.sp_setting.oh; if (stripe & LEFT_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_left_pos; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_right_pos; break; case IPU_ROTATE_90_RIGHT_HFLIP: if (stripe & UP_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_top_pos; else if (stripe & DOWN_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_bottom_pos; if (stripe & LEFT_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_left_pos; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.y += t->set.sp_setting.o_right_pos; break; case IPU_ROTATE_90_RIGHT_VFLIP: if (stripe & UP_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_top_pos - t->set.sp_setting.oh; else if (stripe & DOWN_STRIPE) task->output.crop.pos.x = t->output.crop.pos.x + t->output.crop.w - t->set.sp_setting.o_bottom_pos - t->set.sp_setting.oh; if (stripe & LEFT_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_left_pos - t->set.sp_setting.ow; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_right_pos - t->set.sp_setting.ow; break; case IPU_ROTATE_90_LEFT: if (stripe & UP_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_top_pos; else if (stripe & DOWN_STRIPE) task->output.crop.pos.x += t->set.sp_setting.o_bottom_pos; if (stripe & LEFT_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_left_pos - t->set.sp_setting.ow; else if (stripe & RIGHT_STRIPE) task->output.crop.pos.y = t->output.crop.pos.y + t->output.crop.h - t->set.sp_setting.o_right_pos - t->set.sp_setting.ow; break; default: dev_err(t->dev, "ERR:should not be here\n"); break; } ret = create_split_child_task(sp_task); if (ret < 0) dev_err(t->dev, "ERR:create_split_child_task() ret:%d\n", ret); return ret; } static int queue_split_task(struct ipu_task_entry *t, struct ipu_split_task *sp_task, uint32_t size) { int err[4]; int ret = 0; int i, j; struct ipu_task_entry *tsk = NULL; struct mutex *lock = &t->split_lock; struct mutex *vdic_lock = &t->vdic_lock; dev_dbg(t->dev, "Split task 0x%p, no-0x%x, size:%d\n", t, t->task_no, size); mutex_init(lock); mutex_init(vdic_lock); init_waitqueue_head(&t->split_waitq); INIT_LIST_HEAD(&t->split_list); for (j = 0; j < size; j++) { memset(&sp_task[j], 0, sizeof(*sp_task)); sp_task[j].parent_task = t; sp_task[j].task_no = t->task_no; } if (t->set.split_mode == RL_SPLIT) { i = 0; err[i] = create_split_task(RIGHT_STRIPE, &sp_task[i]); if (err[i] < 0) goto err_start; i = 1; err[i] = create_split_task(LEFT_STRIPE, &sp_task[i]); } else if (t->set.split_mode == UD_SPLIT) { i = 0; err[i] = create_split_task(DOWN_STRIPE, &sp_task[i]); if (err[i] < 0) goto err_start; i = 1; err[i] = create_split_task(UP_STRIPE, &sp_task[i]); } else { i = 0; err[i] = create_split_task(RIGHT_STRIPE | DOWN_STRIPE, &sp_task[i]); if (err[i] < 0) goto err_start; i = 1; err[i] = create_split_task(LEFT_STRIPE | DOWN_STRIPE, &sp_task[i]); if (err[i] < 0) goto err_start; i = 2; err[i] = create_split_task(RIGHT_STRIPE | UP_STRIPE, &sp_task[i]); if (err[i] < 0) goto err_start; i = 3; err[i] = create_split_task(LEFT_STRIPE | UP_STRIPE, &sp_task[i]); } err_start: for (j = 0; j < (i + 1); j++) { if (err[j] < 0) { if (sp_task[j].child_task) dev_err(t->dev, "sp_task[%d],no-0x%x fail state:%d, queue err:%d.\n", j, sp_task[j].child_task->task_no, sp_task[j].child_task->state, err[j]); goto err_exit; } dev_dbg(t->dev, "[0x%p] sp_task[%d], no-0x%x state:%s, queue ret:%d.\n", sp_task[j].child_task, j, sp_task[j].child_task->task_no, state_msg[sp_task[j].child_task->state].msg, err[j]); } return ret; err_exit: for (j = 0; j < (i + 1); j++) { if (err[j] < 0 && !ret) ret = err[j]; tsk = sp_task[j].child_task; if (!tsk) continue; kfree(tsk); } t->state = STATE_ERR; return ret; } static int init_tiled_buf(struct ipu_soc *ipu, struct ipu_task_entry *t, ipu_channel_t channel, uint32_t ch_type) { int ret = 0; int i; uint32_t ipu_fmt; dma_addr_t inbuf_base = 0; u32 field_size; struct vdoa_params param; struct vdoa_ipu_buf buf; struct ipu_soc *ipu_idx; u32 ipu_stride, obuf_size; u32 height, width; ipu_buffer_t type; if ((IPU_PIX_FMT_YUYV != t->output.format) && (IPU_PIX_FMT_NV12 != t->output.format)) { dev_err(t->dev, "ERR:[0x%d] output format\n", t->task_no); return -EINVAL; } memset(¶m, 0, sizeof(param)); /* init channel tiled bufs */ if (deinterlace_3_field(t) && (IPU_PIX_FMT_TILED_NV12F == t->input.format)) { field_size = tiled_filed_size(t); if (INPUT_CHAN_VDI_P == ch_type) { inbuf_base = t->input.paddr + field_size; param.vfield_buf.prev_veba = inbuf_base + t->set.i_off; } else if (INPUT_CHAN == ch_type) { inbuf_base = t->input.paddr_n; param.vfield_buf.cur_veba = inbuf_base + t->set.i_off; } else if (INPUT_CHAN_VDI_N == ch_type) { inbuf_base = t->input.paddr_n + field_size; param.vfield_buf.next_veba = inbuf_base + t->set.i_off; } else return -EINVAL; height = t->input.crop.h >> 1; /* field format for vdoa */ width = t->input.crop.w; param.vfield_buf.vubo = t->set.i_uoff; param.interlaced = 1; param.scan_order = 1; type = IPU_INPUT_BUFFER; } else if ((IPU_PIX_FMT_TILED_NV12 == t->input.format) && (INPUT_CHAN == ch_type)) { height = t->input.crop.h; width = t->input.crop.w; param.vframe_buf.veba = t->input.paddr + t->set.i_off; param.vframe_buf.vubo = t->set.i_uoff; type = IPU_INPUT_BUFFER; } else return -EINVAL; param.band_mode = (t->set.mode & VDOA_BAND_MODE) ? 1 : 0; if (param.band_mode && (t->set.band_lines != 3) && (t->set.band_lines != 4) && (t->set.band_lines != 5)) return -EINVAL; else if (param.band_mode) param.band_lines = (1 << t->set.band_lines); for (i = 0; i < max_ipu_no; i++) { ipu_idx = ipu_get_soc(i); if (!IS_ERR(ipu_idx) && ipu_idx == ipu) break; } if (t->set.task & VDOA_ONLY) /* dummy, didn't need ipu res */ i = 0; if (max_ipu_no == i) { dev_err(t->dev, "ERR:[0x%p] get ipu num\n", t); return -EINVAL; } param.ipu_num = i; param.vpu_stride = t->input.width; param.height = height; param.width = width; if (IPU_PIX_FMT_NV12 == t->output.format) param.pfs = VDOA_PFS_NV12; else param.pfs = VDOA_PFS_YUYV; ipu_fmt = (param.pfs == VDOA_PFS_YUYV) ? IPU_PIX_FMT_YUYV : IPU_PIX_FMT_NV12; ipu_stride = param.width * bytes_per_pixel(ipu_fmt); obuf_size = PAGE_ALIGN(param.width * param.height * fmt_to_bpp(ipu_fmt)/8); dev_dbg(t->dev, "band_mode:%d, band_lines:%d\n", param.band_mode, param.band_lines); if (!param.band_mode) { /* note: if only for tiled -> raster convert and no other post-processing, we don't need alloc buf and use output buffer directly. */ if (t->set.task & VDOA_ONLY) param.ieba0 = t->output.paddr; else { dev_err(t->dev, "ERR:[0x%d] vdoa task\n", t->task_no); return -EINVAL; } } else { if (IPU_PIX_FMT_TILED_NV12F != t->input.format) { dev_err(t->dev, "ERR [0x%d] vdoa task\n", t->task_no); return -EINVAL; } } ret = vdoa_setup(t->vdoa_handle, ¶m); if (ret) goto done; vdoa_get_output_buf(t->vdoa_handle, &buf); if (t->set.task & VDOA_ONLY) goto done; ret = ipu_init_channel_buffer(ipu, channel, type, ipu_fmt, width, height, ipu_stride, IPU_ROTATE_NONE, buf.ieba0, buf.ieba1, 0, buf.iubo, 0); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } if (param.band_mode) { ret = ipu_set_channel_bandmode(ipu, channel, type, t->set.band_lines); if (ret < 0) { t->state = STATE_INIT_CHAN_BAND_FAIL; goto done; } } done: return ret; } static int init_tiled_ch_bufs(struct ipu_soc *ipu, struct ipu_task_entry *t) { int ret = 0; if (IPU_PIX_FMT_TILED_NV12 == t->input.format) { ret = init_tiled_buf(ipu, t, t->set.ic_chan, INPUT_CHAN); CHECK_RETCODE(ret < 0, "init tiled_ch", t->state, done, ret); } else if (IPU_PIX_FMT_TILED_NV12F == t->input.format) { ret = init_tiled_buf(ipu, t, t->set.ic_chan, INPUT_CHAN); CHECK_RETCODE(ret < 0, "init tiled_ch-c", t->state, done, ret); ret = init_tiled_buf(ipu, t, t->set.vdi_ic_p_chan, INPUT_CHAN_VDI_P); CHECK_RETCODE(ret < 0, "init tiled_ch-p", t->state, done, ret); ret = init_tiled_buf(ipu, t, t->set.vdi_ic_n_chan, INPUT_CHAN_VDI_N); CHECK_RETCODE(ret < 0, "init tiled_ch-n", t->state, done, ret); } else { ret = -EINVAL; dev_err(t->dev, "ERR[no-0x%x] invalid fmt:0x%x!\n", t->task_no, t->input.format); } done: return ret; } static int init_ic(struct ipu_soc *ipu, struct ipu_task_entry *t) { int ret = 0; ipu_channel_params_t params; dma_addr_t inbuf = 0, ovbuf = 0, ov_alp_buf = 0; dma_addr_t inbuf_p = 0, inbuf_n = 0; dma_addr_t outbuf = 0; int out_uoff = 0, out_voff = 0, out_rot; int out_w = 0, out_h = 0, out_stride; int out_fmt; u32 vdi_frame_idx = 0; memset(¶ms, 0, sizeof(params)); /* is it need link a rot channel */ if (ic_and_rot(t->set.mode)) { outbuf = t->set.r_paddr; out_w = t->set.r_width; out_h = t->set.r_height; out_stride = t->set.r_stride; out_fmt = t->set.r_fmt; out_uoff = 0; out_voff = 0; out_rot = IPU_ROTATE_NONE; } else { outbuf = t->output.paddr + t->set.o_off; out_w = t->output.crop.w; out_h = t->output.crop.h; out_stride = t->set.ostride; out_fmt = t->output.format; out_uoff = t->set.o_uoff; out_voff = t->set.o_voff; out_rot = t->output.rotate; } /* settings */ params.mem_prp_vf_mem.in_width = t->input.crop.w; params.mem_prp_vf_mem.out_width = out_w; params.mem_prp_vf_mem.in_height = t->input.crop.h; params.mem_prp_vf_mem.out_height = out_h; params.mem_prp_vf_mem.in_pixel_fmt = t->input.format; params.mem_prp_vf_mem.out_pixel_fmt = out_fmt; params.mem_prp_vf_mem.motion_sel = t->input.deinterlace.motion; params.mem_prp_vf_mem.outh_resize_ratio = t->set.sp_setting.outh_resize_ratio; params.mem_prp_vf_mem.outv_resize_ratio = t->set.sp_setting.outv_resize_ratio; if (t->overlay_en) { params.mem_prp_vf_mem.in_g_pixel_fmt = t->overlay.format; params.mem_prp_vf_mem.graphics_combine_en = 1; if (t->overlay.alpha.mode == IPU_ALPHA_MODE_GLOBAL) params.mem_prp_vf_mem.global_alpha_en = 1; else if (t->overlay.alpha.loc_alp_paddr) params.mem_prp_vf_mem.alpha_chan_en = 1; /* otherwise, alpha bending per pixel is used. */ params.mem_prp_vf_mem.alpha = t->overlay.alpha.gvalue; if (t->overlay.colorkey.enable) { params.mem_prp_vf_mem.key_color_en = 1; params.mem_prp_vf_mem.key_color = t->overlay.colorkey.value; } } if (t->input.deinterlace.enable) { if (t->input.deinterlace.field_fmt & IPU_DEINTERLACE_FIELD_MASK) params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_BOTTOM; else params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_TOP; if (t->input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_EN) vdi_frame_idx = t->input.deinterlace.field_fmt & IPU_DEINTERLACE_RATE_FRAME1; } if (t->set.mode & VDOA_MODE) ipu->vdoa_en = 1; /* init channels */ if (!(t->set.task & VDOA_ONLY)) { ret = ipu_init_channel(ipu, t->set.ic_chan, ¶ms); if (ret < 0) { t->state = STATE_INIT_CHAN_FAIL; goto done; } } if (deinterlace_3_field(t)) { ret = ipu_init_channel(ipu, t->set.vdi_ic_p_chan, ¶ms); if (ret < 0) { t->state = STATE_INIT_CHAN_FAIL; goto done; } ret = ipu_init_channel(ipu, t->set.vdi_ic_n_chan, ¶ms); if (ret < 0) { t->state = STATE_INIT_CHAN_FAIL; goto done; } } /* init channel bufs */ if ((IPU_PIX_FMT_TILED_NV12 == t->input.format) || (IPU_PIX_FMT_TILED_NV12F == t->input.format)) { ret = init_tiled_ch_bufs(ipu, t); if (ret < 0) goto done; } else { if ((deinterlace_3_field(t)) && (IPU_PIX_FMT_TILED_NV12F != t->input.format)) { if (params.mem_prp_vf_mem.field_fmt == IPU_DEINTERLACE_FIELD_TOP) { if (vdi_frame_idx) { inbuf_p = t->input.paddr + t->set.istride + t->set.i_off; inbuf = t->input.paddr_n + t->set.i_off; inbuf_n = t->input.paddr_n + t->set.istride + t->set.i_off; params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_BOTTOM; } else { inbuf_p = t->input.paddr + t->set.i_off; inbuf = t->input.paddr + t->set.istride + t->set.i_off; inbuf_n = t->input.paddr_n + t->set.i_off; } } else { if (vdi_frame_idx) { inbuf_p = t->input.paddr + t->set.i_off; inbuf = t->input.paddr_n + t->set.istride + t->set.i_off; inbuf_n = t->input.paddr_n + t->set.i_off; params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_TOP; } else { inbuf_p = t->input.paddr + t->set.istride + t->set.i_off; inbuf = t->input.paddr + t->set.i_off; inbuf_n = t->input.paddr_n + t->set.istride + t->set.i_off; } } } else { if (t->input.deinterlace.enable) { if (params.mem_prp_vf_mem.field_fmt == IPU_DEINTERLACE_FIELD_TOP) { if (vdi_frame_idx) { inbuf = t->input.paddr + t->set.istride + t->set.i_off; params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_BOTTOM; } else inbuf = t->input.paddr + t->set.i_off; } else { if (vdi_frame_idx) { inbuf = t->input.paddr + t->set.i_off; params.mem_prp_vf_mem.field_fmt = IPU_DEINTERLACE_FIELD_TOP; } else inbuf = t->input.paddr + t->set.istride + t->set.i_off; } } else inbuf = t->input.paddr + t->set.i_off; } if (t->overlay_en) ovbuf = t->overlay.paddr + t->set.ov_off; } if (t->overlay_en && (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL)) ov_alp_buf = t->overlay.alpha.loc_alp_paddr + t->set.ov_alpha_off; if ((IPU_PIX_FMT_TILED_NV12 != t->input.format) && (IPU_PIX_FMT_TILED_NV12F != t->input.format)) { ret = ipu_init_channel_buffer(ipu, t->set.ic_chan, IPU_INPUT_BUFFER, t->input.format, t->input.crop.w, t->input.crop.h, t->set.istride, IPU_ROTATE_NONE, inbuf, 0, 0, t->set.i_uoff, t->set.i_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } } if (deinterlace_3_field(t) && (IPU_PIX_FMT_TILED_NV12F != t->input.format)) { ret = ipu_init_channel_buffer(ipu, t->set.vdi_ic_p_chan, IPU_INPUT_BUFFER, t->input.format, t->input.crop.w, t->input.crop.h, t->set.istride, IPU_ROTATE_NONE, inbuf_p, 0, 0, t->set.i_uoff, t->set.i_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } ret = ipu_init_channel_buffer(ipu, t->set.vdi_ic_n_chan, IPU_INPUT_BUFFER, t->input.format, t->input.crop.w, t->input.crop.h, t->set.istride, IPU_ROTATE_NONE, inbuf_n, 0, 0, t->set.i_uoff, t->set.i_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } } if (t->overlay_en) { ret = ipu_init_channel_buffer(ipu, t->set.ic_chan, IPU_GRAPH_IN_BUFFER, t->overlay.format, t->overlay.crop.w, t->overlay.crop.h, t->set.ovstride, IPU_ROTATE_NONE, ovbuf, 0, 0, t->set.ov_uoff, t->set.ov_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } } if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) { ret = ipu_init_channel_buffer(ipu, t->set.ic_chan, IPU_ALPHA_IN_BUFFER, IPU_PIX_FMT_GENERIC, t->overlay.crop.w, t->overlay.crop.h, t->set.ov_alpha_stride, IPU_ROTATE_NONE, ov_alp_buf, 0, 0, 0, 0); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } } if (!(t->set.task & VDOA_ONLY)) { ret = ipu_init_channel_buffer(ipu, t->set.ic_chan, IPU_OUTPUT_BUFFER, out_fmt, out_w, out_h, out_stride, out_rot, outbuf, 0, 0, out_uoff, out_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } } if ((t->set.mode & VDOA_BAND_MODE) && (t->set.task & VDI_VF)) { ret = ipu_link_channels(ipu, MEM_VDOA_MEM, t->set.ic_chan); CHECK_RETCODE(ret < 0, "ipu_link_ch vdoa_ic", STATE_LINK_CHAN_FAIL, done, ret); } done: return ret; } static void uninit_ic(struct ipu_soc *ipu, struct ipu_task_entry *t) { int ret; if ((t->set.mode & VDOA_BAND_MODE) && (t->set.task & VDI_VF)) { ret = ipu_unlink_channels(ipu, MEM_VDOA_MEM, t->set.ic_chan); CHECK_RETCODE_CONT(ret < 0, "ipu_unlink_ch vdoa_ic", STATE_UNLINK_CHAN_FAIL, ret); } ipu_uninit_channel(ipu, t->set.ic_chan); if (deinterlace_3_field(t)) { ipu_uninit_channel(ipu, t->set.vdi_ic_p_chan); ipu_uninit_channel(ipu, t->set.vdi_ic_n_chan); } } static int init_rot(struct ipu_soc *ipu, struct ipu_task_entry *t) { int ret = 0; dma_addr_t inbuf = 0, outbuf = 0; int in_uoff = 0, in_voff = 0; int in_fmt, in_width, in_height, in_stride; /* init channel */ ret = ipu_init_channel(ipu, t->set.rot_chan, NULL); if (ret < 0) { t->state = STATE_INIT_CHAN_FAIL; goto done; } /* init channel buf */ /* is it need link to a ic channel */ if (ic_and_rot(t->set.mode)) { in_fmt = t->set.r_fmt; in_width = t->set.r_width; in_height = t->set.r_height; in_stride = t->set.r_stride; inbuf = t->set.r_paddr; in_uoff = 0; in_voff = 0; } else { in_fmt = t->input.format; in_width = t->input.crop.w; in_height = t->input.crop.h; in_stride = t->set.istride; inbuf = t->input.paddr + t->set.i_off; in_uoff = t->set.i_uoff; in_voff = t->set.i_voff; } outbuf = t->output.paddr + t->set.o_off; ret = ipu_init_channel_buffer(ipu, t->set.rot_chan, IPU_INPUT_BUFFER, in_fmt, in_width, in_height, in_stride, t->output.rotate, inbuf, 0, 0, in_uoff, in_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } ret = ipu_init_channel_buffer(ipu, t->set.rot_chan, IPU_OUTPUT_BUFFER, t->output.format, t->output.crop.w, t->output.crop.h, t->set.ostride, IPU_ROTATE_NONE, outbuf, 0, 0, t->set.o_uoff, t->set.o_voff); if (ret < 0) { t->state = STATE_INIT_CHAN_BUF_FAIL; goto done; } done: return ret; } static void uninit_rot(struct ipu_soc *ipu, struct ipu_task_entry *t) { ipu_uninit_channel(ipu, t->set.rot_chan); } static int get_irq(struct ipu_task_entry *t) { int irq; ipu_channel_t chan; if (only_ic(t->set.mode)) chan = t->set.ic_chan; else chan = t->set.rot_chan; switch (chan) { case MEM_ROT_VF_MEM: irq = IPU_IRQ_PRP_VF_ROT_OUT_EOF; break; case MEM_ROT_PP_MEM: irq = IPU_IRQ_PP_ROT_OUT_EOF; break; case MEM_VDI_PRP_VF_MEM: case MEM_PRP_VF_MEM: irq = IPU_IRQ_PRP_VF_OUT_EOF; break; case MEM_PP_MEM: irq = IPU_IRQ_PP_OUT_EOF; break; case MEM_VDI_MEM: irq = IPU_IRQ_VDIC_OUT_EOF; break; default: irq = -EINVAL; } return irq; } static irqreturn_t task_irq_handler(int irq, void *dev_id) { struct ipu_task_entry *prev_tsk = dev_id; CHECK_PERF(&prev_tsk->ts_inirq); complete(&prev_tsk->irq_comp); dev_dbg(prev_tsk->dev, "[0x%p] no-0x%x in-irq!", prev_tsk, prev_tsk->task_no); return IRQ_HANDLED; } /* Fix deinterlace up&down split mode medium line */ static void vdi_split_process(struct ipu_soc *ipu, struct ipu_task_entry *t) { u32 vdi_size; u32 vdi_save_lines; u32 stripe_mode; u32 task_no; u32 i, offset_addr; u32 line_size; unsigned char *base_off; struct ipu_task_entry *parent = t->parent; struct mutex *lock = &parent->vdic_lock; if (!parent) { dev_err(t->dev, "ERR[0x%x]invalid parent\n", t->task_no); return; } mutex_lock(lock); stripe_mode = t->task_no & 0xf; task_no = t->task_no >> 4; /* Save both luma and chroma part for interleaved YUV(e.g. YUYV). * Save luma part for non-interleaved and partial-interleaved * YUV format (e.g NV12 and YV12). */ if (t->output.format == IPU_PIX_FMT_YUYV || t->output.format == IPU_PIX_FMT_UYVY) line_size = t->output.crop.w * fmt_to_bpp(t->output.format)/8; else line_size = t->output.crop.w; vdi_save_lines = (t->output.crop.h - t->set.sp_setting.ud_split_line)/2; vdi_size = vdi_save_lines * line_size; if (vdi_save_lines <= 0) { dev_err(t->dev, "[0x%p] vdi_save_line error\n", (void *)t); mutex_unlock(lock); return; } /*check vditmpbuf buffer have alloced or buffer size is changed */ if ((vdi_save_lines != parent->old_save_lines) || (vdi_size != parent->old_size)) { if (parent->vditmpbuf[0] != NULL) kfree(parent->vditmpbuf[0]); if (parent->vditmpbuf[1] != NULL) kfree(parent->vditmpbuf[1]); parent->vditmpbuf[0] = kmalloc(vdi_size, GFP_KERNEL); if (parent->vditmpbuf[0] == NULL) { dev_err(t->dev, "[0x%p]Falied Alloc vditmpbuf[0]\n", (void *)t); mutex_unlock(lock); return; } memset(parent->vditmpbuf[0], 0, vdi_size); parent->vditmpbuf[1] = kmalloc(vdi_size, GFP_KERNEL); if (parent->vditmpbuf[1] == NULL) { dev_err(t->dev, "[0x%p]Falied Alloc vditmpbuf[1]\n", (void *)t); mutex_unlock(lock); return; } memset(parent->vditmpbuf[1], 0, vdi_size); parent->old_save_lines = vdi_save_lines; parent->old_size = vdi_size; } if (pfn_valid(t->output.paddr >> PAGE_SHIFT)) { base_off = page_address(pfn_to_page(t->output.paddr >> PAGE_SHIFT)); base_off += t->output.paddr & ((1 << PAGE_SHIFT) - 1); } else { base_off = (char *)ioremap_nocache(t->output.paddr, t->output.width * t->output.height * fmt_to_bpp(t->output.format)/8); } if (base_off == NULL) { dev_err(t->dev, "ERR[0x%p]Failed get virtual address\n", t); mutex_unlock(lock); return; } /* UP stripe or UP&LEFT stripe */ if ((stripe_mode == UP_STRIPE) || (stripe_mode == (UP_STRIPE | LEFT_STRIPE))) { if (!parent->buf0filled) { offset_addr = t->set.o_off + t->set.sp_setting.ud_split_line*t->set.ostride; dmac_flush_range(base_off + offset_addr, base_off + offset_addr + vdi_size); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + vdi_size); for (i = 0; i < vdi_save_lines; i++) memcpy(parent->vditmpbuf[0] + i*line_size, base_off + offset_addr + i*t->set.ostride, line_size); parent->buf0filled = true; } else { offset_addr = t->set.o_off + (t->output.crop.h - vdi_save_lines) * t->set.ostride; for (i = 0; i < vdi_save_lines; i++) memcpy(base_off + offset_addr + i*t->set.ostride, parent->vditmpbuf[0] + i*line_size, line_size); dmac_flush_range(base_off + offset_addr, base_off + offset_addr + i*t->set.ostride); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + i*t->set.ostride); parent->buf0filled = false; } } /*Down stripe or Down&Left stripe*/ else if ((stripe_mode == DOWN_STRIPE) || (stripe_mode == (DOWN_STRIPE | LEFT_STRIPE))) { if (!parent->buf0filled) { offset_addr = t->set.o_off + vdi_save_lines*t->set.ostride; dmac_flush_range(base_off + offset_addr, base_off + offset_addr + vdi_size); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + vdi_size); for (i = 0; i < vdi_save_lines; i++) memcpy(parent->vditmpbuf[0] + i*line_size, base_off + offset_addr + i*t->set.ostride, line_size); parent->buf0filled = true; } else { offset_addr = t->set.o_off; for (i = 0; i < vdi_save_lines; i++) memcpy(base_off + offset_addr + i*t->set.ostride, parent->vditmpbuf[0] + i*line_size, line_size); dmac_flush_range(base_off + offset_addr, base_off + offset_addr + i*t->set.ostride); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + i*t->set.ostride); parent->buf0filled = false; } } /*Up&Right stripe*/ else if (stripe_mode == (UP_STRIPE | RIGHT_STRIPE)) { if (!parent->buf1filled) { offset_addr = t->set.o_off + t->set.sp_setting.ud_split_line*t->set.ostride; dmac_flush_range(base_off + offset_addr, base_off + offset_addr + vdi_size); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + vdi_size); for (i = 0; i < vdi_save_lines; i++) memcpy(parent->vditmpbuf[1] + i*line_size, base_off + offset_addr + i*t->set.ostride, line_size); parent->buf1filled = true; } else { offset_addr = t->set.o_off + (t->output.crop.h - vdi_save_lines)*t->set.ostride; for (i = 0; i < vdi_save_lines; i++) memcpy(base_off + offset_addr + i*t->set.ostride, parent->vditmpbuf[1] + i*line_size, line_size); dmac_flush_range(base_off + offset_addr, base_off + offset_addr + i*t->set.ostride); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + i*t->set.ostride); parent->buf1filled = false; } } /*Down stripe or Down&Right stript*/ else if (stripe_mode == (DOWN_STRIPE | RIGHT_STRIPE)) { if (!parent->buf1filled) { offset_addr = t->set.o_off + vdi_save_lines*t->set.ostride; dmac_flush_range(base_off + offset_addr, base_off + offset_addr + vdi_save_lines*t->set.ostride); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + vdi_save_lines*t->set.ostride); for (i = 0; i < vdi_save_lines; i++) memcpy(parent->vditmpbuf[1] + i*line_size, base_off + offset_addr + i*t->set.ostride, line_size); parent->buf1filled = true; } else { offset_addr = t->set.o_off; for (i = 0; i < vdi_save_lines; i++) memcpy(base_off + offset_addr + i*t->set.ostride, parent->vditmpbuf[1] + i*line_size, line_size); dmac_flush_range(base_off + offset_addr, base_off + offset_addr + vdi_save_lines*t->set.ostride); outer_flush_range(t->output.paddr + offset_addr, t->output.paddr + offset_addr + vdi_save_lines*t->set.ostride); parent->buf1filled = false; } } if (!pfn_valid(t->output.paddr >> PAGE_SHIFT)) iounmap(base_off); mutex_unlock(lock); } static void do_task_release(struct ipu_task_entry *t, int fail) { int ret; struct ipu_soc *ipu = t->ipu; if (t->input.deinterlace.enable && !fail && (t->task_no & (UP_STRIPE | DOWN_STRIPE))) vdi_split_process(ipu, t); ipu_free_irq(ipu, t->irq, t); if (t->vdoa_dma.vaddr) dma_free_coherent(t->dev, t->vdoa_dma.size, t->vdoa_dma.vaddr, t->vdoa_dma.paddr); if (only_ic(t->set.mode)) { ret = ipu_disable_channel(ipu, t->set.ic_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch only_ic", STATE_DISABLE_CHAN_FAIL, ret); if (deinterlace_3_field(t)) { ret = ipu_disable_channel(ipu, t->set.vdi_ic_p_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch only_ic_p", STATE_DISABLE_CHAN_FAIL, ret); ret = ipu_disable_channel(ipu, t->set.vdi_ic_n_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch only_ic_n", STATE_DISABLE_CHAN_FAIL, ret); } } else if (only_rot(t->set.mode)) { ret = ipu_disable_channel(ipu, t->set.rot_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch only_rot", STATE_DISABLE_CHAN_FAIL, ret); } else if (ic_and_rot(t->set.mode)) { ret = ipu_unlink_channels(ipu, t->set.ic_chan, t->set.rot_chan); CHECK_RETCODE_CONT(ret < 0, "ipu_unlink_ch", STATE_UNLINK_CHAN_FAIL, ret); ret = ipu_disable_channel(ipu, t->set.rot_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch ic_and_rot-rot", STATE_DISABLE_CHAN_FAIL, ret); ret = ipu_disable_channel(ipu, t->set.ic_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch ic_and_rot-ic", STATE_DISABLE_CHAN_FAIL, ret); if (deinterlace_3_field(t)) { ret = ipu_disable_channel(ipu, t->set.vdi_ic_p_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch icrot-ic-p", STATE_DISABLE_CHAN_FAIL, ret); ret = ipu_disable_channel(ipu, t->set.vdi_ic_n_chan, true); CHECK_RETCODE_CONT(ret < 0, "ipu_disable_ch icrot-ic-n", STATE_DISABLE_CHAN_FAIL, ret); } } if (only_ic(t->set.mode)) uninit_ic(ipu, t); else if (only_rot(t->set.mode)) uninit_rot(ipu, t); else if (ic_and_rot(t->set.mode)) { uninit_ic(ipu, t); uninit_rot(ipu, t); } t->state = STATE_OK; CHECK_PERF(&t->ts_rel); return; } static void do_task_vdoa_only(struct ipu_task_entry *t) { int ret; ret = init_tiled_ch_bufs(NULL, t); CHECK_RETCODE(ret < 0, "do_vdoa_only", STATE_ERR, out, ret); ret = vdoa_start(t->vdoa_handle, VDOA_DEF_TIMEOUT_MS); vdoa_stop(t->vdoa_handle); CHECK_RETCODE(ret < 0, "vdoa_wait4complete, do_vdoa_only", STATE_VDOA_IRQ_TIMEOUT, out, ret); t->state = STATE_OK; out: return; } static void do_task(struct ipu_task_entry *t) { int r_size; int irq; int ret; uint32_t busy; struct ipu_soc *ipu = t->ipu; CHECK_PERF(&t->ts_dotask); if (!ipu) { t->state = STATE_NO_IPU; return; } init_completion(&t->irq_comp); dev_dbg(ipu->dev, "[0x%p]Do task no:0x%x: id %d\n", (void *)t, t->task_no, t->task_id); dump_task_info(t); if (t->set.task & IC_PP) { t->set.ic_chan = MEM_PP_MEM; dev_dbg(ipu->dev, "[0x%p]ic channel MEM_PP_MEM\n", (void *)t); } else if (t->set.task & IC_VF) { t->set.ic_chan = MEM_PRP_VF_MEM; dev_dbg(ipu->dev, "[0x%p]ic channel MEM_PRP_VF_MEM\n", (void *)t); } else if (t->set.task & VDI_VF) { if (t->set.mode & VDOA_BAND_MODE) { t->set.ic_chan = MEM_VDI_MEM; if (deinterlace_3_field(t)) { t->set.vdi_ic_p_chan = MEM_VDI_MEM_P; t->set.vdi_ic_n_chan = MEM_VDI_MEM_N; } dev_dbg(ipu->dev, "[0x%p]ic ch MEM_VDI_MEM\n", (void *)t); } else { t->set.ic_chan = MEM_VDI_PRP_VF_MEM; if (deinterlace_3_field(t)) { t->set.vdi_ic_p_chan = MEM_VDI_PRP_VF_MEM_P; t->set.vdi_ic_n_chan = MEM_VDI_PRP_VF_MEM_N; } dev_dbg(ipu->dev, "[0x%p]ic ch MEM_VDI_PRP_VF_MEM\n", t); } } if (t->set.task & ROT_PP) { t->set.rot_chan = MEM_ROT_PP_MEM; dev_dbg(ipu->dev, "[0x%p]rot channel MEM_ROT_PP_MEM\n", (void *)t); } else if (t->set.task & ROT_VF) { t->set.rot_chan = MEM_ROT_VF_MEM; dev_dbg(ipu->dev, "[0x%p]rot channel MEM_ROT_VF_MEM\n", (void *)t); } if (t->task_id == IPU_TASK_ID_VF) busy = ic_vf_pp_is_busy(ipu, true); else if (t->task_id == IPU_TASK_ID_PP) busy = ic_vf_pp_is_busy(ipu, false); else { dev_err(ipu->dev, "ERR[no:0x%x]ipu task_id:%d invalid!\n", t->task_no, t->task_id); return; } if (busy) { dev_err(ipu->dev, "ERR[0x%p-no:0x%x]ipu task_id:%d busy!\n", (void *)t, t->task_no, t->task_id); t->state = STATE_IPU_BUSY; return; } irq = get_irq(t); if (irq < 0) { t->state = STATE_NO_IRQ; return; } t->irq = irq; /* channel setup */ if (only_ic(t->set.mode)) { dev_dbg(t->dev, "[0x%p]only ic mode\n", (void *)t); ret = init_ic(ipu, t); CHECK_RETCODE(ret < 0, "init_ic only_ic", t->state, chan_setup, ret); } else if (only_rot(t->set.mode)) { dev_dbg(t->dev, "[0x%p]only rot mode\n", (void *)t); ret = init_rot(ipu, t); CHECK_RETCODE(ret < 0, "init_rot only_rot", t->state, chan_setup, ret); } else if (ic_and_rot(t->set.mode)) { int rot_idx = (t->task_id == IPU_TASK_ID_VF) ? 0 : 1; dev_dbg(t->dev, "[0x%p]ic + rot mode\n", (void *)t); t->set.r_fmt = t->output.format; if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { t->set.r_width = t->output.crop.h; t->set.r_height = t->output.crop.w; } else { t->set.r_width = t->output.crop.w; t->set.r_height = t->output.crop.h; } t->set.r_stride = t->set.r_width * bytes_per_pixel(t->set.r_fmt); r_size = PAGE_ALIGN(t->set.r_width * t->set.r_height * fmt_to_bpp(t->set.r_fmt)/8); if (r_size > ipu->rot_dma[rot_idx].size) { dev_dbg(t->dev, "[0x%p]realloc rot buffer\n", (void *)t); if (ipu->rot_dma[rot_idx].vaddr) dma_free_coherent(t->dev, ipu->rot_dma[rot_idx].size, ipu->rot_dma[rot_idx].vaddr, ipu->rot_dma[rot_idx].paddr); ipu->rot_dma[rot_idx].size = r_size; ipu->rot_dma[rot_idx].vaddr = dma_alloc_coherent(t->dev, r_size, &ipu->rot_dma[rot_idx].paddr, GFP_DMA | GFP_KERNEL); CHECK_RETCODE(ipu->rot_dma[rot_idx].vaddr == NULL, "ic_and_rot", STATE_SYS_NO_MEM, chan_setup, -ENOMEM); } t->set.r_paddr = ipu->rot_dma[rot_idx].paddr; dev_dbg(t->dev, "[0x%p]rotation:\n", (void *)t); dev_dbg(t->dev, "[0x%p]\tformat = 0x%x\n", (void *)t, t->set.r_fmt); dev_dbg(t->dev, "[0x%p]\twidth = %d\n", (void *)t, t->set.r_width); dev_dbg(t->dev, "[0x%p]\theight = %d\n", (void *)t, t->set.r_height); dev_dbg(t->dev, "[0x%p]\tpaddr = 0x%x\n", (void *)t, t->set.r_paddr); dev_dbg(t->dev, "[0x%p]\trstride = %d\n", (void *)t, t->set.r_stride); ret = init_ic(ipu, t); CHECK_RETCODE(ret < 0, "init_ic ic_and_rot", t->state, chan_setup, ret); ret = init_rot(ipu, t); CHECK_RETCODE(ret < 0, "init_rot ic_and_rot", t->state, chan_setup, ret); ret = ipu_link_channels(ipu, t->set.ic_chan, t->set.rot_chan); CHECK_RETCODE(ret < 0, "ipu_link_ch ic_and_rot", STATE_LINK_CHAN_FAIL, chan_setup, ret); } else { dev_err(t->dev, "ERR [0x%p]do task: should not be here\n", t); t->state = STATE_ERR; return; } ret = ipu_request_irq(ipu, irq, task_irq_handler, 0, NULL, t); CHECK_RETCODE(ret < 0, "ipu_req_irq", STATE_IRQ_FAIL, chan_setup, ret); /* enable/start channel */ if (only_ic(t->set.mode)) { ret = ipu_enable_channel(ipu, t->set.ic_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch only_ic", STATE_ENABLE_CHAN_FAIL, chan_en, ret); if (deinterlace_3_field(t)) { ret = ipu_enable_channel(ipu, t->set.vdi_ic_p_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch only_ic_p", STATE_ENABLE_CHAN_FAIL, chan_en, ret); ret = ipu_enable_channel(ipu, t->set.vdi_ic_n_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch only_ic_n", STATE_ENABLE_CHAN_FAIL, chan_en, ret); } ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_OUTPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_ic", STATE_SEL_BUF_FAIL, chan_buf, ret); if (t->overlay_en) { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_GRAPH_IN_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_ic_g", STATE_SEL_BUF_FAIL, chan_buf, ret); if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_ALPHA_IN_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_ic_a", STATE_SEL_BUF_FAIL, chan_buf, ret); } } if (!(t->set.mode & VDOA_BAND_MODE)) { if (deinterlace_3_field(t)) ipu_select_multi_vdi_buffer(ipu, 0); else { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_INPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_ic_i", STATE_SEL_BUF_FAIL, chan_buf, ret); } } } else if (only_rot(t->set.mode)) { ret = ipu_enable_channel(ipu, t->set.rot_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch only_rot", STATE_ENABLE_CHAN_FAIL, chan_en, ret); ret = ipu_select_buffer(ipu, t->set.rot_chan, IPU_OUTPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_rot_o", STATE_SEL_BUF_FAIL, chan_buf, ret); ret = ipu_select_buffer(ipu, t->set.rot_chan, IPU_INPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf only_rot_i", STATE_SEL_BUF_FAIL, chan_buf, ret); } else if (ic_and_rot(t->set.mode)) { ret = ipu_enable_channel(ipu, t->set.rot_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch ic_and_rot-rot", STATE_ENABLE_CHAN_FAIL, chan_en, ret); ret = ipu_enable_channel(ipu, t->set.ic_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch ic_and_rot-ic", STATE_ENABLE_CHAN_FAIL, chan_en, ret); if (deinterlace_3_field(t)) { ret = ipu_enable_channel(ipu, t->set.vdi_ic_p_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch ic_and_rot-p", STATE_ENABLE_CHAN_FAIL, chan_en, ret); ret = ipu_enable_channel(ipu, t->set.vdi_ic_n_chan); CHECK_RETCODE(ret < 0, "ipu_enable_ch ic_and_rot-n", STATE_ENABLE_CHAN_FAIL, chan_en, ret); } ret = ipu_select_buffer(ipu, t->set.rot_chan, IPU_OUTPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf ic_and_rot-rot-o", STATE_SEL_BUF_FAIL, chan_buf, ret); if (t->overlay_en) { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_GRAPH_IN_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf ic_and_rot-ic-g", STATE_SEL_BUF_FAIL, chan_buf, ret); if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_ALPHA_IN_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf icrot-ic-a", STATE_SEL_BUF_FAIL, chan_buf, ret); } } ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_OUTPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf ic_and_rot-ic-o", STATE_SEL_BUF_FAIL, chan_buf, ret); if (deinterlace_3_field(t)) ipu_select_multi_vdi_buffer(ipu, 0); else { ret = ipu_select_buffer(ipu, t->set.ic_chan, IPU_INPUT_BUFFER, 0); CHECK_RETCODE(ret < 0, "ipu_sel_buf ic_and_rot-ic-i", STATE_SEL_BUF_FAIL, chan_buf, ret); } } if (need_split(t)) t->state = STATE_IN_PROGRESS; if (t->set.mode & VDOA_BAND_MODE) { ret = vdoa_start(t->vdoa_handle, VDOA_DEF_TIMEOUT_MS); CHECK_RETCODE(ret < 0, "vdoa_wait4complete, do_vdoa_band", STATE_VDOA_IRQ_TIMEOUT, chan_rel, ret); } CHECK_PERF(&t->ts_waitirq); ret = wait_for_completion_timeout(&t->irq_comp, msecs_to_jiffies(t->timeout - DEF_DELAY_MS)); CHECK_PERF(&t->ts_wakeup); CHECK_RETCODE(ret == 0, "wait_for_comp_timeout", STATE_IRQ_TIMEOUT, chan_rel, ret); dev_dbg(t->dev, "[0x%p] no-0x%x ipu irq done!", t, t->task_no); chan_rel: chan_buf: chan_en: chan_setup: if (t->set.mode & VDOA_BAND_MODE) vdoa_stop(t->vdoa_handle); do_task_release(t, t->state >= STATE_ERR); return; } static void do_task_vdoa_vdi(struct ipu_task_entry *t) { int i; int ret; u32 stripe_width; /* FIXME: crop mode not support now */ stripe_width = t->input.width >> 1; t->input.crop.pos.x = 0; t->input.crop.pos.y = 0; t->input.crop.w = stripe_width; t->input.crop.h = t->input.height; t->output.crop.w = stripe_width; t->output.crop.h = t->input.height; for (i = 0; i < 2; i++) { t->input.crop.pos.x = t->input.crop.pos.x + i * stripe_width; t->output.crop.pos.x = t->output.crop.pos.x + i * stripe_width; /* check input */ ret = set_crop(&t->input.crop, t->input.width, t->input.height, t->input.format); if (ret < 0) { ret = STATE_ERR; goto done; } else update_offset(t->input.format, t->input.width, t->input.height, t->input.crop.pos.x, t->input.crop.pos.y, &t->set.i_off, &t->set.i_uoff, &t->set.i_voff, &t->set.istride); dev_dbg(t->dev, "i_off:0x%x, i_uoff:0x%x, istride:%d.\n", t->set.i_off, t->set.i_uoff, t->set.istride); /* check output */ ret = set_crop(&t->output.crop, t->input.width, t->output.height, t->output.format); if (ret < 0) { ret = STATE_ERR; goto done; } else update_offset(t->output.format, t->output.width, t->output.height, t->output.crop.pos.x, t->output.crop.pos.y, &t->set.o_off, &t->set.o_uoff, &t->set.o_voff, &t->set.ostride); dev_dbg(t->dev, "o_off:0x%x, o_uoff:0x%x, ostride:%d.\n", t->set.o_off, t->set.o_uoff, t->set.ostride); do_task(t); } return; done: dev_err(t->dev, "ERR %s set_crop.\n", __func__); t->state = ret; return; } static void get_res_do_task(struct ipu_task_entry *t) { uint32_t found; uint32_t split_child; struct mutex *lock; found = get_vdoa_ipu_res(t); if (!found) { dev_err(t->dev, "ERR:[0x%p] no-0x%x can not get res\n", t, t->task_no); return; } else { if (t->set.task & VDOA_ONLY) do_task_vdoa_only(t); else if ((IPU_PIX_FMT_TILED_NV12F == t->input.format) && (t->set.mode & VDOA_BAND_MODE) && (t->input.crop.w > soc_max_vdi_in_width(t->ipu))) do_task_vdoa_vdi(t); else do_task(t); put_vdoa_ipu_res(t, 0); } if (t->state != STATE_OK) { dev_err(t->dev, "ERR:[0x%p] no-0x%x state: %s\n", t, t->task_no, state_msg[t->state].msg); } split_child = need_split(t) && t->parent; if (split_child) { lock = &t->parent->split_lock; mutex_lock(lock); t->split_done = 1; mutex_unlock(lock); wake_up(&t->parent->split_waitq); } return; } static void wait_split_task_complete(struct ipu_task_entry *parent, struct ipu_split_task *sp_task, uint32_t size) { struct ipu_task_entry *tsk = NULL; int ret = 0, rc; int j, idx = -1; unsigned long flags; struct mutex *lock = &parent->split_lock; int k, busy_vf, busy_pp; struct ipu_soc *ipu; DECLARE_PERF_VAR; for (j = 0; j < size; j++) { rc = wait_event_timeout( parent->split_waitq, sp_task_check_done(sp_task, parent, size, &idx), msecs_to_jiffies(parent->timeout - DEF_DELAY_MS)); if (!rc) { dev_err(parent->dev, "ERR:[0x%p] no-0x%x, split_task timeout,j:%d," "size:%d.\n", parent, parent->task_no, j, size); ret = -ETIMEDOUT; goto out; } else { if (idx < 0) { dev_err(parent->dev, "ERR:[0x%p] no-0x%x, invalid task idx:%d\n", parent, parent->task_no, idx); continue; } tsk = sp_task[idx].child_task; mutex_lock(lock); if (!tsk->split_done || !tsk->ipu) dev_err(tsk->dev, "ERR:no-0x%x,split not done:%d/null ipu:0x%p\n", tsk->task_no, tsk->split_done, tsk->ipu); tsk->split_done = 0; mutex_unlock(lock); dev_dbg(tsk->dev, "[0x%p] no-0x%x sp_tsk[%d] done,state:%d.\n", tsk, tsk->task_no, idx, tsk->state); #ifdef DBG_IPU_PERF CHECK_PERF(&tsk->ts_rel); PRINT_TASK_STATISTICS; #endif } } out: if (ret == -ETIMEDOUT) { /* debug */ for (k = 0; k < max_ipu_no; k++) { ipu = ipu_get_soc(k); if (IS_ERR(ipu)) { dev_err(parent->dev, "no:0x%x, null ipu:%d\n", parent->task_no, k); } else { busy_vf = ic_vf_pp_is_busy(ipu, true); busy_pp = ic_vf_pp_is_busy(ipu, false); dev_err(parent->dev, "ERR:ipu[%d] busy_vf:%d, busy_pp:%d.\n", k, busy_vf, busy_pp); } } for (k = 0; k < size; k++) { tsk = sp_task[k].child_task; if (!tsk) continue; dev_err(parent->dev, "ERR: sp_task[%d][0x%p] no-0x%x done:%d," "state:%s,on_list:%d, ipu:0x%p,timeout!\n", k, tsk, tsk->task_no, tsk->split_done, state_msg[tsk->state].msg, tsk->task_in_list, tsk->ipu); } } for (j = 0; j < size; j++) { tsk = sp_task[j].child_task; if (!tsk) continue; spin_lock_irqsave(&ipu_task_list_lock, flags); if (tsk->task_in_list) { list_del(&tsk->node); tsk->task_in_list = 0; dev_dbg(tsk->dev, "[0x%p] no-0x%x,id:%d sp_tsk timeout list_del.\n", tsk, tsk->task_no, tsk->task_id); } spin_unlock_irqrestore(&ipu_task_list_lock, flags); if (!tsk->ipu) continue; if (tsk->state != STATE_OK) { dev_err(tsk->dev, "ERR:[0x%p] no-0x%x,id:%d, sp_tsk state: %s\n", tsk, tsk->task_no, tsk->task_id, state_msg[tsk->state].msg); } kref_put(&tsk->refcount, task_mem_free); } kfree(parent->vditmpbuf[0]); kfree(parent->vditmpbuf[1]); if (ret < 0) parent->state = STATE_TIMEOUT; else parent->state = STATE_OK; return; } static inline int find_task(struct ipu_task_entry **t, int thread_id) { int found; unsigned long flags; struct ipu_task_entry *tsk; struct list_head *task_list = &ipu_task_list; *t = NULL; spin_lock_irqsave(&ipu_task_list_lock, flags); found = !list_empty(task_list); if (found) { tsk = list_first_entry(task_list, struct ipu_task_entry, node); if (tsk->task_in_list) { list_del(&tsk->node); tsk->task_in_list = 0; *t = tsk; kref_get(&tsk->refcount); dev_dbg(tsk->dev, "thread_id:%d,[0x%p] task_no:0x%x,mode:0x%x list_del\n", thread_id, tsk, tsk->task_no, tsk->set.mode); } else dev_err(tsk->dev, "thread_id:%d,task_no:0x%x,mode:0x%x not on list_del\n", thread_id, tsk->task_no, tsk->set.mode); } spin_unlock_irqrestore(&ipu_task_list_lock, flags); return found; } static int ipu_task_thread(void *argv) { struct ipu_task_entry *tsk; struct ipu_task_entry *sp_tsk0; struct ipu_split_task sp_task[4]; /* priority lower than irq_thread */ const struct sched_param param = { .sched_priority = MAX_USER_RT_PRIO/2 - 1, }; int ret; int curr_thread_id; uint32_t size; unsigned long flags; unsigned int cpu; struct cpumask cpu_mask; struct ipu_thread_data *data = (struct ipu_thread_data *)argv; thread_id++; curr_thread_id = thread_id; sched_setscheduler(current, SCHED_FIFO, ¶m); if (!data->is_vdoa) { cpu = cpumask_first(cpu_online_mask); cpumask_set_cpu(cpu, &cpu_mask); ret = sched_setaffinity(data->ipu->thread[data->id]->pid, &cpu_mask); if (ret < 0) { pr_err("%s: sched_setaffinity fail:%d.\n", __func__, ret); } pr_debug("%s: sched_setaffinity cpu:%d.\n", __func__, cpu); } while (!kthread_should_stop()) { int split_fail = 0; int split_parent; int split_child; wait_event_interruptible(thread_waitq, find_task(&tsk, curr_thread_id)); if (!tsk) { pr_err("thread:%d can not find task.\n", curr_thread_id); continue; } /* note: other threads run split child task */ split_parent = need_split(tsk) && !tsk->parent; split_child = need_split(tsk) && tsk->parent; if (split_parent) { if ((tsk->set.split_mode == RL_SPLIT) || (tsk->set.split_mode == UD_SPLIT)) size = 2; else size = 4; ret = queue_split_task(tsk, sp_task, size); if (ret < 0) { split_fail = 1; } else { struct list_head *pos; spin_lock_irqsave(&ipu_task_list_lock, flags); sp_tsk0 = list_first_entry(&tsk->split_list, struct ipu_task_entry, node); list_del(&sp_tsk0->node); list_for_each(pos, &tsk->split_list) { struct ipu_task_entry *tmp; tmp = list_entry(pos, struct ipu_task_entry, node); tmp->task_in_list = 1; dev_dbg(tmp->dev, "[0x%p] no-0x%x,id:%d sp_tsk " "add_to_list.\n", tmp, tmp->task_no, tmp->task_id); } /* add to global list */ list_splice(&tsk->split_list, &ipu_task_list); spin_unlock_irqrestore(&ipu_task_list_lock, flags); /* let the parent thread do the first sp_task */ /* FIXME: ensure the correct sequence for split 4size: 5/6->9/a*/ wake_up_interruptible(&thread_waitq); get_res_do_task(sp_tsk0); dev_dbg(sp_tsk0->dev, "thread:%d complete tsk no:0x%x.\n", curr_thread_id, sp_tsk0->task_no); ret = atomic_read(&req_cnt); if (ret > 0) { wake_up(&res_waitq); dev_dbg(sp_tsk0->dev, "sp_tsk0 sche thread:%d no:0x%x," "req_cnt:%d\n", curr_thread_id, sp_tsk0->task_no, ret); /* For other threads to get_res */ schedule(); } } } else get_res_do_task(tsk); /* wait for all 4 sp_task finished here or timeout and then release all resources */ if (split_parent && !split_fail) wait_split_task_complete(tsk, sp_task, size); if (!split_child) { atomic_inc(&tsk->done); wake_up(&tsk->task_waitq); } dev_dbg(tsk->dev, "thread:%d complete tsk no:0x%x-[0x%p].\n", curr_thread_id, tsk->task_no, tsk); ret = atomic_read(&req_cnt); if (ret > 0) { wake_up(&res_waitq); dev_dbg(tsk->dev, "sche thread:%d no:0x%x,req_cnt:%d\n", curr_thread_id, tsk->task_no, ret); /* note: give cpu to other threads to get_res */ schedule(); } kref_put(&tsk->refcount, task_mem_free); } pr_info("ERR %s exit.\n", __func__); return 0; } int ipu_check_task(struct ipu_task *task) { struct ipu_task_entry *tsk; int ret = 0; tsk = create_task_entry(task); if (IS_ERR(tsk)) return PTR_ERR(tsk); ret = check_task(tsk); task->input = tsk->input; task->output = tsk->output; task->overlay = tsk->overlay; dump_task_info(tsk); kref_put(&tsk->refcount, task_mem_free); if (ret != 0) pr_debug("%s ret:%d.\n", __func__, ret); return ret; } EXPORT_SYMBOL_GPL(ipu_check_task); int ipu_queue_task(struct ipu_task *task) { struct ipu_task_entry *tsk; unsigned long flags; int ret; u32 tmp_task_no; DECLARE_PERF_VAR; tsk = create_task_entry(task); if (IS_ERR(tsk)) return PTR_ERR(tsk); CHECK_PERF(&tsk->ts_queue); ret = prepare_task(tsk); if (ret < 0) goto done; if (need_split(tsk)) { CHECK_PERF(&tsk->ts_dotask); CHECK_PERF(&tsk->ts_waitirq); CHECK_PERF(&tsk->ts_inirq); CHECK_PERF(&tsk->ts_wakeup); } /* task_no last four bits for split task type*/ tmp_task_no = atomic_inc_return(&frame_no); tsk->task_no = tmp_task_no << 4; init_waitqueue_head(&tsk->task_waitq); spin_lock_irqsave(&ipu_task_list_lock, flags); list_add_tail(&tsk->node, &ipu_task_list); tsk->task_in_list = 1; dev_dbg(tsk->dev, "[0x%p,no-0x%x] list_add_tail\n", tsk, tsk->task_no); spin_unlock_irqrestore(&ipu_task_list_lock, flags); wake_up_interruptible(&thread_waitq); ret = wait_event_timeout(tsk->task_waitq, atomic_read(&tsk->done), msecs_to_jiffies(tsk->timeout)); if (0 == ret) { /* note: the timeout should larger than the internal timeout!*/ ret = -ETIMEDOUT; dev_err(tsk->dev, "ERR: [0x%p] no-0x%x, timeout:%dms!\n", tsk, tsk->task_no, tsk->timeout); } else { if (STATE_OK != tsk->state) { dev_err(tsk->dev, "ERR: [0x%p] no-0x%x,state %d: %s\n", tsk, tsk->task_no, tsk->state, state_msg[tsk->state].msg); ret = -ECANCELED; } else ret = 0; } spin_lock_irqsave(&ipu_task_list_lock, flags); if (tsk->task_in_list) { list_del(&tsk->node); tsk->task_in_list = 0; dev_dbg(tsk->dev, "[0x%p] no:0x%x list_del\n", tsk, tsk->task_no); } spin_unlock_irqrestore(&ipu_task_list_lock, flags); #ifdef DBG_IPU_PERF CHECK_PERF(&tsk->ts_rel); PRINT_TASK_STATISTICS; if (ts_frame_avg == 0) ts_frame_avg = ts_frame.tv_nsec / NSEC_PER_USEC + ts_frame.tv_sec * USEC_PER_SEC; else ts_frame_avg = (ts_frame_avg + ts_frame.tv_nsec / NSEC_PER_USEC + ts_frame.tv_sec * USEC_PER_SEC)/2; if (timespec_compare(&ts_frame, &ts_frame_max) > 0) ts_frame_max = ts_frame; atomic_inc(&frame_cnt); if ((atomic_read(&frame_cnt) % 1000) == 0) pr_debug("ipu_dev: max frame time:%ldus, avg frame time:%dus," "frame_cnt:%d\n", ts_frame_max.tv_nsec / NSEC_PER_USEC + ts_frame_max.tv_sec * USEC_PER_SEC, ts_frame_avg, atomic_read(&frame_cnt)); #endif done: if (ret < 0) dev_err(tsk->dev, "ERR: no-0x%x,ipu_queue_task err:%d\n", tsk->task_no, ret); kref_put(&tsk->refcount, task_mem_free); return ret; } EXPORT_SYMBOL_GPL(ipu_queue_task); static int mxc_ipu_open(struct inode *inode, struct file *file) { file->private_data = (void *)atomic_inc_return(&file_index); return 0; } static long mxc_ipu_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int __user *argp = (void __user *)arg; int ret = 0; switch (cmd) { case IPU_CHECK_TASK: { struct ipu_task task; if (copy_from_user (&task, (struct ipu_task *) arg, sizeof(struct ipu_task))) return -EFAULT; ret = ipu_check_task(&task); if (copy_to_user((struct ipu_task *) arg, &task, sizeof(struct ipu_task))) return -EFAULT; break; } case IPU_QUEUE_TASK: { struct ipu_task task; if (copy_from_user (&task, (struct ipu_task *) arg, sizeof(struct ipu_task))) return -EFAULT; ret = ipu_queue_task(&task); break; } case IPU_ALLOC: { int size; struct ipu_alloc_list *mem; mem = kzalloc(sizeof(*mem), GFP_KERNEL); if (mem == NULL) return -ENOMEM; if (get_user(size, argp)) { kfree(mem); return -EFAULT; } mem->size = PAGE_ALIGN(size); mem->cpu_addr = dma_alloc_coherent(ipu_dev, size, &mem->phy_addr, GFP_DMA | GFP_KERNEL); if (mem->cpu_addr == NULL) { kfree(mem); return -ENOMEM; } mem->file_index = file->private_data; mutex_lock(&ipu_alloc_lock); list_add(&mem->list, &ipu_alloc_list); mutex_unlock(&ipu_alloc_lock); if (put_user(mem->phy_addr, argp)) { mutex_lock(&ipu_alloc_lock); list_del(&mem->list); mutex_unlock(&ipu_alloc_lock); dma_free_coherent(ipu_dev, mem->size, mem->cpu_addr, mem->phy_addr); kfree(mem); return -EFAULT; } dev_dbg(ipu_dev, "allocated %d bytes @ 0x%08X\n", mem->size, mem->phy_addr); break; } case IPU_FREE: { unsigned long offset; struct ipu_alloc_list *mem; if (get_user(offset, argp)) return -EFAULT; ret = -EINVAL; mutex_lock(&ipu_alloc_lock); list_for_each_entry(mem, &ipu_alloc_list, list) { if (mem->phy_addr == offset) { list_del(&mem->list); dma_free_coherent(ipu_dev, mem->size, mem->cpu_addr, mem->phy_addr); kfree(mem); ret = 0; break; } } mutex_unlock(&ipu_alloc_lock); if (0 == ret) dev_dbg(ipu_dev, "free %d bytes @ 0x%08X\n", mem->size, mem->phy_addr); break; } default: break; } return ret; } static int mxc_ipu_mmap(struct file *file, struct vm_area_struct *vma) { bool found = false; u32 len; unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; struct ipu_alloc_list *mem; mutex_lock(&ipu_alloc_lock); list_for_each_entry(mem, &ipu_alloc_list, list) { if (offset == mem->phy_addr) { found = true; len = mem->size; break; } } mutex_unlock(&ipu_alloc_lock); if (!found) return -EINVAL; if (vma->vm_end - vma->vm_start > len) return -EINVAL; vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, vma->vm_end - vma->vm_start, vma->vm_page_prot)) { printk(KERN_ERR "mmap failed!\n"); return -ENOBUFS; } return 0; } static int mxc_ipu_release(struct inode *inode, struct file *file) { struct ipu_alloc_list *mem; struct ipu_alloc_list *n; mutex_lock(&ipu_alloc_lock); list_for_each_entry_safe(mem, n, &ipu_alloc_list, list) { if ((mem->cpu_addr != 0) && (file->private_data == mem->file_index)) { list_del(&mem->list); dma_free_coherent(ipu_dev, mem->size, mem->cpu_addr, mem->phy_addr); dev_dbg(ipu_dev, "rel-free %d bytes @ 0x%08X\n", mem->size, mem->phy_addr); kfree(mem); } } mutex_unlock(&ipu_alloc_lock); atomic_dec(&file_index); return 0; } static struct file_operations mxc_ipu_fops = { .owner = THIS_MODULE, .open = mxc_ipu_open, .mmap = mxc_ipu_mmap, .release = mxc_ipu_release, .unlocked_ioctl = mxc_ipu_ioctl, }; int register_ipu_device(struct ipu_soc *ipu, int id) { int ret = 0; static int idx; static struct ipu_thread_data thread_data[5]; if (!major) { major = register_chrdev(0, "mxc_ipu", &mxc_ipu_fops); if (major < 0) { printk(KERN_ERR "Unable to register mxc_ipu as a char device\n"); ret = major; goto register_cdev_fail; } ipu_class = class_create(THIS_MODULE, "mxc_ipu"); if (IS_ERR(ipu_class)) { ret = PTR_ERR(ipu_class); goto ipu_class_fail; } ipu_dev = device_create(ipu_class, NULL, MKDEV(major, 0), NULL, "mxc_ipu"); if (IS_ERR(ipu_dev)) { ret = PTR_ERR(ipu_dev); goto dev_create_fail; } ipu_dev->dma_mask = kmalloc(sizeof(*ipu_dev->dma_mask), GFP_KERNEL); *ipu_dev->dma_mask = DMA_BIT_MASK(32); ipu_dev->coherent_dma_mask = DMA_BIT_MASK(32); mutex_init(&ipu_ch_tbl.lock); } max_ipu_no = ++id; ipu->rot_dma[0].size = 0; ipu->rot_dma[1].size = 0; thread_data[idx].ipu = ipu; thread_data[idx].id = 0; thread_data[idx].is_vdoa = 0; ipu->thread[0] = kthread_run(ipu_task_thread, &thread_data[idx++], "ipu%d_task", id); if (IS_ERR(ipu->thread[0])) { ret = PTR_ERR(ipu->thread[0]); goto kthread0_fail; } thread_data[idx].ipu = ipu; thread_data[idx].id = 1; thread_data[idx].is_vdoa = 0; ipu->thread[1] = kthread_run(ipu_task_thread, &thread_data[idx++], "ipu%d_task", id); if (IS_ERR(ipu->thread[1])) { ret = PTR_ERR(ipu->thread[1]); goto kthread1_fail; } return ret; kthread1_fail: kthread_stop(ipu->thread[0]); kthread0_fail: if (id == 0) device_destroy(ipu_class, MKDEV(major, 0)); dev_create_fail: if (id == 0) { class_destroy(ipu_class); } ipu_class_fail: if (id == 0) unregister_chrdev(major, "mxc_ipu"); register_cdev_fail: return ret; } void unregister_ipu_device(struct ipu_soc *ipu, int id) { int i; kthread_stop(ipu->thread[0]); kthread_stop(ipu->thread[1]); for (i = 0; i < 2; i++) { if (ipu->rot_dma[i].vaddr) dma_free_coherent(ipu_dev, ipu->rot_dma[i].size, ipu->rot_dma[i].vaddr, ipu->rot_dma[i].paddr); } if (major) { device_destroy(ipu_class, MKDEV(major, 0)); class_destroy(ipu_class); unregister_chrdev(major, "mxc_ipu"); major = 0; } }