| 1 | |
| 2 | |
| 3 | *
|
| 4 | * Copyright (C) 2003, 2006 David S. Miller ([email protected])
|
| 5 | * Copyright (C) 1996,1998 Jakub Jelinek ([email protected])
|
| 6 | * Copyright (C) 1996 Miguel de Icaza ([email protected])
|
| 7 | * Copyright (C) 1997 Eddie C. Dost ([email protected])
|
| 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 cg3_setcolreg(unsigned, unsigned, unsigned, unsigned,
|
| 33 | unsigned, struct fb_info *);
|
| 34 | static int cg3_blank(int, struct fb_info *);
|
| 35 |
|
| 36 | static int cg3_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma);
|
| 37 | static int cg3_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg);
|
| 38 |
|
| 39 | |
| 40 | * Frame buffer operations
|
| 41 |
|
| 42 |
|
| 43 | static const struct fb_ops cg3_ops = {
|
| 44 | .owner = THIS_MODULE,
|
| 45 | FB_DEFAULT_SBUS_OPS(cg3),
|
| 46 | .fb_setcolreg = cg3_setcolreg,
|
| 47 | .fb_blank = cg3_blank,
|
| 48 | };
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 | #define CG3_CR_ENABLE_INTS 0x80
|
| 53 | #define CG3_CR_ENABLE_VIDEO 0x40
|
| 54 | #define CG3_CR_ENABLE_TIMING 0x20
|
| 55 | #define CG3_CR_ENABLE_CURCMP 0x10
|
| 56 | #define CG3_CR_XTAL_MASK 0x0c
|
| 57 | #define CG3_CR_DIVISOR_MASK 0x03
|
| 58 |
|
| 59 |
|
| 60 | #define CG3_SR_PENDING_INT 0x80
|
| 61 | #define CG3_SR_RES_MASK 0x70
|
| 62 | #define CG3_SR_1152_900_76_A 0x40
|
| 63 | #define CG3_SR_1152_900_76_B 0x60
|
| 64 | #define CG3_SR_ID_MASK 0x0f
|
| 65 | #define CG3_SR_ID_COLOR 0x01
|
| 66 | #define CG3_SR_ID_MONO 0x02
|
| 67 | #define CG3_SR_ID_MONO_ECL 0x03
|
| 68 |
|
| 69 | enum cg3_type {
|
| 70 | CG3_AT_66HZ = 0,
|
| 71 | CG3_AT_76HZ,
|
| 72 | CG3_RDI
|
| 73 | };
|
| 74 |
|
| 75 | struct bt_regs {
|
| 76 | u32 addr;
|
| 77 | u32 color_map;
|
| 78 | u32 control;
|
| 79 | u32 cursor;
|
| 80 | };
|
| 81 |
|
| 82 | struct cg3_regs {
|
| 83 | struct bt_regs cmap;
|
| 84 | u8 control;
|
| 85 | u8 status;
|
| 86 | u8 cursor_start;
|
| 87 | u8 cursor_end;
|
| 88 | u8 h_blank_start;
|
| 89 | u8 h_blank_end;
|
| 90 | u8 h_sync_start;
|
| 91 | u8 h_sync_end;
|
| 92 | u8 comp_sync_end;
|
| 93 | u8 v_blank_start_high;
|
| 94 | u8 v_blank_start_low;
|
| 95 | u8 v_blank_end;
|
| 96 | u8 v_sync_start;
|
| 97 | u8 v_sync_end;
|
| 98 | u8 xfer_holdoff_start;
|
| 99 | u8 xfer_holdoff_end;
|
| 100 | };
|
| 101 |
|
| 102 |
|
| 103 | #define CG3_REGS_OFFSET 0x400000UL
|
| 104 | #define CG3_RAM_OFFSET 0x800000UL
|
| 105 |
|
| 106 | struct cg3_par {
|
| 107 | spinlock_t lock;
|
| 108 | struct cg3_regs __iomem *regs;
|
| 109 | u32 sw_cmap[((256 * 3) + 3) / 4];
|
| 110 |
|
| 111 | u32 flags;
|
| 112 | #define CG3_FLAG_BLANKED 0x00000001
|
| 113 | #define CG3_FLAG_RDI 0x00000002
|
| 114 |
|
| 115 | unsigned long which_io;
|
| 116 | };
|
| 117 |
|
| 118 | |
| 119 | * cg3_setcolreg - Optional function. Sets a color register.
|
| 120 | * @regno: boolean, 0 copy local, 1 get_user() function
|
| 121 | * @red: frame buffer colormap structure
|
| 122 | * @green: The green value which can be up to 16 bits wide
|
| 123 | * @blue: The blue value which can be up to 16 bits wide.
|
| 124 | * @transp: If supported the alpha value which can be up to 16 bits wide.
|
| 125 | * @info: frame buffer info structure
|
| 126 | *
|
| 127 | * The cg3 palette is loaded with 4 color values at each time
|
| 128 | * so you end up with: (rgb)(r), (gb)(rg), (b)(rgb), and so on.
|
| 129 | * We keep a sw copy of the hw cmap to assist us in this esoteric
|
| 130 | * loading procedure.
|
| 131 |
|
| 132 | static int cg3_setcolreg(unsigned regno,
|
| 133 | unsigned red, unsigned green, unsigned blue,
|
| 134 | unsigned transp, struct fb_info *info)
|
| 135 | {
|
| 136 | struct cg3_par *par = (struct cg3_par *) info->par;
|
| 137 | struct bt_regs __iomem *bt = &par->regs->cmap;
|
| 138 | unsigned long flags;
|
| 139 | u32 *p32;
|
| 140 | u8 *p8;
|
| 141 | int count;
|
| 142 |
|
| 143 | if (regno >= 256)
|
| 144 | return 1;
|
| 145 |
|
| 146 | red >>= 8;
|
| 147 | green >>= 8;
|
| 148 | blue >>= 8;
|
| 149 |
|
| 150 | spin_lock_irqsave(&par->lock, flags);
|
| 151 |
|
| 152 | p8 = (u8 *)par->sw_cmap + (regno * 3);
|
| 153 | p8[0] = red;
|
| 154 | p8[1] = green;
|
| 155 | p8[2] = blue;
|
| 156 |
|
| 157 | #define D4M3(x) ((((x)>>2)<<1) + ((x)>>2))
|
| 158 | #define D4M4(x) ((x)&~0x3)
|
| 159 |
|
| 160 | count = 3;
|
| 161 | p32 = &par->sw_cmap[D4M3(regno)];
|
| 162 | sbus_writel(D4M4(regno), &bt->addr);
|
| 163 | while (count--)
|
| 164 | sbus_writel(*p32++, &bt->color_map);
|
| 165 |
|
| 166 | #undef D4M3
|
| 167 | #undef D4M4
|
| 168 |
|
| 169 | spin_unlock_irqrestore(&par->lock, flags);
|
| 170 |
|
| 171 | return 0;
|
| 172 | }
|
| 173 |
|
| 174 | |
| 175 | * cg3_blank - Optional function. Blanks the display.
|
| 176 | * @blank: the blank mode we want.
|
| 177 | * @info: frame buffer structure that represents a single frame buffer
|
| 178 |
|
| 179 | static int cg3_blank(int blank, struct fb_info *info)
|
| 180 | {
|
| 181 | struct cg3_par *par = (struct cg3_par *) info->par;
|
| 182 | struct cg3_regs __iomem *regs = par->regs;
|
| 183 | unsigned long flags;
|
| 184 | u8 val;
|
| 185 |
|
| 186 | spin_lock_irqsave(&par->lock, flags);
|
| 187 |
|
| 188 | switch (blank) {
|
| 189 | case FB_BLANK_UNBLANK:
|
| 190 | val = sbus_readb(®s->control);
|
| 191 | val |= CG3_CR_ENABLE_VIDEO;
|
| 192 | sbus_writeb(val, ®s->control);
|
| 193 | par->flags &= ~CG3_FLAG_BLANKED;
|
| 194 | break;
|
| 195 |
|
| 196 | case FB_BLANK_NORMAL:
|
| 197 | case FB_BLANK_VSYNC_SUSPEND:
|
| 198 | case FB_BLANK_HSYNC_SUSPEND:
|
| 199 | case FB_BLANK_POWERDOWN:
|
| 200 | val = sbus_readb(®s->control);
|
| 201 | val &= ~CG3_CR_ENABLE_VIDEO;
|
| 202 | sbus_writeb(val, ®s->control);
|
| 203 | par->flags |= CG3_FLAG_BLANKED;
|
| 204 | break;
|
| 205 | }
|
| 206 |
|
| 207 | spin_unlock_irqrestore(&par->lock, flags);
|
| 208 |
|
| 209 | return 0;
|
| 210 | }
|
| 211 |
|
| 212 | static const struct sbus_mmap_map cg3_mmap_map[] = {
|
| 213 | {
|
| 214 | .voff = CG3_MMAP_OFFSET,
|
| 215 | .poff = CG3_RAM_OFFSET,
|
| 216 | .size = SBUS_MMAP_FBSIZE(1)
|
| 217 | },
|
| 218 | { .size = 0 }
|
| 219 | };
|
| 220 |
|
| 221 | static int cg3_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
| 222 | {
|
| 223 | struct cg3_par *par = (struct cg3_par *)info->par;
|
| 224 |
|
| 225 | return sbusfb_mmap_helper(cg3_mmap_map,
|
| 226 | info->fix.smem_start, info->fix.smem_len,
|
| 227 | par->which_io,
|
| 228 | vma);
|
| 229 | }
|
| 230 |
|
| 231 | static int cg3_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
|
| 232 | {
|
| 233 | return sbusfb_ioctl_helper(cmd, arg, info,
|
| 234 | FBTYPE_SUN3COLOR, 8, info->fix.smem_len);
|
| 235 | }
|
| 236 |
|
| 237 | |
| 238 | * Initialisation
|
| 239 |
|
| 240 |
|
| 241 | static void cg3_init_fix(struct fb_info *info, int linebytes,
|
| 242 | struct device_node *dp)
|
| 243 | {
|
| 244 | snprintf(info->fix.id, sizeof(info->fix.id), "%pOFn", dp);
|
| 245 |
|
| 246 | info->fix.type = FB_TYPE_PACKED_PIXELS;
|
| 247 | info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
|
| 248 |
|
| 249 | info->fix.line_length = linebytes;
|
| 250 |
|
| 251 | info->fix.accel = FB_ACCEL_SUN_CGTHREE;
|
| 252 | }
|
| 253 |
|
| 254 | static void cg3_rdi_maybe_fixup_var(struct fb_var_screeninfo *var,
|
| 255 | struct device_node *dp)
|
| 256 | {
|
| 257 | const char *params;
|
| 258 | char *p;
|
| 259 | int ww, hh;
|
| 260 |
|
| 261 | params = of_get_property(dp, "params", NULL);
|
| 262 | if (params) {
|
| 263 | ww = simple_strtoul(params, &p, 10);
|
| 264 | if (ww && *p == 'x') {
|
| 265 | hh = simple_strtoul(p + 1, &p, 10);
|
| 266 | if (hh && *p == '-') {
|
| 267 | if (var->xres != ww ||
|
| 268 | var->yres != hh) {
|
| 269 | var->xres = var->xres_virtual = ww;
|
| 270 | var->yres = var->yres_virtual = hh;
|
| 271 | }
|
| 272 | }
|
| 273 | }
|
| 274 | }
|
| 275 | }
|
| 276 |
|
| 277 | static u8 cg3regvals_66hz[] = {
|
| 278 | 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
|
| 279 | 0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
|
| 280 | 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
|
| 281 | 0x10, 0x20, 0
|
| 282 | };
|
| 283 |
|
| 284 | static u8 cg3regvals_76hz[] = {
|
| 285 | 0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
|
| 286 | 0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
|
| 287 | 0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
|
| 288 | 0x10, 0x24, 0
|
| 289 | };
|
| 290 |
|
| 291 | static u8 cg3regvals_rdi[] = {
|
| 292 | 0x14, 0x70, 0x15, 0x20, 0x16, 0x08, 0x17, 0x10,
|
| 293 | 0x18, 0x06, 0x19, 0x02, 0x1a, 0x31, 0x1b, 0x51,
|
| 294 | 0x1c, 0x06, 0x1d, 0x0c, 0x1e, 0xff, 0x1f, 0x01,
|
| 295 | 0x10, 0x22, 0
|
| 296 | };
|
| 297 |
|
| 298 | static u8 *cg3_regvals[] = {
|
| 299 | cg3regvals_66hz, cg3regvals_76hz, cg3regvals_rdi
|
| 300 | };
|
| 301 |
|
| 302 | static u_char cg3_dacvals[] = {
|
| 303 | 4, 0xff, 5, 0x00, 6, 0x70, 7, 0x00, 0
|
| 304 | };
|
| 305 |
|
| 306 | static int cg3_do_default_mode(struct cg3_par *par)
|
| 307 | {
|
| 308 | enum cg3_type type;
|
| 309 | u8 *p;
|
| 310 |
|
| 311 | if (par->flags & CG3_FLAG_RDI)
|
| 312 | type = CG3_RDI;
|
| 313 | else {
|
| 314 | u8 status = sbus_readb(&par->regs->status), mon;
|
| 315 | if ((status & CG3_SR_ID_MASK) == CG3_SR_ID_COLOR) {
|
| 316 | mon = status & CG3_SR_RES_MASK;
|
| 317 | if (mon == CG3_SR_1152_900_76_A ||
|
| 318 | mon == CG3_SR_1152_900_76_B)
|
| 319 | type = CG3_AT_76HZ;
|
| 320 | else
|
| 321 | type = CG3_AT_66HZ;
|
| 322 | } else {
|
| 323 | printk(KERN_ERR "cgthree: can't handle SR %02x\n",
|
| 324 | status);
|
| 325 | return -EINVAL;
|
| 326 | }
|
| 327 | }
|
| 328 |
|
| 329 | for (p = cg3_regvals[type]; *p; p += 2) {
|
| 330 | u8 __iomem *regp = &((u8 __iomem *)par->regs)[p[0]];
|
| 331 | sbus_writeb(p[1], regp);
|
| 332 | }
|
| 333 | for (p = cg3_dacvals; *p; p += 2) {
|
| 334 | u8 __iomem *regp;
|
| 335 |
|
| 336 | regp = (u8 __iomem *)&par->regs->cmap.addr;
|
| 337 | sbus_writeb(p[0], regp);
|
| 338 | regp = (u8 __iomem *)&par->regs->cmap.control;
|
| 339 | sbus_writeb(p[1], regp);
|
| 340 | }
|
| 341 | return 0;
|
| 342 | }
|
| 343 |
|
| 344 | static int cg3_probe(struct platform_device *op)
|
| 345 | {
|
| 346 | struct device_node *dp = op->dev.of_node;
|
| 347 | struct fb_info *info;
|
| 348 | struct cg3_par *par;
|
| 349 | int linebytes, err;
|
| 350 |
|
| 351 | info = framebuffer_alloc(sizeof(struct cg3_par), &op->dev);
|
| 352 |
|
| 353 | err = -ENOMEM;
|
| 354 | if (!info)
|
| 355 | goto out_err;
|
| 356 | par = info->par;
|
| 357 |
|
| 358 | spin_lock_init(&par->lock);
|
| 359 |
|
| 360 | info->fix.smem_start = op->resource[0].start;
|
| 361 | par->which_io = op->resource[0].flags & IORESOURCE_BITS;
|
| 362 |
|
| 363 | sbusfb_fill_var(&info->var, dp, 8);
|
| 364 | info->var.red.length = 8;
|
| 365 | info->var.green.length = 8;
|
| 366 | info->var.blue.length = 8;
|
| 367 | if (of_node_name_eq(dp, "cgRDI"))
|
| 368 | par->flags |= CG3_FLAG_RDI;
|
| 369 | if (par->flags & CG3_FLAG_RDI)
|
| 370 | cg3_rdi_maybe_fixup_var(&info->var, dp);
|
| 371 |
|
| 372 | linebytes = of_getintprop_default(dp, "linebytes",
|
| 373 | info->var.xres);
|
| 374 | info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres);
|
| 375 |
|
| 376 | par->regs = of_ioremap(&op->resource[0], CG3_REGS_OFFSET,
|
| 377 | sizeof(struct cg3_regs), "cg3 regs");
|
| 378 | if (!par->regs)
|
| 379 | goto out_release_fb;
|
| 380 |
|
| 381 | info->fbops = &cg3_ops;
|
| 382 | info->screen_base = of_ioremap(&op->resource[0], CG3_RAM_OFFSET,
|
| 383 | info->fix.smem_len, "cg3 ram");
|
| 384 | if (!info->screen_base)
|
| 385 | goto out_unmap_regs;
|
| 386 |
|
| 387 | cg3_blank(FB_BLANK_UNBLANK, info);
|
| 388 |
|
| 389 | if (!of_property_present(dp, "width")) {
|
| 390 | err = cg3_do_default_mode(par);
|
| 391 | if (err)
|
| 392 | goto out_unmap_screen;
|
| 393 | }
|
| 394 |
|
| 395 | err = fb_alloc_cmap(&info->cmap, 256, 0);
|
| 396 | if (err)
|
| 397 | goto out_unmap_screen;
|
| 398 |
|
| 399 | fb_set_cmap(&info->cmap, info);
|
| 400 |
|
| 401 | cg3_init_fix(info, linebytes, dp);
|
| 402 |
|
| 403 | err = register_framebuffer(info);
|
| 404 | if (err < 0)
|
| 405 | goto out_dealloc_cmap;
|
| 406 |
|
| 407 | dev_set_drvdata(&op->dev, info);
|
| 408 |
|
| 409 | printk(KERN_INFO "%pOF: cg3 at %lx:%lx\n",
|
| 410 | dp, par->which_io, info->fix.smem_start);
|
| 411 |
|
| 412 | return 0;
|
| 413 |
|
| 414 | out_dealloc_cmap:
|
| 415 | fb_dealloc_cmap(&info->cmap);
|
| 416 |
|
| 417 | out_unmap_screen:
|
| 418 | of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len);
|
| 419 |
|
| 420 | out_unmap_regs:
|
| 421 | of_iounmap(&op->resource[0], par->regs, sizeof(struct cg3_regs));
|
| 422 |
|
| 423 | out_release_fb:
|
| 424 | framebuffer_release(info);
|
| 425 |
|
| 426 | out_err:
|
| 427 | return err;
|
| 428 | }
|
| 429 |
|
| 430 | static void cg3_remove(struct platform_device *op)
|
| 431 | {
|
| 432 | struct fb_info *info = dev_get_drvdata(&op->dev);
|
| 433 | struct cg3_par *par = info->par;
|
| 434 |
|
| 435 | unregister_framebuffer(info);
|
| 436 | fb_dealloc_cmap(&info->cmap);
|
| 437 |
|
| 438 | of_iounmap(&op->resource[0], par->regs, sizeof(struct cg3_regs));
|
| 439 | of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len);
|
| 440 |
|
| 441 | framebuffer_release(info);
|
| 442 | }
|
| 443 |
|
| 444 | static const struct of_device_id cg3_match[] = {
|
| 445 | {
|
| 446 | .name = "cgthree",
|
| 447 | },
|
| 448 | {
|
| 449 | .name = "cgRDI",
|
| 450 | },
|
| 451 | {},
|
| 452 | };
|
| 453 | MODULE_DEVICE_TABLE(of, cg3_match);
|
| 454 |
|
| 455 | static struct platform_driver cg3_driver = {
|
| 456 | .driver = {
|
| 457 | .name = "cg3",
|
| 458 | .of_match_table = cg3_match,
|
| 459 | },
|
| 460 | .probe = cg3_probe,
|
| 461 | .remove = cg3_remove,
|
| 462 | };
|
| 463 |
|
| 464 | static int __init cg3_init(void)
|
| 465 | {
|
| 466 | if (fb_get_options("cg3fb", NULL))
|
| 467 | return -ENODEV;
|
| 468 |
|
| 469 | return platform_driver_register(&cg3_driver);
|
| 470 | }
|
| 471 |
|
| 472 | static void __exit cg3_exit(void)
|
| 473 | {
|
| 474 | platform_driver_unregister(&cg3_driver);
|
| 475 | }
|
| 476 |
|
| 477 | module_init(cg3_init);
|
| 478 | module_exit(cg3_exit);
|
| 479 |
|
| 480 | MODULE_DESCRIPTION("framebuffer driver for CGthree chipsets");
|
| 481 | MODULE_AUTHOR("David S. Miller <[email protected]>");
|
| 482 | MODULE_VERSION("2.0");
|
| 483 | MODULE_LICENSE("GPL");
|