aboutsummaryrefslogtreecommitdiffstats
path: root/patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch')
-rw-r--r--patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch606
1 files changed, 606 insertions, 0 deletions
diff --git a/patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch b/patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch
new file mode 100644
index 00000000000000..5e84aca8408210
--- /dev/null
+++ b/patches.at91/0135-video-atmel_hlcdfb-add-new-driver.patch
@@ -0,0 +1,606 @@
+From 3ceb9c35a7c43f0762c0a857a49a3633fef821fd Mon Sep 17 00:00:00 2001
+From: Wolfram Sang <w.sang@pengutronix.de>
+Date: Mon, 23 May 2011 15:36:52 +0200
+Subject: video: atmel_hlcdfb: add new driver
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Signed-off-by: Wolfram Sang <w.sang@pengutronix.de>
+Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
+---
+ drivers/video/Kconfig | 9 +
+ drivers/video/Makefile | 1 +
+ drivers/video/atmel_hlcdfb.c | 514 +++++++++++++++++++++++++++++++++++++++
+ drivers/video/atmel_lcdfb_core.c | 15 ++
+ 4 files changed, 539 insertions(+)
+ create mode 100644 drivers/video/atmel_hlcdfb.c
+
+diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
+index a290be5..ceccaa3 100644
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -1028,6 +1028,15 @@ config FB_ATMEL_STN
+
+ If unsure, say N.
+
++config FB_ATMEL_HLCD
++ tristate "AT91 HLCD Controller support"
++ depends on FB && HAVE_FB_ATMEL
++ select FB_CFB_FILLRECT
++ select FB_CFB_COPYAREA
++ select FB_CFB_IMAGEBLIT
++ help
++ This enables support for the AT91 HLCD Controller.
++
+ config FB_NVIDIA
+ tristate "nVidia Framebuffer Support"
+ depends on FB && PCI
+diff --git a/drivers/video/Makefile b/drivers/video/Makefile
+index 37c5625..36320ea 100644
+--- a/drivers/video/Makefile
++++ b/drivers/video/Makefile
+@@ -96,6 +96,7 @@ obj-$(CONFIG_FB_SA1100) += sa1100fb.o
+ obj-$(CONFIG_FB_HIT) += hitfb.o
+ obj-$(CONFIG_FB_EPSON1355) += epson1355fb.o
+ obj-$(CONFIG_FB_ATMEL) += atmel_lcdfb.o atmel_lcdfb_core.o
++obj-$(CONFIG_FB_ATMEL_HLCD) += atmel_hlcdfb.o atmel_lcdfb_core.o
+ obj-$(CONFIG_FB_PVR2) += pvr2fb.o
+ obj-$(CONFIG_FB_VOODOO1) += sstfb.o
+ obj-$(CONFIG_FB_ARMCLCD) += amba-clcd.o
+diff --git a/drivers/video/atmel_hlcdfb.c b/drivers/video/atmel_hlcdfb.c
+new file mode 100644
+index 0000000..b772841
+--- /dev/null
++++ b/drivers/video/atmel_hlcdfb.c
+@@ -0,0 +1,514 @@
++/*
++ * Driver for AT91/AT32 LCD Controller
++ *
++ * Copyright (C) 2007 Atmel Corporation
++ *
++ * This file is subject to the terms and conditions of the GNU General Public
++ * License. See the file COPYING in the main directory of this archive for
++ * more details.
++ */
++
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
++#include <linux/interrupt.h>
++#include <linux/backlight.h>
++#include <linux/fb.h>
++#include <linux/clk.h>
++#include <linux/init.h>
++#include <linux/delay.h>
++
++#include <mach/board.h>
++#include <mach/cpu.h>
++#include <mach/atmel_hlcdc.h>
++#include <mach/atmel_hlcdc_ovl.h>
++
++#include <video/atmel_lcdfb.h>
++
++#define ATMEL_LCDFB_FBINFO_DEFAULT (FBINFO_DEFAULT \
++ | FBINFO_PARTIAL_PAN_OK \
++ | FBINFO_HWACCEL_YPAN)
++
++#define ATMEL_LCDC_CVAL_DEFAULT 0xc8
++
++struct atmel_hlcd_dma_desc {
++ u32 address;
++ u32 control;
++ u32 next;
++};
++
++static void atmel_hlcdfb_update_dma_base(struct fb_info *info,
++
++ struct fb_var_screeninfo *var)
++{
++ struct atmel_lcdfb_info *sinfo = info->par;
++ struct fb_fix_screeninfo *fix = &info->fix;
++ unsigned long dma_addr;
++ struct atmel_hlcd_dma_desc *desc;
++
++ dma_addr = (fix->smem_start + var->yoffset * fix->line_length
++ + var->xoffset * var->bits_per_pixel / 8);
++
++ dma_addr &= ~3UL;
++
++ /* Setup the DMA descriptor, this descriptor will loop to itself */
++ desc = sinfo->dma_desc;
++
++ desc->address = dma_addr;
++ /* Disable DMA transfer interrupt & descriptor loaded interrupt. */
++ desc->control = LCDC_BASECTRL_ADDIEN | LCDC_BASECTRL_DSCRIEN
++ | LCDC_BASECTRL_DMAIEN | LCDC_BASECTRL_DFETCH;
++ desc->next = sinfo->dma_desc_phys;
++
++ lcdc_writel(sinfo, ATMEL_LCDC_BASEADDR, dma_addr);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECTRL, desc->control);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASENEXT, sinfo->dma_desc_phys);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECHER, LCDC_BASECHER_CHEN | LCDC_BASECHER_UPDATEEN);
++}
++
++static void atmel_hlcdfb_update_dma_ovl(struct fb_info *info,
++ struct fb_var_screeninfo *var)
++{
++ struct atmel_lcdfb_info *sinfo = info->par;
++ struct fb_fix_screeninfo *fix = &info->fix;
++ unsigned long dma_addr;
++ struct atmel_hlcd_dma_desc *desc;
++
++ dma_addr = (fix->smem_start + var->yoffset * fix->line_length
++ + var->xoffset * var->bits_per_pixel / 8);
++
++ dma_addr &= ~3UL;
++
++ /* Setup the DMA descriptor, this descriptor will loop to itself */
++ desc = sinfo->dma_desc;
++
++ desc->address = dma_addr;
++ /* Disable DMA transfer interrupt & descriptor loaded interrupt. */
++ desc->control = LCDC_OVRCTRL1_ADDIEN | LCDC_OVRCTRL1_DSCRIEN
++ | LCDC_OVRCTRL1_DMAIEN | LCDC_OVRCTRL1_DFETCH;
++ desc->next = sinfo->dma_desc_phys;
++
++ lcdc_writel(sinfo, ATMEL_LCDC_OVRADDR1, dma_addr);
++ lcdc_writel(sinfo, ATMEL_LCDC_OVRCTRL1, desc->control);
++ lcdc_writel(sinfo, ATMEL_LCDC_OVRNEXT1, sinfo->dma_desc_phys);
++ lcdc_writel(sinfo, ATMEL_LCDC_OVRCHER1, LCDC_OVRCHER1_CHEN | LCDC_OVRCHER1_UPDATEEN);
++}
++
++/* some bl->props field just changed */
++static int atmel_bl_update_status(struct backlight_device *bl)
++{
++ struct atmel_lcdfb_info *sinfo = bl_get_data(bl);
++ int power = sinfo->bl_power;
++ int brightness = bl->props.brightness;
++ u32 reg;
++
++ /* REVISIT there may be a meaningful difference between
++ * fb_blank and power ... there seem to be some cases
++ * this doesn't handle correctly.
++ */
++ if (bl->props.fb_blank != sinfo->bl_power)
++ power = bl->props.fb_blank;
++ else if (bl->props.power != sinfo->bl_power)
++ power = bl->props.power;
++
++ if (brightness < 0 && power == FB_BLANK_UNBLANK)
++ brightness = lcdc_readl(sinfo, ATMEL_LCDC_LCDCFG6)
++ >> LCDC_LCDCFG6_PWMCVAL_OFFSET;
++ else if (power != FB_BLANK_UNBLANK)
++ brightness = 0;
++
++ reg = lcdc_readl(sinfo, ATMEL_LCDC_LCDCFG6) & ~LCDC_LCDCFG6_PWMCVAL;
++ reg |= brightness << LCDC_LCDCFG6_PWMCVAL_OFFSET;
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG6, reg);
++
++ bl->props.fb_blank = bl->props.power = sinfo->bl_power = power;
++
++ return 0;
++}
++
++static int atmel_bl_get_brightness(struct backlight_device *bl)
++{
++ struct atmel_lcdfb_info *sinfo = bl_get_data(bl);
++
++ return lcdc_readl(sinfo, ATMEL_LCDC_LCDCFG6) >> LCDC_LCDCFG6_PWMCVAL_OFFSET;
++}
++
++static const struct backlight_ops atmel_hlcdc_bl_ops = {
++ .update_status = atmel_bl_update_status,
++ .get_brightness = atmel_bl_get_brightness,
++};
++
++static void atmel_hlcdfb_init_contrast(struct atmel_lcdfb_info *sinfo)
++{
++ /* have some default contrast/backlight settings */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG6, LCDC_LCDCFG6_PWMPOL |
++ (ATMEL_LCDC_CVAL_DEFAULT << LCDC_LCDCFG6_PWMCVAL_OFFSET));
++}
++
++void atmel_hlcdfb_start(struct atmel_lcdfb_info *sinfo)
++{
++ u32 value;
++
++ value = lcdc_readl(sinfo, ATMEL_LCDC_LCDEN);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, value | LCDC_LCDEN_CLKEN);
++ while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_CLKSTS))
++ msleep(1);
++ value = lcdc_readl(sinfo, ATMEL_LCDC_LCDEN);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, value | LCDC_LCDEN_SYNCEN);
++ while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_LCDSTS))
++ msleep(1);
++ value = lcdc_readl(sinfo, ATMEL_LCDC_LCDEN);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, value | LCDC_LCDEN_DISPEN);
++ while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_DISPSTS))
++ msleep(1);
++ value = lcdc_readl(sinfo, ATMEL_LCDC_LCDEN);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, value | LCDC_LCDEN_PWMEN);
++ while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_PWMSTS))
++ msleep(1);
++}
++
++static void atmel_hlcdfb_stop(struct atmel_lcdfb_info *sinfo, u32 flags)
++{
++ /* Disable DISP signal */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_DISPDIS);
++ while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_DISPSTS))
++ msleep(1);
++ /* Disable synchronization */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_SYNCDIS);
++ while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_LCDSTS))
++ msleep(1);
++ /* Disable pixel clock */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_CLKDIS);
++ while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_CLKSTS))
++ msleep(1);
++ /* Disable PWM */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_PWMDIS);
++ while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_PWMSTS))
++ msleep(1);
++
++ if (!(flags & ATMEL_LCDC_STOP_NOWAIT))
++ /* Wait for the end of DMA transfer */
++ while (!(lcdc_readl(sinfo, ATMEL_LCDC_BASEISR) & LCDC_BASEISR_DMA))
++ msleep(10);
++ //FIXME: OVL DMA?
++}
++
++static u32 atmel_hlcdfb_get_rgbmode(struct fb_info *info)
++{
++ u32 value = 0;
++
++ switch (info->var.bits_per_pixel) {
++ case 1:
++ value = LCDC_BASECFG1_CLUTMODE_1BPP | LCDC_BASECFG1_CLUTEN;
++ break;
++ case 2:
++ value = LCDC_BASECFG1_CLUTMODE_2BPP | LCDC_BASECFG1_CLUTEN;
++ break;
++ case 4:
++ value = LCDC_BASECFG1_CLUTMODE_4BPP | LCDC_BASECFG1_CLUTEN;
++ break;
++ case 8:
++ value = LCDC_BASECFG1_CLUTMODE_8BPP | LCDC_BASECFG1_CLUTEN;
++ break;
++ case 12:
++ value = LCDC_BASECFG1_RGBMODE_12BPP_RGB_444;
++ break;
++ case 16:
++ if (info->var.transp.offset)
++ value = LCDC_BASECFG1_RGBMODE_16BPP_ARGB_4444;
++ else
++ value = LCDC_BASECFG1_RGBMODE_16BPP_RGB_565;
++ break;
++ case 18:
++ value = LCDC_BASECFG1_RGBMODE_18BPP_RGB_666_PACKED;
++ break;
++ case 24:
++ value = LCDC_BASECFG1_RGBMODE_24BPP_RGB_888_PACKED;
++ break;
++ case 32:
++ value = LCDC_BASECFG1_RGBMODE_32BPP_ARGB_8888;
++ break;
++ default:
++ dev_err(info->device, "Cannot set video mode for %dbpp\n",
++ info->var.bits_per_pixel);
++ break;
++ }
++
++ return value;
++}
++
++static int atmel_hlcdfb_setup_core_base(struct fb_info *info)
++{
++ struct atmel_lcdfb_info *sinfo = info->par;
++ unsigned long value;
++ unsigned long clk_value_khz;
++
++ dev_dbg(info->device, "%s:\n", __func__);
++ /* Set pixel clock */
++ clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000;
++
++ value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock));
++
++ if (value < 1) {
++ dev_notice(info->device, "using system clock as pixel clock\n");
++ value = LCDC_LCDCFG0_CLKPOL | LCDC_LCDCFG0_CLKPWMSEL | LCDC_LCDCFG0_CGDISBASE;
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG0, value);
++ } else {
++ info->var.pixclock = KHZ2PICOS(clk_value_khz / value);
++ dev_dbg(info->device, " updated pixclk: %lu KHz\n",
++ PICOS2KHZ(info->var.pixclock));
++ value = value - 2;
++ dev_dbg(info->device, " * programming CLKDIV = 0x%08lx\n",
++ value);
++ value = (value << LCDC_LCDCFG0_CLKDIV_OFFSET)
++ | LCDC_LCDCFG0_CLKPOL
++ | LCDC_LCDCFG0_CGDISBASE;
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG0, value);
++ }
++
++ /* Initialize control register 5 */
++ /* In 9x5, the default_lcdcon2 will use for LCDCFG5 */
++ value = sinfo->default_lcdcon2;
++ value |= (sinfo->guard_time << LCDC_LCDCFG5_GUARDTIME_OFFSET)
++ | LCDC_LCDCFG5_DISPDLY
++ | LCDC_LCDCFG5_VSPDLYS;
++
++ if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))
++ value |= LCDC_LCDCFG5_HSPOL;
++ if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))
++ value |= LCDC_LCDCFG5_VSPOL;
++
++ dev_dbg(info->device, " * LCDC_LCDCFG5 = %08lx\n", value);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG5, value);
++
++ /* Vertical & Horizontal Timing */
++ value = (info->var.vsync_len - 1) << LCDC_LCDCFG1_VSPW_OFFSET;
++ value |= (info->var.hsync_len - 1) << LCDC_LCDCFG1_HSPW_OFFSET;
++ dev_dbg(info->device, " * LCDC_LCDCFG1 = %08lx\n", value);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG1, value);
++
++ value = (info->var.lower_margin) << LCDC_LCDCFG2_VBPW_OFFSET;
++ value |= (info->var.upper_margin - 1) << LCDC_LCDCFG2_VFPW_OFFSET;
++ dev_dbg(info->device, " * LCDC_LCDCFG2 = %08lx\n", value);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG2, value);
++
++ value = (info->var.right_margin - 1) << LCDC_LCDCFG3_HBPW_OFFSET;
++ value |= (info->var.left_margin - 1) << LCDC_LCDCFG3_HFPW_OFFSET;
++ dev_dbg(info->device, " * LCDC_LCDCFG3 = %08lx\n", value);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG3, value);
++
++ /* Display size */
++ value = (info->var.yres - 1) << LCDC_LCDCFG4_RPF_OFFSET;
++ value |= (info->var.xres - 1) << LCDC_LCDCFG4_PPL_OFFSET;
++ dev_dbg(info->device, " * LCDC_LCDCFG4 = %08lx\n", value);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG4, value);
++
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECFG0, LCDC_BASECFG0_BLEN_AHB_INCR4 | LCDC_BASECFG0_DLBO);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECFG1, atmel_hlcdfb_get_rgbmode(info));
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECFG2, 0);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECFG3, 0); /* Default color */
++ lcdc_writel(sinfo, ATMEL_LCDC_BASECFG4, LCDC_BASECFG4_DMA);
++
++ /* Disable all interrupts */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDIDR, ~0UL);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASEIDR, ~0UL);
++ /* Enable BASE LAYER overflow interrupts, if want to enable DMA interrupt, also need set it at LCDC_BASECTRL reg */
++ lcdc_writel(sinfo, ATMEL_LCDC_BASEIER, LCDC_BASEIER_OVR);
++ //FIXME: Let video-driver register a callback
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDIER, LCDC_LCDIER_FIFOERRIE |
++ LCDC_LCDIER_BASEIE | LCDC_LCDIER_HEOIE);
++
++ return 0;
++}
++
++static int atmel_hlcdfb_setup_core_ovl(struct fb_info *info)
++{
++ struct atmel_lcdfb_info *sinfo = info->par;
++ u32 xpos, ypos, xres, yres, cfg9;
++
++ if (info->var.nonstd >> 31) {
++ xpos = (info->var.nonstd >> 10) & 0x3ff;
++ ypos = info->var.nonstd & 0x3ff;
++ xres = info->var.xres ? info->var.xres - 1 : 0;
++ yres = info->var.yres ? info->var.yres - 1 : 0;
++ cfg9 = LCDC_OVR1CFG9_DMA | LCDC_OVR1CFG9_OVR |
++ LCDC_OVR1CFG9_ITER | LCDC_OVR1CFG9_ITER2BL |
++ LCDC_OVR1CFG9_REP;
++ if (info->var.transp.offset)
++ cfg9 |= LCDC_OVR1CFG9_LAEN;
++ else
++ cfg9 |= LCDC_OVR1CFG9_GAEN | LCDC_OVR1CFG9_GA;
++ } else {
++ xpos = ypos = yres = xres = cfg9 = 0;
++ }
++
++ lcdc_writel(sinfo, ATMEL_LCDC_OVR1CFG0,
++ LCDC_OVR1CFG0_BLEN_AHB_INCR4 | LCDC_OVR1CFG0_DLBO);
++ lcdc_writel(sinfo, ATMEL_LCDC_OVR1CFG1,
++ atmel_hlcdfb_get_rgbmode(info));
++ lcdc_writel(sinfo, ATMEL_LCDC_OVR1CFG2, xpos |
++ (ypos << LCDC_OVR1CFG2_YOFFSET_OFFSET));
++ lcdc_writel(sinfo, ATMEL_LCDC_OVR1CFG3, xres |
++ (yres << LCDC_OVR1CFG3_YSIZE_OFFSET));
++ lcdc_writel(sinfo, ATMEL_LCDC_OVR1CFG9, cfg9);
++
++ return 0;
++}
++static void atmelfb_limit_screeninfo(struct fb_var_screeninfo *var)
++{
++ /* Saturate vertical and horizontal timings at maximum values */
++ var->vsync_len = min_t(u32, var->vsync_len,
++ (LCDC_LCDCFG1_VSPW >> LCDC_LCDCFG1_VSPW_OFFSET) + 1);
++ var->upper_margin = min_t(u32, var->upper_margin,
++ (LCDC_LCDCFG2_VFPW >> LCDC_LCDCFG2_VFPW_OFFSET) + 1);
++ var->lower_margin = min_t(u32, var->lower_margin,
++ LCDC_LCDCFG2_VBPW >> LCDC_LCDCFG2_VBPW_OFFSET);
++ var->right_margin = min_t(u32, var->right_margin,
++ (LCDC_LCDCFG3_HBPW >> LCDC_LCDCFG3_HBPW_OFFSET) + 1);
++ var->hsync_len = min_t(u32, var->hsync_len,
++ (LCDC_LCDCFG1_HSPW >> LCDC_LCDCFG1_HSPW_OFFSET) + 1);
++ var->left_margin = min_t(u32, var->left_margin,
++ (LCDC_LCDCFG3_HFPW >> LCDC_LCDCFG3_HFPW_OFFSET) + 1);
++
++}
++
++static irqreturn_t atmel_hlcdfb_interrupt(int irq, void *dev_id)
++{
++ struct fb_info *info = dev_id;
++ struct atmel_lcdfb_info *sinfo = info->par;
++ u32 status, baselayer_status;
++
++ /* Check for error status via interrupt.*/
++ status = lcdc_readl(sinfo, ATMEL_LCDC_LCDISR);
++ if (status & LCDC_LCDISR_HEO)
++ return IRQ_NONE;
++
++ if (status & LCDC_LCDISR_FIFOERR)
++ dev_warn(info->device, "FIFO underflow %#x\n", status);
++
++ if (status & LCDC_LCDISR_BASE) {
++ /* Check base layer's overflow error. */
++ baselayer_status = lcdc_readl(sinfo, ATMEL_LCDC_BASEISR);
++
++ if (baselayer_status & LCDC_BASEISR_OVR)
++ dev_warn(info->device, "base layer overflow %#x\n",
++ baselayer_status);
++ }
++
++ return IRQ_HANDLED;
++}
++
++
++#ifdef CONFIG_PM
++
++static int atmel_hlcdfb_suspend(struct platform_device *pdev, pm_message_t mesg)
++{
++ struct fb_info *info = platform_get_drvdata(pdev);
++ struct atmel_lcdfb_info *sinfo = info->par;
++
++ /*
++ * We don't want to handle interrupts while the clock is
++ * stopped. It may take forever.
++ */
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDIDR, ~0UL);
++ lcdc_writel(sinfo, ATMEL_LCDC_BASEIDR, ~0UL);
++
++ if (sinfo->atmel_lcdfb_power_control)
++ sinfo->atmel_lcdfb_power_control(0);
++
++ atmel_hlcdfb_stop(sinfo, 0);
++ atmel_lcdfb_stop_clock(sinfo);
++
++ return 0;
++}
++
++static int atmel_hlcdfb_resume(struct platform_device *pdev)
++{
++ struct fb_info *info = platform_get_drvdata(pdev);
++ struct atmel_lcdfb_info *sinfo = info->par;
++
++ atmel_lcdfb_start_clock(sinfo);
++ atmel_hlcdfb_start(sinfo);
++ if (sinfo->atmel_lcdfb_power_control)
++ sinfo->atmel_lcdfb_power_control(1);
++
++ /* Enable fifo error & BASE LAYER overflow interrupts */
++ lcdc_writel(sinfo, ATMEL_LCDC_BASEIER, LCDC_BASEIER_OVR);
++ lcdc_writel(sinfo, ATMEL_LCDC_LCDIER, LCDC_LCDIER_FIFOERRIE |
++ LCDC_LCDIER_BASEIE | LCDC_LCDIER_HEOIE);
++
++ return 0;
++}
++
++#else
++#define atmel_hlcdfb_suspend NULL
++#define atmel_hlcdfb_resume NULL
++#endif
++
++static struct atmel_lcdfb_devdata dev_data_base = {
++ .setup_core = atmel_hlcdfb_setup_core_base,
++ .start = atmel_hlcdfb_start,
++ .stop = atmel_hlcdfb_stop,
++ .isr = atmel_hlcdfb_interrupt,
++ .update_dma = atmel_hlcdfb_update_dma_base,
++ .bl_ops = &atmel_hlcdc_bl_ops,
++ .init_contrast = atmel_hlcdfb_init_contrast,
++ .limit_screeninfo = atmelfb_limit_screeninfo,
++ .fbinfo_flags = ATMEL_LCDFB_FBINFO_DEFAULT,
++ .lut_base = ATMEL_HLCDC_LUT,
++ .dma_desc_size = sizeof(struct atmel_hlcd_dma_desc),
++};
++
++static struct atmel_lcdfb_devdata dev_data_ovl = {
++ .setup_core = atmel_hlcdfb_setup_core_ovl,
++ .update_dma = atmel_hlcdfb_update_dma_ovl,
++ .limit_screeninfo = atmelfb_limit_screeninfo,
++ .fbinfo_flags = ATMEL_LCDFB_FBINFO_DEFAULT,
++ .lut_base = 0x800, //FIXME: add define
++ .dma_desc_size = sizeof(struct atmel_hlcd_dma_desc),
++};
++
++static const struct platform_device_id atmelfb_dev_table[] = {
++ { "atmel_hlcdfb_base", (kernel_ulong_t)&dev_data_base },
++ { "atmel_hlcdfb_ovl", (kernel_ulong_t)&dev_data_ovl },
++}
++MODULE_DEVICE_TABLE(platform, atmelfb_dev_table);
++
++static int __init atmel_hlcdfb_probe(struct platform_device *pdev)
++{
++ const struct platform_device_id *id = platform_get_device_id(pdev);
++
++ return __atmel_lcdfb_probe(pdev, (struct atmel_lcdfb_devdata *)id->driver_data);
++}
++static int __exit atmel_hlcdfb_remove(struct platform_device *pdev)
++{
++ return __atmel_lcdfb_remove(pdev);
++}
++
++static struct platform_driver atmel_hlcdfb_driver = {
++ .remove = __exit_p(atmel_hlcdfb_remove),
++ .suspend = atmel_hlcdfb_suspend,
++ .resume = atmel_hlcdfb_resume,
++
++ .driver = {
++ .name = "atmel_hlcdfb",
++ .owner = THIS_MODULE,
++ },
++ .id_table = atmelfb_dev_table,
++};
++
++static int __init atmel_hlcdfb_init(void)
++{
++ return platform_driver_probe(&atmel_hlcdfb_driver, atmel_hlcdfb_probe);
++}
++module_init(atmel_hlcdfb_init);
++
++static void __exit atmel_hlcdfb_exit(void)
++{
++ platform_driver_unregister(&atmel_hlcdfb_driver);
++}
++module_exit(atmel_hlcdfb_exit);
++
++MODULE_DESCRIPTION("AT91 HLCD Controller framebuffer driver");
++MODULE_AUTHOR("Nicolas Ferre <nicolas.ferre@atmel.com> "
++ "and Wolfram Sang <w.sang@pengutronix.de");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/video/atmel_lcdfb_core.c b/drivers/video/atmel_lcdfb_core.c
+index 060d41f..cd57361 100644
+--- a/drivers/video/atmel_lcdfb_core.c
++++ b/drivers/video/atmel_lcdfb_core.c
+@@ -90,6 +90,10 @@ static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo)
+
+ dma_free_writecombine(info->device, info->fix.smem_len,
+ info->screen_base, info->fix.smem_start);
++
++ if (sinfo->dev_data->dma_desc_size && sinfo->dma_desc)
++ dma_free_writecombine(info->device, sinfo->dev_data->dma_desc_size,
++ sinfo->dma_desc, sinfo->dma_desc_phys);
+ }
+
+ /**
+@@ -118,6 +122,17 @@ static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo)
+
+ memset(info->screen_base, 0, info->fix.smem_len);
+
++ if (sinfo->dev_data->dma_desc_size) {
++ sinfo->dma_desc = dma_alloc_writecombine(info->device,
++ sinfo->dev_data->dma_desc_size,
++ &(sinfo->dma_desc_phys), GFP_KERNEL);
++
++ if (!sinfo->dma_desc) {
++ dma_free_writecombine(info->device, info->fix.smem_len,
++ info->screen_base, info->fix.smem_start);
++ return -ENOMEM;
++ }
++ }
+ return 0;
+ }
+
+--
+1.8.0.197.g5a90748
+