18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * MTD map driver for BIOS Flash on Intel SCB2 boards
48c2ecf20Sopenharmony_ci * Copyright (C) 2002 Sun Microsystems, Inc.
58c2ecf20Sopenharmony_ci * Tim Hockin <thockin@sun.com>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * A few notes on this MTD map:
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * This was developed with a small number of SCB2 boards to test on.
108c2ecf20Sopenharmony_ci * Hopefully, Intel has not introducted too many unaccounted variables in the
118c2ecf20Sopenharmony_ci * making of this board.
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci * The BIOS marks its own memory region as 'reserved' in the e820 map.  We
148c2ecf20Sopenharmony_ci * try to request it here, but if it fails, we carry on anyway.
158c2ecf20Sopenharmony_ci *
168c2ecf20Sopenharmony_ci * This is how the chip is attached, so said the schematic:
178c2ecf20Sopenharmony_ci * * a 4 MiB (32 Mib) 16 bit chip
188c2ecf20Sopenharmony_ci * * a 1 MiB memory region
198c2ecf20Sopenharmony_ci * * A20 and A21 pulled up
208c2ecf20Sopenharmony_ci * * D8-D15 ignored
218c2ecf20Sopenharmony_ci * What this means is that, while we are addressing bytes linearly, we are
228c2ecf20Sopenharmony_ci * really addressing words, and discarding the other byte.  This means that
238c2ecf20Sopenharmony_ci * the chip MUST BE at least 2 MiB.  This also means that every block is
248c2ecf20Sopenharmony_ci * actually half as big as the chip reports.  It also means that accesses of
258c2ecf20Sopenharmony_ci * logical address 0 hit higher-address sections of the chip, not physical 0.
268c2ecf20Sopenharmony_ci * One can only hope that these 4MiB x16 chips were a lot cheaper than 1MiB x8
278c2ecf20Sopenharmony_ci * chips.
288c2ecf20Sopenharmony_ci *
298c2ecf20Sopenharmony_ci * This driver assumes the chip is not write-protected by an external signal.
308c2ecf20Sopenharmony_ci * As of the this writing, that is true, but may change, just to spite me.
318c2ecf20Sopenharmony_ci *
328c2ecf20Sopenharmony_ci * The actual BIOS layout has been mostly reverse engineered.  Intel BIOS
338c2ecf20Sopenharmony_ci * updates for this board include 10 related (*.bio - &.bi9) binary files and
348c2ecf20Sopenharmony_ci * another separate (*.bbo) binary file.  The 10 files are 64k of data + a
358c2ecf20Sopenharmony_ci * small header.  If the headers are stripped off, the 10 64k files can be
368c2ecf20Sopenharmony_ci * concatenated into a 640k image.  This is your BIOS image, proper.  The
378c2ecf20Sopenharmony_ci * separate .bbo file also has a small header.  It is the 'Boot Block'
388c2ecf20Sopenharmony_ci * recovery BIOS.  Once the header is stripped, no further prep is needed.
398c2ecf20Sopenharmony_ci * As best I can tell, the BIOS is arranged as such:
408c2ecf20Sopenharmony_ci * offset 0x00000 to 0x4ffff (320k):  unknown - SCSI BIOS, etc?
418c2ecf20Sopenharmony_ci * offset 0x50000 to 0xeffff (640k):  BIOS proper
428c2ecf20Sopenharmony_ci * offset 0xf0000 ty 0xfffff (64k):   Boot Block region
438c2ecf20Sopenharmony_ci *
448c2ecf20Sopenharmony_ci * Intel's BIOS update program flashes the BIOS and Boot Block in separate
458c2ecf20Sopenharmony_ci * steps.  Probably a wise thing to do.
468c2ecf20Sopenharmony_ci */
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci#include <linux/module.h>
498c2ecf20Sopenharmony_ci#include <linux/types.h>
508c2ecf20Sopenharmony_ci#include <linux/kernel.h>
518c2ecf20Sopenharmony_ci#include <asm/io.h>
528c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h>
538c2ecf20Sopenharmony_ci#include <linux/mtd/map.h>
548c2ecf20Sopenharmony_ci#include <linux/mtd/cfi.h>
558c2ecf20Sopenharmony_ci#include <linux/pci.h>
568c2ecf20Sopenharmony_ci#include <linux/pci_ids.h>
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#define MODNAME		"scb2_flash"
598c2ecf20Sopenharmony_ci#define SCB2_ADDR	0xfff00000
608c2ecf20Sopenharmony_ci#define SCB2_WINDOW	0x00100000
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic void __iomem *scb2_ioaddr;
648c2ecf20Sopenharmony_cistatic struct mtd_info *scb2_mtd;
658c2ecf20Sopenharmony_cistatic struct map_info scb2_map = {
668c2ecf20Sopenharmony_ci	.name =      "SCB2 BIOS Flash",
678c2ecf20Sopenharmony_ci	.size =      0,
688c2ecf20Sopenharmony_ci	.bankwidth =  1,
698c2ecf20Sopenharmony_ci};
708c2ecf20Sopenharmony_cistatic int region_fail;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic int scb2_fixup_mtd(struct mtd_info *mtd)
738c2ecf20Sopenharmony_ci{
748c2ecf20Sopenharmony_ci	int i;
758c2ecf20Sopenharmony_ci	int done = 0;
768c2ecf20Sopenharmony_ci	struct map_info *map = mtd->priv;
778c2ecf20Sopenharmony_ci	struct cfi_private *cfi = map->fldrv_priv;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	/* barf if this doesn't look right */
808c2ecf20Sopenharmony_ci	if (cfi->cfiq->InterfaceDesc != CFI_INTERFACE_X16_ASYNC) {
818c2ecf20Sopenharmony_ci		printk(KERN_ERR MODNAME ": unsupported InterfaceDesc: %#x\n",
828c2ecf20Sopenharmony_ci		    cfi->cfiq->InterfaceDesc);
838c2ecf20Sopenharmony_ci		return -1;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	/* I wasn't here. I didn't see. dwmw2. */
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	/* the chip is sometimes bigger than the map - what a waste */
898c2ecf20Sopenharmony_ci	mtd->size = map->size;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/*
928c2ecf20Sopenharmony_ci	 * We only REALLY get half the chip, due to the way it is
938c2ecf20Sopenharmony_ci	 * wired up - D8-D15 are tossed away.  We read linear bytes,
948c2ecf20Sopenharmony_ci	 * but in reality we are getting 1/2 of each 16-bit read,
958c2ecf20Sopenharmony_ci	 * which LOOKS linear to us.  Because CFI code accounts for
968c2ecf20Sopenharmony_ci	 * things like lock/unlock/erase by eraseregions, we need to
978c2ecf20Sopenharmony_ci	 * fudge them to reflect this.  Erases go like this:
988c2ecf20Sopenharmony_ci	 *   * send an erase to an address
998c2ecf20Sopenharmony_ci	 *   * the chip samples the address and erases the block
1008c2ecf20Sopenharmony_ci	 *   * add the block erasesize to the address and repeat
1018c2ecf20Sopenharmony_ci	 *   -- the problem is that addresses are 16-bit addressable
1028c2ecf20Sopenharmony_ci	 *   -- we end up erasing every-other block
1038c2ecf20Sopenharmony_ci	 */
1048c2ecf20Sopenharmony_ci	mtd->erasesize /= 2;
1058c2ecf20Sopenharmony_ci	for (i = 0; i < mtd->numeraseregions; i++) {
1068c2ecf20Sopenharmony_ci		struct mtd_erase_region_info *region = &mtd->eraseregions[i];
1078c2ecf20Sopenharmony_ci		region->erasesize /= 2;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	/*
1118c2ecf20Sopenharmony_ci	 * If the chip is bigger than the map, it is wired with the high
1128c2ecf20Sopenharmony_ci	 * address lines pulled up.  This makes us access the top portion of
1138c2ecf20Sopenharmony_ci	 * the chip, so all our erase-region info is wrong.  Start cutting from
1148c2ecf20Sopenharmony_ci	 * the bottom.
1158c2ecf20Sopenharmony_ci	 */
1168c2ecf20Sopenharmony_ci	for (i = 0; !done && i < mtd->numeraseregions; i++) {
1178c2ecf20Sopenharmony_ci		struct mtd_erase_region_info *region = &mtd->eraseregions[i];
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci		if (region->numblocks * region->erasesize > mtd->size) {
1208c2ecf20Sopenharmony_ci			region->numblocks = ((unsigned long)mtd->size /
1218c2ecf20Sopenharmony_ci						region->erasesize);
1228c2ecf20Sopenharmony_ci			done = 1;
1238c2ecf20Sopenharmony_ci		} else {
1248c2ecf20Sopenharmony_ci			region->numblocks = 0;
1258c2ecf20Sopenharmony_ci		}
1268c2ecf20Sopenharmony_ci		region->offset = 0;
1278c2ecf20Sopenharmony_ci	}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	return 0;
1308c2ecf20Sopenharmony_ci}
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci/* CSB5's 'Function Control Register' has bits for decoding @ >= 0xffc00000 */
1338c2ecf20Sopenharmony_ci#define CSB5_FCR	0x41
1348c2ecf20Sopenharmony_ci#define CSB5_FCR_DECODE_ALL 0x0e
1358c2ecf20Sopenharmony_cistatic int scb2_flash_probe(struct pci_dev *dev,
1368c2ecf20Sopenharmony_ci			    const struct pci_device_id *ent)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	u8 reg;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* enable decoding of the flash region in the south bridge */
1418c2ecf20Sopenharmony_ci	pci_read_config_byte(dev, CSB5_FCR, &reg);
1428c2ecf20Sopenharmony_ci	pci_write_config_byte(dev, CSB5_FCR, reg | CSB5_FCR_DECODE_ALL);
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (!request_mem_region(SCB2_ADDR, SCB2_WINDOW, scb2_map.name)) {
1458c2ecf20Sopenharmony_ci		/*
1468c2ecf20Sopenharmony_ci		 * The BIOS seems to mark the flash region as 'reserved'
1478c2ecf20Sopenharmony_ci		 * in the e820 map.  Warn and go about our business.
1488c2ecf20Sopenharmony_ci		 */
1498c2ecf20Sopenharmony_ci		printk(KERN_WARNING MODNAME
1508c2ecf20Sopenharmony_ci		    ": warning - can't reserve rom window, continuing\n");
1518c2ecf20Sopenharmony_ci		region_fail = 1;
1528c2ecf20Sopenharmony_ci	}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	/* remap the IO window (w/o caching) */
1558c2ecf20Sopenharmony_ci	scb2_ioaddr = ioremap(SCB2_ADDR, SCB2_WINDOW);
1568c2ecf20Sopenharmony_ci	if (!scb2_ioaddr) {
1578c2ecf20Sopenharmony_ci		printk(KERN_ERR MODNAME ": Failed to ioremap window!\n");
1588c2ecf20Sopenharmony_ci		if (!region_fail)
1598c2ecf20Sopenharmony_ci			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
1608c2ecf20Sopenharmony_ci		return -ENOMEM;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	scb2_map.phys = SCB2_ADDR;
1648c2ecf20Sopenharmony_ci	scb2_map.virt = scb2_ioaddr;
1658c2ecf20Sopenharmony_ci	scb2_map.size = SCB2_WINDOW;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	simple_map_init(&scb2_map);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	/* try to find a chip */
1708c2ecf20Sopenharmony_ci	scb2_mtd = do_map_probe("cfi_probe", &scb2_map);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	if (!scb2_mtd) {
1738c2ecf20Sopenharmony_ci		printk(KERN_ERR MODNAME ": flash probe failed!\n");
1748c2ecf20Sopenharmony_ci		iounmap(scb2_ioaddr);
1758c2ecf20Sopenharmony_ci		if (!region_fail)
1768c2ecf20Sopenharmony_ci			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
1778c2ecf20Sopenharmony_ci		return -ENODEV;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	scb2_mtd->owner = THIS_MODULE;
1818c2ecf20Sopenharmony_ci	if (scb2_fixup_mtd(scb2_mtd) < 0) {
1828c2ecf20Sopenharmony_ci		mtd_device_unregister(scb2_mtd);
1838c2ecf20Sopenharmony_ci		map_destroy(scb2_mtd);
1848c2ecf20Sopenharmony_ci		iounmap(scb2_ioaddr);
1858c2ecf20Sopenharmony_ci		if (!region_fail)
1868c2ecf20Sopenharmony_ci			release_mem_region(SCB2_ADDR, SCB2_WINDOW);
1878c2ecf20Sopenharmony_ci		return -ENODEV;
1888c2ecf20Sopenharmony_ci	}
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	printk(KERN_NOTICE MODNAME ": chip size 0x%llx at offset 0x%llx\n",
1918c2ecf20Sopenharmony_ci	       (unsigned long long)scb2_mtd->size,
1928c2ecf20Sopenharmony_ci	       (unsigned long long)(SCB2_WINDOW - scb2_mtd->size));
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	mtd_device_register(scb2_mtd, NULL, 0);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	return 0;
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic void scb2_flash_remove(struct pci_dev *dev)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	if (!scb2_mtd)
2028c2ecf20Sopenharmony_ci		return;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/* disable flash writes */
2058c2ecf20Sopenharmony_ci	mtd_lock(scb2_mtd, 0, scb2_mtd->size);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	mtd_device_unregister(scb2_mtd);
2088c2ecf20Sopenharmony_ci	map_destroy(scb2_mtd);
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	iounmap(scb2_ioaddr);
2118c2ecf20Sopenharmony_ci	scb2_ioaddr = NULL;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	if (!region_fail)
2148c2ecf20Sopenharmony_ci		release_mem_region(SCB2_ADDR, SCB2_WINDOW);
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic struct pci_device_id scb2_flash_pci_ids[] = {
2188c2ecf20Sopenharmony_ci	{
2198c2ecf20Sopenharmony_ci	  .vendor = PCI_VENDOR_ID_SERVERWORKS,
2208c2ecf20Sopenharmony_ci	  .device = PCI_DEVICE_ID_SERVERWORKS_CSB5,
2218c2ecf20Sopenharmony_ci	  .subvendor = PCI_ANY_ID,
2228c2ecf20Sopenharmony_ci	  .subdevice = PCI_ANY_ID
2238c2ecf20Sopenharmony_ci	},
2248c2ecf20Sopenharmony_ci	{ 0, }
2258c2ecf20Sopenharmony_ci};
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_cistatic struct pci_driver scb2_flash_driver = {
2288c2ecf20Sopenharmony_ci	.name =     "Intel SCB2 BIOS Flash",
2298c2ecf20Sopenharmony_ci	.id_table = scb2_flash_pci_ids,
2308c2ecf20Sopenharmony_ci	.probe =    scb2_flash_probe,
2318c2ecf20Sopenharmony_ci	.remove =   scb2_flash_remove,
2328c2ecf20Sopenharmony_ci};
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cimodule_pci_driver(scb2_flash_driver);
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2378c2ecf20Sopenharmony_ciMODULE_AUTHOR("Tim Hockin <thockin@sun.com>");
2388c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for Intel SCB2 BIOS Flash");
2398c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, scb2_flash_pci_ids);
240