diff options
author | Russell King <rmk+kernel@arm.linux.org.uk> | 2010-10-28 15:14:38 -0400 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2010-10-28 15:14:38 -0400 |
commit | b3773301c4290f054aa2aa5379e59a1bf4f78bdf (patch) | |
tree | aac1619d32800393b587bf034c7dadb9e3c24f7b /drivers/video/sh_mobile_lcdcfb.c | |
parent | be6786ac738801d39cfd264ec88c352efd029578 (diff) | |
parent | 1a0b1eac5012326e52d1dcf78695ac08f41c37d7 (diff) |
Merge master.kernel.org:/pub/scm/linux/kernel/git/lethal/genesis-2.6 into devel-stable
Conflicts:
drivers/video/sh_mobile_hdmi.c
Diffstat (limited to 'drivers/video/sh_mobile_lcdcfb.c')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 348 |
1 files changed, 227 insertions, 121 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 7a1419279c8f..50963739a409 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -12,7 +12,6 @@ | |||
12 | #include <linux/init.h> | 12 | #include <linux/init.h> |
13 | #include <linux/delay.h> | 13 | #include <linux/delay.h> |
14 | #include <linux/mm.h> | 14 | #include <linux/mm.h> |
15 | #include <linux/fb.h> | ||
16 | #include <linux/clk.h> | 15 | #include <linux/clk.h> |
17 | #include <linux/pm_runtime.h> | 16 | #include <linux/pm_runtime.h> |
18 | #include <linux/platform_device.h> | 17 | #include <linux/platform_device.h> |
@@ -21,10 +20,12 @@ | |||
21 | #include <linux/vmalloc.h> | 20 | #include <linux/vmalloc.h> |
22 | #include <linux/ioctl.h> | 21 | #include <linux/ioctl.h> |
23 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
23 | #include <linux/console.h> | ||
24 | #include <video/sh_mobile_lcdc.h> | 24 | #include <video/sh_mobile_lcdc.h> |
25 | #include <asm/atomic.h> | 25 | #include <asm/atomic.h> |
26 | 26 | ||
27 | #define PALETTE_NR 16 | 27 | #include "sh_mobile_lcdcfb.h" |
28 | |||
28 | #define SIDE_B_OFFSET 0x1000 | 29 | #define SIDE_B_OFFSET 0x1000 |
29 | #define MIRROR_OFFSET 0x2000 | 30 | #define MIRROR_OFFSET 0x2000 |
30 | 31 | ||
@@ -53,11 +54,8 @@ static int lcdc_shared_regs[] = { | |||
53 | }; | 54 | }; |
54 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) | 55 | #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) |
55 | 56 | ||
56 | /* per-channel registers */ | 57 | #define DEFAULT_XRES 1280 |
57 | enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, | 58 | #define DEFAULT_YRES 1024 |
58 | LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR, | ||
59 | LDHAJR, | ||
60 | NR_CH_REGS }; | ||
61 | 59 | ||
62 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { | 60 | static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { |
63 | [LDDCKPAT1R] = 0x400, | 61 | [LDDCKPAT1R] = 0x400, |
@@ -112,23 +110,21 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = { | |||
112 | #define LDRCNTR_MRC 0x00000001 | 110 | #define LDRCNTR_MRC 0x00000001 |
113 | #define LDSR_MRS 0x00000100 | 111 | #define LDSR_MRS 0x00000100 |
114 | 112 | ||
115 | struct sh_mobile_lcdc_priv; | 113 | static const struct fb_videomode default_720p = { |
116 | struct sh_mobile_lcdc_chan { | 114 | .name = "HDMI 720p", |
117 | struct sh_mobile_lcdc_priv *lcdc; | 115 | .xres = 1280, |
118 | unsigned long *reg_offs; | 116 | .yres = 720, |
119 | unsigned long ldmt1r_value; | 117 | |
120 | unsigned long enabled; /* ME and SE in LDCNT2R */ | 118 | .left_margin = 200, |
121 | struct sh_mobile_lcdc_chan_cfg cfg; | 119 | .right_margin = 88, |
122 | u32 pseudo_palette[PALETTE_NR]; | 120 | .hsync_len = 48, |
123 | unsigned long saved_ch_regs[NR_CH_REGS]; | 121 | |
124 | struct fb_info *info; | 122 | .upper_margin = 20, |
125 | dma_addr_t dma_handle; | 123 | .lower_margin = 5, |
126 | struct fb_deferred_io defio; | 124 | .vsync_len = 5, |
127 | struct scatterlist *sglist; | 125 | |
128 | unsigned long frame_end; | 126 | .pixclock = 13468, |
129 | unsigned long pan_offset; | 127 | .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_HOR_HIGH_ACT, |
130 | wait_queue_head_t frame_end_wait; | ||
131 | struct completion vsync_completion; | ||
132 | }; | 128 | }; |
133 | 129 | ||
134 | struct sh_mobile_lcdc_priv { | 130 | struct sh_mobile_lcdc_priv { |
@@ -409,8 +405,8 @@ static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, | |||
409 | 405 | ||
410 | static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | 406 | static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) |
411 | { | 407 | { |
412 | struct fb_var_screeninfo *var = &ch->info->var; | 408 | struct fb_var_screeninfo *var = &ch->info->var, *display_var = &ch->display_var; |
413 | unsigned long h_total, hsync_pos; | 409 | unsigned long h_total, hsync_pos, display_h_total; |
414 | u32 tmp; | 410 | u32 tmp; |
415 | 411 | ||
416 | tmp = ch->ldmt1r_value; | 412 | tmp = ch->ldmt1r_value; |
@@ -428,31 +424,33 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | |||
428 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); | 424 | lcdc_write_chan(ch, LDMT3R, ch->cfg.sys_bus_cfg.ldmt3r); |
429 | 425 | ||
430 | /* horizontal configuration */ | 426 | /* horizontal configuration */ |
431 | h_total = var->xres + var->hsync_len + | 427 | h_total = display_var->xres + display_var->hsync_len + |
432 | var->left_margin + var->right_margin; | 428 | display_var->left_margin + display_var->right_margin; |
433 | tmp = h_total / 8; /* HTCN */ | 429 | tmp = h_total / 8; /* HTCN */ |
434 | tmp |= (var->xres / 8) << 16; /* HDCN */ | 430 | tmp |= (min(display_var->xres, var->xres) / 8) << 16; /* HDCN */ |
435 | lcdc_write_chan(ch, LDHCNR, tmp); | 431 | lcdc_write_chan(ch, LDHCNR, tmp); |
436 | 432 | ||
437 | hsync_pos = var->xres + var->right_margin; | 433 | hsync_pos = display_var->xres + display_var->right_margin; |
438 | tmp = hsync_pos / 8; /* HSYNP */ | 434 | tmp = hsync_pos / 8; /* HSYNP */ |
439 | tmp |= (var->hsync_len / 8) << 16; /* HSYNW */ | 435 | tmp |= (display_var->hsync_len / 8) << 16; /* HSYNW */ |
440 | lcdc_write_chan(ch, LDHSYNR, tmp); | 436 | lcdc_write_chan(ch, LDHSYNR, tmp); |
441 | 437 | ||
442 | /* vertical configuration */ | 438 | /* vertical configuration */ |
443 | tmp = var->yres + var->vsync_len + | 439 | tmp = display_var->yres + display_var->vsync_len + |
444 | var->upper_margin + var->lower_margin; /* VTLN */ | 440 | display_var->upper_margin + display_var->lower_margin; /* VTLN */ |
445 | tmp |= var->yres << 16; /* VDLN */ | 441 | tmp |= min(display_var->yres, var->yres) << 16; /* VDLN */ |
446 | lcdc_write_chan(ch, LDVLNR, tmp); | 442 | lcdc_write_chan(ch, LDVLNR, tmp); |
447 | 443 | ||
448 | tmp = var->yres + var->lower_margin; /* VSYNP */ | 444 | tmp = display_var->yres + display_var->lower_margin; /* VSYNP */ |
449 | tmp |= var->vsync_len << 16; /* VSYNW */ | 445 | tmp |= display_var->vsync_len << 16; /* VSYNW */ |
450 | lcdc_write_chan(ch, LDVSYNR, tmp); | 446 | lcdc_write_chan(ch, LDVSYNR, tmp); |
451 | 447 | ||
452 | /* Adjust horizontal synchronisation for HDMI */ | 448 | /* Adjust horizontal synchronisation for HDMI */ |
453 | tmp = ((var->xres & 7) << 24) | | 449 | display_h_total = display_var->xres + display_var->hsync_len + |
454 | ((h_total & 7) << 16) | | 450 | display_var->left_margin + display_var->right_margin; |
455 | ((var->hsync_len & 7) << 8) | | 451 | tmp = ((display_var->xres & 7) << 24) | |
452 | ((display_h_total & 7) << 16) | | ||
453 | ((display_var->hsync_len & 7) << 8) | | ||
456 | hsync_pos; | 454 | hsync_pos; |
457 | lcdc_write_chan(ch, LDHAJR, tmp); | 455 | lcdc_write_chan(ch, LDHAJR, tmp); |
458 | } | 456 | } |
@@ -460,7 +458,6 @@ static void sh_mobile_lcdc_geometry(struct sh_mobile_lcdc_chan *ch) | |||
460 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | 458 | static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) |
461 | { | 459 | { |
462 | struct sh_mobile_lcdc_chan *ch; | 460 | struct sh_mobile_lcdc_chan *ch; |
463 | struct fb_videomode *lcd_cfg; | ||
464 | struct sh_mobile_lcdc_board_cfg *board_cfg; | 461 | struct sh_mobile_lcdc_board_cfg *board_cfg; |
465 | unsigned long tmp; | 462 | unsigned long tmp; |
466 | int k, m; | 463 | int k, m; |
@@ -503,7 +500,8 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
503 | m = 1 << 6; | 500 | m = 1 << 6; |
504 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); | 501 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); |
505 | 502 | ||
506 | lcdc_write_chan(ch, LDDCKPAT1R, 0x00000000); | 503 | /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */ |
504 | lcdc_write_chan(ch, LDDCKPAT1R, 0); | ||
507 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); | 505 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); |
508 | } | 506 | } |
509 | 507 | ||
@@ -518,7 +516,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
518 | 516 | ||
519 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { | 517 | for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { |
520 | ch = &priv->ch[k]; | 518 | ch = &priv->ch[k]; |
521 | lcd_cfg = &ch->cfg.lcd_cfg; | ||
522 | 519 | ||
523 | if (!ch->enabled) | 520 | if (!ch->enabled) |
524 | continue; | 521 | continue; |
@@ -547,7 +544,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
547 | 544 | ||
548 | /* set bpp format in PKF[4:0] */ | 545 | /* set bpp format in PKF[4:0] */ |
549 | tmp = lcdc_read_chan(ch, LDDFR); | 546 | tmp = lcdc_read_chan(ch, LDDFR); |
550 | tmp &= ~(0x0001001f); | 547 | tmp &= ~0x0001001f; |
551 | tmp |= (ch->info->var.bits_per_pixel == 16) ? 3 : 0; | 548 | tmp |= (ch->info->var.bits_per_pixel == 16) ? 3 : 0; |
552 | lcdc_write_chan(ch, LDDFR, tmp); | 549 | lcdc_write_chan(ch, LDDFR, tmp); |
553 | 550 | ||
@@ -591,8 +588,10 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
591 | continue; | 588 | continue; |
592 | 589 | ||
593 | board_cfg = &ch->cfg.board_cfg; | 590 | board_cfg = &ch->cfg.board_cfg; |
594 | if (board_cfg->display_on) | 591 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { |
595 | board_cfg->display_on(board_cfg->board_data, ch->info); | 592 | board_cfg->display_on(board_cfg->board_data, ch->info); |
593 | module_put(board_cfg->owner); | ||
594 | } | ||
596 | } | 595 | } |
597 | 596 | ||
598 | return 0; | 597 | return 0; |
@@ -614,7 +613,7 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
614 | * flush frame, and wait for frame end interrupt | 613 | * flush frame, and wait for frame end interrupt |
615 | * clean up deferred io and enable clock | 614 | * clean up deferred io and enable clock |
616 | */ | 615 | */ |
617 | if (ch->info->fbdefio) { | 616 | if (ch->info && ch->info->fbdefio) { |
618 | ch->frame_end = 0; | 617 | ch->frame_end = 0; |
619 | schedule_delayed_work(&ch->info->deferred_work, 0); | 618 | schedule_delayed_work(&ch->info->deferred_work, 0); |
620 | wait_event(ch->frame_end_wait, ch->frame_end); | 619 | wait_event(ch->frame_end_wait, ch->frame_end); |
@@ -624,8 +623,10 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) | |||
624 | } | 623 | } |
625 | 624 | ||
626 | board_cfg = &ch->cfg.board_cfg; | 625 | board_cfg = &ch->cfg.board_cfg; |
627 | if (board_cfg->display_off) | 626 | if (try_module_get(board_cfg->owner) && board_cfg->display_off) { |
628 | board_cfg->display_off(board_cfg->board_data); | 627 | board_cfg->display_off(board_cfg->board_data); |
628 | module_put(board_cfg->owner); | ||
629 | } | ||
629 | } | 630 | } |
630 | 631 | ||
631 | /* stop the lcdc */ | 632 | /* stop the lcdc */ |
@@ -704,7 +705,6 @@ static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, | |||
704 | return PTR_ERR(priv->dot_clk); | 705 | return PTR_ERR(priv->dot_clk); |
705 | } | 706 | } |
706 | } | 707 | } |
707 | atomic_set(&priv->hw_usecnt, -1); | ||
708 | 708 | ||
709 | /* Runtime PM support involves two step for this driver: | 709 | /* Runtime PM support involves two step for this driver: |
710 | * 1) Enable Runtime PM | 710 | * 1) Enable Runtime PM |
@@ -837,6 +837,102 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, | |||
837 | return retval; | 837 | return retval; |
838 | } | 838 | } |
839 | 839 | ||
840 | static void sh_mobile_fb_reconfig(struct fb_info *info) | ||
841 | { | ||
842 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
843 | struct fb_videomode mode1, mode2; | ||
844 | struct fb_event event; | ||
845 | int evnt = FB_EVENT_MODE_CHANGE_ALL; | ||
846 | |||
847 | if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) | ||
848 | /* More framebuffer users are active */ | ||
849 | return; | ||
850 | |||
851 | fb_var_to_videomode(&mode1, &ch->display_var); | ||
852 | fb_var_to_videomode(&mode2, &info->var); | ||
853 | |||
854 | if (fb_mode_is_equal(&mode1, &mode2)) | ||
855 | return; | ||
856 | |||
857 | /* Display has been re-plugged, framebuffer is free now, reconfigure */ | ||
858 | if (fb_set_var(info, &ch->display_var) < 0) | ||
859 | /* Couldn't reconfigure, hopefully, can continue as before */ | ||
860 | return; | ||
861 | |||
862 | info->fix.line_length = mode2.xres * (ch->cfg.bpp / 8); | ||
863 | |||
864 | /* | ||
865 | * fb_set_var() calls the notifier change internally, only if | ||
866 | * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a | ||
867 | * user event, we have to call the chain ourselves. | ||
868 | */ | ||
869 | event.info = info; | ||
870 | event.data = &mode2; | ||
871 | fb_notifier_call_chain(evnt, &event); | ||
872 | } | ||
873 | |||
874 | /* | ||
875 | * Locking: both .fb_release() and .fb_open() are called with info->lock held if | ||
876 | * user == 1, or with console sem held, if user == 0. | ||
877 | */ | ||
878 | static int sh_mobile_release(struct fb_info *info, int user) | ||
879 | { | ||
880 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
881 | |||
882 | mutex_lock(&ch->open_lock); | ||
883 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
884 | |||
885 | ch->use_count--; | ||
886 | |||
887 | /* Nothing to reconfigure, when called from fbcon */ | ||
888 | if (user) { | ||
889 | acquire_console_sem(); | ||
890 | sh_mobile_fb_reconfig(info); | ||
891 | release_console_sem(); | ||
892 | } | ||
893 | |||
894 | mutex_unlock(&ch->open_lock); | ||
895 | |||
896 | return 0; | ||
897 | } | ||
898 | |||
899 | static int sh_mobile_open(struct fb_info *info, int user) | ||
900 | { | ||
901 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
902 | |||
903 | mutex_lock(&ch->open_lock); | ||
904 | ch->use_count++; | ||
905 | |||
906 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
907 | mutex_unlock(&ch->open_lock); | ||
908 | |||
909 | return 0; | ||
910 | } | ||
911 | |||
912 | static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
913 | { | ||
914 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
915 | |||
916 | if (var->xres < 160 || var->xres > 1920 || | ||
917 | var->yres < 120 || var->yres > 1080 || | ||
918 | var->left_margin < 32 || var->left_margin > 320 || | ||
919 | var->right_margin < 12 || var->right_margin > 240 || | ||
920 | var->upper_margin < 12 || var->upper_margin > 120 || | ||
921 | var->lower_margin < 1 || var->lower_margin > 64 || | ||
922 | var->hsync_len < 32 || var->hsync_len > 240 || | ||
923 | var->vsync_len < 2 || var->vsync_len > 64 || | ||
924 | var->pixclock < 6000 || var->pixclock > 40000 || | ||
925 | var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) { | ||
926 | dev_warn(info->dev, "Invalid info: %u %u %u %u %u %u %u %u %u!\n", | ||
927 | var->xres, var->yres, | ||
928 | var->left_margin, var->right_margin, | ||
929 | var->upper_margin, var->lower_margin, | ||
930 | var->hsync_len, var->vsync_len, | ||
931 | var->pixclock); | ||
932 | return -EINVAL; | ||
933 | } | ||
934 | return 0; | ||
935 | } | ||
840 | 936 | ||
841 | static struct fb_ops sh_mobile_lcdc_ops = { | 937 | static struct fb_ops sh_mobile_lcdc_ops = { |
842 | .owner = THIS_MODULE, | 938 | .owner = THIS_MODULE, |
@@ -848,6 +944,9 @@ static struct fb_ops sh_mobile_lcdc_ops = { | |||
848 | .fb_imageblit = sh_mobile_lcdc_imageblit, | 944 | .fb_imageblit = sh_mobile_lcdc_imageblit, |
849 | .fb_pan_display = sh_mobile_fb_pan_display, | 945 | .fb_pan_display = sh_mobile_fb_pan_display, |
850 | .fb_ioctl = sh_mobile_ioctl, | 946 | .fb_ioctl = sh_mobile_ioctl, |
947 | .fb_open = sh_mobile_open, | ||
948 | .fb_release = sh_mobile_release, | ||
949 | .fb_check_var = sh_mobile_check_var, | ||
851 | }; | 950 | }; |
852 | 951 | ||
853 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 952 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) |
@@ -958,6 +1057,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = { | |||
958 | .runtime_resume = sh_mobile_lcdc_runtime_resume, | 1057 | .runtime_resume = sh_mobile_lcdc_runtime_resume, |
959 | }; | 1058 | }; |
960 | 1059 | ||
1060 | /* locking: called with info->lock held */ | ||
961 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, | 1061 | static int sh_mobile_lcdc_notify(struct notifier_block *nb, |
962 | unsigned long action, void *data) | 1062 | unsigned long action, void *data) |
963 | { | 1063 | { |
@@ -965,53 +1065,40 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, | |||
965 | struct fb_info *info = event->info; | 1065 | struct fb_info *info = event->info; |
966 | struct sh_mobile_lcdc_chan *ch = info->par; | 1066 | struct sh_mobile_lcdc_chan *ch = info->par; |
967 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; | 1067 | struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg; |
968 | struct fb_var_screeninfo *var; | 1068 | int ret; |
969 | 1069 | ||
970 | if (&ch->lcdc->notifier != nb) | 1070 | if (&ch->lcdc->notifier != nb) |
971 | return 0; | 1071 | return NOTIFY_DONE; |
972 | 1072 | ||
973 | dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", | 1073 | dev_dbg(info->dev, "%s(): action = %lu, data = %p\n", |
974 | __func__, action, event->data); | 1074 | __func__, action, event->data); |
975 | 1075 | ||
976 | switch(action) { | 1076 | switch(action) { |
977 | case FB_EVENT_SUSPEND: | 1077 | case FB_EVENT_SUSPEND: |
978 | if (board_cfg->display_off) | 1078 | if (try_module_get(board_cfg->owner) && board_cfg->display_off) { |
979 | board_cfg->display_off(board_cfg->board_data); | 1079 | board_cfg->display_off(board_cfg->board_data); |
1080 | module_put(board_cfg->owner); | ||
1081 | } | ||
980 | pm_runtime_put(info->device); | 1082 | pm_runtime_put(info->device); |
1083 | sh_mobile_lcdc_stop(ch->lcdc); | ||
981 | break; | 1084 | break; |
982 | case FB_EVENT_RESUME: | 1085 | case FB_EVENT_RESUME: |
983 | var = &info->var; | 1086 | mutex_lock(&ch->open_lock); |
1087 | sh_mobile_fb_reconfig(info); | ||
1088 | mutex_unlock(&ch->open_lock); | ||
984 | 1089 | ||
985 | /* HDMI must be enabled before LCDC configuration */ | 1090 | /* HDMI must be enabled before LCDC configuration */ |
986 | if (board_cfg->display_on) | 1091 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { |
987 | board_cfg->display_on(board_cfg->board_data, ch->info); | 1092 | board_cfg->display_on(board_cfg->board_data, info); |
988 | 1093 | module_put(board_cfg->owner); | |
989 | /* Check if the new display is not in our modelist */ | ||
990 | if (ch->info->modelist.next && | ||
991 | !fb_match_mode(var, &ch->info->modelist)) { | ||
992 | struct fb_videomode mode; | ||
993 | int ret; | ||
994 | |||
995 | /* Can we handle this display? */ | ||
996 | if (var->xres > ch->cfg.lcd_cfg.xres || | ||
997 | var->yres > ch->cfg.lcd_cfg.yres) | ||
998 | return -ENOMEM; | ||
999 | |||
1000 | /* Add to the modelist */ | ||
1001 | fb_var_to_videomode(&mode, var); | ||
1002 | ret = fb_add_videomode(&mode, &ch->info->modelist); | ||
1003 | if (ret < 0) | ||
1004 | return ret; | ||
1005 | } | 1094 | } |
1006 | 1095 | ||
1007 | pm_runtime_get_sync(info->device); | 1096 | ret = sh_mobile_lcdc_start(ch->lcdc); |
1008 | 1097 | if (!ret) | |
1009 | sh_mobile_lcdc_geometry(ch); | 1098 | pm_runtime_get_sync(info->device); |
1010 | |||
1011 | break; | ||
1012 | } | 1099 | } |
1013 | 1100 | ||
1014 | return 0; | 1101 | return NOTIFY_OK; |
1015 | } | 1102 | } |
1016 | 1103 | ||
1017 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); | 1104 | static int sh_mobile_lcdc_remove(struct platform_device *pdev); |
@@ -1020,14 +1107,13 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1020 | { | 1107 | { |
1021 | struct fb_info *info; | 1108 | struct fb_info *info; |
1022 | struct sh_mobile_lcdc_priv *priv; | 1109 | struct sh_mobile_lcdc_priv *priv; |
1023 | struct sh_mobile_lcdc_info *pdata; | 1110 | struct sh_mobile_lcdc_info *pdata = pdev->dev.platform_data; |
1024 | struct sh_mobile_lcdc_chan_cfg *cfg; | ||
1025 | struct resource *res; | 1111 | struct resource *res; |
1026 | int error; | 1112 | int error; |
1027 | void *buf; | 1113 | void *buf; |
1028 | int i, j; | 1114 | int i, j; |
1029 | 1115 | ||
1030 | if (!pdev->dev.platform_data) { | 1116 | if (!pdata) { |
1031 | dev_err(&pdev->dev, "no platform data defined\n"); | 1117 | dev_err(&pdev->dev, "no platform data defined\n"); |
1032 | return -EINVAL; | 1118 | return -EINVAL; |
1033 | } | 1119 | } |
@@ -1055,31 +1141,33 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1055 | } | 1141 | } |
1056 | 1142 | ||
1057 | priv->irq = i; | 1143 | priv->irq = i; |
1058 | pdata = pdev->dev.platform_data; | 1144 | atomic_set(&priv->hw_usecnt, -1); |
1059 | 1145 | ||
1060 | j = 0; | 1146 | j = 0; |
1061 | for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) { | 1147 | for (i = 0; i < ARRAY_SIZE(pdata->ch); i++) { |
1062 | priv->ch[j].lcdc = priv; | 1148 | struct sh_mobile_lcdc_chan *ch = priv->ch + j; |
1063 | memcpy(&priv->ch[j].cfg, &pdata->ch[i], sizeof(pdata->ch[i])); | 1149 | |
1150 | ch->lcdc = priv; | ||
1151 | memcpy(&ch->cfg, &pdata->ch[i], sizeof(pdata->ch[i])); | ||
1064 | 1152 | ||
1065 | error = sh_mobile_lcdc_check_interface(&priv->ch[j]); | 1153 | error = sh_mobile_lcdc_check_interface(ch); |
1066 | if (error) { | 1154 | if (error) { |
1067 | dev_err(&pdev->dev, "unsupported interface type\n"); | 1155 | dev_err(&pdev->dev, "unsupported interface type\n"); |
1068 | goto err1; | 1156 | goto err1; |
1069 | } | 1157 | } |
1070 | init_waitqueue_head(&priv->ch[j].frame_end_wait); | 1158 | init_waitqueue_head(&ch->frame_end_wait); |
1071 | init_completion(&priv->ch[j].vsync_completion); | 1159 | init_completion(&ch->vsync_completion); |
1072 | priv->ch[j].pan_offset = 0; | 1160 | ch->pan_offset = 0; |
1073 | 1161 | ||
1074 | switch (pdata->ch[i].chan) { | 1162 | switch (pdata->ch[i].chan) { |
1075 | case LCDC_CHAN_MAINLCD: | 1163 | case LCDC_CHAN_MAINLCD: |
1076 | priv->ch[j].enabled = 1 << 1; | 1164 | ch->enabled = 1 << 1; |
1077 | priv->ch[j].reg_offs = lcdc_offs_mainlcd; | 1165 | ch->reg_offs = lcdc_offs_mainlcd; |
1078 | j++; | 1166 | j++; |
1079 | break; | 1167 | break; |
1080 | case LCDC_CHAN_SUBLCD: | 1168 | case LCDC_CHAN_SUBLCD: |
1081 | priv->ch[j].enabled = 1 << 2; | 1169 | ch->enabled = 1 << 2; |
1082 | priv->ch[j].reg_offs = lcdc_offs_sublcd; | 1170 | ch->reg_offs = lcdc_offs_sublcd; |
1083 | j++; | 1171 | j++; |
1084 | break; | 1172 | break; |
1085 | } | 1173 | } |
@@ -1103,69 +1191,83 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1103 | 1191 | ||
1104 | for (i = 0; i < j; i++) { | 1192 | for (i = 0; i < j; i++) { |
1105 | struct fb_var_screeninfo *var; | 1193 | struct fb_var_screeninfo *var; |
1106 | struct fb_videomode *lcd_cfg; | 1194 | const struct fb_videomode *lcd_cfg, *max_cfg = NULL; |
1107 | cfg = &priv->ch[i].cfg; | 1195 | struct sh_mobile_lcdc_chan *ch = priv->ch + i; |
1196 | struct sh_mobile_lcdc_chan_cfg *cfg = &ch->cfg; | ||
1197 | const struct fb_videomode *mode = cfg->lcd_cfg; | ||
1198 | unsigned long max_size = 0; | ||
1199 | int k; | ||
1108 | 1200 | ||
1109 | priv->ch[i].info = framebuffer_alloc(0, &pdev->dev); | 1201 | ch->info = framebuffer_alloc(0, &pdev->dev); |
1110 | if (!priv->ch[i].info) { | 1202 | if (!ch->info) { |
1111 | dev_err(&pdev->dev, "unable to allocate fb_info\n"); | 1203 | dev_err(&pdev->dev, "unable to allocate fb_info\n"); |
1112 | error = -ENOMEM; | 1204 | error = -ENOMEM; |
1113 | break; | 1205 | break; |
1114 | } | 1206 | } |
1115 | 1207 | ||
1116 | info = priv->ch[i].info; | 1208 | info = ch->info; |
1117 | var = &info->var; | 1209 | var = &info->var; |
1118 | lcd_cfg = &cfg->lcd_cfg; | ||
1119 | info->fbops = &sh_mobile_lcdc_ops; | 1210 | info->fbops = &sh_mobile_lcdc_ops; |
1120 | var->xres = var->xres_virtual = lcd_cfg->xres; | 1211 | info->par = ch; |
1121 | var->yres = lcd_cfg->yres; | 1212 | |
1213 | mutex_init(&ch->open_lock); | ||
1214 | |||
1215 | for (k = 0, lcd_cfg = mode; | ||
1216 | k < cfg->num_cfg && lcd_cfg; | ||
1217 | k++, lcd_cfg++) { | ||
1218 | unsigned long size = lcd_cfg->yres * lcd_cfg->xres; | ||
1219 | |||
1220 | if (size > max_size) { | ||
1221 | max_cfg = lcd_cfg; | ||
1222 | max_size = size; | ||
1223 | } | ||
1224 | } | ||
1225 | |||
1226 | if (!mode) | ||
1227 | max_size = DEFAULT_XRES * DEFAULT_YRES; | ||
1228 | else if (max_cfg) | ||
1229 | dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n", | ||
1230 | max_cfg->xres, max_cfg->yres); | ||
1231 | |||
1232 | info->fix = sh_mobile_lcdc_fix; | ||
1233 | info->fix.smem_len = max_size * (cfg->bpp / 8) * 2; | ||
1234 | |||
1235 | if (!mode) | ||
1236 | mode = &default_720p; | ||
1237 | |||
1238 | fb_videomode_to_var(var, mode); | ||
1122 | /* Default Y virtual resolution is 2x panel size */ | 1239 | /* Default Y virtual resolution is 2x panel size */ |
1123 | var->yres_virtual = var->yres * 2; | 1240 | var->yres_virtual = var->yres * 2; |
1124 | var->width = cfg->lcd_size_cfg.width; | ||
1125 | var->height = cfg->lcd_size_cfg.height; | ||
1126 | var->activate = FB_ACTIVATE_NOW; | 1241 | var->activate = FB_ACTIVATE_NOW; |
1127 | var->left_margin = lcd_cfg->left_margin; | ||
1128 | var->right_margin = lcd_cfg->right_margin; | ||
1129 | var->upper_margin = lcd_cfg->upper_margin; | ||
1130 | var->lower_margin = lcd_cfg->lower_margin; | ||
1131 | var->hsync_len = lcd_cfg->hsync_len; | ||
1132 | var->vsync_len = lcd_cfg->vsync_len; | ||
1133 | var->sync = lcd_cfg->sync; | ||
1134 | var->pixclock = lcd_cfg->pixclock; | ||
1135 | 1242 | ||
1136 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); | 1243 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); |
1137 | if (error) | 1244 | if (error) |
1138 | break; | 1245 | break; |
1139 | 1246 | ||
1140 | info->fix = sh_mobile_lcdc_fix; | ||
1141 | info->fix.line_length = lcd_cfg->xres * (cfg->bpp / 8); | ||
1142 | info->fix.smem_len = info->fix.line_length * | ||
1143 | var->yres_virtual; | ||
1144 | |||
1145 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, | 1247 | buf = dma_alloc_coherent(&pdev->dev, info->fix.smem_len, |
1146 | &priv->ch[i].dma_handle, GFP_KERNEL); | 1248 | &ch->dma_handle, GFP_KERNEL); |
1147 | if (!buf) { | 1249 | if (!buf) { |
1148 | dev_err(&pdev->dev, "unable to allocate buffer\n"); | 1250 | dev_err(&pdev->dev, "unable to allocate buffer\n"); |
1149 | error = -ENOMEM; | 1251 | error = -ENOMEM; |
1150 | break; | 1252 | break; |
1151 | } | 1253 | } |
1152 | 1254 | ||
1153 | info->pseudo_palette = &priv->ch[i].pseudo_palette; | 1255 | info->pseudo_palette = &ch->pseudo_palette; |
1154 | info->flags = FBINFO_FLAG_DEFAULT; | 1256 | info->flags = FBINFO_FLAG_DEFAULT; |
1155 | 1257 | ||
1156 | error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); | 1258 | error = fb_alloc_cmap(&info->cmap, PALETTE_NR, 0); |
1157 | if (error < 0) { | 1259 | if (error < 0) { |
1158 | dev_err(&pdev->dev, "unable to allocate cmap\n"); | 1260 | dev_err(&pdev->dev, "unable to allocate cmap\n"); |
1159 | dma_free_coherent(&pdev->dev, info->fix.smem_len, | 1261 | dma_free_coherent(&pdev->dev, info->fix.smem_len, |
1160 | buf, priv->ch[i].dma_handle); | 1262 | buf, ch->dma_handle); |
1161 | break; | 1263 | break; |
1162 | } | 1264 | } |
1163 | 1265 | ||
1164 | memset(buf, 0, info->fix.smem_len); | 1266 | info->fix.smem_start = ch->dma_handle; |
1165 | info->fix.smem_start = priv->ch[i].dma_handle; | 1267 | info->fix.line_length = var->xres * (cfg->bpp / 8); |
1166 | info->screen_base = buf; | 1268 | info->screen_base = buf; |
1167 | info->device = &pdev->dev; | 1269 | info->device = &pdev->dev; |
1168 | info->par = &priv->ch[i]; | 1270 | ch->display_var = *var; |
1169 | } | 1271 | } |
1170 | 1272 | ||
1171 | if (error) | 1273 | if (error) |
@@ -1179,6 +1281,10 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1179 | 1281 | ||
1180 | for (i = 0; i < j; i++) { | 1282 | for (i = 0; i < j; i++) { |
1181 | struct sh_mobile_lcdc_chan *ch = priv->ch + i; | 1283 | struct sh_mobile_lcdc_chan *ch = priv->ch + i; |
1284 | const struct fb_videomode *mode = ch->cfg.lcd_cfg; | ||
1285 | |||
1286 | if (!mode) | ||
1287 | mode = &default_720p; | ||
1182 | 1288 | ||
1183 | info = ch->info; | 1289 | info = ch->info; |
1184 | 1290 | ||
@@ -1191,6 +1297,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1191 | } | 1297 | } |
1192 | } | 1298 | } |
1193 | 1299 | ||
1300 | fb_videomode_to_modelist(mode, ch->cfg.num_cfg, &info->modelist); | ||
1194 | error = register_framebuffer(info); | 1301 | error = register_framebuffer(info); |
1195 | if (error < 0) | 1302 | if (error < 0) |
1196 | goto err1; | 1303 | goto err1; |
@@ -1200,8 +1307,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1200 | pdev->name, | 1307 | pdev->name, |
1201 | (ch->cfg.chan == LCDC_CHAN_MAINLCD) ? | 1308 | (ch->cfg.chan == LCDC_CHAN_MAINLCD) ? |
1202 | "mainlcd" : "sublcd", | 1309 | "mainlcd" : "sublcd", |
1203 | (int) ch->cfg.lcd_cfg.xres, | 1310 | info->var.xres, info->var.yres, |
1204 | (int) ch->cfg.lcd_cfg.yres, | ||
1205 | ch->cfg.bpp); | 1311 | ch->cfg.bpp); |
1206 | 1312 | ||
1207 | /* deferred io mode: disable clock to save power */ | 1313 | /* deferred io mode: disable clock to save power */ |