1316 lines · 33.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * i740fb - framebuffer driver for Intel740 * Copyright (c) 2011 Ondrej Zary * * Based on old i740fb driver (c) 2001-2002 Andrey Ulanov <drey@rt.mipt.ru> * which was partially based on: *  VGA 16-color framebuffer driver (c) 1999 Ben Pfaff <pfaffben@debian.org> *	and Petr Vandrovec <VANDROVE@vc.cvut.cz> *  i740 driver from XFree86 (c) 1998-1999 Precision Insight, Inc., Cedar Park, *	Texas. *  i740fb by Patrick LERDA, v0.9 */ #include <linux/aperture.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/errno.h>#include <linux/string.h>#include <linux/mm.h>#include <linux/slab.h>#include <linux/delay.h>#include <linux/fb.h>#include <linux/init.h>#include <linux/pci.h>#include <linux/pci_ids.h>#include <linux/i2c.h>#include <linux/i2c-algo-bit.h>#include <linux/console.h>#include <video/vga.h> #include "i740_reg.h" static char *mode_option;static int mtrr = 1; struct i740fb_par {	unsigned char __iomem *regs;	bool has_sgram;	int wc_cookie;	bool ddc_registered;	struct i2c_adapter ddc_adapter;	struct i2c_algo_bit_data ddc_algo;	u32 pseudo_palette[16];	struct mutex open_lock;	unsigned int ref_count; 	u8 crtc[VGA_CRT_C];	u8 atc[VGA_ATT_C];	u8 gdc[VGA_GFX_C];	u8 seq[VGA_SEQ_C];	u8 misc;	u8 vss; 	/* i740 specific registers */	u8 display_cntl;	u8 pixelpipe_cfg0;	u8 pixelpipe_cfg1;	u8 pixelpipe_cfg2;	u8 video_clk2_m;	u8 video_clk2_n;	u8 video_clk2_mn_msbs;	u8 video_clk2_div_sel;	u8 pll_cntl;	u8 address_mapping;	u8 io_cntl;	u8 bitblt_cntl;	u8 ext_vert_total;	u8 ext_vert_disp_end;	u8 ext_vert_sync_start;	u8 ext_vert_blank_start;	u8 ext_horiz_total;	u8 ext_horiz_blank;	u8 ext_offset;	u8 interlace_cntl;	u32 lmi_fifo_watermark;	u8 ext_start_addr;	u8 ext_start_addr_hi;}; #define DACSPEED8	203#define DACSPEED16	163#define DACSPEED24_SG	136#define DACSPEED24_SD	128#define DACSPEED32	86 static const struct fb_fix_screeninfo i740fb_fix = {	.id =		"i740fb",	.type =		FB_TYPE_PACKED_PIXELS,	.visual =	FB_VISUAL_TRUECOLOR,	.xpanstep =	8,	.ypanstep =	1,	.accel =	FB_ACCEL_NONE,}; static inline void i740outb(struct i740fb_par *par, u16 port, u8 val){	vga_mm_w(par->regs, port, val);}static inline u8 i740inb(struct i740fb_par *par, u16 port){	return vga_mm_r(par->regs, port);}static inline void i740outreg(struct i740fb_par *par, u16 port, u8 reg, u8 val){	vga_mm_w_fast(par->regs, port, reg, val);}static inline u8 i740inreg(struct i740fb_par *par, u16 port, u8 reg){	vga_mm_w(par->regs, port, reg);	return vga_mm_r(par->regs, port+1);}static inline void i740outreg_mask(struct i740fb_par *par, u16 port, u8 reg,				   u8 val, u8 mask){	vga_mm_w_fast(par->regs, port, reg, (val & mask)		| (i740inreg(par, port, reg) & ~mask));} #define REG_DDC_DRIVE	0x62#define REG_DDC_STATE	0x63#define DDC_SCL		(1 << 3)#define DDC_SDA		(1 << 2) static void i740fb_ddc_setscl(void *data, int val){	struct i740fb_par *par = data; 	i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SCL, DDC_SCL);	i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SCL : 0, DDC_SCL);} static void i740fb_ddc_setsda(void *data, int val){	struct i740fb_par *par = data; 	i740outreg_mask(par, XRX, REG_DDC_DRIVE, DDC_SDA, DDC_SDA);	i740outreg_mask(par, XRX, REG_DDC_STATE, val ? DDC_SDA : 0, DDC_SDA);} static int i740fb_ddc_getscl(void *data){	struct i740fb_par *par = data; 	i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SCL); 	return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SCL);} static int i740fb_ddc_getsda(void *data){	struct i740fb_par *par = data; 	i740outreg_mask(par, XRX, REG_DDC_DRIVE, 0, DDC_SDA); 	return !!(i740inreg(par, XRX, REG_DDC_STATE) & DDC_SDA);} static int i740fb_setup_ddc_bus(struct fb_info *info){	struct i740fb_par *par = info->par; 	strscpy(par->ddc_adapter.name, info->fix.id,		sizeof(par->ddc_adapter.name));	par->ddc_adapter.owner		= THIS_MODULE;	par->ddc_adapter.algo_data	= &par->ddc_algo;	par->ddc_adapter.dev.parent	= info->device;	par->ddc_algo.setsda		= i740fb_ddc_setsda;	par->ddc_algo.setscl		= i740fb_ddc_setscl;	par->ddc_algo.getsda		= i740fb_ddc_getsda;	par->ddc_algo.getscl		= i740fb_ddc_getscl;	par->ddc_algo.udelay		= 10;	par->ddc_algo.timeout		= 20;	par->ddc_algo.data		= par; 	i2c_set_adapdata(&par->ddc_adapter, par); 	return i2c_bit_add_bus(&par->ddc_adapter);} static int i740fb_open(struct fb_info *info, int user){	struct i740fb_par *par = info->par; 	mutex_lock(&(par->open_lock));	par->ref_count++;	mutex_unlock(&(par->open_lock)); 	return 0;} static int i740fb_release(struct fb_info *info, int user){	struct i740fb_par *par = info->par; 	mutex_lock(&(par->open_lock));	if (par->ref_count == 0) {		fb_err(info, "release called with zero refcount\n");		mutex_unlock(&(par->open_lock));		return -EINVAL;	} 	par->ref_count--;	mutex_unlock(&(par->open_lock)); 	return 0;} static u32 i740_calc_fifo(struct i740fb_par *par, u32 freq, int bpp){	/*	 * Would like to calculate these values automatically, but a generic	 * algorithm does not seem possible.  Note: These FIFO water mark	 * values were tested on several cards and seem to eliminate the	 * all of the snow and vertical banding, but fine adjustments will	 * probably be required for other cards.	 */ 	u32 wm; 	switch (bpp) {	case 8:		if	(freq > 200)			wm = 0x18120000;		else if (freq > 175)			wm = 0x16110000;		else if (freq > 135)			wm = 0x120E0000;		else			wm = 0x100D0000;		break;	case 15:	case 16:		if (par->has_sgram) {			if	(freq > 140)				wm = 0x2C1D0000;			else if (freq > 120)				wm = 0x2C180000;			else if (freq > 100)				wm = 0x24160000;			else if (freq >  90)				wm = 0x18120000;			else if (freq >  50)				wm = 0x16110000;			else if (freq >  32)				wm = 0x13100000;			else				wm = 0x120E0000;		} else {			if	(freq > 160)				wm = 0x28200000;			else if (freq > 140)				wm = 0x2A1E0000;			else if (freq > 130)				wm = 0x2B1A0000;			else if (freq > 120)				wm = 0x2C180000;			else if (freq > 100)				wm = 0x24180000;			else if (freq >  90)				wm = 0x18120000;			else if (freq >  50)				wm = 0x16110000;			else if (freq >  32)				wm = 0x13100000;			else				wm = 0x120E0000;		}		break;	case 24:		if (par->has_sgram) {			if	(freq > 130)				wm = 0x31200000;			else if (freq > 120)				wm = 0x2E200000;			else if (freq > 100)				wm = 0x2C1D0000;			else if (freq >  80)				wm = 0x25180000;			else if (freq >  64)				wm = 0x24160000;			else if (freq >  49)				wm = 0x18120000;			else if (freq >  32)				wm = 0x16110000;			else				wm = 0x13100000;		} else {			if	(freq > 120)				wm = 0x311F0000;			else if (freq > 100)				wm = 0x2C1D0000;			else if (freq >  80)				wm = 0x25180000;			else if (freq >  64)				wm = 0x24160000;			else if (freq >  49)				wm = 0x18120000;			else if (freq >  32)				wm = 0x16110000;			else				wm = 0x13100000;		}		break;	case 32:		if (par->has_sgram) {			if	(freq >  80)				wm = 0x2A200000;			else if (freq >  60)				wm = 0x281A0000;			else if (freq >  49)				wm = 0x25180000;			else if (freq >  32)				wm = 0x18120000;			else				wm = 0x16110000;		} else {			if	(freq >  80)				wm = 0x29200000;			else if (freq >  60)				wm = 0x281A0000;			else if (freq >  49)				wm = 0x25180000;			else if (freq >  32)				wm = 0x18120000;			else				wm = 0x16110000;		}		break;	} 	return wm;} /* clock calculation from i740fb by Patrick LERDA */ #define I740_RFREQ		1000000#define TARGET_MAX_N		30#define I740_FFIX		(1 << 8)#define I740_RFREQ_FIX		(I740_RFREQ / I740_FFIX)#define I740_REF_FREQ		(6667 * I740_FFIX / 100)	/* 66.67 MHz */#define I740_MAX_VCO_FREQ	(450 * I740_FFIX)		/* 450 MHz */ static void i740_calc_vclk(u32 freq, struct i740fb_par *par){	const u32 err_max    = freq / (200  * I740_RFREQ / I740_FFIX);	const u32 err_target = freq / (1000 * I740_RFREQ / I740_FFIX);	u32 err_best = 512 * I740_FFIX;	u32 f_err, f_vco;	int m_best = 0, n_best = 0, p_best = 0;	int m, n; 	p_best = min(15, ilog2(I740_MAX_VCO_FREQ / (freq / I740_RFREQ_FIX)));	f_vco = (freq * (1 << p_best)) / I740_RFREQ_FIX;	freq = freq / I740_RFREQ_FIX; 	n = 2;	do {		n++;		m = ((f_vco * n) / I740_REF_FREQ + 2) / 4; 		if (m < 3)			m = 3; 		{			u32 f_out = (((m * I740_REF_FREQ * 4)				 / n) + ((1 << p_best) / 2)) / (1 << p_best); 			f_err = (freq - f_out); 			if (abs(f_err) < err_max) {				m_best = m;				n_best = n;				err_best = f_err;			}		}	} while ((abs(f_err) >= err_target) &&		 ((n <= TARGET_MAX_N) || (abs(err_best) > err_max))); 	if (abs(f_err) < err_target) {		m_best = m;		n_best = n;	} 	par->video_clk2_m = (m_best - 2) & 0xFF;	par->video_clk2_n = (n_best - 2) & 0xFF;	par->video_clk2_mn_msbs = ((((n_best - 2) >> 4) & VCO_N_MSBS)				 | (((m_best - 2) >> 8) & VCO_M_MSBS));	par->video_clk2_div_sel = ((p_best << 4) | REF_DIV_1);} static int i740fb_decode_var(const struct fb_var_screeninfo *var,			     struct i740fb_par *par, struct fb_info *info){	/*	 * Get the video params out of 'var'.	 * If a value doesn't fit, round it up, if it's too big, return -EINVAL.	 */ 	u32 xres, right, hslen, left, xtotal;	u32 yres, lower, vslen, upper, ytotal;	u32 vxres, xoffset, vyres, yoffset;	u32 bpp, base, dacspeed24, mem, freq;	u8 r7;	int i; 	dev_dbg(info->device, "decode_var: xres: %i, yres: %i, xres_v: %i, xres_v: %i\n",		  var->xres, var->yres, var->xres_virtual, var->xres_virtual);	dev_dbg(info->device, "	xoff: %i, yoff: %i, bpp: %i, graysc: %i\n",		  var->xoffset, var->yoffset, var->bits_per_pixel,		  var->grayscale);	dev_dbg(info->device, "	activate: %i, nonstd: %i, vmode: %i\n",		  var->activate, var->nonstd, var->vmode);	dev_dbg(info->device, "	pixclock: %i, hsynclen:%i, vsynclen:%i\n",		  var->pixclock, var->hsync_len, var->vsync_len);	dev_dbg(info->device, "	left: %i, right: %i, up:%i, lower:%i\n",		  var->left_margin, var->right_margin, var->upper_margin,		  var->lower_margin);  	bpp = var->bits_per_pixel;	switch (bpp) {	case 1 ... 8:		bpp = 8;		if ((1000000 / var->pixclock) > DACSPEED8) {			dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 8bpp)\n",				1000000 / var->pixclock, DACSPEED8);			return -EINVAL;		}		break;	case 9 ... 15:		bpp = 15;		fallthrough;	case 16:		if ((1000000 / var->pixclock) > DACSPEED16) {			dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 15/16bpp)\n",				1000000 / var->pixclock, DACSPEED16);			return -EINVAL;		}		break;	case 17 ... 24:		bpp = 24;		dacspeed24 = par->has_sgram ? DACSPEED24_SG : DACSPEED24_SD;		if ((1000000 / var->pixclock) > dacspeed24) {			dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 24bpp)\n",				1000000 / var->pixclock, dacspeed24);			return -EINVAL;		}		break;	case 25 ... 32:		bpp = 32;		if ((1000000 / var->pixclock) > DACSPEED32) {			dev_err(info->device, "requested pixclock %i MHz out of range (max. %i MHz at 32bpp)\n",				1000000 / var->pixclock, DACSPEED32);			return -EINVAL;		}		break;	default:		return -EINVAL;	} 	xres = ALIGN(var->xres, 8);	vxres = ALIGN(var->xres_virtual, 16);	if (vxres < xres)		vxres = xres; 	xoffset = ALIGN(var->xoffset, 8);	if (xres + xoffset > vxres)		xoffset = vxres - xres; 	left = ALIGN(var->left_margin, 8);	right = ALIGN(var->right_margin, 8);	hslen = ALIGN(var->hsync_len, 8); 	yres = var->yres;	vyres = var->yres_virtual;	if (yres > vyres)		vyres = yres; 	yoffset = var->yoffset;	if (yres + yoffset > vyres)		yoffset = vyres - yres; 	lower = var->lower_margin;	vslen = var->vsync_len;	upper = var->upper_margin; 	mem = vxres * vyres * ((bpp + 1) / 8);	if (mem > info->screen_size) {		dev_err(info->device, "not enough video memory (%d KB requested, %ld KB available)\n",			mem >> 10, info->screen_size >> 10);		return -ENOMEM;	} 	if (yoffset + yres > vyres)		yoffset = vyres - yres; 	xtotal = xres + right + hslen + left;	ytotal = yres + lower + vslen + upper; 

Lines 1–500 of 1316.