summaryrefslogtreecommitdiff
path: root/drivers/video/fbdev/mxsfb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/fbdev/mxsfb.c')
-rw-r--r--drivers/video/fbdev/mxsfb.c1847
1 files changed, 1663 insertions, 184 deletions
diff --git a/drivers/video/fbdev/mxsfb.c b/drivers/video/fbdev/mxsfb.c
index 7846f0e8bbbb..5659ec2d18c4 100644
--- a/drivers/video/fbdev/mxsfb.c
+++ b/drivers/video/fbdev/mxsfb.c
@@ -4,7 +4,8 @@
* This code is based on:
* Author: Vitaly Wool <vital@embeddedalley.com>
*
- * Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2017 NXP
+ * Copyright 2008-2015 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
@@ -39,18 +40,30 @@
* the required value in the imx_fb_videomode structure.
*/
+#include <linux/busfreq-imx.h>
+#include <linux/console.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/pm_qos.h>
#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/interrupt.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
+#include <linux/pinctrl/consumer.h>
#include <linux/fb.h>
+#include <linux/mxcfb.h>
#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
#include <video/of_display_timing.h>
-#include <video/of_videomode.h>
#include <video/videomode.h>
+#include <linux/uaccess.h>
+
+#include "mxc/mxc_dispdrv.h"
#define REG_SET 4
#define REG_CLR 8
@@ -79,6 +92,9 @@
#define LCDC_V3_DATA 0x1b0
#define LCDC_V4_DEBUG0 0x1d0
#define LCDC_V3_DEBUG0 0x1f0
+#define LCDC_AS_CTRL 0x210
+#define LCDC_AS_BUF 0x220
+#define LCDC_AS_NEXT_BUF 0x230
#define CTRL_SFTRST (1 << 31)
#define CTRL_CLKGATE (1 << 30)
@@ -96,9 +112,30 @@
#define CTRL_DF24 (1 << 1)
#define CTRL_RUN (1 << 0)
-#define CTRL1_FIFO_CLEAR (1 << 21)
-#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16)
-#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf)
+#define CTRL1_RECOVERY_ON_UNDERFLOW (1 << 24)
+#define CTRL1_FIFO_CLEAR (1 << 21)
+#define CTRL1_SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16)
+#define CTRL1_GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf)
+#define CTRL1_OVERFLOW_IRQ_EN (1 << 15)
+#define CTRL1_UNDERFLOW_IRQ_EN (1 << 14)
+#define CTRL1_CUR_FRAME_DONE_IRQ_EN (1 << 13)
+#define CTRL1_VSYNC_EDGE_IRQ_EN (1 << 12)
+#define CTRL1_OVERFLOW_IRQ (1 << 11)
+#define CTRL1_UNDERFLOW_IRQ (1 << 10)
+#define CTRL1_CUR_FRAME_DONE_IRQ (1 << 9)
+#define CTRL1_VSYNC_EDGE_IRQ (1 << 8)
+#define CTRL1_IRQ_ENABLE_MASK (CTRL1_OVERFLOW_IRQ_EN | \
+ CTRL1_UNDERFLOW_IRQ_EN | \
+ CTRL1_CUR_FRAME_DONE_IRQ_EN | \
+ CTRL1_VSYNC_EDGE_IRQ_EN)
+#define CTRL1_IRQ_ENABLE_SHIFT 12
+#define CTRL1_IRQ_STATUS_MASK (CTRL1_OVERFLOW_IRQ | \
+ CTRL1_UNDERFLOW_IRQ | \
+ CTRL1_CUR_FRAME_DONE_IRQ | \
+ CTRL1_VSYNC_EDGE_IRQ)
+#define CTRL1_IRQ_STATUS_SHIFT 8
+
+#define CTRL2_OUTSTANDING_REQS__REQ_16 (4 << 21)
#define TRANSFER_COUNT_SET_VCOUNT(x) (((x) & 0xffff) << 16)
#define TRANSFER_COUNT_GET_VCOUNT(x) (((x) >> 16) & 0xffff)
@@ -149,12 +186,13 @@
#define STMLCDIF_18BIT 2 /** pixel data bus to the display is of 18 bit width */
#define STMLCDIF_24BIT 3 /** pixel data bus to the display is of 24 bit width */
-#define MXSFB_SYNC_DATA_ENABLE_HIGH_ACT (1 << 6)
-#define MXSFB_SYNC_DOTCLK_FALLING_ACT (1 << 7) /* negtive edge sampling */
+#define FB_SYNC_OE_LOW_ACT 0x80000000
+#define FB_SYNC_CLK_LAT_FALL 0x40000000
enum mxsfb_devtype {
MXSFB_V3,
MXSFB_V4,
+ MXSFB_V5,
};
/* CPU dependent register offsets */
@@ -166,26 +204,79 @@ struct mxsfb_devdata {
unsigned hs_wdth_mask;
unsigned hs_wdth_shift;
unsigned ipversion;
+ u32 flags;
+};
+
+struct mxsfb_layer;
+
+struct mxsfb_layer_ops {
+ void (*enable)(struct mxsfb_layer *ofb);
+ void (*disable)(struct mxsfb_layer *ofb);
+ void (*setup)(struct mxsfb_layer *ofb);
+};
+
+struct mxsfb_layer {
+ struct fb_info *ol_fb;
+ int id;
+ int registered;
+ atomic_t usage;
+ int blank_state;
+ uint32_t global_alpha;
+
+ struct mxsfb_layer_ops *ops;
+
+ struct device *dev;
+ void __iomem *video_mem;
+ unsigned long video_mem_phys;
+ size_t video_mem_size;
+
+ struct mxsfb_info *fbi;
};
+#define NAME_LEN 32
+
struct mxsfb_info {
- struct fb_info fb_info;
+ struct fb_info *fb_info;
struct platform_device *pdev;
- struct clk *clk;
+ struct clk *clk_pix;
struct clk *clk_axi;
struct clk *clk_disp_axi;
+ bool clk_pix_enabled;
+ bool clk_axi_enabled;
+ bool clk_disp_axi_enabled;
void __iomem *base; /* registers */
+ u32 sync; /* record display timing polarities */
unsigned allocated_size;
int enabled;
unsigned ld_intf_width;
unsigned dotclk_delay;
const struct mxsfb_devdata *devdata;
- u32 sync;
struct regulator *reg_lcd;
+ bool wait4vsync;
+ struct completion vsync_complete;
+ struct completion flip_complete;
+ int cur_blank;
+ int restore_blank;
+ char disp_dev[NAME_LEN];
+ struct mxc_dispdrv_handle *dispdrv;
+ int id;
+ struct fb_var_screeninfo var;
+ struct pm_qos_request pm_qos_req;
+
+ char disp_videomode[NAME_LEN];
+
+#ifdef CONFIG_FB_MXC_OVERLAY
+ struct mxsfb_layer overlay;
+#endif
};
#define mxsfb_is_v3(host) (host->devdata->ipversion == 3)
#define mxsfb_is_v4(host) (host->devdata->ipversion == 4)
+#define mxsfb_is_v5(host) (host->devdata->ipversion == 5)
+
+#define MXSFB_FLAG_NULL 0x0
+#define MXSFB_FLAG_BUSFREQ 0x1
+#define MXSFB_FLAG_PMQOS 0x2
static const struct mxsfb_devdata mxsfb_devdata[] = {
[MXSFB_V3] = {
@@ -196,6 +287,7 @@ static const struct mxsfb_devdata mxsfb_devdata[] = {
.hs_wdth_mask = 0xff,
.hs_wdth_shift = 24,
.ipversion = 3,
+ .flags = MXSFB_FLAG_NULL,
},
[MXSFB_V4] = {
.transfer_count = LCDC_V4_TRANSFER_COUNT,
@@ -205,10 +297,77 @@ static const struct mxsfb_devdata mxsfb_devdata[] = {
.hs_wdth_mask = 0x3fff,
.hs_wdth_shift = 18,
.ipversion = 4,
+ .flags = MXSFB_FLAG_BUSFREQ,
+ },
+ [MXSFB_V5] = {
+ .transfer_count = LCDC_V4_TRANSFER_COUNT,
+ .cur_buf = LCDC_V4_CUR_BUF,
+ .next_buf = LCDC_V4_NEXT_BUF,
+ .debug0 = LCDC_V4_DEBUG0,
+ .hs_wdth_mask = 0x3fff,
+ .hs_wdth_shift = 18,
+ .ipversion = 4,
+ .flags = MXSFB_FLAG_PMQOS,
},
};
-#define to_imxfb_host(x) (container_of(x, struct mxsfb_info, fb_info))
+static int mxsfb_map_videomem(struct fb_info *info);
+static int mxsfb_unmap_videomem(struct fb_info *info);
+static int mxsfb_set_par(struct fb_info *fb_info);
+
+/* enable lcdif pix clock */
+static inline void clk_enable_pix(struct mxsfb_info *host)
+{
+ if (!host->clk_pix_enabled && (host->clk_pix != NULL)) {
+ clk_prepare_enable(host->clk_pix);
+ host->clk_pix_enabled = true;
+ }
+}
+
+/* disable lcdif pix clock */
+static inline void clk_disable_pix(struct mxsfb_info *host)
+{
+ if (host->clk_pix_enabled && (host->clk_pix != NULL)) {
+ clk_disable_unprepare(host->clk_pix);
+ host->clk_pix_enabled = false;
+ }
+}
+
+/* enable lcdif axi clock */
+static inline void clk_enable_axi(struct mxsfb_info *host)
+{
+ if (!host->clk_axi_enabled && (host->clk_axi != NULL)) {
+ clk_prepare_enable(host->clk_axi);
+ host->clk_axi_enabled = true;
+ }
+}
+
+/* disable lcdif axi clock */
+static inline void clk_disable_axi(struct mxsfb_info *host)
+{
+ if (host->clk_axi_enabled && (host->clk_axi != NULL)) {
+ clk_disable_unprepare(host->clk_axi);
+ host->clk_axi_enabled = false;
+ }
+}
+
+/* enable DISP axi clock */
+static inline void clk_enable_disp_axi(struct mxsfb_info *host)
+{
+ if (!host->clk_disp_axi_enabled && (host->clk_disp_axi != NULL)) {
+ clk_prepare_enable(host->clk_disp_axi);
+ host->clk_disp_axi_enabled = true;
+ }
+}
+
+/* disable DISP axi clock */
+static inline void clk_disable_disp_axi(struct mxsfb_info *host)
+{
+ if (host->clk_disp_axi_enabled && (host->clk_disp_axi != NULL)) {
+ clk_disable_unprepare(host->clk_disp_axi);
+ host->clk_disp_axi_enabled = false;
+ }
+}
/* mask and shift depends on architecture */
static inline u32 set_hsync_pulse_width(struct mxsfb_info *host, unsigned val)
@@ -241,6 +400,105 @@ static const struct fb_bitfield def_rgb565[] = {
}
};
+#ifdef CONFIG_FB_MXC_OVERLAY
+static u32 saved_as_ctrl;
+static u32 saved_as_next_buf;
+
+static const struct fb_bitfield def_argb555[] = {
+ [RED] = {
+ .offset = 10,
+ .length = 5,
+ },
+ [GREEN] = {
+ .offset = 5,
+ .length = 5,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 5,
+ },
+ [TRANSP] = {
+ .offset = 15,
+ .length = 0,
+ }
+};
+
+static const struct fb_bitfield def_rgb555[] = {
+ [RED] = {
+ .offset = 10,
+ .length = 5,
+ },
+ [GREEN] = {
+ .offset = 5,
+ .length = 5,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 5,
+ },
+ [TRANSP] = {
+ .offset = 0,
+ .length = 0,
+ }
+};
+
+static const struct fb_bitfield def_argb444[] = {
+ [RED] = {
+ .offset = 8,
+ .length = 4,
+ },
+ [GREEN] = {
+ .offset = 4,
+ .length = 4,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 4,
+ },
+ [TRANSP] = {
+ .offset = 12,
+ .length = 4,
+ }
+};
+
+static const struct fb_bitfield def_rgb444[] = {
+ [RED] = {
+ .offset = 8,
+ .length = 4,
+ },
+ [GREEN] = {
+ .offset = 4,
+ .length = 4,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 4,
+ },
+ [TRANSP] = {
+ .offset = 0,
+ .length = 0,
+ }
+};
+#endif
+
+static const struct fb_bitfield def_rgb666[] = {
+ [RED] = {
+ .offset = 16,
+ .length = 6,
+ },
+ [GREEN] = {
+ .offset = 8,
+ .length = 6,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 6,
+ },
+ [TRANSP] = { /* no support for transparency */
+ .length = 0,
+ }
+};
+
static const struct fb_bitfield def_rgb888[] = {
[RED] = {
.offset = 16,
@@ -259,6 +517,38 @@ static const struct fb_bitfield def_rgb888[] = {
}
};
+static const struct fb_bitfield def_argb32[] = {
+ [RED] = {
+ .offset = 16,
+ .length = 8,
+ },
+ [GREEN] = {
+ .offset = 8,
+ .length = 8,
+ },
+ [BLUE] = {
+ .offset = 0,
+ .length = 8,
+ },
+ [TRANSP] = {
+ .offset = 24,
+ .length = 8,
+ }
+};
+
+#define bitfield_is_equal(f1, f2) (!memcmp(&(f1), &(f2), sizeof(f1)))
+
+static inline bool pixfmt_is_equal(struct fb_var_screeninfo *var,
+ const struct fb_bitfield *f)
+{
+ if (bitfield_is_equal(var->red, f[RED]) &&
+ bitfield_is_equal(var->green, f[GREEN]) &&
+ bitfield_is_equal(var->blue, f[BLUE]))
+ return true;
+
+ return false;
+}
+
static inline unsigned chan_to_field(unsigned chan, struct fb_bitfield *bf)
{
chan &= 0xffff;
@@ -266,10 +556,46 @@ static inline unsigned chan_to_field(unsigned chan, struct fb_bitfield *bf)
return chan << bf->offset;
}
+static irqreturn_t mxsfb_irq_handler(int irq, void *dev_id)
+{
+ struct mxsfb_info *host = dev_id;
+ u32 ctrl1, enable, status, acked_status;
+
+ ctrl1 = readl(host->base + LCDC_CTRL1);
+ enable = (ctrl1 & CTRL1_IRQ_ENABLE_MASK) >> CTRL1_IRQ_ENABLE_SHIFT;
+ status = (ctrl1 & CTRL1_IRQ_STATUS_MASK) >> CTRL1_IRQ_STATUS_SHIFT;
+ acked_status = (enable & status) << CTRL1_IRQ_STATUS_SHIFT;
+
+ if ((acked_status & CTRL1_VSYNC_EDGE_IRQ) && host->wait4vsync) {
+ writel(CTRL1_VSYNC_EDGE_IRQ,
+ host->base + LCDC_CTRL1 + REG_CLR);
+ writel(CTRL1_VSYNC_EDGE_IRQ_EN,
+ host->base + LCDC_CTRL1 + REG_CLR);
+ host->wait4vsync = 0;
+ complete(&host->vsync_complete);
+ }
+
+ if (acked_status & CTRL1_CUR_FRAME_DONE_IRQ) {
+ writel(CTRL1_CUR_FRAME_DONE_IRQ,
+ host->base + LCDC_CTRL1 + REG_CLR);
+ writel(CTRL1_CUR_FRAME_DONE_IRQ_EN,
+ host->base + LCDC_CTRL1 + REG_CLR);
+ complete(&host->flip_complete);
+ }
+
+ if (acked_status & CTRL1_UNDERFLOW_IRQ)
+ writel(CTRL1_UNDERFLOW_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
+
+ if (acked_status & CTRL1_OVERFLOW_IRQ)
+ writel(CTRL1_OVERFLOW_IRQ, host->base + LCDC_CTRL1 + REG_CLR);
+
+ return IRQ_HANDLED;
+}
+
static int mxsfb_check_var(struct fb_var_screeninfo *var,
struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = fb_info->par;
const struct fb_bitfield *rgb = NULL;
if (var->xres < MIN_XRES)
@@ -277,9 +603,18 @@ static int mxsfb_check_var(struct fb_var_screeninfo *var,
if (var->yres < MIN_YRES)
var->yres = MIN_YRES;
- var->xres_virtual = var->xres;
+ if (var->xres_virtual > var->xres) {
+ dev_dbg(fb_info->device, "stride not supported\n");
+ return -EINVAL;
+ }
- var->yres_virtual = var->yres;
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 16))
+ var->bits_per_pixel = 32;
switch (var->bits_per_pixel) {
case 16:
@@ -290,17 +625,35 @@ static int mxsfb_check_var(struct fb_var_screeninfo *var,
switch (host->ld_intf_width) {
case STMLCDIF_8BIT:
pr_debug("Unsupported LCD bus width mapping\n");
- break;
+ return -EINVAL;
case STMLCDIF_16BIT:
+ /* 24 bit to 18 bit mapping */
+ rgb = def_rgb666;
+ break;
case STMLCDIF_18BIT:
+ if (pixfmt_is_equal(var, def_rgb666))
+ /* 24 bit to 18 bit mapping */
+ rgb = def_rgb666;
+ else
+ rgb = def_rgb888;
+ break;
case STMLCDIF_24BIT:
/* real 24 bit */
rgb = def_rgb888;
break;
+ default:
+ /*
+ * 32-bit output is possible through I/O muxing, if this
+ * option is available on chip. Currently not
+ * implemented.
+ */
+ pr_debug("Currently unsupported output colour depth: %u\n",
+ host->ld_intf_width);
+ return -EINVAL;
}
break;
default:
- pr_err("Unsupported colour depth: %u\n", var->bits_per_pixel);
+ pr_debug("Unsupported colour depth: %u\n", var->bits_per_pixel);
return -EINVAL;
}
@@ -316,26 +669,27 @@ static int mxsfb_check_var(struct fb_var_screeninfo *var,
return 0;
}
-static inline void mxsfb_enable_axi_clk(struct mxsfb_info *host)
-{
- if (host->clk_axi)
- clk_prepare_enable(host->clk_axi);
-}
-
-static inline void mxsfb_disable_axi_clk(struct mxsfb_info *host)
-{
- if (host->clk_axi)
- clk_disable_unprepare(host->clk_axi);
-}
-
static void mxsfb_enable_controller(struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = fb_info->par;
u32 reg;
int ret;
+#ifdef CONFIG_FB_IMX64_DEBUG
+ static int pix_enable;
+#endif
dev_dbg(&host->pdev->dev, "%s\n", __func__);
+ if (host->dispdrv && host->dispdrv->drv->setup) {
+ ret = host->dispdrv->drv->setup(host->dispdrv, fb_info);
+ if (ret < 0) {
+ dev_err(&host->pdev->dev, "failed to setup"
+ "dispdrv:%s\n", host->dispdrv->drv->name);
+ return;
+ }
+ host->sync = fb_info->var.sync;
+ }
+
if (host->reg_lcd) {
ret = regulator_enable(host->reg_lcd);
if (ret) {
@@ -345,12 +699,44 @@ static void mxsfb_enable_controller(struct fb_info *fb_info)
}
}
- if (host->clk_disp_axi)
- clk_prepare_enable(host->clk_disp_axi);
- clk_prepare_enable(host->clk);
- clk_set_rate(host->clk, PICOS2KHZ(fb_info->var.pixclock) * 1000U);
+ if (host->dispdrv && host->dispdrv->drv->enable) {
+ ret = host->dispdrv->drv->enable(host->dispdrv, fb_info);
+ if (ret < 0)
+ dev_err(&host->pdev->dev, "failed to enable "
+ "dispdrv:%s\n", host->dispdrv->drv->name);
+ }
+
+#ifdef CONFIG_FB_IMX64_DEBUG
+ if (unlikely(!pix_enable)) {
+ /* the pixel clock should be disabled before
+ * trying to set its clock rate successfully.
+ */
+#else
+ clk_disable_pix(host);
+#endif
+ ret = clk_set_rate(host->clk_pix,
+ PICOS2KHZ(fb_info->var.pixclock) * 1000U);
+ if (ret) {
+ dev_err(&host->pdev->dev,
+ "lcd pixel rate set failed: %d\n", ret);
+
+ if (host->reg_lcd) {
+ ret = regulator_disable(host->reg_lcd);
+ if (ret)
+ dev_err(&host->pdev->dev,
+ "lcd regulator disable failed: %d\n",
+ ret);
+ }
+ return;
+ }
+ clk_enable_pix(host);
+#ifdef CONFIG_FB_IMX64_DEBUG
+ pix_enable++;
+ }
+#endif
- mxsfb_enable_axi_clk(host);
+ writel(CTRL2_OUTSTANDING_REQS__REQ_16,
+ host->base + LCDC_V4_CTRL2 + REG_SET);
/* if it was disabled, re-enable the mode again */
writel(CTRL_DOTCLK_MODE, host->base + LCDC_CTRL + REG_SET);
@@ -360,20 +746,30 @@ static void mxsfb_enable_controller(struct fb_info *fb_info)
reg |= VDCTRL4_SYNC_SIGNALS_ON;
writel(reg, host->base + LCDC_VDCTRL4);
+ writel(CTRL_MASTER, host->base + LCDC_CTRL + REG_SET);
writel(CTRL_RUN, host->base + LCDC_CTRL + REG_SET);
+ /* Recovery on underflow */
+ writel(CTRL1_RECOVERY_ON_UNDERFLOW, host->base + LCDC_CTRL1 + REG_SET);
+
host->enabled = 1;
+
}
static void mxsfb_disable_controller(struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = fb_info->par;
unsigned loop;
u32 reg;
int ret;
dev_dbg(&host->pdev->dev, "%s\n", __func__);
+ writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
+
+ if (host->dispdrv && host->dispdrv->drv->disable)
+ host->dispdrv->drv->disable(host->dispdrv, fb_info);
+
/*
* Even if we disable the controller here, it will still continue
* until its FIFOs are running out of data
@@ -388,15 +784,11 @@ static void mxsfb_disable_controller(struct fb_info *fb_info)
loop--;
}
+ writel(CTRL_MASTER, host->base + LCDC_CTRL + REG_CLR);
+
reg = readl(host->base + LCDC_VDCTRL4);
writel(reg & ~VDCTRL4_SYNC_SIGNALS_ON, host->base + LCDC_VDCTRL4);
- mxsfb_disable_axi_clk(host);
-
- clk_disable_unprepare(host->clk);
- if (host->clk_disp_axi)
- clk_disable_unprepare(host->clk_disp_axi);
-
host->enabled = 0;
if (host->reg_lcd) {
@@ -407,20 +799,67 @@ static void mxsfb_disable_controller(struct fb_info *fb_info)
}
}
+/**
+ This function compare the fb parameter see whether it was different
+ parameter for hardware, if it was different parameter, the hardware
+ will reinitialize. All will compared except x/y offset.
+ */
+static bool mxsfb_par_equal(struct fb_info *fbi, struct mxsfb_info *host)
+{
+ /* Here we set the xoffset, yoffset to zero, and compare two
+ * var see have different or not. */
+ struct fb_var_screeninfo oldvar = host->var;
+ struct fb_var_screeninfo newvar = fbi->var;
+
+ if ((fbi->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW &&
+ fbi->var.activate & FB_ACTIVATE_FORCE)
+ return false;
+
+ oldvar.xoffset = newvar.xoffset = 0;
+ oldvar.yoffset = newvar.yoffset = 0;
+
+ return memcmp(&oldvar, &newvar, sizeof(struct fb_var_screeninfo)) == 0;
+}
+
static int mxsfb_set_par(struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = fb_info->par;
u32 ctrl, vdctrl0, vdctrl4;
int line_size, fb_size;
int reenable = 0;
+ static u32 equal_bypass = 0;
+#ifdef CONFIG_FB_IMX64_DEBUG
+ static int time;
+
+ if (time == 1)
+ return 0;
+ time++;
+#endif
+
+ if (likely(equal_bypass > 1)) {
+ /* If parameter no change, don't reconfigure. */
+ if (mxsfb_par_equal(fb_info, host))
+ return 0;
+ } else
+ equal_bypass++;
+
+ dev_dbg(&host->pdev->dev, "%s\n", __func__);
+
+ /* If fb is in blank mode, it is
+ * unnecessary to really set par here.
+ * It can be delayed when unblank fb
+ */
+ if (host->cur_blank != FB_BLANK_UNBLANK)
+ return 0;
line_size = fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);
+ fb_info->fix.line_length = line_size;
fb_size = fb_info->var.yres_virtual * line_size;
- if (fb_size > fb_info->fix.smem_len)
+ if (fb_size > fb_info->fix.smem_len) {
+ dev_err(&host->pdev->dev, "exceeds the fb buffer size limit!\n");
return -ENOMEM;
-
- fb_info->fix.line_length = line_size;
+ }
/*
* It seems, you can't re-program the controller if it is still running.
@@ -432,8 +871,6 @@ static int mxsfb_set_par(struct fb_info *fb_info)
mxsfb_disable_controller(fb_info);
}
- mxsfb_enable_axi_clk(host);
-
/* clear the FIFOs */
writel(CTRL1_FIFO_CLEAR, host->base + LCDC_CTRL1 + REG_SET);
@@ -451,12 +888,22 @@ static int mxsfb_set_par(struct fb_info *fb_info)
ctrl |= CTRL_SET_WORD_LENGTH(3);
switch (host->ld_intf_width) {
case STMLCDIF_8BIT:
- mxsfb_disable_axi_clk(host);
- dev_err(&host->pdev->dev,
+ dev_dbg(&host->pdev->dev,
"Unsupported LCD bus width mapping\n");
return -EINVAL;
case STMLCDIF_16BIT:
+ /* 24 bit to 18 bit mapping */
+ ctrl |= CTRL_DF24; /* ignore the upper 2 bits in
+ * each colour component
+ */
+ break;
case STMLCDIF_18BIT:
+ if (pixfmt_is_equal(&fb_info->var, def_rgb666))
+ /* 24 bit to 18 bit mapping */
+ ctrl |= CTRL_DF24; /* ignore the upper 2 bits in
+ * each colour component
+ */
+ break;
case STMLCDIF_24BIT:
/* real 24 bit */
break;
@@ -465,8 +912,7 @@ static int mxsfb_set_par(struct fb_info *fb_info)
writel(CTRL1_SET_BYTE_PACKAGING(0x7), host->base + LCDC_CTRL1);
break;
default:
- mxsfb_disable_axi_clk(host);
- dev_err(&host->pdev->dev, "Unhandled color depth of %u\n",
+ dev_dbg(&host->pdev->dev, "Unhandled color depth of %u\n",
fb_info->var.bits_per_pixel);
return -EINVAL;
}
@@ -481,13 +927,16 @@ static int mxsfb_set_par(struct fb_info *fb_info)
VDCTRL0_VSYNC_PERIOD_UNIT |
VDCTRL0_VSYNC_PULSE_WIDTH_UNIT |
VDCTRL0_SET_VSYNC_PULSE_WIDTH(fb_info->var.vsync_len);
- if (fb_info->var.sync & FB_SYNC_HOR_HIGH_ACT)
+ /* use the saved sync to avoid wrong sync information */
+ if (host->sync & FB_SYNC_HOR_HIGH_ACT)
vdctrl0 |= VDCTRL0_HSYNC_ACT_HIGH;
- if (fb_info->var.sync & FB_SYNC_VERT_HIGH_ACT)
+ if (host->sync & FB_SYNC_VERT_HIGH_ACT)
vdctrl0 |= VDCTRL0_VSYNC_ACT_HIGH;
- if (host->sync & MXSFB_SYNC_DATA_ENABLE_HIGH_ACT)
+#ifndef CONFIG_FB_IMX64_DEBUG
+ if (!(host->sync & FB_SYNC_OE_LOW_ACT))
vdctrl0 |= VDCTRL0_ENABLE_ACT_HIGH;
- if (host->sync & MXSFB_SYNC_DOTCLK_FALLING_ACT)
+#endif
+ if (host->sync & FB_SYNC_CLK_LAT_FALL)
vdctrl0 |= VDCTRL0_DOTCLK_ACT_FALLING;
writel(vdctrl0, host->base + LCDC_VDCTRL0);
@@ -519,11 +968,15 @@ static int mxsfb_set_par(struct fb_info *fb_info)
fb_info->fix.line_length * fb_info->var.yoffset,
host->base + host->devdata->next_buf);
- mxsfb_disable_axi_clk(host);
-
if (reenable)
mxsfb_enable_controller(fb_info);
+ /* Clear activate as not Reconfiguring framebuffer again */
+ if ((fb_info->var.activate & FB_ACTIVATE_FORCE) &&
+ (fb_info->var.activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW)
+ fb_info->var.activate = FB_ACTIVATE_NOW;
+
+ host->var = fb_info->var;
return 0;
}
@@ -567,22 +1020,90 @@ static int mxsfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
return ret;
}
+static int mxsfb_wait_for_vsync(struct fb_info *fb_info)
+{
+ struct mxsfb_info *host = fb_info->par;
+ int ret = 0;
+
+ if (host->cur_blank != FB_BLANK_UNBLANK) {
+ dev_err(fb_info->device, "can't wait for VSYNC when fb "
+ "is blank\n");
+ return -EINVAL;
+ }
+
+ init_completion(&host->vsync_complete);
+
+ host->wait4vsync = 1;
+ writel(CTRL1_VSYNC_EDGE_IRQ_EN,
+ host->base + LCDC_CTRL1 + REG_SET);
+ ret = wait_for_completion_interruptible_timeout(
+ &host->vsync_complete, 1 * HZ);
+ if (ret == 0) {
+ dev_err(fb_info->device,
+ "mxs wait for vsync timeout\n");
+ host->wait4vsync = 0;
+ ret = -ETIME;
+ } else if (ret > 0) {
+ ret = 0;
+ }
+ return ret;
+}
+
+static int mxsfb_ioctl(struct fb_info *fb_info, unsigned int cmd,
+ unsigned long arg)
+{
+ int ret = -EINVAL;
+
+ switch (cmd) {
+ case MXCFB_WAIT_FOR_VSYNC:
+ ret = mxsfb_wait_for_vsync(fb_info);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
static int mxsfb_blank(int blank, struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = fb_info->par;
+
+#ifdef CONFIG_FB_IMX64_DEBUG
+ return 0;
+#endif
+
+ host->cur_blank = blank;
switch (blank) {
case FB_BLANK_POWERDOWN:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
- if (host->enabled)
+ if (host->enabled) {
mxsfb_disable_controller(fb_info);
+ pm_runtime_put_sync_suspend(&host->pdev->dev);
+ }
+
+ clk_disable_disp_axi(host);
+ clk_disable_axi(host);
+ clk_disable_pix(host);
break;
case FB_BLANK_UNBLANK:
- if (!host->enabled)
+ fb_info->var.activate = (fb_info->var.activate & ~FB_ACTIVATE_MASK) |
+ FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
+
+ clk_enable_pix(host);
+ clk_enable_axi(host);
+ clk_enable_disp_axi(host);
+
+ if (!host->enabled) {
+ pm_runtime_get_sync(&host->pdev->dev);
+
+ writel(0, host->base + LCDC_CTRL);
+ mxsfb_set_par(host->fb_info);
mxsfb_enable_controller(fb_info);
+ }
break;
}
return 0;
@@ -591,21 +1112,71 @@ static int mxsfb_blank(int blank, struct fb_info *fb_info)
static int mxsfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *fb_info)
{
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ int ret = 0;
+ struct mxsfb_info *host = fb_info->par;
unsigned offset;
- if (var->xoffset != 0)
+ if (host->cur_blank != FB_BLANK_UNBLANK) {
+ dev_dbg(fb_info->device, "can't do pan display when fb "
+ "is blank\n");
return -EINVAL;
+ }
- offset = fb_info->fix.line_length * var->yoffset;
+ if (var->xoffset > 0) {
+ dev_dbg(fb_info->device, "x panning not supported\n");
+ return -EINVAL;
+ }
- mxsfb_enable_axi_clk(host);
+ if ((var->yoffset + var->yres > var->yres_virtual)) {
+ dev_err(fb_info->device, "y panning exceeds\n");
+ return -EINVAL;
+ }
+
+ init_completion(&host->flip_complete);
+
+ offset = fb_info->fix.line_length * var->yoffset;
/* update on next VSYNC */
writel(fb_info->fix.smem_start + offset,
host->base + host->devdata->next_buf);
- mxsfb_disable_axi_clk(host);
+ writel(CTRL1_CUR_FRAME_DONE_IRQ_EN,
+ host->base + LCDC_CTRL1 + REG_SET);
+
+ ret = wait_for_completion_timeout(&host->flip_complete, HZ / 2);
+ if (!ret) {
+ dev_err(fb_info->device,
+ "mxs wait for pan flip timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int mxsfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
+{
+ u32 len;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ if (offset < info->fix.smem_len) {
+ /* mapping framebuffer memory */
+ len = info->fix.smem_len - offset;
+ vma->vm_pgoff = (info->fix.smem_start + offset) >> PAGE_SHIFT;
+ } else
+ 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);
+
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ dev_dbg(info->device, "mmap remap_pfn_range failed\n");
+ return -ENOBUFS;
+ }
return 0;
}
@@ -615,31 +1186,43 @@ static struct fb_ops mxsfb_ops = {
.fb_check_var = mxsfb_check_var,
.fb_set_par = mxsfb_set_par,
.fb_setcolreg = mxsfb_setcolreg,
+ .fb_ioctl = mxsfb_ioctl,
.fb_blank = mxsfb_blank,
.fb_pan_display = mxsfb_pan_display,
+ .fb_mmap = mxsfb_mmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
-static int mxsfb_restore_mode(struct mxsfb_info *host,
- struct fb_videomode *vmode)
+static int mxsfb_restore_mode(struct mxsfb_info *host)
{
- struct fb_info *fb_info = &host->fb_info;
+ struct fb_info *fb_info = host->fb_info;
unsigned line_count;
unsigned period;
unsigned long pa, fbsize;
- int bits_per_pixel, ofs, ret = 0;
+ int bits_per_pixel, ofs;
u32 transfer_count, vdctrl0, vdctrl2, vdctrl3, vdctrl4, ctrl;
+ struct fb_videomode vmode;
- mxsfb_enable_axi_clk(host);
+ clk_enable_axi(host);
+ clk_enable_disp_axi(host);
+
+#ifndef CONFIG_FB_IMX64_DEBUG
+ /* Enable pixel clock earlier since in 7D
+ * the lcdif registers should be accessed
+ * when the pixel clock is enabled, otherwise
+ * the bus will be hang.
+ */
+ clk_enable_pix(host);
+#endif
/* Only restore the mode when the controller is running */
ctrl = readl(host->base + LCDC_CTRL);
- if (!(ctrl & CTRL_RUN)) {
- ret = -EINVAL;
- goto err;
- }
+ if (!(ctrl & CTRL_RUN))
+ return -EINVAL;
+
+ memset(&vmode, 0, sizeof(vmode));
vdctrl0 = readl(host->base + LCDC_VDCTRL0);
vdctrl2 = readl(host->base + LCDC_VDCTRL2);
@@ -648,8 +1231,8 @@ static int mxsfb_restore_mode(struct mxsfb_info *host,
transfer_count = readl(host->base + host->devdata->transfer_count);
- vmode->xres = TRANSFER_COUNT_GET_HCOUNT(transfer_count);
- vmode->yres = TRANSFER_COUNT_GET_VCOUNT(transfer_count);
+ vmode.xres = TRANSFER_COUNT_GET_HCOUNT(transfer_count);
+ vmode.yres = TRANSFER_COUNT_GET_VCOUNT(transfer_count);
switch (CTRL_GET_WORD_LENGTH(ctrl)) {
case 0:
@@ -660,53 +1243,49 @@ static int mxsfb_restore_mode(struct mxsfb_info *host,
break;
case 1:
default:
- ret = -EINVAL;
- goto err;
+ return -EINVAL;
}
fb_info->var.bits_per_pixel = bits_per_pixel;
- vmode->pixclock = KHZ2PICOS(clk_get_rate(host->clk) / 1000U);
- vmode->hsync_len = get_hsync_pulse_width(host, vdctrl2);
- vmode->left_margin = GET_HOR_WAIT_CNT(vdctrl3) - vmode->hsync_len;
- vmode->right_margin = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2) -
- vmode->hsync_len - vmode->left_margin - vmode->xres;
- vmode->vsync_len = VDCTRL0_GET_VSYNC_PULSE_WIDTH(vdctrl0);
+ vmode.pixclock = KHZ2PICOS(clk_get_rate(host->clk_pix) / 1000U);
+ vmode.hsync_len = get_hsync_pulse_width(host, vdctrl2);
+ vmode.left_margin = GET_HOR_WAIT_CNT(vdctrl3) - vmode.hsync_len;
+ vmode.right_margin = VDCTRL2_GET_HSYNC_PERIOD(vdctrl2) - vmode.hsync_len -
+ vmode.left_margin - vmode.xres;
+ vmode.vsync_len = VDCTRL0_GET_VSYNC_PULSE_WIDTH(vdctrl0);
period = readl(host->base + LCDC_VDCTRL1);
- vmode->upper_margin = GET_VERT_WAIT_CNT(vdctrl3) - vmode->vsync_len;
- vmode->lower_margin = period - vmode->vsync_len -
- vmode->upper_margin - vmode->yres;
+ vmode.upper_margin = GET_VERT_WAIT_CNT(vdctrl3) - vmode.vsync_len;
+ vmode.lower_margin = period - vmode.vsync_len - vmode.upper_margin - vmode.yres;
- vmode->vmode = FB_VMODE_NONINTERLACED;
+ vmode.vmode = FB_VMODE_NONINTERLACED;
- vmode->sync = 0;
+ vmode.sync = 0;
if (vdctrl0 & VDCTRL0_HSYNC_ACT_HIGH)
- vmode->sync |= FB_SYNC_HOR_HIGH_ACT;
+ vmode.sync |= FB_SYNC_HOR_HIGH_ACT;
if (vdctrl0 & VDCTRL0_VSYNC_ACT_HIGH)
- vmode->sync |= FB_SYNC_VERT_HIGH_ACT;
+ vmode.sync |= FB_SYNC_VERT_HIGH_ACT;
pr_debug("Reconstructed video mode:\n");
pr_debug("%dx%d, hsync: %u left: %u, right: %u, vsync: %u, upper: %u, lower: %u\n",
- vmode->xres, vmode->yres, vmode->hsync_len, vmode->left_margin,
- vmode->right_margin, vmode->vsync_len, vmode->upper_margin,
- vmode->lower_margin);
- pr_debug("pixclk: %ldkHz\n", PICOS2KHZ(vmode->pixclock));
+ vmode.xres, vmode.yres,
+ vmode.hsync_len, vmode.left_margin, vmode.right_margin,
+ vmode.vsync_len, vmode.upper_margin, vmode.lower_margin);
+ pr_debug("pixclk: %ldkHz\n", PICOS2KHZ(vmode.pixclock));
+
+ fb_add_videomode(&vmode, &fb_info->modelist);
host->ld_intf_width = CTRL_GET_BUS_WIDTH(ctrl);
host->dotclk_delay = VDCTRL4_GET_DOTCLK_DLY(vdctrl4);
- fb_info->fix.line_length = vmode->xres * (bits_per_pixel >> 3);
+ fb_info->fix.line_length = vmode.xres * (bits_per_pixel >> 3);
pa = readl(host->base + host->devdata->cur_buf);
- fbsize = fb_info->fix.line_length * vmode->yres;
- if (pa < fb_info->fix.smem_start) {
- ret = -EINVAL;
- goto err;
- }
- if (pa + fbsize > fb_info->fix.smem_start + fb_info->fix.smem_len) {
- ret = -EINVAL;
- goto err;
- }
+ fbsize = fb_info->fix.line_length * vmode.yres;
+ if (pa < fb_info->fix.smem_start)
+ return -EINVAL;
+ if (pa + fbsize > fb_info->fix.smem_start + fb_info->fix.smem_len)
+ return -EINVAL;
ofs = pa - fb_info->fix.smem_start;
if (ofs) {
memmove(fb_info->screen_base, fb_info->screen_base + ofs, fbsize);
@@ -715,28 +1294,28 @@ static int mxsfb_restore_mode(struct mxsfb_info *host,
line_count = fb_info->fix.smem_len / fb_info->fix.line_length;
fb_info->fix.ypanstep = 1;
+ fb_info->fix.ywrapstep = 1;
- clk_prepare_enable(host->clk);
host->enabled = 1;
-err:
- if (ret)
- mxsfb_disable_axi_clk(host);
-
- return ret;
+ return 0;
}
-static int mxsfb_init_fbinfo_dt(struct mxsfb_info *host,
- struct fb_videomode *vmode)
+static int mxsfb_init_fbinfo_dt(struct mxsfb_info *host)
{
- struct fb_info *fb_info = &host->fb_info;
+ struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
struct device *dev = &host->pdev->dev;
struct device_node *np = host->pdev->dev.of_node;
struct device_node *display_np;
- struct videomode vm;
+ struct device_node *timings_np;
+ struct display_timings *timings = NULL;
+ const char *disp_dev, *disp_videomode;
u32 width;
- int ret;
+ int i;
+ int ret = 0;
+
+ host->id = of_alias_get_id(np, "lcdif");
display_np = of_parse_phandle(np, "display", 0);
if (!display_np) {
@@ -776,77 +1355,216 @@ static int mxsfb_init_fbinfo_dt(struct mxsfb_info *host,
goto put_display_node;
}
- ret = of_get_videomode(display_np, &vm, OF_USE_NATIVE_MODE);
- if (ret) {
- dev_err(dev, "failed to get videomode from DT\n");
+ ret = of_property_read_string(np, "disp-dev", &disp_dev);
+ if (!ret) {
+ memcpy(host->disp_dev, disp_dev, strlen(disp_dev));
+
+ if (!of_property_read_string(np, "disp-videomode",
+ &disp_videomode)) {
+ memcpy(host->disp_videomode, disp_videomode,
+ strlen(disp_videomode));
+ }
+
+ /* Timing is from encoder driver */
+ goto put_display_node;
+ }
+
+ timings = of_get_display_timings(display_np);
+ if (!timings) {
+ dev_err(dev, "failed to get display timings\n");
+ ret = -ENOENT;
goto put_display_node;
}
- ret = fb_videomode_from_videomode(&vm, vmode);
- if (ret < 0)
+ timings_np = of_find_node_by_name(display_np,
+ "display-timings");
+ if (!timings_np) {
+ dev_err(dev, "failed to find display-timings node\n");
+ ret = -ENOENT;
goto put_display_node;
+ }
- if (vm.flags & DISPLAY_FLAGS_DE_HIGH)
- host->sync |= MXSFB_SYNC_DATA_ENABLE_HIGH_ACT;
- if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
- host->sync |= MXSFB_SYNC_DOTCLK_FALLING_ACT;
+ for (i = 0; i < of_get_child_count(timings_np); i++) {
+ struct videomode vm;
+ struct fb_videomode fb_vm;
+
+ ret = videomode_from_timings(timings, &vm, i);
+ if (ret < 0)
+ goto put_timings_node;
+ ret = fb_videomode_from_videomode(&vm, &fb_vm);
+ if (ret < 0)
+ goto put_timings_node;
+
+ if (!(vm.flags & DISPLAY_FLAGS_DE_HIGH))
+ fb_vm.sync |= FB_SYNC_OE_LOW_ACT;
+ if (vm.flags & DISPLAY_FLAGS_PIXDATA_NEGEDGE)
+ fb_vm.sync |= FB_SYNC_CLK_LAT_FALL;
+ fb_add_videomode(&fb_vm, &fb_info->modelist);
+ }
+put_timings_node:
+ of_node_put(timings_np);
put_display_node:
+ if (timings)
+ kfree(timings);
of_node_put(display_np);
return ret;
}
-static int mxsfb_init_fbinfo(struct mxsfb_info *host,
- struct fb_videomode *vmode)
+static int mxsfb_init_fbinfo(struct mxsfb_info *host)
{
int ret;
- struct device *dev = &host->pdev->dev;
- struct fb_info *fb_info = &host->fb_info;
+ struct fb_info *fb_info = host->fb_info;
struct fb_var_screeninfo *var = &fb_info->var;
- dma_addr_t fb_phys;
- void *fb_virt;
- unsigned fb_size;
+ struct fb_modelist *modelist;
fb_info->fbops = &mxsfb_ops;
fb_info->flags = FBINFO_FLAG_DEFAULT | FBINFO_READS_FAST;
- strlcpy(fb_info->fix.id, "mxs", sizeof(fb_info->fix.id));
fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
fb_info->fix.ypanstep = 1;
+ fb_info->fix.ywrapstep = 1;
fb_info->fix.visual = FB_VISUAL_TRUECOLOR,
fb_info->fix.accel = FB_ACCEL_NONE;
- ret = mxsfb_init_fbinfo_dt(host, vmode);
+ ret = mxsfb_init_fbinfo_dt(host);
if (ret)
return ret;
+ if (host->id < 0)
+ sprintf(fb_info->fix.id, "mxs-lcdif");
+ else
+ sprintf(fb_info->fix.id, "mxs-lcdif%d", host->id);
+
+ if (!list_empty(&fb_info->modelist)) {
+ /* first video mode in the modelist as default video mode */
+ modelist = list_first_entry(&fb_info->modelist,
+ struct fb_modelist, list);
+ fb_videomode_to_var(var, &modelist->mode);
+ }
+ /* save the sync value getting from dtb */
+ host->sync = fb_info->var.sync;
+
var->nonstd = 0;
var->activate = FB_ACTIVATE_NOW;
var->accel_flags = 0;
var->vmode = FB_VMODE_NONINTERLACED;
+ /* init the color fields */
+ mxsfb_check_var(var, fb_info);
+
+ fb_info->fix.line_length =
+ fb_info->var.xres * (fb_info->var.bits_per_pixel >> 3);
+ fb_info->fix.smem_len = SZ_32M;
+
/* Memory allocation for framebuffer */
- fb_size = SZ_2M;
- fb_virt = dma_alloc_wc(dev, PAGE_ALIGN(fb_size), &fb_phys, GFP_KERNEL);
- if (!fb_virt)
+ if (mxsfb_map_videomem(fb_info) < 0)
return -ENOMEM;
- fb_info->fix.smem_start = fb_phys;
- fb_info->screen_base = fb_virt;
- fb_info->screen_size = fb_info->fix.smem_len = fb_size;
+ if (mxsfb_restore_mode(host))
+ memset((char *)fb_info->screen_base, 0, fb_info->fix.smem_len);
- if (mxsfb_restore_mode(host, vmode))
- memset(fb_virt, 0, fb_size);
+ return 0;
+}
+
+static int mxsfb_dispdrv_init(struct platform_device *pdev,
+ struct fb_info *fbi)
+{
+ struct mxsfb_info *host = fbi->par;
+ struct mxc_dispdrv_setting setting;
+ struct device *dev = &pdev->dev;
+ char disp_dev[32];
+
+ if (!strlen(host->disp_dev))
+ return 0;
+
+ memset(&setting, 0x0, sizeof(setting));
+ setting.fbi = fbi;
+ memcpy(disp_dev, host->disp_dev, strlen(host->disp_dev));
+ disp_dev[strlen(host->disp_dev)] = '\0';
+
+ /* Use videomode name from dtb, if any given */
+ if (host->disp_videomode) {
+ setting.dft_mode_str = kmalloc(NAME_LEN, GFP_KERNEL);
+ if (setting.dft_mode_str) {
+ memset(setting.dft_mode_str, 0x0, NAME_LEN);
+ memcpy(setting.dft_mode_str, host->disp_videomode,
+ strlen(host->disp_videomode));
+ }
+ }
+
+ host->dispdrv = mxc_dispdrv_gethandle(disp_dev, &setting);
+
+ kfree(setting.dft_mode_str);
+
+ if (IS_ERR(host->dispdrv))
+ return -EPROBE_DEFER;
+ else
+ dev_info(dev, "registered mxc display driver %s\n",
+ disp_dev);
return 0;
}
static void mxsfb_free_videomem(struct mxsfb_info *host)
{
- struct device *dev = &host->pdev->dev;
- struct fb_info *fb_info = &host->fb_info;
+ struct fb_info *fb_info = host->fb_info;
- dma_free_wc(dev, fb_info->screen_size, fb_info->screen_base,
- fb_info->fix.smem_start);
+ mxsfb_unmap_videomem(fb_info);
+}
+
+/*!
+ * 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 mxsfb_map_videomem(struct fb_info *fbi)
+{
+ 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;
+
+ 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 mxsfb_unmap_videomem(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;
}
static const struct platform_device_id mxsfb_devtype[] = {
@@ -857,6 +1575,9 @@ static const struct platform_device_id mxsfb_devtype[] = {
.name = "imx28-fb",
.driver_data = MXSFB_V4,
}, {
+ .name = "imx7ulp-fb",
+ .driver_data = MXSFB_V5,
+ }, {
/* sentinel */
}
};
@@ -865,10 +1586,602 @@ MODULE_DEVICE_TABLE(platform, mxsfb_devtype);
static const struct of_device_id mxsfb_dt_ids[] = {
{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], },
{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], },
+ { .compatible = "fsl,imx7ulp-lcdif", .data = &mxsfb_devtype[2], },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxsfb_dt_ids);
+#ifdef CONFIG_FB_MXC_OVERLAY
+static int overlay_fmt_support(uint32_t fmt)
+{
+ switch (fmt) {
+ case V4L2_PIX_FMT_ARGB32:
+ case V4L2_PIX_FMT_XRGB32:
+ return 32;
+ case V4L2_PIX_FMT_ARGB555:
+ case V4L2_PIX_FMT_ARGB444:
+ case V4L2_PIX_FMT_XRGB555:
+ case V4L2_PIX_FMT_RGB444:
+ case V4L2_PIX_FMT_RGB565:
+ return 16;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* alpha mode */
+#define ALPHA_CTRL_EMBEDDED 0x0
+#define ALPHA_CTRL_OVERRIDE 0x1
+#define ALPHA_CTRL_MULTIPLY 0x2
+#define ALPHA_CTRL_ROPS 0x3
+
+static void overlayfb_enable(struct mxsfb_layer *ofb)
+{
+ struct mxsfb_info *fbi = ofb->fbi;
+
+ if (!lock_fb_info(fbi->fb_info))
+ return;
+
+ if (fbi->cur_blank == FB_BLANK_UNBLANK) {
+ mxsfb_disable_controller(fbi->fb_info);
+ writel(CTRL1_FIFO_CLEAR, fbi->base + LCDC_CTRL1 + REG_SET);
+ }
+
+ writel(0x1, fbi->base + LCDC_AS_CTRL + REG_SET);
+
+ if (fbi->cur_blank == FB_BLANK_UNBLANK) {
+ writel(CTRL1_FIFO_CLEAR, fbi->base + LCDC_CTRL1 + REG_CLR);
+ mxsfb_enable_controller(fbi->fb_info);
+ }
+ unlock_fb_info(fbi->fb_info);
+}
+
+static void overlayfb_disable(struct mxsfb_layer *ofb)
+{
+ struct mxsfb_info *fbi = ofb->fbi;
+
+ writel(0x1, fbi->base + LCDC_AS_CTRL + REG_CLR);
+}
+
+static void overlayfb_setup(struct mxsfb_layer *ofb)
+{
+ uint32_t as_next_buf, as_ctrl = 0;
+ uint8_t format, alpha_ctrl, global_alpha_en = 0;
+ struct mxsfb_info *fbi = ofb->fbi;
+ struct fb_var_screeninfo *var = &ofb->ol_fb->var;
+
+ /* set fb1 framebuffer address */
+ as_next_buf = ofb->video_mem_phys;
+ writel(as_next_buf, fbi->base + LCDC_AS_NEXT_BUF);
+
+ /* clear the LCDC_AS_CTRL */
+ writel(0x0, fbi->base + LCDC_AS_CTRL);
+
+ switch (var->grayscale) {
+ case 0: /* color */
+ switch (var->bits_per_pixel) {
+ case 16: /* RGB565 */
+ format = 0xE;
+ global_alpha_en = 1;
+ break;
+ case 32: /* ARGB8888 */
+ format = 0x0;
+ global_alpha_en = 1;
+ break;
+ default:
+ return;
+ }
+ break;
+ case 1: /* grayscale */
+ return;
+ default:
+ switch (var->grayscale) {
+ case V4L2_PIX_FMT_ARGB32:
+ format = 0x0;
+ break;
+ case V4L2_PIX_FMT_XRGB32:
+ format = 0x4;
+ global_alpha_en = 1;
+ break;
+ case V4L2_PIX_FMT_ARGB555:
+ format = 0x8;
+ break;
+ case V4L2_PIX_FMT_ARGB444:
+ format = 0x9;
+ break;
+ case V4L2_PIX_FMT_RGB555:
+ format = 0xC;
+ global_alpha_en = 1;
+ break;
+ case V4L2_PIX_FMT_RGB444:
+ format = 0xD;
+ global_alpha_en = 1;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ format = 0xE;
+ global_alpha_en = 1;
+ break;
+ default:
+ return;
+ }
+ break;
+ }
+ as_ctrl |= ((format & 0xf) << 4);
+
+ alpha_ctrl = global_alpha_en ? ALPHA_CTRL_OVERRIDE :
+ ALPHA_CTRL_EMBEDDED;
+ as_ctrl |= ((alpha_ctrl & 0x3) << 1);
+ if (global_alpha_en)
+ as_ctrl |= ((ofb->global_alpha & 0xff) << 8);
+
+ writel(as_ctrl, fbi->base + LCDC_AS_CTRL);
+}
+
+static struct mxsfb_layer_ops ofb_ops = {
+ .enable = overlayfb_enable,
+ .disable = overlayfb_disable,
+ .setup = overlayfb_setup,
+};
+
+static int overlayfb_open(struct fb_info *info, int user)
+{
+ struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par;
+ struct mxsfb_info *fbi = ofb->fbi;
+
+ if (atomic_inc_return(&ofb->usage) == 1) {
+ ofb->ol_fb->var.xres = fbi->fb_info->var.xres;
+ ofb->ol_fb->var.yres = fbi->fb_info->var.yres;
+ ofb->ol_fb->var.xres_virtual = fbi->fb_info->var.xres_virtual;
+ ofb->ol_fb->var.yres_virtual = fbi->fb_info->var.yres;
+ ofb->ol_fb->var.bits_per_pixel = fbi->fb_info->var.bits_per_pixel;
+ ofb->ol_fb->var.vmode = FB_VMODE_NONINTERLACED;
+ }
+
+ return 0;
+}
+
+static int overlayfb_release(struct fb_info *info, int user)
+{
+ struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par;
+
+ BUG_ON(!atomic_read(&ofb->usage));
+
+ if (atomic_dec_return(&ofb->usage) == 0) {
+ if (ofb->blank_state == FB_BLANK_UNBLANK)
+ ofb->ops->disable(ofb);
+
+ ofb->blank_state = -1;
+ }
+
+ return 0;
+}
+
+static void fill_fmt_bitfields(struct fb_var_screeninfo *var,
+ const struct fb_bitfield *color)
+{
+ var->red = color[RED];
+ var->green = color[GREEN];
+ var->blue = color[BLUE];
+ var->transp = color[TRANSP];
+}
+
+static int overlayfb_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ int bpp;
+ struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par;
+ struct mxsfb_info *fbi = ofb->fbi;
+ const struct fb_bitfield *rgb = NULL;
+
+ /* lcdif doesn't support different bpp of AS and PS */
+ if (var->bits_per_pixel != fbi->fb_info->var.bits_per_pixel)
+ return -EINVAL;
+
+ /* overlay width & should be equal to fb0 */
+ if ((var->xres != fbi->fb_info->var.xres) ||
+ (var->yres != fbi->fb_info->var.yres))
+ return -EINVAL;
+
+ if ((var->xres > 2048) || (var->yres > 2048))
+ return -EINVAL;
+
+ if (var->xres_virtual > var->xres)
+ return -EINVAL;
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ switch (var->grayscale) {
+ case 0: /* color */
+ switch (var->bits_per_pixel) {
+ case 16:/* RGB565 */
+ rgb = def_rgb565;
+ break;
+ case 32:/* ARGB8888 */
+ rgb = def_argb32;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case 1: /* grayscale */
+ return -EINVAL;
+ default: /* fourcc */
+ if ((bpp = overlay_fmt_support(var->grayscale)) < 0) {
+ dev_err(info->dev, "unsupport pixel format for overlay\n");
+ return -EINVAL;
+ }
+
+ var->bits_per_pixel = bpp;
+ if (var->bits_per_pixel < 16)
+ return -EINVAL;
+
+ switch (var->grayscale) {
+ case V4L2_PIX_FMT_ARGB32:
+ rgb = def_argb32;
+ break;
+ case V4L2_PIX_FMT_XRGB32:
+ rgb = def_rgb888;
+ break;
+ case V4L2_PIX_FMT_ARGB555:
+ rgb = def_argb555;
+ break;
+ case V4L2_PIX_FMT_ARGB444:
+ rgb = def_argb444;
+ break;
+ case V4L2_PIX_FMT_RGB555:
+ rgb = def_rgb555;
+ break;
+ case V4L2_PIX_FMT_RGB444:
+ rgb = def_rgb444;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ rgb = def_rgb565;
+ break;
+ default:
+ /*
+ * This should never be reached since the verification
+ * is done in overlay_fmt_support(), but handle this in
+ * case there will be a sync error between formats
+ * supported in fmt_support and this function.
+ */
+ return -EINVAL;
+ }
+ break;
+ }
+
+ if (var->xres_virtual * var->yres_virtual * var->bits_per_pixel / 8 >
+ info->fix.smem_len)
+ return -EINVAL;
+
+ fill_fmt_bitfields(var, rgb);
+
+ return 0;
+}
+
+static int overlayfb_set_par(struct fb_info *info)
+{
+ int size, bpp;
+ struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par;
+ struct mxsfb_info *fbi = ofb->fbi;
+ struct fb_var_screeninfo *var = &ofb->ol_fb->var;
+
+ bpp = var->bits_per_pixel;
+ ofb->ol_fb->fix.line_length = var->xres_virtual * bpp / 8;
+
+ size = PAGE_ALIGN(ofb->ol_fb->fix.line_length * var->yres_virtual);
+ if (ofb->video_mem_size < size)
+ return -EINVAL;
+
+ if (!lock_fb_info(fbi->fb_info))
+ return -EINVAL;
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_enable_pix(fbi);
+ clk_enable_axi(fbi);
+ clk_enable_disp_axi(fbi);
+ }
+ unlock_fb_info(fbi->fb_info);
+
+ if (ofb->blank_state == FB_BLANK_UNBLANK)
+ ofb->ops->disable(ofb);
+
+ ofb->ops->setup(ofb);
+
+ if (ofb->blank_state == FB_BLANK_UNBLANK)
+ ofb->ops->enable(ofb);
+
+ if (!lock_fb_info(fbi->fb_info))
+ return -EINVAL;
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_disable_disp_axi(fbi);
+ clk_disable_axi(fbi);
+ clk_disable_pix(fbi);
+ }
+ unlock_fb_info(fbi->fb_info);
+
+ if ((var->activate & FB_ACTIVATE_FORCE) &&
+ (var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW)
+ var->activate = FB_ACTIVATE_NOW;
+
+ return 0;
+}
+
+static int overlayfb_blank(int blank, struct fb_info *info)
+{
+ struct mxsfb_layer *ofb = (struct mxsfb_layer*)info->par;
+ struct mxsfb_info *fbi = ofb->fbi;
+
+ if (ofb->blank_state == blank)
+ return 0;
+
+ if (!lock_fb_info(fbi->fb_info))
+ return -EINVAL;
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_enable_pix(fbi);
+ clk_enable_axi(fbi);
+ clk_enable_disp_axi(fbi);
+ }
+ unlock_fb_info(fbi->fb_info);
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ ofb->ops->disable(ofb);
+ break;
+ case FB_BLANK_UNBLANK:
+ ofb->ops->enable(ofb);
+ break;
+ }
+
+ if (!lock_fb_info(fbi->fb_info))
+ return -EINVAL;
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_disable_disp_axi(fbi);
+ clk_disable_axi(fbi);
+ clk_disable_pix(fbi);
+ }
+ unlock_fb_info(fbi->fb_info);
+
+ ofb->blank_state = blank;
+
+ return 0;
+}
+
+static int overlayfb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ int ret = 0;
+ unsigned int bytes_offset;
+ struct mxsfb_layer *ofb = (struct mxsfb_layer *)info->par;
+ struct mxsfb_info *fbi = ofb->fbi;
+
+ init_completion(&fbi->flip_complete);
+
+ if (!lock_fb_info(fbi->fb_info))
+ return -EINVAL;
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ unlock_fb_info(fbi->fb_info);
+ return -EINVAL;
+ }
+
+ unlock_fb_info(fbi->fb_info);
+
+ bytes_offset = info->fix.line_length * var->yoffset;
+ writel(info->fix.smem_start + bytes_offset,
+ fbi->base + LCDC_AS_NEXT_BUF);
+
+ /* update on next VSYNC */
+ writel(CTRL1_CUR_FRAME_DONE_IRQ_EN,
+ fbi->base + LCDC_CTRL1 + REG_SET);
+
+ ret = wait_for_completion_timeout(&fbi->flip_complete, HZ / 2);
+ if (!ret) {
+ dev_err(info->device,
+ "overlay wait for pane flip timeout\n");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static struct fb_ops overlay_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_open = overlayfb_open,
+ .fb_release = overlayfb_release,
+ .fb_check_var = overlayfb_check_var,
+ .fb_set_par = overlayfb_set_par,
+ .fb_blank = overlayfb_blank,
+ .fb_pan_display = overlayfb_pan_display,
+ .fb_mmap = mxsfb_mmap,
+};
+
+static void init_mxsfb_overlay(struct mxsfb_info *fbi,
+ struct mxsfb_layer *ofb)
+{
+ dev_dbg(&fbi->pdev->dev, "AS overlay init\n");
+
+ ofb->ol_fb->fix.type = FB_TYPE_PACKED_PIXELS;
+ ofb->ol_fb->fix.xpanstep = 0;
+ ofb->ol_fb->fix.ypanstep = 1;
+ ofb->ol_fb->fix.ywrapstep = 1;
+ ofb->ol_fb->fix.visual = FB_VISUAL_TRUECOLOR;
+ ofb->ol_fb->fix.accel = FB_ACCEL_NONE;
+
+ ofb->ol_fb->var.activate = FB_ACTIVATE_NXTOPEN;
+ ofb->ol_fb->var.xres = fbi->fb_info->var.xres;
+ ofb->ol_fb->var.yres = fbi->fb_info->var.yres;
+ ofb->ol_fb->var.xres_virtual = fbi->fb_info->var.xres_virtual;
+ ofb->ol_fb->var.yres_virtual = fbi->fb_info->var.yres;
+ ofb->ol_fb->var.bits_per_pixel = fbi->fb_info->var.bits_per_pixel;
+ ofb->ol_fb->var.vmode = FB_VMODE_NONINTERLACED;
+ ofb->ol_fb->var.nonstd = 0;
+
+ /* Copy timings of primary fb */
+ ofb->ol_fb->var.pixclock = fbi->fb_info->var.pixclock;
+ ofb->ol_fb->var.left_margin = fbi->fb_info->var.left_margin;
+ ofb->ol_fb->var.right_margin = fbi->fb_info->var.right_margin;
+ ofb->ol_fb->var.upper_margin = fbi->fb_info->var.upper_margin;
+ ofb->ol_fb->var.lower_margin = fbi->fb_info->var.lower_margin;
+ ofb->ol_fb->var.hsync_len = fbi->fb_info->var.hsync_len;
+ ofb->ol_fb->var.vsync_len = fbi->fb_info->var.vsync_len;
+
+ ofb->ol_fb->fbops = &overlay_fb_ops;
+ ofb->ol_fb->node = -1;
+ ofb->ol_fb->par = ofb;
+ INIT_LIST_HEAD(&ofb->ol_fb->modelist);
+
+ ofb->id = 0;
+ ofb->ops = &ofb_ops;
+ atomic_set(&ofb->usage, 0);
+ ofb->blank_state = -1;
+ ofb->global_alpha = 255;
+ ofb->fbi = fbi;
+
+ sprintf(ofb->ol_fb->fix.id, "FG");
+}
+
+static int mxsfb_overlay_map_video_memory(struct mxsfb_info *fbi,
+ struct mxsfb_layer *ofb)
+{
+ struct fb_info *fb = fbi->fb_info;
+ BUG_ON(!fb->fix.smem_len);
+
+ ofb->video_mem_size = fb->fix.smem_len;
+ ofb->video_mem = dma_alloc_writecombine(ofb->dev,
+ ofb->video_mem_size,
+ (dma_addr_t *)&ofb->video_mem_phys,
+ GFP_DMA | GFP_KERNEL);
+
+ if (ofb->video_mem == NULL) {
+ dev_err(ofb->dev, "Unable to allocate overlay fb memory\n");
+ return -ENOMEM;
+ }
+
+ /* clear overlay fb memory buffer */
+ memset(ofb->video_mem, 0x0, ofb->video_mem_size);
+
+ ofb->ol_fb->fix.smem_start = ofb->video_mem_phys;
+ ofb->ol_fb->fix.smem_len = ofb->video_mem_size;
+ ofb->ol_fb->screen_base = ofb->video_mem;
+
+ return 0;
+}
+
+static void mxsfb_overlay_init(struct mxsfb_info *fbi)
+{
+ int ret;
+ struct mxsfb_layer *ofb = &fbi->overlay;
+ struct fb_videomode ofb_vm;
+
+ ofb->dev = &fbi->pdev->dev;
+ ofb->ol_fb = framebuffer_alloc(0, ofb->dev);
+ if (!ofb->ol_fb) {
+ dev_err(ofb->dev, "Faild to allocate overlay fbinfo\n");
+ return;
+ }
+
+ init_mxsfb_overlay(fbi, ofb);
+
+ /* add videomode to overlay fb */
+ fb_var_to_videomode(&ofb_vm, &fbi->fb_info->var);
+ ret = fb_add_videomode(&ofb_vm, &ofb->ol_fb->modelist);
+ if (ret) {
+ dev_err(ofb->dev, "add vm to ofb failed\n");
+ goto fb_release;
+ }
+
+ ret = register_framebuffer(ofb->ol_fb);
+ if (ret) {
+ dev_err(ofb->dev, "failed to register overlay\n");
+ goto fb_release;
+ }
+
+ ret = mxsfb_overlay_map_video_memory(fbi, ofb);
+ if (ret) {
+ dev_err(ofb->dev, "failed to map video mem for overlay\n");
+ goto fb_unregister;
+ }
+
+ /* setup the initial params for overlay fb */
+ overlayfb_check_var(&ofb->ol_fb->var, ofb->ol_fb);
+ overlayfb_set_par(ofb->ol_fb);
+
+ ofb->registered = 1;
+
+ return;
+
+fb_unregister:
+ unregister_framebuffer(ofb->ol_fb);
+fb_release:
+ framebuffer_release(ofb->ol_fb);
+}
+
+static void mxsfb_overlay_exit(struct mxsfb_info *fbi)
+{
+ struct mxsfb_layer *ofb = &fbi->overlay;
+
+ if (ofb->registered) {
+ if (ofb->video_mem)
+ dma_free_writecombine(ofb->dev, ofb->video_mem_size,
+ ofb->video_mem, ofb->video_mem_phys);
+
+ unregister_framebuffer(ofb->ol_fb);
+ framebuffer_release(ofb->ol_fb);
+ }
+}
+
+static void mxsfb_overlay_resume(struct mxsfb_info *fbi)
+{
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_enable_pix(fbi);
+ clk_enable_axi(fbi);
+ clk_enable_disp_axi(fbi);
+ }
+
+ writel(saved_as_ctrl, fbi->base + LCDC_AS_CTRL);
+ writel(saved_as_next_buf, fbi->base + LCDC_AS_NEXT_BUF);
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_disable_disp_axi(fbi);
+ clk_disable_axi(fbi);
+ clk_disable_pix(fbi);
+ }
+
+}
+
+static void mxsfb_overlay_suspend(struct mxsfb_info *fbi)
+{
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_enable_pix(fbi);
+ clk_enable_axi(fbi);
+ clk_enable_disp_axi(fbi);
+ }
+
+ saved_as_ctrl = readl(fbi->base + LCDC_AS_CTRL);
+ saved_as_next_buf = readl(fbi->base + LCDC_AS_NEXT_BUF);
+
+ if (fbi->cur_blank != FB_BLANK_UNBLANK) {
+ clk_disable_disp_axi(fbi);
+ clk_disable_axi(fbi);
+ clk_disable_pix(fbi);
+ }
+}
+#else
+static void mxsfb_overlay_init(struct mxsfb_info *fbi) {}
+static void mxsfb_overlay_exit(struct mxsfb_info *fbi) {}
+static void mxsfb_overlay_resume(struct mxsfb_info *fbi) {}
+static void mxsfb_overlay_suspend(struct mxsfb_info *fbi) {}
+#endif
+
static int mxsfb_probe(struct platform_device *pdev)
{
const struct of_device_id *of_id =
@@ -876,28 +2189,58 @@ static int mxsfb_probe(struct platform_device *pdev)
struct resource *res;
struct mxsfb_info *host;
struct fb_info *fb_info;
- struct fb_videomode *mode;
- int ret;
+ struct pinctrl *pinctrl;
+ int irq = platform_get_irq(pdev, 0);
+ int gpio, ret;
if (of_id)
pdev->id_entry = of_id->data;
- fb_info = framebuffer_alloc(sizeof(struct mxsfb_info), &pdev->dev);
- if (!fb_info) {
- dev_err(&pdev->dev, "Failed to allocate fbdev\n");
+ gpio = of_get_named_gpio(pdev->dev.of_node, "enable-gpio", 0);
+ if (gpio == -EPROBE_DEFER)
+ return -EPROBE_DEFER;
+
+ if (gpio_is_valid(gpio)) {
+ ret = devm_gpio_request_one(&pdev->dev, gpio, GPIOF_OUT_INIT_LOW, "lcd_pwr_en");
+ if (ret) {
+ dev_err(&pdev->dev, "faild to request gpio %d, ret = %d\n", gpio, ret);
+ return ret;
+ }
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(&pdev->dev, "Cannot get memory IO resource\n");
+ return -ENODEV;
+ }
+
+ host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL);
+ if (!host) {
+ dev_err(&pdev->dev, "Failed to allocate IO resource\n");
return -ENOMEM;
}
- mode = devm_kzalloc(&pdev->dev, sizeof(struct fb_videomode),
- GFP_KERNEL);
- if (mode == NULL)
+ fb_info = framebuffer_alloc(0, &pdev->dev);
+ if (!fb_info) {
+ dev_err(&pdev->dev, "Failed to allocate fbdev\n");
+ devm_kfree(&pdev->dev, host);
return -ENOMEM;
+ }
+ host->fb_info = fb_info;
+ fb_info->par = host;
- host = to_imxfb_host(fb_info);
+ ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0,
+ dev_name(&pdev->dev), host);
+ if (ret) {
+ dev_err(&pdev->dev, "request_irq (%d) failed with error %d\n",
+ irq, ret);
+ ret = -ENODEV;
+ goto fb_release;
+ }
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->base)) {
+ dev_err(&pdev->dev, "ioremap failed\n");
ret = PTR_ERR(host->base);
goto fb_release;
}
@@ -907,19 +2250,28 @@ static int mxsfb_probe(struct platform_device *pdev)
host->devdata = &mxsfb_devdata[pdev->id_entry->driver_data];
- host->clk = devm_clk_get(&host->pdev->dev, NULL);
- if (IS_ERR(host->clk)) {
- ret = PTR_ERR(host->clk);
+ host->clk_pix = devm_clk_get(&host->pdev->dev, "pix");
+ if (IS_ERR(host->clk_pix)) {
+ host->clk_pix = NULL;
+ ret = PTR_ERR(host->clk_pix);
goto fb_release;
}
host->clk_axi = devm_clk_get(&host->pdev->dev, "axi");
- if (IS_ERR(host->clk_axi))
+ if (IS_ERR(host->clk_axi)) {
host->clk_axi = NULL;
+ ret = PTR_ERR(host->clk_axi);
+ dev_err(&pdev->dev, "Failed to get axi clock: %d\n", ret);
+ goto fb_release;
+ }
host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi");
- if (IS_ERR(host->clk_disp_axi))
+ if (IS_ERR(host->clk_disp_axi)) {
host->clk_disp_axi = NULL;
+ ret = PTR_ERR(host->clk_disp_axi);
+ dev_err(&pdev->dev, "Failed to get disp_axi clock: %d\n", ret);
+ goto fb_release;
+ }
host->reg_lcd = devm_regulator_get(&pdev->dev, "lcd");
if (IS_ERR(host->reg_lcd))
@@ -929,79 +2281,205 @@ static int mxsfb_probe(struct platform_device *pdev)
GFP_KERNEL);
if (!fb_info->pseudo_palette) {
ret = -ENOMEM;
+ dev_err(&pdev->dev, "Failed to allocate pseudo_palette memory\n");
goto fb_release;
}
- ret = mxsfb_init_fbinfo(host, mode);
- if (ret != 0)
- goto fb_release;
-
- fb_videomode_to_var(&fb_info->var, mode);
+ INIT_LIST_HEAD(&fb_info->modelist);
- /* init the color fields */
- mxsfb_check_var(&fb_info->var, fb_info);
+ pm_runtime_enable(&host->pdev->dev);
- platform_set_drvdata(pdev, fb_info);
+ ret = mxsfb_init_fbinfo(host);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to initialize fbinfo: %d\n", ret);
+ goto fb_pm_runtime_disable;
+ }
- ret = register_framebuffer(fb_info);
+ ret = mxsfb_dispdrv_init(pdev, fb_info);
if (ret != 0) {
- dev_err(&pdev->dev,"Failed to register framebuffer\n");
- goto fb_destroy;
+ if (ret == -EPROBE_DEFER)
+ dev_info(&pdev->dev,
+ "Defer fb probe due to dispdrv not ready\n");
+ goto fb_free_videomem;
+ }
+
+ if (!host->dispdrv) {
+ pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ ret = PTR_ERR(pinctrl);
+ goto fb_pm_runtime_disable;
+ }
}
if (!host->enabled) {
- mxsfb_enable_axi_clk(host);
writel(0, host->base + LCDC_CTRL);
- mxsfb_disable_axi_clk(host);
mxsfb_set_par(fb_info);
mxsfb_enable_controller(fb_info);
+ pm_runtime_get_sync(&host->pdev->dev);
+ }
+
+ ret = register_framebuffer(fb_info);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to register framebuffer\n");
+ goto fb_destroy;
+ }
+
+ mxsfb_overlay_init(host);
+
+#ifndef CONFIG_FB_IMX64_DEBUG
+ console_lock();
+ ret = fb_blank(fb_info, FB_BLANK_UNBLANK);
+ console_unlock();
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to unblank framebuffer\n");
+ goto fb_unregister;
}
+#endif
dev_info(&pdev->dev, "initialized\n");
return 0;
+#ifndef CONFIG_FB_IMX64_DEBUG
+fb_unregister:
+ unregister_framebuffer(fb_info);
+#endif
fb_destroy:
- if (host->enabled)
- clk_disable_unprepare(host->clk);
+ fb_destroy_modelist(&fb_info->modelist);
+fb_free_videomem:
+ mxsfb_free_videomem(host);
+fb_pm_runtime_disable:
+ clk_disable_pix(host);
+ clk_disable_axi(host);
+ clk_disable_disp_axi(host);
+
+ pm_runtime_disable(&host->pdev->dev);
+ devm_kfree(&pdev->dev, fb_info->pseudo_palette);
fb_release:
framebuffer_release(fb_info);
+ devm_kfree(&pdev->dev, host);
return ret;
}
static int mxsfb_remove(struct platform_device *pdev)
{
- struct fb_info *fb_info = platform_get_drvdata(pdev);
- struct mxsfb_info *host = to_imxfb_host(fb_info);
+ struct mxsfb_info *host = platform_get_drvdata(pdev);
+ struct fb_info *fb_info = host->fb_info;
if (host->enabled)
mxsfb_disable_controller(fb_info);
+ if (host->devdata->flags & MXSFB_FLAG_PMQOS)
+ pm_qos_remove_request(&host->pm_qos_req);
+
+ pm_runtime_disable(&host->pdev->dev);
+ mxsfb_overlay_exit(host);
unregister_framebuffer(fb_info);
mxsfb_free_videomem(host);
+ platform_set_drvdata(pdev, NULL);
+
+ devm_kfree(&pdev->dev, fb_info->pseudo_palette);
framebuffer_release(fb_info);
+ devm_kfree(&pdev->dev, host);
return 0;
}
static void mxsfb_shutdown(struct platform_device *pdev)
{
- struct fb_info *fb_info = platform_get_drvdata(pdev);
- struct mxsfb_info *host = to_imxfb_host(fb_info);
-
- mxsfb_enable_axi_clk(host);
+ struct mxsfb_info *host = platform_get_drvdata(pdev);
/*
* Force stop the LCD controller as keeping it running during reboot
* might interfere with the BootROM's boot mode pads sampling.
*/
- writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
+ if (host->cur_blank == FB_BLANK_UNBLANK) {
+ writel(CTRL_RUN, host->base + LCDC_CTRL + REG_CLR);
+ writel(CTRL_MASTER, host->base + LCDC_CTRL + REG_CLR);
+ }
+}
+
+#ifdef CONFIG_PM
+static int mxsfb_runtime_suspend(struct device *dev)
+{
+ struct mxsfb_info *host = dev_get_drvdata(dev);
- mxsfb_disable_axi_clk(host);
+ if (host->devdata->flags & MXSFB_FLAG_BUSFREQ)
+ release_bus_freq(BUS_FREQ_HIGH);
+
+ if (host->devdata->flags & MXSFB_FLAG_PMQOS)
+ pm_qos_remove_request(&host->pm_qos_req);
+
+ dev_dbg(dev, "mxsfb busfreq high release.\n");
+
+ return 0;
}
+static int mxsfb_runtime_resume(struct device *dev)
+{
+ struct mxsfb_info *host = dev_get_drvdata(dev);
+
+ if (host->devdata->flags & MXSFB_FLAG_BUSFREQ)
+ request_bus_freq(BUS_FREQ_HIGH);
+
+ if (host->devdata->flags & MXSFB_FLAG_PMQOS)
+ pm_qos_add_request(&host->pm_qos_req,
+ PM_QOS_CPU_DMA_LATENCY, 0);
+
+ dev_dbg(dev, "mxsfb busfreq high request.\n");
+
+ return 0;
+}
+
+static int mxsfb_suspend(struct device *pdev)
+{
+ struct mxsfb_info *host = dev_get_drvdata(pdev);
+ struct fb_info *fb_info = host->fb_info;
+ int saved_blank;
+
+ console_lock();
+ mxsfb_overlay_suspend(host);
+ fb_set_suspend(fb_info, 1);
+ saved_blank = host->cur_blank;
+ mxsfb_blank(FB_BLANK_POWERDOWN, fb_info);
+ host->restore_blank = saved_blank;
+ console_unlock();
+
+ pinctrl_pm_select_sleep_state(pdev);
+
+ return 0;
+}
+
+static int mxsfb_resume(struct device *pdev)
+{
+ struct mxsfb_info *host = dev_get_drvdata(pdev);
+ struct fb_info *fb_info = host->fb_info;
+
+ pinctrl_pm_select_default_state(pdev);
+
+ console_lock();
+ mxsfb_blank(host->restore_blank, fb_info);
+ fb_set_suspend(fb_info, 0);
+ mxsfb_overlay_resume(host);
+ console_unlock();
+
+ return 0;
+}
+#else
+#define mxsfb_runtime_suspend NULL
+#define mxsfb_runtime_resume NULL
+
+#define mxsfb_suspend NULL
+#define mxsfb_resume NULL
+#endif
+
+static const struct dev_pm_ops mxsfb_pm_ops = {
+ SET_RUNTIME_PM_OPS(mxsfb_runtime_suspend, mxsfb_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(mxsfb_suspend, mxsfb_resume)
+};
+
static struct platform_driver mxsfb_driver = {
.probe = mxsfb_probe,
.remove = mxsfb_remove,
@@ -1010,6 +2488,7 @@ static struct platform_driver mxsfb_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = mxsfb_dt_ids,
+ .pm = &mxsfb_pm_ops,
},
};