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