/* linux/drivers/video/s3c-fb.c
  *
  * Copyright 2008 Openmoko Inc.
- * Copyright 2008 Simtec Electronics
+ * Copyright 2008-2010 Simtec Electronics
  *      Ben Dooks <ben@simtec.co.uk>
  *      http://armlinux.simtec.co.uk/
  *
  * output timings and as the control for the output power-down state.
 */
 
-/* note, some of the functions that get called are derived from including
- * <mach/regs-fb.h> as they are specific to the architecture that the code
- * is being built for.
+/* note, the previous use of <mach/regs-fb.h> to get platform specific data
+ * has been replaced by using the platform device name to pick the correct
+ * configuration data for the system.
 */
 
 #ifdef CONFIG_FB_S3C_DEBUG_REGWRITE
 
 struct s3c_fb;
 
+#define VALID_BPP(x) (1 << ((x) - 1))
+
+/**
+ * struct s3c_fb_variant - fb variant information
+ * @nr_windows: The number of windows.
+ * @palette: Address of palette memory, or 0 if none.
+ */
+struct s3c_fb_variant {
+       unsigned short  nr_windows;
+       unsigned short  palette[S3C_FB_MAX_WIN];
+};
+
+/**
+ * struct s3c_fb_win_variant
+ * @has_osd_c: Set if has OSD C register.
+ * @has_osd_d: Set if has OSD D register.
+ * @palette_sz: Size of palette in entries.
+ * @palette_16bpp: Set if palette is 16bits wide.
+ * @valid_bpp: 1 bit per BPP setting to show valid bits-per-pixel.
+ *
+ * valid_bpp bit x is set if (x+1)BPP is supported.
+ */
+struct s3c_fb_win_variant {
+       unsigned int    has_osd_c:1;
+       unsigned int    has_osd_d:1;
+       unsigned int    palette_16bpp:1;
+       unsigned short  palette_sz;
+       u32             valid_bpp;
+};
+
+/**
+ * struct s3c_fb_driverdata - per-device type driver data for init time.
+ * @variant: The variant information for this driver.
+ * @win: The window information for each window.
+ */
+struct s3c_fb_driverdata {
+       struct s3c_fb_variant   variant;
+       struct s3c_fb_win_variant *win[S3C_FB_MAX_WIN];
+};
+
 /**
  * struct s3c_fb_win - per window private data for each framebuffer.
  * @windata: The platform data supplied for the window configuration.
  * @parent: The hardware that this window is part of.
  * @fbinfo: Pointer pack to the framebuffer info for this window.
+ * @varint: The variant information for this window.
  * @palette_buffer: Buffer/cache to hold palette entries.
  * @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
  * @index: The window number of this window.
        struct s3c_fb           *parent;
        struct fb_info          *fbinfo;
        struct s3c_fb_palette    palette;
+       struct s3c_fb_win_variant variant;
 
        u32                     *palette_buffer;
        u32                      pseudo_palette[16];
  * @regs_res: The resource we claimed for the IO registers.
  * @bus_clk: The clk (hclk) feeding our interface and possibly pixclk.
  * @regs: The mapped hardware registers.
+ * @variant: Variant information for this hardware.
  * @enabled: A bitmask of enabled hardware windows.
  * @pdata: The platform configuration data passed with the device.
  * @windows: The hardware windows that have been claimed.
        struct resource         *regs_res;
        struct clk              *bus_clk;
        void __iomem            *regs;
+       struct s3c_fb_variant    variant;
 
        unsigned char            enabled;
 
 };
 
 /**
- * s3c_fb_win_has_palette() - determine if a mode has a palette
- * @win: The window number being queried.
- * @bpp: The number of bits per pixel to test.
- *
- * Work out if the given window supports palletised data at the specified bpp.
+ * s3c_fb_validate_win_bpp - validate the bits-per-pixel for this mode.
+ * @win: The device window.
+ * @bpp: The bit depth.
  */
-static int s3c_fb_win_has_palette(unsigned int win, unsigned int bpp)
+static bool s3c_fb_validate_win_bpp(struct s3c_fb_win *win, unsigned int bpp)
 {
-       return s3c_fb_win_pal_size(win) <= (1 << bpp);
+       return win->variant.valid_bpp & VALID_BPP(bpp);
 }
 
 /**
        var->xres_virtual = max((unsigned int)windata->virtual_x, var->xres);
        var->yres_virtual = max((unsigned int)windata->virtual_y, var->yres);
 
-       if (!s3c_fb_validate_win_bpp(win->index, var->bits_per_pixel)) {
+       if (!s3c_fb_validate_win_bpp(win, var->bits_per_pixel)) {
                dev_dbg(sfb->dev, "win %d: unsupported bpp %d\n",
                        win->index, var->bits_per_pixel);
                return -EINVAL;
        case 2:
        case 4:
        case 8:
-               if (!s3c_fb_win_has_palette(win->index, var->bits_per_pixel)) {
+               if (sfb->variant.palette[win->index] != 0) {
                        /* non palletised, A:1,R:2,G:3,B:2 mode */
                        var->red.offset         = 4;
                        var->green.offset       = 2;
                info->fix.visual = FB_VISUAL_TRUECOLOR;
                break;
        case 8:
