162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * BIOS Flash chip on Intel 440GX board. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Bugs this currently does not work under linuxBIOS. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/pci.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <asm/io.h> 1362306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1462306a36Sopenharmony_ci#include <linux/mtd/map.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define PIIXE_IOBASE_RESOURCE 11 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define WINDOW_ADDR 0xfff00000 1962306a36Sopenharmony_ci#define WINDOW_SIZE 0x00100000 2062306a36Sopenharmony_ci#define BUSWIDTH 1 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistatic u32 iobase; 2362306a36Sopenharmony_ci#define IOBASE iobase 2462306a36Sopenharmony_ci#define TRIBUF_PORT (IOBASE+0x37) 2562306a36Sopenharmony_ci#define VPP_PORT (IOBASE+0x28) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic struct mtd_info *mymtd; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci/* Is this really the vpp port? */ 3162306a36Sopenharmony_cistatic DEFINE_SPINLOCK(l440gx_vpp_lock); 3262306a36Sopenharmony_cistatic int l440gx_vpp_refcnt; 3362306a36Sopenharmony_cistatic void l440gx_set_vpp(struct map_info *map, int vpp) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci unsigned long flags; 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci spin_lock_irqsave(&l440gx_vpp_lock, flags); 3862306a36Sopenharmony_ci if (vpp) { 3962306a36Sopenharmony_ci if (++l440gx_vpp_refcnt == 1) /* first nested 'on' */ 4062306a36Sopenharmony_ci outl(inl(VPP_PORT) | 1, VPP_PORT); 4162306a36Sopenharmony_ci } else { 4262306a36Sopenharmony_ci if (--l440gx_vpp_refcnt == 0) /* last nested 'off' */ 4362306a36Sopenharmony_ci outl(inl(VPP_PORT) & ~1, VPP_PORT); 4462306a36Sopenharmony_ci } 4562306a36Sopenharmony_ci spin_unlock_irqrestore(&l440gx_vpp_lock, flags); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic struct map_info l440gx_map = { 4962306a36Sopenharmony_ci .name = "L440GX BIOS", 5062306a36Sopenharmony_ci .size = WINDOW_SIZE, 5162306a36Sopenharmony_ci .bankwidth = BUSWIDTH, 5262306a36Sopenharmony_ci .phys = WINDOW_ADDR, 5362306a36Sopenharmony_ci#if 0 5462306a36Sopenharmony_ci /* FIXME verify that this is the 5562306a36Sopenharmony_ci * appripriate code for vpp enable/disable 5662306a36Sopenharmony_ci */ 5762306a36Sopenharmony_ci .set_vpp = l440gx_set_vpp 5862306a36Sopenharmony_ci#endif 5962306a36Sopenharmony_ci}; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic int __init init_l440gx(void) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci struct pci_dev *dev, *pm_dev; 6462306a36Sopenharmony_ci struct resource *pm_iobase; 6562306a36Sopenharmony_ci __u16 word; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci dev = pci_get_device(PCI_VENDOR_ID_INTEL, 6862306a36Sopenharmony_ci PCI_DEVICE_ID_INTEL_82371AB_0, NULL); 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci pm_dev = pci_get_device(PCI_VENDOR_ID_INTEL, 7162306a36Sopenharmony_ci PCI_DEVICE_ID_INTEL_82371AB_3, NULL); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci pci_dev_put(dev); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (!dev || !pm_dev) { 7662306a36Sopenharmony_ci printk(KERN_NOTICE "L440GX flash mapping: failed to find PIIX4 ISA bridge, cannot continue\n"); 7762306a36Sopenharmony_ci pci_dev_put(pm_dev); 7862306a36Sopenharmony_ci return -ENODEV; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci l440gx_map.virt = ioremap(WINDOW_ADDR, WINDOW_SIZE); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci if (!l440gx_map.virt) { 8462306a36Sopenharmony_ci printk(KERN_WARNING "Failed to ioremap L440GX flash region\n"); 8562306a36Sopenharmony_ci pci_dev_put(pm_dev); 8662306a36Sopenharmony_ci return -ENOMEM; 8762306a36Sopenharmony_ci } 8862306a36Sopenharmony_ci simple_map_init(&l440gx_map); 8962306a36Sopenharmony_ci pr_debug("window_addr = %p\n", l440gx_map.virt); 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci /* Setup the pm iobase resource 9262306a36Sopenharmony_ci * This code should move into some kind of generic bridge 9362306a36Sopenharmony_ci * driver but for the moment I'm content with getting the 9462306a36Sopenharmony_ci * allocation correct. 9562306a36Sopenharmony_ci */ 9662306a36Sopenharmony_ci pm_iobase = &pm_dev->resource[PIIXE_IOBASE_RESOURCE]; 9762306a36Sopenharmony_ci if (!(pm_iobase->flags & IORESOURCE_IO)) { 9862306a36Sopenharmony_ci pm_iobase->name = "pm iobase"; 9962306a36Sopenharmony_ci pm_iobase->start = 0; 10062306a36Sopenharmony_ci pm_iobase->end = 63; 10162306a36Sopenharmony_ci pm_iobase->flags = IORESOURCE_IO; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci /* Put the current value in the resource */ 10462306a36Sopenharmony_ci pci_read_config_dword(pm_dev, 0x40, &iobase); 10562306a36Sopenharmony_ci iobase &= ~1; 10662306a36Sopenharmony_ci pm_iobase->start += iobase & ~1; 10762306a36Sopenharmony_ci pm_iobase->end += iobase & ~1; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci pci_dev_put(pm_dev); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci /* Allocate the resource region */ 11262306a36Sopenharmony_ci if (pci_assign_resource(pm_dev, PIIXE_IOBASE_RESOURCE) != 0) { 11362306a36Sopenharmony_ci pci_dev_put(dev); 11462306a36Sopenharmony_ci pci_dev_put(pm_dev); 11562306a36Sopenharmony_ci printk(KERN_WARNING "Could not allocate pm iobase resource\n"); 11662306a36Sopenharmony_ci iounmap(l440gx_map.virt); 11762306a36Sopenharmony_ci return -ENXIO; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci } 12062306a36Sopenharmony_ci /* Set the iobase */ 12162306a36Sopenharmony_ci iobase = pm_iobase->start; 12262306a36Sopenharmony_ci pci_write_config_dword(pm_dev, 0x40, iobase | 1); 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci /* Set XBCS# */ 12662306a36Sopenharmony_ci pci_read_config_word(dev, 0x4e, &word); 12762306a36Sopenharmony_ci word |= 0x4; 12862306a36Sopenharmony_ci pci_write_config_word(dev, 0x4e, word); 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci /* Supply write voltage to the chip */ 13162306a36Sopenharmony_ci l440gx_set_vpp(&l440gx_map, 1); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci /* Enable the gate on the WE line */ 13462306a36Sopenharmony_ci outb(inb(TRIBUF_PORT) & ~1, TRIBUF_PORT); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci printk(KERN_NOTICE "Enabled WE line to L440GX BIOS flash chip.\n"); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci mymtd = do_map_probe("jedec_probe", &l440gx_map); 13962306a36Sopenharmony_ci if (!mymtd) { 14062306a36Sopenharmony_ci printk(KERN_NOTICE "JEDEC probe on BIOS chip failed. Using ROM\n"); 14162306a36Sopenharmony_ci mymtd = do_map_probe("map_rom", &l440gx_map); 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci if (mymtd) { 14462306a36Sopenharmony_ci mymtd->owner = THIS_MODULE; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci mtd_device_register(mymtd, NULL, 0); 14762306a36Sopenharmony_ci return 0; 14862306a36Sopenharmony_ci } 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci iounmap(l440gx_map.virt); 15162306a36Sopenharmony_ci return -ENXIO; 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic void __exit cleanup_l440gx(void) 15562306a36Sopenharmony_ci{ 15662306a36Sopenharmony_ci mtd_device_unregister(mymtd); 15762306a36Sopenharmony_ci map_destroy(mymtd); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci iounmap(l440gx_map.virt); 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cimodule_init(init_l440gx); 16362306a36Sopenharmony_cimodule_exit(cleanup_l440gx); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 16662306a36Sopenharmony_ciMODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>"); 16762306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for BIOS chips on Intel L440GX motherboards"); 168