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