| /* |
| * Copyright 2005-2011 Freescale Semiconductor, Inc. All Rights Reserved. |
| */ |
| |
| /* |
| * 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 <linux/types.h> |
| #include <linux/init.h> |
| #include <linux/platform_device.h> |
| #include <linux/err.h> |
| #include <linux/spinlock.h> |
| #include <linux/delay.h> |
| #include <linux/clk.h> |
| #include <linux/poll.h> |
| #include <linux/sched.h> |
| #include <linux/time.h> |
| #include <linux/wait.h> |
| #include <linux/slab.h> |
| #include <linux/dma-mapping.h> |
| #include <linux/io.h> |
| #include <linux/ipu.h> |
| #include <linux/kthread.h> |
| #include <asm/cacheflush.h> |
| |
| #include "ipu_prv.h" |
| #include "ipu_regs.h" |
| #include "ipu_param_mem.h" |
| |
| /* Strucutures and variables for exporting MXC IPU as device*/ |
| typedef enum { |
| RGB_CS, |
| YUV_CS, |
| NULL_CS |
| } cs_t; |
| |
| typedef enum { |
| STATE_OK = 0, |
| STATE_NO_IPU, |
| STATE_NO_IRQ, |
| STATE_IRQ_FAIL, |
| STATE_IRQ_TIMEOUT, |
| STATE_INIT_CHAN_FAIL, |
| STATE_LINK_CHAN_FAIL, |
| STATE_INIT_CHAN_BUF_FAIL, |
| } ipu_state_t; |
| |
| struct ipu_state_msg { |
| int state; |
| char *msg; |
| } state_msg[] = { |
| {STATE_OK, "ok"}, |
| {STATE_NO_IPU, "no ipu found"}, |
| {STATE_NO_IRQ, "no irq found for task"}, |
| {STATE_IRQ_FAIL, "request irq failed"}, |
| {STATE_IRQ_TIMEOUT, "wait for irq timeout"}, |
| {STATE_INIT_CHAN_FAIL, "ipu init channel fail"}, |
| {STATE_LINK_CHAN_FAIL, "ipu link channel fail"}, |
| {STATE_INIT_CHAN_BUF_FAIL, "ipu init channel buffer 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; |
| }; |
| |
| struct task_set { |
| #define NULL_MODE 0x0 |
| #define IC_MODE 0x1 |
| #define ROT_MODE 0x2 |
| #define VDI_MODE 0x4 |
| u8 mode; |
| #define IC_VF 0x1 |
| #define IC_PP 0x2 |
| #define ROT_VF 0x4 |
| #define ROT_PP 0x8 |
| #define VDI_VF 0x10 |
| u8 task; |
| |
| 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; |
| |
| #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 |
| u8 split_mode; |
| struct stripe_setting sp_setting; |
| }; |
| |
| struct ipu_split_task { |
| struct ipu_task task; |
| struct ipu_task_entry *parent_task; |
| struct task_struct *thread; |
| bool could_finish; |
| wait_queue_head_t waitq; |
| int ret; |
| }; |
| |
| struct ipu_task_entry { |
| struct ipu_input input; |
| struct ipu_output output; |
| |
| bool overlay_en; |
| struct ipu_overlay overlay; |
| |
| u8 priority; |
| u8 task_id; |
| #define DEF_TIMEOUT_MS 1000 |
| int timeout; |
| |
| struct list_head node; |
| struct device *dev; |
| struct task_set set; |
| struct completion comp; |
| ipu_state_t state; |
| }; |
| |
| struct ipu_alloc_list { |
| struct list_head list; |
| dma_addr_t phy_addr; |
| void *cpu_addr; |
| u32 size; |
| }; |
| LIST_HEAD(ipu_alloc_list); |
| |
| static int major; |
| static struct class *ipu_class; |
| static struct device *ipu_dev; |
| |
| int ipu_queue_sp_task(struct ipu_split_task *sp_task); |
| |
| static bool deinterlace_3_field(struct ipu_task_entry *t) |
| { |
| return ((t->set.mode & VDI_MODE) && |
| (t->input.deinterlace.motion != HIGH_MOTION)); |
| } |
| |
| static u32 fmt_to_bpp(u32 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: |
| 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; |
| } |
| |
| static cs_t colorspaceofpixel(int fmt) |
| { |
| switch (fmt) { |
| case IPU_PIX_FMT_RGB565: |
| 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_NV12: |
| return YUV_CS; |
| break; |
| default: |
| return NULL_CS; |
| } |
| } |
| |
| static 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; |
| } |
| |
| static int soc_max_in_width(void) |
| { |
| return 4096; |
| } |
| |
| 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 int list_size(struct list_head *head) |
| { |
| struct list_head *p, *n; |
| int size = 0; |
| |
| list_for_each_safe(p, n, head) |
| size++; |
| |
| return size; |
| } |
| |
| static int get_task_size(struct ipu_soc *ipu, int id) |
| { |
| struct list_head *task_list; |
| |
| if (id == IPU_TASK_ID_VF) |
| task_list = &ipu->task_list[0]; |
| else if (id == IPU_TASK_ID_PP) |
| task_list = &ipu->task_list[1]; |
| else { |
| printk(KERN_ERR "query error task id\n"); |
| return -EINVAL; |
| } |
| |
| return list_size(task_list); |
| } |
| |
| static struct ipu_soc *most_free_ipu_task(struct ipu_task_entry *t) |
| { |
| unsigned int task_num[2][2] = { |
| {0xffffffff, 0xffffffff}, |
| {0xffffffff, 0xffffffff} }; |
| struct ipu_soc *ipu; |
| int ipu_idx, task_id; |
| int i; |
| |
| /* decide task_id */ |
| if (t->task_id >= IPU_TASK_ID_MAX) |
| t->task_id %= IPU_TASK_ID_MAX; |
| /* must use task_id VF for VDI task*/ |
| if ((t->set.mode & VDI_MODE) && |
| (t->task_id != IPU_TASK_ID_VF)) |
| t->task_id = IPU_TASK_ID_VF; |
| |
| for (i = 0; i < MXC_IPU_MAX_NUM; i++) { |
| ipu = ipu_get_soc(i); |
| if (!IS_ERR(ipu)) { |
| task_num[i][0] = get_task_size(ipu, IPU_TASK_ID_VF); |
| task_num[i][1] = get_task_size(ipu, IPU_TASK_ID_PP); |
| } |
| } |
| |
| task_id = t->task_id; |
| if (t->task_id == IPU_TASK_ID_VF) { |
| if (task_num[0][0] < task_num[1][0]) |
| ipu_idx = 0; |
| else |
| ipu_idx = 1; |
| } else if (t->task_id == IPU_TASK_ID_PP) { |
| if (task_num[0][1] < task_num[1][1]) |
| ipu_idx = 0; |
| else |
| ipu_idx = 1; |
| } else { |
| unsigned int min; |
| ipu_idx = 0; |
| task_id = IPU_TASK_ID_VF; |
| min = task_num[0][0]; |
| if (task_num[0][1] < min) { |
| min = task_num[0][1]; |
| task_id = IPU_TASK_ID_PP; |
| } |
| if (task_num[1][0] < min) { |
| min = task_num[1][0]; |
| ipu_idx = 1; |
| task_id = IPU_TASK_ID_VF; |
| } |
| if (task_num[1][1] < min) { |
| ipu_idx = 1; |
| task_id = IPU_TASK_ID_PP; |
| } |
| } |
| |
| t->task_id = task_id; |
| ipu = ipu_get_soc(ipu_idx); |
| |
| return ipu; |
| } |
| |
| static void dump_task_info(struct ipu_task_entry *t) |
| { |
| 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); |
| } |
| |
| 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; |
| 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) |
| { |
| if (crop->w || crop->h) { |
| if (((crop->w + crop->pos.x) > width) |
| || ((crop->h + crop->pos.y) > height)) |
| 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; |
| |
| 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); |
| *voff = *uoff + (width/2 * height/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); |
| *uoff = *voff + (width/2 * height/2); |
| break; |
| case IPU_PIX_FMT_YVU422P: |
| *off = pos_y * width + pos_x; |
| *voff = (width * (height - pos_y) - pos_x) |
| + ((width * pos_y)/2 + pos_x/2); |
| *uoff = *voff + (width * height)/2; |
| break; |
| case IPU_PIX_FMT_YUV422P: |
| *off = pos_y * width + pos_x; |
| *uoff = (width * (height - pos_y) - pos_x) |
| + (width * pos_y)/2 + pos_x/2; |
| *voff = *uoff + (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; |
| 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) |
| { |
| 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; |
| |
| iw = t->input.crop.w; |
| ih = t->input.crop.h; |
| if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { |
| ow = t->output.crop.h; |
| oh = t->output.crop.w; |
| } else { |
| ow = t->output.crop.w; |
| oh = t->output.crop.h; |
| } |
| |
| if (t->set.split_mode & RL_SPLIT) { |
| ipu_calc_stripes_sizes(iw, |
| ow, |
| soc_max_out_width(), |
| (((unsigned long long)1) << 32), /* 32bit for fractional*/ |
| 1, /* equal stripes */ |
| t->input.format, |
| t->output.format, |
| &left_stripe, |
| &right_stripe); |
| 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) |
| 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 (t->set.split_mode & UD_SPLIT) { |
| ipu_calc_stripes_sizes(ih, |
| oh, |
| soc_max_out_height(), |
| (((unsigned long long)1) << 32), /* 32bit for fractional*/ |
| 1, /* equal stripes */ |
| t->input.format, |
| t->output.format, |
| &up_stripe, |
| &down_stripe); |
| 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; |
| } |
| if ((t->set.sp_setting.ih + t->set.sp_setting.i_bottom_pos) > ih) |
| 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; |
| |
| return IPU_CHECK_OK; |
| } |
| |
| static int check_task(struct ipu_task_entry *t) |
| { |
| int tmp; |
| int ret = IPU_CHECK_OK; |
| |
| /* check input */ |
| ret = set_crop(&t->input.crop, t->input.width, t->input.height); |
| 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); |
| 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); |
| |
| /* 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); |
| if (ret < 0) { |
| ret = IPU_CHECK_ERR_OVERLAY_CROP; |
| goto done; |
| } else { |
| int ow = t->output.crop.w; |
| int oh = t->output.crop.h; |
| |
| if (t->output.rotate >= IPU_ROTATE_90_RIGHT) { |
| ow = t->output.crop.h; |
| oh = t->output.crop.w; |
| } |
| if ((t->overlay.crop.w != ow) || (t->overlay.crop.h != oh)) { |
| 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 ((t->input.crop.w > soc_max_in_width()) || |
| (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 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 (t->set.mode & (IC_MODE | VDI_MODE)) { |
| 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; |
| ret = update_split_setting(t); |
| 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 (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; |
| |
| dump_task_info(t); |
| |
| return ret; |
| } |
| |
| /* should call from a process context */ |
| static int queue_task(struct ipu_task_entry *t) |
| { |
| int ret = 0; |
| struct ipu_soc *ipu; |
| struct list_head *task_list = NULL; |
| struct mutex *task_lock = NULL; |
| wait_queue_head_t *waitq = NULL; |
| |
| ipu = most_free_ipu_task(t); |
| t->dev = ipu->dev; |
| |
| dev_dbg(t->dev, "[0x%p]Queue task: id %d\n", (void *)t, t->task_id); |
| |
| init_completion(&t->comp); |
| |
| t->set.task = 0; |
| switch (t->task_id) { |
| case IPU_TASK_ID_VF: |
| task_list = &ipu->task_list[0]; |
| task_lock = &ipu->task_lock[0]; |
| waitq = &ipu->waitq[0]; |
| if (t->set.mode & IC_MODE) |
| t->set.task |= IC_VF; |
| else if (t->set.mode & VDI_MODE) |
| t->set.task |= VDI_VF; |
| if (t->set.mode & ROT_MODE) |
| t->set.task |= ROT_VF; |
| break; |
| case IPU_TASK_ID_PP: |
| task_list = &ipu->task_list[1]; |
| task_lock = &ipu->task_lock[1]; |
| waitq = &ipu->waitq[1]; |
| if (t->set.mode & IC_MODE) |
| t->set.task |= IC_PP; |
| if (t->set.mode & ROT_MODE) |
| t->set.task |= ROT_PP; |
| break; |
| default: |
| dev_err(t->dev, "[0x%p]should never come here\n", (void *)t); |
| } |
| |
| dev_dbg(t->dev, "[0x%p]choose task_id[%d] mode[0x%x]\n", |
| (void *)t, t->task_id, t->set.task); |
| dev_dbg(t->dev, "[0x%p]\tIPU_TASK_ID_VF = %d\n", |
| (void *)t, IPU_TASK_ID_VF); |
| dev_dbg(t->dev, "[0x%p]\tIPU_TASK_ID_PP = %d\n", |
| (void *)t, IPU_TASK_ID_PP); |
| dev_dbg(t->dev, "[0x%p]\tIC_VF = 0x%x\n", (void *)t, IC_VF); |
| dev_dbg(t->dev, "[0x%p]\tIC_PP = 0x%x\n", (void *)t, IC_PP); |
| dev_dbg(t->dev, "[0x%p]\tROT_VF = 0x%x\n", (void *)t, ROT_VF); |
| dev_dbg(t->dev, "[0x%p]\tROT_PP = 0x%x\n", (void *)t, ROT_PP); |
| dev_dbg(t->dev, "[0x%p]\tVDI_VF = 0x%x\n", (void *)t, VDI_VF); |
| |
| /* add and wait task */ |
| mutex_lock(task_lock); |
| list_add_tail(&t->node, task_list); |
| mutex_unlock(task_lock); |
| |
| wake_up_interruptible(waitq); |
| |
| wait_for_completion(&t->comp); |
| |
| dev_dbg(t->dev, "[0x%p]Queue task finished\n", (void *)t); |
| |
| if (t->state != STATE_OK) { |
| dev_err(t->dev, "[0x%p]state %d: %s\n", |
| (void *)t, t->state, state_msg[t->state].msg); |
| ret = -ECANCELED; |
| } |
| |
| return ret; |
| } |
| |
| static bool need_split(struct ipu_task_entry *t) |
| { |
| return (t->set.split_mode != NO_SPLIT); |
| } |
| |
| static int split_task_thread(void *data) |
| { |
| struct ipu_split_task *t = data; |
| |
| t->ret = ipu_queue_sp_task(t); |
| |
| while (!kthread_should_stop()) |
| wait_event_interruptible(t->waitq, t->could_finish); |
| |
| return 0; |
| } |
| |
| 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; |
| |
| task->input = t->input; |
| task->output = t->output; |
| task->overlay_en = t->overlay_en; |
| if (task->overlay_en) |
| task->overlay = t->overlay; |
| task->priority = t->priority; |
| task->task_id = t->task_id; |
| task->timeout = t->timeout; |
| |
| 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; |
| } else { |
| task->output.crop.w = t->set.sp_setting.ow; |
| task->output.crop.h = t->set.sp_setting.oh; |
| } |
| |
| 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, "should not be here\n"); |
| break; |
| } |
| |
| sp_task->thread = kthread_run(split_task_thread, sp_task, |
| "ipu_split_task"); |
| if (IS_ERR(sp_task->thread)) { |
| dev_err(t->dev, "split thread can not create\n"); |
| return PTR_ERR(sp_task->thread); |
| } |
| |
| return 0; |
| } |
| |
| static int queue_split_task(struct ipu_task_entry *t) |
| { |
| struct ipu_split_task sp_task[4]; |
| int i, ret = 0, size; |
| |
| dev_dbg(t->dev, "Split task 0x%p\n", (void *)t); |
| |
| if ((t->set.split_mode == RL_SPLIT) || (t->set.split_mode == UD_SPLIT)) |
| size = 2; |
| else |
| size = 4; |
| |
| for (i = 0; i < size; i++) { |
| memset(&sp_task[i], 0, sizeof(struct ipu_split_task)); |
| init_waitqueue_head(&(sp_task[i].waitq)); |
| sp_task[i].could_finish = false; |
| sp_task[i].parent_task = t; |
| } |
| |
| if (t->set.split_mode == RL_SPLIT) { |
| create_split_task(LEFT_STRIPE, &sp_task[0]); |
| create_split_task(RIGHT_STRIPE, &sp_task[1]); |
| } else if (t->set.split_mode == UD_SPLIT) { |
| create_split_task(UP_STRIPE, &sp_task[0]); |
| create_split_task(DOWN_STRIPE, &sp_task[1]); |
| } else { |
| create_split_task(LEFT_STRIPE | UP_STRIPE, &sp_task[0]); |
| create_split_task(RIGHT_STRIPE | UP_STRIPE, &sp_task[1]); |
| create_split_task(LEFT_STRIPE | DOWN_STRIPE, &sp_task[2]); |
| create_split_task(RIGHT_STRIPE | DOWN_STRIPE, &sp_task[3]); |
| } |
| |
| for (i = 0; i < size; i++) { |
| sp_task[i].could_finish = true; |
| wake_up_interruptible(&sp_task[i].waitq); |
| kthread_stop(sp_task[i].thread); |
| if (sp_task[i].ret < 0) { |
| ret = sp_task[i].ret; |
| dev_err(t->dev, |
| "split task %d fail with ret %d\n", |
| i, ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| 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); |
| |
| 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; |
| tsk->priority = task->priority; |
| tsk->task_id = task->task_id; |
| if (task->timeout && (task->timeout > DEF_TIMEOUT_MS)) |
| tsk->timeout = task->timeout; |
| else |
| tsk->timeout = DEF_TIMEOUT_MS; |
| |
| return tsk; |
| } |
| |
| 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; |
| |
| kfree(tsk); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(ipu_check_task); |
| |
| int ipu_queue_sp_task(struct ipu_split_task *sp_task) |
| { |
| struct ipu_task_entry *tsk; |
| int ret; |
| |
| tsk = create_task_entry(&sp_task->task); |
| if (IS_ERR(tsk)) |
| return PTR_ERR(tsk); |
| |
| ret = prepare_task(tsk); |
| if (ret < 0) |
| goto done; |
| |
| tsk->set.sp_setting = sp_task->parent_task->set.sp_setting; |
| tsk->set.sp_setting = sp_task->parent_task->set.sp_setting; |
| |
| ret = queue_task(tsk); |
| done: |
| kfree(tsk); |
| return ret; |
| } |
| |
| int ipu_queue_task(struct ipu_task *task) |
| { |
| struct ipu_task_entry *tsk; |
| int ret; |
| |
| tsk = create_task_entry(task); |
| if (IS_ERR(tsk)) |
| return PTR_ERR(tsk); |
| |
| ret = prepare_task(tsk); |
| if (ret < 0) |
| goto done; |
| |
| if (need_split(tsk)) |
| ret = queue_split_task(tsk); |
| else |
| ret = queue_task(tsk); |
| done: |
| kfree(tsk); |
| return ret; |
| } |
| EXPORT_SYMBOL_GPL(ipu_queue_task); |
| |
| static bool only_ic(u8 mode) |
| { |
| return ((mode == IC_MODE) || (mode == VDI_MODE)); |
| } |
| |
| static bool only_rot(u8 mode) |
| { |
| return (mode == ROT_MODE); |
| } |
| |
| static bool ic_and_rot(u8 mode) |
| { |
| return ((mode == (IC_MODE | ROT_MODE)) || |
| (mode == (VDI_MODE | ROT_MODE))); |
| } |
| |
| 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; |
| |
| 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 |
| params.mem_prp_vf_mem.alpha_chan_en = 1; |
| 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; |
| } |
| } |
| |
| /* init channels */ |
| 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 (deinterlace_3_field(t)) { |
| 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; |
| } else |
| inbuf = t->input.paddr + t->set.i_off; |
| |
| if (t->overlay_en) { |
| ovbuf = t->overlay.paddr + t->set.ov_off; |
| if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) |
| ov_alp_buf = t->overlay.alpha.loc_alp_paddr |
| + t->set.ov_alpha_off; |
| } |
| |
| 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)) { |
| 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; |
| } |
| } |
| } |
| |
| 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; |
| } |
| |
| done: |
| return ret; |
| } |
| |
| static void uninit_ic(struct ipu_soc *ipu, struct ipu_task_entry *t) |
| { |
| 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, |
| t->set.istride, |
| 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; |
| default: |
| irq = -EINVAL; |
| } |
| |
| return irq; |
| } |
| |
| static irqreturn_t task_irq_handler(int irq, void *dev_id) |
| { |
| struct completion *comp = dev_id; |
| complete(comp); |
| return IRQ_HANDLED; |
| } |
| |
| static void do_task(struct ipu_soc *ipu, struct ipu_task_entry *t) |
| { |
| struct completion comp; |
| void *r_vaddr; |
| int r_size; |
| int irq; |
| int ret; |
| |
| if (!ipu) { |
| t->state = STATE_NO_IPU; |
| return; |
| } |
| |
| dev_dbg(ipu->dev, "[0x%p]Do task: id %d\n", (void *)t, 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) { |
| 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 channel MEM_VDI_PRP_VF_MEM\n", (void *)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); |
| } |
| |
| /* 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); |
| if (ret < 0) |
| goto chan_done; |
| } else if (only_rot(t->set.mode)) { |
| dev_dbg(t->dev, "[0x%p]only rot mode\n", (void *)t); |
| ret = init_rot(ipu, t); |
| if (ret < 0) |
| goto chan_done; |
| } else if (ic_and_rot(t->set.mode)) { |
| 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 = t->set.r_width * t->set.r_height |
| * fmt_to_bpp(t->set.r_fmt)/8; |
| r_vaddr = dma_alloc_coherent(t->dev, |
| r_size, |
| &t->set.r_paddr, |
| GFP_DMA | GFP_KERNEL); |
| if (r_vaddr == NULL) { |
| ret = -ENOMEM; |
| goto chan_done; |
| } |
| |
| 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); |
| if (ret < 0) |
| goto chan_done; |
| ret = init_rot(ipu, t); |
| if (ret < 0) |
| goto chan_done; |
| ret = ipu_link_channels(ipu, t->set.ic_chan, |
| t->set.rot_chan); |
| if (ret < 0) { |
| t->state = STATE_LINK_CHAN_FAIL; |
| goto chan_done; |
| } |
| } else { |
| dev_err(t->dev, "[0x%p]do_task: should not be here\n", (void *)t); |
| return; |
| } |
| |
| /* irq setup */ |
| irq = get_irq(t); |
| if (irq < 0) { |
| t->state = STATE_NO_IRQ; |
| goto chan_done; |
| } |
| |
| dev_dbg(t->dev, "[0x%p]task irq is %d\n", (void *)t, irq); |
| |
| init_completion(&comp); |
| ipu_clear_irq(ipu, irq); |
| ret = ipu_request_irq(ipu, irq, task_irq_handler, 0, NULL, &comp); |
| if (ret < 0) { |
| t->state = STATE_IRQ_FAIL; |
| goto chan_done; |
| } |
| |
| /* enable/start channel */ |
| if (only_ic(t->set.mode)) { |
| ipu_enable_channel(ipu, t->set.ic_chan); |
| if (deinterlace_3_field(t)) { |
| ipu_enable_channel(ipu, t->set.vdi_ic_p_chan); |
| ipu_enable_channel(ipu, t->set.vdi_ic_n_chan); |
| } |
| |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_OUTPUT_BUFFER, 0); |
| if (t->overlay_en) { |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_GRAPH_IN_BUFFER, 0); |
| if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_ALPHA_IN_BUFFER, 0); |
| } |
| if (deinterlace_3_field(t)) |
| ipu_select_multi_vdi_buffer(ipu, 0); |
| else |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_INPUT_BUFFER, 0); |
| } else if (only_rot(t->set.mode)) { |
| ipu_enable_channel(ipu, t->set.rot_chan); |
| ipu_select_buffer(ipu, t->set.rot_chan, IPU_OUTPUT_BUFFER, 0); |
| ipu_select_buffer(ipu, t->set.rot_chan, IPU_INPUT_BUFFER, 0); |
| } else if (ic_and_rot(t->set.mode)) { |
| ipu_enable_channel(ipu, t->set.rot_chan); |
| ipu_enable_channel(ipu, t->set.ic_chan); |
| if (deinterlace_3_field(t)) { |
| ipu_enable_channel(ipu, t->set.vdi_ic_p_chan); |
| ipu_enable_channel(ipu, t->set.vdi_ic_n_chan); |
| } |
| |
| ipu_select_buffer(ipu, t->set.rot_chan, IPU_OUTPUT_BUFFER, 0); |
| if (t->overlay_en) { |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_GRAPH_IN_BUFFER, 0); |
| if (t->overlay.alpha.mode == IPU_ALPHA_MODE_LOCAL) |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_ALPHA_IN_BUFFER, 0); |
| } |
| if (deinterlace_3_field(t)) |
| ipu_select_multi_vdi_buffer(ipu, 0); |
| else |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_INPUT_BUFFER, 0); |
| ipu_select_buffer(ipu, t->set.ic_chan, IPU_OUTPUT_BUFFER, 0); |
| } |
| |
| ret = wait_for_completion_timeout(&comp, msecs_to_jiffies(t->timeout)); |
| if (ret == 0) |
| t->state = STATE_IRQ_TIMEOUT; |
| |
| ipu_free_irq(ipu, irq, &comp); |
| if (t->set.task & IC_VF) { |
| ipu_clear_irq(ipu, IPU_IRQ_PRP_IN_EOF); |
| ipu_clear_irq(ipu, IPU_IRQ_PRP_VF_OUT_EOF); |
| } else if (t->set.task & IC_PP) { |
| ipu_clear_irq(ipu, IPU_IRQ_PP_IN_EOF); |
| ipu_clear_irq(ipu, IPU_IRQ_PP_OUT_EOF); |
| } else if (t->set.task & VDI_VF) { |
| ipu_clear_irq(ipu, IPU_IRQ_VDI_C_IN_EOF); |
| if (deinterlace_3_field(t)) { |
| ipu_clear_irq(ipu, IPU_IRQ_VDI_P_IN_EOF); |
| ipu_clear_irq(ipu, IPU_IRQ_VDI_N_IN_EOF); |
| } |
| ipu_clear_irq(ipu, IPU_IRQ_PRP_VF_OUT_EOF); |
| } |
| if (t->set.task & ROT_VF) { |
| ipu_clear_irq(ipu, IPU_IRQ_PRP_VF_ROT_IN_EOF); |
| ipu_clear_irq(ipu, IPU_IRQ_PRP_VF_ROT_OUT_EOF); |
| } else if (t->set.task & ROT_PP) { |
| ipu_clear_irq(ipu, IPU_IRQ_PP_ROT_IN_EOF); |
| ipu_clear_irq(ipu, IPU_IRQ_PP_ROT_OUT_EOF); |
| } |
| |
| if (only_ic(t->set.mode)) { |
| ipu_disable_channel(ipu, t->set.ic_chan, true); |
| if (deinterlace_3_field(t)) { |
| ipu_disable_channel(ipu, t->set.vdi_ic_p_chan, true); |
| ipu_disable_channel(ipu, t->set.vdi_ic_n_chan, true); |
| } |
| } else if (only_rot(t->set.mode)) |
| ipu_disable_channel(ipu, t->set.rot_chan, true); |
| else if (ic_and_rot(t->set.mode)) { |
| ipu_disable_channel(ipu, t->set.ic_chan, true); |
| if (deinterlace_3_field(t)) { |
| ipu_disable_channel(ipu, t->set.vdi_ic_p_chan, true); |
| ipu_disable_channel(ipu, t->set.vdi_ic_n_chan, true); |
| } |
| ipu_disable_channel(ipu, t->set.rot_chan, true); |
| } |
| |
| chan_done: |
| 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)) { |
| ipu_unlink_channels(ipu, t->set.ic_chan, |
| t->set.rot_chan); |
| uninit_ic(ipu, t); |
| uninit_rot(ipu, t); |
| if (r_vaddr) |
| dma_free_coherent(t->dev, |
| r_size, |
| r_vaddr, |
| t->set.r_paddr); |
| } |
| return; |
| } |
| |
| static int thread_loop(struct ipu_soc *ipu, int id) |
| { |
| struct ipu_task_entry *tsk; |
| struct list_head *task_list = &ipu->task_list[id]; |
| struct mutex *task_lock = &ipu->task_lock[id]; |
| int ret; |
| |
| while (!kthread_should_stop()) { |
| int found = 0; |
| |
| ret = wait_event_interruptible(ipu->waitq[id], !list_empty(task_list)); |
| if (0 != ret) |
| continue; |
| |
| mutex_lock(task_lock); |
| |
| list_for_each_entry(tsk, task_list, node) { |
| if (tsk->priority == IPU_TASK_PRIORITY_HIGH) { |
| found = 1; |
| break; |
| } |
| } |
| |
| if (!found) |
| tsk = list_first_entry(task_list, struct ipu_task_entry, node); |
| |
| mutex_unlock(task_lock); |
| |
| do_task(ipu, tsk); |
| |
| mutex_lock(task_lock); |
| list_del(&tsk->node); |
| mutex_unlock(task_lock); |
| |
| complete(&tsk->comp); |
| } |
| |
| return 0; |
| } |
| |
| static int task_vf_thread(void *data) |
| { |
| struct ipu_soc *ipu = data; |
| |
| thread_loop(ipu, 0); |
| |
| return 0; |
| } |
| |
| static int task_pp_thread(void *data) |
| { |
| struct ipu_soc *ipu = data; |
| |
| thread_loop(ipu, 1); |
| |
| return 0; |
| } |
| |
| static int mxc_ipu_open(struct inode *inode, struct file *file) |
| { |
| 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)) |
| return -EFAULT; |
| |
| mem->size = PAGE_ALIGN(size); |
| |
| mem->cpu_addr = dma_alloc_coherent(ipu_dev, size, |
| &mem->phy_addr, |
| GFP_DMA); |
| if (mem->cpu_addr == NULL) { |
| kfree(mem); |
| return -ENOMEM; |
| } |
| |
| list_add(&mem->list, &ipu_alloc_list); |
| |
| dev_dbg(ipu_dev, "allocated %d bytes @ 0x%08X\n", |
| mem->size, mem->phy_addr); |
| |
| if (put_user(mem->phy_addr, argp)) |
| return -EFAULT; |
| |
| break; |
| } |
| case IPU_FREE: |
| { |
| unsigned long offset; |
| struct ipu_alloc_list *mem; |
| |
| if (get_user(offset, argp)) |
| return -EFAULT; |
| |
| ret = -EINVAL; |
| 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; |
| } |
| } |
| |
| 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; |
| |
| list_for_each_entry(mem, &ipu_alloc_list, list) { |
| if (offset == mem->phy_addr) { |
| found = true; |
| len = mem->size; |
| break; |
| } |
| } |
| 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) |
| { |
| 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; |
| |
| 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); |
| } |
| |
| INIT_LIST_HEAD(&ipu->task_list[0]); |
| INIT_LIST_HEAD(&ipu->task_list[1]); |
| init_waitqueue_head(&ipu->waitq[0]); |
| init_waitqueue_head(&ipu->waitq[1]); |
| mutex_init(&ipu->task_lock[0]); |
| mutex_init(&ipu->task_lock[1]); |
| ipu->thread[0] = kthread_run(task_vf_thread, ipu, |
| "ipu%d_process-vf", id); |
| if (IS_ERR(ipu->thread[0])) { |
| ret = PTR_ERR(ipu->thread[0]); |
| goto kthread0_fail; |
| } |
| |
| ipu->thread[1] = kthread_run(task_pp_thread, ipu, |
| "ipu%d_process-pp", 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); |
| unregister_chrdev(major, "mxc_ipu"); |
| } |
| 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) |
| { |
| kthread_stop(ipu->thread[0]); |
| kthread_stop(ipu->thread[1]); |
| if (major) { |
| device_destroy(ipu_class, MKDEV(major, 0)); |
| class_destroy(ipu_class); |
| unregister_chrdev(major, "mxc_ipu"); |
| major = 0; |
| } |
| } |