blob: c6001b6a73d27ae54cedd72ec5a5696f55e999cd [file] [log] [blame]
/*
* Copyright 2004-2016 Freescale Semiconductor, Inc. All Rights Reserved.
*/
/*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
/*!
* @defgroup Framebuffer Framebuffer Driver for SDC and ADC.
*/
/*!
* @file mxcfb.c
*
* @brief MXC Frame buffer driver for SDC
*
* @ingroup Framebuffer
*/
/*!
* Include files
*/
#include <linux/clk.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/fb.h>
#include <linux/fsl_devices.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/ipu.h>
#include <linux/ipu-v3.h>
#include <linux/ipu-v3-pre.h>
#include <linux/ipu-v3-prg.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mxcfb.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/time.h>
#include <linux/uaccess.h>
#include "mxc_dispdrv.h"
/*
* Driver name
*/
#define MXCFB_NAME "mxc_sdc_fb"
/* Display port number */
#define MXCFB_PORT_NUM 2
/*!
* Structure containing the MXC specific framebuffer information.
*/
struct mxcfb_info {
int default_bpp;
int cur_blank;
int next_blank;
ipu_channel_t ipu_ch;
int ipu_id;
int ipu_di;
int pre_num;
u32 ipu_di_pix_fmt;
bool ipu_int_clk;
bool overlay;
bool alpha_chan_en;
bool late_init;
bool first_set_par;
bool resolve;
bool prefetch;
bool on_the_fly;
uint32_t final_pfmt;
unsigned long gpu_sec_buf_off;
unsigned long base;
uint32_t x_crop;
uint32_t y_crop;
unsigned int sec_buf_off;
unsigned int trd_buf_off;
dma_addr_t store_addr;
dma_addr_t alpha_phy_addr0;
dma_addr_t alpha_phy_addr1;
void *alpha_virt_addr0;
void *alpha_virt_addr1;
uint32_t alpha_mem_len;
uint32_t ipu_ch_irq;
uint32_t ipu_ch_nf_irq;
uint32_t ipu_alp_ch_irq;
uint32_t cur_ipu_buf;
uint32_t cur_ipu_alpha_buf;
u32 pseudo_palette[16];
bool mode_found;
struct completion flip_complete;
struct completion alpha_flip_complete;
struct completion vsync_complete;
struct completion otf_complete; /* on the fly */
void *ipu;
struct fb_info *ovfbi;
struct mxc_dispdrv_handle *dispdrv;
struct fb_var_screeninfo cur_var;
uint32_t cur_ipu_pfmt;
uint32_t cur_fb_pfmt;
bool cur_prefetch;
spinlock_t spin_lock; /* for PRE small yres cases */
struct ipu_pre_context *pre_config;
};
struct mxcfb_pfmt {
u32 fb_pix_fmt;
int bpp;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
};
struct mxcfb_tile_block {
u32 fb_pix_fmt;
int bw; /* in pixel */
int bh; /* in pixel */
};
#define NA (~0x0UL)
static const struct mxcfb_pfmt mxcfb_pfmts[] = {
/* pixel bpp red green blue transp */
{IPU_PIX_FMT_RGB565, 16, {11, 5, 0}, { 5, 6, 0}, { 0, 5, 0}, { 0, 0, 0} },
{IPU_PIX_FMT_BGRA4444, 16, { 8, 4, 0}, { 4, 4, 0}, { 0, 4, 0}, { 12, 4, 0} },
{IPU_PIX_FMT_BGRA5551, 16, {10, 5, 0}, { 5, 5, 0}, { 0, 5, 0}, { 15, 1, 0} },
{IPU_PIX_FMT_RGB24, 24, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, { 0, 0, 0} },
{IPU_PIX_FMT_BGR24, 24, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, { 0, 0, 0} },
{IPU_PIX_FMT_RGB32, 32, { 0, 8, 0}, { 8, 8, 0}, {16, 8, 0}, {24, 8, 0} },
{IPU_PIX_FMT_BGR32, 32, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0}, {24, 8, 0} },
{IPU_PIX_FMT_ABGR32, 32, {24, 8, 0}, {16, 8, 0}, { 8, 8, 0}, { 0, 8, 0} },
/* pixel bpp red green blue transp */
{IPU_PIX_FMT_YUV420P2, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YUV420P, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YVU420P, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_NV12, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{PRE_PIX_FMT_NV21, 12, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_NV16, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{PRE_PIX_FMT_NV61, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YUV422P, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YVU422P, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_UYVY, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YUYV, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YUV444, 24, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_YUV444P, 24, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_AYUV, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
/* pixel bpp red green blue transp */
{IPU_PIX_FMT_GPU32_SB_ST, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU32_SB_SRT, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU32_ST, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU32_SRT, 32, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU16_SB_ST, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU16_SB_SRT, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU16_ST, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
{IPU_PIX_FMT_GPU16_SRT, 16, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA}, {NA, NA, NA} },
};
/* Tile fb alignment */
static const struct mxcfb_tile_block tas[] = {
{IPU_PIX_FMT_GPU32_SB_ST, 16, 8},
{IPU_PIX_FMT_GPU32_SB_SRT, 64, 128},
{IPU_PIX_FMT_GPU32_ST, 16, 4},
{IPU_PIX_FMT_GPU32_SRT, 64, 64},
{IPU_PIX_FMT_GPU16_SB_ST, 16, 8},
{IPU_PIX_FMT_GPU16_SB_SRT, 64, 128},
{IPU_PIX_FMT_GPU16_ST, 16, 4},
{IPU_PIX_FMT_GPU16_SRT, 64, 64},
};
/* The block can be resolved */
static const struct mxcfb_tile_block trs[] = {
/* pixel w h */
{IPU_PIX_FMT_GPU32_SB_ST, 16, 4},
{IPU_PIX_FMT_GPU32_SB_SRT, 16, 4},
{IPU_PIX_FMT_GPU32_ST, 16, 4},
{IPU_PIX_FMT_GPU32_SRT, 16, 4},
{IPU_PIX_FMT_GPU16_SB_ST, 16, 4},
{IPU_PIX_FMT_GPU16_SB_SRT, 16, 4},
{IPU_PIX_FMT_GPU16_ST, 16, 4},
{IPU_PIX_FMT_GPU16_SRT, 16, 4},
};
struct mxcfb_alloc_list {
struct list_head list;
dma_addr_t phy_addr;
void *cpu_addr;
u32 size;
};
enum {
BOTH_ON,
SRC_ON,
TGT_ON,
BOTH_OFF
};
static bool g_dp_in_use[2];
LIST_HEAD(fb_alloc_list);
/* Return default standard(RGB) pixel format */
static uint32_t bpp_to_pixfmt(int bpp)
{
uint32_t pixfmt = 0;
switch (bpp) {
case 24:
pixfmt = IPU_PIX_FMT_BGR24;
break;
case 32:
pixfmt = IPU_PIX_FMT_BGR32;
break;
case 16:
pixfmt = IPU_PIX_FMT_RGB565;
break;
}
return pixfmt;
}
static inline int bitfield_is_equal(struct fb_bitfield f1,
struct fb_bitfield f2)
{
return !memcmp(&f1, &f2, sizeof(f1));
}
static int pixfmt_to_var(uint32_t pixfmt, struct fb_var_screeninfo *var)
{
int i, ret = -1;
for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
if (pixfmt == mxcfb_pfmts[i].fb_pix_fmt) {
var->red = mxcfb_pfmts[i].red;
var->green = mxcfb_pfmts[i].green;
var->blue = mxcfb_pfmts[i].blue;
var->transp = mxcfb_pfmts[i].transp;
var->bits_per_pixel = mxcfb_pfmts[i].bpp;
ret = 0;
break;
}
}
return ret;
}
static int bpp_to_var(int bpp, struct fb_var_screeninfo *var)
{
uint32_t pixfmt = 0;
if (var->nonstd)
return -1;
pixfmt = bpp_to_pixfmt(bpp);
if (pixfmt)
return pixfmt_to_var(pixfmt, var);
else
return -1;
}
static int check_var_pixfmt(struct fb_var_screeninfo *var)
{
int i, ret = -1;
if (var->nonstd) {
for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
if (mxcfb_pfmts[i].fb_pix_fmt == var->nonstd) {
var->bits_per_pixel = mxcfb_pfmts[i].bpp;
ret = 0;
break;
}
}
return ret;
}
for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
if (bitfield_is_equal(var->red, mxcfb_pfmts[i].red) &&
bitfield_is_equal(var->green, mxcfb_pfmts[i].green) &&
bitfield_is_equal(var->blue, mxcfb_pfmts[i].blue) &&
bitfield_is_equal(var->transp, mxcfb_pfmts[i].transp) &&
var->bits_per_pixel == mxcfb_pfmts[i].bpp) {
ret = 0;
break;
}
}
return ret;
}
static uint32_t fb_to_store_pixfmt(uint32_t fb_pixfmt)
{
switch (fb_pixfmt) {
case IPU_PIX_FMT_RGB32:
case IPU_PIX_FMT_BGR32:
case IPU_PIX_FMT_ABGR32:
case IPU_PIX_FMT_RGB24:
case IPU_PIX_FMT_BGR24:
case IPU_PIX_FMT_RGB565:
case IPU_PIX_FMT_BGRA4444:
case IPU_PIX_FMT_BGRA5551:
case IPU_PIX_FMT_UYVY:
case IPU_PIX_FMT_YUYV:
case IPU_PIX_FMT_YUV444:
case IPU_PIX_FMT_AYUV:
return fb_pixfmt;
case IPU_PIX_FMT_YUV422P:
case IPU_PIX_FMT_YVU422P:
case IPU_PIX_FMT_YUV420P2:
case IPU_PIX_FMT_YVU420P:
case IPU_PIX_FMT_NV12:
case PRE_PIX_FMT_NV21:
case IPU_PIX_FMT_NV16:
case PRE_PIX_FMT_NV61:
case IPU_PIX_FMT_YUV420P:
return IPU_PIX_FMT_UYVY;
case IPU_PIX_FMT_YUV444P:
return IPU_PIX_FMT_AYUV;
default:
return 0;
}
}
static uint32_t fbi_to_pixfmt(struct fb_info *fbi, bool original_fb)
{
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
int i;
uint32_t pixfmt = 0;
if (fbi->var.nonstd) {
if (mxc_fbi->prefetch && !original_fb) {
if (ipu_pixel_format_is_gpu_tile(fbi->var.nonstd))
goto next;
return fb_to_store_pixfmt(fbi->var.nonstd);
} else {
return fbi->var.nonstd;
}
}
next:
for (i = 0; i < ARRAY_SIZE(mxcfb_pfmts); i++) {
if (bitfield_is_equal(fbi->var.red, mxcfb_pfmts[i].red) &&
bitfield_is_equal(fbi->var.green, mxcfb_pfmts[i].green) &&
bitfield_is_equal(fbi->var.blue, mxcfb_pfmts[i].blue) &&
bitfield_is_equal(fbi->var.transp, mxcfb_pfmts[i].transp)) {
pixfmt = mxcfb_pfmts[i].fb_pix_fmt;
break;
}
}
if (pixfmt == 0)
dev_err(fbi->device, "cannot get pixel format\n");
return pixfmt;
}
static void fmt_to_tile_alignment(uint32_t fmt, int *bw, int *bh)
{
int i;
for (i = 0; i < ARRAY_SIZE(tas); i++) {
if (tas[i].fb_pix_fmt == fmt) {
*bw = tas[i].bw;
*bh = tas[i].bh;
}
}
BUG_ON(!(*bw) || !(*bh));
}
static void fmt_to_tile_block(uint32_t fmt, int *bw, int *bh)
{
int i;
for (i = 0; i < ARRAY_SIZE(trs); i++) {
if (trs[i].fb_pix_fmt == fmt) {
*bw = trs[i].bw;
*bh = trs[i].bh;
}
}
BUG_ON(!(*bw) || !(*bh));
}
static struct fb_info *found_registered_fb(ipu_channel_t ipu_ch, int ipu_id)
{
int i;
struct mxcfb_info *mxc_fbi;
struct fb_info *fbi = NULL;
for (i = 0; i < num_registered_fb; i++) {
mxc_fbi =
((struct mxcfb_info *)(registered_fb[i]->par));
if ((mxc_fbi->ipu_ch == ipu_ch) &&
(mxc_fbi->ipu_id == ipu_id)) {
fbi = registered_fb[i];
break;
}
}
return fbi;
}
static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id);
static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id);
static int mxcfb_blank(int blank, struct fb_info *info);
static int mxcfb_map_video_memory(struct fb_info *fbi);
static int mxcfb_unmap_video_memory(struct fb_info *fbi);
static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd,
unsigned long arg);
/*
* Set fixed framebuffer parameters based on variable settings.
*
* @param info framebuffer information pointer
*/
static int mxcfb_set_fix(struct fb_info *info)
{
struct fb_fix_screeninfo *fix = &info->fix;
struct fb_var_screeninfo *var = &info->var;
fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
fix->type = FB_TYPE_PACKED_PIXELS;
fix->accel = FB_ACCEL_NONE;
fix->visual = FB_VISUAL_TRUECOLOR;
fix->xpanstep = 1;
fix->ywrapstep = 1;
fix->ypanstep = 1;
return 0;
}
static int _setup_disp_channel1(struct fb_info *fbi)
{
ipu_channel_params_t params;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
memset(&params, 0, sizeof(params));
if (mxc_fbi->ipu_ch == MEM_DC_SYNC) {
params.mem_dc_sync.di = mxc_fbi->ipu_di;
if (fbi->var.vmode & FB_VMODE_INTERLACED)
params.mem_dc_sync.interlaced = true;
params.mem_dc_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
params.mem_dc_sync.in_pixel_fmt = mxc_fbi->on_the_fly ?
mxc_fbi->final_pfmt :
fbi_to_pixfmt(fbi, false);
} else {
params.mem_dp_bg_sync.di = mxc_fbi->ipu_di;
if (fbi->var.vmode & FB_VMODE_INTERLACED)
params.mem_dp_bg_sync.interlaced = true;
params.mem_dp_bg_sync.out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
params.mem_dp_bg_sync.in_pixel_fmt = mxc_fbi->on_the_fly ?
mxc_fbi->final_pfmt :
fbi_to_pixfmt(fbi, false);
if (mxc_fbi->alpha_chan_en)
params.mem_dp_bg_sync.alpha_chan_en = true;
}
if (ipu_init_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, &params) < 0) {
dev_err(fbi->device, "init ipu channel fail\n");
return -EINVAL;
}
return 0;
}
static int _setup_disp_channel2(struct fb_info *fbi)
{
int retval = 0;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
int fb_stride, ipu_stride, bw = 0, bh = 0;
unsigned long base, ipu_base;
unsigned int fr_xoff, fr_yoff, fr_w, fr_h;
unsigned int prg_width;
struct ipu_pre_context pre;
bool post_pre_disable = false;
switch (fbi_to_pixfmt(fbi, true)) {
case IPU_PIX_FMT_YUV420P2:
case IPU_PIX_FMT_YVU420P:
case IPU_PIX_FMT_NV12:
case PRE_PIX_FMT_NV21:
case IPU_PIX_FMT_NV16:
case PRE_PIX_FMT_NV61:
case IPU_PIX_FMT_YUV422P:
case IPU_PIX_FMT_YVU422P:
case IPU_PIX_FMT_YUV420P:
case IPU_PIX_FMT_YUV444P:
fb_stride = fbi->var.xres_virtual;
break;
default:
fb_stride = fbi->fix.line_length;
}
base = fbi->fix.smem_start;
fr_xoff = fbi->var.xoffset;
fr_w = fbi->var.xres_virtual;
if (!(fbi->var.vmode & FB_VMODE_YWRAP)) {
dev_dbg(fbi->device, "Y wrap disabled\n");
fr_yoff = fbi->var.yoffset % fbi->var.yres;
fr_h = fbi->var.yres;
base += fbi->fix.line_length * fbi->var.yres *
(fbi->var.yoffset / fbi->var.yres);
if (ipu_pixel_format_is_split_gpu_tile(fbi->var.nonstd))
base += (mxc_fbi->gpu_sec_buf_off -
fbi->fix.line_length * fbi->var.yres / 2) *
(fbi->var.yoffset / fbi->var.yres);
} else {
dev_dbg(fbi->device, "Y wrap enabled\n");
fr_yoff = fbi->var.yoffset;
fr_h = fbi->var.yres_virtual;
}
/* pixel block alignment for resolving cases */
if (mxc_fbi->resolve) {
fmt_to_tile_block(fbi->var.nonstd, &bw, &bh);
} else {
base += fr_yoff * fb_stride + fr_xoff *
bytes_per_pixel(fbi_to_pixfmt(fbi, true));
}
if (!mxc_fbi->on_the_fly)
mxc_fbi->cur_ipu_buf = 2;
init_completion(&mxc_fbi->flip_complete);
/*
* We don't need to wait for vsync at the first time
* we do pan display after fb is initialized, as IPU will
* switch to the newly selected buffer automatically,
* so we call complete() for both mxc_fbi->flip_complete
* and mxc_fbi->alpha_flip_complete.
*/
if (!mxc_fbi->prefetch ||
(mxc_fbi->prefetch && !ipu_pre_yres_is_small(fbi->var.yres)))
complete(&mxc_fbi->flip_complete);
if (mxc_fbi->alpha_chan_en) {
mxc_fbi->cur_ipu_alpha_buf = 1;
init_completion(&mxc_fbi->alpha_flip_complete);
complete(&mxc_fbi->alpha_flip_complete);
}
if (mxc_fbi->prefetch) {
struct ipu_prg_config prg;
struct fb_var_screeninfo from_var, to_var;
if (mxc_fbi->pre_num < 0) {
mxc_fbi->pre_num = ipu_pre_alloc(mxc_fbi->ipu_id,
mxc_fbi->ipu_ch);
if (mxc_fbi->pre_num < 0) {
dev_dbg(fbi->device, "failed to alloc PRE\n");
mxc_fbi->prefetch = mxc_fbi->cur_prefetch;
mxc_fbi->resolve = false;
if (!mxc_fbi->on_the_fly)
mxc_fbi->cur_blank = FB_BLANK_POWERDOWN;
return mxc_fbi->pre_num;
}
}
pre.repeat = true;
pre.vflip = fbi->var.rotate ? true : false;
pre.handshake_en = true;
pre.hsk_abort_en = true;
pre.hsk_line_num = 0;
pre.sdw_update = true;
pre.cur_buf = base;
pre.next_buf = pre.cur_buf;
if (fbi->var.vmode & FB_VMODE_INTERLACED) {
pre.interlaced = 2;
if (mxc_fbi->resolve) {
pre.field_inverse = fbi->var.rotate;
pre.interlace_offset = 0;
} else {
pre.field_inverse = 0;
if (fbi->var.rotate) {
pre.interlace_offset = ~(fbi->var.xres_virtual *
bytes_per_pixel(fbi_to_pixfmt(fbi, true))) + 1;
pre.cur_buf += fbi->var.xres_virtual * bytes_per_pixel(fbi_to_pixfmt(fbi, true));
pre.next_buf = pre.cur_buf;
} else {
pre.interlace_offset = fbi->var.xres_virtual *
bytes_per_pixel(fbi_to_pixfmt(fbi, true));
}
}
} else {
pre.interlaced = 0;
pre.interlace_offset = 0;
}
pre.prefetch_mode = mxc_fbi->resolve ? 1 : 0;
pre.tile_fmt = mxc_fbi->resolve ? fbi->var.nonstd : 0;
pre.read_burst = mxc_fbi->resolve ? 0x4 : 0x3;
if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444) {
if ((fbi->var.xres * 3) % 8 == 0 &&
(fbi->var.xres_virtual * 3) % 8 == 0)
pre.prefetch_input_bpp = 64;
else if ((fbi->var.xres * 3) % 4 == 0 &&
(fbi->var.xres_virtual * 3) % 4 == 0)
pre.prefetch_input_bpp = 32;
else if ((fbi->var.xres * 3) % 2 == 0 &&
(fbi->var.xres_virtual * 3) % 2 == 0)
pre.prefetch_input_bpp = 16;
else
pre.prefetch_input_bpp = 8;
} else {
pre.prefetch_input_bpp =
8 * bytes_per_pixel(fbi_to_pixfmt(fbi, true));
}
pre.prefetch_input_pixel_fmt = mxc_fbi->resolve ?
0x1 : (fbi->var.nonstd ? fbi->var.nonstd : 0);
pre.shift_bypass = (mxc_fbi->on_the_fly &&
mxc_fbi->final_pfmt != fbi_to_pixfmt(fbi, false)) ?
false : true;
pixfmt_to_var(fbi_to_pixfmt(fbi, false), &from_var);
pixfmt_to_var(mxc_fbi->final_pfmt, &to_var);
if (mxc_fbi->on_the_fly &&
(format_to_colorspace(fbi_to_pixfmt(fbi, true)) == RGB) &&
(bytes_per_pixel(fbi_to_pixfmt(fbi, true)) == 4)) {
pre.prefetch_shift_offset = (from_var.red.offset << to_var.red.offset) |
(from_var.green.offset << to_var.green.offset) |
(from_var.blue.offset << to_var.blue.offset) |
(from_var.transp.offset << to_var.transp.offset);
pre.prefetch_shift_width = (to_var.red.length << (to_var.red.offset/2)) |
(to_var.green.length << (to_var.green.offset/2)) |
(to_var.blue.length << (to_var.blue.offset/2)) |
(to_var.transp.length << (to_var.transp.offset/2));
} else {
pre.prefetch_shift_offset = 0;
pre.prefetch_shift_width = 0;
}
pre.tpr_coor_offset_en = mxc_fbi->resolve ? true : false;
pre.prefetch_output_size.left = mxc_fbi->resolve ? (fr_xoff & ~(bw - 1)) : 0;
pre.prefetch_output_size.top = mxc_fbi->resolve ? (fr_yoff & ~(bh - 1)) : 0;
if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444) {
pre.prefetch_output_size.width = (fbi->var.xres * 3) /
(pre.prefetch_input_bpp / 8);
pre.store_output_bpp = pre.prefetch_input_bpp;
} else {
pre.prefetch_output_size.width = fbi->var.xres;
pre.store_output_bpp = 8 *
bytes_per_pixel(fbi_to_pixfmt(fbi, false));
}
pre.prefetch_output_size.height = fbi->var.yres;
pre.prefetch_input_active_width = pre.prefetch_output_size.width;
if (fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_RGB24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_BGR24 ||
fbi_to_pixfmt(fbi, true) == IPU_PIX_FMT_YUV444)
pre.prefetch_input_width = (fbi->var.xres_virtual * 3) /
(pre.prefetch_input_bpp / 8);
else
pre.prefetch_input_width = fbi->var.xres_virtual;
pre.prefetch_input_height = fbi->var.yres;
prg_width = pre.prefetch_output_size.width;
if (!(pre.prefetch_input_active_width % 32))
pre.block_size = 0;
else if (!(pre.prefetch_input_active_width % 16))
pre.block_size = 1;
else
pre.block_size = 0;
if (mxc_fbi->resolve) {
int bs = pre.block_size ? 16 : 32;
pre.prefetch_input_active_width += fr_xoff % bw;
if (((fr_xoff % bw) + pre.prefetch_input_active_width) % bs)
pre.prefetch_input_active_width =
ALIGN(pre.prefetch_input_active_width, bs);
pre.prefetch_output_size.width =
pre.prefetch_input_active_width;
prg_width = pre.prefetch_output_size.width;
pre.prefetch_input_height += fr_yoff % bh;
if (((fr_yoff % bh) + fbi->var.yres) % 4) {
pre.prefetch_input_height =
(fbi->var.vmode & FB_VMODE_INTERLACED) ?
ALIGN(pre.prefetch_input_height, 8) :
ALIGN(pre.prefetch_input_height, 4);
} else {
if (fbi->var.vmode & FB_VMODE_INTERLACED)
pre.prefetch_input_height =
ALIGN(pre.prefetch_input_height, 8);
}
pre.prefetch_output_size.height = pre.prefetch_input_height;
}
/* store output pitch 8-byte aligned */
while ((pre.store_output_bpp * prg_width) % 64)
prg_width++;
pre.store_pitch = (pre.store_output_bpp * prg_width) / 8;
pre.store_en = true;
pre.write_burst = 0x3;
ipu_get_channel_offset(fbi_to_pixfmt(fbi, true),
fbi->var.xres,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff,
&pre.sec_buf_off,
&pre.trd_buf_off);
if (mxc_fbi->resolve)
pre.sec_buf_off = mxc_fbi->gpu_sec_buf_off;
ipu_pre_config(mxc_fbi->pre_num, &pre);
ipu_stride = pre.store_pitch;
ipu_base = pre.store_addr;
mxc_fbi->store_addr = ipu_base;
if (mxc_fbi->cur_prefetch && mxc_fbi->on_the_fly) {
/*
* Make sure any pending interrupt is handled so that
* the buffer panned can start to be scanned out.
*/
if (!ipu_pre_yres_is_small(fbi->var.yres)) {
mxcfb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
(unsigned long)fbi->par);
mxcfb_ioctl(fbi, MXCFB_WAIT_FOR_VSYNC,
(unsigned long)fbi->par);
}
mxc_fbi->pre_config = &pre;
/*
* Write the PRE control register in the flip interrupt
* handler in this on-the-fly case to workaround the
* SoC design bug recorded by errata ERR009624.
*/
init_completion(&mxc_fbi->otf_complete);
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
retval = wait_for_completion_timeout(
&mxc_fbi->otf_complete, HZ/2);
if (retval == 0) {
dev_err(fbi->device, "timeout when waiting "
"for on the fly config irq\n");
return -ETIMEDOUT;
} else {
retval = 0;
}
} else {
retval = ipu_pre_set_ctrl(mxc_fbi->pre_num, &pre);
if (retval < 0)
return retval;
}
if (!mxc_fbi->on_the_fly || !mxc_fbi->cur_prefetch) {
prg.id = mxc_fbi->ipu_id;
prg.pre_num = mxc_fbi->pre_num;
prg.ipu_ch = mxc_fbi->ipu_ch;
prg.so = (fbi->var.vmode & FB_VMODE_INTERLACED) ?
PRG_SO_INTERLACE : PRG_SO_PROGRESSIVE;
prg.vflip = fbi->var.rotate ? true : false;
prg.block_mode = mxc_fbi->resolve ? PRG_BLOCK_MODE : PRG_SCAN_MODE;
prg.stride = (fbi->var.vmode & FB_VMODE_INTERLACED) ?
ipu_stride * 2 : ipu_stride;
prg.ilo = (fbi->var.vmode & FB_VMODE_INTERLACED) ?
ipu_stride : 0;
prg.height = mxc_fbi->resolve ?
pre.prefetch_output_size.height : fbi->var.yres;
prg.ipu_height = fbi->var.yres;
prg.crop_line = mxc_fbi->resolve ?
((fbi->var.vmode & FB_VMODE_INTERLACED) ? (fr_yoff % bh) / 2 : fr_yoff % bh) : 0;
prg.baddr = pre.store_addr;
prg.offset = mxc_fbi->resolve ? (prg.crop_line * prg.stride +
(fr_xoff % bw) *
bytes_per_pixel(fbi_to_pixfmt(fbi, false))) : 0;
ipu_base += prg.offset;
if (ipu_base % 8) {
dev_err(fbi->device,
"IPU base address is not 8byte aligned\n");
return -EINVAL;
}
mxc_fbi->store_addr = ipu_base;
if (!mxc_fbi->on_the_fly) {
retval = ipu_init_channel_buffer(mxc_fbi->ipu,
mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
fbi_to_pixfmt(fbi, false),
fbi->var.xres, fbi->var.yres,
ipu_stride,
fbi->var.rotate,
ipu_base,
ipu_base,
fbi->var.accel_flags &
FB_ACCEL_DOUBLE_FLAG ? 0 : ipu_base,
0, 0);
if (retval) {
dev_err(fbi->device,
"ipu_init_channel_buffer error %d\n", retval);
return retval;
}
}
retval = ipu_prg_config(&prg);
if (retval < 0) {
dev_err(fbi->device,
"failed to configure PRG %d\n", retval);
return retval;
}
retval = ipu_pre_enable(mxc_fbi->pre_num);
if (retval < 0) {
ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num);
dev_err(fbi->device,
"failed to enable PRE %d\n", retval);
return retval;
}
retval = ipu_prg_wait_buf_ready(mxc_fbi->ipu_id,
mxc_fbi->pre_num,
pre.hsk_line_num,
pre.prefetch_output_size.height);
if (retval < 0) {
ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num);
ipu_pre_disable(mxc_fbi->pre_num);
ipu_pre_free(&mxc_fbi->pre_num);
dev_err(fbi->device, "failed to wait PRG ready %d\n", retval);
return retval;
}
}
} else {
ipu_stride = fb_stride;
ipu_base = base;
if (mxc_fbi->on_the_fly)
post_pre_disable = true;
}
if (mxc_fbi->on_the_fly && ((mxc_fbi->cur_prefetch && !mxc_fbi->prefetch) ||
(!mxc_fbi->cur_prefetch && mxc_fbi->prefetch))) {
int htotal = fbi->var.xres + fbi->var.right_margin +
fbi->var.hsync_len + fbi->var.left_margin;
int vtotal = fbi->var.yres + fbi->var.lower_margin +
fbi->var.vsync_len + fbi->var.upper_margin;
int timeout = ((htotal * vtotal) / PICOS2KHZ(fbi->var.pixclock)) * 2 ;
int cur_buf = 0;
BUG_ON(timeout <= 0);
++mxc_fbi->cur_ipu_buf;
mxc_fbi->cur_ipu_buf %= 3;
if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER,
mxc_fbi->cur_ipu_buf,
ipu_base) == 0) {
if (!mxc_fbi->prefetch)
ipu_update_channel_offset(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER,
fbi_to_pixfmt(fbi, true),
fr_w,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff);
ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf);
for (; timeout > 0; timeout--) {
cur_buf = ipu_get_cur_buffer_idx(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER);
if (cur_buf == mxc_fbi->cur_ipu_buf)
break;
udelay(1000);
}
if (!timeout)
dev_err(fbi->device, "Timeout for switch to buf %d "
"to address=0x%08lX, current buf %d, "
"buf0 ready %d, buf1 ready %d, buf2 ready "
"%d\n", mxc_fbi->cur_ipu_buf, ipu_base,
ipu_get_cur_buffer_idx(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER),
ipu_check_buffer_ready(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 0),
ipu_check_buffer_ready(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 1),
ipu_check_buffer_ready(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 2));
}
} else if (!mxc_fbi->on_the_fly && !mxc_fbi->prefetch) {
retval = ipu_init_channel_buffer(mxc_fbi->ipu,
mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
mxc_fbi->on_the_fly ? mxc_fbi->final_pfmt :
fbi_to_pixfmt(fbi, false),
fbi->var.xres, fbi->var.yres,
ipu_stride,
fbi->var.rotate,
ipu_base,
ipu_base,
fbi->var.accel_flags &
FB_ACCEL_DOUBLE_FLAG ? 0 : ipu_base,
0, 0);
if (retval) {
dev_err(fbi->device,
"ipu_init_channel_buffer error %d\n", retval);
return retval;
}
/* update u/v offset */
if (!mxc_fbi->prefetch)
ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER,
fbi_to_pixfmt(fbi, true),
fr_w,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff);
}
if (post_pre_disable) {
ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num);
ipu_pre_disable(mxc_fbi->pre_num);
ipu_pre_free(&mxc_fbi->pre_num);
}
if (mxc_fbi->alpha_chan_en) {
retval = ipu_init_channel_buffer(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
IPU_ALPHA_IN_BUFFER,
IPU_PIX_FMT_GENERIC,
fbi->var.xres, fbi->var.yres,
fbi->var.xres,
fbi->var.rotate,
mxc_fbi->alpha_phy_addr1,
mxc_fbi->alpha_phy_addr0,
0,
0, 0);
if (retval) {
dev_err(fbi->device,
"ipu_init_channel_buffer error %d\n", retval);
return retval;
}
}
return retval;
}
static bool mxcfb_need_to_set_par(struct fb_info *fbi)
{
struct mxcfb_info *mxc_fbi = fbi->par;
if ((fbi->var.activate & FB_ACTIVATE_FORCE) &&
(fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW)
return true;
/*
* Ignore xoffset and yoffset update,
* because pan display handles this case.
*/
mxc_fbi->cur_var.xoffset = fbi->var.xoffset;
mxc_fbi->cur_var.yoffset = fbi->var.yoffset;
return !!memcmp(&mxc_fbi->cur_var, &fbi->var,
sizeof(struct fb_var_screeninfo));
}
static bool mxcfb_can_set_par_on_the_fly(struct fb_info *fbi,
uint32_t *final_pfmt)
{
struct mxcfb_info *mxc_fbi = fbi->par;
struct fb_var_screeninfo cur_var = mxc_fbi->cur_var;
uint32_t cur_pfmt = mxc_fbi->cur_ipu_pfmt;
uint32_t new_pfmt = fbi_to_pixfmt(fbi, false);
uint32_t new_fb_pfmt = fbi_to_pixfmt(fbi, true);
int cur_bpp, new_bpp, cur_bw, cur_bh, new_bw, new_bh;
unsigned int mem_len;
ipu_color_space_t cur_space, new_space;
cur_space = format_to_colorspace(cur_pfmt);
new_space = format_to_colorspace(new_pfmt);
cur_bpp = bytes_per_pixel(cur_pfmt);
new_bpp = bytes_per_pixel(new_pfmt);
if (mxc_fbi->first_set_par || mxc_fbi->cur_blank != FB_BLANK_UNBLANK)
return false;
if (!mxc_fbi->prefetch && !mxc_fbi->cur_prefetch)
return false;
if (!mxc_fbi->prefetch && cur_pfmt != new_pfmt)
return false;
if (cur_space == RGB && (cur_bpp == 2 || cur_bpp == 3) &&
cur_pfmt != new_pfmt)
return false;
if (!(mxc_fbi->cur_prefetch && mxc_fbi->prefetch) &&
((cur_var.xres_virtual != fbi->var.xres_virtual) ||
(cur_var.xres != cur_var.xres_virtual) ||
(fbi->var.xres != fbi->var.xres_virtual)))
return false;
if (cur_space != new_space ||
(new_space == RGB && cur_bpp != new_bpp))
return false;
if (new_space == YCbCr)
return false;
mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) {
if (fbi->var.vmode & FB_VMODE_YWRAP)
mem_len = mxc_fbi->gpu_sec_buf_off + mem_len / 2;
else
mem_len = mxc_fbi->gpu_sec_buf_off *
(fbi->var.yres_virtual / fbi->var.yres) + mem_len / 2;
}
if (mem_len > fbi->fix.smem_len)
return false;
if (mxc_fbi->resolve && ipu_pixel_format_is_gpu_tile(mxc_fbi->cur_fb_pfmt)) {
fmt_to_tile_block(mxc_fbi->cur_fb_pfmt, &cur_bw, &cur_bh);
fmt_to_tile_block(new_fb_pfmt, &new_bw, &new_bh);
if (cur_bw != new_bw || cur_bh != new_bh ||
cur_var.xoffset % cur_bw != fbi->var.xoffset % new_bw ||
cur_var.yoffset % cur_bh != fbi->var.yoffset % new_bh)
return false;
} else if (mxc_fbi->resolve && mxc_fbi->cur_prefetch) {
fmt_to_tile_block(new_fb_pfmt, &new_bw, &new_bh);
if (fbi->var.xoffset % new_bw || fbi->var.yoffset % new_bh ||
fbi->var.xres % 16 || fbi->var.yres %
(fbi->var.vmode & FB_VMODE_INTERLACED ? 8 : 4))
return false;
} else if (mxc_fbi->prefetch && ipu_pixel_format_is_gpu_tile(mxc_fbi->cur_fb_pfmt)) {
fmt_to_tile_block(mxc_fbi->cur_fb_pfmt, &cur_bw, &cur_bh);
if (cur_var.xoffset % cur_bw || cur_var.yoffset % cur_bh ||
cur_var.xres % 16 || cur_var.yres %
(cur_var.vmode & FB_VMODE_INTERLACED ? 8 : 4))
return false;
}
cur_var.xres_virtual = fbi->var.xres_virtual;
cur_var.yres_virtual = fbi->var.yres_virtual;
cur_var.xoffset = fbi->var.xoffset;
cur_var.yoffset = fbi->var.yoffset;
cur_var.red = fbi->var.red;
cur_var.green = fbi->var.green;
cur_var.blue = fbi->var.blue;
cur_var.transp = fbi->var.transp;
cur_var.nonstd = fbi->var.nonstd;
if (memcmp(&cur_var, &fbi->var,
sizeof(struct fb_var_screeninfo)))
return false;
*final_pfmt = cur_pfmt;
return true;
}
static void mxcfb_check_resolve(struct fb_info *fbi)
{
struct mxcfb_info *mxc_fbi = fbi->par;
switch (fbi->var.nonstd) {
case IPU_PIX_FMT_GPU32_ST:
case IPU_PIX_FMT_GPU32_SRT:
case IPU_PIX_FMT_GPU16_ST:
case IPU_PIX_FMT_GPU16_SRT:
mxc_fbi->gpu_sec_buf_off = 0;
case IPU_PIX_FMT_GPU32_SB_ST:
case IPU_PIX_FMT_GPU32_SB_SRT:
case IPU_PIX_FMT_GPU16_SB_ST:
case IPU_PIX_FMT_GPU16_SB_SRT:
mxc_fbi->prefetch = true;
mxc_fbi->resolve = true;
break;
default:
mxc_fbi->resolve = false;
}
}
static void mxcfb_check_yuv(struct fb_info *fbi)
{
struct mxcfb_info *mxc_fbi = fbi->par;
if (fbi->var.vmode & FB_VMODE_INTERLACED) {
if (ipu_pixel_format_is_multiplanar_yuv(fbi_to_pixfmt(fbi, true)))
mxc_fbi->prefetch = false;
} else {
if (fbi->var.nonstd == PRE_PIX_FMT_NV21 ||
fbi->var.nonstd == PRE_PIX_FMT_NV61)
mxc_fbi->prefetch = true;
}
}
/*
* Set framebuffer parameters and change the operating mode.
*
* @param info framebuffer information pointer
*/
static int mxcfb_set_par(struct fb_info *fbi)
{
int retval = 0;
u32 mem_len, alpha_mem_len;
ipu_di_signal_cfg_t sig_cfg;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
uint32_t final_pfmt = 0;
int16_t ov_pos_x = 0, ov_pos_y = 0;
int ov_pos_ret = 0;
struct mxcfb_info *mxc_fbi_fg = NULL;
bool ovfbi_enable = false, on_the_fly;
if (ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi, true)) &&
mxc_fbi->alpha_chan_en) {
dev_err(fbi->device, "Bad pixel format for "
"graphics plane fb\n");
return -EINVAL;
}
if (mxc_fbi->ovfbi)
mxc_fbi_fg = (struct mxcfb_info *)mxc_fbi->ovfbi->par;
if (mxc_fbi->ovfbi && mxc_fbi_fg)
if (mxc_fbi_fg->next_blank == FB_BLANK_UNBLANK)
ovfbi_enable = true;
if (!mxcfb_need_to_set_par(fbi))
return 0;
dev_dbg(fbi->device, "Reconfiguring framebuffer\n");
if (fbi->var.xres == 0 || fbi->var.yres == 0)
return 0;
mxcfb_set_fix(fbi);
mxcfb_check_resolve(fbi);
mxcfb_check_yuv(fbi);
on_the_fly = mxcfb_can_set_par_on_the_fly(fbi, &final_pfmt);
mxc_fbi->on_the_fly = on_the_fly;
mxc_fbi->final_pfmt = final_pfmt;
if (on_the_fly)
dev_dbg(fbi->device, "Reconfiguring framebuffer on the fly\n");
if (ovfbi_enable) {
ov_pos_ret = ipu_disp_get_window_pos(
mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch,
&ov_pos_x, &ov_pos_y);
if (ov_pos_ret < 0)
dev_err(fbi->device, "Get overlay pos failed, dispdrv:%s.\n",
mxc_fbi->dispdrv->drv->name);
if (!on_the_fly) {
ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq);
ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_irq);
ipu_clear_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq);
ipu_disable_irq(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch_nf_irq);
ipu_disable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch, true);
ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
if (mxc_fbi_fg->cur_prefetch) {
ipu_prg_disable(mxc_fbi_fg->ipu_id, mxc_fbi_fg->pre_num);
ipu_pre_disable(mxc_fbi_fg->pre_num);
ipu_pre_free(&mxc_fbi_fg->pre_num);
}
}
}
if (!on_the_fly) {
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
ipu_disable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true);
ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
if (mxc_fbi->cur_prefetch) {
ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num);
ipu_pre_disable(mxc_fbi->pre_num);
ipu_pre_free(&mxc_fbi->pre_num);
}
}
/*
* Disable IPU hsp clock if it is enabled for an
* additional time in ipu common driver.
*/
if (mxc_fbi->first_set_par && mxc_fbi->late_init)
ipu_disable_hsp_clk(mxc_fbi->ipu);
mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) {
if (fbi->var.vmode & FB_VMODE_YWRAP)
mem_len = mxc_fbi->gpu_sec_buf_off + mem_len / 2;
else
mem_len = mxc_fbi->gpu_sec_buf_off *
(fbi->var.yres_virtual / fbi->var.yres) + mem_len / 2;
}
if (!fbi->fix.smem_start || (mem_len > fbi->fix.smem_len)) {
if (fbi->fix.smem_start)
mxcfb_unmap_video_memory(fbi);
if (mxcfb_map_video_memory(fbi) < 0)
return -ENOMEM;
}
if (mxc_fbi->first_set_par) {
/*
* Clear the screen in case uboot fb pixel format is not
* the same to kernel fb pixel format.
*/
if (mxc_fbi->late_init)
memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
mxc_fbi->first_set_par = false;
}
if (mxc_fbi->alpha_chan_en) {
alpha_mem_len = fbi->var.xres * fbi->var.yres;
if ((!mxc_fbi->alpha_phy_addr0 && !mxc_fbi->alpha_phy_addr1) ||
(alpha_mem_len > mxc_fbi->alpha_mem_len)) {
if (mxc_fbi->alpha_phy_addr0)
dma_free_coherent(fbi->device,
mxc_fbi->alpha_mem_len,
mxc_fbi->alpha_virt_addr0,
mxc_fbi->alpha_phy_addr0);
if (mxc_fbi->alpha_phy_addr1)
dma_free_coherent(fbi->device,
mxc_fbi->alpha_mem_len,
mxc_fbi->alpha_virt_addr1,
mxc_fbi->alpha_phy_addr1);
mxc_fbi->alpha_virt_addr0 =
dma_alloc_coherent(fbi->device,
alpha_mem_len,
&mxc_fbi->alpha_phy_addr0,
GFP_DMA | GFP_KERNEL);
mxc_fbi->alpha_virt_addr1 =
dma_alloc_coherent(fbi->device,
alpha_mem_len,
&mxc_fbi->alpha_phy_addr1,
GFP_DMA | GFP_KERNEL);
if (mxc_fbi->alpha_virt_addr0 == NULL ||
mxc_fbi->alpha_virt_addr1 == NULL) {
dev_err(fbi->device, "mxcfb: dma alloc for"
" alpha buffer failed.\n");
if (mxc_fbi->alpha_virt_addr0)
dma_free_coherent(fbi->device,
mxc_fbi->alpha_mem_len,
mxc_fbi->alpha_virt_addr0,
mxc_fbi->alpha_phy_addr0);
if (mxc_fbi->alpha_virt_addr1)
dma_free_coherent(fbi->device,
mxc_fbi->alpha_mem_len,
mxc_fbi->alpha_virt_addr1,
mxc_fbi->alpha_phy_addr1);
return -ENOMEM;
}
mxc_fbi->alpha_mem_len = alpha_mem_len;
}
}
if (mxc_fbi->next_blank != FB_BLANK_UNBLANK)
return retval;
if (!on_the_fly && mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->setup) {
retval = mxc_fbi->dispdrv->drv->setup(mxc_fbi->dispdrv, fbi);
if (retval < 0) {
dev_err(fbi->device, "setup error, dispdrv:%s.\n",
mxc_fbi->dispdrv->drv->name);
return -EINVAL;
}
}
if (!on_the_fly) {
_setup_disp_channel1(fbi);
if (ovfbi_enable)
_setup_disp_channel1(mxc_fbi->ovfbi);
}
if (!mxc_fbi->overlay && !on_the_fly) {
uint32_t out_pixel_fmt;
memset(&sig_cfg, 0, sizeof(sig_cfg));
if (fbi->var.vmode & FB_VMODE_INTERLACED)
sig_cfg.interlaced = true;
out_pixel_fmt = mxc_fbi->ipu_di_pix_fmt;
if (fbi->var.vmode & FB_VMODE_ODD_FLD_FIRST) /* PAL */
sig_cfg.odd_field_first = true;
if (mxc_fbi->ipu_int_clk)
sig_cfg.int_clk = true;
if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
sig_cfg.Hsync_pol = true;
if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
sig_cfg.Vsync_pol = true;
if (!(fbi->var.sync & FB_SYNC_CLK_LAT_FALL))
sig_cfg.clk_pol = true;
if (fbi->var.sync & FB_SYNC_DATA_INVERT)
sig_cfg.data_pol = true;
if (!(fbi->var.sync & FB_SYNC_OE_LOW_ACT))
sig_cfg.enable_pol = true;
if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
sig_cfg.clkidle_en = true;
dev_dbg(fbi->device, "pixclock = %ul Hz\n",
(u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL));
if (ipu_init_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di,
(PICOS2KHZ(fbi->var.pixclock)) * 1000UL,
fbi->var.xres, fbi->var.yres,
out_pixel_fmt,
fbi->var.left_margin,
fbi->var.hsync_len,
fbi->var.right_margin,
fbi->var.upper_margin,
fbi->var.vsync_len,
fbi->var.lower_margin,
0, sig_cfg) != 0) {
dev_err(fbi->device,
"mxcfb: Error initializing panel.\n");
return -EINVAL;
}
fbi->mode =
(struct fb_videomode *)fb_match_mode(&fbi->var,
&fbi->modelist);
ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, 0, 0);
}
retval = _setup_disp_channel2(fbi);
if (retval) {
if (!on_the_fly)
ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
return retval;
}
if (ovfbi_enable && !on_the_fly) {
if (ov_pos_ret >= 0)
ipu_disp_set_window_pos(
mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch,
ov_pos_x, ov_pos_y);
retval = _setup_disp_channel2(mxc_fbi->ovfbi);
if (retval) {
ipu_uninit_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
return retval;
}
}
if (!on_the_fly) {
ipu_enable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
if (ovfbi_enable)
ipu_enable_channel(mxc_fbi_fg->ipu, mxc_fbi_fg->ipu_ch);
}
if (!on_the_fly && mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->enable) {
retval = mxc_fbi->dispdrv->drv->enable(mxc_fbi->dispdrv, fbi);
if (retval < 0) {
dev_err(fbi->device, "enable error, dispdrv:%s.\n",
mxc_fbi->dispdrv->drv->name);
return -EINVAL;
}
}
mxc_fbi->cur_var = fbi->var;
mxc_fbi->cur_ipu_pfmt = on_the_fly ? mxc_fbi->final_pfmt :
fbi_to_pixfmt(fbi, false);
mxc_fbi->cur_fb_pfmt = fbi_to_pixfmt(fbi, true);
mxc_fbi->cur_prefetch = mxc_fbi->prefetch;
return retval;
}
static int _swap_channels(struct fb_info *fbi_from,
struct fb_info *fbi_to, bool both_on)
{
int retval, tmp;
ipu_channel_t old_ch;
struct fb_info *ovfbi;
struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
struct mxcfb_info *mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
if (both_on) {
ipu_disable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch, true);
ipu_uninit_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
}
/* switch the mxc fbi parameters */
old_ch = mxc_fbi_from->ipu_ch;
mxc_fbi_from->ipu_ch = mxc_fbi_to->ipu_ch;
mxc_fbi_to->ipu_ch = old_ch;
tmp = mxc_fbi_from->ipu_ch_irq;
mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
mxc_fbi_to->ipu_ch_irq = tmp;
tmp = mxc_fbi_from->ipu_ch_nf_irq;
mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
mxc_fbi_to->ipu_ch_nf_irq = tmp;
ovfbi = mxc_fbi_from->ovfbi;
mxc_fbi_from->ovfbi = mxc_fbi_to->ovfbi;
mxc_fbi_to->ovfbi = ovfbi;
_setup_disp_channel1(fbi_from);
retval = _setup_disp_channel2(fbi_from);
if (retval)
return retval;
/* switch between dp and dc, disable old idmac, enable new idmac */
retval = ipu_swap_channel(mxc_fbi_from->ipu, old_ch, mxc_fbi_from->ipu_ch);
ipu_uninit_channel(mxc_fbi_from->ipu, old_ch);
if (both_on) {
_setup_disp_channel1(fbi_to);
retval = _setup_disp_channel2(fbi_to);
if (retval)
return retval;
ipu_enable_channel(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch);
}
return retval;
}
static int swap_channels(struct fb_info *fbi_from)
{
int i;
int swap_mode;
ipu_channel_t ch_to;
struct mxcfb_info *mxc_fbi_from = (struct mxcfb_info *)fbi_from->par;
struct fb_info *fbi_to = NULL;
struct mxcfb_info *mxc_fbi_to;
/* what's the target channel? */
if (mxc_fbi_from->ipu_ch == MEM_BG_SYNC)
ch_to = MEM_DC_SYNC;
else
ch_to = MEM_BG_SYNC;
fbi_to = found_registered_fb(ch_to, mxc_fbi_from->ipu_id);
if (!fbi_to)
return -1;
mxc_fbi_to = (struct mxcfb_info *)fbi_to->par;
ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq, fbi_from);
ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq, fbi_to);
ipu_clear_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
ipu_clear_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
ipu_free_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq, fbi_from);
ipu_free_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq, fbi_to);
if (mxc_fbi_from->cur_blank == FB_BLANK_UNBLANK) {
if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
swap_mode = BOTH_ON;
else
swap_mode = SRC_ON;
} else {
if (mxc_fbi_to->cur_blank == FB_BLANK_UNBLANK)
swap_mode = TGT_ON;
else
swap_mode = BOTH_OFF;
}
switch (swap_mode) {
case BOTH_ON:
/* disable target->switch src->enable target */
_swap_channels(fbi_from, fbi_to, true);
break;
case SRC_ON:
/* just switch src */
_swap_channels(fbi_from, fbi_to, false);
break;
case TGT_ON:
/* just switch target */
_swap_channels(fbi_to, fbi_from, false);
break;
case BOTH_OFF:
/* switch directly, no more need to do */
mxc_fbi_to->ipu_ch = mxc_fbi_from->ipu_ch;
mxc_fbi_from->ipu_ch = ch_to;
i = mxc_fbi_from->ipu_ch_irq;
mxc_fbi_from->ipu_ch_irq = mxc_fbi_to->ipu_ch_irq;
mxc_fbi_to->ipu_ch_irq = i;
i = mxc_fbi_from->ipu_ch_nf_irq;
mxc_fbi_from->ipu_ch_nf_irq = mxc_fbi_to->ipu_ch_nf_irq;
mxc_fbi_to->ipu_ch_nf_irq = i;
break;
default:
break;
}
if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq,
mxcfb_irq_handler, IPU_IRQF_ONESHOT,
MXCFB_NAME, fbi_from) != 0) {
dev_err(fbi_from->device, "Error registering irq %d\n",
mxc_fbi_from->ipu_ch_irq);
return -EBUSY;
}
ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_irq);
if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq,
mxcfb_irq_handler, IPU_IRQF_ONESHOT,
MXCFB_NAME, fbi_to) != 0) {
dev_err(fbi_to->device, "Error registering irq %d\n",
mxc_fbi_to->ipu_ch_irq);
return -EBUSY;
}
ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_irq);
if (ipu_request_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq,
mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT,
MXCFB_NAME, fbi_from) != 0) {
dev_err(fbi_from->device, "Error registering irq %d\n",
mxc_fbi_from->ipu_ch_nf_irq);
return -EBUSY;
}
ipu_disable_irq(mxc_fbi_from->ipu, mxc_fbi_from->ipu_ch_nf_irq);
if (ipu_request_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq,
mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT,
MXCFB_NAME, fbi_to) != 0) {
dev_err(fbi_to->device, "Error registering irq %d\n",
mxc_fbi_to->ipu_ch_nf_irq);
return -EBUSY;
}
ipu_disable_irq(mxc_fbi_to->ipu, mxc_fbi_to->ipu_ch_nf_irq);
return 0;
}
/*
* Check framebuffer variable parameters and adjust to valid values.
*
* @param var framebuffer variable parameters
*
* @param info framebuffer information pointer
*/
static int mxcfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
u32 vtotal;
u32 htotal;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;
struct fb_info tmp_fbi;
unsigned int fr_xoff, fr_yoff, fr_w, fr_h, line_length;
unsigned long base = 0;
int ret, bw = 0, bh = 0;
bool triple_buffer = false;
if (var->xres == 0 || var->yres == 0)
return 0;
/* fg should not bigger than bg */
if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
struct fb_info *fbi_tmp;
int bg_xres = 0, bg_yres = 0;
int16_t pos_x, pos_y;
bg_xres = var->xres;
bg_yres = var->yres;
fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
if (!fbi_tmp) {
dev_err(info->device,
"cannot find background fb for overlay fb\n");
return -EINVAL;
}
bg_xres = fbi_tmp->var.xres;
bg_yres = fbi_tmp->var.yres;
ipu_disp_get_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch, &pos_x, &pos_y);
if ((var->xres + pos_x) > bg_xres)
var->xres = bg_xres - pos_x;
if ((var->yres + pos_y) > bg_yres)
var->yres = bg_yres - pos_y;
if (fbi_tmp->var.vmode & FB_VMODE_INTERLACED)
var->vmode |= FB_VMODE_INTERLACED;
else
var->vmode &= ~FB_VMODE_INTERLACED;
var->pixclock = fbi_tmp->var.pixclock;
var->right_margin = fbi_tmp->var.right_margin;
var->hsync_len = fbi_tmp->var.hsync_len;
var->left_margin = fbi_tmp->var.left_margin +
fbi_tmp->var.xres - var->xres;
var->upper_margin = fbi_tmp->var.upper_margin;
var->vsync_len = fbi_tmp->var.vsync_len;
var->lower_margin = fbi_tmp->var.lower_margin +
fbi_tmp->var.yres - var->yres;
}
if (var->rotate > IPU_ROTATE_VERT_FLIP)
var->rotate = IPU_ROTATE_NONE;
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres) {
var->yres_virtual = var->yres * 3;
triple_buffer = true;
}
if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
(var->bits_per_pixel != 16) && (var->bits_per_pixel != 12) &&
(var->bits_per_pixel != 8))
var->bits_per_pixel = 16;
if (check_var_pixfmt(var)) {
/* Fall back to default */
ret = bpp_to_var(var->bits_per_pixel, var);
if (ret < 0)
return ret;
}
if (ipu_pixel_format_is_gpu_tile(var->nonstd)) {
fmt_to_tile_alignment(var->nonstd, &bw, &bh);
var->xres_virtual = ALIGN(var->xres_virtual, bw);
if (triple_buffer)
var->yres_virtual = 3 * ALIGN(var->yres, bh);
else
var->yres_virtual = ALIGN(var->yres_virtual, bh);
}
line_length = var->xres_virtual * var->bits_per_pixel / 8;
fr_xoff = var->xoffset;
fr_w = var->xres_virtual;
if (!(var->vmode & FB_VMODE_YWRAP)) {
fr_yoff = var->yoffset % var->yres;
fr_h = var->yres;
base = line_length * var->yres *
(var->yoffset / var->yres);
if (ipu_pixel_format_is_split_gpu_tile(var->nonstd))
base += (mxc_fbi->gpu_sec_buf_off -
line_length * var->yres / 2) *
(var->yoffset / var->yres);
} else {
fr_yoff = var->yoffset;
fr_h = var->yres_virtual;
}
tmp_fbi.device = info->device;
tmp_fbi.var = *var;
tmp_fbi.par = mxc_fbi;
if (ipu_pixel_format_is_gpu_tile(var->nonstd)) {
unsigned int crop_line, prg_width = var->xres, offset;
int ipu_stride, prg_stride, bs;
bool tmp_prefetch = mxc_fbi->prefetch;
if (!(var->xres % 32))
bs = 32;
else if (!(var->xres % 16))
bs = 16;
else
bs = 32;
prg_width += fr_xoff % bw;
if (((fr_xoff % bw) + prg_width) % bs)
prg_width = ALIGN(prg_width, bs);
mxc_fbi->prefetch = true;
ipu_stride = prg_width *
bytes_per_pixel(fbi_to_pixfmt(&tmp_fbi, false));
if (var->vmode & FB_VMODE_INTERLACED) {
if ((fr_yoff % bh) % 2) {
dev_err(info->device,
"wrong crop value in interlaced mode\n");
return -EINVAL;
}
crop_line = (fr_yoff % bh) / 2;
prg_stride = ipu_stride * 2;
} else {
crop_line = fr_yoff % bh;
prg_stride = ipu_stride;
}
offset = crop_line * prg_stride +
(fr_xoff % bw) *
bytes_per_pixel(fbi_to_pixfmt(&tmp_fbi, false));
mxc_fbi->prefetch = tmp_prefetch;
if (offset % 8) {
dev_err(info->device,
"IPU base address is not 8byte aligned\n");
return -EINVAL;
}
} else {
unsigned int uoff = 0, voff = 0;
int fb_stride;
switch (fbi_to_pixfmt(&tmp_fbi, true)) {
case IPU_PIX_FMT_YUV420P2:
case IPU_PIX_FMT_YVU420P:
case IPU_PIX_FMT_NV12:
case PRE_PIX_FMT_NV21:
case IPU_PIX_FMT_NV16:
case PRE_PIX_FMT_NV61:
case IPU_PIX_FMT_YUV422P:
case IPU_PIX_FMT_YVU422P:
case IPU_PIX_FMT_YUV420P:
case IPU_PIX_FMT_YUV444P:
fb_stride = var->xres_virtual;
break;
default:
fb_stride = line_length;
}
base += fr_yoff * fb_stride +
fr_xoff * var->bits_per_pixel / 8;
ipu_get_channel_offset(fbi_to_pixfmt(&tmp_fbi, true),
var->xres,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff,
&uoff,
&voff);
if (base % 8 || uoff % 8 || voff % 8) {
dev_err(info->device,
"IPU base address is not 8byte aligned\n");
return -EINVAL;
}
}
if (var->pixclock < 1000) {
htotal = var->xres + var->right_margin + var->hsync_len +
var->left_margin;
vtotal = var->yres + var->lower_margin + var->vsync_len +
var->upper_margin;
var->pixclock = (vtotal * htotal * 6UL) / 100UL;
var->pixclock = KHZ2PICOS(var->pixclock);
dev_dbg(info->device,
"pixclock set for 60Hz refresh = %u ps\n",
var->pixclock);
}
var->height = -1;
var->width = -1;
var->grayscale = 0;
return 0;
}
static inline u_int _chan_to_field(u_int chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return chan << bf->offset;
}
static int mxcfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
u_int trans, struct fb_info *fbi)
{
unsigned int val;
int ret = 1;
/*
* If greyscale is true, then we convert the RGB value
* to greyscale no matter what visual we are using.
*/
if (fbi->var.grayscale)
red = green = blue = (19595 * red + 38470 * green +
7471 * blue) >> 16;
switch (fbi->fix.visual) {
case FB_VISUAL_TRUECOLOR:
/*
* 16-bit True Colour. We encode the RGB value
* according to the RGB bitfield information.
*/
if (regno < 16) {
u32 *pal = fbi->pseudo_palette;
val = _chan_to_field(red, &fbi->var.red);
val |= _chan_to_field(green, &fbi->var.green);
val |= _chan_to_field(blue, &fbi->var.blue);
pal[regno] = val;
ret = 0;
}
break;
case FB_VISUAL_STATIC_PSEUDOCOLOR:
case FB_VISUAL_PSEUDOCOLOR:
break;
}
return ret;
}
/*
* Function to handle custom ioctls for MXC framebuffer.
*
* @param inode inode struct
*
* @param file file struct
*
* @param cmd Ioctl command to handle
*
* @param arg User pointer to command arguments
*
* @param fbi framebuffer information pointer
*/
static int mxcfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg)
{
int retval = 0;
int __user *argp = (void __user *)arg;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
switch (cmd) {
case MXCFB_SET_GBL_ALPHA:
{
struct mxcfb_gbl_alpha ga;
if (copy_from_user(&ga, (void *)arg, sizeof(ga))) {
retval = -EFAULT;
break;
}
if (ipu_disp_set_global_alpha(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
(bool)ga.enable,
ga.alpha)) {
retval = -EINVAL;
break;
}
if (ga.enable)
mxc_fbi->alpha_chan_en = false;
if (ga.enable)
dev_dbg(fbi->device,
"Set global alpha of %s to %d\n",
fbi->fix.id, ga.alpha);
break;
}
case MXCFB_SET_LOC_ALPHA:
{
struct mxcfb_loc_alpha la;
bool bad_pixfmt =
ipu_ch_param_bad_alpha_pos(fbi_to_pixfmt(fbi, true));
if (copy_from_user(&la, (void *)arg, sizeof(la))) {
retval = -EFAULT;
break;
}
if (la.enable && !la.alpha_in_pixel) {
struct fb_info *fbi_tmp;
ipu_channel_t ipu_ch;
if (bad_pixfmt) {
dev_err(fbi->device, "Bad pixel format "
"for graphics plane fb\n");
retval = -EINVAL;
break;
}
mxc_fbi->alpha_chan_en = true;
if (mxc_fbi->ipu_ch == MEM_FG_SYNC)
ipu_ch = MEM_BG_SYNC;
else if (mxc_fbi->ipu_ch == MEM_BG_SYNC)
ipu_ch = MEM_FG_SYNC;
else {
retval = -EINVAL;
break;
}
fbi_tmp = found_registered_fb(ipu_ch, mxc_fbi->ipu_id);
if (fbi_tmp)
((struct mxcfb_info *)(fbi_tmp->par))->alpha_chan_en = false;
} else
mxc_fbi->alpha_chan_en = false;
if (ipu_disp_set_global_alpha(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
!(bool)la.enable, 0)) {
retval = -EINVAL;
break;
}
fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) |
FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
mxcfb_set_par(fbi);
la.alpha_phy_addr0 = mxc_fbi->alpha_phy_addr0;
la.alpha_phy_addr1 = mxc_fbi->alpha_phy_addr1;
if (copy_to_user((void *)arg, &la, sizeof(la))) {
retval = -EFAULT;
break;
}
if (la.enable)
dev_dbg(fbi->device,
"Enable DP local alpha for %s\n",
fbi->fix.id);
break;
}
case MXCFB_SET_LOC_ALP_BUF:
{
unsigned long base;
uint32_t ipu_alp_ch_irq;
if (!(((mxc_fbi->ipu_ch == MEM_FG_SYNC) ||
(mxc_fbi->ipu_ch == MEM_BG_SYNC)) &&
(mxc_fbi->alpha_chan_en))) {
dev_err(fbi->device,
"Should use background or overlay "
"framebuffer to set the alpha buffer "
"number\n");
return -EINVAL;
}
if (get_user(base, argp))
return -EFAULT;
if (base != mxc_fbi->alpha_phy_addr0 &&
base != mxc_fbi->alpha_phy_addr1) {
dev_err(fbi->device,
"Wrong alpha buffer physical address "
"%lu\n", base);
return -EINVAL;
}
if (mxc_fbi->ipu_ch == MEM_FG_SYNC)
ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
else
ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
retval = wait_for_completion_timeout(
&mxc_fbi->alpha_flip_complete, HZ/2);
if (retval == 0) {
dev_err(fbi->device, "timeout when waiting for alpha flip irq\n");
retval = -ETIMEDOUT;
break;
}
mxc_fbi->cur_ipu_alpha_buf =
!mxc_fbi->cur_ipu_alpha_buf;
if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_ALPHA_IN_BUFFER,
mxc_fbi->
cur_ipu_alpha_buf,
base) == 0) {
ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_ALPHA_IN_BUFFER,
mxc_fbi->cur_ipu_alpha_buf);
ipu_clear_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
ipu_enable_irq(mxc_fbi->ipu, ipu_alp_ch_irq);
} else {
dev_err(fbi->device,
"Error updating %s SDC alpha buf %d "
"to address=0x%08lX\n",
fbi->fix.id,
mxc_fbi->cur_ipu_alpha_buf, base);
}
break;
}
case MXCFB_SET_CLR_KEY:
{
struct mxcfb_color_key key;
if (copy_from_user(&key, (void *)arg, sizeof(key))) {
retval = -EFAULT;
break;
}
retval = ipu_disp_set_color_key(mxc_fbi->ipu, mxc_fbi->ipu_ch,
key.enable,
key.color_key);
dev_dbg(fbi->device, "Set color key to 0x%08X\n",
key.color_key);
break;
}
case MXCFB_SET_GAMMA:
{
struct mxcfb_gamma gamma;
if (copy_from_user(&gamma, (void *)arg, sizeof(gamma))) {
retval = -EFAULT;
break;
}
retval = ipu_disp_set_gamma_correction(mxc_fbi->ipu,
mxc_fbi->ipu_ch,
gamma.enable,
gamma.constk,
gamma.slopek);
break;
}
case MXCFB_SET_GPU_SPLIT_FMT:
{
struct mxcfb_gpu_split_fmt fmt;
if (copy_from_user(&fmt, (void *)arg, sizeof(fmt))) {
retval = -EFAULT;
break;
}
if (fmt.var.nonstd != IPU_PIX_FMT_GPU32_SB_ST &&
fmt.var.nonstd != IPU_PIX_FMT_GPU32_SB_SRT &&
fmt.var.nonstd != IPU_PIX_FMT_GPU16_SB_ST &&
fmt.var.nonstd != IPU_PIX_FMT_GPU16_SB_SRT) {
retval = -EINVAL;
break;
}
mxc_fbi->gpu_sec_buf_off = fmt.offset;
fmt.var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) |
FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
console_lock();
fbi->flags |= FBINFO_MISC_USEREVENT;
retval = fb_set_var(fbi, &fmt.var);
fbi->flags &= ~FBINFO_MISC_USEREVENT;
console_unlock();
break;
}
case MXCFB_SET_PREFETCH:
{
int enable;
if (copy_from_user(&enable, (void *)arg, sizeof(enable))) {
retval = -EFAULT;
break;
}
if (!enable) {
if (ipu_pixel_format_is_gpu_tile(fbi_to_pixfmt(fbi, true))) {
dev_err(fbi->device, "Cannot disable prefetch in "
"resolving mode\n");
retval = -EINVAL;
break;
}
if (ipu_pixel_format_is_pre_yuv(fbi_to_pixfmt(fbi, true))) {
dev_err(fbi->device, "Cannot disable prefetch when "
"PRE gets NV61 or NV21\n");
retval = -EINVAL;
break;
}
} else {
if ((fbi->var.vmode & FB_VMODE_INTERLACED) &&
ipu_pixel_format_is_multiplanar_yuv(fbi_to_pixfmt(fbi, true))) {
dev_err(fbi->device, "Cannot enable prefetch when "
"PRE gets multiplanar YUV frames\n");
retval = -EINVAL;
break;
}
}
retval = mxcfb_check_var(&fbi->var, fbi);
if (retval)
break;
mxc_fbi->prefetch = !!enable;
if (mxc_fbi->cur_prefetch == mxc_fbi->prefetch)
break;
fbi->var.activate = (fbi->var.activate & ~FB_ACTIVATE_MASK) |
FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
retval = mxcfb_set_par(fbi);
break;
}
case MXCFB_GET_PREFETCH:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (put_user(mxc_fbi->cur_prefetch, argp))
return -EFAULT;
break;
}
case MXCFB_WAIT_FOR_VSYNC:
{
if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
/* BG should poweron */
struct mxcfb_info *bg_mxcfbi = NULL;
struct fb_info *fbi_tmp;
fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
if (fbi_tmp)
bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
if (!bg_mxcfbi) {
retval = -EINVAL;
break;
}
if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK) {
retval = -EINVAL;
break;
}
}
if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK) {
retval = -EINVAL;
break;
}
init_completion(&mxc_fbi->vsync_complete);
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_nf_irq);
retval = wait_for_completion_interruptible_timeout(
&mxc_fbi->vsync_complete, 1 * HZ);
if (retval == 0) {
dev_err(fbi->device,
"MXCFB_WAIT_FOR_VSYNC: timeout %d\n",
retval);
retval = -ETIME;
} else if (retval > 0) {
retval = 0;
}
break;
}
case FBIO_ALLOC:
{
int size;
struct mxcfb_alloc_list *mem;
mem = kzalloc(sizeof(*mem), GFP_KERNEL);
if (mem == NULL)
return -ENOMEM;
if (get_user(size, argp)) {
kfree(mem);
return -EFAULT;
}
mem->size = PAGE_ALIGN(size);
mem->cpu_addr = dma_alloc_coherent(fbi->device, size,
&mem->phy_addr,
GFP_KERNEL);
if (mem->cpu_addr == NULL) {
kfree(mem);
return -ENOMEM;
}
list_add(&mem->list, &fb_alloc_list);
if (put_user(mem->phy_addr, argp)) {
list_del(&mem->list);
dma_free_coherent(fbi->device,
mem->size,
mem->cpu_addr,
mem->phy_addr);
kfree(mem);
return -EFAULT;
}
dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n",
mem->size, mem->phy_addr);
break;
}
case FBIO_FREE:
{
unsigned long offset;
struct mxcfb_alloc_list *mem;
if (get_user(offset, argp))
return -EFAULT;
retval = -EINVAL;
list_for_each_entry(mem, &fb_alloc_list, list) {
if (mem->phy_addr == offset) {
list_del(&mem->list);
dma_free_coherent(fbi->device,
mem->size,
mem->cpu_addr,
mem->phy_addr);
kfree(mem);
retval = 0;
break;
}
}
break;
}
case MXCFB_SET_OVERLAY_POS:
{
struct mxcfb_pos pos;
struct fb_info *bg_fbi = NULL;
struct mxcfb_info *bg_mxcfbi = NULL;
if (mxc_fbi->ipu_ch != MEM_FG_SYNC) {
dev_err(fbi->device, "Should use the overlay "
"framebuffer to set the position of "
"the overlay window\n");
retval = -EINVAL;
break;
}
if (copy_from_user(&pos, (void *)arg, sizeof(pos))) {
retval = -EFAULT;
break;
}
bg_fbi = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
if (bg_fbi)
bg_mxcfbi = ((struct mxcfb_info *)(bg_fbi->par));
if (bg_fbi == NULL) {
dev_err(fbi->device, "Cannot find the "
"background framebuffer\n");
retval = -ENOENT;
break;
}
/* if fb is unblank, check if the pos fit the display */
if (mxc_fbi->cur_blank == FB_BLANK_UNBLANK) {
if (fbi->var.xres + pos.x > bg_fbi->var.xres) {
if (bg_fbi->var.xres < fbi->var.xres)
pos.x = 0;
else
pos.x = bg_fbi->var.xres - fbi->var.xres;
}
if (fbi->var.yres + pos.y > bg_fbi->var.yres) {
if (bg_fbi->var.yres < fbi->var.yres)
pos.y = 0;
else
pos.y = bg_fbi->var.yres - fbi->var.yres;
}
}
retval = ipu_disp_set_window_pos(mxc_fbi->ipu, mxc_fbi->ipu_ch,
pos.x, pos.y);
if (copy_to_user((void *)arg, &pos, sizeof(pos))) {
retval = -EFAULT;
break;
}
break;
}
case MXCFB_GET_FB_IPU_CHAN:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (put_user(mxc_fbi->ipu_ch, argp))
return -EFAULT;
break;
}
case MXCFB_GET_DIFMT:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (put_user(mxc_fbi->ipu_di_pix_fmt, argp))
return -EFAULT;
break;
}
case MXCFB_GET_FB_IPU_DI:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (put_user(mxc_fbi->ipu_di, argp))
return -EFAULT;
break;
}
case MXCFB_GET_FB_BLANK:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (put_user(mxc_fbi->cur_blank, argp))
return -EFAULT;
break;
}
case MXCFB_SET_DIFMT:
{
struct mxcfb_info *mxc_fbi =
(struct mxcfb_info *)fbi->par;
if (get_user(mxc_fbi->ipu_di_pix_fmt, argp))
return -EFAULT;
break;
}
case MXCFB_CSC_UPDATE:
{
struct mxcfb_csc_matrix csc;
if (copy_from_user(&csc, (void *) arg, sizeof(csc)))
return -EFAULT;
if ((mxc_fbi->ipu_ch != MEM_FG_SYNC) &&
(mxc_fbi->ipu_ch != MEM_BG_SYNC) &&
(mxc_fbi->ipu_ch != MEM_BG_ASYNC0))
return -EFAULT;
ipu_set_csc_coefficients(mxc_fbi->ipu, mxc_fbi->ipu_ch,
csc.param);
break;
}
default:
retval = -EINVAL;
}
return retval;
}
/*
* mxcfb_blank():
* Blank the display.
*/
static int mxcfb_blank(int blank, struct fb_info *info)
{
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par;
int ret = 0;
dev_dbg(info->device, "blank = %d\n", blank);
if (blank)
blank = FB_BLANK_POWERDOWN;
if (mxc_fbi->cur_blank == blank)
return 0;
mxc_fbi->next_blank = blank;
if (blank == FB_BLANK_UNBLANK) {
info->var.activate = (info->var.activate & ~FB_ACTIVATE_MASK) |
FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
ret = fb_set_var(info, &info->var);
} else {
if (mxc_fbi->dispdrv && mxc_fbi->dispdrv->drv->disable)
mxc_fbi->dispdrv->drv->disable(mxc_fbi->dispdrv, info);
ipu_disable_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch, true);
if (mxc_fbi->ipu_di >= 0)
ipu_uninit_sync_panel(mxc_fbi->ipu, mxc_fbi->ipu_di);
ipu_uninit_channel(mxc_fbi->ipu, mxc_fbi->ipu_ch);
if (mxc_fbi->cur_prefetch) {
ipu_prg_disable(mxc_fbi->ipu_id, mxc_fbi->pre_num);
ipu_pre_disable(mxc_fbi->pre_num);
ipu_pre_free(&mxc_fbi->pre_num);
}
}
if (!ret)
mxc_fbi->cur_blank = blank;
return ret;
}
/*
* Pan or Wrap the Display
*
* This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
*
* @param var Variable screen buffer information
* @param info Framebuffer information pointer
*/
static int
mxcfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)info->par,
*mxc_graphic_fbi = NULL;
u_int y_bottom;
unsigned int fr_xoff, fr_yoff, fr_w, fr_h;
unsigned long base, ipu_base = 0, active_alpha_phy_addr = 0;
bool loc_alpha_en = false;
int fb_stride;
int bw = 0, bh = 0;
int i;
int ret;
/* no pan display during fb blank */
if (mxc_fbi->ipu_ch == MEM_FG_SYNC) {
struct mxcfb_info *bg_mxcfbi = NULL;
struct fb_info *fbi_tmp;
fbi_tmp = found_registered_fb(MEM_BG_SYNC, mxc_fbi->ipu_id);
if (fbi_tmp)
bg_mxcfbi = ((struct mxcfb_info *)(fbi_tmp->par));
if (!bg_mxcfbi)
return -EINVAL;
if (bg_mxcfbi->cur_blank != FB_BLANK_UNBLANK)
return -EINVAL;
}
if (mxc_fbi->cur_blank != FB_BLANK_UNBLANK)
return -EINVAL;
if (mxc_fbi->resolve) {
fmt_to_tile_block(info->var.nonstd, &bw, &bh);
if (mxc_fbi->cur_var.xoffset % bw != var->xoffset % bw ||
mxc_fbi->cur_var.yoffset % bh != var->yoffset % bh) {
dev_err(info->device, "do not support panning "
"with tile crop settings changed\n");
return -EINVAL;
}
}
y_bottom = var->yoffset;
if (y_bottom > info->var.yres_virtual)
return -EINVAL;
switch (fbi_to_pixfmt(info, true)) {
case IPU_PIX_FMT_YUV420P2:
case IPU_PIX_FMT_YVU420P:
case IPU_PIX_FMT_NV12:
case PRE_PIX_FMT_NV21:
case IPU_PIX_FMT_NV16:
case PRE_PIX_FMT_NV61:
case IPU_PIX_FMT_YUV422P:
case IPU_PIX_FMT_YVU422P:
case IPU_PIX_FMT_YUV420P:
case IPU_PIX_FMT_YUV444P:
fb_stride = info->var.xres_virtual;
break;
default:
fb_stride = info->fix.line_length;
}
base = info->fix.smem_start;
fr_xoff = var->xoffset;
fr_w = info->var.xres_virtual;
if (!(var->vmode & FB_VMODE_YWRAP)) {
dev_dbg(info->device, "Y wrap disabled\n");
fr_yoff = var->yoffset % info->var.yres;
fr_h = info->var.yres;
base += info->fix.line_length * info->var.yres *
(var->yoffset / info->var.yres);
if (ipu_pixel_format_is_split_gpu_tile(var->nonstd))
base += (mxc_fbi->gpu_sec_buf_off -
info->fix.line_length * info->var.yres / 2) *
(var->yoffset / info->var.yres);
} else {
dev_dbg(info->device, "Y wrap enabled\n");
fr_yoff = var->yoffset;
fr_h = info->var.yres_virtual;
}
if (!mxc_fbi->resolve) {
base += fr_yoff * fb_stride + fr_xoff *
bytes_per_pixel(fbi_to_pixfmt(info, true));
if (mxc_fbi->cur_prefetch && (info->var.vmode & FB_VMODE_INTERLACED))
base += info->var.rotate ?
fr_w * bytes_per_pixel(fbi_to_pixfmt(info, true)) : 0;
}
if (mxc_fbi->cur_prefetch) {
unsigned long lock_flags = 0;
if (ipu_pre_yres_is_small(info->var.yres))
/*
* Update the PRE buffer address in the flip interrupt
* handler in this case to workaround the SoC design
* bug recorded by errata ERR009624.
*/
spin_lock_irqsave(&mxc_fbi->spin_lock, lock_flags);
if (mxc_fbi->resolve) {
mxc_fbi->x_crop = fr_xoff & ~(bw - 1);
mxc_fbi->y_crop = fr_yoff & ~(bh - 1);
} else {
mxc_fbi->x_crop = 0;
mxc_fbi->y_crop = 0;
}
ipu_get_channel_offset(fbi_to_pixfmt(info, true),
info->var.xres,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff,
&mxc_fbi->sec_buf_off,
&mxc_fbi->trd_buf_off);
if (mxc_fbi->resolve)
mxc_fbi->sec_buf_off = mxc_fbi->gpu_sec_buf_off;
if (ipu_pre_yres_is_small(info->var.yres)) {
mxc_fbi->base = base;
spin_unlock_irqrestore(&mxc_fbi->spin_lock, lock_flags);
}
} else {
ipu_base = base;
}
/* Check if DP local alpha is enabled and find the graphic fb */
if (mxc_fbi->ipu_ch == MEM_BG_SYNC || mxc_fbi->ipu_ch == MEM_FG_SYNC) {
for (i = 0; i < num_registered_fb; i++) {
char bg_id[] = "DISP3 BG";
char fg_id[] = "DISP3 FG";
char *idstr = registered_fb[i]->fix.id;
bg_id[4] += mxc_fbi->ipu_id;
fg_id[4] += mxc_fbi->ipu_id;
if ((strcmp(idstr, bg_id) == 0 ||
strcmp(idstr, fg_id) == 0) &&
((struct mxcfb_info *)
(registered_fb[i]->par))->alpha_chan_en) {
loc_alpha_en = true;
mxc_graphic_fbi = (struct mxcfb_info *)
(registered_fb[i]->par);
active_alpha_phy_addr =
mxc_fbi->cur_ipu_alpha_buf ?
mxc_graphic_fbi->alpha_phy_addr1 :
mxc_graphic_fbi->alpha_phy_addr0;
dev_dbg(info->device, "Updating SDC alpha "
"buf %d address=0x%08lX\n",
!mxc_fbi->cur_ipu_alpha_buf,
active_alpha_phy_addr);
break;
}
}
}
if (!mxc_fbi->cur_prefetch ||
(mxc_fbi->cur_prefetch && !ipu_pre_yres_is_small(info->var.yres))) {
ret = wait_for_completion_timeout(&mxc_fbi->flip_complete,
HZ/2);
if (ret == 0) {
dev_err(info->device, "timeout when waiting for flip "
"irq\n");
return -ETIMEDOUT;
}
}
if (!mxc_fbi->cur_prefetch) {
++mxc_fbi->cur_ipu_buf;
mxc_fbi->cur_ipu_buf %= 3;
dev_dbg(info->device, "Updating SDC %s buf %d address=0x%08lX\n",
info->fix.id, mxc_fbi->cur_ipu_buf, base);
}
mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
if (mxc_fbi->cur_prefetch)
goto next;
if (ipu_update_channel_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch, IPU_INPUT_BUFFER,
mxc_fbi->cur_ipu_buf, ipu_base) == 0) {
next:
/* Update the DP local alpha buffer only for graphic plane */
if (loc_alpha_en && mxc_graphic_fbi == mxc_fbi &&
ipu_update_channel_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
IPU_ALPHA_IN_BUFFER,
mxc_fbi->cur_ipu_alpha_buf,
active_alpha_phy_addr) == 0) {
ipu_select_buffer(mxc_graphic_fbi->ipu, mxc_graphic_fbi->ipu_ch,
IPU_ALPHA_IN_BUFFER,
mxc_fbi->cur_ipu_alpha_buf);
}
/* update u/v offset */
if (!mxc_fbi->cur_prefetch) {
ipu_update_channel_offset(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER,
fbi_to_pixfmt(info, true),
fr_w,
fr_h,
fr_w,
0, 0,
fr_yoff,
fr_xoff);
ipu_select_buffer(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, mxc_fbi->cur_ipu_buf);
} else if (!ipu_pre_yres_is_small(info->var.yres)) {
ipu_pre_set_fb_buffer(mxc_fbi->pre_num,
mxc_fbi->resolve,
base, info->var.yres,
mxc_fbi->x_crop,
mxc_fbi->y_crop,
mxc_fbi->sec_buf_off,
mxc_fbi->trd_buf_off);
}
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
} else {
dev_err(info->device,
"Error updating SDC buf %d to address=0x%08lX, "
"current buf %d, buf0 ready %d, buf1 ready %d, "
"buf2 ready %d\n", mxc_fbi->cur_ipu_buf, base,
ipu_get_cur_buffer_idx(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER),
ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 0),
ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 1),
ipu_check_buffer_ready(mxc_fbi->ipu, mxc_fbi->ipu_ch,
IPU_INPUT_BUFFER, 2));
if (!mxc_fbi->cur_prefetch) {
++mxc_fbi->cur_ipu_buf;
mxc_fbi->cur_ipu_buf %= 3;
++mxc_fbi->cur_ipu_buf;
mxc_fbi->cur_ipu_buf %= 3;
}
mxc_fbi->cur_ipu_alpha_buf = !mxc_fbi->cur_ipu_alpha_buf;
ipu_clear_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
ipu_enable_irq(mxc_fbi->ipu, mxc_fbi->ipu_ch_irq);
return -EBUSY;
}
if (mxc_fbi->cur_prefetch && ipu_pre_yres_is_small(info->var.yres)) {
ret = wait_for_completion_timeout(&mxc_fbi->flip_complete,
HZ/2);
if (ret == 0) {
dev_err(info->device, "timeout when waiting for flip "
"irq\n");
return -ETIMEDOUT;
}
}
dev_dbg(info->device, "Update complete\n");
info->var.yoffset = var->yoffset;
mxc_fbi->cur_var.xoffset = var->xoffset;
mxc_fbi->cur_var.yoffset = var->yoffset;
return 0;
}
/*
* Function to handle custom mmap for MXC framebuffer.
*
* @param fbi framebuffer information pointer
*
* @param vma Pointer to vm_area_struct
*/
static int mxcfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
{
bool found = false;
u32 len;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
struct mxcfb_alloc_list *mem;
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
if (offset < fbi->fix.smem_len) {
/* mapping framebuffer memory */
len = fbi->fix.smem_len - offset;
vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
} else if ((vma->vm_pgoff ==
(mxc_fbi->alpha_phy_addr0 >> PAGE_SHIFT)) ||
(vma->vm_pgoff ==
(mxc_fbi->alpha_phy_addr1 >> PAGE_SHIFT))) {
len = mxc_fbi->alpha_mem_len;
} else {
list_for_each_entry(mem, &fb_alloc_list, list) {
if (offset == mem->phy_addr) {
found = true;
len = mem->size;
break;
}
}
if (!found)
return -EINVAL;
}
len = PAGE_ALIGN(len);
if (vma->vm_end - vma->vm_start > len)
return -EINVAL;
/* make buffers bufferable */
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
vma->vm_flags |= VM_IO;
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
return -ENOBUFS;
}
return 0;
}
/*!
* This structure contains the pointers to the control functions that are
* invoked by the core framebuffer driver to perform operations like
* blitting, rectangle filling, copy regions and cursor definition.
*/
static struct fb_ops mxcfb_ops = {
.owner = THIS_MODULE,
.fb_set_par = mxcfb_set_par,
.fb_check_var = mxcfb_check_var,
.fb_setcolreg = mxcfb_setcolreg,
.fb_pan_display = mxcfb_pan_display,
.fb_ioctl = mxcfb_ioctl,
.fb_mmap = mxcfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_blank = mxcfb_blank,
};
static irqreturn_t mxcfb_irq_handler(int irq, void *dev_id)
{
struct fb_info *fbi = dev_id;
struct mxcfb_info *mxc_fbi = fbi->par;
if (mxc_fbi->pre_config) {
ipu_pre_set_ctrl(mxc_fbi->pre_num, mxc_fbi->pre_config);
mxc_fbi->pre_config = NULL;
complete(&mxc_fbi->otf_complete);
return IRQ_HANDLED;
}
if (mxc_fbi->cur_prefetch && ipu_pre_yres_is_small(fbi->var.yres)) {
spin_lock(&mxc_fbi->spin_lock);
ipu_pre_set_fb_buffer(mxc_fbi->pre_num,
mxc_fbi->resolve,
mxc_fbi->base, fbi->var.yres,
mxc_fbi->x_crop, mxc_fbi->y_crop,
mxc_fbi->sec_buf_off,
mxc_fbi->trd_buf_off);
spin_unlock(&mxc_fbi->spin_lock);
}
complete(&mxc_fbi->flip_complete);
return IRQ_HANDLED;
}
static irqreturn_t mxcfb_nf_irq_handler(int irq, void *dev_id)
{
struct fb_info *fbi = dev_id;
struct mxcfb_info *mxc_fbi = fbi->par;
complete(&mxc_fbi->vsync_complete);
return IRQ_HANDLED;
}
static irqreturn_t mxcfb_alpha_irq_handler(int irq, void *dev_id)
{
struct fb_info *fbi = dev_id;
struct mxcfb_info *mxc_fbi = fbi->par;
complete(&mxc_fbi->alpha_flip_complete);
return IRQ_HANDLED;
}
/*
* Suspends the framebuffer and blanks the screen. Power management support
*/
static int mxcfb_suspend(struct platform_device *pdev, pm_message_t state)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
int saved_blank;
#ifdef CONFIG_FB_MXC_LOW_PWR_DISPLAY
void *fbmem;
#endif
if (mxc_fbi->ovfbi) {
struct mxcfb_info *mxc_fbi_fg =
(struct mxcfb_info *)mxc_fbi->ovfbi->par;
console_lock();
fb_set_suspend(mxc_fbi->ovfbi, 1);
saved_blank = mxc_fbi_fg->cur_blank;
mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
mxc_fbi_fg->next_blank = saved_blank;
console_unlock();
}
console_lock();
fb_set_suspend(fbi, 1);
saved_blank = mxc_fbi->cur_blank;
mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
mxc_fbi->next_blank = saved_blank;
console_unlock();
return 0;
}
/*
* Resumes the framebuffer and unblanks the screen. Power management support
*/
static int mxcfb_resume(struct platform_device *pdev)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
console_lock();
mxcfb_blank(mxc_fbi->next_blank, fbi);
fb_set_suspend(fbi, 0);
console_unlock();
if (mxc_fbi->ovfbi) {
struct mxcfb_info *mxc_fbi_fg =
(struct mxcfb_info *)mxc_fbi->ovfbi->par;
console_lock();
mxcfb_blank(mxc_fbi_fg->next_blank, mxc_fbi->ovfbi);
fb_set_suspend(mxc_fbi->ovfbi, 0);
console_unlock();
}
return 0;
}
/*
* Main framebuffer functions
*/
/*!
* Allocates the DRAM memory for the frame buffer. This buffer is remapped
* into a non-cached, non-buffered, memory region to allow palette and pixel
* writes to occur without flushing the cache. Once this area is remapped,
* all virtual memory access to the video memory should occur at the new region.
*
* @param fbi framebuffer information pointer
*
* @return Error code indicating success or failure
*/
static int mxcfb_map_video_memory(struct fb_info *fbi)
{
struct mxcfb_info *mxc_fbi = (struct mxcfb_info *)fbi->par;
if (fbi->fix.smem_len < fbi->var.yres_virtual * fbi->fix.line_length)
fbi->fix.smem_len = fbi->var.yres_virtual *
fbi->fix.line_length;
if (mxc_fbi->resolve && mxc_fbi->gpu_sec_buf_off) {
if (fbi->var.vmode & FB_VMODE_YWRAP)
fbi->fix.smem_len = mxc_fbi->gpu_sec_buf_off +
fbi->fix.smem_len / 2;
else
fbi->fix.smem_len = mxc_fbi->gpu_sec_buf_off *
(fbi->var.yres_virtual / fbi->var.yres) +
fbi->fix.smem_len / 2;
}
fbi->screen_base = dma_alloc_writecombine(fbi->device,
fbi->fix.smem_len,
(dma_addr_t *)&fbi->fix.smem_start,
GFP_DMA | GFP_KERNEL);
if (fbi->screen_base == 0) {
dev_err(fbi->device, "Unable to allocate framebuffer memory\n");
fbi->fix.smem_len = 0;
fbi->fix.smem_start = 0;
return -EBUSY;
}
dev_dbg(fbi->device, "allocated fb @ paddr=0x%08X, size=%d.\n",
(uint32_t) fbi->fix.smem_start, fbi->fix.smem_len);
fbi->screen_size = fbi->fix.smem_len;
/* Clear the screen */
memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
return 0;
}
/*!
* De-allocates the DRAM memory for the frame buffer.
*
* @param fbi framebuffer information pointer
*
* @return Error code indicating success or failure
*/
static int mxcfb_unmap_video_memory(struct fb_info *fbi)
{
dma_free_writecombine(fbi->device, fbi->fix.smem_len,
fbi->screen_base, fbi->fix.smem_start);
fbi->screen_base = 0;
fbi->fix.smem_start = 0;
fbi->fix.smem_len = 0;
return 0;
}
/*!
* Initializes the framebuffer information pointer. After allocating
* sufficient memory for the framebuffer structure, the fields are
* filled with custom information passed in from the configurable
* structures. This includes information such as bits per pixel,
* color maps, screen width/height and RGBA offsets.
*
* @return Framebuffer structure initialized with our information
*/
static struct fb_info *mxcfb_init_fbinfo(struct device *dev, struct fb_ops *ops)
{
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
struct ipuv3_fb_platform_data *plat_data = dev->platform_data;
/*
* Allocate sufficient memory for the fb structure
*/
fbi = framebuffer_alloc(sizeof(struct mxcfb_info), dev);
if (!fbi)
return NULL;
mxcfbi = (struct mxcfb_info *)fbi->par;
fbi->var.activate = FB_ACTIVATE_NOW;
bpp_to_var(plat_data->default_bpp, &fbi->var);
fbi->fbops = ops;
fbi->flags = FBINFO_FLAG_DEFAULT;
fbi->pseudo_palette = mxcfbi->pseudo_palette;
/*
* Allocate colormap
*/
fb_alloc_cmap(&fbi->cmap, 16, 0);
return fbi;
}
static ssize_t show_disp_chan(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fb_info *info = dev_get_drvdata(dev);
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
if (mxcfbi->ipu_ch == MEM_BG_SYNC)
return sprintf(buf, "2-layer-fb-bg\n");
else if (mxcfbi->ipu_ch == MEM_FG_SYNC)
return sprintf(buf, "2-layer-fb-fg\n");
else if (mxcfbi->ipu_ch == MEM_DC_SYNC)
return sprintf(buf, "1-layer-fb\n");
else
return sprintf(buf, "err: no display chan\n");
}
static ssize_t swap_disp_chan(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct fb_info *info = dev_get_drvdata(dev);
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
struct mxcfb_info *fg_mxcfbi = NULL;
console_lock();
/* swap only happen between DP-BG and DC, while DP-FG disable */
if (((mxcfbi->ipu_ch == MEM_BG_SYNC) &&
(strstr(buf, "1-layer-fb") != NULL)) ||
((mxcfbi->ipu_ch == MEM_DC_SYNC) &&
(strstr(buf, "2-layer-fb-bg") != NULL))) {
struct fb_info *fbi_fg;
fbi_fg = found_registered_fb(MEM_FG_SYNC, mxcfbi->ipu_id);
if (fbi_fg)
fg_mxcfbi = (struct mxcfb_info *)fbi_fg->par;
if (!fg_mxcfbi ||
fg_mxcfbi->cur_blank == FB_BLANK_UNBLANK) {
dev_err(dev,
"Can not switch while fb2(fb-fg) is on.\n");
console_unlock();
return count;
}
if (swap_channels(info) < 0)
dev_err(dev, "Swap display channel failed.\n");
}
console_unlock();
return count;
}
static DEVICE_ATTR(fsl_disp_property, S_IWUSR | S_IRUGO,
show_disp_chan, swap_disp_chan);
static ssize_t show_disp_dev(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct fb_info *info = dev_get_drvdata(dev);
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)info->par;
if (mxcfbi->ipu_ch == MEM_FG_SYNC)
return sprintf(buf, "overlay\n");
else
return sprintf(buf, "%s\n", mxcfbi->dispdrv->drv->name);
}
static DEVICE_ATTR(fsl_disp_dev_property, S_IRUGO, show_disp_dev, NULL);
static int mxcfb_get_crtc(struct device *dev, struct mxcfb_info *mxcfbi,
enum crtc crtc)
{
int i = 0;
for (; i < ARRAY_SIZE(ipu_di_crtc_maps); i++)
if (ipu_di_crtc_maps[i].crtc == crtc) {
mxcfbi->ipu_id = ipu_di_crtc_maps[i].ipu_id;
mxcfbi->ipu_di = ipu_di_crtc_maps[i].ipu_di;
return 0;
}
dev_err(dev, "failed to get valid crtc\n");
return -EINVAL;
}
static int mxcfb_dispdrv_init(struct platform_device *pdev,
struct fb_info *fbi)
{
struct ipuv3_fb_platform_data *plat_data = pdev->dev.platform_data;
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
struct mxc_dispdrv_setting setting;
char disp_dev[32], *default_dev = "lcd";
int ret = 0;
setting.if_fmt = plat_data->interface_pix_fmt;
setting.dft_mode_str = plat_data->mode_str;
setting.default_bpp = plat_data->default_bpp;
if (!setting.default_bpp)
setting.default_bpp = 16;
setting.fbi = fbi;
if (!strlen(plat_data->disp_dev)) {
memcpy(disp_dev, default_dev, strlen(default_dev));
disp_dev[strlen(default_dev)] = '\0';
} else {
memcpy(disp_dev, plat_data->disp_dev,
strlen(plat_data->disp_dev));
disp_dev[strlen(plat_data->disp_dev)] = '\0';
}
mxcfbi->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
if (IS_ERR(mxcfbi->dispdrv)) {
ret = PTR_ERR(mxcfbi->dispdrv);
dev_err(&pdev->dev, "NO mxc display driver found!\n");
return ret;
} else {
/* fix-up */
mxcfbi->ipu_di_pix_fmt = setting.if_fmt;
mxcfbi->default_bpp = setting.default_bpp;
ret = mxcfb_get_crtc(&pdev->dev, mxcfbi, setting.crtc);
if (ret)
return ret;
dev_dbg(&pdev->dev, "di_pixfmt:0x%x, bpp:0x%x, di:%d, ipu:%d\n",
setting.if_fmt, setting.default_bpp,
mxcfbi->ipu_di, mxcfbi->ipu_id);
}
dev_info(&pdev->dev, "registered mxc display driver %s\n", disp_dev);
return ret;
}
/*
* Parse user specified options (`video=trident:')
* example:
* video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,bpp=16,noaccel
* video=mxcfb0:dev=lcd,800x480M-16@55,if=RGB565,fbpix=RGB565
*/
static int mxcfb_option_setup(struct platform_device *pdev, struct fb_info *fbi)
{
struct ipuv3_fb_platform_data *pdata = pdev->dev.platform_data;
char *options, *opt, *fb_mode_str = NULL;
char name[] = "mxcfb0";
uint32_t fb_pix_fmt = 0;
name[5] += pdev->id;
if (fb_get_options(name, &options)) {
dev_err(&pdev->dev, "Can't get fb option for %s!\n", name);
return -ENODEV;
}
if (!options || !*options)
return 0;
while ((opt = strsep(&options, ",")) != NULL) {
if (!*opt)
continue;
if (!strncmp(opt, "dev=", 4)) {
memcpy(pdata->disp_dev, opt + 4, strlen(opt) - 4);
pdata->disp_dev[strlen(opt) - 4] = '\0';
} else if (!strncmp(opt, "if=", 3)) {
if (!strncmp(opt+3, "RGB24", 5))
pdata->interface_pix_fmt = IPU_PIX_FMT_RGB24;
else if (!strncmp(opt+3, "BGR24", 5))
pdata->interface_pix_fmt = IPU_PIX_FMT_BGR24;
else if (!strncmp(opt+3, "GBR24", 5))
pdata->interface_pix_fmt = IPU_PIX_FMT_GBR24;
else if (!strncmp(opt+3, "RGB565", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_RGB565;
else if (!strncmp(opt+3, "RGB666", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_RGB666;
else if (!strncmp(opt+3, "YUV444", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_YUV444;
else if (!strncmp(opt+3, "LVDS666", 7))
pdata->interface_pix_fmt = IPU_PIX_FMT_LVDS666;
else if (!strncmp(opt+3, "YUYV16", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_YUYV;
else if (!strncmp(opt+3, "UYVY16", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_UYVY;
else if (!strncmp(opt+3, "YVYU16", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_YVYU;
else if (!strncmp(opt+3, "VYUY16", 6))
pdata->interface_pix_fmt = IPU_PIX_FMT_VYUY;
} else if (!strncmp(opt, "fbpix=", 6)) {
if (!strncmp(opt+6, "RGB24", 5))
fb_pix_fmt = IPU_PIX_FMT_RGB24;
else if (!strncmp(opt+6, "BGR24", 5))
fb_pix_fmt = IPU_PIX_FMT_BGR24;
else if (!strncmp(opt+6, "RGB32", 5))
fb_pix_fmt = IPU_PIX_FMT_RGB32;
else if (!strncmp(opt+6, "BGR32", 5))
fb_pix_fmt = IPU_PIX_FMT_BGR32;
else if (!strncmp(opt+6, "ABGR32", 6))
fb_pix_fmt = IPU_PIX_FMT_ABGR32;
else if (!strncmp(opt+6, "RGB565", 6))
fb_pix_fmt = IPU_PIX_FMT_RGB565;
else if (!strncmp(opt+6, "BGRA4444", 8))
fb_pix_fmt = IPU_PIX_FMT_BGRA4444;
else if (!strncmp(opt+6, "BGRA5551", 8))
fb_pix_fmt = IPU_PIX_FMT_BGRA5551;
if (fb_pix_fmt) {
pixfmt_to_var(fb_pix_fmt, &fbi->var);
pdata->default_bpp =
fbi->var.bits_per_pixel;
}
} else if (!strncmp(opt, "int_clk", 7)) {
pdata->int_clk = true;
continue;
} else if (!strncmp(opt, "bpp=", 4)) {
/* bpp setting cannot overwirte fbpix setting */
if (fb_pix_fmt)
continue;
pdata->default_bpp =
simple_strtoul(opt + 4, NULL, 0);
fb_pix_fmt = bpp_to_pixfmt(pdata->default_bpp);
if (fb_pix_fmt)
pixfmt_to_var(fb_pix_fmt, &fbi->var);
} else
fb_mode_str = opt;
}
if (fb_mode_str)
pdata->mode_str = fb_mode_str;
return 0;
}
static int mxcfb_register(struct fb_info *fbi)
{
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
struct fb_videomode m;
int ret = 0;
char bg0_id[] = "DISP3 BG";
char bg1_id[] = "DISP3 BG - DI1";
char fg_id[] = "DISP3 FG";
if (mxcfbi->ipu_di == 0) {
bg0_id[4] += mxcfbi->ipu_id;
strcpy(fbi->fix.id, bg0_id);
} else if (mxcfbi->ipu_di == 1) {
bg1_id[4] += mxcfbi->ipu_id;
strcpy(fbi->fix.id, bg1_id);
} else { /* Overlay */
fg_id[4] += mxcfbi->ipu_id;
strcpy(fbi->fix.id, fg_id);
}
mxcfb_check_var(&fbi->var, fbi);
mxcfb_set_fix(fbi);
/* Added first mode to fbi modelist. */
if (!fbi->modelist.next || !fbi->modelist.prev)
INIT_LIST_HEAD(&fbi->modelist);
fb_var_to_videomode(&m, &fbi->var);
fb_add_videomode(&m, &fbi->modelist);
if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq,
mxcfb_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) {
dev_err(fbi->device, "Error registering EOF irq handler.\n");
ret = -EBUSY;
goto err0;
}
ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq);
if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq,
mxcfb_nf_irq_handler, IPU_IRQF_ONESHOT, MXCFB_NAME, fbi) != 0) {
dev_err(fbi->device, "Error registering NFACK irq handler.\n");
ret = -EBUSY;
goto err1;
}
ipu_disable_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq);
if (mxcfbi->ipu_alp_ch_irq != -1)
if (ipu_request_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq,
mxcfb_alpha_irq_handler, IPU_IRQF_ONESHOT,
MXCFB_NAME, fbi) != 0) {
dev_err(fbi->device, "Error registering alpha irq "
"handler.\n");
ret = -EBUSY;
goto err2;
}
if (!mxcfbi->late_init) {
fbi->var.activate |= FB_ACTIVATE_FORCE;
console_lock();
fbi->flags |= FBINFO_MISC_USEREVENT;
ret = fb_set_var(fbi, &fbi->var);
fbi->flags &= ~FBINFO_MISC_USEREVENT;
console_unlock();
if (ret < 0) {
dev_err(fbi->device, "Error fb_set_var ret:%d\n", ret);
goto err3;
}
if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
console_lock();
ret = fb_blank(fbi, FB_BLANK_UNBLANK);
console_unlock();
if (ret < 0) {
dev_err(fbi->device,
"Error fb_blank ret:%d\n", ret);
goto err4;
}
}
} else {
/*
* Setup the channel again though bootloader
* has done this, then set_par() can stop the
* channel neatly and re-initialize it .
*/
if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
console_lock();
_setup_disp_channel1(fbi);
ipu_enable_channel(mxcfbi->ipu, mxcfbi->ipu_ch);
console_unlock();
}
}
ret = register_framebuffer(fbi);
if (ret < 0)
goto err5;
return ret;
err5:
if (mxcfbi->next_blank == FB_BLANK_UNBLANK) {
console_lock();
if (!mxcfbi->late_init)
fb_blank(fbi, FB_BLANK_POWERDOWN);
else {
ipu_disable_channel(mxcfbi->ipu, mxcfbi->ipu_ch,
true);
ipu_uninit_channel(mxcfbi->ipu, mxcfbi->ipu_ch);
}
console_unlock();
}
err4:
err3:
if (mxcfbi->ipu_alp_ch_irq != -1)
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
err2:
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
err1:
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
err0:
return ret;
}
static void mxcfb_unregister(struct fb_info *fbi)
{
struct mxcfb_info *mxcfbi = (struct mxcfb_info *)fbi->par;
if (mxcfbi->ipu_alp_ch_irq != -1)
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_alp_ch_irq, fbi);
if (mxcfbi->ipu_ch_irq)
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_irq, fbi);
if (mxcfbi->ipu_ch_nf_irq)
ipu_free_irq(mxcfbi->ipu, mxcfbi->ipu_ch_nf_irq, fbi);
unregister_framebuffer(fbi);
}
static int mxcfb_setup_overlay(struct platform_device *pdev,
struct fb_info *fbi_bg, struct resource *res)
{
struct fb_info *ovfbi;
struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
struct mxcfb_info *mxcfbi_fg;
int ret = 0;
ovfbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
if (!ovfbi) {
ret = -ENOMEM;
goto init_ovfbinfo_failed;
}
mxcfbi_fg = (struct mxcfb_info *)ovfbi->par;
mxcfbi_fg->ipu = ipu_get_soc(mxcfbi_bg->ipu_id);
if (IS_ERR(mxcfbi_fg->ipu)) {
ret = -ENODEV;
goto get_ipu_failed;
}
mxcfbi_fg->ipu_id = mxcfbi_bg->ipu_id;
mxcfbi_fg->ipu_ch_irq = IPU_IRQ_FG_SYNC_EOF;
mxcfbi_fg->ipu_ch_nf_irq = IPU_IRQ_FG_SYNC_NFACK;
mxcfbi_fg->ipu_alp_ch_irq = IPU_IRQ_FG_ALPHA_SYNC_EOF;
mxcfbi_fg->ipu_ch = MEM_FG_SYNC;
mxcfbi_fg->ipu_di = -1;
mxcfbi_fg->ipu_di_pix_fmt = mxcfbi_bg->ipu_di_pix_fmt;
mxcfbi_fg->overlay = true;
mxcfbi_fg->cur_blank = mxcfbi_fg->next_blank = FB_BLANK_POWERDOWN;
mxcfbi_fg->prefetch = false;
mxcfbi_fg->resolve = false;
mxcfbi_fg->pre_num = -1;
/* Need dummy values until real panel is configured */
ovfbi->var.xres = 240;
ovfbi->var.yres = 320;
if (res && res->start && res->end) {
ovfbi->fix.smem_len = res->end - res->start + 1;
ovfbi->fix.smem_start = res->start;
ovfbi->screen_base = ioremap(
ovfbi->fix.smem_start,
ovfbi->fix.smem_len);
}
ret = mxcfb_register(ovfbi);
if (ret < 0)
goto register_ov_failed;
mxcfbi_bg->ovfbi = ovfbi;
return ret;
register_ov_failed:
get_ipu_failed:
fb_dealloc_cmap(&ovfbi->cmap);
framebuffer_release(ovfbi);
init_ovfbinfo_failed:
return ret;
}
static void mxcfb_unsetup_overlay(struct fb_info *fbi_bg)
{
struct mxcfb_info *mxcfbi_bg = (struct mxcfb_info *)fbi_bg->par;
struct fb_info *ovfbi = mxcfbi_bg->ovfbi;
mxcfb_unregister(ovfbi);
if (&ovfbi->cmap)
fb_dealloc_cmap(&ovfbi->cmap);
framebuffer_release(ovfbi);
}
static bool ipu_usage[2][2];
static int ipu_test_set_usage(int ipu, int di)
{
if (ipu_usage[ipu][di])
return -EBUSY;
else
ipu_usage[ipu][di] = true;
return 0;
}
static void ipu_clear_usage(int ipu, int di)
{
ipu_usage[ipu][di] = false;
}
static int mxcfb_get_of_property(struct platform_device *pdev,
struct ipuv3_fb_platform_data *plat_data)
{
struct device_node *np = pdev->dev.of_node;
const char *disp_dev;
const char *mode_str = NULL;
const char *pixfmt;
int err;
int len;
u32 bpp, int_clk;
u32 late_init;
err = of_property_read_string(np, "disp_dev", &disp_dev);
if (err < 0) {
dev_dbg(&pdev->dev, "get of property disp_dev fail\n");
return err;
}
err = of_property_read_string(np, "mode_str", &mode_str);
if (err < 0)
dev_dbg(&pdev->dev, "get of property mode_str fail\n");
err = of_property_read_string(np, "interface_pix_fmt", &pixfmt);
if (err) {
dev_dbg(&pdev->dev, "get of property pix fmt fail\n");
return err;
}
err = of_property_read_u32(np, "default_bpp", &bpp);
if (err) {
dev_dbg(&pdev->dev, "get of property bpp fail\n");
return err;
}
err = of_property_read_u32(np, "int_clk", &int_clk);
if (err) {
dev_dbg(&pdev->dev, "get of property int_clk fail\n");
return err;
}
err = of_property_read_u32(np, "late_init", &late_init);
if (err) {
dev_dbg(&pdev->dev, "get of property late_init fail\n");
return err;
}
plat_data->prefetch = of_property_read_bool(np, "prefetch");
if (!strncmp(pixfmt, "RGB24", 5))
plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB24;
else if (!strncmp(pixfmt, "BGR24", 5))
plat_data->interface_pix_fmt = IPU_PIX_FMT_BGR24;
else if (!strncmp(pixfmt, "GBR24", 5))
plat_data->interface_pix_fmt = IPU_PIX_FMT_GBR24;
else if (!strncmp(pixfmt, "RGB565", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB565;
else if (!strncmp(pixfmt, "RGB666", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_RGB666;
else if (!strncmp(pixfmt, "YUV444", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_YUV444;
else if (!strncmp(pixfmt, "LVDS666", 7))
plat_data->interface_pix_fmt = IPU_PIX_FMT_LVDS666;
else if (!strncmp(pixfmt, "YUYV16", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_YUYV;
else if (!strncmp(pixfmt, "UYVY16", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_UYVY;
else if (!strncmp(pixfmt, "YVYU16", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_YVYU;
else if (!strncmp(pixfmt, "VYUY16", 6))
plat_data->interface_pix_fmt = IPU_PIX_FMT_VYUY;
else {
dev_err(&pdev->dev, "err interface_pix_fmt!\n");
return -ENOENT;
}
len = min(sizeof(plat_data->disp_dev) - 1, strlen(disp_dev));
memcpy(plat_data->disp_dev, disp_dev, len);
plat_data->disp_dev[len] = '\0';
plat_data->mode_str = (char *)mode_str;
plat_data->default_bpp = bpp;
plat_data->int_clk = (bool)int_clk;
plat_data->late_init = (bool)late_init;
return err;
}
/*!
* Probe routine for the framebuffer driver. It is called during the
* driver binding process. The following functions are performed in
* this routine: Framebuffer initialization, Memory allocation and
* mapping, Framebuffer registration, IPU initialization.
*
* @return Appropriate error code to the kernel common code
*/
static int mxcfb_probe(struct platform_device *pdev)
{
struct ipuv3_fb_platform_data *plat_data;
struct fb_info *fbi;
struct mxcfb_info *mxcfbi;
struct resource *res;
int ret = 0;
dev_dbg(&pdev->dev, "%s enter\n", __func__);
pdev->id = of_alias_get_id(pdev->dev.of_node, "mxcfb");
if (pdev->id < 0) {
dev_err(&pdev->dev, "can not get alias id\n");
return pdev->id;
}
plat_data = devm_kzalloc(&pdev->dev, sizeof(struct
ipuv3_fb_platform_data), GFP_KERNEL);
if (!plat_data)
return -ENOMEM;
pdev->dev.platform_data = plat_data;
ret = mxcfb_get_of_property(pdev, plat_data);
if (ret < 0) {
dev_err(&pdev->dev, "get mxcfb of property fail\n");
return ret;
}
/* Initialize FB structures */
fbi = mxcfb_init_fbinfo(&pdev->dev, &mxcfb_ops);
if (!fbi) {
ret = -ENOMEM;
goto init_fbinfo_failed;
}
ret = mxcfb_option_setup(pdev, fbi);
if (ret)
goto get_fb_option_failed;
mxcfbi = (struct mxcfb_info *)fbi->par;
mxcfbi->ipu_int_clk = plat_data->int_clk;
mxcfbi->late_init = plat_data->late_init;
mxcfbi->first_set_par = true;
mxcfbi->prefetch = plat_data->prefetch;
mxcfbi->pre_num = -1;
spin_lock_init(&mxcfbi->spin_lock);
ret = mxcfb_dispdrv_init(pdev, fbi);
if (ret < 0)
goto init_dispdrv_failed;
ret = ipu_test_set_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
if (ret < 0) {
dev_err(&pdev->dev, "ipu%d-di%d already in use\n",
mxcfbi->ipu_id, mxcfbi->ipu_di);
goto ipu_in_busy;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res && res->start && res->end) {
fbi->fix.smem_len = res->end - res->start + 1;
fbi->fix.smem_start = res->start;
fbi->screen_base = ioremap(fbi->fix.smem_start, fbi->fix.smem_len);
/* Do not clear the fb content drawn in bootloader. */
if (!mxcfbi->late_init)
memset(fbi->screen_base, 0, fbi->fix.smem_len);
}
mxcfbi->ipu = ipu_get_soc(mxcfbi->ipu_id);
if (IS_ERR(mxcfbi->ipu)) {
ret = -ENODEV;
goto get_ipu_failed;
}
/* first user uses DP with alpha feature */
if (!g_dp_in_use[mxcfbi->ipu_id]) {
mxcfbi->ipu_ch_irq = IPU_IRQ_BG_SYNC_EOF;
mxcfbi->ipu_ch_nf_irq = IPU_IRQ_BG_SYNC_NFACK;
mxcfbi->ipu_alp_ch_irq = IPU_IRQ_BG_ALPHA_SYNC_EOF;
mxcfbi->ipu_ch = MEM_BG_SYNC;
/* Unblank the primary fb only by default */
if (pdev->id == 0)
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_UNBLANK;
else
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
ret = mxcfb_register(fbi);
if (ret < 0)
goto mxcfb_register_failed;
ipu_disp_set_global_alpha(mxcfbi->ipu, mxcfbi->ipu_ch,
true, 0x80);
ipu_disp_set_color_key(mxcfbi->ipu, mxcfbi->ipu_ch, false, 0);
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
ret = mxcfb_setup_overlay(pdev, fbi, res);
if (ret < 0) {
mxcfb_unregister(fbi);
goto mxcfb_setupoverlay_failed;
}
g_dp_in_use[mxcfbi->ipu_id] = true;
ret = device_create_file(mxcfbi->ovfbi->dev,
&dev_attr_fsl_disp_property);
if (ret)
dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
"file for disp property\n",
ret);
ret = device_create_file(mxcfbi->ovfbi->dev,
&dev_attr_fsl_disp_dev_property);
if (ret)
dev_err(mxcfbi->ovfbi->dev, "Error %d on creating "
"file for disp device "
"propety\n", ret);
} else {
mxcfbi->ipu_ch_irq = IPU_IRQ_DC_SYNC_EOF;
mxcfbi->ipu_ch_nf_irq = IPU_IRQ_DC_SYNC_NFACK;
mxcfbi->ipu_alp_ch_irq = -1;
mxcfbi->ipu_ch = MEM_DC_SYNC;
mxcfbi->cur_blank = mxcfbi->next_blank = FB_BLANK_POWERDOWN;
ret = mxcfb_register(fbi);
if (ret < 0)
goto mxcfb_register_failed;
}
platform_set_drvdata(pdev, fbi);
ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_property);
if (ret)
dev_err(&pdev->dev, "Error %d on creating file for disp "
"property\n", ret);
ret = device_create_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
if (ret)
dev_err(&pdev->dev, "Error %d on creating file for disp "
" device propety\n", ret);
return 0;
mxcfb_setupoverlay_failed:
mxcfb_register_failed:
get_ipu_failed:
ipu_clear_usage(mxcfbi->ipu_id, mxcfbi->ipu_di);
ipu_in_busy:
init_dispdrv_failed:
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
get_fb_option_failed:
init_fbinfo_failed:
return ret;
}
static int mxcfb_remove(struct platform_device *pdev)
{
struct fb_info *fbi = platform_get_drvdata(pdev);
struct mxcfb_info *mxc_fbi = fbi->par;
device_remove_file(fbi->dev, &dev_attr_fsl_disp_dev_property);
device_remove_file(fbi->dev, &dev_attr_fsl_disp_property);
mxcfb_blank(FB_BLANK_POWERDOWN, fbi);
mxcfb_unregister(fbi);
mxcfb_unmap_video_memory(fbi);
if (mxc_fbi->ovfbi) {
device_remove_file(mxc_fbi->ovfbi->dev,
&dev_attr_fsl_disp_dev_property);
device_remove_file(mxc_fbi->ovfbi->dev,
&dev_attr_fsl_disp_property);
mxcfb_blank(FB_BLANK_POWERDOWN, mxc_fbi->ovfbi);
mxcfb_unsetup_overlay(fbi);
mxcfb_unmap_video_memory(mxc_fbi->ovfbi);
}
ipu_clear_usage(mxc_fbi->ipu_id, mxc_fbi->ipu_di);
if (&fbi->cmap)
fb_dealloc_cmap(&fbi->cmap);
framebuffer_release(fbi);
return 0;
}
static const struct of_device_id imx_mxcfb_dt_ids[] = {
{ .compatible = "fsl,mxc_sdc_fb"},
{ /* sentinel */ }
};
/*!
* This structure contains pointers to the power management callback functions.
*/
static struct platform_driver mxcfb_driver = {
.driver = {
.name = MXCFB_NAME,
.of_match_table = imx_mxcfb_dt_ids,
},
.probe = mxcfb_probe,
.remove = mxcfb_remove,
.suspend = mxcfb_suspend,
.resume = mxcfb_resume,
};
/*!
* Main entry function for the framebuffer. The function registers the power
* management callback functions with the kernel and also registers the MXCFB
* callback functions with the core Linux framebuffer driver \b fbmem.c
*
* @return Error code indicating success or failure
*/
int __init mxcfb_init(void)
{
return platform_driver_register(&mxcfb_driver);
}
void mxcfb_exit(void)
{
platform_driver_unregister(&mxcfb_driver);
}
module_init(mxcfb_init);
module_exit(mxcfb_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC framebuffer driver");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("fb");