diff options
| -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 |
