/* * Copyright (C) 2014-2015 Freescale Semiconductor, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include #include #include #include #include #include #include #include #include #include #include #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 */ int ipu_pre_set_ctrl(unsigned int id, struct ipu_pre_context *config) { struct ipu_pre_data *pre = get_pre(id); unsigned long lock_flags; int ret = 0; if (!pre) return -EINVAL; if (!pre->enabled) clk_prepare_enable(pre->clk); spin_lock_irqsave(&pre->lock, lock_flags); pre_write(pre, BF_PRE_CTRL_TPR_RESET_SEL(1), HW_PRE_CTRL_SET); if (config->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 (config->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 (config->handshake_en) { pre_write(pre, BF_PRE_CTRL_HANDSHAKE_EN(1), HW_PRE_CTRL_SET); if (config->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 (config->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 (config->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 (config->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 (config->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 (config->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); if (!pre->enabled) clk_disable_unprepare(pre->clk); return ret; } EXPORT_SYMBOL(ipu_pre_set_ctrl); 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; 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, bool resolve, unsigned long fb_paddr, unsigned int y_res, 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 int store_stat, store_block_y; unsigned long lock_flags; bool update = true; 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); /* * Update shadow only when store engine runs out of the problematic * window to workaround the SoC design bug recorded by errata ERR009624. */ if (y_res > IPU_PRE_SMALL_LINE) { unsigned long timeout = jiffies + msecs_to_jiffies(20); do { if (time_after(jiffies, timeout)) { update = false; dev_warn(pre->dev, "timeout waiting for PRE " "to run out of problematic window for " "shadow update\n"); break; } store_stat = pre_read(pre, HW_PRE_STORE_ENGINE_STATUS); store_block_y = (store_stat & BM_PRE_STORE_ENGINE_STATUS_STORE_BLOCK_Y) >> BP_PRE_STORE_ENGINE_STATUS_STORE_BLOCK_Y; } while (store_block_y >= (resolve ? DIV_ROUND_UP(y_res, 4) - 1 : y_res - 2) || store_block_y == 0); } if (update) 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");