tridentfb: Add DDC support

Add DDC support for Trident cards.

Tested on TGUI9440, TGUI9680, 3DImage 9750, Blade3D 9880 and Blade XP.

Signed-off-by: Ondrej Zary <linux@rainbow-software.org>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
This commit is contained in:
Ondrej Zary 2015-09-25 00:14:14 +02:00 committed by Tomi Valkeinen
parent ec90631c8a
commit 6a5e3bd0c8
2 changed files with 179 additions and 5 deletions

View File

@ -1666,6 +1666,8 @@ config FB_TRIDENT
select FB_CFB_FILLRECT select FB_CFB_FILLRECT
select FB_CFB_COPYAREA select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT select FB_CFB_IMAGEBLIT
select FB_DDC
select FB_MODE_HELPERS
---help--- ---help---
This is the frame buffer device driver for Trident PCI/AGP chipsets. This is the frame buffer device driver for Trident PCI/AGP chipsets.
Supported chipset families are TGUI 9440/96XX, 3DImage, Blade3D Supported chipset families are TGUI 9440/96XX, 3DImage, Blade3D

View File

@ -25,6 +25,9 @@
#include <video/vga.h> #include <video/vga.h>
#include <video/trident.h> #include <video/trident.h>
#include <linux/i2c.h>
#include <linux/i2c-algo-bit.h>
struct tridentfb_par { struct tridentfb_par {
void __iomem *io_virt; /* iospace virtual memory address */ void __iomem *io_virt; /* iospace virtual memory address */
u32 pseudo_pal[16]; u32 pseudo_pal[16];
@ -40,6 +43,9 @@ struct tridentfb_par {
(struct tridentfb_par *par, const char*, (struct tridentfb_par *par, const char*,
u32, u32, u32, u32, u32, u32); u32, u32, u32, u32, u32, u32);
unsigned char eng_oper; /* engine operation... */ unsigned char eng_oper; /* engine operation... */
bool ddc_registered;
struct i2c_adapter ddc_adapter;
struct i2c_algo_bit_data ddc_algo;
}; };
static struct fb_fix_screeninfo tridentfb_fix = { static struct fb_fix_screeninfo tridentfb_fix = {
@ -53,7 +59,7 @@ static struct fb_fix_screeninfo tridentfb_fix = {
/* defaults which are normally overriden by user values */ /* defaults which are normally overriden by user values */
/* video mode */ /* video mode */
static char *mode_option = "640x480-8@60"; static char *mode_option;
static int bpp = 8; static int bpp = 8;
static int noaccel; static int noaccel;
@ -174,6 +180,121 @@ static inline u32 readmmr(struct tridentfb_par *par, u16 r)
return fb_readl(par->io_virt + r); return fb_readl(par->io_virt + r);
} }
#define DDC_SDA_TGUI BIT(0)
#define DDC_SCL_TGUI BIT(1)
#define DDC_SCL_DRIVE_TGUI BIT(2)
#define DDC_SDA_DRIVE_TGUI BIT(3)
#define DDC_MASK_TGUI (DDC_SCL_DRIVE_TGUI | DDC_SDA_DRIVE_TGUI)
static void tridentfb_ddc_setscl_tgui(void *data, int val)
{
struct tridentfb_par *par = data;
u8 reg = vga_mm_rcrt(par->io_virt, I2C) & DDC_MASK_TGUI;
if (val)
reg &= ~DDC_SCL_DRIVE_TGUI; /* disable drive - don't drive hi */
else
reg |= DDC_SCL_DRIVE_TGUI; /* drive low */
vga_mm_wcrt(par->io_virt, I2C, reg);
}
static void tridentfb_ddc_setsda_tgui(void *data, int val)
{
struct tridentfb_par *par = data;
u8 reg = vga_mm_rcrt(par->io_virt, I2C) & DDC_MASK_TGUI;
if (val)
reg &= ~DDC_SDA_DRIVE_TGUI; /* disable drive - don't drive hi */
else
reg |= DDC_SDA_DRIVE_TGUI; /* drive low */
vga_mm_wcrt(par->io_virt, I2C, reg);
}
static int tridentfb_ddc_getsda_tgui(void *data)
{
struct tridentfb_par *par = data;
return !!(vga_mm_rcrt(par->io_virt, I2C) & DDC_SDA_TGUI);
}
#define DDC_SDA_IN BIT(0)
#define DDC_SCL_OUT BIT(1)
#define DDC_SDA_OUT BIT(3)
#define DDC_SCL_IN BIT(6)
#define DDC_MASK (DDC_SCL_OUT | DDC_SDA_OUT)
static void tridentfb_ddc_setscl(void *data, int val)
{
struct tridentfb_par *par = data;
unsigned char reg;
reg = vga_mm_rcrt(par->io_virt, I2C) & DDC_MASK;
if (val)
reg |= DDC_SCL_OUT;
else
reg &= ~DDC_SCL_OUT;
vga_mm_wcrt(par->io_virt, I2C, reg);
}
static void tridentfb_ddc_setsda(void *data, int val)
{
struct tridentfb_par *par = data;
unsigned char reg;
reg = vga_mm_rcrt(par->io_virt, I2C) & DDC_MASK;
if (!val)
reg |= DDC_SDA_OUT;
else
reg &= ~DDC_SDA_OUT;
vga_mm_wcrt(par->io_virt, I2C, reg);
}
static int tridentfb_ddc_getscl(void *data)
{
struct tridentfb_par *par = data;
return !!(vga_mm_rcrt(par->io_virt, I2C) & DDC_SCL_IN);
}
static int tridentfb_ddc_getsda(void *data)
{
struct tridentfb_par *par = data;
return !!(vga_mm_rcrt(par->io_virt, I2C) & DDC_SDA_IN);
}
static int tridentfb_setup_ddc_bus(struct fb_info *info)
{
struct tridentfb_par *par = info->par;
strlcpy(par->ddc_adapter.name, info->fix.id,
sizeof(par->ddc_adapter.name));
par->ddc_adapter.owner = THIS_MODULE;
par->ddc_adapter.class = I2C_CLASS_DDC;
par->ddc_adapter.algo_data = &par->ddc_algo;
par->ddc_adapter.dev.parent = info->device;
if (is_oldclock(par->chip_id)) { /* not sure if this check is OK */
par->ddc_algo.setsda = tridentfb_ddc_setsda_tgui;
par->ddc_algo.setscl = tridentfb_ddc_setscl_tgui;
par->ddc_algo.getsda = tridentfb_ddc_getsda_tgui;
/* no getscl */
} else {
par->ddc_algo.setsda = tridentfb_ddc_setsda;
par->ddc_algo.setscl = tridentfb_ddc_setscl;
par->ddc_algo.getsda = tridentfb_ddc_getsda;
par->ddc_algo.getscl = tridentfb_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);
}
/* /*
* Blade specific acceleration. * Blade specific acceleration.
*/ */
@ -1340,6 +1461,7 @@ static int trident_pci_probe(struct pci_dev *dev,
struct tridentfb_par *default_par; struct tridentfb_par *default_par;
int chip3D; int chip3D;
int chip_id; int chip_id;
bool found = false;
err = pci_enable_device(dev); err = pci_enable_device(dev);
if (err) if (err)
@ -1493,6 +1615,7 @@ static int trident_pci_probe(struct pci_dev *dev,
info->pixmap.scan_align = 1; info->pixmap.scan_align = 1;
info->pixmap.access_align = 32; info->pixmap.access_align = 32;
info->pixmap.flags = FB_PIXMAP_SYSTEM; info->pixmap.flags = FB_PIXMAP_SYSTEM;
info->var.bits_per_pixel = 8;
if (default_par->image_blit) { if (default_par->image_blit) {
info->flags |= FBINFO_HWACCEL_IMAGEBLIT; info->flags |= FBINFO_HWACCEL_IMAGEBLIT;
@ -1505,11 +1628,56 @@ static int trident_pci_probe(struct pci_dev *dev,
info->pixmap.scan_align = 1; info->pixmap.scan_align = 1;
} }
if (!fb_find_mode(&info->var, info, if (tridentfb_setup_ddc_bus(info) == 0) {
mode_option, NULL, 0, NULL, bpp)) { u8 *edid = fb_ddc_read(&default_par->ddc_adapter);
err = -EINVAL;
goto out_unmap2; default_par->ddc_registered = true;
if (edid) {
fb_edid_to_monspecs(edid, &info->monspecs);
kfree(edid);
if (!info->monspecs.modedb)
dev_err(info->device, "error getting mode database\n");
else {
const struct fb_videomode *m;
fb_videomode_to_modelist(info->monspecs.modedb,
info->monspecs.modedb_len,
&info->modelist);
m = fb_find_best_display(&info->monspecs,
&info->modelist);
if (m) {
fb_videomode_to_var(&info->var, m);
/* fill all other info->var's fields */
if (tridentfb_check_var(&info->var,
info) == 0)
found = true;
}
}
}
} }
if (!mode_option && !found)
mode_option = "640x480-8@60";
/* Prepare startup mode */
if (mode_option) {
err = fb_find_mode(&info->var, info, mode_option,
info->monspecs.modedb,
info->monspecs.modedb_len,
NULL, info->var.bits_per_pixel);
if (!err || err == 4) {
err = -EINVAL;
dev_err(info->device, "mode %s not found\n",
mode_option);
fb_destroy_modedb(info->monspecs.modedb);
info->monspecs.modedb = NULL;
goto out_unmap2;
}
}
fb_destroy_modedb(info->monspecs.modedb);
info->monspecs.modedb = NULL;
err = fb_alloc_cmap(&info->cmap, 256, 0); err = fb_alloc_cmap(&info->cmap, 256, 0);
if (err < 0) if (err < 0)
goto out_unmap2; goto out_unmap2;
@ -1530,6 +1698,8 @@ static int trident_pci_probe(struct pci_dev *dev,
return 0; return 0;
out_unmap2: out_unmap2:
if (default_par->ddc_registered)
i2c_del_adapter(&default_par->ddc_adapter);
kfree(info->pixmap.addr); kfree(info->pixmap.addr);
if (info->screen_base) if (info->screen_base)
iounmap(info->screen_base); iounmap(info->screen_base);
@ -1549,6 +1719,8 @@ static void trident_pci_remove(struct pci_dev *dev)
struct tridentfb_par *par = info->par; struct tridentfb_par *par = info->par;
unregister_framebuffer(info); unregister_framebuffer(info);
if (par->ddc_registered)
i2c_del_adapter(&par->ddc_adapter);
iounmap(par->io_virt); iounmap(par->io_virt);
iounmap(info->screen_base); iounmap(info->screen_base);
release_mem_region(tridentfb_fix.smem_start, tridentfb_fix.smem_len); release_mem_region(tridentfb_fix.smem_start, tridentfb_fix.smem_len);