802 lines · 18.1 KB · C
RawDownload
/* *  linux/drivers/video/fbmem.c * *  Copyright (C) 1994 Martin Schaller * *	2001 - Documented with DocBook *	- Brad Douglas <brad@neruo.com> * * This file is subject to the terms and conditions of the GNU General Public * License.  See the file COPYING in the main directory of this archive * for more details. */ #include <linux/console.h>#include <linux/export.h>#include <linux/fb.h>#include <linux/lcd.h>#include <linux/leds.h> #include <video/nomodeset.h> #include "fb_internal.h"#include "fbcon.h"     /*     *  Frame buffer device initialization and setup routines     */ #define FBPIXMAPSIZE	(1024 * 8) struct class *fb_class; DEFINE_MUTEX(registration_lock);struct fb_info *registered_fb[FB_MAX] __read_mostly;int num_registered_fb __read_mostly;#define for_each_registered_fb(i)		\	for (i = 0; i < FB_MAX; i++)		\		if (!registered_fb[i]) {} else struct fb_info *get_fb_info(unsigned int idx){	struct fb_info *fb_info; 	if (idx >= FB_MAX)		return ERR_PTR(-ENODEV); 	mutex_lock(&registration_lock);	fb_info = registered_fb[idx];	if (fb_info)		refcount_inc(&fb_info->count);	mutex_unlock(&registration_lock); 	return fb_info;} void put_fb_info(struct fb_info *fb_info){	if (!refcount_dec_and_test(&fb_info->count))		return;	if (fb_info->fbops->fb_destroy)		fb_info->fbops->fb_destroy(fb_info);} /* * Helpers */ int fb_get_color_depth(struct fb_var_screeninfo *var,		       struct fb_fix_screeninfo *fix){	int depth = 0; 	if (fix->visual == FB_VISUAL_MONO01 ||	    fix->visual == FB_VISUAL_MONO10)		depth = 1;	else {		if (var->green.length == var->blue.length &&		    var->green.length == var->red.length &&		    var->green.offset == var->blue.offset &&		    var->green.offset == var->red.offset)			depth = var->green.length;		else			depth = var->green.length + var->red.length +				var->blue.length;	} 	return depth;}EXPORT_SYMBOL(fb_get_color_depth); /* * Data padding functions. */void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 s_pitch, u32 height){	__fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height);}EXPORT_SYMBOL(fb_pad_aligned_buffer); void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, const u8 *src, u32 idx, u32 height,			     u32 shift_high, u32 shift_low, u32 mod){	u8 mask = (u8) (0xff << shift_high), tmp;	int i, j; 	for (i = height; i--; ) {		for (j = 0; j < idx; j++) {			tmp = dst[j];			tmp &= mask;			tmp |= *src >> shift_low;			dst[j] = tmp;			tmp = *src << shift_high;			dst[j+1] = tmp;			src++;		}		tmp = dst[idx];		tmp &= mask;		tmp |= *src >> shift_low;		dst[idx] = tmp;		if (shift_high < mod) {			tmp = *src << shift_high;			dst[idx+1] = tmp;		}		src++;		dst += d_pitch;	}}EXPORT_SYMBOL(fb_pad_unaligned_buffer); /* * we need to lock this section since fb_cursor * may use fb_imageblit() */char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size){	u32 align = buf->buf_align - 1, offset;	char *addr = buf->addr; 	/* If IO mapped, we need to sync before access, no sharing of	 * the pixmap is done	 */	if (buf->flags & FB_PIXMAP_IO) {		if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))			info->fbops->fb_sync(info);		return addr;	} 	/* See if we fit in the remaining pixmap space */	offset = buf->offset + align;	offset &= ~align;	if (offset + size > buf->size) {		/* We do not fit. In order to be able to re-use the buffer,		 * we must ensure no asynchronous DMA'ing or whatever operation		 * is in progress, we sync for that.		 */		if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC))			info->fbops->fb_sync(info);		offset = 0;	}	buf->offset = offset + size;	addr += offset; 	return addr;}EXPORT_SYMBOL(fb_get_buffer_offset); intfb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var){	struct fb_fix_screeninfo *fix = &info->fix;	unsigned int yres = info->var.yres;	int err = 0; 	if (var->yoffset > 0) {		if (var->vmode & FB_VMODE_YWRAP) {			if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep))				err = -EINVAL;			else				yres = 0;		} else if (!fix->ypanstep || (var->yoffset % fix->ypanstep))			err = -EINVAL;	} 	if (var->xoffset > 0 && (!fix->xpanstep ||				 (var->xoffset % fix->xpanstep)))		err = -EINVAL; 	if (err || !info->fbops->fb_pan_display ||	    var->yoffset > info->var.yres_virtual - yres ||	    var->xoffset > info->var.xres_virtual - info->var.xres)		return -EINVAL; 	if ((err = info->fbops->fb_pan_display(var, info)))		return err;	info->var.xoffset = var->xoffset;	info->var.yoffset = var->yoffset;	if (var->vmode & FB_VMODE_YWRAP)		info->var.vmode |= FB_VMODE_YWRAP;	else		info->var.vmode &= ~FB_VMODE_YWRAP;	return 0;}EXPORT_SYMBOL(fb_pan_display); static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var,			 u32 activate){	struct fb_blit_caps caps, fbcaps;	int err = 0; 	memset(&caps, 0, sizeof(caps));	memset(&fbcaps, 0, sizeof(fbcaps));	caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0;	fbcon_get_requirement(info, &caps);	info->fbops->fb_get_caps(info, &fbcaps, var); 	if (!bitmap_subset(caps.x, fbcaps.x, FB_MAX_BLIT_WIDTH) ||	    !bitmap_subset(caps.y, fbcaps.y, FB_MAX_BLIT_HEIGHT) ||	    (fbcaps.len < caps.len))		err = -EINVAL; 	return err;} static void fb_lcd_notify_mode_change(struct fb_info *info,				      struct fb_videomode *mode){	lcd_notify_mode_change_all(info->device, mode->xres, mode->yres);} intfb_set_var(struct fb_info *info, struct fb_var_screeninfo *var){	int ret = 0;	u32 activate;	struct fb_var_screeninfo old_var;	struct fb_videomode mode;	u32 unused; 	if (var->activate & FB_ACTIVATE_INV_MODE) {		struct fb_videomode mode1, mode2; 		fb_var_to_videomode(&mode1, var);		fb_var_to_videomode(&mode2, &info->var);		/* make sure we don't delete the videomode of current var */		ret = fb_mode_is_equal(&mode1, &mode2);		if (!ret) {			ret = fbcon_mode_deleted(info, &mode1);			if (!ret) {				if (info->mode && fb_mode_is_equal(info->mode, &mode1))					info->mode = NULL;				fb_delete_videomode(&mode1, &info->modelist);			}		} 		return ret ? -EINVAL : 0;	} 	if (!(var->activate & FB_ACTIVATE_FORCE) &&	    !memcmp(&info->var, var, sizeof(struct fb_var_screeninfo)))		return 0; 	activate = var->activate; 	/* When using FOURCC mode, make sure the red, green, blue and	 * transp fields are set to 0.	 */	if ((info->fix.capabilities & FB_CAP_FOURCC) &&	    var->grayscale > 1) {		if (var->red.offset     || var->green.offset    ||		    var->blue.offset    || var->transp.offset   ||		    var->red.length     || var->green.length    ||		    var->blue.length    || var->transp.length   ||		    var->red.msb_right  || var->green.msb_right ||		    var->blue.msb_right || var->transp.msb_right)			return -EINVAL;	} 	if (!info->fbops->fb_check_var) {		*var = info->var;		return 0;	} 	/* bitfill_aligned() assumes that it's at least 8x8 */	if (var->xres < 8 || var->yres < 8)		return -EINVAL; 	/* Too huge resolution causes multiplication overflow. */	if (check_mul_overflow(var->xres, var->yres, &unused) ||	    check_mul_overflow(var->xres_virtual, var->yres_virtual, &unused))		return -EINVAL; 	ret = info->fbops->fb_check_var(var, info); 	if (ret)		return ret; 	/* verify that virtual resolution >= physical resolution */	if (var->xres_virtual < var->xres ||	    var->yres_virtual < var->yres) {		pr_warn("WARNING: fbcon: Driver '%s' missed to adjust virtual screen size (%ux%u vs. %ux%u)\n",			info->fix.id,			var->xres_virtual, var->yres_virtual,			var->xres, var->yres);		return -EINVAL;	} 	if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW)		return 0; 	if (info->fbops->fb_get_caps) {		ret = fb_check_caps(info, var, activate); 		if (ret)			return ret;	} 	old_var = info->var;	info->var = *var; 	if (info->fbops->fb_set_par) {		ret = info->fbops->fb_set_par(info); 		if (ret) {			info->var = old_var;			printk(KERN_WARNING "detected "				"fb_set_par error, "				"error code: %d\n", ret);			return ret;		}	} 	fb_pan_display(info, &info->var);	fb_set_cmap(&info->cmap, info);	fb_var_to_videomode(&mode, &info->var); 	if (info->modelist.prev && info->modelist.next &&	    !list_empty(&info->modelist))		ret = fb_add_videomode(&mode, &info->modelist); 	if (ret) {		info->var = old_var;		return ret;	} 	fb_lcd_notify_mode_change(info, &mode); 	return 0;}EXPORT_SYMBOL(fb_set_var); int fb_set_var_from_user(struct fb_info *info, struct fb_var_screeninfo *var){	int ret = fbcon_modechange_possible(info, var); 	if (!ret)		ret = fb_set_var(info, var);	if (!ret)		fbcon_update_vcs(info, var->activate & FB_ACTIVATE_ALL); 	return ret;}EXPORT_SYMBOL(fb_set_var_from_user); static void fb_lcd_notify_blank(struct fb_info *info){	int power; 	switch (info->blank) {	case FB_BLANK_UNBLANK:		power = LCD_POWER_ON;		break;	/* deprecated; TODO: should become 'off' */	case FB_BLANK_NORMAL:		power = LCD_POWER_REDUCED;		break;	case FB_BLANK_VSYNC_SUSPEND:		power = LCD_POWER_REDUCED_VSYNC_SUSPEND;		break;	/* 'off' */	case FB_BLANK_HSYNC_SUSPEND:	case FB_BLANK_POWERDOWN:	default:		power = LCD_POWER_OFF;		break;	} 	lcd_notify_blank_all(info->device, power);} static void fb_ledtrig_backlight_notify_blank(struct fb_info *info){	if (info->blank == FB_BLANK_UNBLANK)		ledtrig_backlight_blank(false);	else		ledtrig_backlight_blank(true);} int fb_blank(struct fb_info *info, int blank){	int old_blank = info->blank;	int ret; 	if (!info->fbops->fb_blank)		return -EINVAL; 	if (blank > FB_BLANK_POWERDOWN)		blank = FB_BLANK_POWERDOWN; 	info->blank = blank; 	ret = info->fbops->fb_blank(blank, info);	if (ret)		goto err; 	fb_bl_notify_blank(info, old_blank);	fb_lcd_notify_blank(info);	fb_ledtrig_backlight_notify_blank(info); 	return 0; err:	info->blank = old_blank;	return ret;}EXPORT_SYMBOL(fb_blank); int fb_blank_from_user(struct fb_info *info, int blank){	int ret = fb_blank(info, blank); 	/* might again call into fb_blank */	fbcon_fb_blanked(info, blank); 	return ret;} static int fb_check_foreignness(struct fb_info *fi){	const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; 	fi->flags &= ~FBINFO_FOREIGN_ENDIAN; #ifdef __BIG_ENDIAN	fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH;#else	fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0;#endif /* __BIG_ENDIAN */ 	if (fi->flags & FBINFO_BE_MATH && !fb_be_math(fi)) {		pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to "		       "support this framebuffer\n", fi->fix.id);		return -ENOSYS;	} else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(fi)) {		pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to "		       "support this framebuffer\n", fi->fix.id);		return -ENOSYS;	} 	return 0;} static int do_register_framebuffer(struct fb_info *fb_info){	int i, err = 0;	struct fb_videomode mode; 	if (fb_check_foreignness(fb_info))		return -ENOSYS; 	if (num_registered_fb == FB_MAX)		return -ENXIO; 	for (i = 0 ; i < FB_MAX; i++)		if (!registered_fb[i])			break; 	if (i >= FB_MAX)		return -ENXIO; 	if (!fb_info->modelist.prev || !fb_info->modelist.next)		INIT_LIST_HEAD(&fb_info->modelist); 	fb_var_to_videomode(&mode, &fb_info->var);	err = fb_add_videomode(&mode, &fb_info->modelist);	if (err < 0)		return err; 	fb_info->node = i;	refcount_set(&fb_info->count, 1);	mutex_init(&fb_info->lock);	mutex_init(&fb_info->mm_lock); 	/*	 * With an fb_blank callback present, we assume that the	 * display is blank, so that fb_blank() enables it on the	 * first modeset.	 */	if (fb_info->fbops->fb_blank)		fb_info->blank = FB_BLANK_POWERDOWN;

Lines 1–500 of 802.