blob: e351e083a869742dc79654837653c61155fbefa7 [file] [log] [blame]
Wu Zhangjind7edf472009-11-23 10:28:24 +08001/*
2 * Silicon Motion SM7XX frame buffer device
3 *
4 * Copyright (C) 2006 Silicon Motion Technology Corp.
Javier M. Mellid86f31252012-04-26 20:45:49 +02005 * Authors: Ge Wang, gewang@siliconmotion.com
6 * Boyod boyod.yang@siliconmotion.com.cn
Wu Zhangjind7edf472009-11-23 10:28:24 +08007 *
8 * Copyright (C) 2009 Lemote, Inc.
Javier M. Mellid86f31252012-04-26 20:45:49 +02009 * Author: Wu Zhangjin, wuzhangjin@gmail.com
Wu Zhangjind7edf472009-11-23 10:28:24 +080010 *
Javier M. Melliddc762c42011-05-07 03:11:58 +020011 * Copyright (C) 2011 Igalia, S.L.
Javier M. Mellid86f31252012-04-26 20:45:49 +020012 * Author: Javier M. Mellid <jmunhoz@igalia.com>
Javier M. Melliddc762c42011-05-07 03:11:58 +020013 *
Javier M. Mellid86f31252012-04-26 20:45:49 +020014 * This file is subject to the terms and conditions of the GNU General Public
15 * License. See the file COPYING in the main directory of this archive for
16 * more details.
Wu Zhangjind7edf472009-11-23 10:28:24 +080017 *
Wu Zhangjind7edf472009-11-23 10:28:24 +080018 */
19
Wu Zhangjind7edf472009-11-23 10:28:24 +080020#include <linux/io.h>
21#include <linux/fb.h>
22#include <linux/pci.h>
23#include <linux/init.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090024#include <linux/slab.h>
Wu Zhangjind7edf472009-11-23 10:28:24 +080025#include <linux/uaccess.h>
Paul Gortmaker99c97852011-07-03 15:49:50 -040026#include <linux/module.h>
Wu Zhangjind7edf472009-11-23 10:28:24 +080027#include <linux/console.h>
28#include <linux/screen_info.h>
29
30#ifdef CONFIG_PM
31#include <linux/pm.h>
32#endif
33
Wu Zhangjind7edf472009-11-23 10:28:24 +080034#include "smtcfb.h"
Wu Zhangjind7edf472009-11-23 10:28:24 +080035
Javier M. Melliddc762c42011-05-07 03:11:58 +020036struct screen_info smtc_screen_info;
37
Wu Zhangjind7edf472009-11-23 10:28:24 +080038/*
39* Private structure
40*/
41struct smtcfb_info {
Wu Zhangjind7edf472009-11-23 10:28:24 +080042 struct fb_info fb;
43 struct display_switch *dispsw;
44 struct pci_dev *dev;
45 signed int currcon;
46
47 struct {
48 u8 red, green, blue;
49 } palette[NR_RGB];
50
51 u_int palette_size;
52};
53
54struct par_info {
55 /*
56 * Hardware
57 */
58 u16 chipID;
59 unsigned char __iomem *m_pMMIO;
60 char __iomem *m_pLFB;
61 char *m_pDPR;
62 char *m_pVPR;
63 char *m_pCPR;
64
65 u_int width;
66 u_int height;
67 u_int hz;
68 u_long BaseAddressInVRAM;
69 u8 chipRevID;
70};
71
72struct vesa_mode_table {
73 char mode_index[6];
74 u16 lfb_width;
75 u16 lfb_height;
76 u16 lfb_depth;
77};
78
79static struct vesa_mode_table vesa_mode[] = {
80 {"0x301", 640, 480, 8},
81 {"0x303", 800, 600, 8},
Javier M. Mellid3b70a262011-04-30 17:44:26 +020082 {"0x305", 1024, 768, 8},
Wu Zhangjind7edf472009-11-23 10:28:24 +080083 {"0x307", 1280, 1024, 8},
84
85 {"0x311", 640, 480, 16},
86 {"0x314", 800, 600, 16},
Javier M. Mellid3b70a262011-04-30 17:44:26 +020087 {"0x317", 1024, 768, 16},
Wu Zhangjind7edf472009-11-23 10:28:24 +080088 {"0x31A", 1280, 1024, 16},
89
90 {"0x312", 640, 480, 24},
91 {"0x315", 800, 600, 24},
Javier M. Mellid3b70a262011-04-30 17:44:26 +020092 {"0x318", 1024, 768, 24},
Wu Zhangjind7edf472009-11-23 10:28:24 +080093 {"0x31B", 1280, 1024, 24},
94};
95
96char __iomem *smtc_RegBaseAddress; /* Memory Map IO starting address */
97char __iomem *smtc_VRAMBaseAddress; /* video memory starting address */
98
Wu Zhangjind7edf472009-11-23 10:28:24 +080099static u32 colreg[17];
100static struct par_info hw; /* hardware information */
101
Javier M. Melliddc762c42011-05-07 03:11:58 +0200102static struct fb_var_screeninfo smtcfb_var = {
103 .xres = 1024,
104 .yres = 600,
105 .xres_virtual = 1024,
106 .yres_virtual = 600,
107 .bits_per_pixel = 16,
108 .red = {16, 8, 0},
109 .green = {8, 8, 0},
110 .blue = {0, 8, 0},
111 .activate = FB_ACTIVATE_NOW,
112 .height = -1,
113 .width = -1,
114 .vmode = FB_VMODE_NONINTERLACED,
115};
116
117static struct fb_fix_screeninfo smtcfb_fix = {
118 .id = "sm712fb",
119 .type = FB_TYPE_PACKED_PIXELS,
120 .visual = FB_VISUAL_TRUECOLOR,
121 .line_length = 800 * 3,
122 .accel = FB_ACCEL_SMI_LYNX,
123};
124
Wu Zhangjind7edf472009-11-23 10:28:24 +0800125static void sm712_set_timing(struct smtcfb_info *sfb,
126 struct par_info *ppar_info)
127{
128 int i = 0, j = 0;
129 u32 m_nScreenStride;
130
Javier M. Mellidbfdd4032012-04-26 20:45:51 +0200131 dev_dbg(&sfb->dev->dev,
132 "ppar_info->width=%d ppar_info->height=%d"
133 "sfb->fb.var.bits_per_pixel=%d ppar_info->hz=%d\n",
Wu Zhangjind7edf472009-11-23 10:28:24 +0800134 ppar_info->width, ppar_info->height,
135 sfb->fb.var.bits_per_pixel, ppar_info->hz);
136
137 for (j = 0; j < numVGAModes; j++) {
138 if (VGAMode[j].mmSizeX == ppar_info->width &&
139 VGAMode[j].mmSizeY == ppar_info->height &&
140 VGAMode[j].bpp == sfb->fb.var.bits_per_pixel &&
141 VGAMode[j].hz == ppar_info->hz) {
142
Javier M. Mellidbfdd4032012-04-26 20:45:51 +0200143 dev_dbg(&sfb->dev->dev,
144 "VGAMode[j].mmSizeX=%d VGAMode[j].mmSizeY=%d"
145 "VGAMode[j].bpp=%d VGAMode[j].hz=%d\n",
146 VGAMode[j].mmSizeX, VGAMode[j].mmSizeY,
147 VGAMode[j].bpp, VGAMode[j].hz);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800148
Javier M. Mellidbfdd4032012-04-26 20:45:51 +0200149 dev_dbg(&sfb->dev->dev,
150 "VGAMode index=%d\n", j);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800151
152 smtc_mmiowb(0x0, 0x3c6);
153
154 smtc_seqw(0, 0x1);
155
156 smtc_mmiowb(VGAMode[j].Init_MISC, 0x3c2);
157
158 /* init SEQ register SR00 - SR04 */
159 for (i = 0; i < SIZE_SR00_SR04; i++)
160 smtc_seqw(i, VGAMode[j].Init_SR00_SR04[i]);
161
162 /* init SEQ register SR10 - SR24 */
163 for (i = 0; i < SIZE_SR10_SR24; i++)
164 smtc_seqw(i + 0x10,
165 VGAMode[j].Init_SR10_SR24[i]);
166
167 /* init SEQ register SR30 - SR75 */
168 for (i = 0; i < SIZE_SR30_SR75; i++)
169 if (((i + 0x30) != 0x62) \
170 && ((i + 0x30) != 0x6a) \
171 && ((i + 0x30) != 0x6b))
172 smtc_seqw(i + 0x30,
173 VGAMode[j].Init_SR30_SR75[i]);
174
175 /* init SEQ register SR80 - SR93 */
176 for (i = 0; i < SIZE_SR80_SR93; i++)
177 smtc_seqw(i + 0x80,
178 VGAMode[j].Init_SR80_SR93[i]);
179
180 /* init SEQ register SRA0 - SRAF */
181 for (i = 0; i < SIZE_SRA0_SRAF; i++)
182 smtc_seqw(i + 0xa0,
183 VGAMode[j].Init_SRA0_SRAF[i]);
184
185 /* init Graphic register GR00 - GR08 */
186 for (i = 0; i < SIZE_GR00_GR08; i++)
187 smtc_grphw(i, VGAMode[j].Init_GR00_GR08[i]);
188
189 /* init Attribute register AR00 - AR14 */
190 for (i = 0; i < SIZE_AR00_AR14; i++)
191 smtc_attrw(i, VGAMode[j].Init_AR00_AR14[i]);
192
193 /* init CRTC register CR00 - CR18 */
194 for (i = 0; i < SIZE_CR00_CR18; i++)
195 smtc_crtcw(i, VGAMode[j].Init_CR00_CR18[i]);
196
197 /* init CRTC register CR30 - CR4D */
198 for (i = 0; i < SIZE_CR30_CR4D; i++)
199 smtc_crtcw(i + 0x30,
200 VGAMode[j].Init_CR30_CR4D[i]);
201
202 /* init CRTC register CR90 - CRA7 */
203 for (i = 0; i < SIZE_CR90_CRA7; i++)
204 smtc_crtcw(i + 0x90,
205 VGAMode[j].Init_CR90_CRA7[i]);
206 }
207 }
208 smtc_mmiowb(0x67, 0x3c2);
209
210 /* set VPR registers */
211 writel(0x0, ppar_info->m_pVPR + 0x0C);
212 writel(0x0, ppar_info->m_pVPR + 0x40);
213
214 /* set data width */
215 m_nScreenStride =
216 (ppar_info->width * sfb->fb.var.bits_per_pixel) / 64;
217 switch (sfb->fb.var.bits_per_pixel) {
218 case 8:
219 writel(0x0, ppar_info->m_pVPR + 0x0);
220 break;
221 case 16:
222 writel(0x00020000, ppar_info->m_pVPR + 0x0);
223 break;
224 case 24:
225 writel(0x00040000, ppar_info->m_pVPR + 0x0);
226 break;
227 case 32:
228 writel(0x00030000, ppar_info->m_pVPR + 0x0);
229 break;
230 }
231 writel((u32) (((m_nScreenStride + 2) << 16) | m_nScreenStride),
232 ppar_info->m_pVPR + 0x10);
233
234}
235
236static void sm712_setpalette(int regno, unsigned red, unsigned green,
237 unsigned blue, struct fb_info *info)
238{
239 struct par_info *cur_par = (struct par_info *)info->par;
240
241 if (cur_par->BaseAddressInVRAM)
242 /*
243 * second display palette for dual head. Enable CRT RAM, 6-bit
244 * RAM
245 */
246 smtc_seqw(0x66, (smtc_seqr(0x66) & 0xC3) | 0x20);
247 else
248 /* primary display palette. Enable LCD RAM only, 6-bit RAM */
249 smtc_seqw(0x66, (smtc_seqr(0x66) & 0xC3) | 0x10);
250 smtc_mmiowb(regno, dac_reg);
251 smtc_mmiowb(red >> 10, dac_val);
252 smtc_mmiowb(green >> 10, dac_val);
253 smtc_mmiowb(blue >> 10, dac_val);
254}
255
256static void smtc_set_timing(struct smtcfb_info *sfb, struct par_info
257 *ppar_info)
258{
259 switch (ppar_info->chipID) {
260 case 0x710:
261 case 0x712:
262 case 0x720:
263 sm712_set_timing(sfb, ppar_info);
264 break;
265 }
266}
267
Wu Zhangjind7edf472009-11-23 10:28:24 +0800268/* chan_to_field
269 *
270 * convert a colour value into a field position
271 *
272 * from pxafb.c
273 */
274
275static inline unsigned int chan_to_field(unsigned int chan,
276 struct fb_bitfield *bf)
277{
278 chan &= 0xffff;
279 chan >>= 16 - bf->length;
280 return chan << bf->offset;
281}
282
Wu Zhangjin3af80572010-01-06 16:33:10 +0800283static int cfb_blank(int blank_mode, struct fb_info *info)
Wu Zhangjind7edf472009-11-23 10:28:24 +0800284{
285 /* clear DPMS setting */
286 switch (blank_mode) {
287 case FB_BLANK_UNBLANK:
288 /* Screen On: HSync: On, VSync : On */
289 smtc_seqw(0x01, (smtc_seqr(0x01) & (~0x20)));
290 smtc_seqw(0x6a, 0x16);
291 smtc_seqw(0x6b, 0x02);
292 smtc_seqw(0x21, (smtc_seqr(0x21) & 0x77));
293 smtc_seqw(0x22, (smtc_seqr(0x22) & (~0x30)));
294 smtc_seqw(0x23, (smtc_seqr(0x23) & (~0xc0)));
295 smtc_seqw(0x24, (smtc_seqr(0x24) | 0x01));
296 smtc_seqw(0x31, (smtc_seqr(0x31) | 0x03));
297 break;
298 case FB_BLANK_NORMAL:
299 /* Screen Off: HSync: On, VSync : On Soft blank */
300 smtc_seqw(0x01, (smtc_seqr(0x01) & (~0x20)));
301 smtc_seqw(0x6a, 0x16);
302 smtc_seqw(0x6b, 0x02);
303 smtc_seqw(0x22, (smtc_seqr(0x22) & (~0x30)));
304 smtc_seqw(0x23, (smtc_seqr(0x23) & (~0xc0)));
305 smtc_seqw(0x24, (smtc_seqr(0x24) | 0x01));
306 smtc_seqw(0x31, ((smtc_seqr(0x31) & (~0x07)) | 0x00));
307 break;
308 case FB_BLANK_VSYNC_SUSPEND:
309 /* Screen On: HSync: On, VSync : Off */
310 smtc_seqw(0x01, (smtc_seqr(0x01) | 0x20));
311 smtc_seqw(0x20, (smtc_seqr(0x20) & (~0xB0)));
312 smtc_seqw(0x6a, 0x0c);
313 smtc_seqw(0x6b, 0x02);
314 smtc_seqw(0x21, (smtc_seqr(0x21) | 0x88));
315 smtc_seqw(0x22, ((smtc_seqr(0x22) & (~0x30)) | 0x20));
316 smtc_seqw(0x23, ((smtc_seqr(0x23) & (~0xc0)) | 0x20));
317 smtc_seqw(0x24, (smtc_seqr(0x24) & (~0x01)));
318 smtc_seqw(0x31, ((smtc_seqr(0x31) & (~0x07)) | 0x00));
319 smtc_seqw(0x34, (smtc_seqr(0x34) | 0x80));
320 break;
321 case FB_BLANK_HSYNC_SUSPEND:
322 /* Screen On: HSync: Off, VSync : On */
323 smtc_seqw(0x01, (smtc_seqr(0x01) | 0x20));
324 smtc_seqw(0x20, (smtc_seqr(0x20) & (~0xB0)));
325 smtc_seqw(0x6a, 0x0c);
326 smtc_seqw(0x6b, 0x02);
327 smtc_seqw(0x21, (smtc_seqr(0x21) | 0x88));
328 smtc_seqw(0x22, ((smtc_seqr(0x22) & (~0x30)) | 0x10));
329 smtc_seqw(0x23, ((smtc_seqr(0x23) & (~0xc0)) | 0xD8));
330 smtc_seqw(0x24, (smtc_seqr(0x24) & (~0x01)));
331 smtc_seqw(0x31, ((smtc_seqr(0x31) & (~0x07)) | 0x00));
332 smtc_seqw(0x34, (smtc_seqr(0x34) | 0x80));
333 break;
334 case FB_BLANK_POWERDOWN:
335 /* Screen On: HSync: Off, VSync : Off */
336 smtc_seqw(0x01, (smtc_seqr(0x01) | 0x20));
337 smtc_seqw(0x20, (smtc_seqr(0x20) & (~0xB0)));
338 smtc_seqw(0x6a, 0x0c);
339 smtc_seqw(0x6b, 0x02);
340 smtc_seqw(0x21, (smtc_seqr(0x21) | 0x88));
341 smtc_seqw(0x22, ((smtc_seqr(0x22) & (~0x30)) | 0x30));
342 smtc_seqw(0x23, ((smtc_seqr(0x23) & (~0xc0)) | 0xD8));
343 smtc_seqw(0x24, (smtc_seqr(0x24) & (~0x01)));
344 smtc_seqw(0x31, ((smtc_seqr(0x31) & (~0x07)) | 0x00));
345 smtc_seqw(0x34, (smtc_seqr(0x34) | 0x80));
346 break;
347 default:
348 return -EINVAL;
349 }
350
351 return 0;
352}
353
354static int smtc_setcolreg(unsigned regno, unsigned red, unsigned green,
355 unsigned blue, unsigned trans, struct fb_info *info)
356{
357 struct smtcfb_info *sfb = (struct smtcfb_info *)info;
358 u32 val;
359
360 if (regno > 255)
361 return 1;
362
363 switch (sfb->fb.fix.visual) {
364 case FB_VISUAL_DIRECTCOLOR:
365 case FB_VISUAL_TRUECOLOR:
366 /*
367 * 16/32 bit true-colour, use pseuo-palette for 16 base color
368 */
369 if (regno < 16) {
370 if (sfb->fb.var.bits_per_pixel == 16) {
371 u32 *pal = sfb->fb.pseudo_palette;
372 val = chan_to_field(red, &sfb->fb.var.red);
373 val |= chan_to_field(green, \
374 &sfb->fb.var.green);
375 val |= chan_to_field(blue, &sfb->fb.var.blue);
376#ifdef __BIG_ENDIAN
377 pal[regno] =
378 ((red & 0xf800) >> 8) |
379 ((green & 0xe000) >> 13) |
380 ((green & 0x1c00) << 3) |
381 ((blue & 0xf800) >> 3);
382#else
383 pal[regno] = val;
384#endif
385 } else {
386 u32 *pal = sfb->fb.pseudo_palette;
387 val = chan_to_field(red, &sfb->fb.var.red);
388 val |= chan_to_field(green, \
389 &sfb->fb.var.green);
390 val |= chan_to_field(blue, &sfb->fb.var.blue);
391#ifdef __BIG_ENDIAN
392 val =
393 (val & 0xff00ff00 >> 8) |
394 (val & 0x00ff00ff << 8);
395#endif
396 pal[regno] = val;
397 }
398 }
399 break;
400
401 case FB_VISUAL_PSEUDOCOLOR:
402 /* color depth 8 bit */
403 sm712_setpalette(regno, red, green, blue, info);
404 break;
405
406 default:
407 return 1; /* unknown type */
408 }
409
410 return 0;
411
412}
413
414#ifdef __BIG_ENDIAN
Josenivaldo Benito Jrc8100d22012-02-12 19:03:14 -0200415static ssize_t smtcfb_read(struct fb_info *info, char __user *buf, size_t
Wu Zhangjind7edf472009-11-23 10:28:24 +0800416 count, loff_t *ppos)
417{
418 unsigned long p = *ppos;
419
420 u32 *buffer, *dst;
421 u32 __iomem *src;
422 int c, i, cnt = 0, err = 0;
423 unsigned long total_size;
424
425 if (!info || !info->screen_base)
426 return -ENODEV;
427
428 if (info->state != FBINFO_STATE_RUNNING)
429 return -EPERM;
430
431 total_size = info->screen_size;
432
433 if (total_size == 0)
434 total_size = info->fix.smem_len;
435
436 if (p >= total_size)
437 return 0;
438
439 if (count >= total_size)
440 count = total_size;
441
442 if (count + p > total_size)
443 count = total_size - p;
444
445 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL);
446 if (!buffer)
447 return -ENOMEM;
448
449 src = (u32 __iomem *) (info->screen_base + p);
450
451 if (info->fbops->fb_sync)
452 info->fbops->fb_sync(info);
453
454 while (count) {
455 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
456 dst = buffer;
457 for (i = c >> 2; i--;) {
458 *dst = fb_readl(src++);
459 *dst =
460 (*dst & 0xff00ff00 >> 8) |
461 (*dst & 0x00ff00ff << 8);
462 dst++;
463 }
464 if (c & 3) {
465 u8 *dst8 = (u8 *) dst;
466 u8 __iomem *src8 = (u8 __iomem *) src;
467
468 for (i = c & 3; i--;) {
469 if (i & 1) {
470 *dst8++ = fb_readb(++src8);
471 } else {
472 *dst8++ = fb_readb(--src8);
473 src8 += 2;
474 }
475 }
476 src = (u32 __iomem *) src8;
477 }
478
479 if (copy_to_user(buf, buffer, c)) {
480 err = -EFAULT;
481 break;
482 }
483 *ppos += c;
484 buf += c;
485 cnt += c;
486 count -= c;
487 }
488
489 kfree(buffer);
490
491 return (err) ? err : cnt;
492}
493
494static ssize_t
495smtcfb_write(struct fb_info *info, const char __user *buf, size_t count,
496 loff_t *ppos)
497{
498 unsigned long p = *ppos;
499
500 u32 *buffer, *src;
501 u32 __iomem *dst;
502 int c, i, cnt = 0, err = 0;
503 unsigned long total_size;
504
505 if (!info || !info->screen_base)
506 return -ENODEV;
507
508 if (info->state != FBINFO_STATE_RUNNING)
509 return -EPERM;
510
511 total_size = info->screen_size;
512
513 if (total_size == 0)
514 total_size = info->fix.smem_len;
515
516 if (p > total_size)
517 return -EFBIG;
518
519 if (count > total_size) {
520 err = -EFBIG;
521 count = total_size;
522 }
523
524 if (count + p > total_size) {
525 if (!err)
526 err = -ENOSPC;
527
528 count = total_size - p;
529 }
530
531 buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count, GFP_KERNEL);
532 if (!buffer)
533 return -ENOMEM;
534
535 dst = (u32 __iomem *) (info->screen_base + p);
536
537 if (info->fbops->fb_sync)
538 info->fbops->fb_sync(info);
539
540 while (count) {
541 c = (count > PAGE_SIZE) ? PAGE_SIZE : count;
542 src = buffer;
543
544 if (copy_from_user(src, buf, c)) {
545 err = -EFAULT;
546 break;
547 }
548
549 for (i = c >> 2; i--;) {
550 fb_writel((*src & 0xff00ff00 >> 8) |
551 (*src & 0x00ff00ff << 8), dst++);
552 src++;
553 }
554 if (c & 3) {
555 u8 *src8 = (u8 *) src;
556 u8 __iomem *dst8 = (u8 __iomem *) dst;
557
558 for (i = c & 3; i--;) {
559 if (i & 1) {
560 fb_writeb(*src8++, ++dst8);
561 } else {
562 fb_writeb(*src8++, --dst8);
563 dst8 += 2;
564 }
565 }
566 dst = (u32 __iomem *) dst8;
567 }
568
569 *ppos += c;
570 buf += c;
571 cnt += c;
572 count -= c;
573 }
574
575 kfree(buffer);
576
577 return (cnt) ? cnt : err;
578}
579#endif /* ! __BIG_ENDIAN */
580
Wu Zhangjind7edf472009-11-23 10:28:24 +0800581void smtcfb_setmode(struct smtcfb_info *sfb)
582{
583 switch (sfb->fb.var.bits_per_pixel) {
584 case 32:
585 sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
586 sfb->fb.fix.line_length = sfb->fb.var.xres * 4;
587 sfb->fb.var.red.length = 8;
588 sfb->fb.var.green.length = 8;
589 sfb->fb.var.blue.length = 8;
590 sfb->fb.var.red.offset = 16;
591 sfb->fb.var.green.offset = 8;
592 sfb->fb.var.blue.offset = 0;
593
594 break;
595 case 8:
596 sfb->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
597 sfb->fb.fix.line_length = sfb->fb.var.xres;
598 sfb->fb.var.red.offset = 5;
599 sfb->fb.var.red.length = 3;
600 sfb->fb.var.green.offset = 2;
601 sfb->fb.var.green.length = 3;
602 sfb->fb.var.blue.offset = 0;
603 sfb->fb.var.blue.length = 2;
604 break;
605 case 24:
606 sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
607 sfb->fb.fix.line_length = sfb->fb.var.xres * 3;
608 sfb->fb.var.red.length = 8;
609 sfb->fb.var.green.length = 8;
610 sfb->fb.var.blue.length = 8;
611
612 sfb->fb.var.red.offset = 16;
613 sfb->fb.var.green.offset = 8;
614 sfb->fb.var.blue.offset = 0;
615
616 break;
617 case 16:
618 default:
619 sfb->fb.fix.visual = FB_VISUAL_TRUECOLOR;
620 sfb->fb.fix.line_length = sfb->fb.var.xres * 2;
621
622 sfb->fb.var.red.length = 5;
623 sfb->fb.var.green.length = 6;
624 sfb->fb.var.blue.length = 5;
625
626 sfb->fb.var.red.offset = 11;
627 sfb->fb.var.green.offset = 5;
628 sfb->fb.var.blue.offset = 0;
629
630 break;
631 }
632
633 hw.width = sfb->fb.var.xres;
634 hw.height = sfb->fb.var.yres;
635 hw.hz = 60;
636 smtc_set_timing(sfb, &hw);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800637}
638
Javier M. Melliddc762c42011-05-07 03:11:58 +0200639static int smtc_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
640{
641 /* sanity checks */
642 if (var->xres_virtual < var->xres)
643 var->xres_virtual = var->xres;
644
645 if (var->yres_virtual < var->yres)
646 var->yres_virtual = var->yres;
647
648 /* set valid default bpp */
649 if ((var->bits_per_pixel != 8) && (var->bits_per_pixel != 16) &&
650 (var->bits_per_pixel != 24) && (var->bits_per_pixel != 32))
651 var->bits_per_pixel = 16;
652
653 return 0;
654}
655
656static int smtc_set_par(struct fb_info *info)
657{
658 struct smtcfb_info *sfb = (struct smtcfb_info *)info;
659
660 smtcfb_setmode(sfb);
661
662 return 0;
663}
664
665static struct fb_ops smtcfb_ops = {
666 .owner = THIS_MODULE,
667 .fb_check_var = smtc_check_var,
668 .fb_set_par = smtc_set_par,
669 .fb_setcolreg = smtc_setcolreg,
670 .fb_blank = cfb_blank,
671 .fb_fillrect = cfb_fillrect,
672 .fb_imageblit = cfb_imageblit,
673 .fb_copyarea = cfb_copyarea,
674#ifdef __BIG_ENDIAN
675 .fb_read = smtcfb_read,
676 .fb_write = smtcfb_write,
677#endif
678};
679
Wu Zhangjind7edf472009-11-23 10:28:24 +0800680/*
681 * Alloc struct smtcfb_info and assign the default value
682 */
683static struct smtcfb_info *smtc_alloc_fb_info(struct pci_dev *dev,
684 char *name)
685{
686 struct smtcfb_info *sfb;
687
anish kumar617a0c72011-05-19 20:58:51 +0530688 sfb = kzalloc(sizeof(*sfb), GFP_KERNEL);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800689
690 if (!sfb)
691 return NULL;
692
Wu Zhangjind7edf472009-11-23 10:28:24 +0800693 sfb->currcon = -1;
694 sfb->dev = dev;
695
696 /*** Init sfb->fb with default value ***/
697 sfb->fb.flags = FBINFO_FLAG_DEFAULT;
698 sfb->fb.fbops = &smtcfb_ops;
699 sfb->fb.var = smtcfb_var;
700 sfb->fb.fix = smtcfb_fix;
701
702 strcpy(sfb->fb.fix.id, name);
703
704 sfb->fb.fix.type = FB_TYPE_PACKED_PIXELS;
705 sfb->fb.fix.type_aux = 0;
706 sfb->fb.fix.xpanstep = 0;
707 sfb->fb.fix.ypanstep = 0;
708 sfb->fb.fix.ywrapstep = 0;
709 sfb->fb.fix.accel = FB_ACCEL_SMI_LYNX;
710
711 sfb->fb.var.nonstd = 0;
712 sfb->fb.var.activate = FB_ACTIVATE_NOW;
713 sfb->fb.var.height = -1;
714 sfb->fb.var.width = -1;
715 /* text mode acceleration */
716 sfb->fb.var.accel_flags = FB_ACCELF_TEXT;
717 sfb->fb.var.vmode = FB_VMODE_NONINTERLACED;
718 sfb->fb.par = &hw;
719 sfb->fb.pseudo_palette = colreg;
720
721 return sfb;
722}
723
724/*
725 * Unmap in the memory mapped IO registers
726 */
727
728static void smtc_unmap_mmio(struct smtcfb_info *sfb)
729{
730 if (sfb && smtc_RegBaseAddress)
731 smtc_RegBaseAddress = NULL;
732}
733
734/*
735 * Map in the screen memory
736 */
737
738static int smtc_map_smem(struct smtcfb_info *sfb,
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200739 struct pci_dev *pdev, u_long smem_len)
Wu Zhangjind7edf472009-11-23 10:28:24 +0800740{
741 if (sfb->fb.var.bits_per_pixel == 32) {
742#ifdef __BIG_ENDIAN
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200743 sfb->fb.fix.smem_start = pci_resource_start(pdev, 0)
Wu Zhangjind7edf472009-11-23 10:28:24 +0800744 + 0x800000;
745#else
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200746 sfb->fb.fix.smem_start = pci_resource_start(pdev, 0);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800747#endif
748 } else {
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200749 sfb->fb.fix.smem_start = pci_resource_start(pdev, 0);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800750 }
751
752 sfb->fb.fix.smem_len = smem_len;
753
754 sfb->fb.screen_base = smtc_VRAMBaseAddress;
755
756 if (!sfb->fb.screen_base) {
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200757 dev_err(&pdev->dev,
758 "%s: unable to map screen memory\n", sfb->fb.fix.id);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800759 return -ENOMEM;
760 }
761
762 return 0;
763}
764
765/*
766 * Unmap in the screen memory
767 *
768 */
769static void smtc_unmap_smem(struct smtcfb_info *sfb)
770{
771 if (sfb && sfb->fb.screen_base) {
772 iounmap(sfb->fb.screen_base);
773 sfb->fb.screen_base = NULL;
774 }
775}
776
777/*
778 * We need to wake up the LynxEM+, and make sure its in linear memory mode.
779 */
780static inline void sm7xx_init_hw(void)
781{
782 outb_p(0x18, 0x3c4);
783 outb_p(0x11, 0x3c5);
784}
785
786static void smtc_free_fb_info(struct smtcfb_info *sfb)
787{
788 if (sfb) {
789 fb_alloc_cmap(&sfb->fb.cmap, 0, 0);
790 kfree(sfb);
791 }
792}
793
794/*
795 * sm712vga_setup - process command line options, get vga parameter
796 * @options: string of options
797 * Returns zero.
798 *
799 */
Javier M. Melliddc762c42011-05-07 03:11:58 +0200800static int __init sm712vga_setup(char *options)
Wu Zhangjind7edf472009-11-23 10:28:24 +0800801{
802 int index;
803
Javier M. Mellidbfdd4032012-04-26 20:45:51 +0200804 if (!options || !*options)
Wu Zhangjind7edf472009-11-23 10:28:24 +0800805 return -EINVAL;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800806
807 smtc_screen_info.lfb_width = 0;
808 smtc_screen_info.lfb_height = 0;
809 smtc_screen_info.lfb_depth = 0;
810
Javier M. Mellidbfdd4032012-04-26 20:45:51 +0200811 pr_debug("sm712vga_setup = %s\n", options);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800812
813 for (index = 0;
anish kumar1639c8a2011-05-19 20:58:42 +0530814 index < ARRAY_SIZE(vesa_mode);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800815 index++) {
816 if (strstr(options, vesa_mode[index].mode_index)) {
817 smtc_screen_info.lfb_width = vesa_mode[index].lfb_width;
818 smtc_screen_info.lfb_height =
819 vesa_mode[index].lfb_height;
820 smtc_screen_info.lfb_depth = vesa_mode[index].lfb_depth;
821 return 0;
822 }
823 }
824
825 return -1;
826}
827__setup("vga=", sm712vga_setup);
828
Wu Zhangjinb99e1942010-07-18 03:16:28 +0800829static int __devinit smtcfb_pci_probe(struct pci_dev *pdev,
Wu Zhangjind7edf472009-11-23 10:28:24 +0800830 const struct pci_device_id *ent)
831{
832 struct smtcfb_info *sfb;
833 u_long smem_size = 0x00800000; /* default 8MB */
834 char name[16];
835 int err;
836 unsigned long pFramebufferPhysical;
837
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200838 dev_info(&pdev->dev,
839 "Silicon Motion display driver " SMTC_LINUX_FB_VERSION);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800840
841 err = pci_enable_device(pdev); /* enable SMTC chip */
Wu Zhangjind7edf472009-11-23 10:28:24 +0800842 if (err)
843 return err;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800844
845 hw.chipID = ent->device;
846 sprintf(name, "sm%Xfb", hw.chipID);
847
848 sfb = smtc_alloc_fb_info(pdev, name);
849
850 if (!sfb)
Kulikov Vasiliy918e3592010-08-06 23:53:23 +0400851 goto failed_free;
Javier M. Mellid86f31252012-04-26 20:45:49 +0200852
Wu Zhangjind7edf472009-11-23 10:28:24 +0800853 pci_set_drvdata(pdev, sfb);
854
855 sm7xx_init_hw();
856
857 /*get mode parameter from smtc_screen_info */
858 if (smtc_screen_info.lfb_width != 0) {
859 sfb->fb.var.xres = smtc_screen_info.lfb_width;
860 sfb->fb.var.yres = smtc_screen_info.lfb_height;
861 sfb->fb.var.bits_per_pixel = smtc_screen_info.lfb_depth;
862 } else {
863 /* default resolution 1024x600 16bit mode */
864 sfb->fb.var.xres = SCREEN_X_RES;
865 sfb->fb.var.yres = SCREEN_Y_RES;
866 sfb->fb.var.bits_per_pixel = SCREEN_BPP;
867 }
868
869#ifdef __BIG_ENDIAN
870 if (sfb->fb.var.bits_per_pixel == 24)
871 sfb->fb.var.bits_per_pixel = (smtc_screen_info.lfb_depth = 32);
872#endif
873 /* Map address and memory detection */
874 pFramebufferPhysical = pci_resource_start(pdev, 0);
875 pci_read_config_byte(pdev, PCI_REVISION_ID, &hw.chipRevID);
876
877 switch (hw.chipID) {
878 case 0x710:
879 case 0x712:
880 sfb->fb.fix.mmio_start = pFramebufferPhysical + 0x00400000;
881 sfb->fb.fix.mmio_len = 0x00400000;
882 smem_size = SM712_VIDEOMEMORYSIZE;
883#ifdef __BIG_ENDIAN
884 hw.m_pLFB = (smtc_VRAMBaseAddress =
885 ioremap(pFramebufferPhysical, 0x00c00000));
886#else
887 hw.m_pLFB = (smtc_VRAMBaseAddress =
888 ioremap(pFramebufferPhysical, 0x00800000));
889#endif
890 hw.m_pMMIO = (smtc_RegBaseAddress =
891 smtc_VRAMBaseAddress + 0x00700000);
Wu Zhangjin3af80572010-01-06 16:33:10 +0800892 hw.m_pDPR = smtc_VRAMBaseAddress + 0x00408000;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800893 hw.m_pVPR = hw.m_pLFB + 0x0040c000;
894#ifdef __BIG_ENDIAN
895 if (sfb->fb.var.bits_per_pixel == 32) {
896 smtc_VRAMBaseAddress += 0x800000;
897 hw.m_pLFB += 0x800000;
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200898 dev_info(&pdev->dev,
899 "smtc_VRAMBaseAddress=%p sfb->m_pLFB=%p",
900 smtc_VRAMBaseAddress, sfb->m_pLFB);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800901 }
902#endif
903 if (!smtc_RegBaseAddress) {
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200904 dev_err(&pdev->dev,
905 "%s: unable to map memory mapped IO!",
Wu Zhangjind7edf472009-11-23 10:28:24 +0800906 sfb->fb.fix.id);
Kulikov Vasiliy918e3592010-08-06 23:53:23 +0400907 err = -ENOMEM;
908 goto failed_fb;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800909 }
910
911 /* set MCLK = 14.31818 * (0x16 / 0x2) */
912 smtc_seqw(0x6a, 0x16);
913 smtc_seqw(0x6b, 0x02);
914 smtc_seqw(0x62, 0x3e);
915 /* enable PCI burst */
916 smtc_seqw(0x17, 0x20);
917 /* enable word swap */
918#ifdef __BIG_ENDIAN
919 if (sfb->fb.var.bits_per_pixel == 32)
920 smtc_seqw(0x17, 0x30);
921#endif
Wu Zhangjind7edf472009-11-23 10:28:24 +0800922 break;
923 case 0x720:
924 sfb->fb.fix.mmio_start = pFramebufferPhysical;
925 sfb->fb.fix.mmio_len = 0x00200000;
926 smem_size = SM722_VIDEOMEMORYSIZE;
Wu Zhangjin3af80572010-01-06 16:33:10 +0800927 hw.m_pDPR = ioremap(pFramebufferPhysical, 0x00a00000);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800928 hw.m_pLFB = (smtc_VRAMBaseAddress =
Wu Zhangjin3af80572010-01-06 16:33:10 +0800929 hw.m_pDPR + 0x00200000);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800930 hw.m_pMMIO = (smtc_RegBaseAddress =
Wu Zhangjin3af80572010-01-06 16:33:10 +0800931 hw.m_pDPR + 0x000c0000);
932 hw.m_pVPR = hw.m_pDPR + 0x800;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800933
934 smtc_seqw(0x62, 0xff);
935 smtc_seqw(0x6a, 0x0d);
936 smtc_seqw(0x6b, 0x02);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800937 break;
938 default:
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200939 dev_err(&pdev->dev,
940 "No valid Silicon Motion display chip was detected!");
Wu Zhangjind7edf472009-11-23 10:28:24 +0800941
Kulikov Vasiliy918e3592010-08-06 23:53:23 +0400942 goto failed_fb;
Wu Zhangjind7edf472009-11-23 10:28:24 +0800943 }
944
945 /* can support 32 bpp */
946 if (15 == sfb->fb.var.bits_per_pixel)
947 sfb->fb.var.bits_per_pixel = 16;
948
949 sfb->fb.var.xres_virtual = sfb->fb.var.xres;
950 sfb->fb.var.yres_virtual = sfb->fb.var.yres;
951 err = smtc_map_smem(sfb, pdev, smem_size);
952 if (err)
953 goto failed;
954
955 smtcfb_setmode(sfb);
Lucas De Marchi25985ed2011-03-30 22:57:33 -0300956 /* Primary display starting from 0 position */
Wu Zhangjind7edf472009-11-23 10:28:24 +0800957 hw.BaseAddressInVRAM = 0;
958 sfb->fb.par = &hw;
959
960 err = register_framebuffer(&sfb->fb);
961 if (err < 0)
962 goto failed;
963
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200964 dev_info(&pdev->dev,
965 "Silicon Motion SM%X Rev%X primary display mode"
966 "%dx%d-%d Init Complete.\n", hw.chipID, hw.chipRevID,
967 sfb->fb.var.xres, sfb->fb.var.yres,
968 sfb->fb.var.bits_per_pixel);
Wu Zhangjind7edf472009-11-23 10:28:24 +0800969
970 return 0;
971
anish kumar1639c8a2011-05-19 20:58:42 +0530972failed:
Javier M. Mellid6f54b092012-04-26 20:45:52 +0200973 dev_err(&pdev->dev, "Silicon Motion, Inc. primary display init fail.");
Wu Zhangjind7edf472009-11-23 10:28:24 +0800974
975 smtc_unmap_smem(sfb);
976 smtc_unmap_mmio(sfb);
Kulikov Vasiliy918e3592010-08-06 23:53:23 +0400977failed_fb:
Wu Zhangjind7edf472009-11-23 10:28:24 +0800978 smtc_free_fb_info(sfb);
979
Kulikov Vasiliy918e3592010-08-06 23:53:23 +0400980failed_free:
981 pci_disable_device(pdev);
982
Wu Zhangjind7edf472009-11-23 10:28:24 +0800983 return err;
984}
985
986
Namhyung Kim6f475b72010-12-10 01:40:25 +0900987static DEFINE_PCI_DEVICE_TABLE(smtcfb_pci_table) = {
Peter Huewec0fe6022011-11-27 15:12:57 +0100988 { PCI_DEVICE(0x126f, 0x710), },
989 { PCI_DEVICE(0x126f, 0x712), },
990 { PCI_DEVICE(0x126f, 0x720), },
Wu Zhangjind7edf472009-11-23 10:28:24 +0800991 {0,}
992};
993
994
Wu Zhangjind7edf472009-11-23 10:28:24 +0800995static void __devexit smtcfb_pci_remove(struct pci_dev *pdev)
996{
997 struct smtcfb_info *sfb;
998
999 sfb = pci_get_drvdata(pdev);
1000 pci_set_drvdata(pdev, NULL);
1001 smtc_unmap_smem(sfb);
1002 smtc_unmap_mmio(sfb);
1003 unregister_framebuffer(&sfb->fb);
1004 smtc_free_fb_info(sfb);
1005}
1006
Javier M. Mellid392a0022011-03-30 16:24:10 +02001007#ifdef CONFIG_PM
Javier M. Mellid59815672011-04-30 17:44:25 +02001008static int smtcfb_pci_suspend(struct device *device)
Wu Zhangjind7edf472009-11-23 10:28:24 +08001009{
Javier M. Mellid59815672011-04-30 17:44:25 +02001010 struct pci_dev *pdev = to_pci_dev(device);
Wu Zhangjind7edf472009-11-23 10:28:24 +08001011 struct smtcfb_info *sfb;
Wu Zhangjind7edf472009-11-23 10:28:24 +08001012
1013 sfb = pci_get_drvdata(pdev);
1014
1015 /* set the hw in sleep mode use externel clock and self memory refresh
1016 * so that we can turn off internal PLLs later on
1017 */
1018 smtc_seqw(0x20, (smtc_seqr(0x20) | 0xc0));
1019 smtc_seqw(0x69, (smtc_seqr(0x69) & 0xf7));
1020
Javier M. Mellid59815672011-04-30 17:44:25 +02001021 console_lock();
1022 fb_set_suspend(&sfb->fb, 1);
1023 console_unlock();
Wu Zhangjind7edf472009-11-23 10:28:24 +08001024
Lucas De Marchi25985ed2011-03-30 22:57:33 -03001025 /* additionally turn off all function blocks including internal PLLs */
Wu Zhangjind7edf472009-11-23 10:28:24 +08001026 smtc_seqw(0x21, 0xff);
1027
1028 return 0;
1029}
1030
Javier M. Mellid59815672011-04-30 17:44:25 +02001031static int smtcfb_pci_resume(struct device *device)
Wu Zhangjind7edf472009-11-23 10:28:24 +08001032{
Javier M. Mellid59815672011-04-30 17:44:25 +02001033 struct pci_dev *pdev = to_pci_dev(device);
Wu Zhangjind7edf472009-11-23 10:28:24 +08001034 struct smtcfb_info *sfb;
Wu Zhangjind7edf472009-11-23 10:28:24 +08001035
1036 sfb = pci_get_drvdata(pdev);
1037
Wu Zhangjind7edf472009-11-23 10:28:24 +08001038 /* reinit hardware */
1039 sm7xx_init_hw();
1040 switch (hw.chipID) {
1041 case 0x710:
1042 case 0x712:
1043 /* set MCLK = 14.31818 * (0x16 / 0x2) */
1044 smtc_seqw(0x6a, 0x16);
1045 smtc_seqw(0x6b, 0x02);
1046 smtc_seqw(0x62, 0x3e);
1047 /* enable PCI burst */
1048 smtc_seqw(0x17, 0x20);
1049#ifdef __BIG_ENDIAN
1050 if (sfb->fb.var.bits_per_pixel == 32)
1051 smtc_seqw(0x17, 0x30);
1052#endif
1053 break;
1054 case 0x720:
1055 smtc_seqw(0x62, 0xff);
1056 smtc_seqw(0x6a, 0x0d);
1057 smtc_seqw(0x6b, 0x02);
1058 break;
1059 }
1060
1061 smtc_seqw(0x34, (smtc_seqr(0x34) | 0xc0));
1062 smtc_seqw(0x33, ((smtc_seqr(0x33) | 0x08) & 0xfb));
1063
1064 smtcfb_setmode(sfb);
1065
Torben Hohnac751ef2011-01-25 15:07:35 -08001066 console_lock();
Wu Zhangjind7edf472009-11-23 10:28:24 +08001067 fb_set_suspend(&sfb->fb, 0);
Torben Hohnac751ef2011-01-25 15:07:35 -08001068 console_unlock();
Wu Zhangjind7edf472009-11-23 10:28:24 +08001069
1070 return 0;
1071}
Wu Zhangjind7edf472009-11-23 10:28:24 +08001072
Javier M. Mellid59815672011-04-30 17:44:25 +02001073static const struct dev_pm_ops sm7xx_pm_ops = {
1074 .suspend = smtcfb_pci_suspend,
1075 .resume = smtcfb_pci_resume,
1076 .freeze = smtcfb_pci_suspend,
1077 .thaw = smtcfb_pci_resume,
1078 .poweroff = smtcfb_pci_suspend,
1079 .restore = smtcfb_pci_resume,
1080};
1081
1082#define SM7XX_PM_OPS (&sm7xx_pm_ops)
1083
1084#else /* !CONFIG_PM */
1085
1086#define SM7XX_PM_OPS NULL
1087
1088#endif /* !CONFIG_PM */
1089
Wu Zhangjind7edf472009-11-23 10:28:24 +08001090static struct pci_driver smtcfb_driver = {
1091 .name = "smtcfb",
1092 .id_table = smtcfb_pci_table,
1093 .probe = smtcfb_pci_probe,
1094 .remove = __devexit_p(smtcfb_pci_remove),
Javier M. Mellid59815672011-04-30 17:44:25 +02001095 .driver.pm = SM7XX_PM_OPS,
Wu Zhangjind7edf472009-11-23 10:28:24 +08001096};
1097
1098static int __init smtcfb_init(void)
1099{
1100 return pci_register_driver(&smtcfb_driver);
1101}
1102
1103static void __exit smtcfb_exit(void)
1104{
1105 pci_unregister_driver(&smtcfb_driver);
1106}
1107
1108module_init(smtcfb_init);
1109module_exit(smtcfb_exit);
1110
1111MODULE_AUTHOR("Siliconmotion ");
1112MODULE_DESCRIPTION("Framebuffer driver for SMI Graphic Cards");
1113MODULE_LICENSE("GPL");