]> Pileus Git - ~andy/linux/blob - drivers/video/broadsheetfb.c
broadsheetfb: add MMIO hooks
[~andy/linux] / drivers / video / broadsheetfb.c
1 /*
2  * broadsheetfb.c -- FB driver for E-Ink Broadsheet controller
3  *
4  * Copyright (C) 2008, Jaya Kumar
5  *
6  * This file is subject to the terms and conditions of the GNU General Public
7  * License. See the file COPYING in the main directory of this archive for
8  * more details.
9  *
10  * Layout is based on skeletonfb.c by James Simmons and Geert Uytterhoeven.
11  *
12  * This driver is written to be used with the Broadsheet display controller.
13  *
14  * It is intended to be architecture independent. A board specific driver
15  * must be used to perform all the physical IO interactions.
16  *
17  */
18
19 #include <linux/module.h>
20 #include <linux/kernel.h>
21 #include <linux/errno.h>
22 #include <linux/string.h>
23 #include <linux/mm.h>
24 #include <linux/slab.h>
25 #include <linux/vmalloc.h>
26 #include <linux/delay.h>
27 #include <linux/interrupt.h>
28 #include <linux/fb.h>
29 #include <linux/init.h>
30 #include <linux/platform_device.h>
31 #include <linux/list.h>
32 #include <linux/uaccess.h>
33
34 #include <video/broadsheetfb.h>
35
36 /* track panel specific parameters */
37 struct panel_info {
38         int w;
39         int h;
40         u16 sdcfg;
41         u16 gdcfg;
42         u16 lutfmt;
43         u16 fsynclen;
44         u16 fendfbegin;
45         u16 lsynclen;
46         u16 lendlbegin;
47         u16 pixclk;
48 };
49
50 /* table of panel specific parameters to be indexed into by the board drivers */
51 static struct panel_info panel_table[] = {
52         {       /* standard 6" on TFT backplane */
53                 .w = 800,
54                 .h = 600,
55                 .sdcfg = (100 | (1 << 8) | (1 << 9)),
56                 .gdcfg = 2,
57                 .lutfmt = (4 | (1 << 7)),
58                 .fsynclen = 4,
59                 .fendfbegin = (10 << 8) | 4,
60                 .lsynclen = 10,
61                 .lendlbegin = (100 << 8) | 4,
62                 .pixclk = 6,
63         },
64         {       /* custom 3.7" flexible on PET or steel */
65                 .w = 320,
66                 .h = 240,
67                 .sdcfg = (67 | (0 << 8) | (0 << 9) | (0 << 10) | (0 << 12)),
68                 .gdcfg = 3,
69                 .lutfmt = (4 | (1 << 7)),
70                 .fsynclen = 0,
71                 .fendfbegin = (80 << 8) | 4,
72                 .lsynclen = 10,
73                 .lendlbegin = (80 << 8) | 20,
74                 .pixclk = 14,
75         },
76         {       /* standard 9.7" on TFT backplane */
77                 .w = 1200,
78                 .h = 825,
79                 .sdcfg = (100 | (1 << 8) | (1 << 9) | (0 << 10) | (0 << 12)),
80                 .gdcfg = 2,
81                 .lutfmt = (4 | (1 << 7)),
82                 .fsynclen = 0,
83                 .fendfbegin = (4 << 8) | 4,
84                 .lsynclen = 4,
85                 .lendlbegin = (60 << 8) | 10,
86                 .pixclk = 3,
87         },
88 };
89
90 #define DPY_W 800
91 #define DPY_H 600
92
93 static struct fb_fix_screeninfo broadsheetfb_fix __devinitdata = {
94         .id =           "broadsheetfb",
95         .type =         FB_TYPE_PACKED_PIXELS,
96         .visual =       FB_VISUAL_STATIC_PSEUDOCOLOR,
97         .xpanstep =     0,
98         .ypanstep =     0,
99         .ywrapstep =    0,
100         .line_length =  DPY_W,
101         .accel =        FB_ACCEL_NONE,
102 };
103
104 static struct fb_var_screeninfo broadsheetfb_var __devinitdata = {
105         .xres           = DPY_W,
106         .yres           = DPY_H,
107         .xres_virtual   = DPY_W,
108         .yres_virtual   = DPY_H,
109         .bits_per_pixel = 8,
110         .grayscale      = 1,
111         .red =          { 0, 4, 0 },
112         .green =        { 0, 4, 0 },
113         .blue =         { 0, 4, 0 },
114         .transp =       { 0, 0, 0 },
115 };
116
117 /* main broadsheetfb functions */
118 static void broadsheet_gpio_issue_data(struct broadsheetfb_par *par, u16 data)
119 {
120         par->board->set_ctl(par, BS_WR, 0);
121         par->board->set_hdb(par, data);
122         par->board->set_ctl(par, BS_WR, 1);
123 }
124
125 static void broadsheet_gpio_issue_cmd(struct broadsheetfb_par *par, u16 data)
126 {
127         par->board->set_ctl(par, BS_DC, 0);
128         broadsheet_gpio_issue_data(par, data);
129 }
130
131 static void broadsheet_gpio_send_command(struct broadsheetfb_par *par, u16 data)
132 {
133         par->board->wait_for_rdy(par);
134
135         par->board->set_ctl(par, BS_CS, 0);
136         broadsheet_gpio_issue_cmd(par, data);
137         par->board->set_ctl(par, BS_DC, 1);
138         par->board->set_ctl(par, BS_CS, 1);
139 }
140
141 static void broadsheet_gpio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd,
142                                         int argc, u16 *argv)
143 {
144         int i;
145
146         par->board->wait_for_rdy(par);
147
148         par->board->set_ctl(par, BS_CS, 0);
149         broadsheet_gpio_issue_cmd(par, cmd);
150         par->board->set_ctl(par, BS_DC, 1);
151
152         for (i = 0; i < argc; i++)
153                 broadsheet_gpio_issue_data(par, argv[i]);
154         par->board->set_ctl(par, BS_CS, 1);
155 }
156
157 static void broadsheet_mmio_send_cmdargs(struct broadsheetfb_par *par, u16 cmd,
158                                     int argc, u16 *argv)
159 {
160         int i;
161
162         par->board->mmio_write(par, BS_MMIO_CMD, cmd);
163
164         for (i = 0; i < argc; i++)
165                 par->board->mmio_write(par, BS_MMIO_DATA, argv[i]);
166 }
167
168 static void broadsheet_send_command(struct broadsheetfb_par *par, u16 data)
169 {
170         if (par->board->mmio_write)
171                 par->board->mmio_write(par, BS_MMIO_CMD, data);
172         else
173                 broadsheet_gpio_send_command(par, data);
174 }
175
176 static void broadsheet_send_cmdargs(struct broadsheetfb_par *par, u16 cmd,
177                                     int argc, u16 *argv)
178 {
179         if (par->board->mmio_write)
180                 broadsheet_mmio_send_cmdargs(par, cmd, argc, argv);
181         else
182                 broadsheet_gpio_send_cmdargs(par, cmd, argc, argv);
183 }
184
185 static void broadsheet_gpio_burst_write(struct broadsheetfb_par *par, int size,
186                                         u16 *data)
187 {
188         int i;
189         u16 tmp;
190
191         par->board->set_ctl(par, BS_CS, 0);
192         par->board->set_ctl(par, BS_DC, 1);
193
194         for (i = 0; i < size; i++) {
195                 par->board->set_ctl(par, BS_WR, 0);
196                 tmp = (data[i] & 0x0F) << 4;
197                 tmp |= (data[i] & 0x0F00) << 4;
198                 par->board->set_hdb(par, tmp);
199                 par->board->set_ctl(par, BS_WR, 1);
200         }
201
202         par->board->set_ctl(par, BS_CS, 1);
203 }
204
205 static void broadsheet_mmio_burst_write(struct broadsheetfb_par *par, int size,
206                                    u16 *data)
207 {
208         int i;
209         u16 tmp;
210
211         for (i = 0; i < size; i++) {
212                 tmp = (data[i] & 0x0F) << 4;
213                 tmp |= (data[i] & 0x0F00) << 4;
214                 par->board->mmio_write(par, BS_MMIO_DATA, tmp);
215         }
216
217 }
218
219 static void broadsheet_burst_write(struct broadsheetfb_par *par, int size,
220                                    u16 *data)
221 {
222         if (par->board->mmio_write)
223                 broadsheet_mmio_burst_write(par, size, data);
224         else
225                 broadsheet_gpio_burst_write(par, size, data);
226 }
227
228 static u16 broadsheet_gpio_get_data(struct broadsheetfb_par *par)
229 {
230         u16 res;
231         /* wait for ready to go hi. (lo is busy) */
232         par->board->wait_for_rdy(par);
233
234         /* cs lo, dc lo for cmd, we lo for each data, db as usual */
235         par->board->set_ctl(par, BS_DC, 1);
236         par->board->set_ctl(par, BS_CS, 0);
237         par->board->set_ctl(par, BS_WR, 0);
238
239         res = par->board->get_hdb(par);
240
241         /* strobe wr */
242         par->board->set_ctl(par, BS_WR, 1);
243         par->board->set_ctl(par, BS_CS, 1);
244
245         return res;
246 }
247
248
249 static u16 broadsheet_get_data(struct broadsheetfb_par *par)
250 {
251         if (par->board->mmio_read)
252                 return par->board->mmio_read(par);
253         else
254                 return broadsheet_gpio_get_data(par);
255 }
256
257 static void broadsheet_gpio_write_reg(struct broadsheetfb_par *par, u16 reg,
258                                         u16 data)
259 {
260         /* wait for ready to go hi. (lo is busy) */
261         par->board->wait_for_rdy(par);
262
263         /* cs lo, dc lo for cmd, we lo for each data, db as usual */
264         par->board->set_ctl(par, BS_CS, 0);
265
266         broadsheet_gpio_issue_cmd(par, BS_CMD_WR_REG);
267
268         par->board->set_ctl(par, BS_DC, 1);
269
270         broadsheet_gpio_issue_data(par, reg);
271         broadsheet_gpio_issue_data(par, data);
272
273         par->board->set_ctl(par, BS_CS, 1);
274 }
275
276 static void broadsheet_mmio_write_reg(struct broadsheetfb_par *par, u16 reg,
277                                  u16 data)
278 {
279         par->board->mmio_write(par, BS_MMIO_CMD, BS_CMD_WR_REG);
280         par->board->mmio_write(par, BS_MMIO_DATA, reg);
281         par->board->mmio_write(par, BS_MMIO_DATA, data);
282
283 }
284
285 static void broadsheet_write_reg(struct broadsheetfb_par *par, u16 reg,
286                                         u16 data)
287 {
288         if (par->board->mmio_write)
289                 broadsheet_mmio_write_reg(par, reg, data);
290         else
291                 broadsheet_gpio_write_reg(par, reg, data);
292 }
293
294 static void broadsheet_write_reg32(struct broadsheetfb_par *par, u16 reg,
295                                         u32 data)
296 {
297         broadsheet_write_reg(par, reg, cpu_to_le32(data) & 0xFFFF);
298         broadsheet_write_reg(par, reg + 2, (cpu_to_le32(data) >> 16) & 0xFFFF);
299 }
300
301
302 static u16 broadsheet_read_reg(struct broadsheetfb_par *par, u16 reg)
303 {
304         broadsheet_send_cmdargs(par, BS_CMD_RD_REG, 1, &reg);
305         par->board->wait_for_rdy(par);
306         return broadsheet_get_data(par);
307 }
308
309 static void __devinit broadsheet_init_display(struct broadsheetfb_par *par)
310 {
311         u16 args[5];
312         int xres = par->info->var.xres;
313         int yres = par->info->var.yres;
314
315         args[0] = panel_table[par->panel_index].w;
316         args[1] = panel_table[par->panel_index].h;
317         args[2] = panel_table[par->panel_index].sdcfg;
318         args[3] = panel_table[par->panel_index].gdcfg;
319         args[4] = panel_table[par->panel_index].lutfmt;
320         broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args);
321
322         /* did the controller really set it? */
323         broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_CFG, 5, args);
324
325         args[0] = panel_table[par->panel_index].fsynclen;
326         args[1] = panel_table[par->panel_index].fendfbegin;
327         args[2] = panel_table[par->panel_index].lsynclen;
328         args[3] = panel_table[par->panel_index].lendlbegin;
329         args[4] = panel_table[par->panel_index].pixclk;
330         broadsheet_send_cmdargs(par, BS_CMD_INIT_DSPE_TMG, 5, args);
331
332         broadsheet_write_reg32(par, 0x310, xres*yres*2);
333
334         /* setup waveform */
335         args[0] = 0x886;
336         args[1] = 0;
337         broadsheet_send_cmdargs(par, BS_CMD_RD_WFM_INFO, 2, args);
338
339         broadsheet_send_command(par, BS_CMD_UPD_GDRV_CLR);
340
341         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG);
342
343         broadsheet_write_reg(par, 0x330, 0x84);
344
345         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG);
346
347         args[0] = (0x3 << 4);
348         broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args);
349
350         args[0] = 0x154;
351         broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args);
352
353         broadsheet_burst_write(par, (panel_table[par->panel_index].w *
354                                         panel_table[par->panel_index].h)/2,
355                                         (u16 *) par->info->screen_base);
356
357         broadsheet_send_command(par, BS_CMD_LD_IMG_END);
358
359         args[0] = 0x4300;
360         broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args);
361
362         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG);
363
364         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND);
365
366         par->board->wait_for_rdy(par);
367 }
368
369 static void __devinit broadsheet_init(struct broadsheetfb_par *par)
370 {
371         broadsheet_send_command(par, BS_CMD_INIT_SYS_RUN);
372         /* the controller needs a second */
373         msleep(1000);
374         broadsheet_init_display(par);
375 }
376
377 static void broadsheetfb_dpy_update_pages(struct broadsheetfb_par *par,
378                                                 u16 y1, u16 y2)
379 {
380         u16 args[5];
381         unsigned char *buf = (unsigned char *)par->info->screen_base;
382
383         /* y1 must be a multiple of 4 so drop the lower bits */
384         y1 &= 0xFFFC;
385         /* y2 must be a multiple of 4 , but - 1 so up the lower bits */
386         y2 |= 0x0003;
387
388         args[0] = 0x3 << 4;
389         args[1] = 0;
390         args[2] = y1;
391         args[3] = cpu_to_le16(par->info->var.xres);
392         args[4] = y2;
393         broadsheet_send_cmdargs(par, BS_CMD_LD_IMG_AREA, 5, args);
394
395         args[0] = 0x154;
396         broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args);
397
398         buf += y1 * par->info->var.xres;
399         broadsheet_burst_write(par, ((1 + y2 - y1) * par->info->var.xres)/2,
400                                 (u16 *) buf);
401
402         broadsheet_send_command(par, BS_CMD_LD_IMG_END);
403
404         args[0] = 0x4300;
405         broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args);
406
407         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG);
408
409         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND);
410
411         par->board->wait_for_rdy(par);
412
413 }
414
415 static void broadsheetfb_dpy_update(struct broadsheetfb_par *par)
416 {
417         u16 args[5];
418
419         args[0] = 0x3 << 4;
420         broadsheet_send_cmdargs(par, BS_CMD_LD_IMG, 1, args);
421
422         args[0] = 0x154;
423         broadsheet_send_cmdargs(par, BS_CMD_WR_REG, 1, args);
424         broadsheet_burst_write(par, (panel_table[par->panel_index].w *
425                                         panel_table[par->panel_index].h)/2,
426                                         (u16 *) par->info->screen_base);
427
428         broadsheet_send_command(par, BS_CMD_LD_IMG_END);
429
430         args[0] = 0x4300;
431         broadsheet_send_cmdargs(par, BS_CMD_UPD_FULL, 1, args);
432
433         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_TRG);
434
435         broadsheet_send_command(par, BS_CMD_WAIT_DSPE_FREND);
436
437         par->board->wait_for_rdy(par);
438
439 }
440
441 /* this is called back from the deferred io workqueue */
442 static void broadsheetfb_dpy_deferred_io(struct fb_info *info,
443                                 struct list_head *pagelist)
444 {
445         u16 y1 = 0, h = 0;
446         int prev_index = -1;
447         struct page *cur;
448         struct fb_deferred_io *fbdefio = info->fbdefio;
449         int h_inc;
450         u16 yres = info->var.yres;
451         u16 xres = info->var.xres;
452
453         /* height increment is fixed per page */
454         h_inc = DIV_ROUND_UP(PAGE_SIZE , xres);
455
456         /* walk the written page list and swizzle the data */
457         list_for_each_entry(cur, &fbdefio->pagelist, lru) {
458                 if (prev_index < 0) {
459                         /* just starting so assign first page */
460                         y1 = (cur->index << PAGE_SHIFT) / xres;
461                         h = h_inc;
462                 } else if ((prev_index + 1) == cur->index) {
463                         /* this page is consecutive so increase our height */
464                         h += h_inc;
465                 } else {
466                         /* page not consecutive, issue previous update first */
467                         broadsheetfb_dpy_update_pages(info->par, y1, y1 + h);
468                         /* start over with our non consecutive page */
469                         y1 = (cur->index << PAGE_SHIFT) / xres;
470                         h = h_inc;
471                 }
472                 prev_index = cur->index;
473         }
474
475         /* if we still have any pages to update we do so now */
476         if (h >= yres) {
477                 /* its a full screen update, just do it */
478                 broadsheetfb_dpy_update(info->par);
479         } else {
480                 broadsheetfb_dpy_update_pages(info->par, y1,
481                                                 min((u16) (y1 + h), yres));
482         }
483 }
484
485 static void broadsheetfb_fillrect(struct fb_info *info,
486                                    const struct fb_fillrect *rect)
487 {
488         struct broadsheetfb_par *par = info->par;
489
490         sys_fillrect(info, rect);
491
492         broadsheetfb_dpy_update(par);
493 }
494
495 static void broadsheetfb_copyarea(struct fb_info *info,
496                                    const struct fb_copyarea *area)
497 {
498         struct broadsheetfb_par *par = info->par;
499
500         sys_copyarea(info, area);
501
502         broadsheetfb_dpy_update(par);
503 }
504
505 static void broadsheetfb_imageblit(struct fb_info *info,
506                                 const struct fb_image *image)
507 {
508         struct broadsheetfb_par *par = info->par;
509
510         sys_imageblit(info, image);
511
512         broadsheetfb_dpy_update(par);
513 }
514
515 /*
516  * this is the slow path from userspace. they can seek and write to
517  * the fb. it's inefficient to do anything less than a full screen draw
518  */
519 static ssize_t broadsheetfb_write(struct fb_info *info, const char __user *buf,
520                                 size_t count, loff_t *ppos)
521 {
522         struct broadsheetfb_par *par = info->par;
523         unsigned long p = *ppos;
524         void *dst;
525         int err = 0;
526         unsigned long total_size;
527
528         if (info->state != FBINFO_STATE_RUNNING)
529                 return -EPERM;
530
531         total_size = info->fix.smem_len;
532
533         if (p > total_size)
534                 return -EFBIG;
535
536         if (count > total_size) {
537                 err = -EFBIG;
538                 count = total_size;
539         }
540
541         if (count + p > total_size) {
542                 if (!err)
543                         err = -ENOSPC;
544
545                 count = total_size - p;
546         }
547
548         dst = (void *)(info->screen_base + p);
549
550         if (copy_from_user(dst, buf, count))
551                 err = -EFAULT;
552
553         if  (!err)
554                 *ppos += count;
555
556         broadsheetfb_dpy_update(par);
557
558         return (err) ? err : count;
559 }
560
561 static struct fb_ops broadsheetfb_ops = {
562         .owner          = THIS_MODULE,
563         .fb_read        = fb_sys_read,
564         .fb_write       = broadsheetfb_write,
565         .fb_fillrect    = broadsheetfb_fillrect,
566         .fb_copyarea    = broadsheetfb_copyarea,
567         .fb_imageblit   = broadsheetfb_imageblit,
568 };
569
570 static struct fb_deferred_io broadsheetfb_defio = {
571         .delay          = HZ/4,
572         .deferred_io    = broadsheetfb_dpy_deferred_io,
573 };
574
575 static int __devinit broadsheetfb_probe(struct platform_device *dev)
576 {
577         struct fb_info *info;
578         struct broadsheet_board *board;
579         int retval = -ENOMEM;
580         int videomemorysize;
581         unsigned char *videomemory;
582         struct broadsheetfb_par *par;
583         int i;
584         int dpyw, dpyh;
585         int panel_index;
586
587         /* pick up board specific routines */
588         board = dev->dev.platform_data;
589         if (!board)
590                 return -EINVAL;
591
592         /* try to count device specific driver, if can't, platform recalls */
593         if (!try_module_get(board->owner))
594                 return -ENODEV;
595
596         info = framebuffer_alloc(sizeof(struct broadsheetfb_par), &dev->dev);
597         if (!info)
598                 goto err;
599
600         switch (board->get_panel_type()) {
601         case 37:
602                 panel_index = 1;
603                 break;
604         case 97:
605                 panel_index = 2;
606                 break;
607         case 6:
608         default:
609                 panel_index = 0;
610                 break;
611         }
612
613         dpyw = panel_table[panel_index].w;
614         dpyh = panel_table[panel_index].h;
615
616         videomemorysize = roundup((dpyw*dpyh), PAGE_SIZE);
617
618         videomemory = vmalloc(videomemorysize);
619         if (!videomemory)
620                 goto err_fb_rel;
621
622         memset(videomemory, 0, videomemorysize);
623
624         info->screen_base = (char *)videomemory;
625         info->fbops = &broadsheetfb_ops;
626
627         broadsheetfb_var.xres = dpyw;
628         broadsheetfb_var.yres = dpyh;
629         broadsheetfb_var.xres_virtual = dpyw;
630         broadsheetfb_var.yres_virtual = dpyh;
631         info->var = broadsheetfb_var;
632
633         broadsheetfb_fix.line_length = dpyw;
634         info->fix = broadsheetfb_fix;
635         info->fix.smem_len = videomemorysize;
636         par = info->par;
637         par->panel_index = panel_index;
638         par->info = info;
639         par->board = board;
640         par->write_reg = broadsheet_write_reg;
641         par->read_reg = broadsheet_read_reg;
642         init_waitqueue_head(&par->waitq);
643
644         info->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
645
646         info->fbdefio = &broadsheetfb_defio;
647         fb_deferred_io_init(info);
648
649         retval = fb_alloc_cmap(&info->cmap, 16, 0);
650         if (retval < 0) {
651                 dev_err(&dev->dev, "Failed to allocate colormap\n");
652                 goto err_vfree;
653         }
654
655         /* set cmap */
656         for (i = 0; i < 16; i++)
657                 info->cmap.red[i] = (((2*i)+1)*(0xFFFF))/32;
658         memcpy(info->cmap.green, info->cmap.red, sizeof(u16)*16);
659         memcpy(info->cmap.blue, info->cmap.red, sizeof(u16)*16);
660
661         retval = par->board->setup_irq(info);
662         if (retval < 0)
663                 goto err_cmap;
664
665         /* this inits the dpy */
666         retval = board->init(par);
667         if (retval < 0)
668                 goto err_free_irq;
669
670         broadsheet_init(par);
671
672         retval = register_framebuffer(info);
673         if (retval < 0)
674                 goto err_free_irq;
675         platform_set_drvdata(dev, info);
676
677         printk(KERN_INFO
678                "fb%d: Broadsheet frame buffer, using %dK of video memory\n",
679                info->node, videomemorysize >> 10);
680
681
682         return 0;
683
684 err_free_irq:
685         board->cleanup(par);
686 err_cmap:
687         fb_dealloc_cmap(&info->cmap);
688 err_vfree:
689         vfree(videomemory);
690 err_fb_rel:
691         framebuffer_release(info);
692 err:
693         module_put(board->owner);
694         return retval;
695
696 }
697
698 static int __devexit broadsheetfb_remove(struct platform_device *dev)
699 {
700         struct fb_info *info = platform_get_drvdata(dev);
701
702         if (info) {
703                 struct broadsheetfb_par *par = info->par;
704                 unregister_framebuffer(info);
705                 fb_deferred_io_cleanup(info);
706                 par->board->cleanup(par);
707                 fb_dealloc_cmap(&info->cmap);
708                 vfree((void *)info->screen_base);
709                 module_put(par->board->owner);
710                 framebuffer_release(info);
711         }
712         return 0;
713 }
714
715 static struct platform_driver broadsheetfb_driver = {
716         .probe  = broadsheetfb_probe,
717         .remove = broadsheetfb_remove,
718         .driver = {
719                 .owner  = THIS_MODULE,
720                 .name   = "broadsheetfb",
721         },
722 };
723
724 static int __init broadsheetfb_init(void)
725 {
726         return platform_driver_register(&broadsheetfb_driver);
727 }
728
729 static void __exit broadsheetfb_exit(void)
730 {
731         platform_driver_unregister(&broadsheetfb_driver);
732 }
733
734 module_init(broadsheetfb_init);
735 module_exit(broadsheetfb_exit);
736
737 MODULE_DESCRIPTION("fbdev driver for Broadsheet controller");
738 MODULE_AUTHOR("Jaya Kumar");
739 MODULE_LICENSE("GPL");