| 1 | |
| 2 | |
| 3 | * SH7760/SH7763 LCDC Framebuffer driver.
|
| 4 | *
|
| 5 | * (c) 2006-2008 MSC Vertriebsges.m.b.H.,
|
| 6 | * Manuel Lauss <[email protected]>
|
| 7 | * (c) 2008 Nobuhiro Iwamatsu <[email protected]>
|
| 8 | *
|
| 9 | * PLEASE HAVE A LOOK AT Documentation/fb/sh7760fb.rst!
|
| 10 | *
|
| 11 | * Thanks to Siegfried Schaefer <s.schaefer at schaefer-edv.de>
|
| 12 | * for his original source and testing!
|
| 13 | *
|
| 14 | * sh7760_setcolreg get from drivers/video/sh_mobile_lcdcfb.c
|
| 15 |
|
| 16 |
|
| 17 | #include <linux/completion.h>
|
| 18 | #include <linux/delay.h>
|
| 19 | #include <linux/dma-mapping.h>
|
| 20 | #include <linux/fb.h>
|
| 21 | #include <linux/interrupt.h>
|
| 22 | #include <linux/io.h>
|
| 23 | #include <linux/kernel.h>
|
| 24 | #include <linux/module.h>
|
| 25 | #include <linux/platform_device.h>
|
| 26 | #include <linux/slab.h>
|
| 27 |
|
| 28 | #include <asm/sh7760fb.h>
|
| 29 |
|
| 30 | struct sh7760fb_par {
|
| 31 | void __iomem *base;
|
| 32 | int irq;
|
| 33 |
|
| 34 | struct sh7760fb_platdata *pd;
|
| 35 |
|
| 36 | dma_addr_t fbdma;
|
| 37 |
|
| 38 | int rot;
|
| 39 |
|
| 40 | u32 pseudo_palette[16];
|
| 41 |
|
| 42 | struct platform_device *dev;
|
| 43 | struct resource *ioarea;
|
| 44 | struct completion vsync;
|
| 45 | };
|
| 46 |
|
| 47 | static irqreturn_t sh7760fb_irq(int irq, void *data)
|
| 48 | {
|
| 49 | struct completion *c = data;
|
| 50 |
|
| 51 | complete(c);
|
| 52 |
|
| 53 | return IRQ_HANDLED;
|
| 54 | }
|
| 55 |
|
| 56 |
|
| 57 | static int wait_for_lps(struct sh7760fb_par *par, int val)
|
| 58 | {
|
| 59 | int i = 100;
|
| 60 | while (--i && ((ioread16(par->base + LDPMMR) & 3) != val))
|
| 61 | msleep(1);
|
| 62 |
|
| 63 | if (i <= 0)
|
| 64 | return -ETIMEDOUT;
|
| 65 |
|
| 66 | return 0;
|
| 67 | }
|
| 68 |
|
| 69 |
|
| 70 | static int sh7760fb_blank(int blank, struct fb_info *info)
|
| 71 | {
|
| 72 | struct sh7760fb_par *par = info->par;
|
| 73 | struct sh7760fb_platdata *pd = par->pd;
|
| 74 | unsigned short cntr = ioread16(par->base + LDCNTR);
|
| 75 | unsigned short intr = ioread16(par->base + LDINTR);
|
| 76 | int lps;
|
| 77 |
|
| 78 | if (blank == FB_BLANK_UNBLANK) {
|
| 79 | intr |= VINT_START;
|
| 80 | cntr = LDCNTR_DON2 | LDCNTR_DON;
|
| 81 | lps = 3;
|
| 82 | } else {
|
| 83 | intr &= ~VINT_START;
|
| 84 | cntr = LDCNTR_DON2;
|
| 85 | lps = 0;
|
| 86 | }
|
| 87 |
|
| 88 | if (pd->blank)
|
| 89 | pd->blank(blank);
|
| 90 |
|
| 91 | iowrite16(intr, par->base + LDINTR);
|
| 92 | iowrite16(cntr, par->base + LDCNTR);
|
| 93 |
|
| 94 | return wait_for_lps(par, lps);
|
| 95 | }
|
| 96 |
|
| 97 | static int sh7760_setcolreg (u_int regno,
|
| 98 | u_int red, u_int green, u_int blue,
|
| 99 | u_int transp, struct fb_info *info)
|
| 100 | {
|
| 101 | u32 *palette = info->pseudo_palette;
|
| 102 |
|
| 103 | if (regno >= 16)
|
| 104 | return -EINVAL;
|
| 105 |
|
| 106 |
|
| 107 |
|
| 108 | red >>= 16 - info->var.red.length;
|
| 109 | green >>= 16 - info->var.green.length;
|
| 110 | blue >>= 16 - info->var.blue.length;
|
| 111 | transp >>= 16 - info->var.transp.length;
|
| 112 |
|
| 113 | palette[regno] = (red << info->var.red.offset) |
|
| 114 | (green << info->var.green.offset) |
|
| 115 | (blue << info->var.blue.offset) |
|
| 116 | (transp << info->var.transp.offset);
|
| 117 |
|
| 118 | return 0;
|
| 119 | }
|
| 120 |
|
| 121 | static int sh7760fb_get_color_info(struct fb_info *info,
|
| 122 | u16 lddfr, int *bpp, int *gray)
|
| 123 | {
|
| 124 | int lbpp, lgray;
|
| 125 |
|
| 126 | lgray = lbpp = 0;
|
| 127 |
|
| 128 | switch (lddfr & LDDFR_COLOR_MASK) {
|
| 129 | case LDDFR_1BPP_MONO:
|
| 130 | lgray = 1;
|
| 131 | lbpp = 1;
|
| 132 | break;
|
| 133 | case LDDFR_2BPP_MONO:
|
| 134 | lgray = 1;
|
| 135 | lbpp = 2;
|
| 136 | break;
|
| 137 | case LDDFR_4BPP_MONO:
|
| 138 | lgray = 1;
|
| 139 | fallthrough;
|
| 140 | case LDDFR_4BPP:
|
| 141 | lbpp = 4;
|
| 142 | break;
|
| 143 | case LDDFR_6BPP_MONO:
|
| 144 | lgray = 1;
|
| 145 | fallthrough;
|
| 146 | case LDDFR_8BPP:
|
| 147 | lbpp = 8;
|
| 148 | break;
|
| 149 | case LDDFR_16BPP_RGB555:
|
| 150 | case LDDFR_16BPP_RGB565:
|
| 151 | lbpp = 16;
|
| 152 | lgray = 0;
|
| 153 | break;
|
| 154 | default:
|
| 155 | fb_dbg(info, "unsupported LDDFR bit depth.\n");
|
| 156 | return -EINVAL;
|
| 157 | }
|
| 158 |
|
| 159 | if (bpp)
|
| 160 | *bpp = lbpp;
|
| 161 | if (gray)
|
| 162 | *gray = lgray;
|
| 163 |
|
| 164 | return 0;
|
| 165 | }
|
| 166 |
|
| 167 | static int sh7760fb_check_var(struct fb_var_screeninfo *var,
|
| 168 | struct fb_info *info)
|
| 169 | {
|
| 170 | struct fb_fix_screeninfo *fix = &info->fix;
|
| 171 | struct sh7760fb_par *par = info->par;
|
| 172 | int ret, bpp;
|
| 173 |
|
| 174 |
|
| 175 | ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
|
| 176 | if (ret)
|
| 177 | return ret;
|
| 178 |
|
| 179 | var->bits_per_pixel = bpp;
|
| 180 |
|
| 181 | if ((var->grayscale) && (var->bits_per_pixel == 1))
|
| 182 | fix->visual = FB_VISUAL_MONO10;
|
| 183 | else if (var->bits_per_pixel >= 15)
|
| 184 | fix->visual = FB_VISUAL_TRUECOLOR;
|
| 185 | else
|
| 186 | fix->visual = FB_VISUAL_PSEUDOCOLOR;
|
| 187 |
|
| 188 |
|
| 189 | return 0;
|
| 190 | }
|
| 191 |
|
| 192 | |
| 193 | * sh7760fb_set_par - set videomode.
|
| 194 | *
|
| 195 | * NOTE: The rotation, grayscale and DSTN codepaths are
|
| 196 | * totally untested!
|
| 197 |
|
| 198 | static int sh7760fb_set_par(struct fb_info *info)
|
| 199 | {
|
| 200 | struct sh7760fb_par *par = info->par;
|
| 201 | struct fb_videomode *vm = par->pd->def_mode;
|
| 202 | unsigned long sbase, dstn_off, ldsarl, stride;
|
| 203 | unsigned short hsynp, hsynw, htcn, hdcn;
|
| 204 | unsigned short vsynp, vsynw, vtln, vdln;
|
| 205 | unsigned short lddfr, ldmtr;
|
| 206 | int ret, bpp, gray;
|
| 207 |
|
| 208 | par->rot = par->pd->rotate;
|
| 209 |
|
| 210 |
|
| 211 | if (par->rot && (vm->xres > 320)) {
|
| 212 | fb_dbg(info, "rotation disabled due to display size\n");
|
| 213 | par->rot = 0;
|
| 214 | }
|
| 215 |
|
| 216 |
|
| 217 | hsynp = vm->right_margin + vm->xres;
|
| 218 | hsynw = vm->hsync_len;
|
| 219 | htcn = vm->left_margin + hsynp + hsynw;
|
| 220 | hdcn = vm->xres;
|
| 221 | vsynp = vm->lower_margin + vm->yres;
|
| 222 | vsynw = vm->vsync_len;
|
| 223 | vtln = vm->upper_margin + vsynp + vsynw;
|
| 224 | vdln = vm->yres;
|
| 225 |
|
| 226 |
|
| 227 | ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, &gray);
|
| 228 | if (ret)
|
| 229 | return ret;
|
| 230 |
|
| 231 | fb_dbg(info, "%dx%d %dbpp %s (orientation %s)\n", hdcn,
|
| 232 | vdln, bpp, gray ? "grayscale" : "color",
|
| 233 | par->rot ? "rotated" : "normal");
|
| 234 |
|
| 235 | #ifdef CONFIG_CPU_LITTLE_ENDIAN
|
| 236 | lddfr = par->pd->lddfr | (1 << 8);
|
| 237 | #else
|
| 238 | lddfr = par->pd->lddfr & ~(1 << 8);
|
| 239 | #endif
|
| 240 |
|
| 241 | ldmtr = par->pd->ldmtr;
|
| 242 |
|
| 243 | if (!(vm->sync & FB_SYNC_HOR_HIGH_ACT))
|
| 244 | ldmtr |= LDMTR_CL1POL;
|
| 245 | if (!(vm->sync & FB_SYNC_VERT_HIGH_ACT))
|
| 246 | ldmtr |= LDMTR_FLMPOL;
|
| 247 |
|
| 248 |
|
| 249 | sh7760fb_blank(FB_BLANK_POWERDOWN, info);
|
| 250 |
|
| 251 | iowrite16(par->pd->ldickr, par->base + LDICKR);
|
| 252 | iowrite16(ldmtr, par->base + LDMTR);
|
| 253 | iowrite16(lddfr, par->base + LDDFR);
|
| 254 | iowrite16((par->rot ? 1 << 13 : 0), par->base + LDSMR);
|
| 255 | iowrite16(par->pd->ldpmmr, par->base + LDPMMR);
|
| 256 | iowrite16(par->pd->ldpspr, par->base + LDPSPR);
|
| 257 |
|
| 258 |
|
| 259 | iowrite16(((htcn >> 3) - 1) | (((hdcn >> 3) - 1) << 8),
|
| 260 | par->base + LDHCNR);
|
| 261 | iowrite16(vdln - 1, par->base + LDVDLNR);
|
| 262 | iowrite16(vtln - 1, par->base + LDVTLNR);
|
| 263 |
|
| 264 | iowrite16((vsynp - 1) | ((vsynw - 1) << 12), par->base + LDVSYNR);
|
| 265 | iowrite16(((hsynp >> 3) - 1) | (((hsynw >> 3) - 1) << 12),
|
| 266 | par->base + LDHSYNR);
|
| 267 |
|
| 268 | iowrite16(par->pd->ldaclnr, par->base + LDACLNR);
|
| 269 |
|
| 270 | stride = (par->rot) ? vtln : hdcn;
|
| 271 | if (!gray)
|
| 272 | stride *= (bpp + 7) >> 3;
|
| 273 | else {
|
| 274 | if (bpp == 1)
|
| 275 | stride >>= 3;
|
| 276 | else if (bpp == 2)
|
| 277 | stride >>= 2;
|
| 278 | else if (bpp == 4)
|
| 279 | stride >>= 1;
|
| 280 |
|
| 281 | }
|
| 282 |
|
| 283 |
|
| 284 | if (par->rot) {
|
| 285 | unsigned long bit = 1 << 31;
|
| 286 | while (bit) {
|
| 287 | if (stride & bit)
|
| 288 | break;
|
| 289 | bit >>= 1;
|
| 290 | }
|
| 291 | if (stride & ~bit)
|
| 292 | stride = bit << 1;
|
| 293 | }
|
| 294 | iowrite16(stride, par->base + LDLAOR);
|
| 295 |
|
| 296 |
|
| 297 | sbase = (unsigned long)par->fbdma;
|
| 298 | if (par->rot)
|
| 299 | sbase += (hdcn - 1) * stride;
|
| 300 |
|
| 301 | iowrite32(sbase, par->base + LDSARU);
|
| 302 |
|
| 303 | |
| 304 | * for DSTN need to set address for lower half.
|
| 305 | * I (mlau) don't know which address to set it to,
|
| 306 | * so I guessed at (stride * yres/2).
|
| 307 |
|
| 308 | if (((ldmtr & 0x003f) >= LDMTR_DSTN_MONO_8) &&
|
| 309 | ((ldmtr & 0x003f) <= LDMTR_DSTN_COLOR_16)) {
|
| 310 |
|
| 311 | fb_dbg(info, " ***** DSTN untested! *****\n");
|
| 312 |
|
| 313 | dstn_off = stride;
|
| 314 | if (par->rot)
|
| 315 | dstn_off *= hdcn >> 1;
|
| 316 | else
|
| 317 | dstn_off *= vdln >> 1;
|
| 318 |
|
| 319 | ldsarl = sbase + dstn_off;
|
| 320 | } else
|
| 321 | ldsarl = 0;
|
| 322 |
|
| 323 | iowrite32(ldsarl, par->base + LDSARL);
|
| 324 |
|
| 325 | info->fix.line_length = stride;
|
| 326 |
|
| 327 | sh7760fb_check_var(&info->var, info);
|
| 328 |
|
| 329 | sh7760fb_blank(FB_BLANK_UNBLANK, info);
|
| 330 |
|
| 331 | fb_dbg(info, "hdcn : %6d htcn : %6d\n", hdcn, htcn);
|
| 332 | fb_dbg(info, "hsynw : %6d hsynp : %6d\n", hsynw, hsynp);
|
| 333 | fb_dbg(info, "vdln : %6d vtln : %6d\n", vdln, vtln);
|
| 334 | fb_dbg(info, "vsynw : %6d vsynp : %6d\n", vsynw, vsynp);
|
| 335 | fb_dbg(info, "clksrc: %6d clkdiv: %6d\n",
|
| 336 | (par->pd->ldickr >> 12) & 3, par->pd->ldickr & 0x1f);
|
| 337 | fb_dbg(info, "ldpmmr: 0x%04x ldpspr: 0x%04x\n", par->pd->ldpmmr,
|
| 338 | par->pd->ldpspr);
|
| 339 | fb_dbg(info, "ldmtr : 0x%04x lddfr : 0x%04x\n", ldmtr, lddfr);
|
| 340 | fb_dbg(info, "ldlaor: %ld\n", stride);
|
| 341 | fb_dbg(info, "ldsaru: 0x%08lx ldsarl: 0x%08lx\n", sbase, ldsarl);
|
| 342 |
|
| 343 | return 0;
|
| 344 | }
|
| 345 |
|
| 346 | static const struct fb_ops sh7760fb_ops = {
|
| 347 | .owner = THIS_MODULE,
|
| 348 | FB_DEFAULT_IOMEM_OPS,
|
| 349 | .fb_blank = sh7760fb_blank,
|
| 350 | .fb_check_var = sh7760fb_check_var,
|
| 351 | .fb_setcolreg = sh7760_setcolreg,
|
| 352 | .fb_set_par = sh7760fb_set_par,
|
| 353 | };
|
| 354 |
|
| 355 | static void sh7760fb_free_mem(struct fb_info *info)
|
| 356 | {
|
| 357 | struct sh7760fb_par *par = info->par;
|
| 358 |
|
| 359 | if (!info->screen_base)
|
| 360 | return;
|
| 361 |
|
| 362 | dma_free_coherent(info->device, info->screen_size,
|
| 363 | info->screen_base, par->fbdma);
|
| 364 |
|
| 365 | par->fbdma = 0;
|
| 366 | info->screen_base = NULL;
|
| 367 | info->screen_size = 0;
|
| 368 | }
|
| 369 |
|
| 370 | |
| 371 | * (dictated by the DMA engine) and contiguous, at a 512 byte boundary.
|
| 372 |
|
| 373 | static int sh7760fb_alloc_mem(struct fb_info *info)
|
| 374 | {
|
| 375 | struct sh7760fb_par *par = info->par;
|
| 376 | void *fbmem;
|
| 377 | unsigned long vram;
|
| 378 | int ret, bpp;
|
| 379 |
|
| 380 | if (info->screen_base)
|
| 381 | return 0;
|
| 382 |
|
| 383 |
|
| 384 | ret = sh7760fb_get_color_info(info, par->pd->lddfr, &bpp, NULL);
|
| 385 | if (ret) {
|
| 386 | printk(KERN_ERR "colinfo\n");
|
| 387 | return ret;
|
| 388 | }
|
| 389 |
|
| 390 | |
| 391 | max VRAM: xres_max = 1024, yres_max = 1024, bpp = 16: 2MB
|
| 392 |
|
| 393 | vram = info->var.xres * info->var.yres;
|
| 394 | if (info->var.grayscale) {
|
| 395 | if (bpp == 1)
|
| 396 | vram >>= 3;
|
| 397 | else if (bpp == 2)
|
| 398 | vram >>= 2;
|
| 399 | else if (bpp == 4)
|
| 400 | vram >>= 1;
|
| 401 | } else if (bpp > 8)
|
| 402 | vram *= 2;
|
| 403 | if ((vram < 1) || (vram > 1024 * 2048)) {
|
| 404 | fb_dbg(info, "too much VRAM required. Check settings\n");
|
| 405 | return -ENODEV;
|
| 406 | }
|
| 407 |
|
| 408 | if (vram < PAGE_SIZE)
|
| 409 | vram = PAGE_SIZE;
|
| 410 |
|
| 411 | fbmem = dma_alloc_coherent(info->device, vram, &par->fbdma, GFP_KERNEL);
|
| 412 | if (!fbmem)
|
| 413 | return -ENOMEM;
|
| 414 |
|
| 415 | if ((par->fbdma & SH7760FB_DMA_MASK) != SH7760FB_DMA_MASK) {
|
| 416 | dma_free_coherent(info->device, vram, fbmem, par->fbdma);
|
| 417 | dev_err(info->device, "kernel gave me memory at 0x%08lx, which is"
|
| 418 | "unusable for the LCDC\n", (unsigned long)par->fbdma);
|
| 419 | return -ENOMEM;
|
| 420 | }
|
| 421 |
|
| 422 | info->screen_base = fbmem;
|
| 423 | info->screen_size = vram;
|
| 424 | info->fix.smem_start = (unsigned long)info->screen_base;
|
| 425 | info->fix.smem_len = info->screen_size;
|
| 426 |
|
| 427 | return 0;
|
| 428 | }
|
| 429 |
|
| 430 | static int sh7760fb_probe(struct platform_device *pdev)
|
| 431 | {
|
| 432 | struct fb_info *info;
|
| 433 | struct resource *res;
|
| 434 | struct sh7760fb_par *par;
|
| 435 | int ret;
|
| 436 |
|
| 437 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
| 438 | if (unlikely(res == NULL)) {
|
| 439 | dev_err(&pdev->dev, "invalid resource\n");
|
| 440 | return -EINVAL;
|
| 441 | }
|
| 442 |
|
| 443 | info = framebuffer_alloc(sizeof(struct sh7760fb_par), &pdev->dev);
|
| 444 | if (!info)
|
| 445 | return -ENOMEM;
|
| 446 |
|
| 447 | par = info->par;
|
| 448 | par->dev = pdev;
|
| 449 |
|
| 450 | par->pd = pdev->dev.platform_data;
|
| 451 | if (!par->pd) {
|
| 452 | dev_dbg(&pdev->dev, "no display setup data!\n");
|
| 453 | ret = -ENODEV;
|
| 454 | goto out_fb;
|
| 455 | }
|
| 456 |
|
| 457 | par->ioarea = request_mem_region(res->start,
|
| 458 | resource_size(res), pdev->name);
|
| 459 | if (!par->ioarea) {
|
| 460 | dev_err(&pdev->dev, "mmio area busy\n");
|
| 461 | ret = -EBUSY;
|
| 462 | goto out_fb;
|
| 463 | }
|
| 464 |
|
| 465 | par->base = ioremap(res->start, resource_size(res));
|
| 466 | if (!par->base) {
|
| 467 | dev_err(&pdev->dev, "cannot remap\n");
|
| 468 | ret = -ENODEV;
|
| 469 | goto out_res;
|
| 470 | }
|
| 471 |
|
| 472 | iowrite16(0, par->base + LDINTR);
|
| 473 | par->irq = platform_get_irq(pdev, 0);
|
| 474 | if (par->irq >= 0) {
|
| 475 | ret = request_irq(par->irq, sh7760fb_irq, 0,
|
| 476 | "sh7760-lcdc", &par->vsync);
|
| 477 | if (ret) {
|
| 478 | dev_err(&pdev->dev, "cannot grab IRQ\n");
|
| 479 | par->irq = -ENXIO;
|
| 480 | } else
|
| 481 | disable_irq_nosync(par->irq);
|
| 482 | }
|
| 483 |
|
| 484 | fb_videomode_to_var(&info->var, par->pd->def_mode);
|
| 485 |
|
| 486 | ret = sh7760fb_alloc_mem(info);
|
| 487 | if (ret) {
|
| 488 | dev_dbg(info->device, "framebuffer memory allocation failed!\n");
|
| 489 | goto out_unmap;
|
| 490 | }
|
| 491 |
|
| 492 | info->pseudo_palette = par->pseudo_palette;
|
| 493 |
|
| 494 |
|
| 495 | info->var.red.offset = 11;
|
| 496 | info->var.red.length = 5;
|
| 497 | info->var.red.msb_right = 0;
|
| 498 |
|
| 499 | info->var.green.offset = 5;
|
| 500 | info->var.green.length = 6;
|
| 501 | info->var.green.msb_right = 0;
|
| 502 |
|
| 503 | info->var.blue.offset = 0;
|
| 504 | info->var.blue.length = 5;
|
| 505 | info->var.blue.msb_right = 0;
|
| 506 |
|
| 507 | info->var.transp.offset = 0;
|
| 508 | info->var.transp.length = 0;
|
| 509 | info->var.transp.msb_right = 0;
|
| 510 |
|
| 511 | strcpy(info->fix.id, "sh7760-lcdc");
|
| 512 |
|
| 513 | |
| 514 | * palette memory.
|
| 515 |
|
| 516 | iowrite16(LDCNTR_DON2, par->base + LDCNTR);
|
| 517 | info->fbops = &sh7760fb_ops;
|
| 518 |
|
| 519 | ret = fb_alloc_cmap(&info->cmap, 256, 0);
|
| 520 | if (ret) {
|
| 521 | dev_dbg(&pdev->dev, "Unable to allocate cmap memory\n");
|
| 522 | goto out_mem;
|
| 523 | }
|
| 524 |
|
| 525 | ret = register_framebuffer(info);
|
| 526 | if (ret < 0) {
|
| 527 | dev_dbg(&pdev->dev, "cannot register fb!\n");
|
| 528 | goto out_cmap;
|
| 529 | }
|
| 530 | platform_set_drvdata(pdev, info);
|
| 531 |
|
| 532 | printk(KERN_INFO "%s: memory at phys 0x%08lx-0x%08lx, size %ld KiB\n",
|
| 533 | pdev->name,
|
| 534 | (unsigned long)par->fbdma,
|
| 535 | (unsigned long)(par->fbdma + info->screen_size - 1),
|
| 536 | info->screen_size >> 10);
|
| 537 |
|
| 538 | return 0;
|
| 539 |
|
| 540 | out_cmap:
|
| 541 | sh7760fb_blank(FB_BLANK_POWERDOWN, info);
|
| 542 | fb_dealloc_cmap(&info->cmap);
|
| 543 | out_mem:
|
| 544 | sh7760fb_free_mem(info);
|
| 545 | out_unmap:
|
| 546 | if (par->irq >= 0)
|
| 547 | free_irq(par->irq, &par->vsync);
|
| 548 | iounmap(par->base);
|
| 549 | out_res:
|
| 550 | release_mem_region(res->start, resource_size(res));
|
| 551 | out_fb:
|
| 552 | framebuffer_release(info);
|
| 553 | return ret;
|
| 554 | }
|
| 555 |
|
| 556 | static void sh7760fb_remove(struct platform_device *dev)
|
| 557 | {
|
| 558 | struct fb_info *info = platform_get_drvdata(dev);
|
| 559 | struct sh7760fb_par *par = info->par;
|
| 560 |
|
| 561 | sh7760fb_blank(FB_BLANK_POWERDOWN, info);
|
| 562 | unregister_framebuffer(info);
|
| 563 | fb_dealloc_cmap(&info->cmap);
|
| 564 | sh7760fb_free_mem(info);
|
| 565 | if (par->irq >= 0)
|
| 566 | free_irq(par->irq, &par->vsync);
|
| 567 | iounmap(par->base);
|
| 568 | release_mem_region(par->ioarea->start, resource_size(par->ioarea));
|
| 569 | framebuffer_release(info);
|
| 570 | }
|
| 571 |
|
| 572 | static struct platform_driver sh7760_lcdc_driver = {
|
| 573 | .driver = {
|
| 574 | .name = "sh7760-lcdc",
|
| 575 | },
|
| 576 | .probe = sh7760fb_probe,
|
| 577 | .remove = sh7760fb_remove,
|
| 578 | };
|
| 579 |
|
| 580 | module_platform_driver(sh7760_lcdc_driver);
|
| 581 |
|
| 582 | MODULE_AUTHOR("Nobuhiro Iwamatsu, Manuel Lauss");
|
| 583 | MODULE_DESCRIPTION("FBdev for SH7760/63 integrated LCD Controller");
|
| 584 | MODULE_LICENSE("GPL v2");
|