linux/drivers/video/fbdev/nuc900fb.c
<<
>>
Prefs
   1/*
   2 *
   3 * Copyright (c) 2009 Nuvoton technology corporation
   4 * All rights reserved.
   5 *
   6 * This program is free software; you can redistribute it and/or modify
   7 * it under the terms of the GNU General Public License as published by
   8 * the Free Software Foundation; either version 2 of the License, or
   9 * (at your option) any later version.
  10 *
  11 *  Description:
  12 *    Nuvoton LCD Controller Driver
  13 *  Author:
  14 *    Wang Qiang (rurality.linux@gmail.com) 2009/12/11
  15 */
  16#include <linux/module.h>
  17#include <linux/kernel.h>
  18#include <linux/err.h>
  19#include <linux/errno.h>
  20#include <linux/string.h>
  21#include <linux/mm.h>
  22#include <linux/tty.h>
  23#include <linux/slab.h>
  24#include <linux/delay.h>
  25#include <linux/fb.h>
  26#include <linux/init.h>
  27#include <linux/dma-mapping.h>
  28#include <linux/interrupt.h>
  29#include <linux/workqueue.h>
  30#include <linux/wait.h>
  31#include <linux/platform_device.h>
  32#include <linux/clk.h>
  33#include <linux/cpufreq.h>
  34#include <linux/io.h>
  35#include <linux/pm.h>
  36#include <linux/device.h>
  37
  38#include <mach/map.h>
  39#include <mach/regs-clock.h>
  40#include <mach/regs-ldm.h>
  41#include <linux/platform_data/video-nuc900fb.h>
  42
  43#include "nuc900fb.h"
  44
  45
  46/*
  47 *  Initialize the nuc900 video (dual) buffer address
  48 */
  49static void nuc900fb_set_lcdaddr(struct fb_info *info)
  50{
  51        struct nuc900fb_info *fbi = info->par;
  52        void __iomem *regs = fbi->io;
  53        unsigned long vbaddr1, vbaddr2;
  54
  55        vbaddr1  = info->fix.smem_start;
  56        vbaddr2  = info->fix.smem_start;
  57        vbaddr2 += info->fix.line_length * info->var.yres;
  58
  59        /* set frambuffer start phy addr*/
  60        writel(vbaddr1, regs + REG_LCM_VA_BADDR0);
  61        writel(vbaddr2, regs + REG_LCM_VA_BADDR1);
  62
  63        writel(fbi->regs.lcd_va_fbctrl, regs + REG_LCM_VA_FBCTRL);
  64        writel(fbi->regs.lcd_va_scale, regs + REG_LCM_VA_SCALE);
  65}
  66
  67/*
  68 *      calculate divider for lcd div
  69 */
  70static unsigned int nuc900fb_calc_pixclk(struct nuc900fb_info *fbi,
  71                                         unsigned long pixclk)
  72{
  73        unsigned long clk = fbi->clk_rate;
  74        unsigned long long div;
  75
  76        /* pixclk is in picseconds. our clock is in Hz*/
  77        /* div = (clk * pixclk)/10^12 */
  78        div = (unsigned long long)clk * pixclk;
  79        div >>= 12;
  80        do_div(div, 625 * 625UL * 625);
  81
  82        dev_dbg(fbi->dev, "pixclk %ld, divisor is %lld\n", pixclk, div);
  83
  84        return div;
  85}
  86
  87/*
  88 *      Check the video params of 'var'.
  89 */
  90static int nuc900fb_check_var(struct fb_var_screeninfo *var,
  91                               struct fb_info *info)
  92{
  93        struct nuc900fb_info *fbi = info->par;
  94        struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev);
  95        struct nuc900fb_display *display = NULL;
  96        struct nuc900fb_display *default_display = mach_info->displays +
  97                                                   mach_info->default_display;
  98        int i;
  99
 100        dev_dbg(fbi->dev, "check_var(var=%p, info=%p)\n", var, info);
 101
 102        /* validate x/y resolution */
 103        /* choose default mode if possible */
 104        if (var->xres == default_display->xres &&
 105            var->yres == default_display->yres &&
 106            var->bits_per_pixel == default_display->bpp)
 107                display = default_display;
 108        else
 109                for (i = 0; i < mach_info->num_displays; i++)
 110                        if (var->xres == mach_info->displays[i].xres &&
 111                            var->yres == mach_info->displays[i].yres &&
 112                            var->bits_per_pixel == mach_info->displays[i].bpp) {
 113                                display = mach_info->displays + i;
 114                                break;
 115                        }
 116
 117        if (display == NULL) {
 118                printk(KERN_ERR "wrong resolution or depth %dx%d at %d bit per pixel\n",
 119                        var->xres, var->yres, var->bits_per_pixel);
 120                return -EINVAL;
 121        }
 122
 123        /* it should be the same size as the display */
 124        var->xres_virtual       = display->xres;
 125        var->yres_virtual       = display->yres;
 126        var->height             = display->height;
 127        var->width              = display->width;
 128
 129        /* copy lcd settings */
 130        var->pixclock           = display->pixclock;
 131        var->left_margin        = display->left_margin;
 132        var->right_margin       = display->right_margin;
 133        var->upper_margin       = display->upper_margin;
 134        var->lower_margin       = display->lower_margin;
 135        var->vsync_len          = display->vsync_len;
 136        var->hsync_len          = display->hsync_len;
 137
 138        var->transp.offset      = 0;
 139        var->transp.length      = 0;
 140
 141        fbi->regs.lcd_dccs = display->dccs;
 142        fbi->regs.lcd_device_ctrl = display->devctl;
 143        fbi->regs.lcd_va_fbctrl = display->fbctrl;
 144        fbi->regs.lcd_va_scale = display->scale;
 145
 146        /* set R/G/B possions */
 147        switch (var->bits_per_pixel) {
 148        case 1:
 149        case 2:
 150        case 4:
 151        case 8:
 152        default:
 153                var->red.offset         = 0;
 154                var->red.length         = var->bits_per_pixel;
 155                var->green              = var->red;
 156                var->blue               = var->red;
 157                break;
 158        case 12:
 159                var->red.length         = 4;
 160                var->green.length       = 4;
 161                var->blue.length        = 4;
 162                var->red.offset         = 8;
 163                var->green.offset       = 4;
 164                var->blue.offset        = 0;
 165                break;
 166        case 16:
 167                var->red.length         = 5;
 168                var->green.length       = 6;
 169                var->blue.length        = 5;
 170                var->red.offset         = 11;
 171                var->green.offset       = 5;
 172                var->blue.offset        = 0;
 173                break;
 174        case 18:
 175                var->red.length         = 6;
 176                var->green.length       = 6;
 177                var->blue.length        = 6;
 178                var->red.offset         = 12;
 179                var->green.offset       = 6;
 180                var->blue.offset        = 0;
 181                break;
 182        case 32:
 183                var->red.length         = 8;
 184                var->green.length       = 8;
 185                var->blue.length        = 8;
 186                var->red.offset         = 16;
 187                var->green.offset       = 8;
 188                var->blue.offset        = 0;
 189                break;
 190        }
 191
 192        return 0;
 193}
 194
 195/*
 196 *      Calculate lcd register values from var setting & save into hw
 197 */
 198static void nuc900fb_calculate_lcd_regs(const struct fb_info *info,
 199                                        struct nuc900fb_hw *regs)
 200{
 201        const struct fb_var_screeninfo *var = &info->var;
 202        int vtt = var->height + var->upper_margin + var->lower_margin;
 203        int htt = var->width + var->left_margin + var->right_margin;
 204        int hsync = var->width + var->right_margin;
 205        int vsync = var->height + var->lower_margin;
 206
 207        regs->lcd_crtc_size = LCM_CRTC_SIZE_VTTVAL(vtt) |
 208                              LCM_CRTC_SIZE_HTTVAL(htt);
 209        regs->lcd_crtc_dend = LCM_CRTC_DEND_VDENDVAL(var->height) |
 210                              LCM_CRTC_DEND_HDENDVAL(var->width);
 211        regs->lcd_crtc_hr = LCM_CRTC_HR_EVAL(var->width + 5) |
 212                            LCM_CRTC_HR_SVAL(var->width + 1);
 213        regs->lcd_crtc_hsync = LCM_CRTC_HSYNC_EVAL(hsync + var->hsync_len) |
 214                               LCM_CRTC_HSYNC_SVAL(hsync);
 215        regs->lcd_crtc_vr = LCM_CRTC_VR_EVAL(vsync + var->vsync_len) |
 216                            LCM_CRTC_VR_SVAL(vsync);
 217
 218}
 219
 220/*
 221 *      Activate (set) the controller from the given framebuffer
 222 *      information
 223 */
 224static void nuc900fb_activate_var(struct fb_info *info)
 225{
 226        struct nuc900fb_info *fbi = info->par;
 227        void __iomem *regs = fbi->io;
 228        struct fb_var_screeninfo *var = &info->var;
 229        int clkdiv;
 230
 231        clkdiv = nuc900fb_calc_pixclk(fbi, var->pixclock) - 1;
 232        if (clkdiv < 0)
 233                clkdiv = 0;
 234
 235        nuc900fb_calculate_lcd_regs(info, &fbi->regs);
 236
 237        /* set the new lcd registers*/
 238
 239        dev_dbg(fbi->dev, "new lcd register set:\n");
 240        dev_dbg(fbi->dev, "dccs       = 0x%08x\n", fbi->regs.lcd_dccs);
 241        dev_dbg(fbi->dev, "dev_ctl    = 0x%08x\n", fbi->regs.lcd_device_ctrl);
 242        dev_dbg(fbi->dev, "crtc_size  = 0x%08x\n", fbi->regs.lcd_crtc_size);
 243        dev_dbg(fbi->dev, "crtc_dend  = 0x%08x\n", fbi->regs.lcd_crtc_dend);
 244        dev_dbg(fbi->dev, "crtc_hr    = 0x%08x\n", fbi->regs.lcd_crtc_hr);
 245        dev_dbg(fbi->dev, "crtc_hsync = 0x%08x\n", fbi->regs.lcd_crtc_hsync);
 246        dev_dbg(fbi->dev, "crtc_vr    = 0x%08x\n", fbi->regs.lcd_crtc_vr);
 247
 248        writel(fbi->regs.lcd_device_ctrl, regs + REG_LCM_DEV_CTRL);
 249        writel(fbi->regs.lcd_crtc_size, regs + REG_LCM_CRTC_SIZE);
 250        writel(fbi->regs.lcd_crtc_dend, regs + REG_LCM_CRTC_DEND);
 251        writel(fbi->regs.lcd_crtc_hr, regs + REG_LCM_CRTC_HR);
 252        writel(fbi->regs.lcd_crtc_hsync, regs + REG_LCM_CRTC_HSYNC);
 253        writel(fbi->regs.lcd_crtc_vr, regs + REG_LCM_CRTC_VR);
 254
 255        /* set lcd address pointers */
 256        nuc900fb_set_lcdaddr(info);
 257
 258        writel(fbi->regs.lcd_dccs, regs + REG_LCM_DCCS);
 259}
 260
 261/*
 262 *      Alters the hardware state.
 263 *
 264 */
 265static int nuc900fb_set_par(struct fb_info *info)
 266{
 267        struct fb_var_screeninfo *var = &info->var;
 268
 269        switch (var->bits_per_pixel) {
 270        case 32:
 271        case 24:
 272        case 18:
 273        case 16:
 274        case 12:
 275                info->fix.visual = FB_VISUAL_TRUECOLOR;
 276                break;
 277        case 1:
 278                info->fix.visual = FB_VISUAL_MONO01;
 279                break;
 280        default:
 281                info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
 282                break;
 283        }
 284
 285        info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8;
 286
 287        /* activate this new configuration */
 288        nuc900fb_activate_var(info);
 289        return 0;
 290}
 291
 292static inline unsigned int chan_to_field(unsigned int chan,
 293                                         struct fb_bitfield *bf)
 294{
 295        chan &= 0xffff;
 296        chan >>= 16 - bf->length;
 297        return chan << bf->offset;
 298}
 299
 300static int nuc900fb_setcolreg(unsigned regno,
 301                               unsigned red, unsigned green, unsigned blue,
 302                               unsigned transp, struct fb_info *info)
 303{
 304        unsigned int val;
 305
 306        switch (info->fix.visual) {
 307        case FB_VISUAL_TRUECOLOR:
 308                /* true-colour, use pseuo-palette */
 309                if (regno < 16) {
 310                        u32 *pal = info->pseudo_palette;
 311
 312                        val  = chan_to_field(red, &info->var.red);
 313                        val |= chan_to_field(green, &info->var.green);
 314                        val |= chan_to_field(blue, &info->var.blue);
 315                        pal[regno] = val;
 316                }
 317                break;
 318
 319        default:
 320                return 1;   /* unknown type */
 321        }
 322        return 0;
 323}
 324
 325/**
 326 *      nuc900fb_blank
 327 *
 328 */
 329static int nuc900fb_blank(int blank_mode, struct fb_info *info)
 330{
 331
 332        return 0;
 333}
 334
 335static struct fb_ops nuc900fb_ops = {
 336        .owner                  = THIS_MODULE,
 337        .fb_check_var           = nuc900fb_check_var,
 338        .fb_set_par             = nuc900fb_set_par,
 339        .fb_blank               = nuc900fb_blank,
 340        .fb_setcolreg           = nuc900fb_setcolreg,
 341        .fb_fillrect            = cfb_fillrect,
 342        .fb_copyarea            = cfb_copyarea,
 343        .fb_imageblit           = cfb_imageblit,
 344};
 345
 346
 347static inline void modify_gpio(void __iomem *reg,
 348                               unsigned long set, unsigned long mask)
 349{
 350        unsigned long tmp;
 351        tmp = readl(reg) & ~mask;
 352        writel(tmp | set, reg);
 353}
 354
 355/*
 356 * Initialise LCD-related registers
 357 */
 358static int nuc900fb_init_registers(struct fb_info *info)
 359{
 360        struct nuc900fb_info *fbi = info->par;
 361        struct nuc900fb_mach_info *mach_info = dev_get_platdata(fbi->dev);
 362        void __iomem *regs = fbi->io;
 363
 364        /*reset the display engine*/
 365        writel(0, regs + REG_LCM_DCCS);
 366        writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_ENG_RST,
 367               regs + REG_LCM_DCCS);
 368        ndelay(100);
 369        writel(readl(regs + REG_LCM_DCCS) & (~LCM_DCCS_ENG_RST),
 370               regs + REG_LCM_DCCS);
 371        ndelay(100);
 372
 373        writel(0, regs + REG_LCM_DEV_CTRL);
 374
 375        /* config gpio output */
 376        modify_gpio(W90X900_VA_GPIO + 0x54, mach_info->gpio_dir,
 377                    mach_info->gpio_dir_mask);
 378        modify_gpio(W90X900_VA_GPIO + 0x58, mach_info->gpio_data,
 379                    mach_info->gpio_data_mask);
 380
 381        return 0;
 382}
 383
 384
 385/*
 386 *    Alloc the SDRAM region of NUC900 for the frame buffer.
 387 *    The buffer should be a non-cached, non-buffered, memory region
 388 *    to allow palette and pixel writes without flushing the cache.
 389 */
 390static int nuc900fb_map_video_memory(struct fb_info *info)
 391{
 392        struct nuc900fb_info *fbi = info->par;
 393        dma_addr_t map_dma;
 394        unsigned long map_size = PAGE_ALIGN(info->fix.smem_len);
 395
 396        dev_dbg(fbi->dev, "nuc900fb_map_video_memory(fbi=%p) map_size %lu\n",
 397                fbi, map_size);
 398
 399        info->screen_base = dma_alloc_wc(fbi->dev, map_size, &map_dma,
 400                                         GFP_KERNEL);
 401
 402        if (!info->screen_base)
 403                return -ENOMEM;
 404
 405        memset(info->screen_base, 0x00, map_size);
 406        info->fix.smem_start = map_dma;
 407
 408        return 0;
 409}
 410
 411static inline void nuc900fb_unmap_video_memory(struct fb_info *info)
 412{
 413        struct nuc900fb_info *fbi = info->par;
 414        dma_free_wc(fbi->dev, PAGE_ALIGN(info->fix.smem_len),
 415                    info->screen_base, info->fix.smem_start);
 416}
 417
 418static irqreturn_t nuc900fb_irqhandler(int irq, void *dev_id)
 419{
 420        struct nuc900fb_info *fbi = dev_id;
 421        void __iomem *regs = fbi->io;
 422        void __iomem *irq_base = fbi->irq_base;
 423        unsigned long lcdirq = readl(regs + REG_LCM_INT_CS);
 424
 425        if (lcdirq & LCM_INT_CS_DISP_F_STATUS) {
 426                writel(readl(irq_base) | 1<<30, irq_base);
 427
 428                /* wait VA_EN low */
 429                if ((readl(regs + REG_LCM_DCCS) &
 430                    LCM_DCCS_SINGLE) == LCM_DCCS_SINGLE)
 431                        while ((readl(regs + REG_LCM_DCCS) &
 432                               LCM_DCCS_VA_EN) == LCM_DCCS_VA_EN)
 433                                ;
 434                /* display_out-enable */
 435                writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_DISP_OUT_EN,
 436                        regs + REG_LCM_DCCS);
 437                /* va-enable*/
 438                writel(readl(regs + REG_LCM_DCCS) | LCM_DCCS_VA_EN,
 439                        regs + REG_LCM_DCCS);
 440        } else if (lcdirq & LCM_INT_CS_UNDERRUN_INT) {
 441                writel(readl(irq_base) | LCM_INT_CS_UNDERRUN_INT, irq_base);
 442        } else if (lcdirq & LCM_INT_CS_BUS_ERROR_INT) {
 443                writel(readl(irq_base) | LCM_INT_CS_BUS_ERROR_INT, irq_base);
 444        }
 445
 446        return IRQ_HANDLED;
 447}
 448
 449#ifdef CONFIG_CPU_FREQ
 450
 451static int nuc900fb_cpufreq_transition(struct notifier_block *nb,
 452                                       unsigned long val, void *data)
 453{
 454        struct nuc900fb_info *info;
 455        struct fb_info *fbinfo;
 456        long delta_f;
 457        info = container_of(nb, struct nuc900fb_info, freq_transition);
 458        fbinfo = platform_get_drvdata(to_platform_device(info->dev));
 459
 460        delta_f = info->clk_rate - clk_get_rate(info->clk);
 461
 462        if ((val == CPUFREQ_POSTCHANGE && delta_f > 0) ||
 463           (val == CPUFREQ_PRECHANGE && delta_f < 0)) {
 464                info->clk_rate = clk_get_rate(info->clk);
 465                nuc900fb_activate_var(fbinfo);
 466        }
 467
 468        return 0;
 469}
 470
 471static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi)
 472{
 473        fbi->freq_transition.notifier_call = nuc900fb_cpufreq_transition;
 474        return cpufreq_register_notifier(&fbi->freq_transition,
 475                                  CPUFREQ_TRANSITION_NOTIFIER);
 476}
 477
 478static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *fbi)
 479{
 480        cpufreq_unregister_notifier(&fbi->freq_transition,
 481                                    CPUFREQ_TRANSITION_NOTIFIER);
 482}
 483#else
 484static inline int nuc900fb_cpufreq_transition(struct notifier_block *nb,
 485                                       unsigned long val, void *data)
 486{
 487        return 0;
 488}
 489
 490static inline int nuc900fb_cpufreq_register(struct nuc900fb_info *fbi)
 491{
 492        return 0;
 493}
 494
 495static inline void nuc900fb_cpufreq_deregister(struct nuc900fb_info *info)
 496{
 497}
 498#endif
 499
 500static char driver_name[] = "nuc900fb";
 501
 502static int nuc900fb_probe(struct platform_device *pdev)
 503{
 504        struct nuc900fb_info *fbi;
 505        struct nuc900fb_display *display;
 506        struct fb_info     *fbinfo;
 507        struct nuc900fb_mach_info *mach_info;
 508        struct resource *res;
 509        int ret;
 510        int irq;
 511        int i;
 512        int size;
 513
 514        dev_dbg(&pdev->dev, "devinit\n");
 515        mach_info = dev_get_platdata(&pdev->dev);
 516        if (mach_info == NULL) {
 517                dev_err(&pdev->dev,
 518                        "no platform data for lcd, cannot attach\n");
 519                return -EINVAL;
 520        }
 521
 522        if (mach_info->default_display > mach_info->num_displays) {
 523                dev_err(&pdev->dev,
 524                        "default display No. is %d but only %d displays \n",
 525                        mach_info->default_display, mach_info->num_displays);
 526                return -EINVAL;
 527        }
 528
 529
 530        display = mach_info->displays + mach_info->default_display;
 531
 532        irq = platform_get_irq(pdev, 0);
 533        if (irq < 0) {
 534                dev_err(&pdev->dev, "no irq for device\n");
 535                return -ENOENT;
 536        }
 537
 538        fbinfo = framebuffer_alloc(sizeof(struct nuc900fb_info), &pdev->dev);
 539        if (!fbinfo)
 540                return -ENOMEM;
 541
 542        platform_set_drvdata(pdev, fbinfo);
 543
 544        fbi = fbinfo->par;
 545        fbi->dev = &pdev->dev;
 546
 547#ifdef CONFIG_CPU_NUC950
 548        fbi->drv_type = LCDDRV_NUC950;
 549#endif
 550
 551        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 552
 553        size = resource_size(res);
 554        fbi->mem = request_mem_region(res->start, size, pdev->name);
 555        if (fbi->mem == NULL) {
 556                dev_err(&pdev->dev, "failed to alloc memory region\n");
 557                ret = -ENOENT;
 558                goto free_fb;
 559        }
 560
 561        fbi->io = ioremap(res->start, size);
 562        if (fbi->io == NULL) {
 563                dev_err(&pdev->dev, "ioremap() of lcd registers failed\n");
 564                ret = -ENXIO;
 565                goto release_mem_region;
 566        }
 567
 568        fbi->irq_base = fbi->io + REG_LCM_INT_CS;
 569
 570
 571        /* Stop the LCD */
 572        writel(0, fbi->io + REG_LCM_DCCS);
 573
 574        /* fill the fbinfo*/
 575        strcpy(fbinfo->fix.id, driver_name);
 576        fbinfo->fix.type                = FB_TYPE_PACKED_PIXELS;
 577        fbinfo->fix.type_aux            = 0;
 578        fbinfo->fix.xpanstep            = 0;
 579        fbinfo->fix.ypanstep            = 0;
 580        fbinfo->fix.ywrapstep           = 0;
 581        fbinfo->fix.accel               = FB_ACCEL_NONE;
 582        fbinfo->var.nonstd              = 0;
 583        fbinfo->var.activate            = FB_ACTIVATE_NOW;
 584        fbinfo->var.accel_flags         = 0;
 585        fbinfo->var.vmode               = FB_VMODE_NONINTERLACED;
 586        fbinfo->fbops                   = &nuc900fb_ops;
 587        fbinfo->flags                   = FBINFO_FLAG_DEFAULT;
 588        fbinfo->pseudo_palette          = &fbi->pseudo_pal;
 589
 590        ret = request_irq(irq, nuc900fb_irqhandler, 0, pdev->name, fbi);
 591        if (ret) {
 592                dev_err(&pdev->dev, "cannot register irq handler %d -err %d\n",
 593                        irq, ret);
 594                ret = -EBUSY;
 595                goto release_regs;
 596        }
 597
 598        fbi->clk = clk_get(&pdev->dev, NULL);
 599        if (IS_ERR(fbi->clk)) {
 600                printk(KERN_ERR "nuc900-lcd:failed to get lcd clock source\n");
 601                ret = PTR_ERR(fbi->clk);
 602                goto release_irq;
 603        }
 604
 605        clk_enable(fbi->clk);
 606        dev_dbg(&pdev->dev, "got and enabled clock\n");
 607
 608        fbi->clk_rate = clk_get_rate(fbi->clk);
 609
 610        /* calutate the video buffer size */
 611        for (i = 0; i < mach_info->num_displays; i++) {
 612                unsigned long smem_len = mach_info->displays[i].xres;
 613                smem_len *= mach_info->displays[i].yres;
 614                smem_len *= mach_info->displays[i].bpp;
 615                smem_len >>= 3;
 616                if (fbinfo->fix.smem_len < smem_len)
 617                        fbinfo->fix.smem_len = smem_len;
 618        }
 619
 620        /* Initialize Video Memory */
 621        ret = nuc900fb_map_video_memory(fbinfo);
 622        if (ret) {
 623                printk(KERN_ERR "Failed to allocate video RAM: %x\n", ret);
 624                goto release_clock;
 625        }
 626
 627        dev_dbg(&pdev->dev, "got video memory\n");
 628
 629        fbinfo->var.xres = display->xres;
 630        fbinfo->var.yres = display->yres;
 631        fbinfo->var.bits_per_pixel = display->bpp;
 632
 633        nuc900fb_init_registers(fbinfo);
 634
 635        nuc900fb_check_var(&fbinfo->var, fbinfo);
 636
 637        ret = nuc900fb_cpufreq_register(fbi);
 638        if (ret < 0) {
 639                dev_err(&pdev->dev, "Failed to register cpufreq\n");
 640                goto free_video_memory;
 641        }
 642
 643        ret = register_framebuffer(fbinfo);
 644        if (ret) {
 645                printk(KERN_ERR "failed to register framebuffer device: %d\n",
 646                        ret);
 647                goto free_cpufreq;
 648        }
 649
 650        fb_info(fbinfo, "%s frame buffer device\n", fbinfo->fix.id);
 651
 652        return 0;
 653
 654free_cpufreq:
 655        nuc900fb_cpufreq_deregister(fbi);
 656free_video_memory:
 657        nuc900fb_unmap_video_memory(fbinfo);
 658release_clock:
 659        clk_disable(fbi->clk);
 660        clk_put(fbi->clk);
 661release_irq:
 662        free_irq(irq, fbi);
 663release_regs:
 664        iounmap(fbi->io);
 665release_mem_region:
 666        release_mem_region(res->start, size);
 667free_fb:
 668        framebuffer_release(fbinfo);
 669        return ret;
 670}
 671
 672/*
 673 * shutdown the lcd controller
 674 */
 675static void nuc900fb_stop_lcd(struct fb_info *info)
 676{
 677        struct nuc900fb_info *fbi = info->par;
 678        void __iomem *regs = fbi->io;
 679
 680        writel((~LCM_DCCS_DISP_INT_EN) | (~LCM_DCCS_VA_EN) | (~LCM_DCCS_OSD_EN),
 681                regs + REG_LCM_DCCS);
 682}
 683
 684/*
 685 *  Cleanup
 686 */
 687static int nuc900fb_remove(struct platform_device *pdev)
 688{
 689        struct fb_info *fbinfo = platform_get_drvdata(pdev);
 690        struct nuc900fb_info *fbi = fbinfo->par;
 691        int irq;
 692
 693        nuc900fb_stop_lcd(fbinfo);
 694        msleep(1);
 695
 696        unregister_framebuffer(fbinfo);
 697        nuc900fb_cpufreq_deregister(fbi);
 698        nuc900fb_unmap_video_memory(fbinfo);
 699
 700        iounmap(fbi->io);
 701
 702        irq = platform_get_irq(pdev, 0);
 703        free_irq(irq, fbi);
 704
 705        release_resource(fbi->mem);
 706        kfree(fbi->mem);
 707
 708        framebuffer_release(fbinfo);
 709
 710        return 0;
 711}
 712
 713#ifdef CONFIG_PM
 714
 715/*
 716 *      suspend and resume support for the lcd controller
 717 */
 718
 719static int nuc900fb_suspend(struct platform_device *dev, pm_message_t state)
 720{
 721        struct fb_info     *fbinfo = platform_get_drvdata(dev);
 722        struct nuc900fb_info *info = fbinfo->par;
 723
 724        nuc900fb_stop_lcd(fbinfo);
 725        msleep(1);
 726        clk_disable(info->clk);
 727        return 0;
 728}
 729
 730static int nuc900fb_resume(struct platform_device *dev)
 731{
 732        struct fb_info     *fbinfo = platform_get_drvdata(dev);
 733        struct nuc900fb_info *fbi = fbinfo->par;
 734
 735        printk(KERN_INFO "nuc900fb resume\n");
 736
 737        clk_enable(fbi->clk);
 738        msleep(1);
 739
 740        nuc900fb_init_registers(fbinfo);
 741        nuc900fb_activate_var(fbinfo);
 742
 743        return 0;
 744}
 745
 746#else
 747#define nuc900fb_suspend NULL
 748#define nuc900fb_resume  NULL
 749#endif
 750
 751static struct platform_driver nuc900fb_driver = {
 752        .probe          = nuc900fb_probe,
 753        .remove         = nuc900fb_remove,
 754        .suspend        = nuc900fb_suspend,
 755        .resume         = nuc900fb_resume,
 756        .driver         = {
 757                .name   = "nuc900-lcd",
 758        },
 759};
 760
 761module_platform_driver(nuc900fb_driver);
 762
 763MODULE_DESCRIPTION("Framebuffer driver for the NUC900");
 764MODULE_LICENSE("GPL");
 765