Commit dd210503 authored by Guennadi Liakhovetski's avatar Guennadi Liakhovetski Committed by Paul Mundt

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: default avatarGuennadi Liakhovetski <g.liakhovetski@gmx.de>
Signed-off-by: default avatarPaul Mundt <lethal@linux-sh.org>
parent 52d5ac00
......@@ -20,6 +20,7 @@
#include <linux/vmalloc.h>
#include <linux/ioctl.h>
#include <linux/slab.h>
#include <linux/console.h>
#include <video/sh_mobile_lcdc.h>
#include <asm/atomic.h>
......@@ -479,6 +480,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
m = 1 << 6;
tmp |= m << (lcdc_chan_is_sublcd(ch) ? 8 : 0);
/* FIXME: sh7724 can only use 42, 48, 54 and 60 for the divider denominator */
lcdc_write_chan(ch, LDDCKPAT1R, 0);
lcdc_write_chan(ch, LDDCKPAT2R, (1 << (m/2)) - 1);
}
......@@ -815,6 +817,103 @@ static int sh_mobile_ioctl(struct fb_info *info, unsigned int cmd,
return retval;
}
static void sh_mobile_fb_reconfig(struct fb_info *info)
{
struct sh_mobile_lcdc_chan *ch = info->par;
struct fb_videomode mode1, mode2;
struct fb_event event;
int evnt = FB_EVENT_MODE_CHANGE_ALL;
if (ch->use_count > 1 || (ch->use_count == 1 && !info->fbcon_par))
/* More framebuffer users are active */
return;
fb_var_to_videomode(&mode1, &ch->display_var);
fb_var_to_videomode(&mode2, &info->var);
if (fb_mode_is_equal(&mode1, &mode2))
return;
/* Display has been re-plugged, framebuffer is free now, reconfigure */
if (fb_set_var(info, &ch->display_var) < 0)
/* Couldn't reconfigure, hopefully, can continue as before */
return;
info->fix.line_length = mode2.xres * (ch->cfg.bpp / 8);
/*
* fb_set_var() calls the notifier change internally, only if
* FBINFO_MISC_USEREVENT flag is set. Since we do not want to fake a
* user event, we have to call the chain ourselves.
*/
event.info = info;
event.data = &mode2;
fb_notifier_call_chain(evnt, &event);
}
/*
* Locking: both .fb_release() and .fb_open() are called with info->lock held if
* user == 1, or with console sem held, if user == 0.
*/
static int sh_mobile_release(struct fb_info *info, int user)
{
struct sh_mobile_lcdc_chan *ch = info->par;
mutex_lock(&ch->open_lock);
dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
ch->use_count--;
/* Nothing to reconfigure, when called from fbcon */
if (user) {
acquire_console_sem();
sh_mobile_fb_reconfig(info);
release_console_sem();
}
mutex_unlock(&ch->open_lock);
return 0;
}
static int sh_mobile_open(struct fb_info *info, int user)
{
struct sh_mobile_lcdc_chan *ch = info->par;
mutex_lock(&ch->open_lock);
ch->use_count++;
dev_dbg(info->dev, "%s(): %d users\n", __func__, ch->use_count);
mutex_unlock(&ch->open_lock);
return 0;
}
static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
struct sh_mobile_lcdc_chan *ch = info->par;
if (var->xres < 160 || var->xres > 1920 ||
var->yres < 120 || var->yres > 1080 ||
var->left_margin < 32 || var->left_margin > 320 ||
var->right_margin < 12 || var->right_margin > 240 ||
var->upper_margin < 12 || var->upper_margin > 120 ||
var->lower_margin < 1 || var->lower_margin > 64 ||
var->hsync_len < 32 || var->hsync_len > 120 ||
var->vsync_len < 2 || var->vsync_len > 64 ||
var->pixclock < 6000 || var->pixclock > 40000 ||
var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) {
dev_warn(info->dev, "Invalid info: %u %u %u %u %u %u %u %u %u!\n",
var->xres, var->yres,
var->left_margin, var->right_margin,
var->upper_margin, var->lower_margin,
var->hsync_len, var->vsync_len,
var->pixclock);
return -EINVAL;
}
return 0;
}
static struct fb_ops sh_mobile_lcdc_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = sh_mobile_lcdc_setcolreg,
......@@ -825,6 +924,9 @@ static struct fb_ops sh_mobile_lcdc_ops = {
.fb_imageblit = sh_mobile_lcdc_imageblit,
.fb_pan_display = sh_mobile_fb_pan_display,
.fb_ioctl = sh_mobile_ioctl,
.fb_open = sh_mobile_open,
.fb_release = sh_mobile_release,
.fb_check_var = sh_mobile_check_var,
};
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,
case FB_EVENT_RESUME:
var = &info->var;
mutex_lock(&ch->open_lock);
sh_mobile_fb_reconfig(info);
mutex_unlock(&ch->open_lock);
/* HDMI must be enabled before LCDC configuration */
if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
board_cfg->display_on(board_cfg->board_data, ch->info);
board_cfg->display_on(board_cfg->board_data, info);
module_put(board_cfg->owner);
}
......@@ -1086,9 +1192,13 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev)
info = ch->info;
var = &info->var;
info->fbops = &sh_mobile_lcdc_ops;
mutex_init(&ch->open_lock);
fb_videomode_to_var(var, &cfg->lcd_cfg[0]);
/* Default Y virtual resolution is 2x panel size */
var->yres_virtual = var->yres * 2;
var->activate = FB_ACTIVATE_NOW;
error = sh_mobile_lcdc_set_bpp(var, cfg->bpp);
if (error)
......
......@@ -3,6 +3,7 @@
#include <linux/completion.h>
#include <linux/fb.h>
#include <linux/mutex.h>
#include <linux/wait.h>
/* per-channel registers */
......@@ -33,6 +34,8 @@ struct sh_mobile_lcdc_chan {
wait_queue_head_t frame_end_wait;
struct completion vsync_completion;
struct fb_var_screeninfo display_var;
int use_count;
struct mutex open_lock; /* protects the use counter */
};
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment