| 1 | |
| 2 | |
| 3 | * HP300 Topcat framebuffer support (derived from macfb of all things)
|
| 4 | * Phil Blundell <[email protected]> 1998
|
| 5 | * DIO-II, colour map and Catseye support by
|
| 6 | * Kars de Jong <[email protected]>, May 2004.
|
| 7 |
|
| 8 |
|
| 9 | #include <linux/module.h>
|
| 10 | #include <linux/kernel.h>
|
| 11 | #include <linux/errno.h>
|
| 12 | #include <linux/string.h>
|
| 13 | #include <linux/mm.h>
|
| 14 | #include <linux/delay.h>
|
| 15 | #include <linux/init.h>
|
| 16 | #include <linux/fb.h>
|
| 17 | #include <linux/dio.h>
|
| 18 |
|
| 19 | #include <asm/io.h>
|
| 20 | #include <linux/uaccess.h>
|
| 21 |
|
| 22 | static struct fb_info fb_info = {
|
| 23 | .fix = {
|
| 24 | .id = "HP300 ",
|
| 25 | .type = FB_TYPE_PACKED_PIXELS,
|
| 26 | .visual = FB_VISUAL_PSEUDOCOLOR,
|
| 27 | .accel = FB_ACCEL_NONE,
|
| 28 | }
|
| 29 | };
|
| 30 |
|
| 31 | static unsigned long fb_regs;
|
| 32 | static unsigned char fb_bitmask;
|
| 33 |
|
| 34 | #define TC_NBLANK 0x4080
|
| 35 | #define TC_WEN 0x4088
|
| 36 | #define TC_REN 0x408c
|
| 37 | #define TC_FBEN 0x4090
|
| 38 | #define TC_PRR 0x40ea
|
| 39 |
|
| 40 |
|
| 41 | #define RR_CLEAR 0x0
|
| 42 | #define RR_COPY 0x3
|
| 43 | #define RR_NOOP 0x5
|
| 44 | #define RR_XOR 0x6
|
| 45 | #define RR_INVERT 0xa
|
| 46 | #define RR_COPYINVERTED 0xc
|
| 47 | #define RR_SET 0xf
|
| 48 |
|
| 49 |
|
| 50 | #define BUSY 0x4044
|
| 51 | #define WMRR 0x40ef
|
| 52 | #define SOURCE_X 0x40f2
|
| 53 | #define SOURCE_Y 0x40f6
|
| 54 | #define DEST_X 0x40fa
|
| 55 | #define DEST_Y 0x40fe
|
| 56 | #define WHEIGHT 0x4106
|
| 57 | #define WWIDTH 0x4102
|
| 58 | #define WMOVE 0x409c
|
| 59 |
|
| 60 | static struct fb_var_screeninfo hpfb_defined = {
|
| 61 | .red = {
|
| 62 | .length = 8,
|
| 63 | },
|
| 64 | .green = {
|
| 65 | .length = 8,
|
| 66 | },
|
| 67 | .blue = {
|
| 68 | .length = 8,
|
| 69 | },
|
| 70 | .activate = FB_ACTIVATE_NOW,
|
| 71 | .height = -1,
|
| 72 | .width = -1,
|
| 73 | .vmode = FB_VMODE_NONINTERLACED,
|
| 74 | };
|
| 75 |
|
| 76 | static int hpfb_setcolreg(unsigned regno, unsigned red, unsigned green,
|
| 77 | unsigned blue, unsigned transp,
|
| 78 | struct fb_info *info)
|
| 79 | {
|
| 80 |
|
| 81 | unsigned char _red =red>>8;
|
| 82 | unsigned char _green=green>>8;
|
| 83 | unsigned char _blue =blue>>8;
|
| 84 | unsigned char _regno=regno;
|
| 85 |
|
| 86 | |
| 87 | * Set a single color register. The values supplied are
|
| 88 | * already rounded down to the hardware's capabilities
|
| 89 | * (according to the entries in the `var' structure). Return
|
| 90 | * != 0 for invalid regno.
|
| 91 |
|
| 92 |
|
| 93 | if (regno >= info->cmap.len)
|
| 94 | return 1;
|
| 95 |
|
| 96 | while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1);
|
| 97 |
|
| 98 | out_be16(fb_regs + 0x60ba, 0xff);
|
| 99 |
|
| 100 | out_be16(fb_regs + 0x60b2, _red);
|
| 101 | out_be16(fb_regs + 0x60b4, _green);
|
| 102 | out_be16(fb_regs + 0x60b6, _blue);
|
| 103 | out_be16(fb_regs + 0x60b8, ~_regno);
|
| 104 | out_be16(fb_regs + 0x60f0, 0xff);
|
| 105 |
|
| 106 | udelay(100);
|
| 107 |
|
| 108 | while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1);
|
| 109 | out_be16(fb_regs + 0x60b2, 0);
|
| 110 | out_be16(fb_regs + 0x60b4, 0);
|
| 111 | out_be16(fb_regs + 0x60b6, 0);
|
| 112 | out_be16(fb_regs + 0x60b8, 0);
|
| 113 |
|
| 114 | return 0;
|
| 115 | }
|
| 116 |
|
| 117 |
|
| 118 |
|
| 119 | static int hpfb_blank(int blank, struct fb_info *info)
|
| 120 | {
|
| 121 | out_8(fb_regs + TC_NBLANK, (blank ? 0x00 : fb_bitmask));
|
| 122 |
|
| 123 | return 0;
|
| 124 | }
|
| 125 |
|
| 126 | static void topcat_blit(int x0, int y0, int x1, int y1, int w, int h, int rr)
|
| 127 | {
|
| 128 | if (rr >= 0) {
|
| 129 | while (in_8(fb_regs + BUSY) & fb_bitmask)
|
| 130 | ;
|
| 131 | }
|
| 132 | out_8(fb_regs + TC_FBEN, fb_bitmask);
|
| 133 | if (rr >= 0) {
|
| 134 | out_8(fb_regs + TC_WEN, fb_bitmask);
|
| 135 | out_8(fb_regs + WMRR, rr);
|
| 136 | }
|
| 137 | out_be16(fb_regs + SOURCE_X, x0);
|
| 138 | out_be16(fb_regs + SOURCE_Y, y0);
|
| 139 | out_be16(fb_regs + DEST_X, x1);
|
| 140 | out_be16(fb_regs + DEST_Y, y1);
|
| 141 | out_be16(fb_regs + WWIDTH, w);
|
| 142 | out_be16(fb_regs + WHEIGHT, h);
|
| 143 | out_8(fb_regs + WMOVE, fb_bitmask);
|
| 144 | }
|
| 145 |
|
| 146 | static void hpfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)
|
| 147 | {
|
| 148 | topcat_blit(area->sx, area->sy, area->dx, area->dy, area->width, area->height, RR_COPY);
|
| 149 | }
|
| 150 |
|
| 151 | static void hpfb_fillrect(struct fb_info *p, const struct fb_fillrect *region)
|
| 152 | {
|
| 153 | u8 clr;
|
| 154 |
|
| 155 | clr = region->color & 0xff;
|
| 156 |
|
| 157 | while (in_8(fb_regs + BUSY) & fb_bitmask)
|
| 158 | ;
|
| 159 |
|
| 160 |
|
| 161 | out_8(fb_regs + TC_WEN, fb_bitmask & clr);
|
| 162 | out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_SET : RR_INVERT));
|
| 163 |
|
| 164 |
|
| 165 | out_8(fb_regs + TC_WEN, fb_bitmask & ~clr);
|
| 166 | out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_CLEAR : RR_NOOP));
|
| 167 |
|
| 168 | topcat_blit(region->dx, region->dy, region->dx, region->dy, region->width, region->height, -1);
|
| 169 | }
|
| 170 |
|
| 171 | static int hpfb_sync(struct fb_info *info)
|
| 172 | {
|
| 173 | |
| 174 | * Since we also access the framebuffer directly, we have to wait
|
| 175 | * until the block mover is finished
|
| 176 |
|
| 177 | while (in_8(fb_regs + BUSY) & fb_bitmask)
|
| 178 | ;
|
| 179 |
|
| 180 | out_8(fb_regs + TC_WEN, fb_bitmask);
|
| 181 | out_8(fb_regs + TC_PRR, RR_COPY);
|
| 182 | out_8(fb_regs + TC_FBEN, fb_bitmask);
|
| 183 |
|
| 184 | return 0;
|
| 185 | }
|
| 186 |
|
| 187 | static const struct fb_ops hpfb_ops = {
|
| 188 | .owner = THIS_MODULE,
|
| 189 | __FB_DEFAULT_IOMEM_OPS_RDWR,
|
| 190 | .fb_setcolreg = hpfb_setcolreg,
|
| 191 | .fb_blank = hpfb_blank,
|
| 192 | .fb_fillrect = hpfb_fillrect,
|
| 193 | .fb_copyarea = hpfb_copyarea,
|
| 194 | .fb_imageblit = cfb_imageblit,
|
| 195 | .fb_sync = hpfb_sync,
|
| 196 | __FB_DEFAULT_IOMEM_OPS_MMAP,
|
| 197 | };
|
| 198 |
|
| 199 |
|
| 200 | #define HPFB_FBWMSB 0x05
|
| 201 | #define HPFB_FBWLSB 0x07
|
| 202 | #define HPFB_FBHMSB 0x09
|
| 203 | #define HPFB_FBHLSB 0x0b
|
| 204 | #define HPFB_DWMSB 0x0d
|
| 205 | #define HPFB_DWLSB 0x0f
|
| 206 | #define HPFB_DHMSB 0x11
|
| 207 | #define HPFB_DHLSB 0x13
|
| 208 | #define HPFB_NUMPLANES 0x5b
|
| 209 | #define HPFB_FBOMSB 0x5d
|
| 210 | #define HPFB_FBOLSB 0x5f
|
| 211 |
|
| 212 | static int hpfb_init_one(unsigned long phys_base, unsigned long virt_base)
|
| 213 | {
|
| 214 | unsigned long fboff, fb_width, fb_height, fb_start;
|
| 215 | int ret;
|
| 216 |
|
| 217 | fb_regs = virt_base;
|
| 218 | fboff = (in_8(fb_regs + HPFB_FBOMSB) << 8) | in_8(fb_regs + HPFB_FBOLSB);
|
| 219 |
|
| 220 | fb_info.fix.smem_start = (in_8(fb_regs + fboff) << 16);
|
| 221 |
|
| 222 | if (phys_base >= DIOII_BASE) {
|
| 223 | fb_info.fix.smem_start += phys_base;
|
| 224 | }
|
| 225 |
|
| 226 | if (DIO_SECID(fb_regs) != DIO_ID2_TOPCAT) {
|
| 227 |
|
| 228 | while (in_be16(fb_regs+0x4800) & 1)
|
| 229 | ;
|
| 230 | out_be16(fb_regs+0x4800, 0);
|
| 231 | out_be16(fb_regs+0x4510, 0);
|
| 232 | out_be16(fb_regs+0x4512, 0);
|
| 233 | out_be16(fb_regs+0x4514, 0);
|
| 234 | out_be16(fb_regs+0x4516, 0);
|
| 235 | out_be16(fb_regs+0x4206, 0x90);
|
| 236 | out_be16(fb_regs+0x60a2, 0);
|
| 237 | out_be16(fb_regs+0x60bc, 0);
|
| 238 | }
|
| 239 |
|
| 240 | |
| 241 | * Fill in the available video resolution
|
| 242 |
|
| 243 | fb_width = (in_8(fb_regs + HPFB_FBWMSB) << 8) | in_8(fb_regs + HPFB_FBWLSB);
|
| 244 | fb_info.fix.line_length = fb_width;
|
| 245 | fb_height = (in_8(fb_regs + HPFB_FBHMSB) << 8) | in_8(fb_regs + HPFB_FBHLSB);
|
| 246 | fb_info.fix.smem_len = fb_width * fb_height;
|
| 247 | fb_start = (unsigned long)ioremap_wt(fb_info.fix.smem_start,
|
| 248 | fb_info.fix.smem_len);
|
| 249 | hpfb_defined.xres = (in_8(fb_regs + HPFB_DWMSB) << 8) | in_8(fb_regs + HPFB_DWLSB);
|
| 250 | hpfb_defined.yres = (in_8(fb_regs + HPFB_DHMSB) << 8) | in_8(fb_regs + HPFB_DHLSB);
|
| 251 | hpfb_defined.xres_virtual = hpfb_defined.xres;
|
| 252 | hpfb_defined.yres_virtual = hpfb_defined.yres;
|
| 253 | hpfb_defined.bits_per_pixel = in_8(fb_regs + HPFB_NUMPLANES);
|
| 254 |
|
| 255 | printk(KERN_INFO "hpfb: framebuffer at 0x%lx, mapped to 0x%lx, size %dk\n",
|
| 256 | fb_info.fix.smem_start, fb_start, fb_info.fix.smem_len/1024);
|
| 257 | printk(KERN_INFO "hpfb: mode is %dx%dx%d, linelength=%d\n",
|
| 258 | hpfb_defined.xres, hpfb_defined.yres, hpfb_defined.bits_per_pixel, fb_info.fix.line_length);
|
| 259 |
|
| 260 | |
| 261 | * Give the hardware a bit of a prod and work out how many bits per
|
| 262 | * pixel are supported.
|
| 263 |
|
| 264 | out_8(fb_regs + TC_WEN, 0xff);
|
| 265 | out_8(fb_regs + TC_PRR, RR_COPY);
|
| 266 | out_8(fb_regs + TC_FBEN, 0xff);
|
| 267 | out_8(fb_start, 0xff);
|
| 268 | fb_bitmask = in_8(fb_start);
|
| 269 | out_8(fb_start, 0);
|
| 270 |
|
| 271 | |
| 272 | * Enable reading/writing of all the planes.
|
| 273 |
|
| 274 | out_8(fb_regs + TC_WEN, fb_bitmask);
|
| 275 | out_8(fb_regs + TC_PRR, RR_COPY);
|
| 276 | out_8(fb_regs + TC_REN, fb_bitmask);
|
| 277 | out_8(fb_regs + TC_FBEN, fb_bitmask);
|
| 278 |
|
| 279 | |
| 280 | * Clear the screen.
|
| 281 |
|
| 282 | topcat_blit(0, 0, 0, 0, fb_width, fb_height, RR_CLEAR);
|
| 283 |
|
| 284 | |
| 285 | * Let there be consoles..
|
| 286 |
|
| 287 | if (DIO_SECID(fb_regs) == DIO_ID2_TOPCAT)
|
| 288 | strcat(fb_info.fix.id, "Topcat");
|
| 289 | else
|
| 290 | strcat(fb_info.fix.id, "Catseye");
|
| 291 | fb_info.fbops = &hpfb_ops;
|
| 292 | fb_info.var = hpfb_defined;
|
| 293 | fb_info.screen_base = (char *)fb_start;
|
| 294 |
|
| 295 | ret = fb_alloc_cmap(&fb_info.cmap, 1 << hpfb_defined.bits_per_pixel, 0);
|
| 296 | if (ret < 0)
|
| 297 | goto unmap_screen_base;
|
| 298 |
|
| 299 | ret = register_framebuffer(&fb_info);
|
| 300 | if (ret < 0)
|
| 301 | goto dealloc_cmap;
|
| 302 |
|
| 303 | fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id);
|
| 304 |
|
| 305 | return 0;
|
| 306 |
|
| 307 | dealloc_cmap:
|
| 308 | fb_dealloc_cmap(&fb_info.cmap);
|
| 309 |
|
| 310 | unmap_screen_base:
|
| 311 | if (fb_info.screen_base) {
|
| 312 | iounmap(fb_info.screen_base);
|
| 313 | fb_info.screen_base = NULL;
|
| 314 | }
|
| 315 |
|
| 316 | return ret;
|
| 317 | }
|
| 318 |
|
| 319 | |
| 320 | * Check that the secondary ID indicates that we have some hope of working with this
|
| 321 | * framebuffer. The catseye boards are pretty much like topcats and we can muddle through.
|
| 322 |
|
| 323 |
|
| 324 | #define topcat_sid_ok(x) (((x) == DIO_ID2_LRCATSEYE) || ((x) == DIO_ID2_HRCCATSEYE) \
|
| 325 | || ((x) == DIO_ID2_HRMCATSEYE) || ((x) == DIO_ID2_TOPCAT))
|
| 326 |
|
| 327 | |
| 328 | * Initialise the framebuffer
|
| 329 |
|
| 330 | static int hpfb_dio_probe(struct dio_dev *d, const struct dio_device_id *ent)
|
| 331 | {
|
| 332 | unsigned long paddr, vaddr;
|
| 333 |
|
| 334 | paddr = d->resource.start;
|
| 335 | if (!request_mem_region(d->resource.start, resource_size(&d->resource), d->name))
|
| 336 | return -EBUSY;
|
| 337 |
|
| 338 | if (d->scode >= DIOII_SCBASE) {
|
| 339 | vaddr = (unsigned long)ioremap(paddr, resource_size(&d->resource));
|
| 340 | } else {
|
| 341 | vaddr = paddr + DIO_VIRADDRBASE;
|
| 342 | }
|
| 343 | printk(KERN_INFO "Topcat found at DIO select code %d "
|
| 344 | "(secondary id %02x)\n", d->scode, (d->id >> 8) & 0xff);
|
| 345 | if (hpfb_init_one(paddr, vaddr)) {
|
| 346 | if (d->scode >= DIOII_SCBASE)
|
| 347 | iounmap((void *)vaddr);
|
| 348 | release_mem_region(d->resource.start, resource_size(&d->resource));
|
| 349 | return -ENOMEM;
|
| 350 | }
|
| 351 | return 0;
|
| 352 | }
|
| 353 |
|
| 354 | static void hpfb_remove_one(struct dio_dev *d)
|
| 355 | {
|
| 356 | unregister_framebuffer(&fb_info);
|
| 357 | if (d->scode >= DIOII_SCBASE)
|
| 358 | iounmap((void *)fb_regs);
|
| 359 | release_mem_region(d->resource.start, resource_size(&d->resource));
|
| 360 | fb_dealloc_cmap(&fb_info.cmap);
|
| 361 | if (fb_info.screen_base)
|
| 362 | iounmap(fb_info.screen_base);
|
| 363 | }
|
| 364 |
|
| 365 | static struct dio_device_id hpfb_dio_tbl[] = {
|
| 366 | { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_LRCATSEYE) },
|
| 367 | { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRCCATSEYE) },
|
| 368 | { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRMCATSEYE) },
|
| 369 | { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_TOPCAT) },
|
| 370 | { 0 }
|
| 371 | };
|
| 372 |
|
| 373 | static struct dio_driver hpfb_driver = {
|
| 374 | .name = "hpfb",
|
| 375 | .id_table = hpfb_dio_tbl,
|
| 376 | .probe = hpfb_dio_probe,
|
| 377 | .remove = hpfb_remove_one,
|
| 378 | };
|
| 379 |
|
| 380 | static int __init hpfb_init(void)
|
| 381 | {
|
| 382 | unsigned int sid;
|
| 383 | unsigned char i;
|
| 384 | int err;
|
| 385 |
|
| 386 | |
| 387 | * The internal variant sits at 0x560000; it has primary
|
| 388 | * and secondary ID registers just like the DIO version.
|
| 389 | * So we merge the two detection routines.
|
| 390 | *
|
| 391 | * Perhaps this #define should be in a global header file:
|
| 392 | * I believe it's common to all internal fbs, not just topcat.
|
| 393 |
|
| 394 | #define INTFBVADDR 0xf0560000
|
| 395 | #define INTFBPADDR 0x560000
|
| 396 |
|
| 397 | if (!MACH_IS_HP300)
|
| 398 | return -ENODEV;
|
| 399 |
|
| 400 | if (fb_get_options("hpfb", NULL))
|
| 401 | return -ENODEV;
|
| 402 |
|
| 403 | err = dio_register_driver(&hpfb_driver);
|
| 404 | if (err)
|
| 405 | return err;
|
| 406 |
|
| 407 | err = copy_from_kernel_nofault(&i, (unsigned char *)INTFBVADDR + DIO_IDOFF, 1);
|
| 408 |
|
| 409 | if (!err && (i == DIO_ID_FBUFFER) && topcat_sid_ok(sid = DIO_SECID(INTFBVADDR))) {
|
| 410 | if (!request_mem_region(INTFBPADDR, DIO_DEVSIZE, "Internal Topcat"))
|
| 411 | return -EBUSY;
|
| 412 | printk(KERN_INFO "Internal Topcat found (secondary id %02x)\n", sid);
|
| 413 | if (hpfb_init_one(INTFBPADDR, INTFBVADDR)) {
|
| 414 | return -ENOMEM;
|
| 415 | }
|
| 416 | }
|
| 417 | return 0;
|
| 418 | }
|
| 419 |
|
| 420 | static void __exit hpfb_cleanup_module(void)
|
| 421 | {
|
| 422 | dio_unregister_driver(&hpfb_driver);
|
| 423 | }
|
| 424 |
|
| 425 | module_init(hpfb_init);
|
| 426 | module_exit(hpfb_cleanup_module);
|
| 427 |
|
| 428 | MODULE_LICENSE("GPL");
|