| 1 | |
| 2 | * linux/drivers/video/hitfb.c -- Hitachi LCD frame buffer device
|
| 3 | *
|
| 4 | * (C) 1999 Mihai Spatar
|
| 5 | * (C) 2000 YAEGASHI Takeshi
|
| 6 | * (C) 2003, 2004 Paul Mundt
|
| 7 | * (C) 2003, 2004, 2006 Andriy Skulysh
|
| 8 | *
|
| 9 | * This file is subject to the terms and conditions of the GNU General Public
|
| 10 | * License. See the file COPYING in the main directory of this archive for
|
| 11 | * more details.
|
| 12 |
|
| 13 |
|
| 14 | #include <linux/module.h>
|
| 15 | #include <linux/kernel.h>
|
| 16 | #include <linux/errno.h>
|
| 17 | #include <linux/string.h>
|
| 18 | #include <linux/mm.h>
|
| 19 | #include <linux/delay.h>
|
| 20 | #include <linux/init.h>
|
| 21 | #include <linux/platform_device.h>
|
| 22 | #include <linux/fb.h>
|
| 23 |
|
| 24 | #include <asm/machvec.h>
|
| 25 | #include <linux/uaccess.h>
|
| 26 | #include <asm/io.h>
|
| 27 | #include <asm/hd64461.h>
|
| 28 | #include <cpu/dac.h>
|
| 29 |
|
| 30 | #define WIDTH 640
|
| 31 |
|
| 32 | static struct fb_var_screeninfo hitfb_var = {
|
| 33 | .activate = FB_ACTIVATE_NOW,
|
| 34 | .height = -1,
|
| 35 | .width = -1,
|
| 36 | .vmode = FB_VMODE_NONINTERLACED,
|
| 37 | };
|
| 38 |
|
| 39 | static struct fb_fix_screeninfo hitfb_fix = {
|
| 40 | .id = "Hitachi HD64461",
|
| 41 | .type = FB_TYPE_PACKED_PIXELS,
|
| 42 | .accel = FB_ACCEL_NONE,
|
| 43 | };
|
| 44 |
|
| 45 | static volatile void __iomem *hitfb_offset_to_addr(unsigned int offset)
|
| 46 | {
|
| 47 | return (__force volatile void __iomem *)(uintptr_t)offset;
|
| 48 | }
|
| 49 |
|
| 50 | static u16 hitfb_readw(unsigned int offset)
|
| 51 | {
|
| 52 | return fb_readw(hitfb_offset_to_addr(offset));
|
| 53 | }
|
| 54 |
|
| 55 | static void hitfb_writew(u16 value, unsigned int offset)
|
| 56 | {
|
| 57 | fb_writew(value, hitfb_offset_to_addr(offset));
|
| 58 | }
|
| 59 |
|
| 60 | static inline void hitfb_accel_wait(void)
|
| 61 | {
|
| 62 | while (hitfb_readw(HD64461_GRCFGR) & HD64461_GRCFGR_ACCSTATUS)
|
| 63 | ;
|
| 64 | }
|
| 65 |
|
| 66 | static inline void hitfb_accel_start(int truecolor)
|
| 67 | {
|
| 68 | if (truecolor) {
|
| 69 | hitfb_writew(6, HD64461_GRCFGR);
|
| 70 | } else {
|
| 71 | hitfb_writew(7, HD64461_GRCFGR);
|
| 72 | }
|
| 73 | }
|
| 74 |
|
| 75 | static inline void hitfb_accel_set_dest(int truecolor, u16 dx, u16 dy,
|
| 76 | u16 width, u16 height)
|
| 77 | {
|
| 78 | u32 saddr = WIDTH * dy + dx;
|
| 79 | if (truecolor)
|
| 80 | saddr <<= 1;
|
| 81 |
|
| 82 | hitfb_writew(width-1, HD64461_BBTDWR);
|
| 83 | hitfb_writew(height-1, HD64461_BBTDHR);
|
| 84 |
|
| 85 | hitfb_writew(saddr & 0xffff, HD64461_BBTDSARL);
|
| 86 | hitfb_writew(saddr >> 16, HD64461_BBTDSARH);
|
| 87 |
|
| 88 | }
|
| 89 |
|
| 90 | static inline void hitfb_accel_bitblt(int truecolor, u16 sx, u16 sy, u16 dx,
|
| 91 | u16 dy, u16 width, u16 height, u16 rop,
|
| 92 | u32 mask_addr)
|
| 93 | {
|
| 94 | u32 saddr, daddr;
|
| 95 | u32 maddr = 0;
|
| 96 |
|
| 97 | height--;
|
| 98 | width--;
|
| 99 | hitfb_writew(rop, HD64461_BBTROPR);
|
| 100 | if ((sy < dy) || ((sy == dy) && (sx <= dx))) {
|
| 101 | saddr = WIDTH * (sy + height) + sx + width;
|
| 102 | daddr = WIDTH * (dy + height) + dx + width;
|
| 103 | if (mask_addr) {
|
| 104 | if (truecolor)
|
| 105 | maddr = ((width >> 3) + 1) * (height + 1) - 1;
|
| 106 | else
|
| 107 | maddr =
|
| 108 | (((width >> 4) + 1) * (height + 1) - 1) * 2;
|
| 109 |
|
| 110 | hitfb_writew((1 << 5) | 1, HD64461_BBTMDR);
|
| 111 | } else
|
| 112 | hitfb_writew(1, HD64461_BBTMDR);
|
| 113 | } else {
|
| 114 | saddr = WIDTH * sy + sx;
|
| 115 | daddr = WIDTH * dy + dx;
|
| 116 | if (mask_addr) {
|
| 117 | hitfb_writew((1 << 5), HD64461_BBTMDR);
|
| 118 | } else {
|
| 119 | hitfb_writew(0, HD64461_BBTMDR);
|
| 120 | }
|
| 121 | }
|
| 122 | if (truecolor) {
|
| 123 | saddr <<= 1;
|
| 124 | daddr <<= 1;
|
| 125 | }
|
| 126 | hitfb_writew(width, HD64461_BBTDWR);
|
| 127 | hitfb_writew(height, HD64461_BBTDHR);
|
| 128 | hitfb_writew(saddr & 0xffff, HD64461_BBTSSARL);
|
| 129 | hitfb_writew(saddr >> 16, HD64461_BBTSSARH);
|
| 130 | hitfb_writew(daddr & 0xffff, HD64461_BBTDSARL);
|
| 131 | hitfb_writew(daddr >> 16, HD64461_BBTDSARH);
|
| 132 | if (mask_addr) {
|
| 133 | maddr += mask_addr;
|
| 134 | hitfb_writew(maddr & 0xffff, HD64461_BBTMARL);
|
| 135 | hitfb_writew(maddr >> 16, HD64461_BBTMARH);
|
| 136 | }
|
| 137 | hitfb_accel_start(truecolor);
|
| 138 | }
|
| 139 |
|
| 140 | static void hitfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
|
| 141 | {
|
| 142 | if (rect->rop != ROP_COPY)
|
| 143 | cfb_fillrect(p, rect);
|
| 144 | else {
|
| 145 | hitfb_accel_wait();
|
| 146 | hitfb_writew(0x00f0, HD64461_BBTROPR);
|
| 147 | hitfb_writew(16, HD64461_BBTMDR);
|
| 148 |
|
| 149 | if (p->var.bits_per_pixel == 16) {
|
| 150 | hitfb_writew(((u32 *) (p->pseudo_palette))[rect->color],
|
| 151 | HD64461_GRSCR);
|
| 152 | hitfb_accel_set_dest(1, rect->dx, rect->dy, rect->width,
|
| 153 | rect->height);
|
| 154 | hitfb_accel_start(1);
|
| 155 | } else {
|
| 156 | hitfb_writew(rect->color, HD64461_GRSCR);
|
| 157 | hitfb_accel_set_dest(0, rect->dx, rect->dy, rect->width,
|
| 158 | rect->height);
|
| 159 | hitfb_accel_start(0);
|
| 160 | }
|
| 161 | }
|
| 162 | }
|
| 163 |
|
| 164 | static void hitfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
|
| 165 | {
|
| 166 | hitfb_accel_wait();
|
| 167 | hitfb_accel_bitblt(p->var.bits_per_pixel == 16, area->sx, area->sy,
|
| 168 | area->dx, area->dy, area->width, area->height,
|
| 169 | 0x00cc, 0);
|
| 170 | }
|
| 171 |
|
| 172 | static int hitfb_pan_display(struct fb_var_screeninfo *var,
|
| 173 | struct fb_info *info)
|
| 174 | {
|
| 175 | int xoffset = var->xoffset;
|
| 176 | int yoffset = var->yoffset;
|
| 177 |
|
| 178 | if (xoffset != 0)
|
| 179 | return -EINVAL;
|
| 180 |
|
| 181 | hitfb_writew((yoffset*info->fix.line_length)>>10, HD64461_LCDCBAR);
|
| 182 |
|
| 183 | return 0;
|
| 184 | }
|
| 185 |
|
| 186 | static int hitfb_blank(int blank_mode, struct fb_info *info)
|
| 187 | {
|
| 188 | unsigned short v;
|
| 189 |
|
| 190 | if (blank_mode) {
|
| 191 | v = hitfb_readw(HD64461_LDR1);
|
| 192 | v &= ~HD64461_LDR1_DON;
|
| 193 | hitfb_writew(v, HD64461_LDR1);
|
| 194 |
|
| 195 | v = hitfb_readw(HD64461_LCDCCR);
|
| 196 | v |= HD64461_LCDCCR_MOFF;
|
| 197 | hitfb_writew(v, HD64461_LCDCCR);
|
| 198 |
|
| 199 | v = hitfb_readw(HD64461_STBCR);
|
| 200 | v |= HD64461_STBCR_SLCDST;
|
| 201 | hitfb_writew(v, HD64461_STBCR);
|
| 202 | } else {
|
| 203 | v = hitfb_readw(HD64461_STBCR);
|
| 204 | v &= ~HD64461_STBCR_SLCDST;
|
| 205 | hitfb_writew(v, HD64461_STBCR);
|
| 206 |
|
| 207 | v = hitfb_readw(HD64461_LCDCCR);
|
| 208 | v &= ~(HD64461_LCDCCR_MOFF | HD64461_LCDCCR_STREQ);
|
| 209 | hitfb_writew(v, HD64461_LCDCCR);
|
| 210 |
|
| 211 | do {
|
| 212 | v = hitfb_readw(HD64461_LCDCCR);
|
| 213 | } while(v&HD64461_LCDCCR_STBACK);
|
| 214 |
|
| 215 | v = hitfb_readw(HD64461_LDR1);
|
| 216 | v |= HD64461_LDR1_DON;
|
| 217 | hitfb_writew(v, HD64461_LDR1);
|
| 218 | }
|
| 219 | return 0;
|
| 220 | }
|
| 221 |
|
| 222 | static int hitfb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
| 223 | unsigned blue, unsigned transp, struct fb_info *info)
|
| 224 | {
|
| 225 | if (regno >= 256)
|
| 226 | return 1;
|
| 227 |
|
| 228 | switch (info->var.bits_per_pixel) {
|
| 229 | case 8:
|
| 230 | hitfb_writew(regno << 8, HD64461_CPTWAR);
|
| 231 | hitfb_writew(red >> 10, HD64461_CPTWDR);
|
| 232 | hitfb_writew(green >> 10, HD64461_CPTWDR);
|
| 233 | hitfb_writew(blue >> 10, HD64461_CPTWDR);
|
| 234 | break;
|
| 235 | case 16:
|
| 236 | if (regno >= 16)
|
| 237 | return 1;
|
| 238 | ((u32 *) (info->pseudo_palette))[regno] =
|
| 239 | ((red & 0xf800)) |
|
| 240 | ((green & 0xfc00) >> 5) | ((blue & 0xf800) >> 11);
|
| 241 | break;
|
| 242 | }
|
| 243 | return 0;
|
| 244 | }
|
| 245 |
|
| 246 | static int hitfb_sync(struct fb_info *info)
|
| 247 | {
|
| 248 | hitfb_accel_wait();
|
| 249 |
|
| 250 | return 0;
|
| 251 | }
|
| 252 |
|
| 253 | static int hitfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
|
| 254 | {
|
| 255 | int maxy;
|
| 256 |
|
| 257 | var->xres = info->var.xres;
|
| 258 | var->xres_virtual = info->var.xres;
|
| 259 | var->yres = info->var.yres;
|
| 260 |
|
| 261 | if ((var->bits_per_pixel != 8) && (var->bits_per_pixel != 16))
|
| 262 | var->bits_per_pixel = info->var.bits_per_pixel;
|
| 263 |
|
| 264 | if (var->yres_virtual < var->yres)
|
| 265 | var->yres_virtual = var->yres;
|
| 266 |
|
| 267 | maxy = info->fix.smem_len / var->xres;
|
| 268 |
|
| 269 | if (var->bits_per_pixel == 16)
|
| 270 | maxy /= 2;
|
| 271 |
|
| 272 | if (var->yres_virtual > maxy)
|
| 273 | var->yres_virtual = maxy;
|
| 274 |
|
| 275 | var->xoffset = 0;
|
| 276 | var->yoffset = 0;
|
| 277 |
|
| 278 | switch (var->bits_per_pixel) {
|
| 279 | case 8:
|
| 280 | var->red.offset = 0;
|
| 281 | var->red.length = 8;
|
| 282 | var->green.offset = 0;
|
| 283 | var->green.length = 8;
|
| 284 | var->blue.offset = 0;
|
| 285 | var->blue.length = 8;
|
| 286 | var->transp.offset = 0;
|
| 287 | var->transp.length = 0;
|
| 288 | break;
|
| 289 | case 16:
|
| 290 | var->red.offset = 11;
|
| 291 | var->red.length = 5;
|
| 292 | var->green.offset = 5;
|
| 293 | var->green.length = 6;
|
| 294 | var->blue.offset = 0;
|
| 295 | var->blue.length = 5;
|
| 296 | var->transp.offset = 0;
|
| 297 | var->transp.length = 0;
|
| 298 | break;
|
| 299 | }
|
| 300 |
|
| 301 | return 0;
|
| 302 | }
|
| 303 |
|
| 304 | static int hitfb_set_par(struct fb_info *info)
|
| 305 | {
|
| 306 | unsigned short ldr3;
|
| 307 |
|
| 308 | switch (info->var.bits_per_pixel) {
|
| 309 | case 8:
|
| 310 | info->fix.line_length = info->var.xres;
|
| 311 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
|
| 312 | info->fix.ypanstep = 16;
|
| 313 | break;
|
| 314 | case 16:
|
| 315 | info->fix.line_length = info->var.xres*2;
|
| 316 | info->fix.visual = FB_VISUAL_TRUECOLOR;
|
| 317 | info->fix.ypanstep = 8;
|
| 318 | break;
|
| 319 | }
|
| 320 |
|
| 321 | hitfb_writew(info->fix.line_length, HD64461_LCDCLOR);
|
| 322 | ldr3 = hitfb_readw(HD64461_LDR3);
|
| 323 | ldr3 &= ~15;
|
| 324 | ldr3 |= (info->var.bits_per_pixel == 8) ? 4 : 8;
|
| 325 | hitfb_writew(ldr3, HD64461_LDR3);
|
| 326 | return 0;
|
| 327 | }
|
| 328 |
|
| 329 | static const struct fb_ops hitfb_ops = {
|
| 330 | .owner = THIS_MODULE,
|
| 331 | __FB_DEFAULT_IOMEM_OPS_RDWR,
|
| 332 | .fb_check_var = hitfb_check_var,
|
| 333 | .fb_set_par = hitfb_set_par,
|
| 334 | .fb_setcolreg = hitfb_setcolreg,
|
| 335 | .fb_blank = hitfb_blank,
|
| 336 | .fb_sync = hitfb_sync,
|
| 337 | .fb_pan_display = hitfb_pan_display,
|
| 338 | .fb_fillrect = hitfb_fillrect,
|
| 339 | .fb_copyarea = hitfb_copyarea,
|
| 340 | .fb_imageblit = cfb_imageblit,
|
| 341 | __FB_DEFAULT_IOMEM_OPS_MMAP,
|
| 342 | };
|
| 343 |
|
| 344 | static int hitfb_probe(struct platform_device *dev)
|
| 345 | {
|
| 346 | unsigned short lcdclor, ldr3, ldvndr;
|
| 347 | struct fb_info *info;
|
| 348 | int ret;
|
| 349 |
|
| 350 | if (fb_get_options("hitfb", NULL))
|
| 351 | return -ENODEV;
|
| 352 |
|
| 353 | hitfb_fix.mmio_start = HD64461_IO_OFFSET(0x1000);
|
| 354 | hitfb_fix.mmio_len = 0x1000;
|
| 355 | hitfb_fix.smem_start = HD64461_IO_OFFSET(0x02000000);
|
| 356 | hitfb_fix.smem_len = 512 * 1024;
|
| 357 |
|
| 358 | lcdclor = hitfb_readw(HD64461_LCDCLOR);
|
| 359 | ldvndr = hitfb_readw(HD64461_LDVNDR);
|
| 360 | ldr3 = hitfb_readw(HD64461_LDR3);
|
| 361 |
|
| 362 | switch (ldr3 & 15) {
|
| 363 | default:
|
| 364 | case 4:
|
| 365 | hitfb_var.bits_per_pixel = 8;
|
| 366 | hitfb_var.xres = lcdclor;
|
| 367 | break;
|
| 368 | case 8:
|
| 369 | hitfb_var.bits_per_pixel = 16;
|
| 370 | hitfb_var.xres = lcdclor / 2;
|
| 371 | break;
|
| 372 | }
|
| 373 | hitfb_fix.line_length = lcdclor;
|
| 374 | hitfb_fix.visual = (hitfb_var.bits_per_pixel == 8) ?
|
| 375 | FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
|
| 376 | hitfb_var.yres = ldvndr + 1;
|
| 377 | hitfb_var.xres_virtual = hitfb_var.xres;
|
| 378 | hitfb_var.yres_virtual = hitfb_fix.smem_len / lcdclor;
|
| 379 | switch (hitfb_var.bits_per_pixel) {
|
| 380 | case 8:
|
| 381 | hitfb_var.red.offset = 0;
|
| 382 | hitfb_var.red.length = 8;
|
| 383 | hitfb_var.green.offset = 0;
|
| 384 | hitfb_var.green.length = 8;
|
| 385 | hitfb_var.blue.offset = 0;
|
| 386 | hitfb_var.blue.length = 8;
|
| 387 | hitfb_var.transp.offset = 0;
|
| 388 | hitfb_var.transp.length = 0;
|
| 389 | break;
|
| 390 | case 16:
|
| 391 | hitfb_var.red.offset = 11;
|
| 392 | hitfb_var.red.length = 5;
|
| 393 | hitfb_var.green.offset = 5;
|
| 394 | hitfb_var.green.length = 6;
|
| 395 | hitfb_var.blue.offset = 0;
|
| 396 | hitfb_var.blue.length = 5;
|
| 397 | hitfb_var.transp.offset = 0;
|
| 398 | hitfb_var.transp.length = 0;
|
| 399 | break;
|
| 400 | }
|
| 401 |
|
| 402 | info = framebuffer_alloc(sizeof(u32) * 16, &dev->dev);
|
| 403 | if (unlikely(!info))
|
| 404 | return -ENOMEM;
|
| 405 |
|
| 406 | info->fbops = &hitfb_ops;
|
| 407 | info->var = hitfb_var;
|
| 408 | info->fix = hitfb_fix;
|
| 409 | info->pseudo_palette = info->par;
|
| 410 | info->flags = FBINFO_HWACCEL_YPAN |
|
| 411 | FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_COPYAREA;
|
| 412 |
|
| 413 | info->screen_base = (char __iomem *)(uintptr_t)hitfb_fix.smem_start;
|
| 414 |
|
| 415 | ret = fb_alloc_cmap(&info->cmap, 256, 0);
|
| 416 | if (unlikely(ret < 0))
|
| 417 | goto err_fb;
|
| 418 |
|
| 419 | ret = register_framebuffer(info);
|
| 420 | if (unlikely(ret < 0))
|
| 421 | goto err;
|
| 422 |
|
| 423 | platform_set_drvdata(dev, info);
|
| 424 |
|
| 425 | fb_info(info, "%s frame buffer device\n", info->fix.id);
|
| 426 |
|
| 427 | return 0;
|
| 428 |
|
| 429 | err:
|
| 430 | fb_dealloc_cmap(&info->cmap);
|
| 431 | err_fb:
|
| 432 | framebuffer_release(info);
|
| 433 | return ret;
|
| 434 | }
|
| 435 |
|
| 436 | static void hitfb_remove(struct platform_device *dev)
|
| 437 | {
|
| 438 | struct fb_info *info = platform_get_drvdata(dev);
|
| 439 |
|
| 440 | unregister_framebuffer(info);
|
| 441 | fb_dealloc_cmap(&info->cmap);
|
| 442 | framebuffer_release(info);
|
| 443 | }
|
| 444 |
|
| 445 | static int hitfb_suspend(struct device *dev)
|
| 446 | {
|
| 447 | u16 v;
|
| 448 |
|
| 449 | hitfb_blank(1, NULL);
|
| 450 | v = hitfb_readw(HD64461_STBCR);
|
| 451 | v |= HD64461_STBCR_SLCKE_IST;
|
| 452 | hitfb_writew(v, HD64461_STBCR);
|
| 453 |
|
| 454 | return 0;
|
| 455 | }
|
| 456 |
|
| 457 | static int hitfb_resume(struct device *dev)
|
| 458 | {
|
| 459 | u16 v;
|
| 460 |
|
| 461 | v = hitfb_readw(HD64461_STBCR);
|
| 462 | v &= ~HD64461_STBCR_SLCKE_OST;
|
| 463 | msleep(100);
|
| 464 | v = hitfb_readw(HD64461_STBCR);
|
| 465 | v &= ~HD64461_STBCR_SLCKE_IST;
|
| 466 | hitfb_writew(v, HD64461_STBCR);
|
| 467 | hitfb_blank(0, NULL);
|
| 468 |
|
| 469 | return 0;
|
| 470 | }
|
| 471 |
|
| 472 | static const struct dev_pm_ops hitfb_dev_pm_ops = {
|
| 473 | .suspend = hitfb_suspend,
|
| 474 | .resume = hitfb_resume,
|
| 475 | };
|
| 476 |
|
| 477 | static struct platform_driver hitfb_driver = {
|
| 478 | .probe = hitfb_probe,
|
| 479 | .remove = hitfb_remove,
|
| 480 | .driver = {
|
| 481 | .name = "hitfb",
|
| 482 | .pm = &hitfb_dev_pm_ops,
|
| 483 | },
|
| 484 | };
|
| 485 |
|
| 486 | static struct platform_device hitfb_device = {
|
| 487 | .name = "hitfb",
|
| 488 | .id = -1,
|
| 489 | };
|
| 490 |
|
| 491 | static int __init hitfb_init(void)
|
| 492 | {
|
| 493 | int ret;
|
| 494 |
|
| 495 | ret = platform_driver_register(&hitfb_driver);
|
| 496 | if (!ret) {
|
| 497 | ret = platform_device_register(&hitfb_device);
|
| 498 | if (ret)
|
| 499 | platform_driver_unregister(&hitfb_driver);
|
| 500 | }
|
| 501 | return ret;
|
| 502 | }
|
| 503 |
|
| 504 |
|
| 505 | static void __exit hitfb_exit(void)
|
| 506 | {
|
| 507 | platform_device_unregister(&hitfb_device);
|
| 508 | platform_driver_unregister(&hitfb_driver);
|
| 509 | }
|
| 510 |
|
| 511 | module_init(hitfb_init);
|
| 512 | module_exit(hitfb_exit);
|
| 513 |
|
| 514 | MODULE_LICENSE("GPL");
|