]> git.hungrycats.org Git - linux/commitdiff
[ARM] Add ARM AMBA CLCD framebuffer driver.
authorRussell King <rmk@flint.arm.linux.org.uk>
Mon, 16 Aug 2004 23:42:32 +0000 (00:42 +0100)
committerRussell King <rmk@flint.arm.linux.org.uk>
Mon, 16 Aug 2004 23:42:32 +0000 (00:42 +0100)
This changeset adds the ARM AMBA CLCD framebuffer driver.  This is
a generic core driver; users are expected to add their platform
specifics via the "platform_data" struct device entry.

drivers/video/Kconfig
drivers/video/Makefile
drivers/video/amba-clcd.c [new file with mode: 0644]
drivers/video/fbmem.c
include/asm-arm/hardware/amba_clcd.h [new file with mode: 0644]

index 798ba0032afa0748919ca0c4ac0bb170d0d43cb0..0db1581dc812d6dba693a968e0b419716647a8e5 100644 (file)
@@ -68,6 +68,19 @@ config FB_PM2_FIFO_DISCONNECT
        help
          Support the Permedia2 FIFO disconnect feature (see CONFIG_FB_PM2).
 
+config FB_ARMCLCD
+       tristate "ARM PrimeCell PL110 support"
+       depends on FB && ARM && ARM_AMBA
+       help
+         This framebuffer device driver is for the ARM PrimeCell PL110
+         Colour LCD controller.  ARM PrimeCells provide the building
+         blocks for System on a Chip devices.
+
+         If you want to compile this as a module (=code which can be
+         inserted into and removed from the running kernel), say M
+         here and read <file:Documentation/modules.txt>.  The module
+         will be called amba-clcd.
+
 config FB_ACORN
        bool "Acorn VIDC support"
        depends on FB && ARM && ARCH_ACORN
index 52e37a0fcda38304f25438f5318ed7b08a5bcaeb..7c9c95435f2e1036e47c47bc17d9ef17ef0a6b32 100644 (file)
@@ -13,6 +13,7 @@ ifeq ($(CONFIG_FB),y)
 obj-$(CONFIG_PPC)                 += macmodes.o
 endif
 
+obj-$(CONFIG_FB_ARMCLCD)         += amba-clcd.o cfbfillrect.o cfbcopyarea.o cfbimgblt.o
 obj-$(CONFIG_FB_ACORN)            += acornfb.o cfbfillrect.o cfbcopyarea.o cfbimgblt.o
 obj-$(CONFIG_FB_AMIGA)            += amifb.o c2p.o
 obj-$(CONFIG_FB_PM2)              += pm2fb.o cfbfillrect.o cfbcopyarea.o cfbimgblt.o
diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c
new file mode 100644 (file)
index 0000000..3b3e1f0
--- /dev/null
@@ -0,0 +1,514 @@
+/*
+ *  linux/drivers/video/amba-clcd.c
+ *
+ * Copyright (C) 2001 ARM Limited, by David A Rusling
+ * Updated to 2.5, Deep Blue Solutions Ltd.
+ *
+ * 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.
+ *
+ *  ARM PrimeCell PL110 Color LCD Controller
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/list.h>
+
+#include <asm/hardware/amba.h>
+#include <asm/hardware/clock.h>
+
+#include <asm/hardware/amba_clcd.h>
+
+#define to_clcd(info)  container_of(info, struct clcd_fb, fb)
+
+/* This is limited to 16 characters when displayed by X startup */
+static const char *clcd_name = "CLCD FB";
+
+/*
+ * Unfortunately, the enable/disable functions may be called either from
+ * process or IRQ context, and we _need_ to delay.  This is _not_ good.
+ */
+static inline void clcdfb_sleep(unsigned int ms)
+{
+       if (in_atomic()) {
+               mdelay(ms);
+       } else {
+               msleep(ms);
+       }
+}
+
+static inline void clcdfb_set_start(struct clcd_fb *fb)
+{
+       unsigned long ustart = fb->fb.fix.smem_start;
+       unsigned long lstart;
+
+       ustart += fb->fb.var.yoffset * fb->fb.fix.line_length;
+       lstart = ustart + fb->fb.var.yres * fb->fb.fix.line_length / 2;
+
+       writel(ustart, fb->regs + CLCD_UBAS);
+       writel(lstart, fb->regs + CLCD_LBAS);
+}
+
+static void clcdfb_disable(struct clcd_fb *fb)
+{
+       u32 val;
+
+       if (fb->board->disable)
+               fb->board->disable(fb);
+
+       val = readl(fb->regs + CLCD_CNTL);
+       if (val & CNTL_LCDPWR) {
+               val &= ~CNTL_LCDPWR;
+               writel(val, fb->regs + CLCD_CNTL);
+
+               clcdfb_sleep(20);
+       }
+       if (val & CNTL_LCDEN) {
+               val &= ~CNTL_LCDEN;
+               writel(val, fb->regs + CLCD_CNTL);
+       }
+
+       /*
+        * Disable CLCD clock source.
+        */
+       clk_disable(fb->clk);
+}
+
+static void clcdfb_enable(struct clcd_fb *fb, u32 cntl)
+{
+       /*
+        * Enable the CLCD clock source.
+        */
+       clk_enable(fb->clk);
+
+       /*
+        * Bring up by first enabling..
+        */
+       cntl |= CNTL_LCDEN;
+       writel(cntl, fb->regs + CLCD_CNTL);
+
+       clcdfb_sleep(20);
+
+       /*
+        * and now apply power.
+        */
+       cntl |= CNTL_LCDPWR;
+       writel(cntl, fb->regs + CLCD_CNTL);
+
+       /*
+        * finally, enable the interface.
+        */
+       if (fb->board->enable)
+               fb->board->enable(fb);
+}
+
+static int
+clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var)
+{
+       int ret = 0;
+
+       memset(&var->transp, 0, sizeof(var->transp));
+       memset(&var->red, 0, sizeof(var->red));
+       memset(&var->green, 0, sizeof(var->green));
+       memset(&var->blue, 0, sizeof(var->blue));
+
+       switch (var->bits_per_pixel) {
+       case 1:
+       case 2:
+       case 4:
+       case 8:
+               var->red.length         = 8;
+               var->red.offset         = 0;
+               var->green.length       = 8;
+               var->green.offset       = 0;
+               var->blue.length        = 8;
+               var->blue.offset        = 0;
+               break;
+       case 16:
+               var->red.length         = 5;
+               var->green.length       = 5;
+               var->blue.length        = 5;
+               if (fb->panel->cntl & CNTL_BGR) {
+                       var->red.offset         = 10;
+                       var->green.offset       = 5;
+                       var->blue.offset        = 0;
+               } else {
+                       var->red.offset         = 0;
+                       var->green.offset       = 5;
+                       var->blue.offset        = 10;
+               }
+               break;
+       case 24:
+               if (fb->panel->cntl & CNTL_LCDTFT) {
+                       var->red.length         = 8;
+                       var->green.length       = 8;
+                       var->blue.length        = 8;
+
+                       if (fb->panel->cntl & CNTL_BGR) {
+                               var->red.offset         = 16;
+                               var->green.offset       = 8;
+                               var->blue.offset        = 0;
+                       } else {
+                               var->red.offset         = 0;
+                               var->green.offset       = 8;
+                               var->blue.offset        = 16;
+                       }
+                       break;
+               }
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+       return ret;
+}
+
+static int clcdfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+       struct clcd_fb *fb = to_clcd(info);
+       int ret = -EINVAL;
+
+       if (fb->board->check)
+               ret = fb->board->check(fb, var);
+       if (ret == 0)
+               ret = clcdfb_set_bitfields(fb, var);
+
+       return ret;
+}
+
+static int clcdfb_set_par(struct fb_info *info)
+{
+       struct clcd_fb *fb = to_clcd(info);
+       struct clcd_regs regs;
+
+       fb->fb.fix.line_length = fb->fb.var.xres_virtual *
+                                fb->fb.var.bits_per_pixel / 8;
+
+       if (fb->fb.var.bits_per_pixel <= 8)
+               fb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
+       else
+               fb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
+
+       fb->board->decode(fb, &regs);
+
+       clcdfb_disable(fb);
+
+       writel(regs.tim0, fb->regs + CLCD_TIM0);
+       writel(regs.tim1, fb->regs + CLCD_TIM1);
+       writel(regs.tim2, fb->regs + CLCD_TIM2);
+       writel(regs.tim3, fb->regs + CLCD_TIM3);
+
+       clcdfb_set_start(fb);
+
+       clk_set_rate(fb->clk, 1000000000 / regs.pixclock);
+
+       fb->clcd_cntl = regs.cntl;
+
+       clcdfb_enable(fb, regs.cntl);
+
+#ifdef DEBUG
+       printk(KERN_INFO "CLCD: Registers set to\n"
+              KERN_INFO "  %08x %08x %08x %08x\n"
+              KERN_INFO "  %08x %08x %08x %08x\n",
+               readl(fb->regs + CLCD_TIM0), readl(fb->regs + CLCD_TIM1),
+               readl(fb->regs + CLCD_TIM2), readl(fb->regs + CLCD_TIM3),
+               readl(fb->regs + CLCD_UBAS), readl(fb->regs + CLCD_LBAS),
+               readl(fb->regs + CLCD_IENB), readl(fb->regs + CLCD_CNTL));
+#endif
+
+       return 0;
+}
+
+static inline u32 convert_bitfield(int val, struct fb_bitfield *bf)
+{
+       unsigned int mask = (1 << bf->length) - 1;
+
+       return (val >> (16 - bf->length) & mask) << bf->offset;
+}
+
+/*
+ *  Set a single color register. The values supplied have a 16 bit
+ *  magnitude.  Return != 0 for invalid regno.
+ */
+static int
+clcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green,
+                unsigned int blue, unsigned int transp, struct fb_info *info)
+{
+       struct clcd_fb *fb = to_clcd(info);
+
+       if (regno < 16)
+               fb->cmap[regno] = convert_bitfield(transp, &fb->fb.var.transp) |
+                                 convert_bitfield(blue, &fb->fb.var.blue) |
+                                 convert_bitfield(green, &fb->fb.var.green) |
+                                 convert_bitfield(red, &fb->fb.var.red);
+
+       if (fb->fb.var.bits_per_pixel == 8 && regno < 256) {
+               int hw_reg = CLCD_PALETTE + ((regno * 2) & ~3);
+               u32 val, mask, newval;
+
+               newval  = (red >> 11)  & 0x001f;
+               newval |= (green >> 6) & 0x03e0;
+               newval |= (blue >> 1)  & 0x7c00;
+
+               /*
+                * 3.2.11: if we're configured for big endian
+                * byte order, the palette entries are swapped.
+                */
+               if (fb->clcd_cntl & CNTL_BEBO)
+                       regno ^= 1;
+
+               if (regno & 1) {
+                       newval <<= 16;
+                       mask = 0x0000ffff;
+               } else {
+                       mask = 0xffff0000;
+               }
+
+               val = readl(fb->regs + hw_reg) & mask;
+               writel(val | newval, fb->regs + hw_reg);
+       }
+
+       return regno > 255;
+}
+
+/*
+ *  Blank the screen if blank_mode != 0, else unblank. If blank == NULL
+ *  then the caller blanks by setting the CLUT (Color Look Up Table) to all
+ *  black. Return 0 if blanking succeeded, != 0 if un-/blanking failed due
+ *  to e.g. a video mode which doesn't support it. Implements VESA suspend
+ *  and powerdown modes on hardware that supports disabling hsync/vsync:
+ *    blank_mode == 2: suspend vsync
+ *    blank_mode == 3: suspend hsync
+ *    blank_mode == 4: powerdown
+ */
+static int clcdfb_blank(int blank_mode, struct fb_info *info)
+{
+       struct clcd_fb *fb = to_clcd(info);
+
+       if (blank_mode != 0) {
+               clcdfb_disable(fb);
+       } else {
+               clcdfb_enable(fb, fb->clcd_cntl);
+       }
+       return 0;
+}
+
+static struct fb_ops clcdfb_ops = {
+       .owner          = THIS_MODULE,
+       .fb_check_var   = clcdfb_check_var,
+       .fb_set_par     = clcdfb_set_par,
+       .fb_setcolreg   = clcdfb_setcolreg,
+       .fb_blank       = clcdfb_blank,
+       .fb_fillrect    = cfb_fillrect,
+       .fb_copyarea    = cfb_copyarea,
+       .fb_imageblit   = cfb_imageblit,
+       .fb_cursor      = soft_cursor,
+};
+
+static int clcdfb_register(struct clcd_fb *fb)
+{
+       int ret;
+
+       fb->clk = clk_get(&fb->dev->dev, "CLCDCLK");
+       if (IS_ERR(fb->clk)) {
+               ret = PTR_ERR(fb->clk);
+               goto out;
+       }
+
+       ret = clk_use(fb->clk);
+       if (ret)
+               goto free_clk;
+
+       fb->fb.fix.mmio_start   = fb->dev->res.start;
+       fb->fb.fix.mmio_len     = SZ_4K;
+
+       fb->regs = ioremap(fb->fb.fix.mmio_start, fb->fb.fix.mmio_len);
+       if (!fb->regs) {
+               printk(KERN_ERR "CLCD: unable to remap registers\n");
+               ret = -ENOMEM;
+               goto unuse_clk;
+       }
+
+       fb->fb.fbops            = &clcdfb_ops;
+       fb->fb.flags            = FBINFO_FLAG_DEFAULT;
+       fb->fb.pseudo_palette   = fb->cmap;
+
+       strncpy(fb->fb.fix.id, clcd_name, sizeof(fb->fb.fix.id));
+       fb->fb.fix.type         = FB_TYPE_PACKED_PIXELS;
+       fb->fb.fix.type_aux     = 0;
+       fb->fb.fix.xpanstep     = 0;
+       fb->fb.fix.ypanstep     = 0;
+       fb->fb.fix.ywrapstep    = 0;
+       fb->fb.fix.accel        = FB_ACCEL_NONE;
+
+       fb->fb.var.xres         = fb->panel->mode.xres;
+       fb->fb.var.yres         = fb->panel->mode.yres;
+       fb->fb.var.xres_virtual = fb->panel->mode.xres;
+       fb->fb.var.yres_virtual = fb->panel->mode.yres;
+       fb->fb.var.bits_per_pixel = fb->panel->bpp;
+       fb->fb.var.grayscale    = fb->panel->grayscale;
+       fb->fb.var.pixclock     = fb->panel->mode.pixclock;
+       fb->fb.var.left_margin  = fb->panel->mode.left_margin;
+       fb->fb.var.right_margin = fb->panel->mode.right_margin;
+       fb->fb.var.upper_margin = fb->panel->mode.upper_margin;
+       fb->fb.var.lower_margin = fb->panel->mode.lower_margin;
+       fb->fb.var.hsync_len    = fb->panel->mode.hsync_len;
+       fb->fb.var.vsync_len    = fb->panel->mode.vsync_len;
+       fb->fb.var.sync         = fb->panel->mode.sync;
+       fb->fb.var.vmode        = fb->panel->mode.vmode;
+       fb->fb.var.activate     = FB_ACTIVATE_NOW;
+       fb->fb.var.nonstd       = 0;
+       fb->fb.var.height       = fb->panel->height;
+       fb->fb.var.width        = fb->panel->width;
+       fb->fb.var.accel_flags  = 0;
+
+       fb->fb.monspecs.hfmin   = 0;
+       fb->fb.monspecs.hfmax   = 100000;
+       fb->fb.monspecs.vfmin   = 0;
+       fb->fb.monspecs.vfmax   = 400;
+       fb->fb.monspecs.dclkmin = 1000000;
+       fb->fb.monspecs.dclkmax = 100000000;
+
+       /*
+        * Make sure that the bitfields are set appropriately.
+        */
+       clcdfb_set_bitfields(fb, &fb->fb.var);
+
+       /*
+        * Allocate colourmap.
+        */
+       fb_alloc_cmap(&fb->fb.cmap, 256, 0);
+
+       /*
+        * Ensure interrupts are disabled.
+        */
+       writel(0, fb->regs + CLCD_IENB);
+
+       fb_set_var(&fb->fb, &fb->fb.var);
+
+        printk(KERN_INFO "CLCD: %s hardware, %s display\n",
+               fb->board->name, fb->panel->mode.name);
+
+       ret = register_framebuffer(&fb->fb);
+       if (ret == 0)
+               goto out;
+
+       printk(KERN_ERR "CLCD: cannot register framebuffer (%d)\n", ret);
+
+       iounmap(fb->regs);
+ unuse_clk:
+       clk_unuse(fb->clk);
+ free_clk:
+       clk_put(fb->clk);
+ out:
+       return ret;
+}
+
+static int clcdfb_probe(struct amba_device *dev, void *id)
+{
+       struct clcd_board *board = dev->dev.platform_data;
+       struct clcd_fb *fb;
+       int ret;
+
+       if (!board)
+               return -EINVAL;
+
+       ret = amba_request_regions(dev, NULL);
+       if (ret) {
+               printk(KERN_ERR "CLCD: unable to reserve regs region\n");
+               goto out;
+       }
+
+       fb = (struct clcd_fb *) kmalloc(sizeof(struct clcd_fb), GFP_KERNEL);
+       if (!fb) {
+               printk(KERN_INFO "CLCD: could not allocate new clcd_fb struct\n");
+               ret = -ENOMEM;
+               goto free_region;
+       }
+       memset(fb, 0, sizeof(struct clcd_fb));
+
+       fb->dev = dev;
+       fb->board = board;
+
+       ret = fb->board->setup(fb);
+       if (ret)
+               goto free_fb;
+
+       ret = clcdfb_register(fb); 
+       if (ret == 0) {
+               amba_set_drvdata(dev, fb);
+               goto out;
+       }
+
+       fb->board->remove(fb);
+ free_fb:
+       kfree(fb);
+ free_region:
+       amba_release_regions(dev);
+ out:
+       return ret;
+}
+
+static int clcdfb_remove(struct amba_device *dev)
+{
+       struct clcd_fb *fb = amba_get_drvdata(dev);
+
+       amba_set_drvdata(dev, NULL);
+
+       clcdfb_disable(fb);
+       unregister_framebuffer(&fb->fb);
+       iounmap(fb->regs);
+       clk_unuse(fb->clk);
+       clk_put(fb->clk);
+
+       fb->board->remove(fb);
+
+       kfree(fb);
+
+       amba_release_regions(dev);
+
+       return 0;
+}
+
+static struct amba_id clcdfb_id_table[] = {
+       {
+               .id     = 0x00041110,
+               .mask   = 0x000fffff,
+       },
+       { 0, 0 },
+};
+
+static struct amba_driver clcd_driver = {
+       .drv            = {
+               .name   = "clcd-pl110",
+       },
+       .probe          = clcdfb_probe,
+       .remove         = clcdfb_remove,
+       .id_table       = clcdfb_id_table,
+};
+
+int __init amba_clcdfb_init(void)
+{
+       return amba_driver_register(&clcd_driver);
+}
+
+#ifdef MODULE
+module_init(amba_clcdfb_init);
+#endif
+
+static void __exit amba_clcdfb_exit(void)
+{
+       amba_driver_unregister(&clcd_driver);
+}
+
+module_exit(amba_clcdfb_exit);
+
+MODULE_DESCRIPTION("ARM PrimeCell PL110 CLCD core driver");
+MODULE_LICENSE("GPL");
index 2a55e5dbf326b5a00a165db1258f5780c434f9a7..554654ea39634cd5ac43661fa661d6445a5631a6 100644 (file)
@@ -56,6 +56,7 @@
 
 extern int acornfb_init(void);
 extern int acornfb_setup(char*);
+extern int amba_clcdfb_init(void);
 extern int amifb_init(void);
 extern int amifb_setup(char*);
 extern int atafb_init(void);
@@ -198,6 +199,9 @@ static struct {
 #ifdef CONFIG_FB_CYBER2000
        { "cyber2000fb", cyber2000fb_init, cyber2000fb_setup },
 #endif
+#ifdef CONFIG_FB_ARMCLCD
+       { "ambaclcdfb", amba_clcdfb_init, NULL },
+#endif
 #ifdef CONFIG_FB_PM2
        { "pm2fb", pm2fb_init, pm2fb_setup },
 #endif
diff --git a/include/asm-arm/hardware/amba_clcd.h b/include/asm-arm/hardware/amba_clcd.h
new file mode 100644 (file)
index 0000000..65436fe
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * linux/include/asm-arm/hardware/amba_clcd.h -- Integrator LCD panel.
+ *
+ * David A Rusling
+ *
+ * Copyright (C) 2001 ARM Limited
+ *
+ * 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/config.h>
+#include <linux/fb.h>
+
+/*
+ * CLCD Controller Internal Register addresses
+ */
+#define CLCD_TIM0              0x00000000
+#define CLCD_TIM1              0x00000004
+#define CLCD_TIM2              0x00000008
+#define CLCD_TIM3              0x0000000c
+#define CLCD_UBAS              0x00000010
+#define CLCD_LBAS              0x00000014
+
+#ifndef CONFIG_ARCH_VERSATILE_PB
+#define CLCD_IENB              0x00000018
+#define CLCD_CNTL              0x0000001c
+#else
+/*
+ * Someone rearranged these two registers on the Versatile
+ * platform...
+ */
+#define CLCD_IENB              0x0000001c
+#define CLCD_CNTL              0x00000018
+#endif
+
+#define CLCD_STAT              0x00000020
+#define CLCD_INTR              0x00000024
+#define CLCD_UCUR              0x00000028
+#define CLCD_LCUR              0x0000002C
+#define CLCD_PALL              0x00000200
+#define CLCD_PALETTE           0x00000200
+
+#define TIM2_CLKSEL            (1 << 5)
+#define TIM2_IVS               (1 << 11)
+#define TIM2_IHS               (1 << 12)
+#define TIM2_IPC               (1 << 13)
+#define TIM2_IOE               (1 << 14)
+#define TIM2_BCD               (1 << 26)
+
+#define CNTL_LCDEN             (1 << 0)
+#define CNTL_LCDBPP1           (0 << 1)
+#define CNTL_LCDBPP2           (1 << 1)
+#define CNTL_LCDBPP4           (2 << 1)
+#define CNTL_LCDBPP8           (3 << 1)
+#define CNTL_LCDBPP16          (4 << 1)
+#define CNTL_LCDBPP24          (5 << 1)
+#define CNTL_LCDBW             (1 << 4)
+#define CNTL_LCDTFT            (1 << 5)
+#define CNTL_LCDMONO8          (1 << 6)
+#define CNTL_LCDDUAL           (1 << 7)
+#define CNTL_BGR               (1 << 8)
+#define CNTL_BEBO              (1 << 9)
+#define CNTL_BEPO              (1 << 10)
+#define CNTL_LCDPWR            (1 << 11)
+#define CNTL_LCDVCOMP(x)       ((x) << 12)
+#define CNTL_LDMAFIFOTIME      (1 << 15)
+#define CNTL_WATERMARK         (1 << 16)
+
+struct clcd_panel {
+       struct fb_videomode     mode;
+       signed short            width;  /* width in mm */
+       signed short            height; /* height in mm */
+       u32                     tim2;
+       u32                     tim3;
+       u32                     cntl;
+       unsigned int            bpp:8,
+                               fixedtimings:1,
+                               grayscale:1;
+       unsigned int            connector;
+};
+
+struct clcd_regs {
+       u32                     tim0;
+       u32                     tim1;
+       u32                     tim2;
+       u32                     tim3;
+       u32                     cntl;
+       unsigned long           pixclock;
+};
+
+struct clcd_fb;
+
+/*
+ * the board-type specific routines
+ */
+struct clcd_board {
+       const char *name;
+
+       /*
+        * Optional.  Check whether the var structure is acceptable
+        * for this display.
+        */
+       int     (*check)(struct clcd_fb *fb, struct fb_var_screeninfo *var);
+
+       /*
+        * Compulsary.  Decode fb->fb.var into regs->*.  In the case of
+        * fixed timing, set regs->* to the register values required.
+        */
+       void    (*decode)(struct clcd_fb *fb, struct clcd_regs *regs);
+
+       /*
+        * Optional.  Disable any extra display hardware.
+        */
+       void    (*disable)(struct clcd_fb *);
+
+       /*
+        * Optional.  Enable any extra display hardware.
+        */
+       void    (*enable)(struct clcd_fb *);
+
+       /*
+        * Setup platform specific parts of CLCD driver
+        */
+       int     (*setup)(struct clcd_fb *);
+
+       /*
+        * Remove platform specific parts of CLCD driver
+        */
+       void    (*remove)(struct clcd_fb *);
+};
+
+struct amba_device;
+struct clk;
+
+/* this data structure describes each frame buffer device we find */
+struct clcd_fb {
+       struct fb_info          fb;
+       struct amba_device      *dev;
+       struct clk              *clk;
+       struct clcd_panel       *panel;
+       struct clcd_board       *board;
+       void                    *board_data;
+       void                    *regs;
+       u32                     clcd_cntl;
+       u32                     cmap[16];
+};
+
+static inline void clcdfb_decode(struct clcd_fb *fb, struct clcd_regs *regs)
+{
+       u32 val;
+
+       /*
+        * Program the CLCD controller registers and start the CLCD
+        */
+       val = ((fb->fb.var.xres / 16) - 1) << 2;
+       val |= (fb->fb.var.hsync_len - 1) << 8;
+       val |= (fb->fb.var.right_margin - 1) << 16;
+       val |= (fb->fb.var.left_margin - 1) << 24;
+       regs->tim0 = val;
+
+       val = fb->fb.var.yres - 1;
+       val |= (fb->fb.var.vsync_len - 1) << 10;
+       val |= fb->fb.var.lower_margin << 16;
+       val |= fb->fb.var.upper_margin << 24;
+       regs->tim1 = val;
+
+       val = fb->panel->tim2;
+       val |= fb->fb.var.sync & FB_SYNC_HOR_HIGH_ACT  ? 0 : TIM2_IHS;
+       val |= fb->fb.var.sync & FB_SYNC_VERT_HIGH_ACT ? 0 : TIM2_IVS;
+
+       if (fb->panel->cntl & CNTL_LCDTFT)
+               val |= (fb->fb.var.xres_virtual - 1) << 16;
+       else if (fb->panel->cntl & CNTL_LCDBW)
+               printk("what value for CPL for stnmono panels?");
+       else
+               val |= ((fb->fb.var.xres_virtual * 8 / 3) - 1) << 16;
+       regs->tim2 = val;
+
+       regs->tim3 = fb->panel->tim3;
+
+       val = fb->panel->cntl;
+       if (fb->fb.var.grayscale)
+               val |= CNTL_LCDBW;
+
+       switch (fb->fb.var.bits_per_pixel) {
+       case 1:
+               val |= CNTL_LCDBPP1;
+               break;
+       case 2:
+               val |= CNTL_LCDBPP2;
+               break;
+       case 4:
+               val |= CNTL_LCDBPP4;
+               break;
+       case 8:
+               val |= CNTL_LCDBPP8;
+               break;
+       case 16:
+               val |= CNTL_LCDBPP16;
+               break;
+       case 24:
+               val |= CNTL_LCDBPP24;
+               break;
+       }
+
+       regs->cntl = val;
+       regs->pixclock = fb->fb.var.pixclock;
+}
+
+static inline int clcdfb_check(struct clcd_fb *fb, struct fb_var_screeninfo *var)
+{
+       var->xres_virtual = var->xres = (var->xres + 7) & ~7;
+       var->yres_virtual = var->yres;
+
+#define CHECK(e,l,h) (var->e < l || var->e > h)
+       if (CHECK(right_margin, (5+1), 256) ||  /* back porch */
+           CHECK(left_margin, (5+1), 256) ||   /* front porch */
+           CHECK(hsync_len, (5+1), 256) ||
+           var->xres > 4096 ||
+           var->lower_margin > 255 ||          /* back porch */
+           var->upper_margin > 255 ||          /* front porch */
+           var->vsync_len > 32 ||
+           var->yres > 1024)
+               return -EINVAL;
+#undef CHECK
+
+       /* single panel mode: PCD = max(PCD, 1) */
+       /* dual panel mode: PCD = max(PCD, 5) */
+
+       /*
+        * You can't change the grayscale setting, and
+        * we can only do non-interlaced video.
+        */
+       if (var->grayscale != fb->fb.var.grayscale ||
+           (var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED)
+               return -EINVAL;
+
+#define CHECK(e) (var->e != fb->fb.var.e)
+       if (fb->panel->fixedtimings &&
+           (CHECK(xres)                ||
+            CHECK(yres)                ||
+            CHECK(bits_per_pixel)      ||
+            CHECK(pixclock)            ||
+            CHECK(left_margin)         ||
+            CHECK(right_margin)        ||
+            CHECK(upper_margin)        ||
+            CHECK(lower_margin)        ||
+            CHECK(hsync_len)           ||
+            CHECK(vsync_len)           ||
+            CHECK(sync)))
+               return -EINVAL;
+#undef CHECK
+
+       var->nonstd = 0;
+       var->accel_flags = 0;
+
+       return 0;
+}