| /* | |
| * | |
| * 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 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) { | |
| printk(KERN_ERR "matroxfb_crtc2: Not enough memory for CRTC2 control structs\n"); | |
| 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 */ |