1/* tcx.c: TCX frame buffer driver 2 * 3 * Copyright (C) 2003, 2006 David S. Miller (davem@davemloft.net) 4 * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) 5 * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) 6 * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) 7 * 8 * Driver layout based loosely on tgafb.c, see that file for credits. 9 */ 10 11#include <linux/module.h> 12#include <linux/kernel.h> 13#include <linux/errno.h> 14#include <linux/string.h> 15#include <linux/slab.h> 16#include <linux/delay.h> 17#include <linux/init.h> 18#include <linux/fb.h> 19#include <linux/mm.h> 20 21#include <asm/io.h> 22#include <asm/prom.h> 23#include <asm/of_device.h> 24#include <asm/fbio.h> 25 26#include "sbuslib.h" 27 28/* 29 * Local functions. 30 */ 31 32static int tcx_setcolreg(unsigned, unsigned, unsigned, unsigned, 33 unsigned, struct fb_info *); 34static int tcx_blank(int, struct fb_info *); 35 36static int tcx_mmap(struct fb_info *, struct vm_area_struct *); 37static int tcx_ioctl(struct fb_info *, unsigned int, unsigned long); 38static int tcx_pan_display(struct fb_var_screeninfo *, struct fb_info *); 39 40/* 41 * Frame buffer operations 42 */ 43 44static struct fb_ops tcx_ops = { 45 .owner = THIS_MODULE, 46 .fb_setcolreg = tcx_setcolreg, 47 .fb_blank = tcx_blank, 48 .fb_pan_display = tcx_pan_display, 49 .fb_fillrect = cfb_fillrect, 50 .fb_copyarea = cfb_copyarea, 51 .fb_imageblit = cfb_imageblit, 52 .fb_mmap = tcx_mmap, 53 .fb_ioctl = tcx_ioctl, 54#ifdef CONFIG_COMPAT 55 .fb_compat_ioctl = sbusfb_compat_ioctl, 56#endif 57}; 58 59/* THC definitions */ 60#define TCX_THC_MISC_REV_SHIFT 16 61#define TCX_THC_MISC_REV_MASK 15 62#define TCX_THC_MISC_VSYNC_DIS (1 << 25) 63#define TCX_THC_MISC_HSYNC_DIS (1 << 24) 64#define TCX_THC_MISC_RESET (1 << 12) 65#define TCX_THC_MISC_VIDEO (1 << 10) 66#define TCX_THC_MISC_SYNC (1 << 9) 67#define TCX_THC_MISC_VSYNC (1 << 8) 68#define TCX_THC_MISC_SYNC_ENAB (1 << 7) 69#define TCX_THC_MISC_CURS_RES (1 << 6) 70#define TCX_THC_MISC_INT_ENAB (1 << 5) 71#define TCX_THC_MISC_INT (1 << 4) 72#define TCX_THC_MISC_INIT 0x9f 73#define TCX_THC_REV_REV_SHIFT 20 74#define TCX_THC_REV_REV_MASK 15 75#define TCX_THC_REV_MINREV_SHIFT 28 76#define TCX_THC_REV_MINREV_MASK 15 77 78/* The contents are unknown */ 79struct tcx_tec { 80 u32 tec_matrix; 81 u32 tec_clip; 82 u32 tec_vdc; 83}; 84 85struct tcx_thc { 86 u32 thc_rev; 87 u32 thc_pad0[511]; 88 u32 thc_hs; /* hsync timing */ 89 u32 thc_hsdvs; 90 u32 thc_hd; 91 u32 thc_vs; /* vsync timing */ 92 u32 thc_vd; 93 u32 thc_refresh; 94 u32 thc_misc; 95 u32 thc_pad1[56]; 96 u32 thc_cursxy; /* cursor x,y position (16 bits each) */ 97 u32 thc_cursmask[32]; /* cursor mask bits */ 98 u32 thc_cursbits[32]; /* what to show where mask enabled */ 99}; 100 101struct bt_regs { 102 u32 addr; 103 u32 color_map; 104 u32 control; 105 u32 cursor; 106}; 107 108#define TCX_MMAP_ENTRIES 14 109 110struct tcx_par { 111 spinlock_t lock; 112 struct bt_regs __iomem *bt; 113 struct tcx_thc __iomem *thc; 114 struct tcx_tec __iomem *tec; 115 u32 __iomem *cplane; 116 117 u32 flags; 118#define TCX_FLAG_BLANKED 0x00000001 119 120 unsigned long physbase; 121 unsigned long which_io; 122 unsigned long fbsize; 123 124 struct sbus_mmap_map mmap_map[TCX_MMAP_ENTRIES]; 125 int lowdepth; 126}; 127 128/* Reset control plane so that WID is 8-bit plane. */ 129static void __tcx_set_control_plane (struct tcx_par *par) 130{ 131 u32 __iomem *p, *pend; 132 133 if (par->lowdepth) 134 return; 135 136 p = par->cplane; 137 if (p == NULL) 138 return; 139 for (pend = p + par->fbsize; p < pend; p++) { 140 u32 tmp = sbus_readl(p); 141 142 tmp &= 0xffffff; 143 sbus_writel(tmp, p); 144 } 145} 146 147static void tcx_reset (struct fb_info *info) 148{ 149 struct tcx_par *par = (struct tcx_par *) info->par; 150 unsigned long flags; 151 152 spin_lock_irqsave(&par->lock, flags); 153 __tcx_set_control_plane(par); 154 spin_unlock_irqrestore(&par->lock, flags); 155} 156 157static int tcx_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) 158{ 159 tcx_reset(info); 160 return 0; 161} 162 163/** 164 * tcx_setcolreg - Optional function. Sets a color register. 165 * @regno: boolean, 0 copy local, 1 get_user() function 166 * @red: frame buffer colormap structure 167 * @green: The green value which can be up to 16 bits wide 168 * @blue: The blue value which can be up to 16 bits wide. 169 * @transp: If supported the alpha value which can be up to 16 bits wide. 170 * @info: frame buffer info structure 171 */ 172static int tcx_setcolreg(unsigned regno, 173 unsigned red, unsigned green, unsigned blue, 174 unsigned transp, struct fb_info *info) 175{ 176 struct tcx_par *par = (struct tcx_par *) info->par; 177 struct bt_regs __iomem *bt = par->bt; 178 unsigned long flags; 179 180 if (regno >= 256) 181 return 1; 182 183 red >>= 8; 184 green >>= 8; 185 blue >>= 8; 186 187 spin_lock_irqsave(&par->lock, flags); 188 189 sbus_writel(regno << 24, &bt->addr); 190 sbus_writel(red << 24, &bt->color_map); 191 sbus_writel(green << 24, &bt->color_map); 192 sbus_writel(blue << 24, &bt->color_map); 193 194 spin_unlock_irqrestore(&par->lock, flags); 195 196 return 0; 197} 198 199/** 200 * tcx_blank - Optional function. Blanks the display. 201 * @blank_mode: the blank mode we want. 202 * @info: frame buffer structure that represents a single frame buffer 203 */ 204static int 205tcx_blank(int blank, struct fb_info *info) 206{ 207 struct tcx_par *par = (struct tcx_par *) info->par; 208 struct tcx_thc __iomem *thc = par->thc; 209 unsigned long flags; 210 u32 val; 211 212 spin_lock_irqsave(&par->lock, flags); 213 214 val = sbus_readl(&thc->thc_misc); 215 216 switch (blank) { 217 case FB_BLANK_UNBLANK: /* Unblanking */ 218 val &= ~(TCX_THC_MISC_VSYNC_DIS | 219 TCX_THC_MISC_HSYNC_DIS); 220 val |= TCX_THC_MISC_VIDEO; 221 par->flags &= ~TCX_FLAG_BLANKED; 222 break; 223 224 case FB_BLANK_NORMAL: /* Normal blanking */ 225 val &= ~TCX_THC_MISC_VIDEO; 226 par->flags |= TCX_FLAG_BLANKED; 227 break; 228 229 case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ 230 val |= TCX_THC_MISC_VSYNC_DIS; 231 break; 232 case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ 233 val |= TCX_THC_MISC_HSYNC_DIS; 234 break; 235 236 case FB_BLANK_POWERDOWN: /* Poweroff */ 237 break; 238 }; 239 240 sbus_writel(val, &thc->thc_misc); 241 242 spin_unlock_irqrestore(&par->lock, flags); 243 244 return 0; 245} 246 247static struct sbus_mmap_map __tcx_mmap_map[TCX_MMAP_ENTRIES] = { 248 { 249 .voff = TCX_RAM8BIT, 250 .size = SBUS_MMAP_FBSIZE(1) 251 }, 252 { 253 .voff = TCX_RAM24BIT, 254 .size = SBUS_MMAP_FBSIZE(4) 255 }, 256 { 257 .voff = TCX_UNK3, 258 .size = SBUS_MMAP_FBSIZE(8) 259 }, 260 { 261 .voff = TCX_UNK4, 262 .size = SBUS_MMAP_FBSIZE(8) 263 }, 264 { 265 .voff = TCX_CONTROLPLANE, 266 .size = SBUS_MMAP_FBSIZE(4) 267 }, 268 { 269 .voff = TCX_UNK6, 270 .size = SBUS_MMAP_FBSIZE(8) 271 }, 272 { 273 .voff = TCX_UNK7, 274 .size = SBUS_MMAP_FBSIZE(8) 275 }, 276 { 277 .voff = TCX_TEC, 278 .size = PAGE_SIZE 279 }, 280 { 281 .voff = TCX_BTREGS, 282 .size = PAGE_SIZE 283 }, 284 { 285 .voff = TCX_THC, 286 .size = PAGE_SIZE 287 }, 288 { 289 .voff = TCX_DHC, 290 .size = PAGE_SIZE 291 }, 292 { 293 .voff = TCX_ALT, 294 .size = PAGE_SIZE 295 }, 296 { 297 .voff = TCX_UNK2, 298 .size = 0x20000 299 }, 300 { .size = 0 } 301}; 302 303static int tcx_mmap(struct fb_info *info, struct vm_area_struct *vma) 304{ 305 struct tcx_par *par = (struct tcx_par *)info->par; 306 307 return sbusfb_mmap_helper(par->mmap_map, 308 par->physbase, par->fbsize, 309 par->which_io, vma); 310} 311 312static int tcx_ioctl(struct fb_info *info, unsigned int cmd, 313 unsigned long arg) 314{ 315 struct tcx_par *par = (struct tcx_par *) info->par; 316 317 return sbusfb_ioctl_helper(cmd, arg, info, 318 FBTYPE_TCXCOLOR, 319 (par->lowdepth ? 8 : 24), 320 par->fbsize); 321} 322 323/* 324 * Initialisation 325 */ 326 327static void 328tcx_init_fix(struct fb_info *info, int linebytes) 329{ 330 struct tcx_par *par = (struct tcx_par *)info->par; 331 const char *tcx_name; 332 333 if (par->lowdepth) 334 tcx_name = "TCX8"; 335 else 336 tcx_name = "TCX24"; 337 338 strlcpy(info->fix.id, tcx_name, sizeof(info->fix.id)); 339 340 info->fix.type = FB_TYPE_PACKED_PIXELS; 341 info->fix.visual = FB_VISUAL_PSEUDOCOLOR; 342 343 info->fix.line_length = linebytes; 344 345 info->fix.accel = FB_ACCEL_SUN_TCX; 346} 347 348struct all_info { 349 struct fb_info info; 350 struct tcx_par par; 351}; 352 353static void tcx_unmap_regs(struct of_device *op, struct all_info *all) 354{ 355 if (all->par.tec) 356 of_iounmap(&op->resource[7], 357 all->par.tec, sizeof(struct tcx_tec)); 358 if (all->par.thc) 359 of_iounmap(&op->resource[9], 360 all->par.thc, sizeof(struct tcx_thc)); 361 if (all->par.bt) 362 of_iounmap(&op->resource[8], 363 all->par.bt, sizeof(struct bt_regs)); 364 if (all->par.cplane) 365 of_iounmap(&op->resource[4], 366 all->par.cplane, all->par.fbsize * sizeof(u32)); 367 if (all->info.screen_base) 368 of_iounmap(&op->resource[0], 369 all->info.screen_base, all->par.fbsize); 370} 371 372static int __devinit tcx_init_one(struct of_device *op) 373{ 374 struct device_node *dp = op->node; 375 struct all_info *all; 376 int linebytes, i, err; 377 378 all = kzalloc(sizeof(*all), GFP_KERNEL); 379 if (!all) 380 return -ENOMEM; 381 382 spin_lock_init(&all->par.lock); 383 384 all->par.lowdepth = 385 (of_find_property(dp, "tcx-8-bit", NULL) != NULL); 386 387 sbusfb_fill_var(&all->info.var, dp->node, 8); 388 all->info.var.red.length = 8; 389 all->info.var.green.length = 8; 390 all->info.var.blue.length = 8; 391 392 linebytes = of_getintprop_default(dp, "linebytes", 393 all->info.var.xres); 394 all->par.fbsize = PAGE_ALIGN(linebytes * all->info.var.yres); 395 396 all->par.tec = of_ioremap(&op->resource[7], 0, 397 sizeof(struct tcx_tec), "tcx tec"); 398 all->par.thc = of_ioremap(&op->resource[9], 0, 399 sizeof(struct tcx_thc), "tcx thc"); 400 all->par.bt = of_ioremap(&op->resource[8], 0, 401 sizeof(struct bt_regs), "tcx dac"); 402 all->info.screen_base = of_ioremap(&op->resource[0], 0, 403 all->par.fbsize, "tcx ram"); 404 if (!all->par.tec || !all->par.thc || 405 !all->par.bt || !all->info.screen_base) { 406 tcx_unmap_regs(op, all); 407 kfree(all); 408 return -ENOMEM; 409 } 410 411 memcpy(&all->par.mmap_map, &__tcx_mmap_map, sizeof(all->par.mmap_map)); 412 if (!all->par.lowdepth) { 413 all->par.cplane = of_ioremap(&op->resource[4], 0, 414 all->par.fbsize * sizeof(u32), 415 "tcx cplane"); 416 if (!all->par.cplane) { 417 tcx_unmap_regs(op, all); 418 kfree(all); 419 return -ENOMEM; 420 } 421 } else { 422 all->par.mmap_map[1].size = SBUS_MMAP_EMPTY; 423 all->par.mmap_map[4].size = SBUS_MMAP_EMPTY; 424 all->par.mmap_map[5].size = SBUS_MMAP_EMPTY; 425 all->par.mmap_map[6].size = SBUS_MMAP_EMPTY; 426 } 427 428 all->par.physbase = 0; 429 all->par.which_io = op->resource[0].flags & IORESOURCE_BITS; 430 431 for (i = 0; i < TCX_MMAP_ENTRIES; i++) { 432 int j; 433 434 switch (i) { 435 case 10: 436 j = 12; 437 break; 438 439 case 11: case 12: 440 j = i - 1; 441 break; 442 443 default: 444 j = i; 445 break; 446 }; 447 all->par.mmap_map[i].poff = op->resource[j].start; 448 } 449 450 all->info.flags = FBINFO_DEFAULT; 451 all->info.fbops = &tcx_ops; 452 all->info.par = &all->par; 453 454 /* Initialize brooktree DAC. */ 455 sbus_writel(0x04 << 24, &all->par.bt->addr); /* color planes */ 456 sbus_writel(0xff << 24, &all->par.bt->control); 457 sbus_writel(0x05 << 24, &all->par.bt->addr); 458 sbus_writel(0x00 << 24, &all->par.bt->control); 459 sbus_writel(0x06 << 24, &all->par.bt->addr); /* overlay plane */ 460 sbus_writel(0x73 << 24, &all->par.bt->control); 461 sbus_writel(0x07 << 24, &all->par.bt->addr); 462 sbus_writel(0x00 << 24, &all->par.bt->control); 463 464 tcx_reset(&all->info); 465 466 tcx_blank(FB_BLANK_UNBLANK, &all->info); 467 468 if (fb_alloc_cmap(&all->info.cmap, 256, 0)) { 469 tcx_unmap_regs(op, all); 470 kfree(all); 471 return -ENOMEM; 472 } 473 474 fb_set_cmap(&all->info.cmap, &all->info); 475 tcx_init_fix(&all->info, linebytes); 476 477 err = register_framebuffer(&all->info); 478 if (err < 0) { 479 fb_dealloc_cmap(&all->info.cmap); 480 tcx_unmap_regs(op, all); 481 kfree(all); 482 return err; 483 } 484 485 dev_set_drvdata(&op->dev, all); 486 487 printk("%s: TCX at %lx:%lx, %s\n", 488 dp->full_name, 489 all->par.which_io, 490 op->resource[0].start, 491 all->par.lowdepth ? "8-bit only" : "24-bit depth"); 492 493 return 0; 494} 495 496static int __devinit tcx_probe(struct of_device *dev, const struct of_device_id *match) 497{ 498 struct of_device *op = to_of_device(&dev->dev); 499 500 return tcx_init_one(op); 501} 502 503static int __devexit tcx_remove(struct of_device *op) 504{ 505 struct all_info *all = dev_get_drvdata(&op->dev); 506 507 unregister_framebuffer(&all->info); 508 fb_dealloc_cmap(&all->info.cmap); 509 510 tcx_unmap_regs(op, all); 511 512 kfree(all); 513 514 dev_set_drvdata(&op->dev, NULL); 515 516 return 0; 517} 518 519static struct of_device_id tcx_match[] = { 520 { 521 .name = "SUNW,tcx", 522 }, 523 {}, 524}; 525MODULE_DEVICE_TABLE(of, tcx_match); 526 527static struct of_platform_driver tcx_driver = { 528 .name = "tcx", 529 .match_table = tcx_match, 530 .probe = tcx_probe, 531 .remove = __devexit_p(tcx_remove), 532}; 533 534int __init tcx_init(void) 535{ 536 if (fb_get_options("tcxfb", NULL)) 537 return -ENODEV; 538 539 return of_register_driver(&tcx_driver, &of_bus_type); 540} 541 542void __exit tcx_exit(void) 543{ 544 of_unregister_driver(&tcx_driver); 545} 546 547module_init(tcx_init); 548module_exit(tcx_exit); 549 550MODULE_DESCRIPTION("framebuffer driver for TCX chipsets"); 551MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); 552MODULE_VERSION("2.0"); 553MODULE_LICENSE("GPL"); 554