summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev/fsl-dcu-fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/fbdev/fsl-dcu-fb.c')
-rw-r--r--drivers/video/fbdev/fsl-dcu-fb.c1275
1 files changed, 1275 insertions, 0 deletions
diff --git a/drivers/video/fbdev/fsl-dcu-fb.c b/drivers/video/fbdev/fsl-dcu-fb.c
new file mode 100644
index 000000000000..a1da36f131f0
--- /dev/null
+++ b/drivers/video/fbdev/fsl-dcu-fb.c
@@ -0,0 +1,1275 @@
+/*
+ * Copyright 2012-2013 Freescale Semiconductor, Inc.
+ *
+ * Freescale DCU framebuffer device driver
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+#include <linux/pm_runtime.h>
+#include <linux/console.h>
+
+#define DRIVER_NAME "fsl-dcu-fb"
+
+#define DCU_DCU_MODE 0x0010
+#define DCU_MODE_BLEND_ITER(x) ((x) << 20)
+#define DCU_MODE_RASTER_EN (1 << 14)
+#define DCU_MODE_DCU_MODE(x) (x)
+#define DCU_MODE_DCU_MODE_MASK 0x03
+#define DCU_MODE_OFF 0
+#define DCU_MODE_NORMAL 1
+#define DCU_MODE_TEST 2
+#define DCU_MODE_COLORBAR 3
+
+#define DCU_BGND 0x0014
+#define DCU_BGND_R(x) ((x) << 16)
+#define DCU_BGND_G(x) ((x) << 8)
+#define DCU_BGND_B(x) (x)
+
+#define DCU_DISP_SIZE 0x0018
+#define DCU_DISP_SIZE_DELTA_Y(x) ((x) << 16)
+#define DCU_DISP_SIZE_DELTA_X(x) (x)
+
+#define DCU_HSYN_PARA 0x001c
+#define DCU_HSYN_PARA_BP(x) ((x) << 22)
+#define DCU_HSYN_PARA_PW(x) ((x) << 11)
+#define DCU_HSYN_PARA_FP(x) (x)
+
+#define DCU_VSYN_PARA 0x0020
+#define DCU_VSYN_PARA_BP(x) ((x) << 22)
+#define DCU_VSYN_PARA_PW(x) ((x) << 11)
+#define DCU_VSYN_PARA_FP(x) (x)
+
+#define DCU_SYN_POL 0x0024
+#define DCU_SYN_POL_INV_PXCK_FALL (1 << 6)
+#define DCU_SYN_POL_NEG_REMAIN (1 << 5)
+#define DCU_SYN_POL_INV_VS_LOW (1 << 1)
+#define DCU_SYN_POL_INV_HS_LOW (1)
+
+#define DCU_THRESHOLD 0x0028
+#define DCU_THRESHOLD_LS_BF_VS(x) ((x) << 16)
+#define DCU_THRESHOLD_OUT_BUF_HIGH(x) ((x) << 8)
+#define DCU_THRESHOLD_OUT_BUF_LOW(x) (x)
+
+#define DCU_INT_STATUS 0x002C
+#define DCU_INT_STATUS_UNDRUN (1 << 1)
+#define DCU_INT_STATUS_VSYNC (1 << 0)
+
+#define DCU_INT_MASK 0x0030
+#define DCU_INT_MASK_UNDRUN (1 << 1)
+#define DCU_INT_MASK_VSYNC (1 << 0)
+
+#define DCU_DIV_RATIO 0x0054
+
+#define DCU_UPDATE_MODE 0x00cc
+#define DCU_UPDATE_MODE_MODE (1 << 31)
+#define DCU_UPDATE_MODE_READREG (1 << 30)
+
+#define DCU_CTRLDESCLN_1(x) (0x200 + (x) * 0x40)
+#define DCU_CTRLDESCLN_1_HEIGHT(x) ((x) << 16)
+#define DCU_CTRLDESCLN_1_WIDTH(x) (x)
+
+#define DCU_CTRLDESCLN_2(x) (0x204 + (x) * 0x40)
+#define DCU_CTRLDESCLN_2_POSY(x) ((x) << 16)
+#define DCU_CTRLDESCLN_2_POSX(x) (x)
+
+#define DCU_CTRLDESCLN_3(x) (0x208 + (x) * 0x40)
+
+#define DCU_CTRLDESCLN_4(x) (0x20c + (x) * 0x40)
+#define DCU_CTRLDESCLN_4_EN (1 << 31)
+#define DCU_CTRLDESCLN_4_TILE_EN (1 << 30)
+#define DCU_CTRLDESCLN_4_DATA_SEL_CLUT (1 << 29)
+#define DCU_CTRLDESCLN_4_SAFETY_EN (1 << 28)
+#define DCU_CTRLDESCLN_4_TRANS(x) ((x) << 20)
+#define DCU_CTRLDESCLN_4_BPP(x) ((x) << 16)
+#define DCU_CTRLDESCLN_4_RLE_EN (1 << 15)
+#define DCU_CTRLDESCLN_4_LUOFFS(x) ((x) << 4)
+#define DCU_CTRLDESCLN_4_BB_ON (1 << 2)
+#define DCU_CTRLDESCLN_4_AB(x) (x)
+
+#define DCU_CTRLDESCLN_5(x) (0x210 + (x) * 0x40)
+#define DCU_CTRLDESCLN_5_CKMAX_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_5_CKMAX_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_5_CKMAX_B(x) (x)
+
+#define DCU_CTRLDESCLN_6(x) (0x214 + (x) * 0x40)
+#define DCU_CTRLDESCLN_6_CKMIN_R(x) ((x) << 16)
+#define DCU_CTRLDESCLN_6_CKMIN_G(x) ((x) << 8)
+#define DCU_CTRLDESCLN_6_CKMIN_B(x) (x)
+
+#define DCU_CTRLDESCLN_7(x) (0x218 + (x) * 0x40)
+#define DCU_CTRLDESCLN_7_TILE_VER(x) ((x) << 16)
+#define DCU_CTRLDESCLN_7_TILE_HOR(x) (x)
+
+#define DCU_CTRLDESCLN_8(x) (0x21c + (x) * 0x40)
+#define DCU_CTRLDESCLN_8_FG_FCOLOR(x) (x)
+
+#define DCU_CTRLDESCLN_9(x) (0x220 + (x) * 0x40)
+#define DCU_CTRLDESCLN_9_BG_BCOLOR(x) (x)
+
+#define DCU_TOTAL_LAYER_NUM 64
+#define DCU_LAYER_NUM_MAX 6
+
+#define BPP_16_RGB565 4
+#define BPP_24_RGB888 5
+#define BPP_32_ARGB8888 6
+
+#define TCON_CTRL1 0x0000
+#define TCON_BYPASS_ENABLE (1 << 29)
+
+#define MFB_SET_ALPHA _IOW('M', 0, __u8)
+#define MFB_GET_ALPHA _IOR('M', 0, __u8)
+#define MFB_SET_LAYER _IOW('M', 4, struct layer_display_offset)
+#define MFB_GET_LAYER _IOR('M', 4, struct layer_display_offset)
+
+struct dcu_fb_data {
+ struct fb_info *fsl_dcu_info[DCU_LAYER_NUM_MAX];
+ struct device *dev;
+ void __iomem *reg_base;
+ unsigned int irq;
+ struct clk *clk;
+ struct fb_videomode *mode_db;
+ int modecnt;
+ struct fb_videomode native_mode;
+ u32 bits_per_pixel;
+ bool pixclockpol;
+ struct completion vsync_wait;
+};
+
+struct layer_display_offset {
+ int x_layer_d;
+ int y_layer_d;
+};
+
+struct mfb_info {
+ int index;
+ char *id;
+ unsigned long pseudo_palette[16];
+ unsigned char alpha;
+ unsigned char blend;
+ unsigned int count;
+ int x_layer_d; /* layer display x offset to physical screen */
+ int y_layer_d; /* layer display y offset to physical screen */
+ struct dcu_fb_data *parent;
+};
+
+enum mfb_index {
+ LAYER0 = 0,
+ LAYER1,
+ LAYER2,
+ LAYER3,
+ LAYER4,
+ LAYER5,
+};
+
+static struct mfb_info mfb_template[] = {
+ {
+ .index = LAYER0,
+ .id = "Layer0",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 0,
+ .y_layer_d = 0,
+ },
+ {
+ .index = LAYER1,
+ .id = "Layer1",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 50,
+ .y_layer_d = 50,
+ },
+ {
+ .index = LAYER2,
+ .id = "Layer2",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 100,
+ .y_layer_d = 100,
+ },
+ {
+ .index = LAYER3,
+ .id = "Layer3",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 150,
+ .y_layer_d = 150,
+ },
+ {
+ .index = LAYER4,
+ .id = "Layer4",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 200,
+ .y_layer_d = 200,
+ },
+ {
+ .index = LAYER5,
+ .id = "Layer5",
+ .alpha = 0xff,
+ .blend = 0,
+ .count = 0,
+ .x_layer_d = 250,
+ .y_layer_d = 250,
+ },
+};
+
+static inline unsigned int fsl_dcu_get_offset(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ int pixel_offset;
+
+ pixel_offset = (var->yoffset * var->xres_virtual) + var->xoffset;
+ return info->fix.smem_start +
+ (pixel_offset * (var->bits_per_pixel >> 3));
+}
+
+static int enable_panel(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int bpp;
+ unsigned int addr;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(var->yres) |
+ DCU_CTRLDESCLN_1_WIDTH(var->xres),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(mfbi->y_layer_d) |
+ DCU_CTRLDESCLN_2_POSX(mfbi->x_layer_d),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ addr = fsl_dcu_get_offset(info);
+ writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ bpp = BPP_16_RGB565;
+ break;
+ case 24:
+ bpp = BPP_24_RGB888;
+ break;
+ case 32:
+ bpp = BPP_32_ARGB8888;
+ break;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ writel(DCU_CTRLDESCLN_4_EN |
+ DCU_CTRLDESCLN_4_TRANS(mfbi->alpha) |
+ DCU_CTRLDESCLN_4_BPP(bpp) |
+ DCU_CTRLDESCLN_4_AB(mfbi->blend),
+ dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0xff) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0xff),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+
+ /* Wait until transfer is complete and switch to automatic updates */
+ while (readl(dcufb->reg_base + DCU_UPDATE_MODE) & DCU_UPDATE_MODE_READREG);
+ writel(DCU_UPDATE_MODE_MODE, dcufb->reg_base + DCU_UPDATE_MODE);
+
+ return 0;
+}
+
+static int disable_panel(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ writel(DCU_CTRLDESCLN_1_HEIGHT(0) |
+ DCU_CTRLDESCLN_1_WIDTH(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_1(mfbi->index));
+ writel(DCU_CTRLDESCLN_2_POSY(0) | DCU_CTRLDESCLN_2_POSX(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_2(mfbi->index));
+
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_5_CKMAX_R(0) |
+ DCU_CTRLDESCLN_5_CKMAX_G(0) |
+ DCU_CTRLDESCLN_5_CKMAX_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_5(mfbi->index));
+ writel(DCU_CTRLDESCLN_6_CKMIN_R(0) |
+ DCU_CTRLDESCLN_6_CKMIN_G(0) |
+ DCU_CTRLDESCLN_6_CKMIN_B(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_6(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_7_TILE_VER(0) | DCU_CTRLDESCLN_7_TILE_HOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_7(mfbi->index));
+
+ writel(DCU_CTRLDESCLN_8_FG_FCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_8(mfbi->index));
+ writel(DCU_CTRLDESCLN_9_BG_BCOLOR(0),
+ dcufb->reg_base + DCU_CTRLDESCLN_9(mfbi->index));
+
+ /* Clear Mode flag and schedule one transfer using READREG */
+ writel(DCU_UPDATE_MODE_READREG, dcufb->reg_base + DCU_UPDATE_MODE);
+ return 0;
+}
+
+static void enable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int dcu_mode;
+
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_NORMAL),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static void disable_controller(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int dcu_mode;
+
+ dcu_mode = readl(dcufb->reg_base + DCU_DCU_MODE);
+ dcu_mode &= ~DCU_MODE_DCU_MODE_MASK;
+ writel(dcu_mode | DCU_MODE_DCU_MODE(DCU_MODE_OFF),
+ dcufb->reg_base + DCU_DCU_MODE);
+}
+
+static int fsl_dcu_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ if (var->grayscale || var->rotate || var->nonstd)
+ return -EINVAL;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if (var->xoffset + info->var.xres > info->var.xres_virtual)
+ var->xoffset = info->var.xres_virtual - info->var.xres;
+
+ if (var->yoffset + info->var.yres > info->var.yres_virtual)
+ var->yoffset = info->var.yres_virtual - info->var.yres;
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ var->red.length = 5;
+ var->red.offset = 11;
+ var->red.msb_right = 0;
+
+ var->green.length = 6;
+ var->green.offset = 5;
+ var->green.msb_right = 0;
+
+ var->blue.length = 5;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 24:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 32:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 8;
+ var->transp.offset = 24;
+ var->transp.msb_right = 0;
+ break;
+ default:
+ dev_err(dcufb->dev, "unsupported color depth: %u\n",
+ var->bits_per_pixel);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_calc_div(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned long long div;
+ div = (unsigned long long)(clk_get_rate(dcufb->clk) / 1000);
+ div *= info->var.pixclock;
+ do_div(div, 1000000000);
+
+ return div;
+}
+
+static void update_controller(struct fb_info *info)
+{
+ struct fb_var_screeninfo *var = &info->var;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int div, pol = 0;
+
+ div = fsl_dcu_calc_div(info);
+ writel((div - 1), dcufb->reg_base + DCU_DIV_RATIO);
+
+ writel(DCU_DISP_SIZE_DELTA_Y(var->yres) |
+ DCU_DISP_SIZE_DELTA_X(var->xres / 16),
+ dcufb->reg_base + DCU_DISP_SIZE);
+
+ /* Horizontal and vertical sync parameters */
+ writel(DCU_HSYN_PARA_BP(var->left_margin) |
+ DCU_HSYN_PARA_PW(var->hsync_len) |
+ DCU_HSYN_PARA_FP(var->right_margin),
+ dcufb->reg_base + DCU_HSYN_PARA);
+
+ writel(DCU_VSYN_PARA_BP(var->upper_margin) |
+ DCU_VSYN_PARA_PW(var->vsync_len) |
+ DCU_VSYN_PARA_FP(var->lower_margin),
+ dcufb->reg_base + DCU_VSYN_PARA);
+
+ /*
+ * pixclockpol = 0 => display samples data on falling edge => 0
+ * pixclockpol = 1 => display samples data on rising edge => 1 (default)
+ */
+ if (dcufb->pixclockpol)
+ pol |= DCU_SYN_POL_INV_PXCK_FALL;
+
+ /* hsync:0 => active low => HS_LOW */
+ if (!(var->sync & FB_SYNC_HOR_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_HS_LOW;
+
+ /* vsync:0 => active low => VS_LOW */
+ if (!(var->sync & FB_SYNC_VERT_HIGH_ACT))
+ pol |= DCU_SYN_POL_INV_VS_LOW;
+
+ writel(pol, dcufb->reg_base + DCU_SYN_POL);
+
+ writel(DCU_BGND_R(0) | DCU_BGND_G(0) | DCU_BGND_B(0),
+ dcufb->reg_base + DCU_BGND);
+
+ writel(DCU_MODE_BLEND_ITER(DCU_LAYER_NUM_MAX) | DCU_MODE_RASTER_EN,
+ dcufb->reg_base + DCU_DCU_MODE);
+
+ writel(DCU_THRESHOLD_LS_BF_VS(0x3) | DCU_THRESHOLD_OUT_BUF_HIGH(0x78) |
+ DCU_THRESHOLD_OUT_BUF_LOW(0), dcufb->reg_base + DCU_THRESHOLD);
+}
+
+static int map_video_memory(struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ u32 smem_len = info->fix.line_length * info->var.yres_virtual;
+
+ info->fix.smem_len = smem_len;
+
+ info->screen_base = dma_alloc_writecombine(info->device,
+ info->fix.smem_len, (dma_addr_t *)&info->fix.smem_start,
+ GFP_KERNEL);
+ if (!info->screen_base) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+
+ memset(info->screen_base, 0, info->fix.smem_len);
+
+ return 0;
+}
+
+static void unmap_video_memory(struct fb_info *info)
+{
+ if (!info->screen_base)
+ return;
+
+ dma_free_writecombine(info->device, info->fix.smem_len,
+ info->screen_base, info->fix.smem_start);
+
+ info->screen_base = NULL;
+ info->fix.smem_start = 0;
+ info->fix.smem_len = 0;
+}
+
+static int fsl_dcu_set_par(struct fb_info *info)
+{
+ unsigned long len;
+ struct fb_var_screeninfo *var = &info->var;
+ struct fb_fix_screeninfo *fix = &info->fix;
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+
+ 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->ypanstep = 1;
+
+ len = info->var.yres_virtual * info->fix.line_length;
+ if (len != info->fix.smem_len) {
+ if (info->fix.smem_start)
+ unmap_video_memory(info);
+
+ if (map_video_memory(info)) {
+ dev_err(dcufb->dev, "unable to allocate fb memory\n");
+ return -ENOMEM;
+ }
+ }
+
+ /* Only layer 0 could update LCD controller */
+ if (mfbi->index == LAYER0) {
+ update_controller(info);
+ enable_controller(info);
+ }
+
+ enable_panel(info);
+ return 0;
+}
+
+static inline __u32 CNVT_TOHW(__u32 val, __u32 width)
+{
+ return ((val<<width) + 0x7FFF - val) >> 16;
+}
+
+static int fsl_dcu_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *info)
+{
+ unsigned int val;
+ int ret = -EINVAL;
+
+ /*
+ * If greyscale is true, then we convert the RGB value
+ * to greyscale no matter what visual we are using.
+ */
+ if (info->var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+ switch (info->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 = info->pseudo_palette;
+
+ red = CNVT_TOHW(red, info->var.red.length);
+ green = CNVT_TOHW(green, info->var.green.length);
+ blue = CNVT_TOHW(blue, info->var.blue.length);
+ transp = CNVT_TOHW(transp, info->var.transp.length);
+
+ val = (red << info->var.red.offset) |
+ (green << info->var.green.offset) |
+ (blue << info->var.blue.offset) |
+ (transp << info->var.transp.offset);
+
+ pal[regno] = val;
+ ret = 0;
+ }
+ break;
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+
+ return ret;
+}
+
+static int fsl_dcu_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ unsigned int addr;
+
+ if ((info->var.xoffset == var->xoffset) &&
+ (info->var.yoffset == var->yoffset))
+ return 0;
+
+ if ((var->xoffset + info->var.xres) > info->var.xres_virtual
+ || (var->yoffset + info->var.yres) > info->var.yres_virtual)
+ return -EINVAL;
+
+ info->var.xoffset = var->xoffset;
+ info->var.yoffset = var->yoffset;
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ info->var.vmode |= FB_VMODE_YWRAP;
+ else
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+
+ addr = fsl_dcu_get_offset(info);
+ writel(addr, dcufb->reg_base + DCU_CTRLDESCLN_3(mfbi->index));
+
+ return 0;
+}
+
+static int fsl_dcu_blank(int blank_mode, struct fb_info *info)
+{
+ switch (blank_mode) {
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ disable_panel(info);
+ break;
+ case FB_BLANK_POWERDOWN:
+ disable_controller(info);
+ disable_panel(info);
+ break;
+ case FB_BLANK_UNBLANK:
+ enable_controller(info);
+ enable_panel(info);
+ break;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_wait_for_vsync(struct dcu_fb_data *dcufb)
+{
+ unsigned long mask = readl(dcufb->reg_base + DCU_INT_MASK);
+ int ret;
+
+ /*
+ * Clear current VSYNC status since that still contains the flag from
+ * last VSYNC...
+ */
+ writel(DCU_INT_STATUS_VSYNC, dcufb->reg_base + DCU_INT_STATUS);
+ writel(mask & ~DCU_INT_MASK_VSYNC, dcufb->reg_base + DCU_INT_MASK);
+
+ ret = wait_for_completion_timeout(&dcufb->vsync_wait, HZ / 2);
+
+ if (ret < 0)
+ return ret;
+ if (ret == 0) {
+ dev_warn(dcufb->dev, "wait_for_vsync timed out!\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int fsl_dcu_ioctl(struct fb_info *info, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct layer_display_offset layer_d;
+ void __user *buf = (void __user *)arg;
+ unsigned char alpha;
+
+ switch (cmd) {
+ case MFB_SET_LAYER:
+ if (copy_from_user(&layer_d, buf, sizeof(layer_d)))
+ return -EFAULT;
+ mfbi->x_layer_d = layer_d.x_layer_d;
+ mfbi->y_layer_d = layer_d.y_layer_d;
+ fsl_dcu_set_par(info);
+ break;
+ case MFB_GET_LAYER:
+ layer_d.x_layer_d = mfbi->x_layer_d;
+ layer_d.y_layer_d = mfbi->y_layer_d;
+ if (copy_to_user(buf, &layer_d, sizeof(layer_d)))
+ return -EFAULT;
+ break;
+ case MFB_GET_ALPHA:
+ alpha = mfbi->alpha;
+ if (copy_to_user(buf, &alpha, sizeof(alpha)))
+ return -EFAULT;
+ break;
+ case MFB_SET_ALPHA:
+ if (copy_from_user(&alpha, buf, sizeof(alpha)))
+ return -EFAULT;
+ mfbi->blend = 1;
+ mfbi->alpha = alpha;
+ fsl_dcu_set_par(info);
+ break;
+ case FBIO_WAITFORVSYNC:
+ return fsl_dcu_wait_for_vsync(dcufb);
+ default:
+ dev_err(dcufb->dev, "unknown ioctl command (0x%08X)\n", cmd);
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static void reset_layers(struct dcu_fb_data *dcufb)
+{
+ int i;
+
+ for (i = 0; i < DCU_TOTAL_LAYER_NUM; i++) {
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_1(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_2(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_3(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_4(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_5(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_6(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_7(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_8(i));
+ writel(0, dcufb->reg_base + DCU_CTRLDESCLN_9(i));
+ }
+}
+
+static int fsl_dcu_open(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ int ret;
+
+ mfbi->index = info->node;
+
+ if (mfbi->count == 0) {
+ ret = fsl_dcu_check_var(&info->var, info);
+ if (ret < 0)
+ return ret;
+
+ ret = fsl_dcu_set_par(info);
+ if (ret < 0)
+ return ret;
+ }
+ mfbi->count++;
+
+ return 0;
+}
+
+static int fsl_dcu_release(struct fb_info *info, int user)
+{
+ struct mfb_info *mfbi = info->par;
+ int ret = 0;
+
+ mfbi->count--;
+ if (mfbi->count == 0)
+ ret = disable_panel(info);
+
+ return ret;
+}
+
+static struct fb_ops fsl_dcu_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = fsl_dcu_check_var,
+ .fb_set_par = fsl_dcu_set_par,
+ .fb_setcolreg = fsl_dcu_setcolreg,
+ .fb_blank = fsl_dcu_blank,
+ .fb_pan_display = fsl_dcu_pan_display,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_ioctl = fsl_dcu_ioctl,
+ .fb_open = fsl_dcu_open,
+ .fb_release = fsl_dcu_release,
+};
+
+static int fsl_dcu_init_modelist(struct dcu_fb_data *dcufb)
+{
+ struct device_node *np = dcufb->dev->of_node;
+ struct device_node *display_np;
+ struct display_timings *timings;
+ int i;
+ int ret = 0;
+
+ display_np = of_parse_phandle(np, "display", 0);
+ if (!display_np) {
+ dev_err(dcufb->dev, "failed to find display phandle\n");
+ return -ENOENT;
+ }
+
+ ret = of_property_read_u32(display_np, "bits-per-pixel",
+ &dcufb->bits_per_pixel);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to get property bits-per-pixel\n");
+ goto put_display_node;
+ }
+
+ timings = of_get_display_timings(display_np);
+ if (!timings) {
+ dev_err(dcufb->dev, "failed to get display timings\n");
+ ret = -ENOENT;
+ goto put_display_node;
+ }
+
+ dcufb->mode_db = devm_kzalloc(dcufb->dev, sizeof(struct fb_videomode) *
+ timings->num_timings, GFP_KERNEL);
+ if (!dcufb->mode_db) {
+ ret = -ENOMEM;
+ goto put_display_node;
+ }
+
+ for (i = 0; i < timings->num_timings; i++) {
+ struct videomode vm;
+
+ ret = videomode_from_timings(timings, &vm, i);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ ret = fb_videomode_from_videomode(&vm, &dcufb->mode_db[i]);
+ if (ret < 0)
+ goto free_dcu_mode_db;
+
+ if (i == timings->native_mode) {
+ fb_videomode_from_videomode(&vm, &dcufb->native_mode);
+
+ /*
+ * Kernel pixelclk settings are controller centric
+ * whereas DCU is display centric:
+ * PIXDATA_NEGEDGE (drive data on falling edge)
+ * => pixclockpol (display samples data on rising edge)
+ */
+ dcufb->pixclockpol = timings->timings[i]->flags &
+ DISPLAY_FLAGS_PIXDATA_NEGEDGE;
+ }
+
+ dcufb->modecnt++;
+ }
+
+ of_node_put(display_np);
+ return 0;
+
+free_dcu_mode_db:
+ kfree(dcufb->mode_db);
+ dcufb->mode_db = NULL;
+put_display_node:
+ of_node_put(display_np);
+ return ret;
+}
+
+static int parse_opt(struct dcu_fb_data *dcufb, char *this_opt, u32 *sync)
+{
+ if (!strncmp(this_opt, "hsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_HOR_HIGH_ACT;
+ } else if (!strncmp(this_opt, "vsync:", 6)) {
+ if (simple_strtoul(this_opt+6, NULL, 0))
+ *sync |= FB_SYNC_VERT_HIGH_ACT;
+ } else if (!strncmp(this_opt, "pixclockpol:", 12))
+ dcufb->pixclockpol = !!simple_strtoul(this_opt+12, NULL, 0);
+ else
+ return -EINVAL;
+
+ return 0;
+}
+
+static int fsl_dcu_parse_options(struct dcu_fb_data *dcufb,
+ struct fb_info *info, char *option)
+{
+ char *this_opt;
+ int ret = 0;
+ u32 sync = 0;
+
+ while ((this_opt = strsep(&option, ",")) != NULL) {
+ /* Parse driver specific arguments */
+ if (parse_opt(dcufb, this_opt, &sync) == 0)
+ continue;
+
+ /* No valid driver specific argument, has to be mode */
+ ret = fb_find_mode(&info->var, info, this_opt, dcufb->mode_db,
+ dcufb->modecnt, &dcufb->native_mode,
+ dcufb->bits_per_pixel);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Overwrite from command line */
+ info->var.sync = sync;
+ return 0;
+}
+
+static int install_framebuffer(struct fb_info *info, char *option)
+{
+ struct mfb_info *mfbi = info->par;
+ struct dcu_fb_data *dcufb = mfbi->parent;
+ struct fb_videomode *mode = &dcufb->native_mode;
+ int ret;
+
+ info->var.activate = FB_ACTIVATE_NOW;
+ info->fbops = &fsl_dcu_ops;
+ info->flags = FBINFO_FLAG_DEFAULT;
+ info->pseudo_palette = &mfbi->pseudo_palette;
+
+ fb_alloc_cmap(&info->cmap, 16, 0);
+
+ INIT_LIST_HEAD(&info->modelist);
+ fb_add_videomode(mode, &info->modelist);
+ fb_videomode_to_var(&info->var, mode);
+ info->var.bits_per_pixel = dcufb->bits_per_pixel;
+
+ /* Parse DCU option for every layer... */
+ ret = fsl_dcu_parse_options(dcufb, info, option);
+ if (ret < 0)
+ return ret;
+
+ fsl_dcu_check_var(&info->var, info);
+ ret = register_framebuffer(info);
+ if (ret < 0) {
+ dev_err(dcufb->dev, "failed to register framebuffer device\n");
+ return ret;
+ }
+
+ printk(KERN_INFO "fb%d: fb device registered successfully.\n",
+ info->node);
+ return 0;
+}
+
+static void uninstall_framebuffer(struct fb_info *info)
+{
+ unregister_framebuffer(info);
+ unmap_video_memory(info);
+
+ if (&info->cmap)
+ fb_dealloc_cmap(&info->cmap);
+}
+
+static irqreturn_t fsl_dcu_irq(int irq, void *dev_id)
+{
+ struct dcu_fb_data *dcufb = dev_id;
+ unsigned int mask = readl(dcufb->reg_base + DCU_INT_MASK);
+ unsigned int status = readl(dcufb->reg_base + DCU_INT_STATUS);
+
+ if (status & DCU_INT_STATUS_VSYNC) {
+ mask |= DCU_INT_MASK_VSYNC;
+ writel(mask, dcufb->reg_base + DCU_INT_MASK);
+ complete(&dcufb->vsync_wait);
+ }
+
+ writel(status, dcufb->reg_base + DCU_INT_STATUS);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM_RUNTIME
+static int fsl_dcu_runtime_suspend(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_disable_unprepare(dcufb->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_runtime_resume(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+
+ clk_prepare_enable(dcufb->clk);
+
+ return 0;
+}
+#endif
+
+static int bypass_tcon(struct device_node *np)
+{
+ struct device_node *tcon_np;
+ struct platform_device *tcon_pdev;
+ struct clk *tcon_clk;
+ struct resource *res;
+ void __iomem *tcon_reg;
+
+ tcon_np = of_parse_phandle(np, "tcon-controller", 0);
+ if (!tcon_np)
+ return -EINVAL;
+
+ tcon_pdev = of_find_device_by_node(tcon_np);
+ if (!tcon_pdev)
+ return -EINVAL;
+
+ tcon_clk = devm_clk_get(&tcon_pdev->dev, "tcon");
+ if (IS_ERR(tcon_clk))
+ return PTR_ERR(tcon_clk);
+ clk_prepare_enable(tcon_clk);
+
+ res = platform_get_resource(tcon_pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ tcon_reg = ioremap(res->start, resource_size(res));
+ if (IS_ERR(tcon_reg))
+ return PTR_ERR(tcon_reg);
+
+ writel(TCON_BYPASS_ENABLE, tcon_reg + TCON_CTRL1);
+
+ iounmap(tcon_reg);
+ clk_disable_unprepare(tcon_clk);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int fsl_dcu_suspend(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+ struct fb_info *fbi = dcufb->fsl_dcu_info[0];
+
+ console_lock();
+ fb_set_suspend(fbi, 1);
+ console_unlock();
+
+ disable_controller(dcufb->fsl_dcu_info[0]);
+ disable_panel(fbi);
+ clk_disable_unprepare(dcufb->clk);
+
+ return 0;
+}
+
+static int fsl_dcu_resume(struct device *dev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(dev);
+ struct fb_info *fbi = dcufb->fsl_dcu_info[0];
+ int ret;
+
+ clk_prepare_enable(dcufb->clk);
+
+ ret = bypass_tcon(dev->of_node);
+ if (ret) {
+ dev_err(dev, "could not bypass TCON\n");
+ goto failed_bypasstcon;
+ }
+
+ reset_layers(dcufb);
+
+ fsl_dcu_set_par(fbi);
+
+ console_lock();
+ fb_set_suspend(fbi, 0);
+ console_unlock();
+
+failed_bypasstcon:
+ return ret;
+}
+#endif
+
+static int fsl_dcu_probe(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb;
+ struct mfb_info *mfbi;
+ struct resource *res;
+ int ret = 0;
+ int i;
+ char *option = NULL;
+
+ fb_get_options("dcufb", &option);
+
+ if (option != NULL) {
+ dev_info(&pdev->dev, "using cmd options: %s\n", option);
+ if (!strcmp(option, "off"))
+ return -ENODEV;
+ }
+
+ dcufb = devm_kzalloc(&pdev->dev,
+ sizeof(struct dcu_fb_data), GFP_KERNEL);
+ if (!dcufb)
+ return -ENOMEM;
+
+ /* initialize the vsync wait queue */
+ init_completion(&dcufb->vsync_wait);
+
+ dcufb->dev = &pdev->dev;
+ dev_set_drvdata(&pdev->dev, dcufb);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "could not get memory IO resource\n");
+ return -ENODEV;
+ }
+
+ dcufb->reg_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dcufb->reg_base)) {
+ dev_err(&pdev->dev, "could not ioremap resource\n");
+ return PTR_ERR(dcufb->reg_base);
+ }
+
+ dcufb->irq = platform_get_irq(pdev, 0);
+ if (!dcufb->irq) {
+ dev_err(&pdev->dev, "could not get irq\n");
+ return -EINVAL;
+ }
+
+ ret = devm_request_irq(&pdev->dev, dcufb->irq, fsl_dcu_irq,
+ 0, DRIVER_NAME, dcufb);
+ if (ret) {
+ dev_err(&pdev->dev, "could not request irq\n");
+ goto failed_ioremap;
+ }
+
+ /* Put TCON in bypass mode, so the input signals from DCU are passed
+ * through TCON unchanged */
+ ret = bypass_tcon(pdev->dev.of_node);
+ if (ret) {
+ dev_err(&pdev->dev, "could not bypass TCON\n");
+ goto failed_bypasstcon;
+ }
+
+ dcufb->clk = devm_clk_get(&pdev->dev, "dcu");
+ if (IS_ERR(dcufb->clk)) {
+ ret = PTR_ERR(dcufb->clk);
+ dev_err(&pdev->dev, "could not get clock\n");
+ goto failed_getclock;
+ }
+ clk_prepare_enable(dcufb->clk);
+
+ pm_runtime_enable(dcufb->dev);
+ pm_runtime_get_sync(dcufb->dev);
+
+ ret = fsl_dcu_init_modelist(dcufb);
+ if (ret)
+ goto failed_alloc_framebuffer;
+
+ reset_layers(dcufb);
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ dcufb->fsl_dcu_info[i] =
+ framebuffer_alloc(sizeof(struct mfb_info), &pdev->dev);
+ if (!dcufb->fsl_dcu_info[i]) {
+ ret = -ENOMEM;
+ goto failed_alloc_framebuffer;
+ }
+
+ dcufb->fsl_dcu_info[i]->fix.smem_start = 0;
+
+ mfbi = dcufb->fsl_dcu_info[i]->par;
+ memcpy(mfbi, &mfb_template[i], sizeof(struct mfb_info));
+ mfbi->parent = dcufb;
+
+ ret = install_framebuffer(dcufb->fsl_dcu_info[i], option);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "could not register framebuffer %d\n", i);
+ goto failed_register_framebuffer;
+ }
+ }
+
+ if (option != NULL) {
+ ret = fsl_dcu_parse_options(dcufb, dcufb->fsl_dcu_info[0],
+ option);
+ if (ret < 0)
+ goto failed_alloc_framebuffer;
+ }
+
+ return 0;
+
+failed_register_framebuffer:
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ if (dcufb->fsl_dcu_info[i])
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+failed_alloc_framebuffer:
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+failed_getclock:
+failed_bypasstcon:
+ free_irq(dcufb->irq, dcufb);
+failed_ioremap:
+ return ret;
+}
+
+static int fsl_dcu_remove(struct platform_device *pdev)
+{
+ struct dcu_fb_data *dcufb = dev_get_drvdata(&pdev->dev);
+ int i;
+
+ pm_runtime_get_sync(dcufb->dev);
+
+ disable_controller(dcufb->fsl_dcu_info[0]);
+
+ clk_disable_unprepare(dcufb->clk);
+ free_irq(dcufb->irq, dcufb);
+
+ for (i = 0; i < ARRAY_SIZE(dcufb->fsl_dcu_info); i++) {
+ uninstall_framebuffer(dcufb->fsl_dcu_info[i]);
+ framebuffer_release(dcufb->fsl_dcu_info[i]);
+ }
+
+ pm_runtime_put_sync(dcufb->dev);
+ pm_runtime_disable(dcufb->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops fsl_dcu_pm_ops = {
+#ifdef CONFIG_PM_RUNTIME
+ SET_RUNTIME_PM_OPS(fsl_dcu_runtime_suspend,
+ fsl_dcu_runtime_resume, NULL)
+#endif
+ SET_SYSTEM_SLEEP_PM_OPS(fsl_dcu_suspend, fsl_dcu_resume)
+};
+
+static struct of_device_id fsl_dcu_dt_ids[] = {
+ {
+ .compatible = "fsl,vf610-dcu",
+ },
+ {}
+};
+
+static struct platform_driver fsl_dcu_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = fsl_dcu_dt_ids,
+ .pm = &fsl_dcu_pm_ops,
+ },
+ .probe = fsl_dcu_probe,
+ .remove = fsl_dcu_remove,
+};
+
+module_platform_driver(fsl_dcu_driver);
+
+MODULE_AUTHOR("Alison Wang");
+MODULE_DESCRIPTION("Freescale DCU framebuffer driver");
+MODULE_LICENSE("GPL v2");