2248 lines · 53.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* linux/drivers/video/sm501fb.c * * Copyright (c) 2006 Simtec Electronics *	Vincent Sanders <vince@simtec.co.uk> *	Ben Dooks <ben@simtec.co.uk> * * Framebuffer driver for the Silicon Motion SM501 */ #include <linux/module.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/string.h>#include <linux/mm.h>#include <linux/tty.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/fb.h>#include <linux/init.h>#include <linux/vmalloc.h>#include <linux/dma-mapping.h>#include <linux/interrupt.h>#include <linux/workqueue.h>#include <linux/wait.h>#include <linux/platform_device.h>#include <linux/clk.h>#include <linux/console.h>#include <linux/io.h>#include <linux/string_choices.h> #include <linux/uaccess.h>#include <asm/div64.h> #ifdef CONFIG_PM#include <linux/pm.h>#endif #include <linux/sm501.h>#include <linux/sm501-regs.h> #include "edid.h" static char *fb_mode = "640x480-16@60";static unsigned long default_bpp = 16; static const struct fb_videomode sm501_default_mode = {	.refresh	= 60,	.xres		= 640,	.yres		= 480,	.pixclock	= 20833,	.left_margin	= 142,	.right_margin	= 13,	.upper_margin	= 21,	.lower_margin	= 1,	.hsync_len	= 69,	.vsync_len	= 3,	.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,	.vmode		= FB_VMODE_NONINTERLACED}; #define NR_PALETTE	256 enum sm501_controller {	HEAD_CRT	= 0,	HEAD_PANEL	= 1,}; /* SM501 memory address. * * This structure is used to track memory usage within the SM501 framebuffer * allocation. The sm_addr field is stored as an offset as it is often used * against both the physical and mapped addresses. */struct sm501_mem {	unsigned long	 size;	unsigned long	 sm_addr;	/* offset from base of sm501 fb. */	void __iomem	*k_addr;}; /* private data that is shared between all frambuffers* */struct sm501fb_info {	struct device		*dev;	struct fb_info		*fb[2];		/* fb info for both heads */	struct resource		*fbmem_res;	/* framebuffer resource */	struct resource		*regs_res;	/* registers resource */	struct resource		*regs2d_res;	/* 2d registers resource */	struct sm501_platdata_fb *pdata;	/* our platform data */ 	unsigned long		 pm_crt_ctrl;	/* pm: crt ctrl save */ 	int			 irq;	int			 swap_endian;	/* set to swap rgb=>bgr */	void __iomem		*regs;		/* remapped registers */	void __iomem		*regs2d;	/* 2d remapped registers */	void __iomem		*fbmem;		/* remapped framebuffer */	size_t			 fbmem_len;	/* length of remapped region */	u8 *edid_data;	char *fb_mode;}; /* per-framebuffer private data */struct sm501fb_par {	u32			 pseudo_palette[16]; 	enum sm501_controller	 head;	struct sm501_mem	 cursor;	struct sm501_mem	 screen;	struct fb_ops		 ops; 	void			*store_fb;	void			*store_cursor;	void __iomem		*cursor_regs;	struct sm501fb_info	*info;}; /* Helper functions */ static inline int h_total(struct fb_var_screeninfo *var){	return var->xres + var->left_margin +		var->right_margin + var->hsync_len;} static inline int v_total(struct fb_var_screeninfo *var){	return var->yres + var->upper_margin +		var->lower_margin + var->vsync_len;} /* sm501fb_sync_regs() * * This call is mainly for PCI bus systems where we need to * ensure that any writes to the bus are completed before the * next phase, or after completing a function.*/ static inline void sm501fb_sync_regs(struct sm501fb_info *info){	smc501_readl(info->regs);} /* sm501_alloc_mem * * This is an attempt to lay out memory for the two framebuffers and * everything else * * |fbmem_res->start					       fbmem_res->end| * |									     | * |fb[0].fix.smem_start    |	      |fb[1].fix.smem_start    |     2K	     | * |-> fb[0].fix.smem_len <-| spare   |-> fb[1].fix.smem_len <-|-> cursors <-| * * The "spare" space is for the 2d engine data * the fixed is space for the cursors (2x1Kbyte) * * we need to allocate memory for the 2D acceleration engine * command list and the data for the engine to deal with. * * - all allocations must be 128bit aligned * - cursors are 64x64x2 bits (1Kbyte) * */ #define SM501_MEMF_CURSOR		(1)#define SM501_MEMF_PANEL		(2)#define SM501_MEMF_CRT			(4)#define SM501_MEMF_ACCEL		(8) static int sm501_alloc_mem(struct sm501fb_info *inf, struct sm501_mem *mem,			   unsigned int why, size_t size, u32 smem_len){	struct sm501fb_par *par;	struct fb_info *fbi;	unsigned int ptr;	unsigned int end; 	switch (why) {	case SM501_MEMF_CURSOR:		ptr = inf->fbmem_len - size;		inf->fbmem_len = ptr;	/* adjust available memory. */		break; 	case SM501_MEMF_PANEL:		if (size > inf->fbmem_len)			return -ENOMEM; 		ptr = inf->fbmem_len - size;		fbi = inf->fb[HEAD_CRT]; 		/* round down, some programs such as directfb do not draw		 * 0,0 correctly unless the start is aligned to a page start.		 */ 		if (ptr > 0)			ptr &= ~(PAGE_SIZE - 1); 		if (fbi && ptr < smem_len)			return -ENOMEM; 		break; 	case SM501_MEMF_CRT:		ptr = 0; 		/* check to see if we have panel memory allocated		 * which would put an limit on available memory. */ 		fbi = inf->fb[HEAD_PANEL];		if (fbi) {			par = fbi->par;			end = par->screen.k_addr ? par->screen.sm_addr : inf->fbmem_len;		} else			end = inf->fbmem_len; 		if ((ptr + size) > end)			return -ENOMEM; 		break; 	case SM501_MEMF_ACCEL:		fbi = inf->fb[HEAD_CRT];		ptr = fbi ? smem_len : 0; 		fbi = inf->fb[HEAD_PANEL];		if (fbi) {			par = fbi->par;			end = par->screen.sm_addr;		} else			end = inf->fbmem_len; 		if ((ptr + size) > end)			return -ENOMEM; 		break; 	default:		return -EINVAL;	} 	mem->size    = size;	mem->sm_addr = ptr;	mem->k_addr  = inf->fbmem + ptr; 	dev_dbg(inf->dev, "%s: result %08lx, %p - %u, %zd\n",		__func__, mem->sm_addr, mem->k_addr, why, size); 	return 0;} /* sm501fb_ps_to_hz * * Converts a period in picoseconds to Hz. * * Note, we try to keep this in Hz to minimise rounding with * the limited PLL settings on the SM501.*/ static unsigned long sm501fb_ps_to_hz(unsigned long psvalue){	unsigned long long numerator=1000000000000ULL; 	/* 10^12 / picosecond period gives frequency in Hz */	do_div(numerator, psvalue);	return (unsigned long)numerator;} /* sm501fb_hz_to_ps is identical to the opposite transform */ #define sm501fb_hz_to_ps(x) sm501fb_ps_to_hz(x) /* sm501fb_setup_gamma * * Programs a linear 1.0 gamma ramp in case the gamma * correction is enabled without programming anything else.*/ static void sm501fb_setup_gamma(struct sm501fb_info *fbi,				unsigned long palette){	unsigned long value = 0;	int offset; 	/* set gamma values */	for (offset = 0; offset < 256 * 4; offset += 4) {		smc501_writel(value, fbi->regs + palette + offset);		value += 0x010101; 	/* Advance RGB by 1,1,1.*/	}} /* sm501fb_check_var * * check common variables for both panel and crt*/ static int sm501fb_check_var(struct fb_var_screeninfo *var,			     struct fb_info *info){	struct sm501fb_par  *par = info->par;	struct sm501fb_info *sm  = par->info;	unsigned long tmp; 	/* check we can fit these values into the registers */ 	if (var->hsync_len > 255 || var->vsync_len > 63)		return -EINVAL; 	/* hdisplay end and hsync start */	if ((var->xres + var->right_margin) > 4096)		return -EINVAL; 	/* vdisplay end and vsync start */	if ((var->yres + var->lower_margin) > 2048)		return -EINVAL; 	/* hard limits of device */ 	if (h_total(var) > 4096 || v_total(var) > 2048)		return -EINVAL; 	/* check our line length is going to be 128 bit aligned */ 	tmp = (var->xres * var->bits_per_pixel) / 8;	if ((tmp & 15) != 0)		return -EINVAL; 	/* check the virtual size */ 	if (var->xres_virtual > 4096 || var->yres_virtual > 2048)		return -EINVAL; 	/* geometry sanity checks */	if (var->xres + var->xoffset > var->xres_virtual)		return -EINVAL; 	if (var->yres + var->yoffset > var->yres_virtual)		return -EINVAL; 	/* can cope with 8,16 or 32bpp */ 	if (var->bits_per_pixel <= 8)		var->bits_per_pixel = 8;	else if (var->bits_per_pixel <= 16)		var->bits_per_pixel = 16;	else if (var->bits_per_pixel == 24)		var->bits_per_pixel = 32; 	/* set r/g/b positions and validate bpp */	switch(var->bits_per_pixel) {	case 8:		var->red.length		= var->bits_per_pixel;		var->red.offset		= 0;		var->green.length	= var->bits_per_pixel;		var->green.offset	= 0;		var->blue.length	= var->bits_per_pixel;		var->blue.offset	= 0;		var->transp.length	= 0;		var->transp.offset	= 0; 		break; 	case 16:		if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {			var->blue.offset	= 11;			var->green.offset	= 5;			var->red.offset		= 0;		} else {			var->red.offset		= 11;			var->green.offset	= 5;			var->blue.offset	= 0;		}		var->transp.offset	= 0; 		var->red.length		= 5;		var->green.length	= 6;		var->blue.length	= 5;		var->transp.length	= 0;		break; 	case 32:		if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {			var->transp.offset	= 0;			var->red.offset		= 8;			var->green.offset	= 16;			var->blue.offset	= 24;		} else {			var->transp.offset	= 24;			var->red.offset		= 16;			var->green.offset	= 8;			var->blue.offset	= 0;		} 		var->red.length		= 8;		var->green.length	= 8;		var->blue.length	= 8;		var->transp.length	= 0;		break; 	default:		return -EINVAL;	} 	return 0;} /* * sm501fb_check_var_crt(): * * check the parameters for the CRT head, and either bring them * back into range, or return -EINVAL.*/ static int sm501fb_check_var_crt(struct fb_var_screeninfo *var,				 struct fb_info *info){	return sm501fb_check_var(var, info);} /* sm501fb_check_var_pnl(): * * check the parameters for the CRT head, and either bring them * back into range, or return -EINVAL.*/ static int sm501fb_check_var_pnl(struct fb_var_screeninfo *var,				 struct fb_info *info){	return sm501fb_check_var(var, info);} /* sm501fb_set_par_common * * set common registers for framebuffers*/ static int sm501fb_set_par_common(struct fb_info *info,				  struct fb_var_screeninfo *var){	struct sm501fb_par  *par = info->par;	struct sm501fb_info *fbi = par->info;	unsigned long pixclock;      /* pixelclock in Hz */	unsigned long sm501pixclock; /* pixelclock the 501 can achieve in Hz */	unsigned int mem_type;	unsigned int clock_type;	unsigned int head_addr;	unsigned int smem_len; 	dev_dbg(fbi->dev, "%s: %dx%d, bpp = %d, virtual %dx%d\n",		__func__, var->xres, var->yres, var->bits_per_pixel,		var->xres_virtual, var->yres_virtual); 	switch (par->head) {	case HEAD_CRT:		mem_type = SM501_MEMF_CRT;		clock_type = SM501_CLOCK_V2XCLK;		head_addr = SM501_DC_CRT_FB_ADDR;		break; 	case HEAD_PANEL:		mem_type = SM501_MEMF_PANEL;		clock_type = SM501_CLOCK_P2XCLK;		head_addr = SM501_DC_PANEL_FB_ADDR;		break; 	default:		mem_type = 0;		/* stop compiler warnings */		head_addr = 0;		clock_type = 0;	} 	switch (var->bits_per_pixel) {	case 8:		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;		break; 	case 16:		info->fix.visual = FB_VISUAL_TRUECOLOR;		break; 	case 32:		info->fix.visual = FB_VISUAL_TRUECOLOR;		break;	} 	/* allocate fb memory within 501 */	info->fix.line_length = (var->xres_virtual * var->bits_per_pixel)/8;	smem_len = info->fix.line_length * var->yres_virtual; 	dev_dbg(fbi->dev, "%s: line length = %u\n", __func__,		info->fix.line_length); 	if (sm501_alloc_mem(fbi, &par->screen, mem_type, smem_len, smem_len)) {		dev_err(fbi->dev, "no memory available\n");		return -ENOMEM;	} 	mutex_lock(&info->mm_lock);	info->fix.smem_start = fbi->fbmem_res->start + par->screen.sm_addr;	info->fix.smem_len   = smem_len;	mutex_unlock(&info->mm_lock); 

Lines 1–500 of 2248.