| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * |
| * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. |
| * |
| * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> |
| * |
| * Portions Copyright (c) 2001 Matrox Graphics Inc. |
| * |
| * Version: 1.65 2002/08/14 |
| * |
| */ |
| |
| #include "matroxfb_maven.h" |
| #include "matroxfb_crtc2.h" |
| #include "matroxfb_misc.h" |
| #include "matroxfb_DAC1064.h" |
| #include <linux/matroxfb.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| |
| /* **************************************************** */ |
| |
| static int mem = 8192; |
| |
| module_param(mem, int, 0); |
| MODULE_PARM_DESC(mem, "Memory size reserved for dualhead (default=8MB)"); |
| |
| /* **************************************************** */ |
| |
| static int matroxfb_dh_setcolreg(unsigned regno, unsigned red, unsigned green, |
| unsigned blue, unsigned transp, struct fb_info* info) { |
| u_int32_t col; |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| |
| if (regno >= 16) |
| return 1; |
| if (m2info->fbcon.var.grayscale) { |
| /* gray = 0.30*R + 0.59*G + 0.11*B */ |
| red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; |
| } |
| red = CNVT_TOHW(red, m2info->fbcon.var.red.length); |
| green = CNVT_TOHW(green, m2info->fbcon.var.green.length); |
| blue = CNVT_TOHW(blue, m2info->fbcon.var.blue.length); |
| transp = CNVT_TOHW(transp, m2info->fbcon.var.transp.length); |
| |
| col = (red << m2info->fbcon.var.red.offset) | |
| (green << m2info->fbcon.var.green.offset) | |
| (blue << m2info->fbcon.var.blue.offset) | |
| (transp << m2info->fbcon.var.transp.offset); |
| |
| switch (m2info->fbcon.var.bits_per_pixel) { |
| case 16: |
| m2info->cmap[regno] = col | (col << 16); |
| break; |
| case 32: |
| m2info->cmap[regno] = col; |
| break; |
| } |
| return 0; |
| #undef m2info |
| } |
| |
| static void matroxfb_dh_restore(struct matroxfb_dh_fb_info* m2info, |
| struct my_timming* mt, |
| int mode, |
| unsigned int pos) { |
| u_int32_t tmp; |
| u_int32_t datactl; |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| switch (mode) { |
| case 15: |
| tmp = 0x00200000; |
| break; |
| case 16: |
| tmp = 0x00400000; |
| break; |
| /* case 32: */ |
| default: |
| tmp = 0x00800000; |
| break; |
| } |
| tmp |= 0x00000001; /* enable CRTC2 */ |
| datactl = 0; |
| if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { |
| if (minfo->devflags.g450dac) { |
| tmp |= 0x00000006; /* source from secondary pixel PLL */ |
| /* no vidrst when in monitor mode */ |
| if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { |
| tmp |= 0xC0001000; /* Enable H/V vidrst */ |
| } |
| } else { |
| tmp |= 0x00000002; /* source from VDOCLK */ |
| tmp |= 0xC0000000; /* enable vvidrst & hvidrst */ |
| /* MGA TVO is our clock source */ |
| } |
| } else if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { |
| tmp |= 0x00000004; /* source from pixclock */ |
| /* PIXPLL is our clock source */ |
| } |
| if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { |
| tmp |= 0x00100000; /* connect CRTC2 to DAC */ |
| } |
| if (mt->interlaced) { |
| tmp |= 0x02000000; /* interlaced, second field is bigger, as G450 apparently ignores it */ |
| mt->VDisplay >>= 1; |
| mt->VSyncStart >>= 1; |
| mt->VSyncEnd >>= 1; |
| mt->VTotal >>= 1; |
| } |
| if ((mt->HTotal & 7) == 2) { |
| datactl |= 0x00000010; |
| mt->HTotal &= ~7; |
| } |
| tmp |= 0x10000000; /* 0x10000000 is VIDRST polarity */ |
| mga_outl(0x3C14, ((mt->HDisplay - 8) << 16) | (mt->HTotal - 8)); |
| mga_outl(0x3C18, ((mt->HSyncEnd - 8) << 16) | (mt->HSyncStart - 8)); |
| mga_outl(0x3C1C, ((mt->VDisplay - 1) << 16) | (mt->VTotal - 1)); |
| mga_outl(0x3C20, ((mt->VSyncEnd - 1) << 16) | (mt->VSyncStart - 1)); |
| mga_outl(0x3C24, ((mt->VSyncStart) << 16) | (mt->HSyncStart)); /* preload */ |
| { |
| u_int32_t linelen = m2info->fbcon.var.xres_virtual * (m2info->fbcon.var.bits_per_pixel >> 3); |
| if (tmp & 0x02000000) { |
| /* field #0 is smaller, so... */ |
| mga_outl(0x3C2C, pos); /* field #1 vmemory start */ |
| mga_outl(0x3C28, pos + linelen); /* field #0 vmemory start */ |
| linelen <<= 1; |
| m2info->interlaced = 1; |
| } else { |
| mga_outl(0x3C28, pos); /* vmemory start */ |
| m2info->interlaced = 0; |
| } |
| mga_outl(0x3C40, linelen); |
| } |
| mga_outl(0x3C4C, datactl); /* data control */ |
| if (tmp & 0x02000000) { |
| int i; |
| |
| mga_outl(0x3C10, tmp & ~0x02000000); |
| for (i = 0; i < 2; i++) { |
| unsigned int nl; |
| unsigned int lastl = 0; |
| |
| while ((nl = mga_inl(0x3C48) & 0xFFF) >= lastl) { |
| lastl = nl; |
| } |
| } |
| } |
| mga_outl(0x3C10, tmp); |
| minfo->hw.crtc2.ctl = tmp; |
| |
| tmp = mt->VDisplay << 16; /* line compare */ |
| if (mt->sync & FB_SYNC_HOR_HIGH_ACT) |
| tmp |= 0x00000100; |
| if (mt->sync & FB_SYNC_VERT_HIGH_ACT) |
| tmp |= 0x00000200; |
| mga_outl(0x3C44, tmp); |
| } |
| |
| static void matroxfb_dh_disable(struct matroxfb_dh_fb_info* m2info) { |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| mga_outl(0x3C10, 0x00000004); /* disable CRTC2, CRTC1->DAC1, PLL as clock source */ |
| minfo->hw.crtc2.ctl = 0x00000004; |
| } |
| |
| static void matroxfb_dh_pan_var(struct matroxfb_dh_fb_info* m2info, |
| struct fb_var_screeninfo* var) { |
| unsigned int pos; |
| unsigned int linelen; |
| unsigned int pixelsize; |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| m2info->fbcon.var.xoffset = var->xoffset; |
| m2info->fbcon.var.yoffset = var->yoffset; |
| pixelsize = m2info->fbcon.var.bits_per_pixel >> 3; |
| linelen = m2info->fbcon.var.xres_virtual * pixelsize; |
| pos = m2info->fbcon.var.yoffset * linelen + m2info->fbcon.var.xoffset * pixelsize; |
| pos += m2info->video.offbase; |
| if (m2info->interlaced) { |
| mga_outl(0x3C2C, pos); |
| mga_outl(0x3C28, pos + linelen); |
| } else { |
| mga_outl(0x3C28, pos); |
| } |
| } |
| |
| static int matroxfb_dh_decode_var(struct matroxfb_dh_fb_info* m2info, |
| struct fb_var_screeninfo* var, |
| int *visual, |
| int *video_cmap_len, |
| int *mode) { |
| unsigned int mask; |
| unsigned int memlen; |
| unsigned int vramlen; |
| |
| switch (var->bits_per_pixel) { |
| case 16: mask = 0x1F; |
| break; |
| case 32: mask = 0x0F; |
| break; |
| default: return -EINVAL; |
| } |
| vramlen = m2info->video.len_usable; |
| if (var->yres_virtual < var->yres) |
| var->yres_virtual = var->yres; |
| if (var->xres_virtual < var->xres) |
| var->xres_virtual = var->xres; |
| var->xres_virtual = (var->xres_virtual + mask) & ~mask; |
| if (var->yres_virtual > 32767) |
| return -EINVAL; |
| memlen = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3); |
| if (memlen > vramlen) |
| return -EINVAL; |
| if (var->xoffset + var->xres > var->xres_virtual) |
| var->xoffset = var->xres_virtual - var->xres; |
| if (var->yoffset + var->yres > var->yres_virtual) |
| var->yoffset = var->yres_virtual - var->yres; |
| |
| var->xres &= ~7; |
| var->left_margin &= ~7; |
| var->right_margin &= ~7; |
| var->hsync_len &= ~7; |
| |
| *mode = var->bits_per_pixel; |
| if (var->bits_per_pixel == 16) { |
| if (var->green.length == 5) { |
| var->red.offset = 10; |
| var->red.length = 5; |
| var->green.offset = 5; |
| var->green.length = 5; |
| var->blue.offset = 0; |
| var->blue.length = 5; |
| var->transp.offset = 15; |
| var->transp.length = 1; |
| *mode = 15; |
| } else { |
| var->red.offset = 11; |
| var->red.length = 5; |
| var->green.offset = 5; |
| var->green.length = 6; |
| var->blue.offset = 0; |
| var->blue.length = 5; |
| var->transp.offset = 0; |
| var->transp.length = 0; |
| } |
| } else { |
| var->red.offset = 16; |
| var->red.length = 8; |
| var->green.offset = 8; |
| var->green.length = 8; |
| var->blue.offset = 0; |
| var->blue.length = 8; |
| var->transp.offset = 24; |
| var->transp.length = 8; |
| } |
| *visual = FB_VISUAL_TRUECOLOR; |
| *video_cmap_len = 16; |
| return 0; |
| } |
| |
| static int matroxfb_dh_open(struct fb_info* info, int user) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| if (minfo) { |
| int err; |
| |
| if (minfo->dead) { |
| return -ENXIO; |
| } |
| err = minfo->fbops.fb_open(&minfo->fbcon, user); |
| if (err) { |
| return err; |
| } |
| } |
| return 0; |
| #undef m2info |
| } |
| |
| static int matroxfb_dh_release(struct fb_info* info, int user) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| int err = 0; |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| if (minfo) { |
| err = minfo->fbops.fb_release(&minfo->fbcon, user); |
| } |
| return err; |
| #undef m2info |
| } |
| |
| /* |
| * This function is called before the register_framebuffer so |
| * no locking is needed. |
| */ |
| static void matroxfb_dh_init_fix(struct matroxfb_dh_fb_info *m2info) |
| { |
| struct fb_fix_screeninfo *fix = &m2info->fbcon.fix; |
| |
| strcpy(fix->id, "MATROX DH"); |
| |
| fix->smem_start = m2info->video.base; |
| fix->smem_len = m2info->video.len_usable; |
| fix->ypanstep = 1; |
| fix->ywrapstep = 0; |
| fix->xpanstep = 8; /* TBD */ |
| fix->mmio_start = m2info->mmio.base; |
| fix->mmio_len = m2info->mmio.len; |
| fix->accel = 0; /* no accel... */ |
| } |
| |
| static int matroxfb_dh_check_var(struct fb_var_screeninfo* var, struct fb_info* info) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| int visual; |
| int cmap_len; |
| int mode; |
| |
| return matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode); |
| #undef m2info |
| } |
| |
| static int matroxfb_dh_set_par(struct fb_info* info) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| int visual; |
| int cmap_len; |
| int mode; |
| int err; |
| struct fb_var_screeninfo* var = &info->var; |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| if ((err = matroxfb_dh_decode_var(m2info, var, &visual, &cmap_len, &mode)) != 0) |
| return err; |
| /* cmap */ |
| { |
| m2info->fbcon.screen_base = vaddr_va(m2info->video.vbase); |
| m2info->fbcon.fix.visual = visual; |
| m2info->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; |
| m2info->fbcon.fix.type_aux = 0; |
| m2info->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; |
| } |
| { |
| struct my_timming mt; |
| unsigned int pos; |
| int out; |
| int cnt; |
| |
| matroxfb_var2my(&m2info->fbcon.var, &mt); |
| mt.crtc = MATROXFB_SRC_CRTC2; |
| /* CRTC2 delay */ |
| mt.delay = 34; |
| |
| pos = (m2info->fbcon.var.yoffset * m2info->fbcon.var.xres_virtual + m2info->fbcon.var.xoffset) * m2info->fbcon.var.bits_per_pixel >> 3; |
| pos += m2info->video.offbase; |
| cnt = 0; |
| down_read(&minfo->altout.lock); |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
| cnt++; |
| if (minfo->outputs[out].output->compute) { |
| minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); |
| } |
| } |
| } |
| minfo->crtc2.pixclock = mt.pixclock; |
| minfo->crtc2.mnp = mt.mnp; |
| up_read(&minfo->altout.lock); |
| if (cnt) { |
| matroxfb_dh_restore(m2info, &mt, mode, pos); |
| } else { |
| matroxfb_dh_disable(m2info); |
| } |
| DAC1064_global_init(minfo); |
| DAC1064_global_restore(minfo); |
| down_read(&minfo->altout.lock); |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && |
| minfo->outputs[out].output->program) { |
| minfo->outputs[out].output->program(minfo->outputs[out].data); |
| } |
| } |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && |
| minfo->outputs[out].output->start) { |
| minfo->outputs[out].output->start(minfo->outputs[out].data); |
| } |
| } |
| up_read(&minfo->altout.lock); |
| } |
| m2info->initialized = 1; |
| return 0; |
| #undef m2info |
| } |
| |
| static int matroxfb_dh_pan_display(struct fb_var_screeninfo* var, struct fb_info* info) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| matroxfb_dh_pan_var(m2info, var); |
| return 0; |
| #undef m2info |
| } |
| |
| static int matroxfb_dh_get_vblank(const struct matroxfb_dh_fb_info* m2info, struct fb_vblank* vblank) { |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| matroxfb_enable_irq(minfo, 0); |
| memset(vblank, 0, sizeof(*vblank)); |
| vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VBLANK; |
| /* mask out reserved bits + field number (odd/even) */ |
| vblank->vcount = mga_inl(0x3C48) & 0x000007FF; |
| /* compatibility stuff */ |
| if (vblank->vcount >= m2info->fbcon.var.yres) |
| vblank->flags |= FB_VBLANK_VBLANKING; |
| if (test_bit(0, &minfo->irq_flags)) { |
| vblank->flags |= FB_VBLANK_HAVE_COUNT; |
| /* Only one writer, aligned int value... |
| it should work without lock and without atomic_t */ |
| vblank->count = minfo->crtc2.vsync.cnt; |
| } |
| return 0; |
| } |
| |
| static int matroxfb_dh_ioctl(struct fb_info *info, |
| unsigned int cmd, |
| unsigned long arg) |
| { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| struct matrox_fb_info *minfo = m2info->primary_dev; |
| |
| DBG(__func__) |
| |
| switch (cmd) { |
| case FBIOGET_VBLANK: |
| { |
| struct fb_vblank vblank; |
| int err; |
| |
| err = matroxfb_dh_get_vblank(m2info, &vblank); |
| if (err) |
| return err; |
| if (copy_to_user((void __user *)arg, &vblank, sizeof(vblank))) |
| return -EFAULT; |
| return 0; |
| } |
| case FBIO_WAITFORVSYNC: |
| { |
| u_int32_t crt; |
| |
| if (get_user(crt, (u_int32_t __user *)arg)) |
| return -EFAULT; |
| |
| if (crt != 0) |
| return -ENODEV; |
| return matroxfb_wait_for_sync(minfo, 1); |
| } |
| case MATROXFB_SET_OUTPUT_MODE: |
| case MATROXFB_GET_OUTPUT_MODE: |
| case MATROXFB_GET_ALL_OUTPUTS: |
| { |
| return minfo->fbcon.fbops->fb_ioctl(&minfo->fbcon, cmd, arg); |
| } |
| case MATROXFB_SET_OUTPUT_CONNECTION: |
| { |
| u_int32_t tmp; |
| int out; |
| int changes; |
| |
| if (get_user(tmp, (u_int32_t __user *)arg)) |
| return -EFAULT; |
| for (out = 0; out < 32; out++) { |
| if (tmp & (1 << out)) { |
| if (out >= MATROXFB_MAX_OUTPUTS) |
| return -ENXIO; |
| if (!minfo->outputs[out].output) |
| return -ENXIO; |
| switch (minfo->outputs[out].src) { |
| case MATROXFB_SRC_NONE: |
| case MATROXFB_SRC_CRTC2: |
| break; |
| default: |
| return -EBUSY; |
| } |
| } |
| } |
| if (minfo->devflags.panellink) { |
| if (tmp & MATROXFB_OUTPUT_CONN_DFP) |
| return -EINVAL; |
| if ((minfo->outputs[2].src == MATROXFB_SRC_CRTC1) && tmp) |
| return -EBUSY; |
| } |
| changes = 0; |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (tmp & (1 << out)) { |
| if (minfo->outputs[out].src != MATROXFB_SRC_CRTC2) { |
| changes = 1; |
| minfo->outputs[out].src = MATROXFB_SRC_CRTC2; |
| } |
| } else if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
| changes = 1; |
| minfo->outputs[out].src = MATROXFB_SRC_NONE; |
| } |
| } |
| if (!changes) |
| return 0; |
| matroxfb_dh_set_par(info); |
| return 0; |
| } |
| case MATROXFB_GET_OUTPUT_CONNECTION: |
| { |
| u_int32_t conn = 0; |
| int out; |
| |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
| conn |= 1 << out; |
| } |
| } |
| if (put_user(conn, (u_int32_t __user *)arg)) |
| return -EFAULT; |
| return 0; |
| } |
| case MATROXFB_GET_AVAILABLE_OUTPUTS: |
| { |
| u_int32_t tmp = 0; |
| int out; |
| |
| for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
| if (minfo->outputs[out].output) { |
| switch (minfo->outputs[out].src) { |
| case MATROXFB_SRC_NONE: |
| case MATROXFB_SRC_CRTC2: |
| tmp |= 1 << out; |
| break; |
| } |
| } |
| } |
| if (minfo->devflags.panellink) { |
| tmp &= ~MATROXFB_OUTPUT_CONN_DFP; |
| if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) { |
| tmp = 0; |
| } |
| } |
| if (put_user(tmp, (u_int32_t __user *)arg)) |
| return -EFAULT; |
| return 0; |
| } |
| } |
| return -ENOTTY; |
| #undef m2info |
| } |
| |
| static int matroxfb_dh_blank(int blank, struct fb_info* info) { |
| #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
| switch (blank) { |
| case 1: |
| case 2: |
| case 3: |
| case 4: |
| default:; |
| } |
| /* do something... */ |
| return 0; |
| #undef m2info |
| } |
| |
| static const struct fb_ops matroxfb_dh_ops = { |
| .owner = THIS_MODULE, |
| .fb_open = matroxfb_dh_open, |
| .fb_release = matroxfb_dh_release, |
| .fb_check_var = matroxfb_dh_check_var, |
| .fb_set_par = matroxfb_dh_set_par, |
| .fb_setcolreg = matroxfb_dh_setcolreg, |
| .fb_pan_display =matroxfb_dh_pan_display, |
| .fb_blank = matroxfb_dh_blank, |
| .fb_ioctl = matroxfb_dh_ioctl, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| }; |
| |
| static struct fb_var_screeninfo matroxfb_dh_defined = { |
| 640,480,640,480,/* W,H, virtual W,H */ |
| 0,0, /* offset */ |
| 32, /* depth */ |
| 0, /* gray */ |
| {0,0,0}, /* R */ |
| {0,0,0}, /* G */ |
| {0,0,0}, /* B */ |
| {0,0,0}, /* alpha */ |
| 0, /* nonstd */ |
| FB_ACTIVATE_NOW, |
| -1,-1, /* display size */ |
| 0, /* accel flags */ |
| 39721L,48L,16L,33L,10L, |
| 96L,2,0, /* no sync info */ |
| FB_VMODE_NONINTERLACED, |
| }; |
| |
| static int matroxfb_dh_regit(const struct matrox_fb_info *minfo, |
| struct matroxfb_dh_fb_info *m2info) |
| { |
| #define minfo (m2info->primary_dev) |
| void* oldcrtc2; |
| |
| m2info->fbcon.fbops = &matroxfb_dh_ops; |
| m2info->fbcon.flags = FBINFO_FLAG_DEFAULT; |
| m2info->fbcon.flags |= FBINFO_HWACCEL_XPAN | |
| FBINFO_HWACCEL_YPAN; |
| m2info->fbcon.pseudo_palette = m2info->cmap; |
| fb_alloc_cmap(&m2info->fbcon.cmap, 256, 1); |
| |
| if (mem < 64) |
| mem *= 1024; |
| if (mem < 64*1024) |
| mem *= 1024; |
| mem &= ~0x00000FFF; /* PAGE_MASK? */ |
| if (minfo->video.len_usable + mem <= minfo->video.len) |
| m2info->video.offbase = minfo->video.len - mem; |
| else if (minfo->video.len < mem) { |
| return -ENOMEM; |
| } else { /* check yres on first head... */ |
| m2info->video.borrowed = mem; |
| minfo->video.len_usable -= mem; |
| m2info->video.offbase = minfo->video.len_usable; |
| } |
| m2info->video.base = minfo->video.base + m2info->video.offbase; |
| m2info->video.len = m2info->video.len_usable = m2info->video.len_maximum = mem; |
| m2info->video.vbase.vaddr = vaddr_va(minfo->video.vbase) + m2info->video.offbase; |
| m2info->mmio.base = minfo->mmio.base; |
| m2info->mmio.vbase = minfo->mmio.vbase; |
| m2info->mmio.len = minfo->mmio.len; |
| |
| matroxfb_dh_init_fix(m2info); |
| if (register_framebuffer(&m2info->fbcon)) { |
| return -ENXIO; |
| } |
| if (!m2info->initialized) |
| fb_set_var(&m2info->fbcon, &matroxfb_dh_defined); |
| down_write(&minfo->crtc2.lock); |
| oldcrtc2 = minfo->crtc2.info; |
| minfo->crtc2.info = m2info; |
| up_write(&minfo->crtc2.lock); |
| if (oldcrtc2) { |
| printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 already present: %p\n", |
| oldcrtc2); |
| } |
| return 0; |
| #undef minfo |
| } |
| |
| /* ************************** */ |
| |
| static int matroxfb_dh_registerfb(struct matroxfb_dh_fb_info* m2info) { |
| #define minfo (m2info->primary_dev) |
| if (matroxfb_dh_regit(minfo, m2info)) { |
| printk(KERN_ERR "matroxfb_crtc2: secondary head failed to register\n"); |
| return -1; |
| } |
| printk(KERN_INFO "matroxfb_crtc2: secondary head of fb%u was registered as fb%u\n", |
| minfo->fbcon.node, m2info->fbcon.node); |
| m2info->fbcon_registered = 1; |
| return 0; |
| #undef minfo |
| } |
| |
| static void matroxfb_dh_deregisterfb(struct matroxfb_dh_fb_info* m2info) { |
| #define minfo (m2info->primary_dev) |
| if (m2info->fbcon_registered) { |
| int id; |
| struct matroxfb_dh_fb_info* crtc2; |
| |
| down_write(&minfo->crtc2.lock); |
| crtc2 = minfo->crtc2.info; |
| if (crtc2 == m2info) |
| minfo->crtc2.info = NULL; |
| up_write(&minfo->crtc2.lock); |
| if (crtc2 != m2info) { |
| printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 mismatch at unload: %p != %p\n", |
| crtc2, m2info); |
| printk(KERN_ERR "matroxfb_crtc2: Expect kernel crash after module unload.\n"); |
| return; |
| } |
| id = m2info->fbcon.node; |
| unregister_framebuffer(&m2info->fbcon); |
| /* return memory back to primary head */ |
| minfo->video.len_usable += m2info->video.borrowed; |
| printk(KERN_INFO "matroxfb_crtc2: fb%u unregistered\n", id); |
| m2info->fbcon_registered = 0; |
| } |
| #undef minfo |
| } |
| |
| static void* matroxfb_crtc2_probe(struct matrox_fb_info* minfo) { |
| struct matroxfb_dh_fb_info* m2info; |
| |
| /* hardware is CRTC2 incapable... */ |
| if (!minfo->devflags.crtc2) |
| return NULL; |
| m2info = kzalloc(sizeof(*m2info), GFP_KERNEL); |
| if (!m2info) |
| return NULL; |
| |
| m2info->primary_dev = minfo; |
| if (matroxfb_dh_registerfb(m2info)) { |
| kfree(m2info); |
| printk(KERN_ERR "matroxfb_crtc2: CRTC2 framebuffer failed to register\n"); |
| return NULL; |
| } |
| return m2info; |
| } |
| |
| static void matroxfb_crtc2_remove(struct matrox_fb_info* minfo, void* crtc2) { |
| matroxfb_dh_deregisterfb(crtc2); |
| kfree(crtc2); |
| } |
| |
| static struct matroxfb_driver crtc2 = { |
| .name = "Matrox G400 CRTC2", |
| .probe = matroxfb_crtc2_probe, |
| .remove = matroxfb_crtc2_remove }; |
| |
| static int matroxfb_crtc2_init(void) { |
| if (fb_get_options("matrox_crtc2fb", NULL)) |
| return -ENODEV; |
| |
| matroxfb_register_driver(&crtc2); |
| return 0; |
| } |
| |
| static void matroxfb_crtc2_exit(void) { |
| matroxfb_unregister_driver(&crtc2); |
| } |
| |
| MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>"); |
| MODULE_DESCRIPTION("Matrox G400 CRTC2 driver"); |
| MODULE_LICENSE("GPL"); |
| module_init(matroxfb_crtc2_init); |
| module_exit(matroxfb_crtc2_exit); |
| /* we do not have __setup() yet */ |