diff options
author | Guennadi Liakhovetski <g.liakhovetski@gmx.de> | 2010-09-14 10:48:54 -0400 |
---|---|---|
committer | Paul Mundt <lethal@linux-sh.org> | 2010-09-16 03:36:17 -0400 |
commit | dd210503b77ae04adfdb25ca45536c4f7e33edb1 (patch) | |
tree | 14f3e8775c17906f8216618d1f7a97ee6927a50e /drivers/video | |
parent | 52d5ac0073eb5faf284574bd98a25a65053eaae0 (diff) |
fbdev: sh_mobile_lcdc: reconfigure the framebuffer, when free
Currently the sh_mobile_lcdc driver only reconfigures the hardware interface,
when a new monitor is plugged in. This patch adds support for dynamic
framebuffer reconfiguration, when no user is holding the framebuffer device
node open.
Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: Paul Mundt <lethal@linux-sh.org>
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 112 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.h | 3 |
2 files changed, 114 insertions, 1 deletions
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index b4a878624510..d6e9ff512443 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c | |||
@@ -20,6 +20,7 @@ | |||
20 | #include <linux/vmalloc.h> | 20 | #include <linux/vmalloc.h> |
21 | #include <linux/ioctl.h> | 21 | #include <linux/ioctl.h> |
22 | #include <linux/slab.h> | 22 | #include <linux/slab.h> |
23 | #include <linux/console.h> | ||
23 | #include <video/sh_mobile_lcdc.h> | 24 | #include <video/sh_mobile_lcdc.h> |
24 | #include <asm/atomic.h> | 25 | #include <asm/atomic.h> |
25 | 26 | ||
@@ -479,6 +480,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) | |||
479 | m = 1 << 6; | 480 | m = 1 << 6; |
480 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); | 481 | tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0); |
481 | 482 | ||
483 | /* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */ | ||
482 | lcdc_write_chan(ch, LDDCKPAT1R, 0); | 484 | lcdc_write_chan(ch, LDDCKPAT1R, 0); |
483 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); | 485 | lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1); |
484 | } | 486 | } |
@@ -815,6 +817,103 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd, | |||
815 | return retval; | 817 | return retval; |
816 | } | 818 | } |
817 | 819 | ||
820 | static void sh_mobile_fb_reconfig(struct fb_info *info) | ||
821 | { | ||
822 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
823 | struct fb_videomode mode1, mode2; | ||
824 | struct fb_event event; | ||
825 | int evnt = FB_EVENT_MODE_CHANGE_ALL; | ||
826 | |||
827 | if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par)) | ||
828 | /* More framebuffer users are active */ | ||
829 | return; | ||
830 | |||
831 | fb_var_to_videomode(&mode1, &ch->display_var); | ||
832 | fb_var_to_videomode(&mode2, &info->var); | ||
833 | |||
834 | if (fb_mode_is_equal(&mode1, &mode2)) | ||
835 | return; | ||
836 | |||
837 | /* Display has been re-plugged, framebuffer is free now, reconfigure */ | ||
838 | if (fb_set_var(info, &ch->display_var) < 0) | ||
839 | /* Couldn't reconfigure, hopefully, can continue as before */ | ||
840 | return; | ||
841 | |||
842 | info->fix.line_length = mode2.xres * (ch->cfg.bpp / 8); | ||
843 | |||
844 | /* | ||
845 | * fb_set_var() calls the notifier change internally, only if | ||
846 | * FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a | ||
847 | * user event, we have to call the chain ourselves. | ||
848 | */ | ||
849 | event.info = info; | ||
850 | event.data = &mode2; | ||
851 | fb_notifier_call_chain(evnt, &event); | ||
852 | } | ||
853 | |||
854 | /* | ||
855 | * Locking: both .fb_release() and .fb_open() are called with info->lock held if | ||
856 | * user == 1, or with console sem held, if user == 0. | ||
857 | */ | ||
858 | static int sh_mobile_release(struct fb_info *info, int user) | ||
859 | { | ||
860 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
861 | |||
862 | mutex_lock(&ch->open_lock); | ||
863 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
864 | |||
865 | ch->use_count--; | ||
866 | |||
867 | /* Nothing to reconfigure, when called from fbcon */ | ||
868 | if (user) { | ||
869 | acquire_console_sem(); | ||
870 | sh_mobile_fb_reconfig(info); | ||
871 | release_console_sem(); | ||
872 | } | ||
873 | |||
874 | mutex_unlock(&ch->open_lock); | ||
875 | |||
876 | return 0; | ||
877 | } | ||
878 | |||
879 | static int sh_mobile_open(struct fb_info *info, int user) | ||
880 | { | ||
881 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
882 | |||
883 | mutex_lock(&ch->open_lock); | ||
884 | ch->use_count++; | ||
885 | |||
886 | dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count); | ||
887 | mutex_unlock(&ch->open_lock); | ||
888 | |||
889 | return 0; | ||
890 | } | ||
891 | |||
892 | static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | ||
893 | { | ||
894 | struct sh_mobile_lcdc_chan *ch = info->par; | ||
895 | |||
896 | if (var->xres < 160 || var->xres > 1920 || | ||
897 | var->yres < 120 || var->yres > 1080 || | ||
898 | var->left_margin < 32 || var->left_margin > 320 || | ||
899 | var->right_margin < 12 || var->right_margin > 240 || | ||
900 | var->upper_margin < 12 || var->upper_margin > 120 || | ||
901 | var->lower_margin < 1 || var->lower_margin > 64 || | ||
902 | var->hsync_len < 32 || var->hsync_len > 120 || | ||
903 | var->vsync_len < 2 || var->vsync_len > 64 || | ||
904 | var->pixclock < 6000 || var->pixclock > 40000 || | ||
905 | var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) { | ||
906 | dev_warn(info->dev, "Invalid info: %u %u %u %u %u %u %u %u %u!\n", | ||
907 | var->xres, var->yres, | ||
908 | var->left_margin, var->right_margin, | ||
909 | var->upper_margin, var->lower_margin, | ||
910 | var->hsync_len, var->vsync_len, | ||
911 | var->pixclock); | ||
912 | return -EINVAL; | ||
913 | } | ||
914 | return 0; | ||
915 | } | ||
916 | |||
818 | static struct fb_ops sh_mobile_lcdc_ops = { | 917 | static struct fb_ops sh_mobile_lcdc_ops = { |
819 | .owner = THIS_MODULE, | 918 | .owner = THIS_MODULE, |
820 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, | 919 | .fb_setcolreg = sh_mobile_lcdc_setcolreg, |
@@ -825,6 +924,9 @@ static struct fb_ops sh_mobile_lcdc_ops = { | |||
825 | .fb_imageblit = sh_mobile_lcdc_imageblit, | 924 | .fb_imageblit = sh_mobile_lcdc_imageblit, |
826 | .fb_pan_display = sh_mobile_fb_pan_display, | 925 | .fb_pan_display = sh_mobile_fb_pan_display, |
827 | .fb_ioctl = sh_mobile_ioctl, | 926 | .fb_ioctl = sh_mobile_ioctl, |
927 | .fb_open = sh_mobile_open, | ||
928 | .fb_release = sh_mobile_release, | ||
929 | .fb_check_var = sh_mobile_check_var, | ||
828 | }; | 930 | }; |
829 | 931 | ||
830 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) | 932 | static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) |
@@ -964,9 +1066,13 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb, | |||
964 | case FB_EVENT_RESUME: | 1066 | case FB_EVENT_RESUME: |
965 | var = &info->var; | 1067 | var = &info->var; |
966 | 1068 | ||
1069 | mutex_lock(&ch->open_lock); | ||
1070 | sh_mobile_fb_reconfig(info); | ||
1071 | mutex_unlock(&ch->open_lock); | ||
1072 | |||
967 | /* HDMI must be enabled before LCDC configuration */ | 1073 | /* HDMI must be enabled before LCDC configuration */ |
968 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { | 1074 | if (try_module_get(board_cfg->owner) && board_cfg->display_on) { |
969 | board_cfg->display_on(board_cfg->board_data, ch->info); | 1075 | board_cfg->display_on(board_cfg->board_data, info); |
970 | module_put(board_cfg->owner); | 1076 | module_put(board_cfg->owner); |
971 | } | 1077 | } |
972 | 1078 | ||
@@ -1086,9 +1192,13 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) | |||
1086 | info = ch->info; | 1192 | info = ch->info; |
1087 | var = &info->var; | 1193 | var = &info->var; |
1088 | info->fbops = &sh_mobile_lcdc_ops; | 1194 | info->fbops = &sh_mobile_lcdc_ops; |
1195 | |||
1196 | mutex_init(&ch->open_lock); | ||
1197 | |||
1089 | fb_videomode_to_var(var, &cfg->lcd_cfg[0]); | 1198 | fb_videomode_to_var(var, &cfg->lcd_cfg[0]); |
1090 | /* Default Y virtual resolution is 2x panel size */ | 1199 | /* Default Y virtual resolution is 2x panel size */ |
1091 | var->yres_virtual = var->yres * 2; | 1200 | var->yres_virtual = var->yres * 2; |
1201 | var->activate = FB_ACTIVATE_NOW; | ||
1092 | 1202 | ||
1093 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); | 1203 | error = sh_mobile_lcdc_set_bpp(var, cfg->bpp); |
1094 | if (error) | 1204 | if (error) |
diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h index dfd3d766a556..9ecee2fba1d7 100644 --- a/drivers/video/sh_mobile_lcdcfb.h +++ b/drivers/video/sh_mobile_lcdcfb.h | |||
@@ -3,6 +3,7 @@ | |||
3 | 3 | ||
4 | #include <linux/completion.h> | 4 | #include <linux/completion.h> |
5 | #include <linux/fb.h> | 5 | #include <linux/fb.h> |
6 | #include <linux/mutex.h> | ||
6 | #include <linux/wait.h> | 7 | #include <linux/wait.h> |
7 | 8 | ||
8 | /* per-channel registers */ | 9 | /* per-channel registers */ |
@@ -33,6 +34,8 @@ struct sh_mobile_lcdc_chan { | |||
33 | wait_queue_head_t frame_end_wait; | 34 | wait_queue_head_t frame_end_wait; |
34 | struct completion vsync_completion; | 35 | struct completion vsync_completion; |
35 | struct fb_var_screeninfo display_var; | 36 | struct fb_var_screeninfo display_var; |
37 | int use_count; | ||
38 | struct mutex open_lock; /* protects the use counter */ | ||
36 | }; | 39 | }; |
37 | 40 | ||
38 | #endif | 41 | #endif |