1002 lines
25 KiB
C
1002 lines
25 KiB
C
/*
|
|
* Copyright (C) 2014-2016 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_SPINLOCK(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;
|
|
unsigned long lock_flags;
|
|
|
|
spin_lock_irqsave(&pre_list_lock, lock_flags);
|
|
list_for_each_entry(pre, &pre_list, list) {
|
|
if (pre->id == id) {
|
|
spin_unlock_irqrestore(&pre_list_lock, lock_flags);
|
|
return pre;
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&pre_list_lock, lock_flags);
|
|
|
|
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 & 0x1f, HW_PRE_IRQ_MASK_CLR);
|
|
return;
|
|
}
|
|
pre_write(pre, mask & 0x1f, 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;
|
|
/* fall-through */
|
|
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;
|
|
/* fall-through */
|
|
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;
|
|
unsigned long lock_flags;
|
|
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_gen_pool_get(pdev->dev.of_node, "ocram", 0);
|
|
if (!pre->iram_pool) {
|
|
dev_err(&pdev->dev, "no iram exist for pre\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
spin_lock_irqsave(&pre_list_lock, lock_flags);
|
|
list_add_tail(&pre->list, &pre_list);
|
|
spin_unlock_irqrestore(&pre_list_lock, lock_flags);
|
|
|
|
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);
|
|
unsigned long lock_flags;
|
|
|
|
if (pre->iram_pool && pre->double_buffer_base) {
|
|
gen_pool_free(pre->iram_pool,
|
|
pre->double_buffer_base,
|
|
pre->double_buffer_size);
|
|
}
|
|
|
|
spin_lock_irqsave(&pre_list_lock, lock_flags);
|
|
list_del(&pre->list);
|
|
spin_unlock_irqrestore(&pre_list_lock, lock_flags);
|
|
|
|
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");
|