#include #include #include #define VOODOO2 "voodoo2" static struct pci_device_id voodoo2_id_table[] = { { PCI_DEVICE(0x121a, 0x0002) }, { 0,} }; MODULE_DEVICE_TABLE(pci, voodoo2_id_table); static int voodoo2_probe(struct pci_dev *pdev, const struct pci_device_id *ent); static void voodoo2_remove(struct pci_dev *pdev); /* Module registration structure */ static struct pci_driver voodoo2 = { .name = VOODOO2, .id_table = voodoo2_id_table, .probe = voodoo2_probe, .remove = voodoo2_remove }; /* This is a "private" data structure */ /* You can store there any data that should be passed between module's functions */ struct voodoo2_priv { volatile u32 __iomem *hwmem; }; /* */ static int __init voodoo2_module_init(void) { return pci_register_driver(&voodoo2); } static void __exit voodoo2_module_exit(void) { pci_unregister_driver(&voodoo2); } void release_device(struct pci_dev *pdev); void release_device(struct pci_dev *pdev) { /* Free memory region */ pci_release_region(pdev, pci_select_bars(pdev, IORESOURCE_MEM)); /* And disable device */ pci_disable_device(pdev); } struct name_int { const char * name; int offset; }; #define STATUS 0x000 #define FBIINIT0 0x210 #define FBIINIT1 0x214 #define FBIINIT2 0x218 #define FBIINIT3 0x21c #define FBIINIT4 0x200 #define FBIINIT5 0x244 #define FBIINIT6 0x248 #define FBIINIT7 0x24c #define DACDATA 0x22c #define LFBMODE 0x114 #define BACKPORCH 0x208 #define VIDEODIMENSIONS 0x20c #define HSYNC 0x220 #define VSYNC 0x224 const struct name_int registers[] = { {"status", STATUS}, {"fbiInit0", FBIINIT0}, {"fbiInit1", FBIINIT1}, {"fbiInit2", FBIINIT2}, {"fbiInit3", FBIINIT3}, {"fbiInit4", FBIINIT4}, {"fbiInit5", FBIINIT5}, {"fbiInit6", FBIINIT6}, {"fbiInit7", FBIINIT7}, // {"backPorch", BACKPORCH}, {"videoDimensions", VIDEODIMENSIONS}, {"hSync", HSYNC}, {"vSync", VSYNC}, }; static inline void dac_data(volatile uint32_t * base, int data, int rs, int read) { int value = 0 | ((data & 0xff) << 0) | ((rs & 0b111) << 8) | ((read & 0b1) << 11); //printk(KERN_INFO "DACDATA = %08x\n", value); base[DACDATA / 4] = value; } #define DAC__RS__PIXEL_ADDRESS_WRITE 0b000 // TDV_GENDAC_WR #define DAC__RS__PIXEL_ADDRESS_READ 0b011 // TDV_GENDAC_RD #define DAC__RS__COLOR_VALUE 0b001 // TDV_GENDAC_LUT #define DAC__RS__PIXEL_MASK 0b010 // TDV_GENDAC_PIXMASK #define DAC__RS__PLL_ADDRESS_WRITE 0b100 // TDV_GENDAC_PLLWR #define DAC__RS__PLL_PARAMETER 0b101 // TDV_GENDAC_PLLDATA #define DAC__RS__COMMAND 0b110 // TDV_GENDAC_CMD #define DAC__RS__PLL_ADDRESS_READ 0b111 // TDV_GENDAC_PLLRD #define CLK0_f0_PLL 0x00 #define CLK0_f1_PLL 0x01 #define CLK0_f2_PLL 0x02 #define CLK0_f3_PLL 0x03 #define CLK0_f4_PLL 0x04 #define CLK0_f5_PLL 0x05 #define CLK0_f6_PLL 0x06 #define CLK0_f7_PLL 0x07 #define CLK1_fA_PLL 0x0A #define CLK1_fB_PLL 0x0B #define PLL_CONTROL 0x0E const struct name_int dac_registers[] = { {"CLK0_f0_PLL", CLK0_f0_PLL}, {"CLK0_f1_PLL", CLK0_f1_PLL}, {"CLK0_f2_PLL", CLK0_f2_PLL}, {"CLK0_f3_PLL", CLK0_f3_PLL}, {"CLK0_f4_PLL", CLK0_f4_PLL}, {"CLK0_f5_PLL", CLK0_f5_PLL}, {"CLK0_f6_PLL", CLK0_f6_PLL}, {"CLK0_f7_PLL", CLK0_f7_PLL}, {"CLK1_fA_PLL", CLK1_fA_PLL}, {"CLK1_fB_PLL", CLK1_fB_PLL}, {"PLL_CONTROL", PLL_CONTROL}, }; #define MAXLOOP 4096 static void busy_wait(volatile uint32_t * base) { uint32_t x, cnt; cnt = 0; for (x = 0; x < MAXLOOP; x++) { if (base[STATUS / 4] & (1 << 7)) // busy cnt = 0; else cnt++; if (cnt >= 5) /* Voodoo2 specs suggest at least 3 */ break; } if (x == MAXLOOP) printk(KERN_INFO "wait timeout\n"); } static inline void dac_write_8(volatile uint32_t * base, int param_address, int m) { dac_data(base, param_address, DAC__RS__PLL_ADDRESS_WRITE, 0); busy_wait(base); dac_data(base, m, DAC__RS__PLL_PARAMETER, 0); busy_wait(base); } static inline void dac_write_16(volatile uint32_t * base, int param_address, int m, int n) { dac_data(base, param_address, DAC__RS__PLL_ADDRESS_WRITE, 0); busy_wait(base); dac_data(base, m, DAC__RS__PLL_PARAMETER, 0); busy_wait(base); dac_data(base, n, DAC__RS__PLL_PARAMETER, 0); busy_wait(base); } struct m_n { uint8_t m; uint8_t n; }; static inline struct m_n dac_read_16(volatile uint32_t * base, int param_address) { dac_data(base, param_address, DAC__RS__PLL_ADDRESS_READ, 0); busy_wait(base); dac_data(base, 0, DAC__RS__PLL_PARAMETER, 1); busy_wait(base); uint32_t m = base[FBIINIT2 / 4] & 0xff; dac_data(base, 0, DAC__RS__PLL_PARAMETER, 1); busy_wait(base); uint32_t n = base[FBIINIT2 / 4] & 0xff; return (struct m_n){ m, n }; } /* This function is called by the kernel */ static int voodoo2_probe(struct pci_dev *pdev, const struct pci_device_id *ent) { int bar, err; u16 vendor, device; unsigned long mmio_start, mmio_len; struct voodoo2_priv *drv_priv; /* Let's read data from the PCI device configuration registers */ pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor); pci_read_config_word(pdev, PCI_DEVICE_ID, &device); printk(KERN_INFO "vid: %04x pid: %04x\n", vendor, device); uint32_t init_enable; init_enable = 0 | (1 << 2) // enable writes to hardware initialization registers | (1 << 0); // remap fbiinit2, fbiinit3 to dacread, videochecksum pci_write_config_dword(pdev, 0x40, init_enable); pci_read_config_dword(pdev, 0x40, &init_enable); printk(KERN_INFO "init_enable: %08x\n", init_enable); /* Request IO BAR */ bar = pci_select_bars(pdev, IORESOURCE_MEM); /* Enable device memory */ err = pci_enable_device_mem(pdev); if (err) { printk(KERN_INFO "pci_enable_device_mem error\n"); return err; } /* Request memory region for the BAR */ err = pci_request_region(pdev, bar, VOODOO2); if (err) { printk(KERN_INFO "pci_request_region error\n"); pci_disable_device(pdev); return err; } /* Get start and stop memory offsets */ mmio_start = pci_resource_start(pdev, 0); mmio_len = pci_resource_len(pdev, 0); printk(KERN_INFO "mmio_start %p mmio_len %p\n", (void*)mmio_start, (void*)mmio_len); /* Allocate memory for the module private data */ drv_priv = kzalloc(sizeof(struct voodoo2_priv), GFP_KERNEL); if (!drv_priv) { release_device(pdev); return -ENOMEM; } /* Remap BAR to the local pointer */ drv_priv->hwmem = ioremap(mmio_start, mmio_len); if (!drv_priv->hwmem) { release_device(pdev); return -EIO; } /* Set module private data */ /* Now we can access mapped "hwmem" from the any module's function */ pci_set_drvdata(pdev, drv_priv); for (int i = 0; i < (sizeof (registers)) / (sizeof (registers[0])); i++) { printk(KERN_INFO "%s %08x\n", registers[i].name, drv_priv->hwmem[registers[i].offset / 4]); } //return 0; init_enable = 0 | (1 << 0); // enable writes to hardware initialization registers pci_write_config_dword(pdev, 0x40, init_enable); busy_wait(drv_priv->hwmem); drv_priv->hwmem[FBIINIT1 / 4] = (1 << 8); // 8: Video Timing Reset (0=run, 1=reset). busy_wait(drv_priv->hwmem); drv_priv->hwmem[FBIINIT0 / 4] = (1 << 1) // 1: Chuck Graphics Reset | (1 << 2); // 2: Chuck FIFO Reset busy_wait(drv_priv->hwmem); drv_priv->hwmem[FBIINIT2 / 4] &= (1 << 22); // 22: dram refresh disable busy_wait(drv_priv->hwmem); init_enable = 0 | (1 << 2) // remap fbiinit2, fbiinit3 to dacread, videochecksum | (1 << 0); // enable writes to hardware initialization registers pci_write_config_dword(pdev, 0x40, init_enable); busy_wait(drv_priv->hwmem); for (int i = 0; i < (sizeof (dac_registers)) / (sizeof (dac_registers[0])); i++) { struct m_n res = dac_read_16(drv_priv->hwmem, dac_registers[i].offset); printk(KERN_INFO "dac read1: %s: m: %02x n: %02x\n", dac_registers[i].name, res.m, res.n); } /* pixel clock: 40MHz graphics/memory clock: 44.6MHz */ dac_data(drv_priv->hwmem, (0b0101 << 4), DAC__RS__COMMAND, 0); //write busy_wait(drv_priv->hwmem); dac_data(drv_priv->hwmem, 0, DAC__RS__COMMAND, 1); //read busy_wait(drv_priv->hwmem); uint32_t command = drv_priv->hwmem[FBIINIT2 / 4] & 0xff; printk(KERN_INFO "dac read2: RS__COMMAND: m: %02x\n", command); int m = 54; int n = 67; dac_write_16(drv_priv->hwmem, CLK0_f0_PLL, m, n); struct m_n res0 = dac_read_16(drv_priv->hwmem, CLK0_f0_PLL); printk(KERN_INFO "dac read2: CLK0_f0_PLL: m: %02x n: %02x\n", res0.m, res0.n); uint8_t pll_control = 0 | (0 << 0) // internal select: f0 | (0 << 4) // clk1 sel: fA | (1 << 5); // enable incs: 1 dac_write_8(drv_priv->hwmem, PLL_CONTROL, pll_control); struct m_n res = dac_read_16(drv_priv->hwmem, PLL_CONTROL); printk(KERN_INFO "dac read2: PLL_CONTROL: m: %02x n: %02x\n", res.m, res.n); /* fbiInit0 00000410 4: Stall PCI enable for High Water Mark 6: (0x10) PCI FIFO Empty Entries Low Water Mark fbiInit1 00201102 1: One wait state 8: Video Timing Reset 12: Software Blanking Enable 20: (0x2) vid_clk_2x_sel fbiInit2 80000002 1: DRAM banking configuration (0=128Kx16 banking, 1=256Kx16 banking) 23: 0x100 Refresh_Load value fbiInit3 00000000 fbiInit4 00000001 0: Wait state cycles for PCI read accesses (0=1 ws, 1=2 ws). fbiInit5 00000128 fbiInit6 001e6000 fbiInit7 000000aa */ int x_tiles = 832 / 32; // fbinit0 int fbiinit0 = 0 | (1 << 0) // no VGA passthrough | (0 << 1) // no chuck graphics reset | (0 << 2) // no chuck fifo reset | (0 << 3) // no byte swizzle | (1 << 4) // Stall PCI enable for High Water Mark | (0x10 << 6) // PCI FIFO Empty Entries Low Water Mark ; // fbiinit1 int fbiinit1 = 0 //| (1 << 1) // one wait state | (1 << 3) // enable linear framebuffer reads | (((x_tiles >> 1) & 0b1111) << 4) // tiles x [4:1] | (1 << 13) // drive data output | (1 << 14) // drive blank output | (1 << 15) // drive hsync/vsync output | (1 << 16) // drive dclk output | (0 << 17) // Video timing vclk input select : vid_clk_2x | (0x2 << 20) // Video timing vclk source select : vid_clk_2x | (((x_tiles >> 5) & 0b1) << 24) // tiles x [5] ; int fbiinit2 = 0 | (1 << 1) // DRAM banking configuration (0=128Kx16 banking, 1=256Kx16 banking) | (1 << 5) // fast RAS | (1 << 6) // DRAM_OE | (247 << 11) // video buffer offset | (1 << 21) // FIFO RDA | (1 << 22) // Refresh Enable (0=disable, 1=enable). Default is 0. | (0x30 << 23) // Refresh_Load Value. ; int fbiinit3 = 0 | (1 << 6) // trex dis ; int fbiinit4 = 0 | (1 << 1) // lfb rda ; int fbiinit5 = 0 | 0x00000128 | (1 << 23) | (1 << 24) ; int fbiinit6 = 0 | (x_tiles & 1) << 30; // tiles x [0] int yHeight = 600; int xWidth = 800; // sc->sc_videomode->vtotal - sc->sc_videomode->vsync_end int vBackPorch = 628 - 605; // sc->sc_videomode->htotal - sc->sc_videomode->hsync_end int hBackPorch = 1056 - 968; // sc->sc_videomode->vsync_end - sc->sc_videomode->vsync_start int vSyncOn = 605 - 601; // sc->sc_videomode->hsync_end - sc->sc_videomode->hsync_start int hSyncOn = 968 - 840; // sc->sc_videomode->vtotal - vsyncon int vSyncOff = 628 - vSyncOn; // sc->sc_videomode->htotal - hsyncon int hSyncOff = 1056 - hSyncOn; int hSync = 0 | (((hSyncOn - 1) & 0x1ff) << 0) | ((hSyncOff & 0x3ff) << 16) ; int vSync = 0 | ((vSyncOn & 0x1fff) << 0) | ((vSyncOff & 0x1fff) << 16) ; int backPorch = 0 | (((hBackPorch - 2) & 0x1ff) << 0) | ((vBackPorch & 0x1ff) << 16) ; int videoDimensions = 0 | (((xWidth - 1) & 0x7ff) << 0) | ((yHeight & 0x7ff) << 16) ; drv_priv->hwmem[HSYNC / 4] = hSync; busy_wait(drv_priv->hwmem); drv_priv->hwmem[VSYNC / 4] = vSync; busy_wait(drv_priv->hwmem); drv_priv->hwmem[BACKPORCH / 4] = backPorch; busy_wait(drv_priv->hwmem); drv_priv->hwmem[VIDEODIMENSIONS / 4] = videoDimensions; busy_wait(drv_priv->hwmem); int lfbMode = 0; drv_priv->hwmem[LFBMODE / 4] = lfbMode; busy_wait(drv_priv->hwmem); init_enable = 0 | (1 << 0); // enable writes to hardware initialization registers pci_write_config_dword(pdev, 0x40, init_enable); busy_wait(drv_priv->hwmem); drv_priv->hwmem[FBIINIT0 / 4] = fbiinit0; drv_priv->hwmem[FBIINIT1 / 4] = fbiinit1; drv_priv->hwmem[FBIINIT2 / 4] = fbiinit2; drv_priv->hwmem[FBIINIT3 / 4] = fbiinit3; drv_priv->hwmem[FBIINIT4 / 4] = fbiinit4; drv_priv->hwmem[FBIINIT5 / 4] = fbiinit5; drv_priv->hwmem[FBIINIT6 / 4] = fbiinit6; busy_wait(drv_priv->hwmem); init_enable = 0 | (1 << 1); // Enable writes to PCI FIFO pci_write_config_dword(pdev, 0x40, init_enable); busy_wait(drv_priv->hwmem); pci_write_config_dword(pdev, 0xC0, 0); // VCLK_ENABLE_REG for (int i = 0; i < (sizeof (registers)) / (sizeof (registers[0])); i++) { printk(KERN_INFO "read2: %s %08x\n", registers[i].name, drv_priv->hwmem[registers[i].offset / 4]); } printk(KERN_INFO "hsync: %08x vsync: %08x\n", hSync, vSync); #define rgb(r, g, b) (((r) << 11) | ((g) << 5) | ((b) << 0)) static const uint16_t panda[] __attribute__((aligned(4))) = { //#embed "panda.rgb565.txt" #include "panda.txt" }; const int colors[] = { rgb(31, 0, 0), rgb(0, 63, 0), rgb(0, 0, 31), rgb(31, 63, 0), // yellow rgb(31, 0, 31), // magenta rgb(0, 63, 31), // cyan rgb(31, 31, 0), // orange rgb(31, 0, 15), // pink rgb(0, 31, 31), // light blue rgb(0, 63, 15), // light green }; if (0) { for (int y = 0; y < 600; y++) { for (int x = 0; x < 1024; x++) { ((volatile uint16_t *)drv_priv->hwmem)[(0x0400000 / 2) + y * 1024 + x] = 0 | colors[(y / 32) % 10]; if (0) { ((volatile uint16_t *)drv_priv->hwmem)[(0x0400000 / 2) + y * 1024 + x] = 0 | rgb(0, 63, 0); } } busy_wait(drv_priv->hwmem); } for (int y = 0; y < 600; y++) { ((volatile uint16_t *)drv_priv->hwmem)[(0x0400000 / 2) + y * 1024 + 10] = 0 | rgb(0, 0, 31); ((volatile uint16_t *)drv_priv->hwmem)[(0x0400000 / 2) + y * 1024 + 790] = 0 | rgb(0, 63, 0); } } for (int y = 0; y < 600; y++) { for (int x = 0; x < 800; x++) { ((volatile uint16_t *)drv_priv->hwmem)[(0x0400000 / 2) + y * 1024 + x] = ((uint16_t*)panda)[y * 800 + x]; } } return 0; } /* Clean up */ static void voodoo2_remove(struct pci_dev *pdev) { struct voodoo2_priv *drv_priv = pci_get_drvdata(pdev); if (drv_priv) { if (drv_priv->hwmem) { iounmap(drv_priv->hwmem); } kfree(drv_priv); } release_device(pdev); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Zachary Buhman "); MODULE_DESCRIPTION("Voodoo2 module"); MODULE_VERSION("0.1"); module_init(voodoo2_module_init); module_exit(voodoo2_module_exit);