Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+111 −1
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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