aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/video/msm/msm_fb.c
diff options
context:
space:
mode:
authorPavel Machek <pavel@ucw.cz>2009-09-22 19:47:03 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2009-09-23 10:39:50 -0400
commitd480ace08d5b59133575e672a0bd1c97b0f8400f (patch)
tree3e2e0edd582d7f511544ad87a6095c6227d280aa /drivers/video/msm/msm_fb.c
parent689620100172e24fdf0981e9978a9559e8769258 (diff)
fbdev: framebuffer support for HTC Dream
Add a framebuffer driver for Qualcomm MSM/QSD SoCs, tested on HTC Dream smartphone (aka T-Mobile G1, aka ADP1). Brian said: I did the original quick and dirty version for bringup. Rebecca took over and (re)wrote the bulk of the driver, getting things stable for production ship of Dream and Sapphire, and Dima is currently adding support for later Qualcomm chipsets (QSD8x50, etc). Signed-off-by: Pavel Machek <pavel@ucw.cz> Cc: Brian Swetland <swetland@google.com> Cc: Krzysztof Helt <krzysztof.h1@poczta.fm> Cc: Rebecca Schultz Zavin <rebecca@android.com> Cc: Dima Zavin <dima@android.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/video/msm/msm_fb.c')
-rw-r--r--drivers/video/msm/msm_fb.c636
1 files changed, 636 insertions, 0 deletions
diff --git a/drivers/video/msm/msm_fb.c b/drivers/video/msm/msm_fb.c
new file mode 100644
index 000000000000..49101dda45ee
--- /dev/null
+++ b/drivers/video/msm/msm_fb.c
@@ -0,0 +1,636 @@
1/* drivers/video/msm/msm_fb.c
2 *
3 * Core MSM framebuffer driver.
4 *
5 * Copyright (C) 2007 Google Incorporated
6 *
7 * This software is licensed under the terms of the GNU General Public
8 * License version 2, as published by the Free Software Foundation, and
9 * may be copied, distributed, and modified under those terms.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 */
16
17#include <linux/platform_device.h>
18#include <linux/module.h>
19#include <linux/fb.h>
20#include <linux/delay.h>
21
22#include <linux/freezer.h>
23#include <linux/wait.h>
24#include <linux/msm_mdp.h>
25#include <linux/io.h>
26#include <linux/uaccess.h>
27#include <mach/msm_fb.h>
28#include <mach/board.h>
29#include <linux/workqueue.h>
30#include <linux/clk.h>
31#include <linux/debugfs.h>
32#include <linux/dma-mapping.h>
33
34#define PRINT_FPS 0
35#define PRINT_BLIT_TIME 0
36
37#define SLEEPING 0x4
38#define UPDATING 0x3
39#define FULL_UPDATE_DONE 0x2
40#define WAKING 0x1
41#define AWAKE 0x0
42
43#define NONE 0
44#define SUSPEND_RESUME 0x1
45#define FPS 0x2
46#define BLIT_TIME 0x4
47#define SHOW_UPDATES 0x8
48
49#define DLOG(mask, fmt, args...) \
50do { \
51 if (msmfb_debug_mask & mask) \
52 printk(KERN_INFO "msmfb: "fmt, ##args); \
53} while (0)
54
55static int msmfb_debug_mask;
56module_param_named(msmfb_debug_mask, msmfb_debug_mask, int,
57 S_IRUGO | S_IWUSR | S_IWGRP);
58
59struct mdp_device *mdp;
60
61struct msmfb_info {
62 struct fb_info *fb;
63 struct msm_panel_data *panel;
64 int xres;
65 int yres;
66 unsigned output_format;
67 unsigned yoffset;
68 unsigned frame_requested;
69 unsigned frame_done;
70 int sleeping;
71 unsigned update_frame;
72 struct {
73 int left;
74 int top;
75 int eright; /* exclusive */
76 int ebottom; /* exclusive */
77 } update_info;
78 char *black;
79
80 spinlock_t update_lock;
81 struct mutex panel_init_lock;
82 wait_queue_head_t frame_wq;
83 struct workqueue_struct *resume_workqueue;
84 struct work_struct resume_work;
85 struct msmfb_callback dma_callback;
86 struct msmfb_callback vsync_callback;
87 struct hrtimer fake_vsync;
88 ktime_t vsync_request_time;
89};
90
91static int msmfb_open(struct fb_info *info, int user)
92{
93 return 0;
94}
95
96static int msmfb_release(struct fb_info *info, int user)
97{
98 return 0;
99}
100
101/* Called from dma interrupt handler, must not sleep */
102static void msmfb_handle_dma_interrupt(struct msmfb_callback *callback)
103{
104 unsigned long irq_flags;
105 struct msmfb_info *msmfb = container_of(callback, struct msmfb_info,
106 dma_callback);
107
108 spin_lock_irqsave(&msmfb->update_lock, irq_flags);
109 msmfb->frame_done = msmfb->frame_requested;
110 if (msmfb->sleeping == UPDATING &&
111 msmfb->frame_done == msmfb->update_frame) {
112 DLOG(SUSPEND_RESUME, "full update completed\n");
113 queue_work(msmfb->resume_workqueue, &msmfb->resume_work);
114 }
115 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
116 wake_up(&msmfb->frame_wq);
117}
118
119static int msmfb_start_dma(struct msmfb_info *msmfb)
120{
121 uint32_t x, y, w, h;
122 unsigned addr;
123 unsigned long irq_flags;
124 uint32_t yoffset;
125 s64 time_since_request;
126 struct msm_panel_data *panel = msmfb->panel;
127
128 spin_lock_irqsave(&msmfb->update_lock, irq_flags);
129 time_since_request = ktime_to_ns(ktime_sub(ktime_get(),
130 msmfb->vsync_request_time));
131 if (time_since_request > 20 * NSEC_PER_MSEC) {
132 uint32_t us;
133 us = do_div(time_since_request, NSEC_PER_MSEC) / NSEC_PER_USEC;
134 printk(KERN_WARNING "msmfb_start_dma %lld.%03u ms after vsync "
135 "request\n", time_since_request, us);
136 }
137 if (msmfb->frame_done == msmfb->frame_requested) {
138 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
139 return -1;
140 }
141 if (msmfb->sleeping == SLEEPING) {
142 DLOG(SUSPEND_RESUME, "tried to start dma while asleep\n");
143 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
144 return -1;
145 }
146 x = msmfb->update_info.left;
147 y = msmfb->update_info.top;
148 w = msmfb->update_info.eright - x;
149 h = msmfb->update_info.ebottom - y;
150 yoffset = msmfb->yoffset;
151 msmfb->update_info.left = msmfb->xres + 1;
152 msmfb->update_info.top = msmfb->yres + 1;
153 msmfb->update_info.eright = 0;
154 msmfb->update_info.ebottom = 0;
155 if (unlikely(w > msmfb->xres || h > msmfb->yres ||
156 w == 0 || h == 0)) {
157 printk(KERN_INFO "invalid update: %d %d %d "
158 "%d\n", x, y, w, h);
159 msmfb->frame_done = msmfb->frame_requested;
160 goto error;
161 }
162 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
163
164 addr = ((msmfb->xres * (yoffset + y) + x) * 2);
165 mdp->dma(mdp, addr + msmfb->fb->fix.smem_start,
166 msmfb->xres * 2, w, h, x, y, &msmfb->dma_callback,
167 panel->interface_type);
168 return 0;
169error:
170 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
171 /* some clients need to clear their vsync interrupt */
172 if (panel->clear_vsync)
173 panel->clear_vsync(panel);
174 wake_up(&msmfb->frame_wq);
175 return 0;
176}
177
178/* Called from esync interrupt handler, must not sleep */
179static void msmfb_handle_vsync_interrupt(struct msmfb_callback *callback)
180{
181 struct msmfb_info *msmfb = container_of(callback, struct msmfb_info,
182 vsync_callback);
183 msmfb_start_dma(msmfb);
184}
185
186static enum hrtimer_restart msmfb_fake_vsync(struct hrtimer *timer)
187{
188 struct msmfb_info *msmfb = container_of(timer, struct msmfb_info,
189 fake_vsync);
190 msmfb_start_dma(msmfb);
191 return HRTIMER_NORESTART;
192}
193
194static void msmfb_pan_update(struct fb_info *info, uint32_t left, uint32_t top,
195 uint32_t eright, uint32_t ebottom,
196 uint32_t yoffset, int pan_display)
197{
198 struct msmfb_info *msmfb = info->par;
199 struct msm_panel_data *panel = msmfb->panel;
200 unsigned long irq_flags;
201 int sleeping;
202 int retry = 1;
203
204 DLOG(SHOW_UPDATES, "update %d %d %d %d %d %d\n",
205 left, top, eright, ebottom, yoffset, pan_display);
206restart:
207 spin_lock_irqsave(&msmfb->update_lock, irq_flags);
208
209 /* if we are sleeping, on a pan_display wait 10ms (to throttle back
210 * drawing otherwise return */
211 if (msmfb->sleeping == SLEEPING) {
212 DLOG(SUSPEND_RESUME, "drawing while asleep\n");
213 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
214 if (pan_display)
215 wait_event_interruptible_timeout(msmfb->frame_wq,
216 msmfb->sleeping != SLEEPING, HZ/10);
217 return;
218 }
219
220 sleeping = msmfb->sleeping;
221 /* on a full update, if the last frame has not completed, wait for it */
222 if (pan_display && (msmfb->frame_requested != msmfb->frame_done ||
223 sleeping == UPDATING)) {
224 int ret;
225 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
226 ret = wait_event_interruptible_timeout(msmfb->frame_wq,
227 msmfb->frame_done == msmfb->frame_requested &&
228 msmfb->sleeping != UPDATING, 5 * HZ);
229 if (ret <= 0 && (msmfb->frame_requested != msmfb->frame_done ||
230 msmfb->sleeping == UPDATING)) {
231 if (retry && panel->request_vsync &&
232 (sleeping == AWAKE)) {
233 panel->request_vsync(panel,
234 &msmfb->vsync_callback);
235 retry = 0;
236 printk(KERN_WARNING "msmfb_pan_display timeout "
237 "rerequest vsync\n");
238 } else {
239 printk(KERN_WARNING "msmfb_pan_display timeout "
240 "waiting for frame start, %d %d\n",
241 msmfb->frame_requested,
242 msmfb->frame_done);
243 return;
244 }
245 }
246 goto restart;
247 }
248
249
250 msmfb->frame_requested++;
251 /* if necessary, update the y offset, if this is the
252 * first full update on resume, set the sleeping state */
253 if (pan_display) {
254 msmfb->yoffset = yoffset;
255 if (left == 0 && top == 0 && eright == info->var.xres &&
256 ebottom == info->var.yres) {
257 if (sleeping == WAKING) {
258 msmfb->update_frame = msmfb->frame_requested;
259 DLOG(SUSPEND_RESUME, "full update starting\n");
260 msmfb->sleeping = UPDATING;
261 }
262 }
263 }
264
265 /* set the update request */
266 if (left < msmfb->update_info.left)
267 msmfb->update_info.left = left;
268 if (top < msmfb->update_info.top)
269 msmfb->update_info.top = top;
270 if (eright > msmfb->update_info.eright)
271 msmfb->update_info.eright = eright;
272 if (ebottom > msmfb->update_info.ebottom)
273 msmfb->update_info.ebottom = ebottom;
274 DLOG(SHOW_UPDATES, "update queued %d %d %d %d %d\n",
275 msmfb->update_info.left, msmfb->update_info.top,
276 msmfb->update_info.eright, msmfb->update_info.ebottom,
277 msmfb->yoffset);
278 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
279
280 /* if the panel is all the way on wait for vsync, otherwise sleep
281 * for 16 ms (long enough for the dma to panel) and then begin dma */
282 msmfb->vsync_request_time = ktime_get();
283 if (panel->request_vsync && (sleeping == AWAKE)) {
284 panel->request_vsync(panel, &msmfb->vsync_callback);
285 } else {
286 if (!hrtimer_active(&msmfb->fake_vsync)) {
287 hrtimer_start(&msmfb->fake_vsync,
288 ktime_set(0, NSEC_PER_SEC/60),
289 HRTIMER_MODE_REL);
290 }
291 }
292}
293
294static void msmfb_update(struct fb_info *info, uint32_t left, uint32_t top,
295 uint32_t eright, uint32_t ebottom)
296{
297 msmfb_pan_update(info, left, top, eright, ebottom, 0, 0);
298}
299
300static void power_on_panel(struct work_struct *work)
301{
302 struct msmfb_info *msmfb =
303 container_of(work, struct msmfb_info, resume_work);
304 struct msm_panel_data *panel = msmfb->panel;
305 unsigned long irq_flags;
306
307 mutex_lock(&msmfb->panel_init_lock);
308 DLOG(SUSPEND_RESUME, "turning on panel\n");
309 if (msmfb->sleeping == UPDATING) {
310 if (panel->unblank(panel)) {
311 printk(KERN_INFO "msmfb: panel unblank failed,"
312 "not starting drawing\n");
313 goto error;
314 }
315 spin_lock_irqsave(&msmfb->update_lock, irq_flags);
316 msmfb->sleeping = AWAKE;
317 wake_up(&msmfb->frame_wq);
318 spin_unlock_irqrestore(&msmfb->update_lock, irq_flags);
319 }
320error:
321 mutex_unlock(&msmfb->panel_init_lock);
322}
323
324
325static int msmfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
326{
327 if ((var->xres != info->var.xres) ||
328 (var->yres != info->var.yres) ||
329 (var->xres_virtual != info->var.xres_virtual) ||
330 (var->yres_virtual != info->var.yres_virtual) ||
331 (var->xoffset != info->var.xoffset) ||
332 (var->bits_per_pixel != info->var.bits_per_pixel) ||
333 (var->grayscale != info->var.grayscale))
334 return -EINVAL;
335 return 0;
336}
337
338int msmfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
339{
340 struct msmfb_info *msmfb = info->par;
341 struct msm_panel_data *panel = msmfb->panel;
342
343 /* "UPDT" */
344 if ((panel->caps & MSMFB_CAP_PARTIAL_UPDATES) &&
345 (var->reserved[0] == 0x54445055)) {
346 msmfb_pan_update(info, var->reserved[1] & 0xffff,
347 var->reserved[1] >> 16,
348 var->reserved[2] & 0xffff,
349 var->reserved[2] >> 16, var->yoffset, 1);
350 } else {
351 msmfb_pan_update(info, 0, 0, info->var.xres, info->var.yres,
352 var->yoffset, 1);
353 }
354 return 0;
355}
356
357static void msmfb_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
358{
359 cfb_fillrect(p, rect);
360 msmfb_update(p, rect->dx, rect->dy, rect->dx + rect->width,
361 rect->dy + rect->height);
362}
363
364static void msmfb_copyarea(struct fb_info *p, const struct fb_copyarea *area)
365{
366 cfb_copyarea(p, area);
367 msmfb_update(p, area->dx, area->dy, area->dx + area->width,
368 area->dy + area->height);
369}
370
371static void msmfb_imageblit(struct fb_info *p, const struct fb_image *image)
372{
373 cfb_imageblit(p, image);
374 msmfb_update(p, image->dx, image->dy, image->dx + image->width,
375 image->dy + image->height);
376}
377
378
379static int msmfb_blit(struct fb_info *info,
380 void __user *p)
381{
382 struct mdp_blit_req req;
383 struct mdp_blit_req_list req_list;
384 int i;
385 int ret;
386
387 if (copy_from_user(&req_list, p, sizeof(req_list)))
388 return -EFAULT;
389
390 for (i = 0; i < req_list.count; i++) {
391 struct mdp_blit_req_list *list =
392 (struct mdp_blit_req_list *)p;
393 if (copy_from_user(&req, &list->req[i], sizeof(req)))
394 return -EFAULT;
395 ret = mdp->blit(mdp, info, &req);
396 if (ret)
397 return ret;
398 }
399 return 0;
400}
401
402
403DEFINE_MUTEX(mdp_ppp_lock);
404
405static int msmfb_ioctl(struct fb_info *p, unsigned int cmd, unsigned long arg)
406{
407 void __user *argp = (void __user *)arg;
408 int ret;
409
410 switch (cmd) {
411 case MSMFB_GRP_DISP:
412 mdp->set_grp_disp(mdp, arg);
413 break;
414 case MSMFB_BLIT:
415 ret = msmfb_blit(p, argp);
416 if (ret)
417 return ret;
418 break;
419 default:
420 printk(KERN_INFO "msmfb unknown ioctl: %d\n", cmd);
421 return -EINVAL;
422 }
423 return 0;
424}
425
426static struct fb_ops msmfb_ops = {
427 .owner = THIS_MODULE,
428 .fb_open = msmfb_open,
429 .fb_release = msmfb_release,
430 .fb_check_var = msmfb_check_var,
431 .fb_pan_display = msmfb_pan_display,
432 .fb_fillrect = msmfb_fillrect,
433 .fb_copyarea = msmfb_copyarea,
434 .fb_imageblit = msmfb_imageblit,
435 .fb_ioctl = msmfb_ioctl,
436};
437
438static unsigned PP[16];
439
440
441
442#define BITS_PER_PIXEL 16
443
444static void setup_fb_info(struct msmfb_info *msmfb)
445{
446 struct fb_info *fb_info = msmfb->fb;
447 int r;
448
449 /* finish setting up the fb_info struct */
450 strncpy(fb_info->fix.id, "msmfb", 16);
451 fb_info->fix.ypanstep = 1;
452
453 fb_info->fbops = &msmfb_ops;
454 fb_info->flags = FBINFO_DEFAULT;
455
456 fb_info->fix.type = FB_TYPE_PACKED_PIXELS;
457 fb_info->fix.visual = FB_VISUAL_TRUECOLOR;
458 fb_info->fix.line_length = msmfb->xres * 2;
459
460 fb_info->var.xres = msmfb->xres;
461 fb_info->var.yres = msmfb->yres;
462 fb_info->var.width = msmfb->panel->fb_data->width;
463 fb_info->var.height = msmfb->panel->fb_data->height;
464 fb_info->var.xres_virtual = msmfb->xres;
465 fb_info->var.yres_virtual = msmfb->yres * 2;
466 fb_info->var.bits_per_pixel = BITS_PER_PIXEL;
467 fb_info->var.accel_flags = 0;
468
469 fb_info->var.yoffset = 0;
470
471 if (msmfb->panel->caps & MSMFB_CAP_PARTIAL_UPDATES) {
472 fb_info->var.reserved[0] = 0x54445055;
473 fb_info->var.reserved[1] = 0;
474 fb_info->var.reserved[2] = (uint16_t)msmfb->xres |
475 ((uint32_t)msmfb->yres << 16);
476 }
477
478 fb_info->var.red.offset = 11;
479 fb_info->var.red.length = 5;
480 fb_info->var.red.msb_right = 0;
481 fb_info->var.green.offset = 5;
482 fb_info->var.green.length = 6;
483 fb_info->var.green.msb_right = 0;
484 fb_info->var.blue.offset = 0;
485 fb_info->var.blue.length = 5;
486 fb_info->var.blue.msb_right = 0;
487
488 r = fb_alloc_cmap(&fb_info->cmap, 16, 0);
489 fb_info->pseudo_palette = PP;
490
491 PP[0] = 0;
492 for (r = 1; r < 16; r++)
493 PP[r] = 0xffffffff;
494}
495
496static int setup_fbmem(struct msmfb_info *msmfb, struct platform_device *pdev)
497{
498 struct fb_info *fb = msmfb->fb;
499 struct resource *resource;
500 unsigned long size = msmfb->xres * msmfb->yres *
501 (BITS_PER_PIXEL >> 3) * 2;
502 unsigned char *fbram;
503
504 /* board file might have attached a resource describing an fb */
505 resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
506 if (!resource)
507 return -EINVAL;
508
509 /* check the resource is large enough to fit the fb */
510 if (resource->end - resource->start < size) {
511 printk(KERN_ERR "allocated resource is too small for "
512 "fb\n");
513 return -ENOMEM;
514 }
515 fb->fix.smem_start = resource->start;
516 fb->fix.smem_len = resource->end - resource->start;
517 fbram = ioremap(resource->start,
518 resource->end - resource->start);
519 if (fbram == 0) {
520 printk(KERN_ERR "msmfb: cannot allocate fbram!\n");
521 return -ENOMEM;
522 }
523 fb->screen_base = fbram;
524 return 0;
525}
526
527static int msmfb_probe(struct platform_device *pdev)
528{
529 struct fb_info *fb;
530 struct msmfb_info *msmfb;
531 struct msm_panel_data *panel = pdev->dev.platform_data;
532 int ret;
533
534 if (!panel) {
535 pr_err("msmfb_probe: no platform data\n");
536 return -EINVAL;
537 }
538 if (!panel->fb_data) {
539 pr_err("msmfb_probe: no fb_data\n");
540 return -EINVAL;
541 }
542
543 fb = framebuffer_alloc(sizeof(struct msmfb_info), &pdev->dev);
544 if (!fb)
545 return -ENOMEM;
546 msmfb = fb->par;
547 msmfb->fb = fb;
548 msmfb->panel = panel;
549 msmfb->xres = panel->fb_data->xres;
550 msmfb->yres = panel->fb_data->yres;
551
552 ret = setup_fbmem(msmfb, pdev);
553 if (ret)
554 goto error_setup_fbmem;
555
556 setup_fb_info(msmfb);
557
558 spin_lock_init(&msmfb->update_lock);
559 mutex_init(&msmfb->panel_init_lock);
560 init_waitqueue_head(&msmfb->frame_wq);
561 msmfb->resume_workqueue = create_workqueue("panel_on");
562 if (msmfb->resume_workqueue == NULL) {
563 printk(KERN_ERR "failed to create panel_on workqueue\n");
564 ret = -ENOMEM;
565 goto error_create_workqueue;
566 }
567 INIT_WORK(&msmfb->resume_work, power_on_panel);
568 msmfb->black = kzalloc(msmfb->fb->var.bits_per_pixel*msmfb->xres,
569 GFP_KERNEL);
570
571 printk(KERN_INFO "msmfb_probe() installing %d x %d panel\n",
572 msmfb->xres, msmfb->yres);
573
574 msmfb->dma_callback.func = msmfb_handle_dma_interrupt;
575 msmfb->vsync_callback.func = msmfb_handle_vsync_interrupt;
576 hrtimer_init(&msmfb->fake_vsync, CLOCK_MONOTONIC,
577 HRTIMER_MODE_REL);
578
579
580 msmfb->fake_vsync.function = msmfb_fake_vsync;
581
582 ret = register_framebuffer(fb);
583 if (ret)
584 goto error_register_framebuffer;
585
586 msmfb->sleeping = WAKING;
587
588 return 0;
589
590error_register_framebuffer:
591 destroy_workqueue(msmfb->resume_workqueue);
592error_create_workqueue:
593 iounmap(fb->screen_base);
594error_setup_fbmem:
595 framebuffer_release(msmfb->fb);
596 return ret;
597}
598
599static struct platform_driver msm_panel_driver = {
600 /* need to write remove */
601 .probe = msmfb_probe,
602 .driver = {.name = "msm_panel"},
603};
604
605
606static int msmfb_add_mdp_device(struct device *dev,
607 struct class_interface *class_intf)
608{
609 /* might need locking if mulitple mdp devices */
610 if (mdp)
611 return 0;
612 mdp = container_of(dev, struct mdp_device, dev);
613 return platform_driver_register(&msm_panel_driver);
614}
615
616static void msmfb_remove_mdp_device(struct device *dev,
617 struct class_interface *class_intf)
618{
619 /* might need locking if mulitple mdp devices */
620 if (dev != &mdp->dev)
621 return;
622 platform_driver_unregister(&msm_panel_driver);
623 mdp = NULL;
624}
625
626static struct class_interface msm_fb_interface = {
627 .add_dev = &msmfb_add_mdp_device,
628 .remove_dev = &msmfb_remove_mdp_device,
629};
630
631static int __init msmfb_init(void)
632{
633 return register_mdp_client(&msm_fb_interface);
634}
635
636module_init(msmfb_init);