| 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 bw2_blank(int, struct fb_info *);
|
| 33 |
|
| 34 | static int bw2_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma);
|
| 35 | static int bw2_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg);
|
| 36 |
|
| 37 | |
| 38 | * Frame buffer operations
|
| 39 |
|
| 40 |
|
| 41 | static const struct fb_ops bw2_ops = {
|
| 42 | .owner = THIS_MODULE,
|
| 43 | FB_DEFAULT_SBUS_OPS(bw2),
|
| 44 | .fb_blank = bw2_blank,
|
| 45 | };
|
| 46 |
|
| 47 |
|
| 48 | #define BWTWO_REGISTER_OFFSET 0x400000
|
| 49 |
|
| 50 | struct bt_regs {
|
| 51 | u32 addr;
|
| 52 | u32 color_map;
|
| 53 | u32 control;
|
| 54 | u32 cursor;
|
| 55 | };
|
| 56 |
|
| 57 | struct bw2_regs {
|
| 58 | struct bt_regs cmap;
|
| 59 | u8 control;
|
| 60 | u8 status;
|
| 61 | u8 cursor_start;
|
| 62 | u8 cursor_end;
|
| 63 | u8 h_blank_start;
|
| 64 | u8 h_blank_end;
|
| 65 | u8 h_sync_start;
|
| 66 | u8 h_sync_end;
|
| 67 | u8 comp_sync_end;
|
| 68 | u8 v_blank_start_high;
|
| 69 | u8 v_blank_start_low;
|
| 70 | u8 v_blank_end;
|
| 71 | u8 v_sync_start;
|
| 72 | u8 v_sync_end;
|
| 73 | u8 xfer_holdoff_start;
|
| 74 | u8 xfer_holdoff_end;
|
| 75 | };
|
| 76 |
|
| 77 |
|
| 78 | #define BWTWO_SR_RES_MASK 0x70
|
| 79 | #define BWTWO_SR_1600_1280 0x50
|
| 80 | #define BWTWO_SR_1152_900_76_A 0x40
|
| 81 | #define BWTWO_SR_1152_900_76_B 0x60
|
| 82 | #define BWTWO_SR_ID_MASK 0x0f
|
| 83 | #define BWTWO_SR_ID_MONO 0x02
|
| 84 | #define BWTWO_SR_ID_MONO_ECL 0x03
|
| 85 | #define BWTWO_SR_ID_MSYNC 0x04
|
| 86 | #define BWTWO_SR_ID_NOCONN 0x0a
|
| 87 |
|
| 88 |
|
| 89 | #define BWTWO_CTL_ENABLE_INTS 0x80
|
| 90 | #define BWTWO_CTL_ENABLE_VIDEO 0x40
|
| 91 | #define BWTWO_CTL_ENABLE_TIMING 0x20
|
| 92 | #define BWTWO_CTL_ENABLE_CURCMP 0x10
|
| 93 | #define BWTWO_CTL_XTAL_MASK 0x0C
|
| 94 | #define BWTWO_CTL_DIVISOR_MASK 0x03
|
| 95 |
|
| 96 |
|
| 97 | #define BWTWO_STAT_PENDING_INT 0x80
|
| 98 | #define BWTWO_STAT_MSENSE_MASK 0x70
|
| 99 | #define BWTWO_STAT_ID_MASK 0x0f
|
| 100 |
|
| 101 | struct bw2_par {
|
| 102 | spinlock_t lock;
|
| 103 | struct bw2_regs __iomem *regs;
|
| 104 |
|
| 105 | u32 flags;
|
| 106 | #define BW2_FLAG_BLANKED 0x00000001
|
| 107 |
|
| 108 | unsigned long which_io;
|
| 109 | };
|
| 110 |
|
| 111 | |
| 112 | * bw2_blank - Optional function. Blanks the display.
|
| 113 | * @blank: the blank mode we want.
|
| 114 | * @info: frame buffer structure that represents a single frame buffer
|
| 115 |
|
| 116 | static int
|
| 117 | bw2_blank(int blank, struct fb_info *info)
|
| 118 | {
|
| 119 | struct bw2_par *par = (struct bw2_par *) info->par;
|
| 120 | struct bw2_regs __iomem *regs = par->regs;
|
| 121 | unsigned long flags;
|
| 122 | u8 val;
|
| 123 |
|
| 124 | spin_lock_irqsave(&par->lock, flags);
|
| 125 |
|
| 126 | switch (blank) {
|
| 127 | case FB_BLANK_UNBLANK:
|
| 128 | val = sbus_readb(®s->control);
|
| 129 | val |= BWTWO_CTL_ENABLE_VIDEO;
|
| 130 | sbus_writeb(val, ®s->control);
|
| 131 | par->flags &= ~BW2_FLAG_BLANKED;
|
| 132 | break;
|
| 133 |
|
| 134 | case FB_BLANK_NORMAL:
|
| 135 | case FB_BLANK_VSYNC_SUSPEND:
|
| 136 | case FB_BLANK_HSYNC_SUSPEND:
|
| 137 | case FB_BLANK_POWERDOWN:
|
| 138 | val = sbus_readb(®s->control);
|
| 139 | val &= ~BWTWO_CTL_ENABLE_VIDEO;
|
| 140 | sbus_writeb(val, ®s->control);
|
| 141 | par->flags |= BW2_FLAG_BLANKED;
|
| 142 | break;
|
| 143 | }
|
| 144 |
|
| 145 | spin_unlock_irqrestore(&par->lock, flags);
|
| 146 |
|
| 147 | return 0;
|
| 148 | }
|
| 149 |
|
| 150 | static const struct sbus_mmap_map bw2_mmap_map[] = {
|
| 151 | {
|
| 152 | .size = SBUS_MMAP_FBSIZE(1)
|
| 153 | },
|
| 154 | { .size = 0 }
|
| 155 | };
|
| 156 |
|
| 157 | static int bw2_sbusfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
|
| 158 | {
|
| 159 | struct bw2_par *par = (struct bw2_par *)info->par;
|
| 160 |
|
| 161 | return sbusfb_mmap_helper(bw2_mmap_map,
|
| 162 | info->fix.smem_start, info->fix.smem_len,
|
| 163 | par->which_io,
|
| 164 | vma);
|
| 165 | }
|
| 166 |
|
| 167 | static int bw2_sbusfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
|
| 168 | {
|
| 169 | return sbusfb_ioctl_helper(cmd, arg, info,
|
| 170 | FBTYPE_SUN2BW, 1, info->fix.smem_len);
|
| 171 | }
|
| 172 |
|
| 173 | |
| 174 | * Initialisation
|
| 175 |
|
| 176 |
|
| 177 | static void bw2_init_fix(struct fb_info *info, int linebytes)
|
| 178 | {
|
| 179 | strscpy(info->fix.id, "bwtwo", sizeof(info->fix.id));
|
| 180 |
|
| 181 | info->fix.type = FB_TYPE_PACKED_PIXELS;
|
| 182 | info->fix.visual = FB_VISUAL_MONO01;
|
| 183 |
|
| 184 | info->fix.line_length = linebytes;
|
| 185 |
|
| 186 | info->fix.accel = FB_ACCEL_SUN_BWTWO;
|
| 187 | }
|
| 188 |
|
| 189 | static u8 bw2regs_1600[] = {
|
| 190 | 0x14, 0x8b, 0x15, 0x28, 0x16, 0x03, 0x17, 0x13,
|
| 191 | 0x18, 0x7b, 0x19, 0x05, 0x1a, 0x34, 0x1b, 0x2e,
|
| 192 | 0x1c, 0x00, 0x1d, 0x0a, 0x1e, 0xff, 0x1f, 0x01,
|
| 193 | 0x10, 0x21, 0
|
| 194 | };
|
| 195 |
|
| 196 | static u8 bw2regs_ecl[] = {
|
| 197 | 0x14, 0x65, 0x15, 0x1e, 0x16, 0x04, 0x17, 0x0c,
|
| 198 | 0x18, 0x5e, 0x19, 0x03, 0x1a, 0xa7, 0x1b, 0x23,
|
| 199 | 0x1c, 0x00, 0x1d, 0x08, 0x1e, 0xff, 0x1f, 0x01,
|
| 200 | 0x10, 0x20, 0
|
| 201 | };
|
| 202 |
|
| 203 | static u8 bw2regs_analog[] = {
|
| 204 | 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x03, 0x17, 0x13,
|
| 205 | 0x18, 0xb0, 0x19, 0x03, 0x1a, 0xa6, 0x1b, 0x22,
|
| 206 | 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
|
| 207 | 0x10, 0x20, 0
|
| 208 | };
|
| 209 |
|
| 210 | static u8 bw2regs_76hz[] = {
|
| 211 | 0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
|
| 212 | 0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
|
| 213 | 0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
|
| 214 | 0x10, 0x24, 0
|
| 215 | };
|
| 216 |
|
| 217 | static u8 bw2regs_66hz[] = {
|
| 218 | 0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
|
| 219 | 0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
|
| 220 | 0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
|
| 221 | 0x10, 0x20, 0
|
| 222 | };
|
| 223 |
|
| 224 | static int bw2_do_default_mode(struct bw2_par *par, struct fb_info *info,
|
| 225 | int *linebytes)
|
| 226 | {
|
| 227 | u8 status, mon;
|
| 228 | u8 *p;
|
| 229 |
|
| 230 | status = sbus_readb(&par->regs->status);
|
| 231 | mon = status & BWTWO_SR_RES_MASK;
|
| 232 | switch (status & BWTWO_SR_ID_MASK) {
|
| 233 | case BWTWO_SR_ID_MONO_ECL:
|
| 234 | if (mon == BWTWO_SR_1600_1280) {
|
| 235 | p = bw2regs_1600;
|
| 236 | info->var.xres = info->var.xres_virtual = 1600;
|
| 237 | info->var.yres = info->var.yres_virtual = 1280;
|
| 238 | *linebytes = 1600 / 8;
|
| 239 | } else
|
| 240 | p = bw2regs_ecl;
|
| 241 | break;
|
| 242 |
|
| 243 | case BWTWO_SR_ID_MONO:
|
| 244 | p = bw2regs_analog;
|
| 245 | break;
|
| 246 |
|
| 247 | case BWTWO_SR_ID_MSYNC:
|
| 248 | if (mon == BWTWO_SR_1152_900_76_A ||
|
| 249 | mon == BWTWO_SR_1152_900_76_B)
|
| 250 | p = bw2regs_76hz;
|
| 251 | else
|
| 252 | p = bw2regs_66hz;
|
| 253 | break;
|
| 254 |
|
| 255 | case BWTWO_SR_ID_NOCONN:
|
| 256 | return 0;
|
| 257 |
|
| 258 | default:
|
| 259 | printk(KERN_ERR "bw2: can't handle SR %02x\n",
|
| 260 | status);
|
| 261 | return -EINVAL;
|
| 262 | }
|
| 263 | for ( ; *p; p += 2) {
|
| 264 | u8 __iomem *regp = &((u8 __iomem *)par->regs)[p[0]];
|
| 265 | sbus_writeb(p[1], regp);
|
| 266 | }
|
| 267 | return 0;
|
| 268 | }
|
| 269 |
|
| 270 | static int bw2_probe(struct platform_device *op)
|
| 271 | {
|
| 272 | struct device_node *dp = op->dev.of_node;
|
| 273 | struct fb_info *info;
|
| 274 | struct bw2_par *par;
|
| 275 | int linebytes, err;
|
| 276 |
|
| 277 | info = framebuffer_alloc(sizeof(struct bw2_par), &op->dev);
|
| 278 |
|
| 279 | err = -ENOMEM;
|
| 280 | if (!info)
|
| 281 | goto out_err;
|
| 282 | par = info->par;
|
| 283 |
|
| 284 | spin_lock_init(&par->lock);
|
| 285 |
|
| 286 | info->fix.smem_start = op->resource[0].start;
|
| 287 | par->which_io = op->resource[0].flags & IORESOURCE_BITS;
|
| 288 |
|
| 289 | sbusfb_fill_var(&info->var, dp, 1);
|
| 290 | linebytes = of_getintprop_default(dp, "linebytes",
|
| 291 | info->var.xres);
|
| 292 |
|
| 293 | info->var.red.length = info->var.green.length =
|
| 294 | info->var.blue.length = info->var.bits_per_pixel;
|
| 295 | info->var.red.offset = info->var.green.offset =
|
| 296 | info->var.blue.offset = 0;
|
| 297 |
|
| 298 | par->regs = of_ioremap(&op->resource[0], BWTWO_REGISTER_OFFSET,
|
| 299 | sizeof(struct bw2_regs), "bw2 regs");
|
| 300 | if (!par->regs)
|
| 301 | goto out_release_fb;
|
| 302 |
|
| 303 | if (!of_property_present(dp, "width")) {
|
| 304 | err = bw2_do_default_mode(par, info, &linebytes);
|
| 305 | if (err)
|
| 306 | goto out_unmap_regs;
|
| 307 | }
|
| 308 |
|
| 309 | info->fix.smem_len = PAGE_ALIGN(linebytes * info->var.yres);
|
| 310 |
|
| 311 | info->fbops = &bw2_ops;
|
| 312 |
|
| 313 | info->screen_base = of_ioremap(&op->resource[0], 0,
|
| 314 | info->fix.smem_len, "bw2 ram");
|
| 315 | if (!info->screen_base) {
|
| 316 | err = -ENOMEM;
|
| 317 | goto out_unmap_regs;
|
| 318 | }
|
| 319 |
|
| 320 | bw2_blank(FB_BLANK_UNBLANK, info);
|
| 321 |
|
| 322 | bw2_init_fix(info, linebytes);
|
| 323 |
|
| 324 | err = register_framebuffer(info);
|
| 325 | if (err < 0)
|
| 326 | goto out_unmap_screen;
|
| 327 |
|
| 328 | dev_set_drvdata(&op->dev, info);
|
| 329 |
|
| 330 | printk(KERN_INFO "%pOF: bwtwo at %lx:%lx\n",
|
| 331 | dp, par->which_io, info->fix.smem_start);
|
| 332 |
|
| 333 | return 0;
|
| 334 |
|
| 335 | out_unmap_screen:
|
| 336 | of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len);
|
| 337 |
|
| 338 | out_unmap_regs:
|
| 339 | of_iounmap(&op->resource[0], par->regs, sizeof(struct bw2_regs));
|
| 340 |
|
| 341 | out_release_fb:
|
| 342 | framebuffer_release(info);
|
| 343 |
|
| 344 | out_err:
|
| 345 | return err;
|
| 346 | }
|
| 347 |
|
| 348 | static void bw2_remove(struct platform_device *op)
|
| 349 | {
|
| 350 | struct fb_info *info = dev_get_drvdata(&op->dev);
|
| 351 | struct bw2_par *par = info->par;
|
| 352 |
|
| 353 | unregister_framebuffer(info);
|
| 354 |
|
| 355 | of_iounmap(&op->resource[0], par->regs, sizeof(struct bw2_regs));
|
| 356 | of_iounmap(&op->resource[0], info->screen_base, info->fix.smem_len);
|
| 357 |
|
| 358 | framebuffer_release(info);
|
| 359 | }
|
| 360 |
|
| 361 | static const struct of_device_id bw2_match[] = {
|
| 362 | {
|
| 363 | .name = "bwtwo",
|
| 364 | },
|
| 365 | {},
|
| 366 | };
|
| 367 | MODULE_DEVICE_TABLE(of, bw2_match);
|
| 368 |
|
| 369 | static struct platform_driver bw2_driver = {
|
| 370 | .driver = {
|
| 371 | .name = "bw2",
|
| 372 | .of_match_table = bw2_match,
|
| 373 | },
|
| 374 | .probe = bw2_probe,
|
| 375 | .remove = bw2_remove,
|
| 376 | };
|
| 377 |
|
| 378 | static int __init bw2_init(void)
|
| 379 | {
|
| 380 | if (fb_get_options("bw2fb", NULL))
|
| 381 | return -ENODEV;
|
| 382 |
|
| 383 | return platform_driver_register(&bw2_driver);
|
| 384 | }
|
| 385 |
|
| 386 | static void __exit bw2_exit(void)
|
| 387 | {
|
| 388 | platform_driver_unregister(&bw2_driver);
|
| 389 | }
|
| 390 |
|
| 391 | module_init(bw2_init);
|
| 392 | module_exit(bw2_exit);
|
| 393 |
|
| 394 | MODULE_DESCRIPTION("framebuffer driver for BWTWO chipsets");
|
| 395 | MODULE_AUTHOR("David S. Miller <[email protected]>");
|
| 396 | MODULE_VERSION("2.0");
|
| 397 | MODULE_LICENSE("GPL");
|