atmel_lcdfb.c

来自「linux 内核源代码」· C语言 代码 · 共 806 行 · 第 1/2 页

C
806
字号
/* *  Driver for AT91/AT32 LCD Controller * *  Copyright (C) 2007 Atmel Corporation * * 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/kernel.h>#include <linux/platform_device.h>#include <linux/dma-mapping.h>#include <linux/interrupt.h>#include <linux/clk.h>#include <linux/fb.h>#include <linux/init.h>#include <linux/delay.h>#include <asm/arch/board.h>#include <asm/arch/cpu.h>#include <asm/arch/gpio.h>#include <video/atmel_lcdc.h>#define lcdc_readl(sinfo, reg)		__raw_readl((sinfo)->mmio+(reg))#define lcdc_writel(sinfo, reg, val)	__raw_writel((val), (sinfo)->mmio+(reg))/* configurable parameters */#define ATMEL_LCDC_CVAL_DEFAULT		0xc8#define ATMEL_LCDC_DMA_BURST_LEN	8#if defined(CONFIG_ARCH_AT91SAM9263)#define ATMEL_LCDC_FIFO_SIZE		2048#else#define ATMEL_LCDC_FIFO_SIZE		512#endif#if defined(CONFIG_ARCH_AT91)#define	ATMEL_LCDFB_FBINFO_DEFAULT	FBINFO_DEFAULTstatic inline void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo,					struct fb_var_screeninfo *var){}#elif defined(CONFIG_AVR32)#define	ATMEL_LCDFB_FBINFO_DEFAULT	(FBINFO_DEFAULT \					| FBINFO_PARTIAL_PAN_OK \					| FBINFO_HWACCEL_XPAN \					| FBINFO_HWACCEL_YPAN)static void atmel_lcdfb_update_dma2d(struct atmel_lcdfb_info *sinfo,				     struct fb_var_screeninfo *var){	u32 dma2dcfg;	u32 pixeloff;	pixeloff = (var->xoffset * var->bits_per_pixel) & 0x1f;	dma2dcfg = ((var->xres_virtual - var->xres) * var->bits_per_pixel) / 8;	dma2dcfg |= pixeloff << ATMEL_LCDC_PIXELOFF_OFFSET;	lcdc_writel(sinfo, ATMEL_LCDC_DMA2DCFG, dma2dcfg);	/* Update configuration */	lcdc_writel(sinfo, ATMEL_LCDC_DMACON,		    lcdc_readl(sinfo, ATMEL_LCDC_DMACON)		    | ATMEL_LCDC_DMAUPDT);}#endifstatic struct fb_fix_screeninfo atmel_lcdfb_fix __initdata = {	.type		= FB_TYPE_PACKED_PIXELS,	.visual		= FB_VISUAL_TRUECOLOR,	.xpanstep	= 0,	.ypanstep	= 0,	.ywrapstep	= 0,	.accel		= FB_ACCEL_NONE,};static unsigned long compute_hozval(unsigned long xres, unsigned long lcdcon2){	unsigned long value;	if (!(cpu_is_at91sam9261() || cpu_is_at32ap7000()))		return xres;	value = xres;	if ((lcdcon2 & ATMEL_LCDC_DISTYPE) != ATMEL_LCDC_DISTYPE_TFT) {		/* STN display */		if ((lcdcon2 & ATMEL_LCDC_DISTYPE) == ATMEL_LCDC_DISTYPE_STNCOLOR) {			value *= 3;		}		if ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_4		   || ( (lcdcon2 & ATMEL_LCDC_IFWIDTH) == ATMEL_LCDC_IFWIDTH_8		      && (lcdcon2 & ATMEL_LCDC_SCANMOD) == ATMEL_LCDC_SCANMOD_DUAL ))			value = DIV_ROUND_UP(value, 4);		else			value = DIV_ROUND_UP(value, 8);	}	return value;}static void atmel_lcdfb_update_dma(struct fb_info *info,			       struct fb_var_screeninfo *var){	struct atmel_lcdfb_info *sinfo = info->par;	struct fb_fix_screeninfo *fix = &info->fix;	unsigned long dma_addr;	dma_addr = (fix->smem_start + var->yoffset * fix->line_length		    + var->xoffset * var->bits_per_pixel / 8);	dma_addr &= ~3UL;	/* Set framebuffer DMA base address and pixel offset */	lcdc_writel(sinfo, ATMEL_LCDC_DMABADDR1, dma_addr);	atmel_lcdfb_update_dma2d(sinfo, var);}static inline void atmel_lcdfb_free_video_memory(struct atmel_lcdfb_info *sinfo){	struct fb_info *info = sinfo->info;	dma_free_writecombine(info->device, info->fix.smem_len,				info->screen_base, info->fix.smem_start);}/** *	atmel_lcdfb_alloc_video_memory - Allocate framebuffer memory *	@sinfo: the frame buffer to allocate memory for */static int atmel_lcdfb_alloc_video_memory(struct atmel_lcdfb_info *sinfo){	struct fb_info *info = sinfo->info;	struct fb_var_screeninfo *var = &info->var;	info->fix.smem_len = (var->xres_virtual * var->yres_virtual			    * ((var->bits_per_pixel + 7) / 8));	info->screen_base = dma_alloc_writecombine(info->device, info->fix.smem_len,					(dma_addr_t *)&info->fix.smem_start, GFP_KERNEL);	if (!info->screen_base) {		return -ENOMEM;	}	return 0;}/** *      atmel_lcdfb_check_var - Validates a var passed in. *      @var: frame buffer variable screen structure *      @info: frame buffer structure that represents a single frame buffer * *	Checks to see if the hardware supports the state requested by *	var passed in. This function does not alter the hardware *	state!!!  This means the data stored in struct fb_info and *	struct atmel_lcdfb_info do not change. This includes the var *	inside of struct fb_info.  Do NOT change these. This function *	can be called on its own if we intent to only test a mode and *	not actually set it. The stuff in modedb.c is a example of *	this. If the var passed in is slightly off by what the *	hardware can support then we alter the var PASSED in to what *	we can do. If the hardware doesn't support mode change a *	-EINVAL will be returned by the upper layers. You don't need *	to implement this function then. If you hardware doesn't *	support changing the resolution then this function is not *	needed. In this case the driver would just provide a var that *	represents the static state the screen is in. * *	Returns negative errno on error, or zero on success. */static int atmel_lcdfb_check_var(struct fb_var_screeninfo *var,			     struct fb_info *info){	struct device *dev = info->device;	struct atmel_lcdfb_info *sinfo = info->par;	unsigned long clk_value_khz;	clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000;	dev_dbg(dev, "%s:\n", __func__);	dev_dbg(dev, "  resolution: %ux%u\n", var->xres, var->yres);	dev_dbg(dev, "  pixclk:     %lu KHz\n", PICOS2KHZ(var->pixclock));	dev_dbg(dev, "  bpp:        %u\n", var->bits_per_pixel);	dev_dbg(dev, "  clk:        %lu KHz\n", clk_value_khz);	if ((PICOS2KHZ(var->pixclock) * var->bits_per_pixel / 8) > clk_value_khz) {		dev_err(dev, "%lu KHz pixel clock is too fast\n", PICOS2KHZ(var->pixclock));		return -EINVAL;	}	/* Force same alignment for each line */	var->xres = (var->xres + 3) & ~3UL;	var->xres_virtual = (var->xres_virtual + 3) & ~3UL;	var->red.msb_right = var->green.msb_right = var->blue.msb_right = 0;	var->transp.msb_right = 0;	var->transp.offset = var->transp.length = 0;	var->xoffset = var->yoffset = 0;	switch (var->bits_per_pixel) {	case 1:	case 2:	case 4:	case 8:		var->red.offset = var->green.offset = var->blue.offset = 0;		var->red.length = var->green.length = var->blue.length			= var->bits_per_pixel;		break;	case 15:	case 16:		var->red.offset = 0;		var->green.offset = 5;		var->blue.offset = 10;		var->red.length = var->green.length = var->blue.length = 5;		break;	case 32:		var->transp.offset = 24;		var->transp.length = 8;		/* fall through */	case 24:		var->red.offset = 0;		var->green.offset = 8;		var->blue.offset = 16;		var->red.length = var->green.length = var->blue.length = 8;		break;	default:		dev_err(dev, "color depth %d not supported\n",					var->bits_per_pixel);		return -EINVAL;	}	return 0;}/** *      atmel_lcdfb_set_par - Alters the hardware state. *      @info: frame buffer structure that represents a single frame buffer * *	Using the fb_var_screeninfo in fb_info we set the resolution *	of the this particular framebuffer. This function alters the *	par AND the fb_fix_screeninfo stored in fb_info. It doesn't *	not alter var in fb_info since we are using that data. This *	means we depend on the data in var inside fb_info to be *	supported by the hardware.  atmel_lcdfb_check_var is always called *	before atmel_lcdfb_set_par to ensure this.  Again if you can't *	change the resolution you don't need this function. * */static int atmel_lcdfb_set_par(struct fb_info *info){	struct atmel_lcdfb_info *sinfo = info->par;	unsigned long hozval_linesz;	unsigned long value;	unsigned long clk_value_khz;	unsigned long bits_per_line;	dev_dbg(info->device, "%s:\n", __func__);	dev_dbg(info->device, "  * resolution: %ux%u (%ux%u virtual)\n",		 info->var.xres, info->var.yres,		 info->var.xres_virtual, info->var.yres_virtual);	/* Turn off the LCD controller and the DMA controller */	lcdc_writel(sinfo, ATMEL_LCDC_PWRCON, sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET);	/* Wait for the LCDC core to become idle */	while (lcdc_readl(sinfo, ATMEL_LCDC_PWRCON) & ATMEL_LCDC_BUSY)		msleep(10);	lcdc_writel(sinfo, ATMEL_LCDC_DMACON, 0);	if (info->var.bits_per_pixel == 1)		info->fix.visual = FB_VISUAL_MONO01;	else if (info->var.bits_per_pixel <= 8)		info->fix.visual = FB_VISUAL_PSEUDOCOLOR;	else		info->fix.visual = FB_VISUAL_TRUECOLOR;	bits_per_line = info->var.xres_virtual * info->var.bits_per_pixel;	info->fix.line_length = DIV_ROUND_UP(bits_per_line, 8);	/* Re-initialize the DMA engine... */	dev_dbg(info->device, "  * update DMA engine\n");	atmel_lcdfb_update_dma(info, &info->var);	/* ...set frame size and burst length = 8 words (?) */	value = (info->var.yres * info->var.xres * info->var.bits_per_pixel) / 32;	value |= ((ATMEL_LCDC_DMA_BURST_LEN - 1) << ATMEL_LCDC_BLENGTH_OFFSET);	lcdc_writel(sinfo, ATMEL_LCDC_DMAFRMCFG, value);	/* Now, the LCDC core... */	/* Set pixel clock */	clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000;	value = DIV_ROUND_UP(clk_value_khz, PICOS2KHZ(info->var.pixclock));	value = (value / 2) - 1;	dev_dbg(info->device, "  * programming CLKVAL = 0x%08lx\n", value);	if (value <= 0) {		dev_notice(info->device, "Bypassing pixel clock divider\n");		lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, ATMEL_LCDC_BYPASS);	} else {		lcdc_writel(sinfo, ATMEL_LCDC_LCDCON1, value << ATMEL_LCDC_CLKVAL_OFFSET);		info->var.pixclock = KHZ2PICOS(clk_value_khz / (2 * (value + 1)));		dev_dbg(info->device, "  updated pixclk:     %lu KHz\n",					PICOS2KHZ(info->var.pixclock));	}	/* Initialize control register 2 */	value = sinfo->default_lcdcon2;	if (!(info->var.sync & FB_SYNC_HOR_HIGH_ACT))		value |= ATMEL_LCDC_INVLINE_INVERTED;	if (!(info->var.sync & FB_SYNC_VERT_HIGH_ACT))		value |= ATMEL_LCDC_INVFRAME_INVERTED;	switch (info->var.bits_per_pixel) {		case 1:	value |= ATMEL_LCDC_PIXELSIZE_1; break;		case 2: value |= ATMEL_LCDC_PIXELSIZE_2; break;		case 4: value |= ATMEL_LCDC_PIXELSIZE_4; break;		case 8: value |= ATMEL_LCDC_PIXELSIZE_8; break;		case 15: /* fall through */		case 16: value |= ATMEL_LCDC_PIXELSIZE_16; break;		case 24: value |= ATMEL_LCDC_PIXELSIZE_24; break;		case 32: value |= ATMEL_LCDC_PIXELSIZE_32; break;		default: BUG(); break;	}	dev_dbg(info->device, "  * LCDCON2 = %08lx\n", value);	lcdc_writel(sinfo, ATMEL_LCDC_LCDCON2, value);	/* Vertical timing */	value = (info->var.vsync_len - 1) << ATMEL_LCDC_VPW_OFFSET;	value |= info->var.upper_margin << ATMEL_LCDC_VBP_OFFSET;	value |= info->var.lower_margin;	dev_dbg(info->device, "  * LCDTIM1 = %08lx\n", value);	lcdc_writel(sinfo, ATMEL_LCDC_TIM1, value);	/* Horizontal timing */	value = (info->var.right_margin - 1) << ATMEL_LCDC_HFP_OFFSET;	value |= (info->var.hsync_len - 1) << ATMEL_LCDC_HPW_OFFSET;	value |= (info->var.left_margin - 1);	dev_dbg(info->device, "  * LCDTIM2 = %08lx\n", value);	lcdc_writel(sinfo, ATMEL_LCDC_TIM2, value);	/* Horizontal value (aka line size) */	hozval_linesz = compute_hozval(info->var.xres,					lcdc_readl(sinfo, ATMEL_LCDC_LCDCON2));	/* Display size */	value = (hozval_linesz - 1) << ATMEL_LCDC_HOZVAL_OFFSET;	value |= info->var.yres - 1;	dev_dbg(info->device, "  * LCDFRMCFG = %08lx\n", value);	lcdc_writel(sinfo, ATMEL_LCDC_LCDFRMCFG, value);	/* FIFO Threshold: Use formula from data sheet */	value = ATMEL_LCDC_FIFO_SIZE - (2 * ATMEL_LCDC_DMA_BURST_LEN + 3);	lcdc_writel(sinfo, ATMEL_LCDC_FIFO, value);	/* Toggle LCD_MODE every frame */	lcdc_writel(sinfo, ATMEL_LCDC_MVAL, 0);	/* Disable all interrupts */	lcdc_writel(sinfo, ATMEL_LCDC_IDR, ~0UL);	/* Set contrast */	value = ATMEL_LCDC_PS_DIV8 | ATMEL_LCDC_POL_POSITIVE | ATMEL_LCDC_ENA_PWMENABLE;	lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_CTR, value);	lcdc_writel(sinfo, ATMEL_LCDC_CONTRAST_VAL, ATMEL_LCDC_CVAL_DEFAULT);	/* ...wait for DMA engine to become idle... */	while (lcdc_readl(sinfo, ATMEL_LCDC_DMACON) & ATMEL_LCDC_DMABUSY)		msleep(10);	dev_dbg(info->device, "  * re-enable DMA engine\n");	/* ...and enable it with updated configuration */	lcdc_writel(sinfo, ATMEL_LCDC_DMACON, sinfo->default_dmacon);	dev_dbg(info->device, "  * re-enable LCDC core\n");	lcdc_writel(sinfo, ATMEL_LCDC_PWRCON,		(sinfo->guard_time << ATMEL_LCDC_GUARDT_OFFSET) | ATMEL_LCDC_PWR);	dev_dbg(info->device, "  * DONE\n");	return 0;}static inline unsigned int chan_to_field(unsigned int chan, const struct fb_bitfield *bf){	chan &= 0xffff;	chan >>= 16 - bf->length;	return chan << bf->offset;}/** *  	atmel_lcdfb_setcolreg - Optional function. Sets a color register. *      @regno: Which register in the CLUT we are programming

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?