* [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work
@ 2026-07-30 18:26 Daniel Palmer
2026-07-30 18:26 ` [PATCH 1/5] fbdev: tdfxfb: Add helper to read config table from BIOS Daniel Palmer
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
Currently using voodoo cards that weren't booted by the video
BIOS aren't usable.
This means if you happen to have a proper - non-x86 - computer,
another card is the primary or you have a new BIOS that cannot
run the video BIOS tdfxfb would probe but you be staring at
"no signal detected".
This series implements manually booting cards using the
values from the video BIOS' own table.
Debugging was partially assisted by Claude Fable 5. I could not
work out why this would not work for months so I told it to
create a voodoo 3 emulation for QEMU to compare the BIOS against
my code. That made it obvious my init sequence matched the BIOS
but the VGA core wasn't running.
This has been tested on a real voodoo 3 in an x86-64 board.
I have some patches to add an interface for doing 3D rendering
that are not included in this series and 3D rendering is also
working so as far as I can tell everything is good.
Daniel Palmer (5):
fbdev: tdfxfb: Add helper to read config table from BIOS
fbdev: tdfxfb: Attempt to detect if the card wasn't booted
fbdev: tdfxfb: Manually boot unbooted cards
fbdev: tdfxfb: Wake the VGA core before programming the CRTC
fbdev: tdfxfb: Program the initial video mode
drivers/video/fbdev/tdfxfb.c | 151 +++++++++++++++++++++++++++++++++++
1 file changed, 151 insertions(+)
--
2.53.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/5] fbdev: tdfxfb: Add helper to read config table from BIOS
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
@ 2026-07-30 18:26 ` Daniel Palmer
2026-07-30 18:26 ` [PATCH 2/5] fbdev: tdfxfb: Attempt to detect if the card wasn't booted Daniel Palmer
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
In the case that the video BIOS didn't run because the card isn't the
primary card, the BIOS doesn't support running old skool video BIOS
(modern BIOS without CSM), or the machine isn't x86 it needs to be
booted manually. To do this the config table in the BIOS is needed.
Add a helper to get the config table in preparation for manually
booting cards.
Signed-off-by: Daniel Palmer <daniel@0x0f.com>
---
drivers/video/fbdev/tdfxfb.c | 74 ++++++++++++++++++++++++++++++++++++
1 file changed, 74 insertions(+)
diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c
index cc6a074f3165..fa554b09a0a6 100644
--- a/drivers/video/fbdev/tdfxfb.c
+++ b/drivers/video/fbdev/tdfxfb.c
@@ -71,6 +71,7 @@
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
+#include <linux/vmalloc.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/pci.h>
@@ -336,6 +337,79 @@ static u32 do_calc_pll(int freq, int *freq_out)
return (n << 8) | (m << 2) | k;
}
+/*
+ * Convert a pllctrl register value back to a frequency in kHz.
+ * Formula from 3dfx documentation.
+ */
+static u32 tdfx_pll_to_khz(u32 pll)
+{
+ return (14318 * (((pll >> 8) & 0xff) + 2) /
+ (((pll >> 2) & 0x3f) + 2)) >> (pll & 3);
+}
+
+/* Layout of the "OEM config" table in voodoo 3 BIOS */
+struct tdfx_bios_cfg {
+ __le32 pciinit0; /* 0x00 */
+ __le32 miscinit0; /* 0x04 */
+ __le32 miscinit1; /* 0x08 */
+ __le32 draminit0; /* 0x0c */
+ __le32 draminit1; /* 0x10 */
+ __le32 agpinit0; /* 0x14 */
+ __le32 pllctrl1; /* 0x18 - memory PLL */
+ __le32 pllctrl2; /* 0x1c - graphics PLL */
+ __le32 sgrammode; /* 0x20 - SGRAM/SDRAM mode register data */
+} __packed;
+
+#define TDFX_ROM_CFG_PTR 0x50
+
+static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev,
+ struct tdfx_bios_cfg *cfg)
+{
+ u16 romcfg, oemcfg;
+ void __iomem *rom;
+ size_t romsize;
+ u8 *image;
+ u32 khz;
+
+ rom = pci_map_rom(pdev, &romsize);
+ if (!rom || !romsize)
+ return false;
+
+ image = vmalloc(romsize);
+ if (!image) {
+ pci_unmap_rom(pdev, rom);
+ return false;
+ }
+ memcpy_fromio(image, rom, romsize);
+ pci_unmap_rom(pdev, rom);
+
+ /* ROM[0x50] -> ROM config table -> OEM config table */
+ if (TDFX_ROM_CFG_PTR + 2 > romsize)
+ goto out;
+ romcfg = image[TDFX_ROM_CFG_PTR] | image[TDFX_ROM_CFG_PTR + 1] << 8;
+ if (romcfg == 0xffff || romcfg + 2 > romsize)
+ goto out;
+ oemcfg = image[romcfg] | image[romcfg + 1] << 8;
+ if (oemcfg == 0xffff || oemcfg + sizeof(*cfg) > romsize)
+ goto out;
+ memcpy(cfg, image + oemcfg, sizeof(*cfg));
+ vfree(image);
+
+ /*
+ * Make sure we didn't read garbage from the BIOS and will
+ * end up setting a frequency that explodes someone's expensive
+ * card.
+ */
+ khz = tdfx_pll_to_khz(le32_to_cpu(cfg->pllctrl1));
+ if (khz < 40000 || khz > 250000 || !le32_to_cpu(cfg->draminit0))
+ return false;
+ return true;
+
+out:
+ vfree(image);
+ return false;
+}
+
static void do_write_regs(struct fb_info *info, struct banshee_reg *reg)
{
struct tdfx_par *par = info->par;
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 2/5] fbdev: tdfxfb: Attempt to detect if the card wasn't booted
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
2026-07-30 18:26 ` [PATCH 1/5] fbdev: tdfxfb: Add helper to read config table from BIOS Daniel Palmer
@ 2026-07-30 18:26 ` Daniel Palmer
2026-07-30 18:26 ` [PATCH 3/5] fbdev: tdfxfb: Manually boot unbooted cards Daniel Palmer
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
Until now a card had to have been booted by its video BIOS
otherwise the driver would probe, create the fb etc but there
would be no output on the display.
There doesn't seem to be a documented way work out if the BIOS
ran or not. Checking if the values in registers match what is
in the config table in the BIOS seems to be the only option.
On my 16MB Voodoo 3 3000 the registers show 4MB of memory when
unbooted. Checking 4MB != 16MB is enough decide if to reject
the card or not for my machine at least.
Signed-off-by: Daniel Palmer <daniel@0x0f.com>
---
drivers/video/fbdev/tdfxfb.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c
index fa554b09a0a6..1d8bffe1e9fd 100644
--- a/drivers/video/fbdev/tdfxfb.c
+++ b/drivers/video/fbdev/tdfxfb.c
@@ -410,6 +410,25 @@ static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev,
return false;
}
+/*
+ * Try to work out if the card was booted or not, just checks
+ * if the register reported memory amount matches what the BIOS
+ * reports for now.
+ */
+static int tdfxfb_hw_init(struct fb_info *info, struct pci_dev *pdev)
+{
+ struct tdfx_par *par = info->par;
+ struct tdfx_bios_cfg cfg;
+
+ if (tdfxfb_get_bios_cfg(pdev, &cfg) &&
+ tdfx_inl(par, DRAMINIT0) == le32_to_cpu(cfg.draminit0))
+ return 0;
+
+ dev_err(&pdev->dev,
+ "Card hasn't booted and is unusable\n");
+ return -ENODEV;
+}
+
static void do_write_regs(struct fb_info *info, struct banshee_reg *reg)
{
struct tdfx_par *par = info->par;
@@ -1505,6 +1524,9 @@ static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
goto out_err_regbase;
}
+ if (tdfxfb_hw_init(info, pdev))
+ goto out_err_regbase;
+
info->fix.smem_start = pci_resource_start(pdev, 1);
info->fix.smem_len = do_lfb_size(default_par, pdev->device);
if (!info->fix.smem_len) {
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 3/5] fbdev: tdfxfb: Manually boot unbooted cards
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
2026-07-30 18:26 ` [PATCH 1/5] fbdev: tdfxfb: Add helper to read config table from BIOS Daniel Palmer
2026-07-30 18:26 ` [PATCH 2/5] fbdev: tdfxfb: Attempt to detect if the card wasn't booted Daniel Palmer
@ 2026-07-30 18:26 ` Daniel Palmer
2026-07-30 18:26 ` [PATCH 4/5] fbdev: tdfxfb: Wake the VGA core before programming the CRTC Daniel Palmer
2026-07-30 18:26 ` [PATCH 5/5] fbdev: tdfxfb: Program the initial video mode Daniel Palmer
4 siblings, 0 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
If the card is detected as being unbooted it isn't too difficult
to use the config table in its BIOS to fire it up so do it.
Signed-off-by: Daniel Palmer <daniel@0x0f.com>
---
drivers/video/fbdev/tdfxfb.c | 56 ++++++++++++++++++++++++++++++++----
1 file changed, 50 insertions(+), 6 deletions(-)
diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c
index 1d8bffe1e9fd..4c1d847c89c9 100644
--- a/drivers/video/fbdev/tdfxfb.c
+++ b/drivers/video/fbdev/tdfxfb.c
@@ -67,6 +67,7 @@
#include <linux/aperture.h>
#include <linux/module.h>
#include <linux/kernel.h>
+#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
@@ -414,19 +415,62 @@ static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev,
* Try to work out if the card was booted or not, just checks
* if the register reported memory amount matches what the BIOS
* reports for now.
+ *
+ * If we have a BIOS config table attempt to manually boot the
+ * card if needed.
*/
static int tdfxfb_hw_init(struct fb_info *info, struct pci_dev *pdev)
{
- struct tdfx_par *par = info->par;
+ u32 mempll, gfxpll, draminit0, draminit1, miscinit1, dram_mode;
struct tdfx_bios_cfg cfg;
+ bool have_cfg = tdfxfb_get_bios_cfg(pdev, &cfg);
+ struct tdfx_par *par = info->par;
- if (tdfxfb_get_bios_cfg(pdev, &cfg) &&
- tdfx_inl(par, DRAMINIT0) == le32_to_cpu(cfg.draminit0))
+ if (have_cfg && tdfx_inl(par, DRAMINIT0) == le32_to_cpu(cfg.draminit0))
return 0;
- dev_err(&pdev->dev,
- "Card hasn't booted and is unusable\n");
- return -ENODEV;
+ if (have_cfg) {
+ dev_info(&pdev->dev,
+ "Manually booting card using config table\n");
+ mempll = le32_to_cpu(cfg.pllctrl1);
+ gfxpll = le32_to_cpu(cfg.pllctrl2);
+ draminit0 = le32_to_cpu(cfg.draminit0);
+ draminit1 = le32_to_cpu(cfg.draminit1);
+ miscinit1 = le32_to_cpu(cfg.miscinit1);
+ dram_mode = le32_to_cpu(cfg.sgrammode);
+ tdfx_outl(par, PCIINIT0, le32_to_cpu(cfg.pciinit0));
+ tdfx_outl(par, AGPINIT, le32_to_cpu(cfg.agpinit0));
+ } else {
+ dev_err(&pdev->dev,
+ "Card hasn't booted and is unusable\n");
+ return -ENODEV;
+ }
+
+ /* memory clock, and the graphics clock if the card wants one */
+ tdfx_outl(par, PLLCTRL1, mempll);
+ if (gfxpll)
+ tdfx_outl(par, PLLCTRL2, gfxpll);
+ /* PLL lock */
+ udelay(100);
+
+ tdfx_outl(par, MISCINIT1, miscinit1);
+ tdfx_outl(par, DRAMINIT0, draminit0);
+ tdfx_outl(par, DRAMINIT1, draminit1);
+
+ /* Make sure the DRAM config is applied before continuing */
+ wmb();
+
+ /* SDRAM/SGRAM wake up: load the mode register */
+ tdfx_outl(par, DRAMDATA, dram_mode);
+ tdfx_outl(par, DRAMCOMMAND, 0x10d);
+
+ tdfx_outl(par, LFBMEMORYCONFIG, 0x00001fff);
+ tdfx_outl(par, MISCINIT0, 0);
+
+ /* Make sure the remaining config is applied */
+ wmb();
+
+ return 0;
}
static void do_write_regs(struct fb_info *info, struct banshee_reg *reg)
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 4/5] fbdev: tdfxfb: Wake the VGA core before programming the CRTC
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
` (2 preceding siblings ...)
2026-07-30 18:26 ` [PATCH 3/5] fbdev: tdfxfb: Manually boot unbooted cards Daniel Palmer
@ 2026-07-30 18:26 ` Daniel Palmer
2026-07-30 18:26 ` [PATCH 5/5] fbdev: tdfxfb: Program the initial video mode Daniel Palmer
4 siblings, 0 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
If the card was unbooted the VGA core needs to be woken up before
poking at it.
Signed-off-by: Daniel Palmer <daniel@0x0f.com>
---
drivers/video/fbdev/tdfxfb.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c
index 4c1d847c89c9..9d55efd2c934 100644
--- a/drivers/video/fbdev/tdfxfb.c
+++ b/drivers/video/fbdev/tdfxfb.c
@@ -482,6 +482,10 @@ static void do_write_regs(struct fb_info *info, struct banshee_reg *reg)
tdfx_outl(par, MISCINIT1, tdfx_inl(par, MISCINIT1) | 0x01);
+ /* Wake the VGA core if it hasn't already been woken up */
+ tdfx_outl(par, VGAINIT0, reg->vgainit0);
+ vga_outb(par, 0x3c3, 0x01);
+
crt_outb(par, 0x11, crt_inb(par, 0x11) & 0x7f); /* CRT unprotect */
banshee_make_room(par, 3);
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 5/5] fbdev: tdfxfb: Program the initial video mode
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
` (3 preceding siblings ...)
2026-07-30 18:26 ` [PATCH 4/5] fbdev: tdfxfb: Wake the VGA core before programming the CRTC Daniel Palmer
@ 2026-07-30 18:26 ` Daniel Palmer
4 siblings, 0 replies; 6+ messages in thread
From: Daniel Palmer @ 2026-07-30 18:26 UTC (permalink / raw)
To: deller; +Cc: linux-fbdev, dri-devel, linux-kernel, Daniel Palmer
If the card does not get bound to by fbcon set_par() never happens
and the initial video mode is not setup and the display detects
no signal.
Program the video mode and also clear the framebuffer memory so
random garbage isn't displayed.
Signed-off-by: Daniel Palmer <daniel@0x0f.com>
---
drivers/video/fbdev/tdfxfb.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c
index 9d55efd2c934..d03cb47b34a5 100644
--- a/drivers/video/fbdev/tdfxfb.c
+++ b/drivers/video/fbdev/tdfxfb.c
@@ -1692,6 +1692,13 @@ static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id)
* Our driver data
*/
pci_set_drvdata(pdev, info);
+
+ /* Program a video mode so the display detects a signal */
+ tdfxfb_set_par(info);
+
+ /* Don't scare the user with random garbage on their display */
+ memset_io(info->screen_base, 0, info->fix.smem_len);
+
return 0;
out_err_iobase:
--
2.53.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-30 18:27 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-30 18:26 [PATCH 0/5] fbdev: tdfxfb: Make "unbooted" cards work Daniel Palmer
2026-07-30 18:26 ` [PATCH 1/5] fbdev: tdfxfb: Add helper to read config table from BIOS Daniel Palmer
2026-07-30 18:26 ` [PATCH 2/5] fbdev: tdfxfb: Attempt to detect if the card wasn't booted Daniel Palmer
2026-07-30 18:26 ` [PATCH 3/5] fbdev: tdfxfb: Manually boot unbooted cards Daniel Palmer
2026-07-30 18:26 ` [PATCH 4/5] fbdev: tdfxfb: Wake the VGA core before programming the CRTC Daniel Palmer
2026-07-30 18:26 ` [PATCH 5/5] fbdev: tdfxfb: Program the initial video mode Daniel Palmer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox