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