diff options
Diffstat (limited to 'drivers/mxc/ipu3/pre.c')
-rw-r--r-- | drivers/mxc/ipu3/pre.c | 973 |
1 files changed, 973 insertions, 0 deletions
diff --git a/drivers/mxc/ipu3/pre.c b/drivers/mxc/ipu3/pre.c new file mode 100644 index 000000000000..88783febdd08 --- /dev/null +++ b/drivers/mxc/ipu3/pre.c @@ -0,0 +1,973 @@ +/* + * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. + * + * 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 + */ +#include <linux/clk.h> +#include <linux/genalloc.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ipu-v3.h> +#include <linux/ipu-v3-pre.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#include "pre-regs.h" + +struct ipu_pre_data { + unsigned int id; + struct device *dev; + void __iomem *base; + struct clk *clk; + + struct mutex mutex; /* for in_use */ + spinlock_t lock; /* for register access */ + + struct list_head list; + + struct gen_pool *iram_pool; + unsigned long double_buffer_size; + unsigned long double_buffer_base; + unsigned long double_buffer_paddr; + + bool in_use; + bool enabled; +}; + +static LIST_HEAD(pre_list); +static DEFINE_MUTEX(pre_list_lock); + +static inline void pre_write(struct ipu_pre_data *pre, + u32 value, unsigned int offset) +{ + writel(value, pre->base + offset); +} + +static inline u32 pre_read(struct ipu_pre_data *pre, unsigned offset) +{ + return readl(pre->base + offset); +} + +static struct ipu_pre_data *get_pre(unsigned int id) +{ + struct ipu_pre_data *pre; + + mutex_lock(&pre_list_lock); + list_for_each_entry(pre, &pre_list, list) { + if (pre->id == id) { + mutex_unlock(&pre_list_lock); + return pre; + } + } + mutex_unlock(&pre_list_lock); + + return NULL; +} + +int ipu_pre_alloc(int ipu_id, ipu_channel_t channel) +{ + struct ipu_pre_data *pre; + int i, fixed; + + if (channel == MEM_BG_SYNC) { + fixed = ipu_id ? 3 : 0; + pre = get_pre(fixed); + if (pre) { + mutex_lock(&pre->mutex); + if (!pre->in_use) { + pre->in_use = true; + mutex_unlock(&pre->mutex); + return pre->id; + } + mutex_unlock(&pre->mutex); + } + return pre ? -EBUSY : -ENOENT; + } + + for (i = 1; i < 3; i++) { + pre = get_pre(i); + if (!pre) + continue; + mutex_lock(&pre->mutex); + if (!pre->in_use) { + pre->in_use = true; + mutex_unlock(&pre->mutex); + return pre->id; + } + mutex_unlock(&pre->mutex); + } + + return pre ? -EBUSY : -ENOENT; +} +EXPORT_SYMBOL(ipu_pre_alloc); + +void ipu_pre_free(unsigned int *id) +{ + struct ipu_pre_data *pre; + + pre = get_pre(*id); + if (!pre) + return; + + mutex_lock(&pre->mutex); + pre->in_use = false; + mutex_unlock(&pre->mutex); + + *id = -1; +} +EXPORT_SYMBOL(ipu_pre_free); + +unsigned long ipu_pre_alloc_double_buffer(unsigned int id, unsigned int size) +{ + struct ipu_pre_data *pre = get_pre(id); + + if (!pre) + return -ENOENT; + + if (!size) + return -EINVAL; + + pre->double_buffer_base = gen_pool_alloc(pre->iram_pool, size); + if (!pre->double_buffer_base) { + dev_err(pre->dev, "double buffer allocate failed\n"); + return -ENOMEM; + } + pre->double_buffer_size = size; + + pre->double_buffer_paddr = gen_pool_virt_to_phys(pre->iram_pool, + pre->double_buffer_base); + + return pre->double_buffer_paddr; +} +EXPORT_SYMBOL(ipu_pre_alloc_double_buffer); + +void ipu_pre_free_double_buffer(unsigned int id) +{ + struct ipu_pre_data *pre = get_pre(id); + + if (!pre) + return; + + if (pre->double_buffer_base) { + gen_pool_free(pre->iram_pool, + pre->double_buffer_base, + pre->double_buffer_size); + pre->double_buffer_base = 0; + pre->double_buffer_size = 0; + pre->double_buffer_paddr = 0; + } +} +EXPORT_SYMBOL(ipu_pre_free_double_buffer); + +/* PRE register configurations */ +static int ipu_pre_set_ctrl(unsigned int id, + bool repeat, + bool vflip, + bool handshake_en, + bool hsk_abort_en, + unsigned int hsk_line_num, + bool sdw_update, + unsigned int block_size, + unsigned int interlaced, + unsigned int prefetch_mode) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + int ret = 0; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, BF_PRE_CTRL_TPR_RESET_SEL(1), HW_PRE_CTRL_SET); + + if (repeat) + pre_write(pre, BF_PRE_CTRL_EN_REPEAT(1), HW_PRE_CTRL_SET); + else + pre_write(pre, BM_PRE_CTRL_EN_REPEAT, HW_PRE_CTRL_CLR); + + if (vflip) + pre_write(pre, BF_PRE_CTRL_VFLIP(1), HW_PRE_CTRL_SET); + else + pre_write(pre, BM_PRE_CTRL_VFLIP, HW_PRE_CTRL_CLR); + + if (handshake_en) { + pre_write(pre, BF_PRE_CTRL_HANDSHAKE_EN(1), HW_PRE_CTRL_SET); + if (hsk_abort_en) + pre_write(pre, BF_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN(1), + HW_PRE_CTRL_SET); + else + pre_write(pre, BM_PRE_CTRL_HANDSHAKE_ABORT_SKIP_EN, + HW_PRE_CTRL_CLR); + + switch (hsk_line_num) { + case 0 /* 4 lines */: + pre_write(pre, BM_PRE_CTRL_HANDSHAKE_LINE_NUM, + HW_PRE_CTRL_CLR); + break; + case 1 /* 8 lines */: + pre_write(pre, BM_PRE_CTRL_HANDSHAKE_LINE_NUM, + HW_PRE_CTRL_CLR); + pre_write(pre, BF_PRE_CTRL_HANDSHAKE_LINE_NUM(1), + HW_PRE_CTRL_SET); + break; + case 2 /* 16 lines */: + pre_write(pre, BM_PRE_CTRL_HANDSHAKE_LINE_NUM, + HW_PRE_CTRL_CLR); + pre_write(pre, BF_PRE_CTRL_HANDSHAKE_LINE_NUM(2), + HW_PRE_CTRL_SET); + break; + default: + dev_err(pre->dev, "invalid hanshake line number\n"); + ret = -EINVAL; + goto err; + } + } else + pre_write(pre, BM_PRE_CTRL_HANDSHAKE_EN, HW_PRE_CTRL_CLR); + + + switch (prefetch_mode) { + case 0: + pre_write(pre, BM_PRE_CTRL_BLOCK_EN, HW_PRE_CTRL_CLR); + break; + case 1: + pre_write(pre, BF_PRE_CTRL_BLOCK_EN(1), HW_PRE_CTRL_SET); + switch (block_size) { + case 0: + pre_write(pre, BM_PRE_CTRL_BLOCK_16, HW_PRE_CTRL_CLR); + break; + case 1: + pre_write(pre, BF_PRE_CTRL_BLOCK_16(1), HW_PRE_CTRL_SET); + break; + default: + dev_err(pre->dev, "invalid block size for pre\n"); + ret = -EINVAL; + goto err; + } + break; + default: + dev_err(pre->dev, "invalid prefech mode for pre\n"); + ret = -EINVAL; + goto err; + } + + switch (interlaced) { + case 0: /* progressive mode */ + pre_write(pre, BM_PRE_CTRL_SO, HW_PRE_CTRL_CLR); + break; + case 2: /* interlaced mode: Pal */ + pre_write(pre, BF_PRE_CTRL_SO(1), HW_PRE_CTRL_SET); + pre_write(pre, BM_PRE_CTRL_INTERLACED_FIELD, HW_PRE_CTRL_CLR); + break; + case 3: /* interlaced mode: NTSC */ + pre_write(pre, BF_PRE_CTRL_SO(1), HW_PRE_CTRL_SET); + pre_write(pre, BF_PRE_CTRL_INTERLACED_FIELD(1), HW_PRE_CTRL_SET); + break; + default: + dev_err(pre->dev, "invalid interlaced or progressive mode\n"); + ret = -EINVAL; + goto err; + } + + if (sdw_update) + pre_write(pre, BF_PRE_CTRL_SDW_UPDATE(1), HW_PRE_CTRL_SET); + else + pre_write(pre, BM_PRE_CTRL_SDW_UPDATE, HW_PRE_CTRL_CLR); + +err: + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return ret; +} + +static void ipu_pre_irq_mask(struct ipu_pre_data *pre, + unsigned long mask, bool clear) +{ + if (clear) { + pre_write(pre, mask & 0xf, HW_PRE_IRQ_MASK_CLR); + return; + } + pre_write(pre, mask & 0xf, HW_PRE_IRQ_MASK_SET); +} + +static int ipu_pre_buf_set(unsigned int id, unsigned long cur_buf, + unsigned long next_buf) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, cur_buf, HW_PRE_CUR_BUF); + pre_write(pre, next_buf, HW_PRE_NEXT_BUF); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} + +static int ipu_pre_plane_buf_off_set(unsigned int id, + unsigned long sec_buf_off, + unsigned long trd_buf_off) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre || sec_buf_off & BM_PRE_U_BUF_OFFSET_RSVD0 || + trd_buf_off & BM_PRE_V_BUF_OFFSET_RSVD0) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, sec_buf_off, HW_PRE_U_BUF_OFFSET); + pre_write(pre, trd_buf_off, HW_PRE_V_BUF_OFFSET); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} + +static int ipu_pre_tpr_set(unsigned int id, unsigned int tile_fmt) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + unsigned int tpr_ctrl, fmt; + + if (!pre) + return -EINVAL; + + switch (tile_fmt) { + case 0x0: /* Bypass */ + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x0); + break; + case IPU_PIX_FMT_GPU32_SB_ST: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x10); + break; + case IPU_PIX_FMT_GPU16_SB_ST: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x11); + break; + case IPU_PIX_FMT_GPU32_ST: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x20); + break; + case IPU_PIX_FMT_GPU16_ST: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x21); + break; + case IPU_PIX_FMT_GPU32_SB_SRT: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x50); + break; + case IPU_PIX_FMT_GPU16_SB_SRT: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x51); + break; + case IPU_PIX_FMT_GPU32_SRT: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x60); + break; + case IPU_PIX_FMT_GPU16_SRT: + fmt = BF_PRE_TPR_CTRL_TILE_FORMAT(0x61); + break; + default: + dev_err(pre->dev, "invalid tile fmt for pre\n"); + return -EINVAL; + } + + spin_lock_irqsave(&pre->lock, lock_flags); + tpr_ctrl = pre_read(pre, HW_PRE_TPR_CTRL); + tpr_ctrl &= ~BM_PRE_TPR_CTRL_TILE_FORMAT; + tpr_ctrl |= fmt; + pre_write(pre, tpr_ctrl, HW_PRE_TPR_CTRL); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} + +static int ipu_pre_set_shift(int id, unsigned int offset, unsigned int width) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, offset, HW_PRE_PREFETCH_ENGINE_SHIFT_OFFSET); + pre_write(pre, width, HW_PRE_PREFETCH_ENGINE_SHIFT_WIDTH); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} + +static int ipu_pre_prefetch(unsigned int id, + unsigned int read_burst, + unsigned int input_bpp, + unsigned int input_pixel_fmt, + bool shift_bypass, + bool field_inverse, + bool tpr_coor_offset_en, + struct ipu_rect output_size, + unsigned int input_width, + unsigned int input_height, + unsigned int input_active_width, + unsigned int interlaced, + int interlace_offset) +{ + unsigned int prefetch_ctrl = 0; + unsigned int input_y_pitch = 0, input_uv_pitch = 0; + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_PREFETCH_EN(1); + switch (read_burst) { + case 0x0: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_RD_NUM_BYTES(0x0); + break; + case 0x1: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_RD_NUM_BYTES(0x1); + break; + case 0x2: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_RD_NUM_BYTES(0x2); + break; + case 0x3: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_RD_NUM_BYTES(0x3); + break; + case 0x4: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_RD_NUM_BYTES(0x4); + break; + default: + spin_unlock_irqrestore(&pre->lock, lock_flags); + dev_err(pre->dev, "invalid read burst for prefetch engine\n"); + return -EINVAL; + } + + switch (input_bpp) { + case 8: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_ACTIVE_BPP(0x0); + break; + case 16: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_ACTIVE_BPP(0x1); + break; + case 32: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_ACTIVE_BPP(0x2); + break; + case 64: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_ACTIVE_BPP(0x3); + break; + default: + spin_unlock_irqrestore(&pre->lock, lock_flags); + dev_err(pre->dev, "invalid input bpp for prefetch engine\n"); + return -EINVAL; + } + + switch (input_pixel_fmt) { + case 0x1: /* tile */ + case 0x0: /* generic data */ + case IPU_PIX_FMT_RGB666: + case IPU_PIX_FMT_RGB565: + case IPU_PIX_FMT_BGRA4444: + case IPU_PIX_FMT_BGRA5551: + case IPU_PIX_FMT_BGR24: + case IPU_PIX_FMT_RGB24: + case IPU_PIX_FMT_GBR24: + 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: + case IPU_PIX_FMT_YUYV: + case IPU_PIX_FMT_UYVY: + case IPU_PIX_FMT_YUV444: + case IPU_PIX_FMT_AYUV: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x0); + input_y_pitch = input_width * (input_bpp >> 3); + if (interlaced && input_pixel_fmt != 0x1) + input_y_pitch *= 2; + break; + case IPU_PIX_FMT_YUV444P: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x1); + input_y_pitch = input_width; + input_uv_pitch = input_width; + break; + case IPU_PIX_FMT_YUV422P: + case IPU_PIX_FMT_YVU422P: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x2); + input_y_pitch = input_width; + input_uv_pitch = input_width >> 1; + break; + case IPU_PIX_FMT_YUV420P2: + case IPU_PIX_FMT_YUV420P: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x3); + input_y_pitch = input_width; + input_uv_pitch = input_width >> 1; + break; + case PRE_PIX_FMT_NV61: + prefetch_ctrl |= BM_PRE_PREFETCH_ENGINE_CTRL_PARTIAL_UV_SWAP; + case IPU_PIX_FMT_NV16: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x4); + input_y_pitch = input_width; + input_uv_pitch = input_width; + break; + case PRE_PIX_FMT_NV21: + prefetch_ctrl |= BM_PRE_PREFETCH_ENGINE_CTRL_PARTIAL_UV_SWAP; + case IPU_PIX_FMT_NV12: + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_INPUT_PIXEL_FORMAT(0x5); + input_y_pitch = input_width; + input_uv_pitch = input_width; + break; + default: + spin_unlock_irqrestore(&pre->lock, lock_flags); + dev_err(pre->dev, "invalid input pixel format for prefetch engine\n"); + return -EINVAL; + } + + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_SHIFT_BYPASS(shift_bypass ? 1 : 0); + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_FIELD_INVERSE(field_inverse ? 1 : 0); + prefetch_ctrl |= BF_PRE_PREFETCH_ENGINE_CTRL_TPR_COOR_OFFSET_EN(tpr_coor_offset_en ? 1 : 0); + + pre_write(pre, BF_PRE_PREFETCH_ENGINE_INPUT_SIZE_INPUT_WIDTH(input_active_width) | + BF_PRE_PREFETCH_ENGINE_INPUT_SIZE_INPUT_HEIGHT(input_height), + HW_PRE_PREFETCH_ENGINE_INPUT_SIZE); + + if (tpr_coor_offset_en) + pre_write(pre, BF_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC_OUTPUT_SIZE_ULC_X(output_size.left) | + BF_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC_OUTPUT_SIZE_ULC_Y(output_size.top), + HW_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC); + + pre_write(pre, BF_PRE_PREFETCH_ENGINE_PITCH_INPUT_Y_PITCH(input_y_pitch) | + BF_PRE_PREFETCH_ENGINE_PITCH_INPUT_UV_PITCH(input_uv_pitch), + HW_PRE_PREFETCH_ENGINE_PITCH); + + pre_write(pre, BF_PRE_PREFETCH_ENGINE_INTERLACE_OFFSET_INTERLACE_OFFSET(interlace_offset), HW_PRE_PREFETCH_ENGINE_INTERLACE_OFFSET); + + pre_write(pre, prefetch_ctrl, HW_PRE_PREFETCH_ENGINE_CTRL); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} + +static int ipu_pre_store(unsigned int id, + bool store_en, + unsigned int write_burst, + unsigned int output_bpp, + /* this means the output + * width by prefetch + */ + unsigned int input_width, + unsigned int input_height, + unsigned int out_pitch, + unsigned int output_addr) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned int store_ctrl = 0; + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_STORE_EN(store_en ? 1 : 0); + + if (store_en) { + switch (write_burst) { + case 0x0: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_WR_NUM_BYTES(0x0); + break; + case 0x1: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_WR_NUM_BYTES(0x1); + break; + case 0x2: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_WR_NUM_BYTES(0x2); + break; + case 0x3: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_WR_NUM_BYTES(0x3); + break; + case 0x4: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_WR_NUM_BYTES(0x4); + break; + default: + spin_unlock_irqrestore(&pre->lock, lock_flags); + dev_err(pre->dev, "invalid write burst value for store engine\n"); + return -EINVAL; + } + + switch (output_bpp) { + case 8: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_OUTPUT_ACTIVE_BPP(0x0); + break; + case 16: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_OUTPUT_ACTIVE_BPP(0x1); + break; + case 32: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_OUTPUT_ACTIVE_BPP(0x2); + break; + case 64: + store_ctrl |= BF_PRE_STORE_ENGINE_CTRL_OUTPUT_ACTIVE_BPP(0x3); + break; + default: + spin_unlock_irqrestore(&pre->lock, lock_flags); + dev_err(pre->dev, "invalid ouput bpp for store engine\n"); + return -EINVAL; + } + + pre_write(pre, BF_PRE_STORE_ENGINE_SIZE_INPUT_TOTAL_WIDTH(input_width) | + BF_PRE_STORE_ENGINE_SIZE_INPUT_TOTAL_HEIGHT(input_height), + HW_PRE_STORE_ENGINE_SIZE); + + pre_write(pre, BF_PRE_STORE_ENGINE_PITCH_OUT_PITCH(out_pitch), + HW_PRE_STORE_ENGINE_PITCH); + + pre_write(pre, BF_PRE_STORE_ENGINE_ADDR_OUT_BASE_ADDR(output_addr), + HW_PRE_STORE_ENGINE_ADDR); + } + + pre_write(pre, store_ctrl, HW_PRE_STORE_ENGINE_CTRL); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} +/* End */ + +static irqreturn_t ipu_pre_irq_handle(int irq, void *dev_id) +{ + struct ipu_pre_data *pre = dev_id; + unsigned int irq_stat, axi_id = 0; + + spin_lock(&pre->lock); + irq_stat = pre_read(pre, HW_PRE_IRQ); + + if (irq_stat & BM_PRE_IRQ_HANDSHAKE_ABORT_IRQ) { + dev_warn(pre->dev, "handshake abort\n"); + pre_write(pre, BM_PRE_IRQ_HANDSHAKE_ABORT_IRQ, HW_PRE_IRQ_CLR); + } + + if (irq_stat & BM_PRE_IRQ_TPR_RD_NUM_BYTES_OVFL_IRQ) { + dev_warn(pre->dev, "tpr read num bytes overflow\n"); + pre_write(pre, BM_PRE_IRQ_TPR_RD_NUM_BYTES_OVFL_IRQ, + HW_PRE_IRQ_CLR); + } + + if (irq_stat & BM_PRE_IRQ_HANDSHAKE_ERROR_IRQ) { + dev_warn(pre->dev, "handshake error\n"); + pre_write(pre, BM_PRE_IRQ_HANDSHAKE_ERROR_IRQ, HW_PRE_IRQ_CLR); + } + + axi_id = (irq_stat & BM_PRE_IRQ_AXI_ERROR_ID) >> + BP_PRE_IRQ_AXI_ERROR_ID; + if (irq_stat & BM_PRE_IRQ_AXI_WRITE_ERROR) { + dev_warn(pre->dev, "AXI%d write error\n", axi_id); + pre_write(pre, BM_PRE_IRQ_AXI_WRITE_ERROR, HW_PRE_IRQ_CLR); + } + + if (irq_stat & BM_PRE_IRQ_AXI_READ_ERROR) { + dev_warn(pre->dev, "AXI%d read error\n", axi_id); + pre_write(pre, BM_PRE_IRQ_AXI_READ_ERROR, HW_PRE_IRQ_CLR); + } + spin_unlock(&pre->lock); + + return IRQ_HANDLED; +} + +static void ipu_pre_out_of_reset(unsigned int id) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, BF_PRE_CTRL_SFTRST(1) | BF_PRE_CTRL_CLKGATE(1), + HW_PRE_CTRL_CLR); + spin_unlock_irqrestore(&pre->lock, lock_flags); +} + +int ipu_pre_config(int id, struct ipu_pre_context *config) +{ + int ret = 0; + struct ipu_pre_data *pre = get_pre(id); + + if (!config || !pre) + return -EINVAL; + + config->store_addr = pre->double_buffer_paddr; + + if (!pre->enabled) + clk_prepare_enable(pre->clk); + + ipu_pre_out_of_reset(id); + + ret = ipu_pre_plane_buf_off_set(id, config->sec_buf_off, + config->trd_buf_off); + if (ret < 0) + goto out; + + ret = ipu_pre_tpr_set(id, config->tile_fmt); + if (ret < 0) + goto out; + + ret = ipu_pre_buf_set(id, config->cur_buf, config->next_buf); + if (ret < 0) + goto out; + + ret = ipu_pre_set_shift(id, config->prefetch_shift_offset, + config->prefetch_shift_width); + if (ret < 0) + goto out; + + ret = ipu_pre_prefetch(id, config->read_burst, config->prefetch_input_bpp, + config->prefetch_input_pixel_fmt, config->shift_bypass, + config->field_inverse, config->tpr_coor_offset_en, + config->prefetch_output_size, config->prefetch_input_width, + config->prefetch_input_height, + config->prefetch_input_active_width, + config->interlaced, + config->interlace_offset); + if (ret < 0) + goto out; + + ret = ipu_pre_store(id, config->store_en, + config->write_burst, config->store_output_bpp, + config->prefetch_output_size.width, config->prefetch_output_size.height, + config->store_pitch, + config->store_addr); + if (ret < 0) + goto out; + + ret = ipu_pre_set_ctrl(id, config->repeat, + config->vflip, config->handshake_en, + config->hsk_abort_en, config->hsk_line_num, + config->sdw_update, config->block_size, + config->interlaced, config->prefetch_mode); + + ipu_pre_irq_mask(pre, BM_PRE_IRQ_HANDSHAKE_ABORT_IRQ | + BM_PRE_IRQ_TPR_RD_NUM_BYTES_OVFL_IRQ | + BM_PRE_IRQ_HANDSHAKE_ERROR_IRQ, false); +out: + if (!pre->enabled) + clk_disable_unprepare(pre->clk); + + return ret; +} +EXPORT_SYMBOL(ipu_pre_config); + +int ipu_pre_enable(int id) +{ + int ret = 0; + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + if (pre->enabled) + return 0; + + clk_prepare_enable(pre->clk); + + /* start the pre engine */ + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, BF_PRE_CTRL_ENABLE(1), HW_PRE_CTRL_SET); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + pre->enabled = true; + + return ret; +} +EXPORT_SYMBOL(ipu_pre_enable); + +int ipu_pre_sdw_update(int id) +{ + int ret = 0; + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + if (!pre->enabled) + clk_prepare_enable(pre->clk); + + /* start the pre engine */ + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, BF_PRE_CTRL_SDW_UPDATE(1), HW_PRE_CTRL_SET); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + if (!pre->enabled) + clk_disable_unprepare(pre->clk); + + return ret; +} +EXPORT_SYMBOL(ipu_pre_sdw_update); + +void ipu_pre_disable(int id) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return; + + if (!pre->enabled) + return; + + /* stop the pre engine */ + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, BF_PRE_CTRL_ENABLE(1), HW_PRE_CTRL_CLR); + pre_write(pre, BF_PRE_CTRL_SDW_UPDATE(1), HW_PRE_CTRL_SET); + pre_write(pre, BF_PRE_CTRL_SFTRST(1), HW_PRE_CTRL_SET); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + clk_disable_unprepare(pre->clk); + + pre->enabled = false; +} +EXPORT_SYMBOL(ipu_pre_disable); + +int ipu_pre_set_fb_buffer(int id, unsigned long fb_paddr, + unsigned int x_crop, + unsigned int y_crop, + unsigned int sec_buf_off, + unsigned int trd_buf_off) +{ + struct ipu_pre_data *pre = get_pre(id); + unsigned long lock_flags; + + if (!pre) + return -EINVAL; + + spin_lock_irqsave(&pre->lock, lock_flags); + pre_write(pre, fb_paddr, HW_PRE_NEXT_BUF); + pre_write(pre, sec_buf_off, HW_PRE_U_BUF_OFFSET); + pre_write(pre, trd_buf_off, HW_PRE_V_BUF_OFFSET); + pre_write(pre, BF_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC_OUTPUT_SIZE_ULC_X(x_crop) | + BF_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC_OUTPUT_SIZE_ULC_Y(y_crop), + HW_PRE_PREFETCH_ENGINE_OUTPUT_SIZE_ULC); + pre_write(pre, BF_PRE_CTRL_SDW_UPDATE(1), HW_PRE_CTRL_SET); + spin_unlock_irqrestore(&pre->lock, lock_flags); + + return 0; +} +EXPORT_SYMBOL(ipu_pre_set_fb_buffer); + +static int ipu_pre_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct ipu_pre_data *pre; + struct resource *res; + int id, irq, err; + + pre = devm_kzalloc(&pdev->dev, sizeof(*pre), GFP_KERNEL); + if (!pre) + return -ENOMEM; + pre->dev = &pdev->dev; + + id = of_alias_get_id(np, "pre"); + if (id < 0) { + dev_err(&pdev->dev, "failed to get PRE id\n"); + return id; + } + pre->id = id; + + mutex_init(&pre->mutex); + spin_lock_init(&pre->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + pre->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(pre->base)) + return PTR_ERR(pre->base); + + pre->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pre->clk)) { + dev_err(&pdev->dev, "failed to get the pre clk\n"); + return PTR_ERR(pre->clk); + } + + irq = platform_get_irq(pdev, 0); + err = devm_request_irq(&pdev->dev, irq, ipu_pre_irq_handle, + IRQF_TRIGGER_RISING, pdev->name, pre); + if (err) { + dev_err(&pdev->dev, "failed to request pre irq\n"); + return err; + } + + pre->iram_pool = of_get_named_gen_pool(pdev->dev.of_node, "ocram", 0); + if (!pre->iram_pool) { + dev_err(&pdev->dev, "no iram exist for pre\n"); + return -ENOMEM; + } + + mutex_lock(&pre_list_lock); + list_add_tail(&pre->list, &pre_list); + mutex_unlock(&pre_list_lock); + + ipu_pre_alloc_double_buffer(pre->id, IPU_PRE_MAX_WIDTH * 8 * IPU_PRE_MAX_BPP); + + /* PRE GATE ON */ + clk_prepare_enable(pre->clk); + pre_write(pre, BF_PRE_CTRL_SFTRST(1) | BF_PRE_CTRL_CLKGATE(1), + HW_PRE_CTRL_CLR); + pre_write(pre, 0xf, HW_PRE_IRQ_MASK); + clk_disable_unprepare(pre->clk); + + platform_set_drvdata(pdev, pre); + + dev_info(&pdev->dev, "driver probed\n"); + + return 0; +} + +static int ipu_pre_remove(struct platform_device *pdev) +{ + struct ipu_pre_data *pre = platform_get_drvdata(pdev); + + if (pre->iram_pool && pre->double_buffer_base) { + gen_pool_free(pre->iram_pool, + pre->double_buffer_base, + pre->double_buffer_size); + } + + mutex_lock(&pre_list_lock); + list_del(&pre->list); + mutex_unlock(&pre_list_lock); + + return 0; +} + +static const struct of_device_id imx_ipu_pre_dt_ids[] = { + { .compatible = "fsl,imx6q-pre", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_ipu_pre_dt_ids); + +static struct platform_driver ipu_pre_driver = { + .driver = { + .name = "imx-pre", + .of_match_table = of_match_ptr(imx_ipu_pre_dt_ids), + }, + .probe = ipu_pre_probe, + .remove = ipu_pre_remove, +}; + +static int __init ipu_pre_init(void) +{ + return platform_driver_register(&ipu_pre_driver); +} +subsys_initcall(ipu_pre_init); + +static void __exit ipu_pre_exit(void) +{ + platform_driver_unregister(&ipu_pre_driver); +} +module_exit(ipu_pre_exit); + +MODULE_DESCRIPTION("i.MX PRE driver"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_LICENSE("GPL"); |