blob: e407ac724f46a4e85a1f9436712758b80910bddf [file] [log] [blame]
/*
* 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_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 & 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;
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_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;
}
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");