Commit 57b55f79 authored by David S. Miller's avatar David S. Miller

[FRAMEBUFFER]: Convert bw2, cg6, and cg3 drivers to new APIs.

parent 41ae6422
...@@ -376,19 +376,19 @@ config FB_SUN3 ...@@ -376,19 +376,19 @@ config FB_SUN3
bool "Sun3 framebuffer support" bool "Sun3 framebuffer support"
depends on FB && (SUN3 || SUN3X) depends on FB && (SUN3 || SUN3X)
config FB_BWTWO config FB_BW2
bool "BWtwo support" bool "BWtwo support"
depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3) depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3)
help help
This is the frame buffer device driver for the BWtwo frame buffer. This is the frame buffer device driver for the BWtwo frame buffer.
config FB_CGTHREE config FB_CG3
bool "CGthree support" bool "CGthree support"
depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3) depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3)
help help
This is the frame buffer device driver for the CGthree frame buffer. This is the frame buffer device driver for the CGthree frame buffer.
config FB_CGSIX config FB_CG6
bool "CGsix (GX,TurboGX) support" bool "CGsix (GX,TurboGX) support"
depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3) depends on FB && ((SPARC32 || SPARC64) && FB_SBUS || (SUN3 || SUN3X) && FB_SUN3)
help help
......
...@@ -71,16 +71,17 @@ obj-$(CONFIG_FB_PVR2) += pvr2fb.o ...@@ -71,16 +71,17 @@ obj-$(CONFIG_FB_PVR2) += pvr2fb.o
obj-$(CONFIG_FB_VOODOO1) += sstfb.o cfbfillrect.o cfbcopyarea.o cfbimgblt.o obj-$(CONFIG_FB_VOODOO1) += sstfb.o cfbfillrect.o cfbcopyarea.o cfbimgblt.o
# One by one these are being converted over to the new APIs # One by one these are being converted over to the new APIs
#obj-$(CONFIG_FB_CREATOR) += creatorfb.o sbusfb.o
#obj-$(CONFIG_FB_CGSIX) += cgsixfb.o sbusfb.o
#obj-$(CONFIG_FB_BWTWO) += bwtwofb.o sbusfb.o
#obj-$(CONFIG_FB_CGTHREE) += cgthreefb.o sbusfb.o
#obj-$(CONFIG_FB_TCX) += tcxfb.o sbusfb.o #obj-$(CONFIG_FB_TCX) += tcxfb.o sbusfb.o
#obj-$(CONFIG_FB_CGFOURTEEN) += cgfourteenfb.o sbusfb.o #obj-$(CONFIG_FB_CGFOURTEEN) += cgfourteenfb.o sbusfb.o
#obj-$(CONFIG_FB_P9100) += p9100fb.o sbusfb.o #obj-$(CONFIG_FB_P9100) += p9100fb.o sbusfb.o
#obj-$(CONFIG_FB_LEO) += leofb.o sbusfb.o #obj-$(CONFIG_FB_LEO) += leofb.o sbusfb.o
obj-$(CONFIG_FB_FFB) += ffb.o sbuslib.o cfbimgblt.o cfbcopyarea.o obj-$(CONFIG_FB_FFB) += ffb.o sbuslib.o cfbimgblt.o cfbcopyarea.o
obj-$(CONFIG_FB_CG6) += cg6.o sbuslib.o cfbimgblt.o cfbcopyarea.o
obj-$(CONFIG_FB_CG3) += cg3.o sbuslib.o cfbimgblt.o cfbcopyarea.o \
cfbfillrect.o
obj-$(CONFIG_FB_BW2) += bw2.o sbuslib.o cfbimgblt.o cfbcopyarea.o \
cfbfillrect.o
# Files generated that shall be removed upon make clean # Files generated that shall be removed upon make clean
clean-files := promcon_tbl.c clean-files := promcon_tbl.c
......
/* bw2.c: BWTWO frame buffer driver
*
* Copyright (C) 2003 David S. Miller (davem@redhat.com)
* Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*
* Driver layout based loosely on tgafb.c, see that file for credits.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/sbus.h>
#include <asm/oplib.h>
#include <asm/fbio.h>
#ifdef CONFIG_SPARC32
#include <asm/sun4paddr.h>
#endif
#include "sbuslib.h"
/*
* Local functions.
*/
static int bw2_check_var(struct fb_var_screeninfo *, struct fb_info *);
static int bw2_set_par(struct fb_info *);
static int bw2_blank(int, struct fb_info *);
static int bw2_mmap(struct fb_info *, struct file *, struct vm_area_struct *);
/*
* Frame buffer operations
*/
static struct fb_ops bw2_ops = {
.owner = THIS_MODULE,
.fb_check_var = bw2_check_var,
.fb_set_par = bw2_set_par,
.fb_blank = bw2_blank,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = bw2_mmap,
.fb_cursor = soft_cursor,
};
/* OBio addresses for the bwtwo registers */
#define BWTWO_REGISTER_OFFSET 0x400000
struct bt_regs {
volatile u32 addr;
volatile u32 color_map;
volatile u32 control;
volatile u32 cursor;
};
struct bw2_regs {
struct bt_regs cmap;
volatile u8 control;
volatile u8 status;
volatile u8 cursor_start;
volatile u8 cursor_end;
volatile u8 h_blank_start;
volatile u8 h_blank_end;
volatile u8 h_sync_start;
volatile u8 h_sync_end;
volatile u8 comp_sync_end;
volatile u8 v_blank_start_high;
volatile u8 v_blank_start_low;
volatile u8 v_blank_end;
volatile u8 v_sync_start;
volatile u8 v_sync_end;
volatile u8 xfer_holdoff_start;
volatile u8 xfer_holdoff_end;
};
/* Status Register Constants */
#define BWTWO_SR_RES_MASK 0x70
#define BWTWO_SR_1600_1280 0x50
#define BWTWO_SR_1152_900_76_A 0x40
#define BWTWO_SR_1152_900_76_B 0x60
#define BWTWO_SR_ID_MASK 0x0f
#define BWTWO_SR_ID_MONO 0x02
#define BWTWO_SR_ID_MONO_ECL 0x03
#define BWTWO_SR_ID_MSYNC 0x04
#define BWTWO_SR_ID_NOCONN 0x0a
/* Control Register Constants */
#define BWTWO_CTL_ENABLE_INTS 0x80
#define BWTWO_CTL_ENABLE_VIDEO 0x40
#define BWTWO_CTL_ENABLE_TIMING 0x20
#define BWTWO_CTL_ENABLE_CURCMP 0x10
#define BWTWO_CTL_XTAL_MASK 0x0C
#define BWTWO_CTL_DIVISOR_MASK 0x03
/* Status Register Constants */
#define BWTWO_STAT_PENDING_INT 0x80
#define BWTWO_STAT_MSENSE_MASK 0x70
#define BWTWO_STAT_ID_MASK 0x0f
struct bw2_par {
spinlock_t lock;
struct bw2_regs *regs;
u32 flags;
#define BW2_FLAG_BLANKED 0x00000001
unsigned long physbase;
unsigned long fbsize;
struct sbus_dev *sdev;
struct list_head list;
};
/**
* bw2_check_var - Optional function. Validates a var passed in.
* @var: frame buffer variable screen structure
* @info: frame buffer structure that represents a single frame buffer
*/
static int bw2_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
if (var->bits_per_pixel != 8)
return -EINVAL;
if (var->xres_virtual != var->xres || var->yres_virtual != var->yres)
return -EINVAL;
if (var->nonstd)
return -EINVAL;
if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED)
return -EINVAL;
if (var->xres != info->var.xres || var->yres != info->var.yres)
return -EINVAL;
return 0;
}
/**
* bw2_set_par - Optional function. Alters the hardware state.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
bw2_set_par(struct fb_info *info)
{
return 0;
}
/**
* bw2_blank - Optional function. Blanks the display.
* @blank_mode: the blank mode we want.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
bw2_blank(int blank, struct fb_info *info)
{
struct bw2_par *par = (struct bw2_par *) info->par;
struct bw2_regs *regs = par->regs;
unsigned long flags;
u8 val;
spin_lock_irqsave(&par->lock, flags);
switch (blank) {
case 0: /* Unblanking */
val = sbus_readb(&regs->control);
val |= BWTWO_CTL_ENABLE_VIDEO;
sbus_writeb(val, &regs->control);
par->flags &= ~BW2_FLAG_BLANKED;
break;
case 1: /* Normal blanking */
case 2: /* VESA blank (vsync off) */
case 3: /* VESA blank (hsync off) */
case 4: /* Poweroff */
val = sbus_readb(&regs->control);
val &= ~BWTWO_CTL_ENABLE_VIDEO;
sbus_writeb(val, &regs->control);
par->flags |= BW2_FLAG_BLANKED;
break;
}
spin_unlock_irqrestore(&par->lock, flags);
return 0;
}
static struct sbus_mmap_map bw2_mmap_map[] = {
{ 0, 0, SBUS_MMAP_FBSIZE(1) },
{ 0, 0, 0 }
};
static int bw2_mmap(struct fb_info *info, struct file *file, struct vm_area_struct *vma)
{
struct bw2_par *par = (struct bw2_par *)info->par;
return sbusfb_mmap_helper(bw2_mmap_map,
par->physbase, par->fbsize,
(par->sdev ?
par->sdev->reg_addrs[0].which_io :
0),
vma);
}
/*
* Initialisation
*/
static void
bw2_init_fix(struct fb_info *info, int linebytes)
{
strncpy(info->fix.id, "bwtwo", sizeof(info->fix.id) - 1);
info->fix.id[sizeof(info->fix.id)-1] = 0;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_MONO01;
info->fix.line_length = linebytes;
info->fix.accel = FB_ACCEL_SUN_BWTWO;
}
static u8 bw2regs_1600[] __initdata = {
0x14, 0x8b, 0x15, 0x28, 0x16, 0x03, 0x17, 0x13,
0x18, 0x7b, 0x19, 0x05, 0x1a, 0x34, 0x1b, 0x2e,
0x1c, 0x00, 0x1d, 0x0a, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x21, 0
};
static u8 bw2regs_ecl[] __initdata = {
0x14, 0x65, 0x15, 0x1e, 0x16, 0x04, 0x17, 0x0c,
0x18, 0x5e, 0x19, 0x03, 0x1a, 0xa7, 0x1b, 0x23,
0x1c, 0x00, 0x1d, 0x08, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 bw2regs_analog[] __initdata = {
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x03, 0x17, 0x13,
0x18, 0xb0, 0x19, 0x03, 0x1a, 0xa6, 0x1b, 0x22,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 bw2regs_76hz[] __initdata = {
0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x24, 0
};
static u8 bw2regs_66hz[] __initdata = {
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static void bw2_do_default_mode(struct bw2_par *par, struct fb_info *info,
int *linebytes)
{
u8 status, mon;
u8 *p;
status = sbus_readb(&par->regs->status);
mon = status & BWTWO_SR_RES_MASK;
switch (status & BWTWO_SR_ID_MASK) {
case BWTWO_SR_ID_MONO_ECL:
if (mon == BWTWO_SR_1600_1280) {
p = bw2regs_1600;
info->var.xres = info->var.xres_virtual = 1600;
info->var.yres = info->var.yres_virtual = 1280;
*linebytes = 1600 / 8;
} else
p = bw2regs_ecl;
break;
case BWTWO_SR_ID_MONO:
p = bw2regs_analog;
break;
case BWTWO_SR_ID_MSYNC:
if (mon == BWTWO_SR_1152_900_76_A ||
mon == BWTWO_SR_1152_900_76_B)
p = bw2regs_76hz;
else
p = bw2regs_66hz;
break;
case BWTWO_SR_ID_NOCONN:
return;
default:
prom_printf("bw2: can't handle SR %02x\n",
status);
prom_halt();
}
for ( ; *p; p += 2) {
u8 *regp = &((u8 *)par->regs)[p[0]];
sbus_writeb(p[1], regp);
}
}
struct all_info {
struct fb_info info;
struct bw2_par par;
struct list_head list;
};
static LIST_HEAD(bw2_list);
static void bw2_init_one(struct sbus_dev *sdev)
{
struct all_info *all;
struct resource *resp;
#ifdef CONFIG_SUN4
struct resource res;
#endif
int linebytes;
all = kmalloc(sizeof(*all), GFP_KERNEL);
if (!all) {
printk(KERN_ERR "bw2: Cannot allocate memory.\n");
return;
}
memset(all, 0, sizeof(*all));
INIT_LIST_HEAD(&all->list);
spin_lock_init(&all->par.lock);
all->par.sdev = sdev;
#ifdef CONFIG_SUN4
if (!sdev) {
all->par.physbase = sun4_bwtwo_physaddr;
res.start = sun4_bwtwo_physaddr;
res.end = res.start + BWTWO_REGISTER_OFFSET + sizeof(struct bw2_regs) - 1;
res.flags = IORESOURCE_IO;
resp = &res;
all->info.var.xres = all->info.var.xres_virtual = 1152;
all->info.var.yres = all->info.var.yres_virtual = 900;
all->info.bits_per_pixel = 1;
linebytes = 1152 / 8;
} else
#else
{
if (!sdev)
BUG();
all->par.physbase = sdev->reg_addrs[0].phys_addr;
resp = &sdev->resource[0];
sbusfb_fill_var(&all->info.var, (sdev ? sdev->prom_node : 0), 1);
linebytes = prom_getintdefault(sdev->prom_node, "linebytes",
all->info.var.xres);
}
#endif
all->par.regs = (struct bw2_regs *)
sbus_ioremap(resp, BWTWO_REGISTER_OFFSET,
sizeof(struct bw2_regs), "bw2 regs");
if (sdev && !prom_getbool(sdev->prom_node, "width"))
bw2_do_default_mode(&all->par, &all->info, &linebytes);
all->par.fbsize = PAGE_ALIGN(linebytes * all->info.var.yres);
all->info.node = NODEV;
all->info.flags = FBINFO_FLAG_DEFAULT;
all->info.fbops = &bw2_ops;
#if defined(CONFIG_SPARC32)
if (sdev)
all->info.screen_base = (char *)
prom_getintdefault(sdev->prom_node, "address", 0);
#endif
if (!all->info.screen_base)
all->info.screen_base = (char *)
sbus_ioremap(resp, 0, all->par.fbsize, "bw2 ram");
all->info.currcon = -1;
all->info.par = &all->par;
bw2_blank(0, &all->info);
bw2_set_par(&all->info);
bw2_init_fix(&all->info, linebytes);
if (register_framebuffer(&all->info) < 0) {
printk(KERN_ERR "bw2: Could not register framebuffer.\n");
kfree(all);
return;
}
list_add(&all->list, &bw2_list);
printk("bw2: bwtwo at %lx:%lx\n",
(long) (sdev ? sdev->reg_addrs[0].which_io : 0),
(long) all->par.physbase);
}
int __init bw2_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
#ifdef CONFIG_SUN4
bw2_init_one(NULL);
#endif
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "bwtwo"))
bw2_init_one(sdev);
}
return 0;
}
void __exit bw2_exit(void)
{
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &bw2_list) {
struct all_info *all = list_entry(pos, typeof(*all), list);
unregister_framebuffer(&all->info);
kfree(all);
}
}
int __init
bw2_setup(char *arg)
{
/* No cmdline options yet... */
return 0;
}
#ifdef MODULE
module_init(bw2_init);
module_exit(bw2_exit);
#endif
MODULE_DESCRIPTION("framebuffer driver for BWTWO chipsets");
MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
MODULE_LICENSE("GPL");
/* $Id: bwtwofb.c,v 1.15 2001/09/19 00:04:33 davem Exp $
* bwtwofb.c: BWtwo frame buffer driver
*
* Copyright (C) 1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
* Copyright (C) 1998 Pavel Machek (pavel@ucw.cz)
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/selection.h>
#include <video/sbusfb.h>
#include <asm/io.h>
#if !defined(__sparc_v9__) && !defined(__mc68000__)
#include <asm/sun4paddr.h>
#endif
#include <video/fbcon-mfb.h>
/* OBio addresses for the bwtwo registers */
#define BWTWO_REGISTER_OFFSET 0x400000
struct bw2_regs {
struct bt_regs bt;
volatile u8 control;
volatile u8 status;
volatile u8 cursor_start;
volatile u8 cursor_end;
volatile u8 h_blank_start;
volatile u8 h_blank_end;
volatile u8 h_sync_start;
volatile u8 h_sync_end;
volatile u8 comp_sync_end;
volatile u8 v_blank_start_high;
volatile u8 v_blank_start_low;
volatile u8 v_blank_end;
volatile u8 v_sync_start;
volatile u8 v_sync_end;
volatile u8 xfer_holdoff_start;
volatile u8 xfer_holdoff_end;
};
/* Status Register Constants */
#define BWTWO_SR_RES_MASK 0x70
#define BWTWO_SR_1600_1280 0x50
#define BWTWO_SR_1152_900_76_A 0x40
#define BWTWO_SR_1152_900_76_B 0x60
#define BWTWO_SR_ID_MASK 0x0f
#define BWTWO_SR_ID_MONO 0x02
#define BWTWO_SR_ID_MONO_ECL 0x03
#define BWTWO_SR_ID_MSYNC 0x04
#define BWTWO_SR_ID_NOCONN 0x0a
/* Control Register Constants */
#define BWTWO_CTL_ENABLE_INTS 0x80
#define BWTWO_CTL_ENABLE_VIDEO 0x40
#define BWTWO_CTL_ENABLE_TIMING 0x20
#define BWTWO_CTL_ENABLE_CURCMP 0x10
#define BWTWO_CTL_XTAL_MASK 0x0C
#define BWTWO_CTL_DIVISOR_MASK 0x03
/* Status Register Constants */
#define BWTWO_STAT_PENDING_INT 0x80
#define BWTWO_STAT_MSENSE_MASK 0x70
#define BWTWO_STAT_ID_MASK 0x0f
static struct sbus_mmap_map bw2_mmap_map[] = {
{ 0, 0, SBUS_MMAP_FBSIZE(1) },
{ 0, 0, 0 }
};
static int bw2_blank (struct fb_info_sbusfb *fb)
{
unsigned long flags;
u8 tmp;
spin_lock_irqsave(&fb->lock, flags);
tmp = sbus_readb(&fb->s.bw2.regs->control);
tmp &= ~BWTWO_CTL_ENABLE_VIDEO;
sbus_writeb(tmp, &fb->s.bw2.regs->control);
spin_unlock_irqrestore(&fb->lock, flags);
return 0;
}
static int bw2_unblank (struct fb_info_sbusfb *fb)
{
unsigned long flags;
u8 tmp;
spin_lock_irqsave(&fb->lock, flags);
tmp = sbus_readb(&fb->s.bw2.regs->control);
tmp |= BWTWO_CTL_ENABLE_VIDEO;
sbus_writeb(tmp, &fb->s.bw2.regs->control);
spin_unlock_irqrestore(&fb->lock, flags);
return 0;
}
static void bw2_margins (struct fb_info_sbusfb *fb, struct display *p,
int x_margin, int y_margin)
{
fb->info.screen_base += (y_margin - fb->y_margin) *
p->fb_info->fix.line_length + ((x_margin - fb->x_margin) >> 3);
}
static u8 bw2regs_1600[] __initdata = {
0x14, 0x8b, 0x15, 0x28, 0x16, 0x03, 0x17, 0x13,
0x18, 0x7b, 0x19, 0x05, 0x1a, 0x34, 0x1b, 0x2e,
0x1c, 0x00, 0x1d, 0x0a, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x21, 0
};
static u8 bw2regs_ecl[] __initdata = {
0x14, 0x65, 0x15, 0x1e, 0x16, 0x04, 0x17, 0x0c,
0x18, 0x5e, 0x19, 0x03, 0x1a, 0xa7, 0x1b, 0x23,
0x1c, 0x00, 0x1d, 0x08, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 bw2regs_analog[] __initdata = {
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x03, 0x17, 0x13,
0x18, 0xb0, 0x19, 0x03, 0x1a, 0xa6, 0x1b, 0x22,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 bw2regs_76hz[] __initdata = {
0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x24, 0
};
static u8 bw2regs_66hz[] __initdata = {
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static char idstring[60] __initdata = { 0 };
char __init *bwtwofb_init(struct fb_info_sbusfb *fb)
{
struct fb_fix_screeninfo *fix = &fb->info.fix;
struct display *disp = &fb->disp;
struct fbtype *type = &fb->type;
#ifdef CONFIG_SUN4
unsigned long phys = sun4_bwtwo_physaddr;
struct resource res;
#else
unsigned long phys = fb->sbdp->reg_addrs[0].phys_addr;
#endif
struct resource *resp;
unsigned int vaddr;
#ifndef FBCON_HAS_MFB
return NULL;
#endif
#ifdef CONFIG_SUN4
res.start = phys;
res.end = res.start + BWTWO_REGISTER_OFFSET + sizeof(struct bw2_regs) - 1;
res.flags = IORESOURCE_IO | (fb->iospace & 0xff);
resp = &res;
#else
resp = &fb->sbdp->resource[0];
#endif
if (!fb->s.bw2.regs) {
fb->s.bw2.regs = (struct bw2_regs *)
sbus_ioremap(resp, BWTWO_REGISTER_OFFSET,
sizeof(struct bw2_regs), "bw2 regs");
if ((!ARCH_SUN4) && (!prom_getbool(fb->prom_node, "width"))) {
/* Ugh, broken PROM didn't initialize us.
* Let's deal with this ourselves.
*/
u8 status, mon;
u8 *p;
int sizechange = 0;
status = sbus_readb(&fb->s.bw2.regs->status);
mon = status & BWTWO_SR_RES_MASK;
switch (status & BWTWO_SR_ID_MASK) {
case BWTWO_SR_ID_MONO_ECL:
if (mon == BWTWO_SR_1600_1280) {
p = bw2regs_1600;
fb->type.fb_width = 1600;
fb->type.fb_height = 1280;
sizechange = 1;
} else
p = bw2regs_ecl;
break;
case BWTWO_SR_ID_MONO:
p = bw2regs_analog;
break;
case BWTWO_SR_ID_MSYNC:
if (mon == BWTWO_SR_1152_900_76_A ||
mon == BWTWO_SR_1152_900_76_B)
p = bw2regs_76hz;
else
p = bw2regs_66hz;
break;
case BWTWO_SR_ID_NOCONN:
return NULL;
default:
#ifndef CONFIG_FB_SUN3
prom_printf("bw2: can't handle SR %02x\n",
status);
prom_halt();
#endif
return NULL; /* fool gcc. */
}
for ( ; *p; p += 2) {
u8 *regp = &((u8 *)fb->s.bw2.regs)[p[0]];
sbus_writeb(p[1], regp);
}
}
}
strcpy(fb->info.modename, "BWtwo");
strcpy(fix->id, "BWtwo");
fix->line_length = fb->info.var.xres_virtual >> 3;
fix->accel = FB_ACCEL_SUN_BWTWO;
disp->scrollmode = SCROLL_YREDRAW;
disp->inverse = 1;
if (!fb->info.screen_base) {
fb->info.screen_base = (char *)
sbus_ioremap(resp, 0, type->fb_size, "bw2 ram");
}
fb->info.screen_base += fix->line_length * fb->y_margin + (fb->x_margin >> 3);
fb->dispsw = fbcon_mfb;
fix->visual = FB_VISUAL_MONO01;
#ifndef CONFIG_SUN4
fb->blank = bw2_blank;
fb->unblank = bw2_unblank;
prom_getproperty(fb->sbdp->prom_node, "address",
(char *)&vaddr, sizeof(vaddr));
fb->physbase = __get_phys((unsigned long)vaddr);
#endif
fb->margins = bw2_margins;
fb->mmap_map = bw2_mmap_map;
#ifdef __sparc_v9__
sprintf(idstring, "bwtwo at %016lx", phys);
#else
sprintf(idstring, "bwtwo at %x.%08lx", fb->iospace, phys);
#endif
return idstring;
}
MODULE_LICENSE("GPL");
/* cg3.c: CGTHREE frame buffer driver
*
* Copyright (C) 2003 David S. Miller (davem@redhat.com)
* Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*
* Driver layout based loosely on tgafb.c, see that file for credits.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/mm.h>
#include <asm/io.h>
#include <asm/sbus.h>
#include <asm/oplib.h>
#include <asm/fbio.h>
#include "sbuslib.h"
/*
* Local functions.
*/
static int cg3_check_var(struct fb_var_screeninfo *, struct fb_info *);
static int cg3_set_par(struct fb_info *);
static int cg3_setcolreg(unsigned, unsigned, unsigned, unsigned,
unsigned, struct fb_info *);
static int cg3_blank(int, struct fb_info *);
static int cg3_mmap(struct fb_info *, struct file *, struct vm_area_struct *);
/*
* Frame buffer operations
*/
static struct fb_ops cg3_ops = {
.owner = THIS_MODULE,
.fb_check_var = cg3_check_var,
.fb_set_par = cg3_set_par,
.fb_setcolreg = cg3_setcolreg,
.fb_blank = cg3_blank,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_mmap = cg3_mmap,
.fb_cursor = soft_cursor,
};
/* Control Register Constants */
#define CG3_CR_ENABLE_INTS 0x80
#define CG3_CR_ENABLE_VIDEO 0x40
#define CG3_CR_ENABLE_TIMING 0x20
#define CG3_CR_ENABLE_CURCMP 0x10
#define CG3_CR_XTAL_MASK 0x0c
#define CG3_CR_DIVISOR_MASK 0x03
/* Status Register Constants */
#define CG3_SR_PENDING_INT 0x80
#define CG3_SR_RES_MASK 0x70
#define CG3_SR_1152_900_76_A 0x40
#define CG3_SR_1152_900_76_B 0x60
#define CG3_SR_ID_MASK 0x0f
#define CG3_SR_ID_COLOR 0x01
#define CG3_SR_ID_MONO 0x02
#define CG3_SR_ID_MONO_ECL 0x03
enum cg3_type {
CG3_AT_66HZ = 0,
CG3_AT_76HZ,
CG3_RDI
};
struct bt_regs {
volatile u32 addr;
volatile u32 color_map;
volatile u32 control;
volatile u32 cursor;
};
struct cg3_regs {
struct bt_regs cmap;
volatile u8 control;
volatile u8 status;
volatile u8 cursor_start;
volatile u8 cursor_end;
volatile u8 h_blank_start;
volatile u8 h_blank_end;
volatile u8 h_sync_start;
volatile u8 h_sync_end;
volatile u8 comp_sync_end;
volatile u8 v_blank_start_high;
volatile u8 v_blank_start_low;
volatile u8 v_blank_end;
volatile u8 v_sync_start;
volatile u8 v_sync_end;
volatile u8 xfer_holdoff_start;
volatile u8 xfer_holdoff_end;
};
/* Offset of interesting structures in the OBIO space */
#define CG3_REGS_OFFSET 0x400000UL
#define CG3_RAM_OFFSET 0x800000UL
struct cg3_par {
spinlock_t lock;
struct cg3_regs *regs;
u32 sw_cmap[((256 * 3) + 3) / 4];
u32 flags;
#define CG3_FLAG_BLANKED 0x00000001
#define CG3_FLAG_RDI 0x00000002
unsigned long physbase;
unsigned long fbsize;
struct sbus_dev *sdev;
struct list_head list;
};
/**
* cg3_check_var - Optional function. Validates a var passed in.
* @var: frame buffer variable screen structure
* @info: frame buffer structure that represents a single frame buffer
*/
static int cg3_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
if (var->bits_per_pixel != 8)
return -EINVAL;
if (var->xres_virtual != var->xres || var->yres_virtual != var->yres)
return -EINVAL;
if (var->nonstd)
return -EINVAL;
if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED)
return -EINVAL;
if (var->xres != info->var.xres || var->yres != info->var.yres)
return -EINVAL;
return 0;
}
/**
* cg3_set_par - Optional function. Alters the hardware state.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
cg3_set_par(struct fb_info *info)
{
return 0;
}
/**
* cg3_setcolreg - Optional function. Sets a color register.
* @regno: boolean, 0 copy local, 1 get_user() function
* @red: frame buffer colormap structure
* @green: The green value which can be up to 16 bits wide
* @blue: The blue value which can be up to 16 bits wide.
* @transp: If supported the alpha value which can be up to 16 bits wide.
* @info: frame buffer info structure
*
* The cg3 palette is loaded with 4 color values at each time
* so you end up with: (rgb)(r), (gb)(rg), (b)(rgb), and so on.
* We keep a sw copy of the hw cmap to assist us in this esoteric
* loading procedure.
*/
static int cg3_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{
struct cg3_par *par = (struct cg3_par *) info->par;
struct bt_regs *bt = &par->regs->cmap;
unsigned long flags;
u32 *p32;
u8 *p8;
int count;
if (regno >= 256)
return 1;
red >>= 8;
green >>= 8;
blue >>= 8;
spin_lock_irqsave(&par->lock, flags);
p8 = (u8 *)par->sw_cmap + (regno * 3);
p8[0] = red;
p8[1] = green;
p8[2] = blue;
#define D4M3(x) ((((x)>>2)<<1) + ((x)>>2)) /* (x/4)*3 */
#define D4M4(x) ((x)&~0x3) /* (x/4)*4 */
count = 3;
p32 = &par->sw_cmap[D4M3(regno)];
sbus_writel(D4M4(regno), &bt->addr);
while (count--)
sbus_writel(*p32++, &bt->color_map);
#undef D4M3
#undef D4M4
spin_unlock_irqrestore(&par->lock, flags);
return 0;
}
/**
* cg3_blank - Optional function. Blanks the display.
* @blank_mode: the blank mode we want.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
cg3_blank(int blank, struct fb_info *info)
{
struct cg3_par *par = (struct cg3_par *) info->par;
struct cg3_regs *regs = par->regs;
unsigned long flags;
u8 val;
spin_lock_irqsave(&par->lock, flags);
switch (blank) {
case 0: /* Unblanking */
val = sbus_readl(&regs->control);
val |= CG3_CR_ENABLE_VIDEO;
sbus_writel(val, &regs->control);
par->flags &= ~CG3_FLAG_BLANKED;
break;
case 1: /* Normal blanking */
case 2: /* VESA blank (vsync off) */
case 3: /* VESA blank (hsync off) */
case 4: /* Poweroff */
val = sbus_readl(&regs->control);
val |= CG3_CR_ENABLE_VIDEO;
sbus_writel(val, &regs->control);
par->flags |= CG3_FLAG_BLANKED;
break;
}
spin_unlock_irqrestore(&par->lock, flags);
return 0;
}
static struct sbus_mmap_map cg3_mmap_map[] = {
{ CG3_MMAP_OFFSET, CG3_RAM_OFFSET, SBUS_MMAP_FBSIZE(1) },
{ 0, 0, 0 }
};
static int cg3_mmap(struct fb_info *info, struct file *file, struct vm_area_struct *vma)
{
struct cg3_par *par = (struct cg3_par *)info->par;
return sbusfb_mmap_helper(cg3_mmap_map,
par->physbase, par->fbsize,
par->sdev->reg_addrs[0].which_io,
vma);
}
/*
* Initialisation
*/
static void
cg3_init_fix(struct fb_info *info, int linebytes)
{
struct cg3_par *par = (struct cg3_par *)info->par;
strncpy(info->fix.id, par->sdev->prom_name, sizeof(info->fix.id) - 1);
info->fix.id[sizeof(info->fix.id)-1] = 0;
info->fix.type = FB_TYPE_PACKED_PIXELS;
info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
info->fix.line_length = linebytes;
info->fix.accel = FB_ACCEL_SUN_CGTHREE;
}
static void cg3_rdi_maybe_fixup_var(struct fb_var_screeninfo *var,
struct sbus_dev *sdev)
{
char buffer[40];
char *p;
int ww, hh;
*buffer = 0;
prom_getstring(sdev->prom_node, "params", buffer, sizeof(buffer));
if (*buffer) {
ww = simple_strtoul(buffer, &p, 10);
if (ww && *p == 'x') {
hh = simple_strtoul(p + 1, &p, 10);
if (hh && *p == '-') {
if (var->xres != ww ||
var->yres != hh) {
var->xres = var->xres_virtual = ww;
var->yres = var->yres_virtual = hh;
}
}
}
}
}
static u8 cg3regvals_66hz[] __initdata = { /* 1152 x 900, 66 Hz */
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 cg3regvals_76hz[] __initdata = { /* 1152 x 900, 76 Hz */
0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x24, 0
};
static u8 cg3regvals_rdi[] __initdata = { /* 640 x 480, cgRDI */
0x14, 0x70, 0x15, 0x20, 0x16, 0x08, 0x17, 0x10,
0x18, 0x06, 0x19, 0x02, 0x1a, 0x31, 0x1b, 0x51,
0x1c, 0x06, 0x1d, 0x0c, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x22, 0
};
static u8 *cg3_regvals[] __initdata = {
cg3regvals_66hz, cg3regvals_76hz, cg3regvals_rdi
};
static u_char cg3_dacvals[] __initdata = {
4, 0xff, 5, 0x00, 6, 0x70, 7, 0x00, 0
};
static void cg3_do_default_mode(struct cg3_par *par)
{
enum cg3_type type;
u8 *p;
if (par->flags & CG3_FLAG_RDI)
type = CG3_RDI;
else {
u8 status = sbus_readb(&par->regs->status), mon;
if ((status & CG3_SR_ID_MASK) == CG3_SR_ID_COLOR) {
mon = status & CG3_SR_RES_MASK;
if (mon == CG3_SR_1152_900_76_A ||
mon == CG3_SR_1152_900_76_B)
type = CG3_AT_76HZ;
else
type = CG3_AT_66HZ;
} else {
prom_printf("cgthree: can't handle SR %02x\n",
status);
prom_halt();
return;
}
}
for (p = cg3_regvals[type]; *p; p += 2) {
u8 *regp = &((u8 *)par->regs)[p[0]];
sbus_writeb(p[1], regp);
}
for (p = cg3_dacvals; *p; p += 2) {
volatile u8 *regp;
regp = (volatile u8 *)&par->regs->cmap.addr;
sbus_writeb(p[0], regp);
regp = (volatile u8 *)&par->regs->cmap.control;
sbus_writeb(p[1], regp);
}
}
struct all_info {
struct fb_info info;
struct cg3_par par;
struct list_head list;
};
static LIST_HEAD(cg3_list);
static void cg3_init_one(struct sbus_dev *sdev)
{
struct all_info *all;
int linebytes;
all = kmalloc(sizeof(*all), GFP_KERNEL);
if (!all) {
printk(KERN_ERR "cg3: Cannot allocate memory.\n");
return;
}
memset(all, 0, sizeof(*all));
INIT_LIST_HEAD(&all->list);
spin_lock_init(&all->par.lock);
all->par.sdev = sdev;
all->par.physbase = sdev->reg_addrs[0].phys_addr;
sbusfb_fill_var(&all->info.var, sdev->prom_node, 8);
if (!strcmp(sdev->prom_name, "cgRDI"))
all->par.flags |= CG3_FLAG_RDI;
if (all->par.flags & CG3_FLAG_RDI)
cg3_rdi_maybe_fixup_var(&all->info.var, sdev);
linebytes = prom_getintdefault(sdev->prom_node, "linebytes",
all->info.var.xres);
all->par.fbsize = PAGE_ALIGN(linebytes * all->info.var.yres);
all->par.regs = (struct cg3_regs *)
sbus_ioremap(&sdev->resource[0], CG3_REGS_OFFSET,
sizeof(struct cg3_regs), "cg3 regs");
all->info.node = NODEV;
all->info.flags = FBINFO_FLAG_DEFAULT;
all->info.fbops = &cg3_ops;
#ifdef CONFIG_SPARC32
all->info.screen_base = (char *)
prom_getintdefault(sdev->prom_node, "address", 0);
#endif
if (!all->info.screen_base)
all->info.screen_base = (char *)
sbus_ioremap(&sdev->resource[0], CG3_RAM_OFFSET,
all->par.fbsize, "cg3 ram");
all->info.currcon = -1;
all->info.par = &all->par;
cg3_blank(0, &all->info);
if (!prom_getbool(sdev->prom_node, "width"))
cg3_do_default_mode(&all->par);
if (fb_alloc_cmap(&all->info.cmap, 256, 0)) {
printk(KERN_ERR "cg3: Could not allocate color map.\n");
kfree(all);
return;
}
cg3_set_par(&all->info);
cg3_init_fix(&all->info, linebytes);
if (register_framebuffer(&all->info) < 0) {
printk(KERN_ERR "cg3: Could not register framebuffer.\n");
fb_dealloc_cmap(&all->info.cmap);
kfree(all);
return;
}
list_add(&all->list, &cg3_list);
printk("cg3: %s at %lx:%lx\n",
sdev->prom_name,
(long) sdev->reg_addrs[0].which_io,
(long) sdev->reg_addrs[0].phys_addr);
}
int __init cg3_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "cgthree") ||
!strcmp(sdev->prom_name, "cgRDI"))
cg3_init_one(sdev);
}
return 0;
}
void __exit cg3_exit(void)
{
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &cg3_list) {
struct all_info *all = list_entry(pos, typeof(*all), list);
unregister_framebuffer(&all->info);
fb_dealloc_cmap(&all->info.cmap);
kfree(all);
}
}
int __init
cg3_setup(char *arg)
{
/* No cmdline options yet... */
return 0;
}
#ifdef MODULE
module_init(cg3_init);
module_exit(cg3_exit);
#endif
MODULE_DESCRIPTION("framebuffer driver for CGthree chipsets");
MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
MODULE_LICENSE("GPL");
/* $Id: cgsixfb.c,v 1.26 2001/10/16 05:44:44 davem Exp $ /* cg6.c: CGSIX (GX, GXplus, TGX) frame buffer driver
* cgsixfb.c: CGsix (GX,GXplus) frame buffer driver
* *
* Copyright (C) 2003 David S. Miller (davem@redhat.com)
* Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz) * Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx) * Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be)
*
* Driver layout based loosely on tgafb.c, see that file for credits.
*/ */
#include <linux/module.h> #include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/errno.h> #include <linux/errno.h>
#include <linux/string.h> #include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/selection.h> #include <linux/fb.h>
#include <linux/mm.h>
#include <video/sbusfb.h>
#include <asm/io.h> #include <asm/io.h>
#include <asm/sbus.h>
#include <asm/oplib.h>
#include <asm/fbio.h>
#include "sbuslib.h"
/*
* Local functions.
*/
static int cg6_check_var(struct fb_var_screeninfo *, struct fb_info *);
static int cg6_set_par(struct fb_info *);
static int cg6_setcolreg(unsigned, unsigned, unsigned, unsigned,
unsigned, struct fb_info *);
static int cg6_blank(int, struct fb_info *);
static void cg6_imageblit(struct fb_info *, struct fb_image *);
static void cg6_fillrect(struct fb_info *, struct fb_fillrect *);
static int cg6_sync(struct fb_info *);
static int cg6_mmap(struct fb_info *, struct file *, struct vm_area_struct *);
/*
* Frame buffer operations
*/
static struct fb_ops cg6_ops = {
.owner = THIS_MODULE,
.fb_check_var = cg6_check_var,
.fb_set_par = cg6_set_par,
.fb_setcolreg = cg6_setcolreg,
.fb_blank = cg6_blank,
.fb_fillrect = cg6_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cg6_imageblit,
.fb_sync = cg6_sync,
.fb_mmap = cg6_mmap,
.fb_cursor = soft_cursor,
};
/* Offset of interesting structures in the OBIO space */ /* Offset of interesting structures in the OBIO space */
/* /*
...@@ -128,8 +162,6 @@ ...@@ -128,8 +162,6 @@
#define CG6_THC_MISC_INT (1 << 4) #define CG6_THC_MISC_INT (1 << 4)
#define CG6_THC_MISC_INIT 0x9f #define CG6_THC_MISC_INIT 0x9f
MODULE_LICENSE("GPL");
/* The contents are unknown */ /* The contents are unknown */
struct cg6_tec { struct cg6_tec {
volatile int tec_matrix; volatile int tec_matrix;
...@@ -210,430 +242,385 @@ struct cg6_fbc { ...@@ -210,430 +242,385 @@ struct cg6_fbc {
volatile u32 rectr, rectg, rectb, recta; volatile u32 rectr, rectg, rectb, recta;
}; };
static struct sbus_mmap_map cg6_mmap_map[] = { struct bt_regs {
{ CG6_FBC, CG6_FBC_OFFSET, PAGE_SIZE }, volatile u32 addr;
{ CG6_TEC, CG6_TEC_OFFSET, PAGE_SIZE }, volatile u32 color_map;
{ CG6_BTREGS, CG6_BROOKTREE_OFFSET, PAGE_SIZE }, volatile u32 control;
{ CG6_FHC, CG6_FHC_OFFSET, PAGE_SIZE }, volatile u32 cursor;
{ CG6_THC, CG6_THC_OFFSET, PAGE_SIZE },
{ CG6_ROM, CG6_ROM_OFFSET, 0x10000 },
{ CG6_RAM, CG6_RAM_OFFSET, SBUS_MMAP_FBSIZE(1) },
{ CG6_DHC, CG6_DHC_OFFSET, 0x40000 },
{ 0, 0, 0 }
}; };
static void cg6_setup(struct display *p) struct cg6_par {
{ spinlock_t lock;
p->next_line = p->fb_info->var.xres_virtual; struct bt_regs *bt;
p->next_plane = 0; struct cg6_fbc *fbc;
} struct cg6_thc *thc;
struct cg6_tec *tec;
volatile u32 *fhc;
u32 flags;
#define CG6_FLAG_BLANKED 0x00000001
unsigned long physbase;
unsigned long fbsize;
struct sbus_dev *sdev;
struct list_head list;
};
static void cg6_clear(struct vc_data *conp, struct display *p, int sy, int sx, static int cg6_sync(struct fb_info *info)
int height, int width)
{ {
struct fb_info_sbusfb *fb = sbusfbinfo(p->fb_info); struct cg6_par *par = (struct cg6_par *) info->par;
register struct cg6_fbc *fbc = fb->s.cg6.fbc; struct cg6_fbc *fbc = par->fbc;
unsigned long flags; int limit = 10000;
int x, y, w, h;
int i;
spin_lock_irqsave(&fb->lock, flags);
do { do {
i = sbus_readl(&fbc->s); if (!(sbus_readl(&fbc->s) & 0x10000000))
} while (i & 0x10000000); break;
sbus_writel(attr_bgcol_ec(p,conp), &fbc->fg); udelay(10);
sbus_writel(attr_bgcol_ec(p,conp), &fbc->bg); } while (--limit > 0);
sbus_writel(~0, &fbc->pixelm);
sbus_writel(0xea80ff00, &fbc->alu);
sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip);
sbus_writel(~0, &fbc->pm);
if (fontheightlog(p)) { return 0;
y = sy << fontheightlog(p); h = height << fontheightlog(p);
} else {
y = sy * fontheight(p); h = height * fontheight(p);
}
if (fontwidthlog(p)) {
x = sx << fontwidthlog(p); w = width << fontwidthlog(p);
} else {
x = sx * fontwidth(p); w = width * fontwidth(p);
}
sbus_writel(y + fb->y_margin, &fbc->arecty);
sbus_writel(x + fb->x_margin, &fbc->arectx);
sbus_writel(y + fb->y_margin + h, &fbc->arecty);
sbus_writel(x + fb->x_margin + w, &fbc->arectx);
do {
i = sbus_readl(&fbc->draw);
} while (i < 0 && (i & 0x20000000));
spin_unlock_irqrestore(&fb->lock, flags);
} }
static void cg6_fill(struct fb_info_sbusfb *fb, struct display *p, int s, /**
int count, unsigned short *boxes) * cg6_fillrect - REQUIRED function. Can use generic routines if
* non acclerated hardware and packed pixel based.
* Draws a rectangle on the screen.
*
* @info: frame buffer structure that represents a single frame buffer
* @rect: structure defining the rectagle and operation.
*/
static void cg6_fillrect(struct fb_info *info, struct fb_fillrect *rect)
{ {
int i; struct cg6_par *par = (struct cg6_par *) info->par;
register struct cg6_fbc *fbc = fb->s.cg6.fbc; struct cg6_fbc *fbc = par->fbc;
unsigned long flags; unsigned long flags;
s32 val;
spin_lock_irqsave(&fb->lock, flags); /* XXX doesn't handle ROP_XOR */
do {
i = sbus_readl(&fbc->s); spin_lock_irqsave(&par->lock, flags);
} while (i & 0x10000000); cg6_sync(info);
sbus_writel(attr_bgcol(p,s), &fbc->fg); sbus_writel(rect->color, &fbc->fg);
sbus_writel(attr_bgcol(p,s), &fbc->bg); sbus_writel(~(u32)0, &fbc->pixelm);
sbus_writel(~0, &fbc->pixelm);
sbus_writel(0xea80ff00, &fbc->alu); sbus_writel(0xea80ff00, &fbc->alu);
sbus_writel(0, &fbc->s); sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip); sbus_writel(0, &fbc->clip);
sbus_writel(~0, &fbc->pm); sbus_writel(~(u32)0, &fbc->pm);
while (count-- > 0) { sbus_writel(rect->dy, &fbc->arecty);
sbus_writel(boxes[1], &fbc->arecty); sbus_writel(rect->dx, &fbc->arectx);
sbus_writel(boxes[0], &fbc->arectx); sbus_writel(rect->dy + rect->height, &fbc->arecty);
sbus_writel(boxes[3], &fbc->arecty); sbus_writel(rect->dx + rect->width, &fbc->arectx);
sbus_writel(boxes[2], &fbc->arectx);
boxes += 4;
do { do {
i = sbus_readl(&fbc->draw); val = sbus_readl(&fbc->draw);
} while (i < 0 && (i & 0x20000000)); } while (val < 0 && (val & 0x20000000));
} spin_unlock_irqrestore(&par->lock, flags);
spin_unlock_irqrestore(&fb->lock, flags);
} }
static void cg6_putc(struct vc_data *conp, struct display *p, int c, int yy, int xx) /**
* cg6_imageblit - REQUIRED function. Can use generic routines if
* non acclerated hardware and packed pixel based.
* Copies a image from system memory to the screen.
*
* @info: frame buffer structure that represents a single frame buffer
* @image: structure defining the image.
*/
static void cg6_imageblit(struct fb_info *info, struct fb_image *image)
{ {
struct fb_info_sbusfb *fb = sbusfbinfo(p->fb_info); struct cg6_par *par = (struct cg6_par *) info->par;
register struct cg6_fbc *fbc = fb->s.cg6.fbc; struct cg6_fbc *fbc = par->fbc;
u8 *data = image->data;
unsigned long flags; unsigned long flags;
int i, x, y; u32 x, y;
u8 *fd; int i, width;
spin_lock_irqsave(&fb->lock, flags); if (image->depth > 1) {
if (fontheightlog(p)) { cfb_imageblit(info, image);
y = fb->y_margin + (yy << fontheightlog(p)); return;
i = ((c & p->charmask) << fontheightlog(p));
} else {
y = fb->y_margin + (yy * fontheight(p));
i = (c & p->charmask) * fontheight(p);
} }
if (fontwidth(p) <= 8)
fd = p->fontdata + i;
else
fd = p->fontdata + (i << 1);
if (fontwidthlog(p))
x = fb->x_margin + (xx << fontwidthlog(p));
else
x = fb->x_margin + (xx * fontwidth(p));
do {
i = sbus_readl(&fbc->s);
} while (i & 0x10000000);
sbus_writel(attr_fgcol(p,c), &fbc->fg);
sbus_writel(attr_bgcol(p,c), &fbc->bg);
sbus_writel(0x140000, &fbc->mode);
sbus_writel(0xe880fc30, &fbc->alu);
sbus_writel(~0, &fbc->pixelm);
sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip);
sbus_writel(0xff, &fbc->pm);
sbus_writel(0, &fbc->incx);
sbus_writel(1, &fbc->incy);
sbus_writel(x, &fbc->x0);
sbus_writel(x + fontwidth(p) - 1, &fbc->x1);
sbus_writel(y, &fbc->y0);
if (fontwidth(p) <= 8) {
for (i = 0; i < fontheight(p); i++) {
u32 val = *fd++ << 24;
sbus_writel(val, &fbc->font);
}
} else {
for (i = 0; i < fontheight(p); i++) {
u32 val = *(u16 *)fd << 16;
sbus_writel(val, &fbc->font); spin_lock_irqsave(&par->lock, flags);
fd += 2;
}
}
spin_unlock_irqrestore(&fb->lock, flags);
}
static void cg6_putcs(struct vc_data *conp, struct display *p, const unsigned short *s, cg6_sync(info);
int count, int yy, int xx)
{
struct fb_info_sbusfb *fb = sbusfbinfo(p->fb_info);
register struct cg6_fbc *fbc = fb->s.cg6.fbc;
unsigned long flags;
int i, x, y;
u8 *fd1, *fd2, *fd3, *fd4;
u16 c;
spin_lock_irqsave(&fb->lock, flags); sbus_writel(image->fg_color, &fbc->fg);
do { sbus_writel(image->bg_color, &fbc->bg);
i = sbus_readl(&fbc->s);
} while (i & 0x10000000);
c = scr_readw(s);
sbus_writel(attr_fgcol(p, c), &fbc->fg);
sbus_writel(attr_bgcol(p, c), &fbc->bg);
sbus_writel(0x140000, &fbc->mode); sbus_writel(0x140000, &fbc->mode);
sbus_writel(0xe880fc30, &fbc->alu); sbus_writel(0xe880fc30, &fbc->alu);
sbus_writel(~0, &fbc->pixelm); sbus_writel(~(u32)0, &fbc->pixelm);
sbus_writel(0, &fbc->s); sbus_writel(0, &fbc->s);
sbus_writel(0, &fbc->clip); sbus_writel(0, &fbc->clip);
sbus_writel(0xff, &fbc->pm); sbus_writel(0xff, &fbc->pm);
x = fb->x_margin; sbus_writel(32, &fbc->incx);
y = fb->y_margin; sbus_writel(0, &fbc->incy);
if (fontwidthlog(p))
x += (xx << fontwidthlog(p)); x = image->dx;
else y = image->dy;
x += xx * fontwidth(p); for (i = 0; i < image->height; i++) {
if (fontheightlog(p)) width = image->width;
y += (yy << fontheightlog(p));
else while (width >= 32) {
y += (yy * fontheight(p)); u32 val;
if (fontwidth(p) <= 8) {
while (count >= 4) {
count -= 4;
sbus_writel(0, &fbc->incx);
sbus_writel(1, &fbc->incy);
sbus_writel(x, &fbc->x0);
sbus_writel((x += 4 * fontwidth(p)) - 1, &fbc->x1);
sbus_writel(y, &fbc->y0); sbus_writel(y, &fbc->y0);
if (fontheightlog(p)) {
fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
fd3 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
fd4 = p->fontdata + ((scr_readw(s++) & p->charmask) << fontheightlog(p));
} else {
fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
fd3 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
fd4 = p->fontdata + ((scr_readw(s++) & p->charmask) * fontheight(p));
}
if (fontwidth(p) == 8) {
for (i = 0; i < fontheight(p); i++) {
u32 val = ((u32)*fd4++) |
((((u32)*fd3++) |
((((u32)*fd2++) |
(((u32)*fd1++)
<< 8)) << 8)) << 8);
sbus_writel(val, &fbc->font);
}
} else {
for (i = 0; i < fontheight(p); i++) {
u32 val = (((u32)*fd4++) |
((((u32)*fd3++) |
((((u32)*fd2++) |
(((u32)*fd1++)
<< fontwidth(p))) <<
fontwidth(p))) <<
fontwidth(p))) <<
(24 - 3 * fontwidth(p));
sbus_writel(val, &fbc->font);
}
}
}
} else {
while (count >= 2) {
count -= 2;
sbus_writel(0, &fbc->incx);
sbus_writel(1, &fbc->incy);
sbus_writel(x, &fbc->x0); sbus_writel(x, &fbc->x0);
sbus_writel((x += 2 * fontwidth(p)) - 1, &fbc->x1); sbus_writel(x + 32 - 1, &fbc->x1);
sbus_writel(y, &fbc->y0);
if (fontheightlog(p)) { val = ((u32)data[0] << 24) |
fd1 = p->fontdata + ((scr_readw(s++) & p->charmask) << (fontheightlog(p) + 1)); ((u32)data[1] << 16) |
fd2 = p->fontdata + ((scr_readw(s++) & p->charmask) << (fontheightlog(p) + 1)); ((u32)data[2] << 8) |
} else { ((u32)data[3] << 0);
fd1 = p->fontdata + (((scr_readw(s++) & p->charmask) * fontheight(p)) << 1);
fd2 = p->fontdata + (((scr_readw(s++) & p->charmask) * fontheight(p)) << 1);
}
for (i = 0; i < fontheight(p); i++) {
u32 val = ((((u32)*(u16 *)fd1) << fontwidth(p)) |
((u32)*(u16 *)fd2)) << (16 - fontwidth(p));
sbus_writel(val, &fbc->font); sbus_writel(val, &fbc->font);
fd1 += 2; fd2 += 2;
} data += 4;
} x += 32;
width -= 32;
} }
while (count) { if (width) {
count--; u32 val;
sbus_writel(0, &fbc->incx);
sbus_writel(1, &fbc->incy);
sbus_writel(x, &fbc->x0);
sbus_writel((x += fontwidth(p)) - 1, &fbc->x1);
sbus_writel(y, &fbc->y0); sbus_writel(y, &fbc->y0);
if (fontheightlog(p)) sbus_writel(x, &fbc->x0);
i = ((scr_readw(s++) & p->charmask) << fontheightlog(p)); sbus_writel(x + width - 1, &fbc->x1);
else if (width <= 8) {
i = ((scr_readw(s++) & p->charmask) * fontheight(p)); val = (u32) data[0] << 24;
if (fontwidth(p) <= 8) { data += 1;
fd1 = p->fontdata + i; } else if (width <= 16) {
for (i = 0; i < fontheight(p); i++) { val = ((u32) data[0] << 24) |
u32 val = *fd1++ << 24; ((u32) data[1] << 16);
sbus_writel(val, &fbc->font); data += 2;
}
} else { } else {
fd1 = p->fontdata + (i << 1); val = ((u32) data[0] << 24) |
for (i = 0; i < fontheight(p); i++) { ((u32) data[1] << 16) |
u32 val = *(u16 *)fd1 << 16; ((u32) data[2] << 8);
sbus_writel(val, &fbc->font); data += 3;
fd1 += 2;
} }
sbus_writel(val, &fbc->font);
} }
y += 1;
x = image->dx;
} }
spin_unlock_irqrestore(&fb->lock, flags);
spin_unlock_irqrestore(&par->lock, flags);
}
/**
* cg6_check_var - Optional function. Validates a var passed in.
* @var: frame buffer variable screen structure
* @info: frame buffer structure that represents a single frame buffer
*/
static int cg6_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
if (var->bits_per_pixel != 8)
return -EINVAL;
if (var->xres_virtual != var->xres || var->yres_virtual != var->yres)
return -EINVAL;
if (var->nonstd)
return -EINVAL;
if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED)
return -EINVAL;
if (var->xres != info->var.xres || var->yres != info->var.yres)
return -EINVAL;
return 0;
} }
static void cg6_revc(struct display *p, int xx, int yy) /**
* cg6_set_par - Optional function. Alters the hardware state.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
cg6_set_par(struct fb_info *info)
{ {
/* Not used if hw cursor */ return 0;
} }
static void cg6_loadcmap (struct fb_info_sbusfb *fb, struct display *p, int index, int count) /**
* cg6_setcolreg - Optional function. Sets a color register.
* @regno: boolean, 0 copy local, 1 get_user() function
* @red: frame buffer colormap structure
* @green: The green value which can be up to 16 bits wide
* @blue: The blue value which can be up to 16 bits wide.
* @transp: If supported the alpha value which can be up to 16 bits wide.
* @info: frame buffer info structure
*/
static int cg6_setcolreg(unsigned regno,
unsigned red, unsigned green, unsigned blue,
unsigned transp, struct fb_info *info)
{ {
struct bt_regs *bt = fb->s.cg6.bt; struct cg6_par *par = (struct cg6_par *) info->par;
struct bt_regs *bt = par->bt;
unsigned long flags; unsigned long flags;
int i;
spin_lock_irqsave(&fb->lock, flags); if (regno >= 256)
sbus_writel(index << 24, &bt->addr); return 1;
for (i = index; count--; i++){
sbus_writel(fb->color_map CM(i,0) << 24, red >>= 8;
&bt->color_map); green >>= 8;
sbus_writel(fb->color_map CM(i,1) << 24, blue >>= 8;
&bt->color_map);
sbus_writel(fb->color_map CM(i,2) << 24, spin_lock_irqsave(&par->lock, flags);
&bt->color_map);
} sbus_writel((u32)regno << 24, &bt->addr);
spin_unlock_irqrestore(&fb->lock, flags); sbus_writel((u32)red << 24, &bt->color_map);
sbus_writel((u32)green << 24, &bt->color_map);
sbus_writel((u32)blue << 24, &bt->color_map);
spin_unlock_irqrestore(&par->lock, flags);
return 0;
} }
static void cg6_restore_palette (struct fb_info_sbusfb *fb) /**
* cg6_blank - Optional function. Blanks the display.
* @blank_mode: the blank mode we want.
* @info: frame buffer structure that represents a single frame buffer
*/
static int
cg6_blank(int blank, struct fb_info *info)
{ {
struct bt_regs *bt = fb->s.cg6.bt; struct cg6_par *par = (struct cg6_par *) info->par;
struct cg6_thc *thc = par->thc;
unsigned long flags; unsigned long flags;
u32 val;
spin_lock_irqsave(&par->lock, flags);
switch (blank) {
case 0: /* Unblanking */
val = sbus_readl(&thc->thc_misc);
val |= CG6_THC_MISC_VIDEO;
sbus_writel(val, &thc->thc_misc);
par->flags &= ~CG6_FLAG_BLANKED;
break;
case 1: /* Normal blanking */
case 2: /* VESA blank (vsync off) */
case 3: /* VESA blank (hsync off) */
case 4: /* Poweroff */
val = sbus_readl(&thc->thc_misc);
val &= ~CG6_THC_MISC_VIDEO;
sbus_writel(val, &thc->thc_misc);
par->flags |= CG6_FLAG_BLANKED;
break;
}
spin_unlock_irqrestore(&par->lock, flags);
spin_lock_irqsave(&fb->lock, flags); return 0;
sbus_writel(0, &bt->addr);
sbus_writel(0xffffffff, &bt->color_map);
sbus_writel(0xffffffff, &bt->color_map);
sbus_writel(0xffffffff, &bt->color_map);
spin_unlock_irqrestore(&fb->lock, flags);
} }
static struct display_switch cg6_dispsw __initdata = { static struct sbus_mmap_map cg6_mmap_map[] = {
.setup = cg6_setup, { CG6_FBC, CG6_FBC_OFFSET, PAGE_SIZE },
.bmove = fbcon_redraw_bmove, { CG6_TEC, CG6_TEC_OFFSET, PAGE_SIZE },
.clear = cg6_clear, { CG6_BTREGS, CG6_BROOKTREE_OFFSET, PAGE_SIZE },
.putc = cg6_putc, { CG6_FHC, CG6_FHC_OFFSET, PAGE_SIZE },
.putcs = cg6_putcs, { CG6_THC, CG6_THC_OFFSET, PAGE_SIZE },
.revc = cg6_revc, { CG6_ROM, CG6_ROM_OFFSET, 0x10000 },
.fontwidthmask =FONTWIDTHRANGE(1,16) /* Allow fontwidths up to 16 */ { CG6_RAM, CG6_RAM_OFFSET, SBUS_MMAP_FBSIZE(1) },
{ CG6_DHC, CG6_DHC_OFFSET, 0x40000 },
{ 0, 0, 0 }
}; };
static void cg6_setcursormap (struct fb_info_sbusfb *fb, u8 *red, u8 *green, u8 *blue) static int cg6_mmap(struct fb_info *info, struct file *file, struct vm_area_struct *vma)
{ {
struct bt_regs *bt = fb->s.cg6.bt; struct cg6_par *par = (struct cg6_par *)info->par;
unsigned long flags;
spin_lock_irqsave(&fb->lock, flags); return sbusfb_mmap_helper(cg6_mmap_map,
sbus_writel(1 << 24, &bt->addr); par->physbase, par->fbsize,
sbus_writel(red[0] << 24, &bt->cursor); par->sdev->reg_addrs[0].which_io,
sbus_writel(green[0] << 24, &bt->cursor); vma);
sbus_writel(blue[0] << 24, &bt->cursor);
sbus_writel(3 << 24, &bt->addr);
sbus_writel(red[1] << 24, &bt->cursor);
sbus_writel(green[1] << 24, &bt->cursor);
sbus_writel(blue[1] << 24, &bt->cursor);
spin_unlock_irqrestore(&fb->lock, flags);
} }
/* Set cursor shape */ /*
static void cg6_setcurshape (struct fb_info_sbusfb *fb) * Initialisation
*/
static void
cg6_init_fix(struct fb_info *info, int linebytes)
{ {
struct cg6_thc *thc = fb->s.cg6.thc; struct cg6_par *par = (struct cg6_par *)info->par;
unsigned long flags; const char *cg6_cpu_name, *cg6_card_name;
int i; u32 conf;
spin_lock_irqsave(&fb->lock, flags); conf = sbus_readl(par->fhc);
for (i = 0; i < 32; i++) { switch(conf & CG6_FHC_CPU_MASK) {
sbus_writel(fb->cursor.bits[0][i], case CG6_FHC_CPU_SPARC:
&thc->thc_cursmask [i]); cg6_cpu_name = "sparc";
sbus_writel(fb->cursor.bits[1][i], break;
&thc->thc_cursbits [i]); case CG6_FHC_CPU_68020:
cg6_cpu_name = "68020";
break;
default:
cg6_cpu_name = "i386";
break;
};
if (((conf >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK) >= 11) {
if (par->fbsize <= 0x100000) {
cg6_card_name = "TGX";
} else {
cg6_card_name = "TGX+";
}
} else {
if (par->fbsize <= 0x100000) {
cg6_card_name = "GX";
} else {
cg6_card_name = "GX+";
}
} }
spin_unlock_irqrestore(&fb->lock, flags);
}
/* Load cursor information */ sprintf(info->fix.id, "%s %s", cg6_card_name, cg6_cpu_name);
static void cg6_setcursor (struct fb_info_sbusfb *fb) info->fix.id[sizeof(info->fix.id)-1] = 0;
{
unsigned int v;
unsigned long flags;
struct cg_cursor *c = &fb->cursor;
spin_lock_irqsave(&fb->lock, flags);
if (c->enable)
v = ((c->cpos.fbx - c->chot.fbx) << 16)
|((c->cpos.fby - c->chot.fby) & 0xffff);
else
/* Magic constant to turn off the cursor */
v = ((65536-32) << 16) | (65536-32);
sbus_writel(v, &fb->s.cg6.thc->thc_cursxy);
spin_unlock_irqrestore(&fb->lock, flags);
}
static int cg6_blank (struct fb_info_sbusfb *fb) info->fix.type = FB_TYPE_PACKED_PIXELS;
{ info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
unsigned long flags;
u32 tmp;
spin_lock_irqsave(&fb->lock, flags); info->fix.line_length = linebytes;
tmp = sbus_readl(&fb->s.cg6.thc->thc_misc);
tmp &= ~CG6_THC_MISC_VIDEO; info->fix.accel = FB_ACCEL_SUN_CGSIX;
sbus_writel(tmp, &fb->s.cg6.thc->thc_misc);
spin_unlock_irqrestore(&fb->lock, flags);
return 0;
} }
static int cg6_unblank (struct fb_info_sbusfb *fb) /* Initialize Brooktree DAC */
static void cg6_bt_init(struct cg6_par *par)
{ {
unsigned long flags; struct bt_regs *bt = par->bt;
u32 tmp;
spin_lock_irqsave(&fb->lock, flags); sbus_writel(0x04 << 24, &bt->addr); /* color planes */
tmp = sbus_readl(&fb->s.cg6.thc->thc_misc); sbus_writel(0xff << 24, &bt->control);
tmp |= CG6_THC_MISC_VIDEO; sbus_writel(0x05 << 24, &bt->addr);
sbus_writel(tmp, &fb->s.cg6.thc->thc_misc); sbus_writel(0x00 << 24, &bt->control);
spin_unlock_irqrestore(&fb->lock, flags); sbus_writel(0x06 << 24, &bt->addr); /* overlay plane */
return 0; sbus_writel(0x73 << 24, &bt->control);
sbus_writel(0x07 << 24, &bt->addr);
sbus_writel(0x00 << 24, &bt->control);
} }
static void cg6_reset (struct fb_info_sbusfb *fb) static void cg6_chip_init(struct fb_info *info)
{ {
unsigned int rev, conf; struct cg6_par *par = (struct cg6_par *) info->par;
struct cg6_tec *tec = fb->s.cg6.tec; struct cg6_tec *tec = par->tec;
struct cg6_fbc *fbc = fb->s.cg6.fbc; struct cg6_fbc *fbc = par->fbc;
unsigned long flags; u32 rev, conf, mode, tmp;
u32 mode, tmp;
int i; int i;
spin_lock_irqsave(&fb->lock, flags);
/* Turn off stuff in the Transform Engine. */ /* Turn off stuff in the Transform Engine. */
sbus_writel(0, &tec->tec_matrix); sbus_writel(0, &tec->tec_matrix);
sbus_writel(0, &tec->tec_clip); sbus_writel(0, &tec->tec_clip);
sbus_writel(0, &tec->tec_vdc); sbus_writel(0, &tec->tec_vdc);
/* Take care of bugs in old revisions. */ /* Take care of bugs in old revisions. */
rev = (sbus_readl(fb->s.cg6.fhc) >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK; rev = (sbus_readl(par->fhc) >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK;
if (rev < 5) { if (rev < 5) {
conf = (sbus_readl(fb->s.cg6.fhc) & CG6_FHC_RES_MASK) | conf = (sbus_readl(par->fhc) & CG6_FHC_RES_MASK) |
CG6_FHC_CPU_68020 | CG6_FHC_TEST | CG6_FHC_CPU_68020 | CG6_FHC_TEST |
(11 << CG6_FHC_TEST_X_SHIFT) | (11 << CG6_FHC_TEST_X_SHIFT) |
(11 << CG6_FHC_TEST_Y_SHIFT); (11 << CG6_FHC_TEST_Y_SHIFT);
if (rev < 2) if (rev < 2)
conf |= CG6_FHC_DST_DISABLE; conf |= CG6_FHC_DST_DISABLE;
sbus_writel(conf, fb->s.cg6.fhc); sbus_writel(conf, par->fhc);
} }
/* Set things in the FBC. Bad things appear to happen if we do /* Set things in the FBC. Bad things appear to happen if we do
...@@ -658,167 +645,149 @@ static void cg6_reset (struct fb_info_sbusfb *fb) ...@@ -658,167 +645,149 @@ static void cg6_reset (struct fb_info_sbusfb *fb)
sbus_writel(0, &fbc->offy); sbus_writel(0, &fbc->offy);
sbus_writel(0, &fbc->clipminx); sbus_writel(0, &fbc->clipminx);
sbus_writel(0, &fbc->clipminy); sbus_writel(0, &fbc->clipminy);
sbus_writel(fb->type.fb_width - 1, &fbc->clipmaxx); sbus_writel(info->var.xres - 1, &fbc->clipmaxx);
sbus_writel(fb->type.fb_height - 1, &fbc->clipmaxy); sbus_writel(info->var.yres - 1, &fbc->clipmaxy);
/* Enable cursor in Brooktree DAC. */ /* Disable cursor in Brooktree DAC. */
sbus_writel(0x06 << 24, &fb->s.cg6.bt->addr); sbus_writel(0x06 << 24, &par->bt->addr);
tmp = sbus_readl(&fb->s.cg6.bt->control); tmp = sbus_readl(&par->bt->control);
tmp |= 0x03 << 24; tmp &= ~(0x03 << 24);
sbus_writel(tmp, &fb->s.cg6.bt->control); sbus_writel(tmp, &par->bt->control);
spin_unlock_irqrestore(&fb->lock, flags);
} }
static void cg6_margins (struct fb_info_sbusfb *fb, struct display *p, int x_margin, int y_margin) struct all_info {
{ struct fb_info info;
fb->info.screen_base += (y_margin - fb->y_margin) * struct cg6_par par;
fb->info.fix.line_length + (x_margin - fb->x_margin); struct list_head list;
} };
static LIST_HEAD(cg6_list);
static int __init cg6_rasterimg (struct fb_info *info, int start) static void cg6_init_one(struct sbus_dev *sdev)
{ {
struct fb_info_sbusfb *fb = sbusfbinfo(info); struct all_info *all;
register struct cg6_fbc *fbc = fb->s.cg6.fbc; int linebytes;
int i;
do { all = kmalloc(sizeof(*all), GFP_KERNEL);
i = sbus_readl(&fbc->s); if (!all) {
} while (i & 0x10000000); printk(KERN_ERR "cg6: Cannot allocate memory.\n");
return 0; return;
} }
memset(all, 0, sizeof(*all));
static char idstring[70] __initdata = { 0 };
char __init *cgsixfb_init(struct fb_info_sbusfb *fb)
{
struct fb_fix_screeninfo *fix = &fb->info.fix;
struct fb_var_screeninfo *var = &fb->info.var;
struct display *disp = &fb->disp;
struct fbtype *type = &fb->type;
struct sbus_dev *sdev = fb->sbdp;
unsigned long phys = sdev->reg_addrs[0].phys_addr;
u32 conf;
char *p;
char *cardtype;
struct bt_regs *bt;
struct fb_ops *fbops;
fbops = kmalloc(sizeof(*fbops), GFP_KERNEL); INIT_LIST_HEAD(&all->list);
if (fbops == NULL)
return NULL;
*fbops = *fb->info.fbops; spin_lock_init(&all->par.lock);
fbops->fb_rasterimg = cg6_rasterimg; all->par.sdev = sdev;
fb->info.fbops = fbops;
if (prom_getbool (fb->prom_node, "dblbuf")) { all->par.physbase = sdev->reg_addrs[0].phys_addr;
type->fb_size *= 4;
fix->smem_len *= 4;
}
fix->line_length = fb->info.var.xres_virtual; sbusfb_fill_var(&all->info.var, sdev->prom_node, 8);
fix->accel = FB_ACCEL_SUN_CGSIX;
var->accel_flags = FB_ACCELF_TEXT; linebytes = prom_getintdefault(sdev->prom_node, "linebytes",
all->info.var.xres);
all->par.fbsize = PAGE_ALIGN(linebytes * all->info.var.yres);
if (prom_getbool(sdev->prom_node, "dblbuf"))
all->par.fbsize *= 4;
disp->scrollmode = SCROLL_YREDRAW; all->par.fbc = (struct cg6_fbc *)
if (!fb->info.screen_base) {
fb->info.screen_base = (char *)
sbus_ioremap(&sdev->resource[0], CG6_RAM_OFFSET,
type->fb_size, "cgsix ram");
}
fb->info.screen_base += fix->line_length * fb->y_margin + fb->x_margin;
fb->s.cg6.fbc = (struct cg6_fbc *)
sbus_ioremap(&sdev->resource[0], CG6_FBC_OFFSET, sbus_ioremap(&sdev->resource[0], CG6_FBC_OFFSET,
4096, "cgsix fbc"); 4096, "cgsix fbc");
fb->s.cg6.tec = (struct cg6_tec *) all->par.tec = (struct cg6_tec *)
sbus_ioremap(&sdev->resource[0], CG6_TEC_OFFSET, sbus_ioremap(&sdev->resource[0], CG6_TEC_OFFSET,
sizeof(struct cg6_tec), "cgsix tec"); sizeof(struct cg6_tec), "cgsix tec");
fb->s.cg6.thc = (struct cg6_thc *) all->par.thc = (struct cg6_thc *)
sbus_ioremap(&sdev->resource[0], CG6_THC_OFFSET, sbus_ioremap(&sdev->resource[0], CG6_THC_OFFSET,
sizeof(struct cg6_thc), "cgsix thc"); sizeof(struct cg6_thc), "cgsix thc");
fb->s.cg6.bt = bt = (struct bt_regs *) all->par.bt = (struct bt_regs *)
sbus_ioremap(&sdev->resource[0], CG6_BROOKTREE_OFFSET, sbus_ioremap(&sdev->resource[0], CG6_BROOKTREE_OFFSET,
sizeof(struct bt_regs), "cgsix dac"); sizeof(struct bt_regs), "cgsix dac");
fb->s.cg6.fhc = (u32 *) all->par.fhc = (u32 *)
sbus_ioremap(&sdev->resource[0], CG6_FHC_OFFSET, sbus_ioremap(&sdev->resource[0], CG6_FHC_OFFSET,
sizeof(u32), "cgsix fhc"); sizeof(u32), "cgsix fhc");
#if 0
prom_printf("CG6: RES[%016lx:%016lx:%016lx]\n", all->info.node = NODEV;
sdev->resource[0].start, all->info.flags = FBINFO_FLAG_DEFAULT;
sdev->resource[0].end, all->info.fbops = &cg6_ops;
sdev->resource[0].flags); #ifdef CONFIG_SPARC32
prom_printf("CG6: fbc(%p) tec(%p) thc(%p) bt(%p) fhc(%p)\n", all->info.screen_base = (char *)
fb->s.cg6.fbc, prom_getintdefault(sdev->prom_node, "address", 0);
fb->s.cg6.tec,
fb->s.cg6.thc,
fb->s.cg6.bt,
fb->s.cg6.fhc);
prom_halt();
#endif #endif
fb->dispsw = cg6_dispsw; if (!all->info.screen_base)
all->info.screen_base = (char *)
fb->margins = cg6_margins; sbus_ioremap(&sdev->resource[0], CG6_RAM_OFFSET,
fb->loadcmap = cg6_loadcmap; all->par.fbsize, "cgsix ram");
fb->setcursor = cg6_setcursor; all->info.currcon = -1;
fb->setcursormap = cg6_setcursormap; all->info.par = &all->par;
fb->setcurshape = cg6_setcurshape;
fb->restore_palette = cg6_restore_palette;
fb->fill = cg6_fill;
fb->blank = cg6_blank;
fb->unblank = cg6_unblank;
fb->reset = cg6_reset;
fb->physbase = phys;
fb->mmap_map = cg6_mmap_map;
/* Initialize Brooktree DAC */
sbus_writel(0x04 << 24, &bt->addr); /* color planes */
sbus_writel(0xff << 24, &bt->control);
sbus_writel(0x05 << 24, &bt->addr);
sbus_writel(0x00 << 24, &bt->control);
sbus_writel(0x06 << 24, &bt->addr); /* overlay plane */
sbus_writel(0x73 << 24, &bt->control);
sbus_writel(0x07 << 24, &bt->addr);
sbus_writel(0x00 << 24, &bt->control);
conf = sbus_readl(fb->s.cg6.fhc); all->info.var.accel_flags = FB_ACCELF_TEXT;
switch(conf & CG6_FHC_CPU_MASK) {
case CG6_FHC_CPU_SPARC: p = "sparc"; break;
case CG6_FHC_CPU_68020: p = "68020"; break;
default: p = "i386"; break;
}
if (((conf >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK) >= 11) { cg6_bt_init(&all->par);
if (fix->smem_len <= 0x100000) { cg6_chip_init(&all->info);
cardtype = "TGX"; cg6_blank(0, &all->info);
} else {
cardtype = "TGX+"; if (fb_alloc_cmap(&all->info.cmap, 256, 0)) {
printk(KERN_ERR "cg6: Could not allocate color map.\n");
kfree(all);
return;
} }
} else {
if (fix->smem_len <= 0x100000) { cg6_set_par(&all->info);
cardtype = "GX"; cg6_init_fix(&all->info, linebytes);
} else {
cardtype = "GX+"; if (register_framebuffer(&all->info) < 0) {
printk(KERN_ERR "cg6: Could not register framebuffer.\n");
fb_dealloc_cmap(&all->info.cmap);
kfree(all);
return;
} }
list_add(&all->list, &cg6_list);
printk("cg6: CGsix [%s] at %lx:%lx\n",
all->info.fix.id,
(long) sdev->reg_addrs[0].which_io,
(long) sdev->reg_addrs[0].phys_addr);
}
int __init cg6_init(void)
{
struct sbus_bus *sbus;
struct sbus_dev *sdev;
for_all_sbusdev(sdev, sbus) {
if (!strcmp(sdev->prom_name, "cgsix") ||
!strcmp(sdev->prom_name, "cgthree+"))
cg6_init_one(sdev);
} }
sprintf(idstring, return 0;
#ifdef __sparc_v9__ }
"cgsix at %016lx TEC Rev %x CPU %s Rev %x [%s]", phys,
#else
"cgsix at %x.%08lx TEC Rev %x CPU %s Rev %x [%s]",
fb->iospace, phys,
#endif
((sbus_readl(&fb->s.cg6.thc->thc_misc) >> CG6_THC_MISC_REV_SHIFT) &
CG6_THC_MISC_REV_MASK),
p, (conf >> CG6_FHC_REV_SHIFT) & CG6_FHC_REV_MASK, cardtype);
sprintf(fb->info.modename, "CGsix [%s]", cardtype); void __exit cg6_exit(void)
sprintf(fix->id, "CGsix [%s]", cardtype); {
struct list_head *pos, *tmp;
list_for_each_safe(pos, tmp, &cg6_list) {
struct all_info *all = list_entry(pos, typeof(*all), list);
cg6_reset(fb); unregister_framebuffer(&all->info);
fb_dealloc_cmap(&all->info.cmap);
kfree(all);
}
}
return idstring; int __init
cg6_setup(char *arg)
{
/* No cmdline options yet... */
return 0;
} }
#ifdef MODULE
module_init(cg6_init);
module_exit(cg6_exit);
#endif
MODULE_DESCRIPTION("framebuffer driver for CGsix chipsets");
MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
MODULE_LICENSE("GPL");
/* $Id: cgthreefb.c,v 1.11 2001/09/19 00:04:33 davem Exp $
* cgthreefb.c: CGthree frame buffer driver
*
* Copyright (C) 1996,1998 Jakub Jelinek (jj@ultra.linux.cz)
* Copyright (C) 1996 Miguel de Icaza (miguel@nuclecu.unam.mx)
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/selection.h>
#include <video/sbusfb.h>
#include <asm/io.h>
#include <video/fbcon-cfb8.h>
/* Control Register Constants */
#define CG3_CR_ENABLE_INTS 0x80
#define CG3_CR_ENABLE_VIDEO 0x40
#define CG3_CR_ENABLE_TIMING 0x20
#define CG3_CR_ENABLE_CURCMP 0x10
#define CG3_CR_XTAL_MASK 0x0c
#define CG3_CR_DIVISOR_MASK 0x03
/* Status Register Constants */
#define CG3_SR_PENDING_INT 0x80
#define CG3_SR_RES_MASK 0x70
#define CG3_SR_1152_900_76_A 0x40
#define CG3_SR_1152_900_76_B 0x60
#define CG3_SR_ID_MASK 0x0f
#define CG3_SR_ID_COLOR 0x01
#define CG3_SR_ID_MONO 0x02
#define CG3_SR_ID_MONO_ECL 0x03
MODULE_LICENSE("GPL");
enum cg3_type {
CG3_AT_66HZ = 0,
CG3_AT_76HZ,
CG3_RDI
};
struct cg3_regs {
struct bt_regs cmap;
volatile u8 control;
volatile u8 status;
volatile u8 cursor_start;
volatile u8 cursor_end;
volatile u8 h_blank_start;
volatile u8 h_blank_end;
volatile u8 h_sync_start;
volatile u8 h_sync_end;
volatile u8 comp_sync_end;
volatile u8 v_blank_start_high;
volatile u8 v_blank_start_low;
volatile u8 v_blank_end;
volatile u8 v_sync_start;
volatile u8 v_sync_end;
volatile u8 xfer_holdoff_start;
volatile u8 xfer_holdoff_end;
};
/* Offset of interesting structures in the OBIO space */
#define CG3_REGS_OFFSET 0x400000UL
#define CG3_RAM_OFFSET 0x800000UL
static struct sbus_mmap_map cg3_mmap_map[] = {
{ CG3_MMAP_OFFSET, CG3_RAM_OFFSET, SBUS_MMAP_FBSIZE(1) },
{ 0, 0, 0 }
};
/* The cg3 palette is loaded with 4 color values at each time */
/* so you end up with: (rgb)(r), (gb)(rg), (b)(rgb), and so on */
#define D4M3(x) ((((x)>>2)<<1) + ((x)>>2)) /* (x/4)*3 */
#define D4M4(x) ((x)&~0x3) /* (x/4)*4 */
static void cg3_loadcmap (struct fb_info_sbusfb *fb, struct display *p, int index, int count)
{
struct bt_regs *bt = &fb->s.cg3.regs->cmap;
unsigned long flags;
u32 *i;
volatile u8 *regp;
int steps;
spin_lock_irqsave(&fb->lock, flags);
i = (((u32 *)fb->color_map) + D4M3(index));
steps = D4M3(index+count-1) - D4M3(index)+3;
regp = (volatile u8 *)&bt->addr;
sbus_writeb(D4M4(index), regp);
while (steps--) {
u32 val = *i++;
sbus_writel(val, &bt->color_map);
}
spin_unlock_irqrestore(&fb->lock, flags);
}
static int cg3_blank (struct fb_info_sbusfb *fb)
{
unsigned long flags;
u8 tmp;
spin_lock_irqsave(&fb->lock, flags);
tmp = sbus_readb(&fb->s.cg3.regs->control);
tmp &= ~CG3_CR_ENABLE_VIDEO;
sbus_writeb(tmp, &fb->s.cg3.regs->control);
spin_unlock_irqrestore(&fb->lock, flags);
return 0;
}
static int cg3_unblank (struct fb_info_sbusfb *fb)
{
unsigned long flags;
u8 tmp;
spin_lock_irqsave(&fb->lock, flags);
tmp = sbus_readb(&fb->s.cg3.regs->control);
tmp |= CG3_CR_ENABLE_VIDEO;
sbus_writeb(tmp, &fb->s.cg3.regs->control);
spin_unlock_irqrestore(&fb->lock, flags);
return 0;
}
static void cg3_margins (struct fb_info_sbusfb *fb, struct display *p,
int x_margin, int y_margin)
{
fb->info.screen_base += (y_margin - fb->y_margin) *
fb->info.fix.line_length + (x_margin - fb->x_margin);
}
static u8 cg3regvals_66hz[] __initdata = { /* 1152 x 900, 66 Hz */
0x14, 0xbb, 0x15, 0x2b, 0x16, 0x04, 0x17, 0x14,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xa8, 0x1b, 0x24,
0x1c, 0x01, 0x1d, 0x05, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x20, 0
};
static u8 cg3regvals_76hz[] __initdata = { /* 1152 x 900, 76 Hz */
0x14, 0xb7, 0x15, 0x27, 0x16, 0x03, 0x17, 0x0f,
0x18, 0xae, 0x19, 0x03, 0x1a, 0xae, 0x1b, 0x2a,
0x1c, 0x01, 0x1d, 0x09, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x24, 0
};
static u8 cg3regvals_rdi[] __initdata = { /* 640 x 480, cgRDI */
0x14, 0x70, 0x15, 0x20, 0x16, 0x08, 0x17, 0x10,
0x18, 0x06, 0x19, 0x02, 0x1a, 0x31, 0x1b, 0x51,
0x1c, 0x06, 0x1d, 0x0c, 0x1e, 0xff, 0x1f, 0x01,
0x10, 0x22, 0
};
static u8 *cg3_regvals[] __initdata = {
cg3regvals_66hz, cg3regvals_76hz, cg3regvals_rdi
};
static u_char cg3_dacvals[] __initdata = {
4, 0xff, 5, 0x00, 6, 0x70, 7, 0x00, 0
};
static char idstring[60] __initdata = { 0 };
char __init *cgthreefb_init(struct fb_info_sbusfb *fb)
{
struct fb_fix_screeninfo *fix = &fb->info.fix;
struct display *disp = &fb->disp;
struct fbtype *type = &fb->type;
struct sbus_dev *sdev = fb->sbdp;
unsigned long phys = sdev->reg_addrs[0].phys_addr;
int cgRDI = strstr(fb->sbdp->prom_name, "cgRDI") != NULL;
#ifndef FBCON_HAS_CFB8
return NULL;
#endif
if (!fb->s.cg3.regs) {
fb->s.cg3.regs = (struct cg3_regs *)
sbus_ioremap(&sdev->resource[0], CG3_REGS_OFFSET,
sizeof(struct cg3_regs), "cg3 regs");
if (cgRDI) {
char buffer[40];
char *p;
int ww, hh;
*buffer = 0;
prom_getstring (fb->prom_node, "params", buffer, sizeof(buffer));
if (*buffer) {
ww = simple_strtoul (buffer, &p, 10);
if (ww && *p == 'x') {
hh = simple_strtoul (p + 1, &p, 10);
if (hh && *p == '-') {
if (type->fb_width != ww || type->fb_height != hh) {
type->fb_width = ww;
type->fb_height = hh;
return SBUSFBINIT_SIZECHANGE;
}
}
}
}
}
}
strcpy(fb->info.modename, "CGthree");
strcpy(fix->id, "CGthree");
fix->line_length = fb->info.var.xres_virtual;
fix->accel = FB_ACCEL_SUN_CGTHREE;
disp->scrollmode = SCROLL_YREDRAW;
if (!fb->info.screen_base) {
fb->info.screen_base = (char *)
sbus_ioremap(&sdev->resource[0], CG3_RAM_OFFSET,
type->fb_size, "cg3 ram");
}
fb->info.screen_base += fix->line_length * fb->y_margin + fb->x_margin;
fb->dispsw = fbcon_cfb8;
fb->margins = cg3_margins;
fb->loadcmap = cg3_loadcmap;
fb->blank = cg3_blank;
fb->unblank = cg3_unblank;
fb->physbase = phys;
fb->mmap_map = cg3_mmap_map;
#ifdef __sparc_v9__
sprintf(idstring, "%s at %016lx", cgRDI ? "cgRDI" : "cgthree", phys);
#else
sprintf(idstring, "%s at %x.%08lx", cgRDI ? "cgRDI" : "cgthree", fb->iospace, phys);
#endif
if (!prom_getbool(fb->prom_node, "width")) {
/* Ugh, broken PROM didn't initialize us.
* Let's deal with this ourselves.
*/
enum cg3_type type;
u8 *p;
if (cgRDI)
type = CG3_RDI;
else {
u8 status = sbus_readb(&fb->s.cg3.regs->status), mon;
if ((status & CG3_SR_ID_MASK) == CG3_SR_ID_COLOR) {
mon = status & CG3_SR_RES_MASK;
if (mon == CG3_SR_1152_900_76_A ||
mon == CG3_SR_1152_900_76_B)
type = CG3_AT_76HZ;
else
type = CG3_AT_66HZ;
} else {
prom_printf("cgthree: can't handle SR %02x\n",
status);
prom_halt();
return NULL; /* fool gcc. */
}
}
for (p = cg3_regvals[type]; *p; p += 2) {
u8 *regp = &((u8 *)fb->s.cg3.regs)[p[0]];
sbus_writeb(p[1], regp);
}
for (p = cg3_dacvals; *p; p += 2) {
volatile u8 *regp;
regp = (volatile u8 *)&fb->s.cg3.regs->cmap.addr;
sbus_writeb(p[0], regp);
regp = (volatile u8 *)&fb->s.cg3.regs->cmap.control;
sbus_writeb(p[1], regp);
}
}
return idstring;
}
...@@ -146,6 +146,12 @@ extern int i810fb_init(void); ...@@ -146,6 +146,12 @@ extern int i810fb_init(void);
extern int i810fb_setup(char*); extern int i810fb_setup(char*);
extern int ffb_init(void); extern int ffb_init(void);
extern int ffb_setup(char*); extern int ffb_setup(char*);
extern int cg6_init(void);
extern int cg6_setup(char*);
extern int cg3_init(void);
extern int cg3_setup(char*);
extern int bw2_init(void);
extern int bw2_setup(char*);
static struct { static struct {
const char *name; const char *name;
...@@ -240,6 +246,15 @@ static struct { ...@@ -240,6 +246,15 @@ static struct {
#ifdef CONFIG_FB_FFB #ifdef CONFIG_FB_FFB
{ "ffb", ffb_init, ffb_setup }, { "ffb", ffb_init, ffb_setup },
#endif #endif
#ifdef CONFIG_FB_CG6
{ "cg6", cg6_init, cg6_setup },
#endif
#ifdef CONFIG_FB_CG3
{ "cg3", cg3_init, cg3_setup },
#endif
#ifdef CONFIG_FB_BW2
{ "bw2", bw2_init, bw2_setup },
#endif
/* /*
* Generic drivers that are used as fallbacks * Generic drivers that are used as fallbacks
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment