linux/drivers/video/fbdev/hpfb.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 *      HP300 Topcat framebuffer support (derived from macfb of all things)
   4 *      Phil Blundell <philb@gnu.org> 1998
   5 *      DIO-II, colour map and Catseye support by
   6 *      Kars de Jong <jongk@linux-m68k.org>, 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
  22static 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
  31static unsigned long fb_regs;
  32static 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/* These defines match the X window system */
  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/* blitter regs */
  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
  60static 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
  76static int hpfb_setcolreg(unsigned regno, unsigned red, unsigned green,
  77                          unsigned blue, unsigned transp,
  78                          struct fb_info *info)
  79{
  80        /* use MSBs */
  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/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */
 118
 119static 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
 126static 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
 146static 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
 151static 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        /* Foreground */
 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        /* Background */
 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
 171static 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
 187static const struct fb_ops hpfb_ops = {
 188        .owner          = THIS_MODULE,
 189        .fb_setcolreg   = hpfb_setcolreg,
 190        .fb_blank       = hpfb_blank,
 191        .fb_fillrect    = hpfb_fillrect,
 192        .fb_copyarea    = hpfb_copyarea,
 193        .fb_imageblit   = cfb_imageblit,
 194        .fb_sync        = hpfb_sync,
 195};
 196
 197/* Common to all HP framebuffers */
 198#define HPFB_FBWMSB     0x05    /* Frame buffer width           */
 199#define HPFB_FBWLSB     0x07
 200#define HPFB_FBHMSB     0x09    /* Frame buffer height          */
 201#define HPFB_FBHLSB     0x0b
 202#define HPFB_DWMSB      0x0d    /* Display width                */
 203#define HPFB_DWLSB      0x0f
 204#define HPFB_DHMSB      0x11    /* Display height               */
 205#define HPFB_DHLSB      0x13
 206#define HPFB_NUMPLANES  0x5b    /* Number of colour planes      */
 207#define HPFB_FBOMSB     0x5d    /* Frame buffer offset          */
 208#define HPFB_FBOLSB     0x5f
 209
 210static int hpfb_init_one(unsigned long phys_base, unsigned long virt_base)
 211{
 212        unsigned long fboff, fb_width, fb_height, fb_start;
 213        int ret;
 214
 215        fb_regs = virt_base;
 216        fboff = (in_8(fb_regs + HPFB_FBOMSB) << 8) | in_8(fb_regs + HPFB_FBOLSB);
 217
 218        fb_info.fix.smem_start = (in_8(fb_regs + fboff) << 16);
 219
 220        if (phys_base >= DIOII_BASE) {
 221                fb_info.fix.smem_start += phys_base;
 222        }
 223
 224        if (DIO_SECID(fb_regs) != DIO_ID2_TOPCAT) {
 225                /* This is the magic incantation the HP X server uses to make Catseye boards work. */
 226                while (in_be16(fb_regs+0x4800) & 1)
 227                        ;
 228                out_be16(fb_regs+0x4800, 0);    /* Catseye status */
 229                out_be16(fb_regs+0x4510, 0);    /* VB */
 230                out_be16(fb_regs+0x4512, 0);    /* TCNTRL */
 231                out_be16(fb_regs+0x4514, 0);    /* ACNTRL */
 232                out_be16(fb_regs+0x4516, 0);    /* PNCNTRL */
 233                out_be16(fb_regs+0x4206, 0x90); /* RUG Command/Status */
 234                out_be16(fb_regs+0x60a2, 0);    /* Overlay Mask */
 235                out_be16(fb_regs+0x60bc, 0);    /* Ram Select */
 236        }
 237
 238        /*
 239         *      Fill in the available video resolution
 240         */
 241        fb_width = (in_8(fb_regs + HPFB_FBWMSB) << 8) | in_8(fb_regs + HPFB_FBWLSB);
 242        fb_info.fix.line_length = fb_width;
 243        fb_height = (in_8(fb_regs + HPFB_FBHMSB) << 8) | in_8(fb_regs + HPFB_FBHLSB);
 244        fb_info.fix.smem_len = fb_width * fb_height;
 245        fb_start = (unsigned long)ioremap_wt(fb_info.fix.smem_start,
 246                                             fb_info.fix.smem_len);
 247        hpfb_defined.xres = (in_8(fb_regs + HPFB_DWMSB) << 8) | in_8(fb_regs + HPFB_DWLSB);
 248        hpfb_defined.yres = (in_8(fb_regs + HPFB_DHMSB) << 8) | in_8(fb_regs + HPFB_DHLSB);
 249        hpfb_defined.xres_virtual = hpfb_defined.xres;
 250        hpfb_defined.yres_virtual = hpfb_defined.yres;
 251        hpfb_defined.bits_per_pixel = in_8(fb_regs + HPFB_NUMPLANES);
 252
 253        printk(KERN_INFO "hpfb: framebuffer at 0x%lx, mapped to 0x%lx, size %dk\n",
 254               fb_info.fix.smem_start, fb_start, fb_info.fix.smem_len/1024);
 255        printk(KERN_INFO "hpfb: mode is %dx%dx%d, linelength=%d\n",
 256               hpfb_defined.xres, hpfb_defined.yres, hpfb_defined.bits_per_pixel, fb_info.fix.line_length);
 257
 258        /*
 259         *      Give the hardware a bit of a prod and work out how many bits per
 260         *      pixel are supported.
 261         */
 262        out_8(fb_regs + TC_WEN, 0xff);
 263        out_8(fb_regs + TC_PRR, RR_COPY);
 264        out_8(fb_regs + TC_FBEN, 0xff);
 265        out_8(fb_start, 0xff);
 266        fb_bitmask = in_8(fb_start);
 267        out_8(fb_start, 0);
 268
 269        /*
 270         *      Enable reading/writing of all the planes.
 271         */
 272        out_8(fb_regs + TC_WEN, fb_bitmask);
 273        out_8(fb_regs + TC_PRR, RR_COPY);
 274        out_8(fb_regs + TC_REN, fb_bitmask);
 275        out_8(fb_regs + TC_FBEN, fb_bitmask);
 276
 277        /*
 278         *      Clear the screen.
 279         */
 280        topcat_blit(0, 0, 0, 0, fb_width, fb_height, RR_CLEAR);
 281
 282        /*
 283         *      Let there be consoles..
 284         */
 285        if (DIO_SECID(fb_regs) == DIO_ID2_TOPCAT)
 286                strcat(fb_info.fix.id, "Topcat");
 287        else
 288                strcat(fb_info.fix.id, "Catseye");
 289        fb_info.fbops = &hpfb_ops;
 290        fb_info.flags = FBINFO_DEFAULT;
 291        fb_info.var   = hpfb_defined;
 292        fb_info.screen_base = (char *)fb_start;
 293
 294        ret = fb_alloc_cmap(&fb_info.cmap, 1 << hpfb_defined.bits_per_pixel, 0);
 295        if (ret < 0)
 296                goto unmap_screen_base;
 297
 298        ret = register_framebuffer(&fb_info);
 299        if (ret < 0)
 300                goto dealloc_cmap;
 301
 302        fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id);
 303
 304        return 0;
 305
 306dealloc_cmap:
 307        fb_dealloc_cmap(&fb_info.cmap);
 308
 309unmap_screen_base:
 310        if (fb_info.screen_base) {
 311                iounmap(fb_info.screen_base);
 312                fb_info.screen_base = NULL;
 313        }
 314
 315        return ret;
 316}
 317
 318/* 
 319 * Check that the secondary ID indicates that we have some hope of working with this
 320 * framebuffer.  The catseye boards are pretty much like topcats and we can muddle through.
 321 */
 322
 323#define topcat_sid_ok(x)  (((x) == DIO_ID2_LRCATSEYE) || ((x) == DIO_ID2_HRCCATSEYE)    \
 324                           || ((x) == DIO_ID2_HRMCATSEYE) || ((x) == DIO_ID2_TOPCAT))
 325
 326/* 
 327 * Initialise the framebuffer
 328 */
 329static int hpfb_dio_probe(struct dio_dev *d, const struct dio_device_id *ent)
 330{
 331        unsigned long paddr, vaddr;
 332
 333        paddr = d->resource.start;
 334        if (!request_mem_region(d->resource.start, resource_size(&d->resource), d->name))
 335                return -EBUSY;
 336
 337        if (d->scode >= DIOII_SCBASE) {
 338                vaddr = (unsigned long)ioremap(paddr, resource_size(&d->resource));
 339        } else {
 340                vaddr = paddr + DIO_VIRADDRBASE;
 341        }
 342        printk(KERN_INFO "Topcat found at DIO select code %d "
 343               "(secondary id %02x)\n", d->scode, (d->id >> 8) & 0xff);
 344        if (hpfb_init_one(paddr, vaddr)) {
 345                if (d->scode >= DIOII_SCBASE)
 346                        iounmap((void *)vaddr);
 347                return -ENOMEM;
 348        }
 349        return 0;
 350}
 351
 352static void hpfb_remove_one(struct dio_dev *d)
 353{
 354        unregister_framebuffer(&fb_info);
 355        if (d->scode >= DIOII_SCBASE)
 356                iounmap((void *)fb_regs);
 357        release_mem_region(d->resource.start, resource_size(&d->resource));
 358        fb_dealloc_cmap(&fb_info.cmap);
 359        if (fb_info.screen_base)
 360                iounmap(fb_info.screen_base);
 361}
 362
 363static struct dio_device_id hpfb_dio_tbl[] = {
 364    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_LRCATSEYE) },
 365    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRCCATSEYE) },
 366    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRMCATSEYE) },
 367    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_TOPCAT) },
 368    { 0 }
 369};
 370
 371static struct dio_driver hpfb_driver = {
 372    .name      = "hpfb",
 373    .id_table  = hpfb_dio_tbl,
 374    .probe     = hpfb_dio_probe,
 375    .remove    = hpfb_remove_one,
 376};
 377
 378int __init hpfb_init(void)
 379{
 380        unsigned int sid;
 381        unsigned char i;
 382        int err;
 383
 384        /* Topcats can be on the internal IO bus or real DIO devices.
 385         * The internal variant sits at 0x560000; it has primary
 386         * and secondary ID registers just like the DIO version.
 387         * So we merge the two detection routines.
 388         *
 389         * Perhaps this #define should be in a global header file:
 390         * I believe it's common to all internal fbs, not just topcat.
 391         */
 392#define INTFBVADDR 0xf0560000
 393#define INTFBPADDR 0x560000
 394
 395        if (!MACH_IS_HP300)
 396                return -ENODEV;
 397
 398        if (fb_get_options("hpfb", NULL))
 399                return -ENODEV;
 400
 401        err = dio_register_driver(&hpfb_driver);
 402        if (err)
 403                return err;
 404
 405        err = copy_from_kernel_nofault(&i, (unsigned char *)INTFBVADDR + DIO_IDOFF, 1);
 406
 407        if (!err && (i == DIO_ID_FBUFFER) && topcat_sid_ok(sid = DIO_SECID(INTFBVADDR))) {
 408                if (!request_mem_region(INTFBPADDR, DIO_DEVSIZE, "Internal Topcat"))
 409                        return -EBUSY;
 410                printk(KERN_INFO "Internal Topcat found (secondary id %02x)\n", sid);
 411                if (hpfb_init_one(INTFBPADDR, INTFBVADDR)) {
 412                        return -ENOMEM;
 413                }
 414        }
 415        return 0;
 416}
 417
 418void __exit hpfb_cleanup_module(void)
 419{
 420        dio_unregister_driver(&hpfb_driver);
 421}
 422
 423module_init(hpfb_init);
 424module_exit(hpfb_cleanup_module);
 425
 426MODULE_LICENSE("GPL");
 427