-               if (s3c_fb_win_has_palette(win_no, 8))
+               if (win->variant.palette_sz >= 256)
                        info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
                else
                        info->fix.visual = FB_VISUAL_TRUECOLOR;
                VIDISD14C_ALPHA1_G(0xf) |
                VIDISD14C_ALPHA1_B(0xf);
 
-       if (s3c_fb_has_osd_d(win_no)) {
+       if (win->variant.has_osd_d) {
                writel(data, regs + VIDOSD_D(win_no));
                writel(osdc_data, regs + VIDOSD_C(win_no));
        } else
        void __iomem *palreg;
        u32 palcon;
 
-       palreg = sfb->regs + s3c_fb_pal_reg(win->index, reg);
+       palreg = sfb->regs + sfb->variant.palette[win->index];
 
        dev_dbg(sfb->dev, "%s: win %d, reg %d (%p): %08x\n",
                __func__, win->index, reg, palreg, value);
        palcon = readl(sfb->regs + WPALCON);
        writel(palcon | WPALCON_PAL_UPDATE, sfb->regs + WPALCON);
 
-       if (s3c_fb_pal_is16(win->index))
-               writew(value, palreg);
+       if (win->variant.palette_16bpp)
+               writew(value, palreg + (reg * 2));
        else
-               writel(value, palreg);
+               writel(value, palreg + (reg * 4));
 
        writel(palcon, sfb->regs + WPALCON);
 }
                break;
 
        case FB_VISUAL_PSEUDOCOLOR:
