From 895d72279da7f24f266f9583c239e7b22230127c Mon Sep 17 00:00:00 2001 From: Andres Salomon Date: Tue, 31 Mar 2009 15:25:21 -0700 Subject: tdfxfb: fix memory leaks in removal path We were leaking the cmap memory. Signed-off-by: Andres Salomon Acked-by: Krzysztof Helt Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/tdfxfb.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/video/tdfxfb.c') diff --git a/drivers/video/tdfxfb.c b/drivers/video/tdfxfb.c index 14bd3f3680b8..ee64771fbe3d 100644 --- a/drivers/video/tdfxfb.c +++ b/drivers/video/tdfxfb.c @@ -1393,6 +1393,7 @@ static void __devexit tdfxfb_remove(struct pci_dev *pdev) release_mem_region(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); pci_set_drvdata(pdev, NULL); + fb_dealloc_cmap(&info->cmap); framebuffer_release(info); } -- cgit v1.2.3-59-g8ed1b From feff3880d06da0cc8fc65b9e40f518fea7594674 Mon Sep 17 00:00:00 2001 From: Krzysztof Helt Date: Mon, 6 Apr 2009 19:01:03 -0700 Subject: tdfxfb: move I2C functionality into the tdfxfb The I2C functionality provided by the i2c-voodoo3 driver is moved into the tdfxfb (frame buffer driver for Voodoo3 cards). This way there is no conflict between the i2c driver and the fb driver. The tdfxfb does not make use from the DDC functionality yet but provides all the functionality of the i2c-voodoo3 driver. Signed-off-by: Krzysztof Helt Acked-by: Jean Delvare Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/Kconfig | 9 +++ drivers/video/tdfxfb.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++++- include/video/tdfx.h | 26 +++++++ 3 files changed, 234 insertions(+), 1 deletion(-) (limited to 'drivers/video/tdfxfb.c') diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index ffe2f2796e29..7826bdce4bbe 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1550,6 +1550,7 @@ config FB_3DFX select FB_CFB_IMAGEBLIT select FB_CFB_FILLRECT select FB_CFB_COPYAREA + select FB_MODE_HELPERS help This driver supports graphics boards with the 3Dfx Banshee, Voodoo3 or VSA-100 (aka Voodoo4/5) chips. Say Y if you have @@ -1565,6 +1566,14 @@ config FB_3DFX_ACCEL This will compile the 3Dfx Banshee/Voodoo3/VSA-100 frame buffer device driver with acceleration functions. +config FB_3DFX_I2C + bool "Enable DDC/I2C support" + depends on FB_3DFX && EXPERIMENTAL + select FB_DDC + default y + help + Say Y here if you want DDC/I2C support for your 3dfx Voodoo3. + config FB_VOODOO1 tristate "3Dfx Voodoo Graphics (sst1) support" depends on FB && PCI diff --git a/drivers/video/tdfxfb.c b/drivers/video/tdfxfb.c index ee64771fbe3d..bc6f916c53be 100644 --- a/drivers/video/tdfxfb.c +++ b/drivers/video/tdfxfb.c @@ -10,6 +10,12 @@ * Created : Thu Sep 23 18:17:43 1999, hmallat * Last modified: Tue Nov 2 21:19:47 1999, hmallat * + * I2C part copied from the i2c-voodoo3.c driver by: + * Frodo Looijaard , + * Philip Edelbrock , + * Ralph Metzler , and + * Mark D. Studebaker + * * Lots of the information here comes from the Daryll Strauss' Banshee * patches to the XF86 server, and the rest comes from the 3dfx * Banshee specification. I'm very much indebted to Daryll for his @@ -1167,6 +1173,190 @@ static struct fb_ops tdfxfb_ops = { #endif }; +#ifdef CONFIG_FB_3DFX_I2C +/* The voo GPIO registers don't have individual masks for each bit + so we always have to read before writing. */ + +static void tdfxfb_i2c_setscl(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= I2C_SCL_OUT; + else + r &= ~I2C_SCL_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static void tdfxfb_i2c_setsda(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= I2C_SDA_OUT; + else + r &= ~I2C_SDA_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +/* The GPIO pins are open drain, so the pins always remain outputs. + We rely on the i2c-algo-bit routines to set the pins high before + reading the input from other chips. */ + +static int tdfxfb_i2c_getscl(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & I2C_SCL_IN)); +} + +static int tdfxfb_i2c_getsda(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & I2C_SDA_IN)); +} + +static void tdfxfb_ddc_setscl(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= DDC_SCL_OUT; + else + r &= ~DDC_SCL_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static void tdfxfb_ddc_setsda(void *data, int val) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + unsigned int r; + + r = tdfx_inl(par, VIDSERPARPORT); + if (val) + r |= DDC_SDA_OUT; + else + r &= ~DDC_SDA_OUT; + tdfx_outl(par, VIDSERPARPORT, r); + tdfx_inl(par, VIDSERPARPORT); /* flush posted write */ +} + +static int tdfxfb_ddc_getscl(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & DDC_SCL_IN)); +} + +static int tdfxfb_ddc_getsda(void *data) +{ + struct tdfxfb_i2c_chan *chan = data; + struct tdfx_par *par = chan->par; + + return (0 != (tdfx_inl(par, VIDSERPARPORT) & DDC_SDA_IN)); +} + +static int __devinit tdfxfb_setup_ddc_bus(struct tdfxfb_i2c_chan *chan, + const char *name, struct device *dev) +{ + int rc; + + strlcpy(chan->adapter.name, name, sizeof(chan->adapter.name)); + chan->adapter.owner = THIS_MODULE; + chan->adapter.class = I2C_CLASS_DDC; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = dev; + chan->algo.setsda = tdfxfb_ddc_setsda; + chan->algo.setscl = tdfxfb_ddc_setscl; + chan->algo.getsda = tdfxfb_ddc_getsda; + chan->algo.getscl = tdfxfb_ddc_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = msecs_to_jiffies(500); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + DPRINTK("I2C bus %s registered.\n", name); + else + chan->par = NULL; + + return rc; +} + +static int __devinit tdfxfb_setup_i2c_bus(struct tdfxfb_i2c_chan *chan, + const char *name, struct device *dev) +{ + int rc; + + strlcpy(chan->adapter.name, name, sizeof(chan->adapter.name)); + chan->adapter.owner = THIS_MODULE; + chan->adapter.class = I2C_CLASS_TV_ANALOG; + chan->adapter.algo_data = &chan->algo; + chan->adapter.dev.parent = dev; + chan->algo.setsda = tdfxfb_i2c_setsda; + chan->algo.setscl = tdfxfb_i2c_setscl; + chan->algo.getsda = tdfxfb_i2c_getsda; + chan->algo.getscl = tdfxfb_i2c_getscl; + chan->algo.udelay = 10; + chan->algo.timeout = msecs_to_jiffies(500); + chan->algo.data = chan; + + i2c_set_adapdata(&chan->adapter, chan); + + rc = i2c_bit_add_bus(&chan->adapter); + if (rc == 0) + DPRINTK("I2C bus %s registered.\n", name); + else + chan->par = NULL; + + return rc; +} + +static void __devinit tdfxfb_create_i2c_busses(struct fb_info *info) +{ + struct tdfx_par *par = info->par; + + tdfx_outl(par, VIDINFORMAT, 0x8160); + tdfx_outl(par, VIDSERPARPORT, 0xcffc0020); + + par->chan[0].par = par; + par->chan[1].par = par; + + tdfxfb_setup_ddc_bus(&par->chan[0], "Voodoo3-DDC", info->dev); + tdfxfb_setup_i2c_bus(&par->chan[1], "Voodoo3-I2C", info->dev); +} + +static void tdfxfb_delete_i2c_busses(struct tdfx_par *par) +{ + if (par->chan[0].par) + i2c_del_adapter(&par->chan[0].adapter); + par->chan[0].par = NULL; + + if (par->chan[1].par) + i2c_del_adapter(&par->chan[1].adapter); + par->chan[1].par = NULL; +} +#endif /* CONFIG_FB_3DFX_I2C */ + /** * tdfxfb_probe - Device Initializiation * @@ -1284,7 +1474,9 @@ static int __devinit tdfxfb_probe(struct pci_dev *pdev, if (hwcursor) info->fix.smem_len = (info->fix.smem_len - 1024) & (PAGE_MASK << 1); - +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_create_i2c_busses(info); +#endif if (!mode_option) mode_option = "640x480@60"; @@ -1315,6 +1507,9 @@ static int __devinit tdfxfb_probe(struct pci_dev *pdev, return 0; out_err_iobase: +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_delete_i2c_busses(default_par); +#endif if (default_par->mtrr_handle >= 0) mtrr_del(default_par->mtrr_handle, info->fix.smem_start, info->fix.smem_len); @@ -1379,6 +1574,9 @@ static void __devexit tdfxfb_remove(struct pci_dev *pdev) struct tdfx_par *par = info->par; unregister_framebuffer(info); +#ifdef CONFIG_FB_3DFX_I2C + tdfxfb_delete_i2c_busses(par); +#endif if (par->mtrr_handle >= 0) mtrr_del(par->mtrr_handle, info->fix.smem_start, info->fix.smem_len); diff --git a/include/video/tdfx.h b/include/video/tdfx.h index 7431d9681e57..befbaf0a92d8 100644 --- a/include/video/tdfx.h +++ b/include/video/tdfx.h @@ -1,6 +1,9 @@ #ifndef _TDFX_H #define _TDFX_H +#include +#include + /* membase0 register offsets */ #define STATUS 0x00 #define PCIINIT0 0x04 @@ -123,6 +126,18 @@ #define VIDCFG_PIXFMT_SHIFT 18 #define DACMODE_2X BIT(0) +/* I2C bit locations in the VIDSERPARPORT register */ +#define DDC_ENAB 0x00040000 +#define DDC_SCL_OUT 0x00080000 +#define DDC_SDA_OUT 0x00100000 +#define DDC_SCL_IN 0x00200000 +#define DDC_SDA_IN 0x00400000 +#define I2C_ENAB 0x00800000 +#define I2C_SCL_OUT 0x01000000 +#define I2C_SDA_OUT 0x02000000 +#define I2C_SCL_IN 0x04000000 +#define I2C_SDA_IN 0x08000000 + /* VGA rubbish, need to change this for multihead support */ #define MISC_W 0x3c2 #define MISC_R 0x3cc @@ -168,12 +183,23 @@ struct banshee_reg { unsigned long miscinit0; }; +struct tdfx_par; + +struct tdfxfb_i2c_chan { + struct tdfx_par *par; + struct i2c_adapter adapter; + struct i2c_algo_bit_data algo; +}; + struct tdfx_par { u32 max_pixclock; u32 palette[16]; void __iomem *regbase_virt; unsigned long iobase; int mtrr_handle; +#ifdef CONFIG_FB_3DFX_I2C + struct tdfxfb_i2c_chan chan[2]; +#endif }; #endif /* __KERNEL__ */ -- cgit v1.2.3-59-g8ed1b From 215059d2421f95c30d1fca6ff31357fcae9f67dc Mon Sep 17 00:00:00 2001 From: Krzysztof Helt Date: Mon, 6 Apr 2009 19:01:04 -0700 Subject: tdfxfb: make use of DDC information about connected monitor Read DDC information from a connected monitor and use it to select initial mode (if the mode is not specified). Also, use the information to protect against modes outside the monitor specs. Signed-off-by: Krzysztof Helt Tested-by: Jean Delvare Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- drivers/video/tdfxfb.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) (limited to 'drivers/video/tdfxfb.c') diff --git a/drivers/video/tdfxfb.c b/drivers/video/tdfxfb.c index bc6f916c53be..89f231dc443f 100644 --- a/drivers/video/tdfxfb.c +++ b/drivers/video/tdfxfb.c @@ -487,6 +487,12 @@ static int tdfxfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return -EINVAL; } + if (info->monspecs.hfmax && info->monspecs.vfmax && + info->monspecs.dclkmax && fb_validate_mode(var, info) < 0) { + DPRINTK("mode outside monitor's specs\n"); + return -EINVAL; + } + var->xres = (var->xres + 15) & ~15; /* could sometimes be 8 */ lpitch = var->xres * ((var->bits_per_pixel + 7) >> 3); @@ -1355,6 +1361,23 @@ static void tdfxfb_delete_i2c_busses(struct tdfx_par *par) i2c_del_adapter(&par->chan[1].adapter); par->chan[1].par = NULL; } + +static int tdfxfb_probe_i2c_connector(struct tdfx_par *par, + struct fb_monspecs *specs) +{ + u8 *edid = NULL; + + DPRINTK("Probe DDC Bus\n"); + if (par->chan[0].par) + edid = fb_ddc_read(&par->chan[0].adapter); + + if (edid) { + fb_edid_to_monspecs(edid, specs); + kfree(edid); + return 0; + } + return 1; +} #endif /* CONFIG_FB_3DFX_I2C */ /** @@ -1372,6 +1395,8 @@ static int __devinit tdfxfb_probe(struct pci_dev *pdev, struct tdfx_par *default_par; struct fb_info *info; int err, lpitch; + struct fb_monspecs *specs; + bool found; err = pci_enable_device(pdev); if (err) { @@ -1474,15 +1499,49 @@ static int __devinit tdfxfb_probe(struct pci_dev *pdev, if (hwcursor) info->fix.smem_len = (info->fix.smem_len - 1024) & (PAGE_MASK << 1); + specs = &info->monspecs; + found = false; + info->var.bits_per_pixel = 8; #ifdef CONFIG_FB_3DFX_I2C tdfxfb_create_i2c_busses(info); + err = tdfxfb_probe_i2c_connector(default_par, specs); + + if (!err) { + if (specs->modedb == NULL) + DPRINTK("Unable to get Mode Database\n"); + else { + const struct fb_videomode *m; + + fb_videomode_to_modelist(specs->modedb, + specs->modedb_len, + &info->modelist); + m = fb_find_best_display(specs, &info->modelist); + if (m) { + fb_videomode_to_var(&info->var, m); + /* fill all other info->var's fields */ + if (tdfxfb_check_var(&info->var, info) < 0) + info->var = tdfx_var; + else + found = true; + } + } + } #endif - if (!mode_option) + if (!mode_option && !found) mode_option = "640x480@60"; - err = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8); - if (!err || err == 4) - info->var = tdfx_var; + if (mode_option) { + err = fb_find_mode(&info->var, info, mode_option, + specs->modedb, specs->modedb_len, + NULL, info->var.bits_per_pixel); + if (!err || err == 4) + info->var = tdfx_var; + } + + if (found) { + fb_destroy_modedb(specs->modedb); + specs->modedb = NULL; + } /* maximize virtual vertical length */ lpitch = info->var.xres_virtual * ((info->var.bits_per_pixel + 7) >> 3); -- cgit v1.2.3-59-g8ed1b