18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * BIOS Flash chip on Intel 440GX board. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Bugs this currently does not work under linuxBIOS. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/pci.h> 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <asm/io.h> 138c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 148c2ecf20Sopenharmony_ci#include <linux/mtd/map.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define PIIXE_IOBASE_RESOURCE 11 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#define WINDOW_ADDR 0xfff00000 198c2ecf20Sopenharmony_ci#define WINDOW_SIZE 0x00100000 208c2ecf20Sopenharmony_ci#define BUSWIDTH 1 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic u32 iobase; 238c2ecf20Sopenharmony_ci#define IOBASE iobase 248c2ecf20Sopenharmony_ci#define TRIBUF_PORT (IOBASE+0x37) 258c2ecf20Sopenharmony_ci#define VPP_PORT (IOBASE+0x28) 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic struct mtd_info *mymtd; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* Is this really the vpp port? */ 318c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(l440gx_vpp_lock); 328c2ecf20Sopenharmony_cistatic int l440gx_vpp_refcnt; 338c2ecf20Sopenharmony_cistatic void l440gx_set_vpp(struct map_info *map, int vpp) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci unsigned long flags; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci spin_lock_irqsave(&l440gx_vpp_lock, flags); 388c2ecf20Sopenharmony_ci if (vpp) { 398c2ecf20Sopenharmony_ci if (++l440gx_vpp_refcnt == 1) /* first nested 'on' */ 408c2ecf20Sopenharmony_ci outl(inl(VPP_PORT) | 1, VPP_PORT); 418c2ecf20Sopenharmony_ci } else { 428c2ecf20Sopenharmony_ci if (--l440gx_vpp_refcnt == 0) /* last nested 'off' */ 438c2ecf20Sopenharmony_ci outl(inl(VPP_PORT) & ~1, VPP_PORT); 448c2ecf20Sopenharmony_ci } 458c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&l440gx_vpp_lock, flags); 468c2ecf20Sopenharmony_ci} 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_cistatic struct map_info l440gx_map = { 498c2ecf20Sopenharmony_ci .name = "L440GX BIOS", 508c2ecf20Sopenharmony_ci .size = WINDOW_SIZE, 518c2ecf20Sopenharmony_ci .bankwidth = BUSWIDTH, 528c2ecf20Sopenharmony_ci .phys = WINDOW_ADDR, 538c2ecf20Sopenharmony_ci#if 0 548c2ecf20Sopenharmony_ci /* FIXME verify that this is the 558c2ecf20Sopenharmony_ci * appripriate code for vpp enable/disable 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_ci .set_vpp = l440gx_set_vpp 588c2ecf20Sopenharmony_ci#endif 598c2ecf20Sopenharmony_ci}; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int __init init_l440gx(void) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci struct pci_dev *dev, *pm_dev; 648c2ecf20Sopenharmony_ci struct resource *pm_iobase; 658c2ecf20Sopenharmony_ci __u16 word; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_INTEL, 688c2ecf20Sopenharmony_ci PCI_DEVICE_ID_INTEL_82371AB_0, NULL); 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci pm_dev = pci_get_device(PCI_VENDOR_ID_INTEL, 718c2ecf20Sopenharmony_ci PCI_DEVICE_ID_INTEL_82371AB_3, NULL); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci pci_dev_put(dev); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (!dev || !pm_dev) { 768c2ecf20Sopenharmony_ci printk(KERN_NOTICE "L440GX flash mapping: failed to find PIIX4 ISA bridge, cannot continue\n"); 778c2ecf20Sopenharmony_ci pci_dev_put(pm_dev); 788c2ecf20Sopenharmony_ci return -ENODEV; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci l440gx_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (!l440gx_map.virt) { 848c2ecf20Sopenharmony_ci printk(KERN_WARNING "Failed to ioremap L440GX flash region\n"); 858c2ecf20Sopenharmony_ci pci_dev_put(pm_dev); 868c2ecf20Sopenharmony_ci return -ENOMEM; 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci simple_map_init(&l440gx_map); 898c2ecf20Sopenharmony_ci pr_debug("window_addr = %p\n", l440gx_map.virt); 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci /* Setup the pm iobase resource 928c2ecf20Sopenharmony_ci * This code should move into some kind of generic bridge 938c2ecf20Sopenharmony_ci * driver but for the moment I'm content with getting the 948c2ecf20Sopenharmony_ci * allocation correct. 958c2ecf20Sopenharmony_ci */ 968c2ecf20Sopenharmony_ci pm_iobase = &pm_dev->resource[PIIXE_IOBASE_RESOURCE]; 978c2ecf20Sopenharmony_ci if (!(pm_iobase->flags & IORESOURCE_IO)) { 988c2ecf20Sopenharmony_ci pm_iobase->name = "pm iobase"; 998c2ecf20Sopenharmony_ci pm_iobase->start = 0; 1008c2ecf20Sopenharmony_ci pm_iobase->end = 63; 1018c2ecf20Sopenharmony_ci pm_iobase->flags = IORESOURCE_IO; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci /* Put the current value in the resource */ 1048c2ecf20Sopenharmony_ci pci_read_config_dword(pm_dev, 0x40, &iobase); 1058c2ecf20Sopenharmony_ci iobase &= ~1; 1068c2ecf20Sopenharmony_ci pm_iobase->start += iobase & ~1; 1078c2ecf20Sopenharmony_ci pm_iobase->end += iobase & ~1; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci pci_dev_put(pm_dev); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci /* Allocate the resource region */ 1128c2ecf20Sopenharmony_ci if (pci_assign_resource(pm_dev, PIIXE_IOBASE_RESOURCE) != 0) { 1138c2ecf20Sopenharmony_ci pci_dev_put(dev); 1148c2ecf20Sopenharmony_ci pci_dev_put(pm_dev); 1158c2ecf20Sopenharmony_ci printk(KERN_WARNING "Could not allocate pm iobase resource\n"); 1168c2ecf20Sopenharmony_ci iounmap(l440gx_map.virt); 1178c2ecf20Sopenharmony_ci return -ENXIO; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci /* Set the iobase */ 1218c2ecf20Sopenharmony_ci iobase = pm_iobase->start; 1228c2ecf20Sopenharmony_ci pci_write_config_dword(pm_dev, 0x40, iobase | 1); 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* Set XBCS# */ 1268c2ecf20Sopenharmony_ci pci_read_config_word(dev, 0x4e, &word); 1278c2ecf20Sopenharmony_ci word |= 0x4; 1288c2ecf20Sopenharmony_ci pci_write_config_word(dev, 0x4e, word); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* Supply write voltage to the chip */ 1318c2ecf20Sopenharmony_ci l440gx_set_vpp(&l440gx_map, 1); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci /* Enable the gate on the WE line */ 1348c2ecf20Sopenharmony_ci outb(inb(TRIBUF_PORT) & ~1, TRIBUF_PORT); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci printk(KERN_NOTICE "Enabled WE line to L440GX BIOS flash chip.\n"); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci mymtd = do_map_probe("jedec_probe", &l440gx_map); 1398c2ecf20Sopenharmony_ci if (!mymtd) { 1408c2ecf20Sopenharmony_ci printk(KERN_NOTICE "JEDEC probe on BIOS chip failed. Using ROM\n"); 1418c2ecf20Sopenharmony_ci mymtd = do_map_probe("map_rom", &l440gx_map); 1428c2ecf20Sopenharmony_ci } 1438c2ecf20Sopenharmony_ci if (mymtd) { 1448c2ecf20Sopenharmony_ci mymtd->owner = THIS_MODULE; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci mtd_device_register(mymtd, NULL, 0); 1478c2ecf20Sopenharmony_ci return 0; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci iounmap(l440gx_map.virt); 1518c2ecf20Sopenharmony_ci return -ENXIO; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic void __exit cleanup_l440gx(void) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci mtd_device_unregister(mymtd); 1578c2ecf20Sopenharmony_ci map_destroy(mymtd); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci iounmap(l440gx_map.virt); 1608c2ecf20Sopenharmony_ci} 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cimodule_init(init_l440gx); 1638c2ecf20Sopenharmony_cimodule_exit(cleanup_l440gx); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 1668c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 1678c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for BIOS chips on Intel L440GX motherboards"); 168