-               if (regno < s3c_fb_win_pal_size(win->index)) {
+               if (regno < win->variant.palette_sz) {
                        val  = chan_to_field(red, &win->palette.r);
                        val |= chan_to_field(green, &win->palette.g);
                        val |= chan_to_field(blue, &win->palette.b);
 /**
  * s3c_fb_probe_win() - register an hardware window
  * @sfb: The base resources for the hardware
+ * @variant: The variant information for this window.
  * @res: Pointer to where to place the resultant window.
  *
  * Allocate and do the basic initialisation for one of the hardware's graphics
  * windows.
  */
 static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
+                                     struct s3c_fb_win_variant *variant,
                                      struct s3c_fb_win **res)
 {
        struct fb_var_screeninfo *var;
 
        dev_dbg(sfb->dev, "probing window %d\n", win_no);
 
-       palette_size = s3c_fb_win_pal_size(win_no);
+       palette_size = variant->palette_sz * 4;
 
        fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
                                   palette_size * sizeof(u32), sfb->dev);
 
        win = fbinfo->par;
        var = &fbinfo->var;
+       win->variant = *variant;
        win->fbinfo = fbinfo;
        win->parent = sfb;
        win->windata = windata;
 
        /* create initial colour map */
 
-       ret = fb_alloc_cmap(&fbinfo->cmap, s3c_fb_win_pal_size(win_no), 1);
+       ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
        if (ret == 0)
                fb_set_cmap(&fbinfo->cmap, fbinfo);
        else
 
 static int __devinit s3c_fb_probe(struct platform_device *pdev)
 {
+       struct s3c_fb_driverdata *fbdrv;
        struct device *dev = &pdev->dev;
        struct s3c_fb_platdata *pd;
        struct s3c_fb *sfb;
        int win;
        int ret = 0;
 
+       fbdrv = (struct s3c_fb_driverdata *)platform_get_device_id(pdev)->driver_data;
+
+       if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {
+               dev_err(dev, "too many windows, cannot attach\n");
+               return -EINVAL;
+       }
+
        pd = pdev->dev.platform_data;
        if (!pd) {
                dev_err(dev, "no platform data specified\n");
 
        sfb->dev = dev;
        sfb->pdata = pd;
+       sfb->variant = fbdrv->variant;
 
        sfb->bus_clk = clk_get(dev, "lcd");
        if (IS_ERR(sfb->bus_clk)) {
 
        /* zero all windows before we do anything */
 
-       for (win = 0; win < S3C_FB_MAX_WIN; win++)
+       for (win = 0; win < fbdrv->variant.nr_windows; win++)
                s3c_fb_clear_win(sfb, win);
 
        /* initialise colour key controls */
-       for (win = 0; win < (S3C_FB_MAX_WIN - 1); win++) {
+       for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
                writel(0xffffff, sfb->regs + WxKEYCONy(win, 0));
                writel(0xffffff, sfb->regs + WxKEYCONy(win, 1));
        }
 
        /* we have the register setup, start allocating framebuffers */
 
-       for (win = 0; win < S3C_FB_MAX_WIN; win++) {
+       for (win = 0; win < fbdrv->variant.nr_windows; win++) {
                if (!pd->win[win])
                        continue;
 
-               ret = s3c_fb_probe_win(sfb, win, &sfb->windows[win]);
+               ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
+                                      &sfb->windows[win]);
                if (ret < 0) {
                        dev_err(dev, "failed to create window %d\n", win);
                        for (; win >= 0; win--)
        writel(pd->vidcon1, sfb->regs + VIDCON1);
 
        /* zero all windows before we do anything */
-       for (win_no = 0; win_no < S3C_FB_MAX_WIN; win_no++)
+       for (win_no = 0; win_no < sfb->variant.nr_windows; win_no++)
                s3c_fb_clear_win(sfb, win_no);
 
-       for (win_no = 0; win_no < S3C_FB_MAX_WIN - 1; win_no++) {
+       for (win_no = 0; win_no < sfb->variant.nr_windows - 1; win_no++) {
                writel(0xffffff, sfb->regs + WxKEYCONy(win_no, 1));
                writel(0xffffff, sfb->regs + WxKEYCONy(win_no, 1));
        }
 #define s3c_fb_resume  NULL
 #endif
 
+
+#define VALID_BPP124 (VALID_BPP(1) | VALID_BPP(2) | VALID_BPP(4))
+#define VALID_BPP1248 (VALID_BPP124 | VALID_BPP(8))
+
+static struct s3c_fb_win_variant s3c_fb_data_64xx_wins[] __devinitdata = {
+       [0] = {
+               .has_osd_c      = 1,
+               .palette_sz     = 256,
+               .valid_bpp      = VALID_BPP1248 | VALID_BPP(16) | VALID_BPP(24),
+       },
+       [1] = {
+               .has_osd_c      = 1,
+               .has_osd_d      = 1,
+               .palette_sz     = 256,
+               .valid_bpp      = (VALID_BPP1248 | VALID_BPP(16) |
+                                  VALID_BPP(18) | VALID_BPP(19) |
+                                  VALID_BPP(24) | VALID_BPP(25)),
+       },
+       [2] = {
+               .has_osd_c      = 1,
+               .has_osd_d      = 1,
+               .palette_sz     = 16,
+               .palette_16bpp  = 1,
+               .valid_bpp      = (VALID_BPP1248 | VALID_BPP(16) |
+                                  VALID_BPP(18) | VALID_BPP(19) |
+                                  VALID_BPP(24) | VALID_BPP(25)),
+       },
+       [3] = {
+               .has_osd_c      = 1,
+               .has_osd_d      = 1,
+               .palette_sz     = 16,
+               .palette_16bpp  = 1,
+               .valid_bpp      = (VALID_BPP124  | VALID_BPP(16) |
+                                  VALID_BPP(18) | VALID_BPP(19) |
+                                  VALID_BPP(24) | VALID_BPP(25)),
+       },
+       [4] = {
+               .has_osd_c      = 1,
+               .palette_sz     = 4,
+               .palette_16bpp  = 1,
+               .valid_bpp      = (VALID_BPP(1) | VALID_BPP(2) |
+                                  VALID_BPP(16) | VALID_BPP(18) |
+                                  VALID_BPP(24) | VALID_BPP(25)),
+       },
+};
+
+static struct s3c_fb_driverdata s3c_fb_data_64xx __devinitdata = {
+       .variant = {
+               .nr_windows     = 5,
+
+               .palette = {
+                       [0] = 0x400,
+                       [1] = 0x800,
+                       [2] = 0x300,
+                       [3] = 0x320,
+                       [4] = 0x340,
+               },
+       },
+       .win[0] = &s3c_fb_data_64xx_wins[0],
+       .win[1] = &s3c_fb_data_64xx_wins[1],
+       .win[2] = &s3c_fb_data_64xx_wins[2],
+       .win[3] = &s3c_fb_data_64xx_wins[3],
+       .win[4] = &s3c_fb_data_64xx_wins[4],
+};
+
+static struct s3c_fb_driverdata s3c_fb_data_s5p __devinitdata = {
+       .variant = {
+               .nr_windows     = 5,
+
+               .palette = {
+                       [0] = 0x2400,
+                       [1] = 0x2800,
+                       [2] = 0x2c00,
+                       [3] = 0x3000,
+                       [4] = 0x3400,
+               },
+       },
+       .win[0] = &s3c_fb_data_64xx_wins[0],
+       .win[1] = &s3c_fb_data_64xx_wins[1],
+       .win[2] = &s3c_fb_data_64xx_wins[2],
+       .win[3] = &s3c_fb_data_64xx_wins[3],
+       .win[4] = &s3c_fb_data_64xx_wins[4],
+};
+
+static struct platform_device_id s3c_fb_driver_ids[] = {
+       {
+               .name           = "s3c-fb",
+               .driver_data    = (unsigned long)&s3c_fb_data_64xx,
+       }, {
+               .name           = "s5p-fb",
+               .driver_data    = (unsigned long)&s3c_fb_data_s5p,
+       },
+       {},
+};
+MODULE_DEVICE_TABLE(platform, s3c_fb_driver_ids);
+
 static struct platform_driver s3c_fb_driver = {
        .probe          = s3c_fb_probe,
        .remove         = __devexit_p(s3c_fb_remove),
        .suspend        = s3c_fb_suspend,
        .resume         = s3c_fb_resume,
+       .id_table       = s3c_fb_driver_ids,
        .driver         = {
                .name   = "s3c-fb",
                .owner  = THIS_MODULE,