162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * amd76xrom.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Normal mappings of chips in physical memory
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include <asm/io.h>
1462306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1562306a36Sopenharmony_ci#include <linux/mtd/map.h>
1662306a36Sopenharmony_ci#include <linux/mtd/cfi.h>
1762306a36Sopenharmony_ci#include <linux/mtd/flashchip.h>
1862306a36Sopenharmony_ci#include <linux/pci.h>
1962306a36Sopenharmony_ci#include <linux/pci_ids.h>
2062306a36Sopenharmony_ci#include <linux/list.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci#define xstr(s) str(s)
2462306a36Sopenharmony_ci#define str(s) #s
2562306a36Sopenharmony_ci#define MOD_NAME xstr(KBUILD_BASENAME)
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define ADDRESS_NAME_LEN 18
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci#define ROM_PROBE_STEP_SIZE (64*1024) /* 64KiB */
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistruct amd76xrom_window {
3262306a36Sopenharmony_ci	void __iomem *virt;
3362306a36Sopenharmony_ci	unsigned long phys;
3462306a36Sopenharmony_ci	unsigned long size;
3562306a36Sopenharmony_ci	struct list_head maps;
3662306a36Sopenharmony_ci	struct resource rsrc;
3762306a36Sopenharmony_ci	struct pci_dev *pdev;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistruct amd76xrom_map_info {
4162306a36Sopenharmony_ci	struct list_head list;
4262306a36Sopenharmony_ci	struct map_info map;
4362306a36Sopenharmony_ci	struct mtd_info *mtd;
4462306a36Sopenharmony_ci	struct resource rsrc;
4562306a36Sopenharmony_ci	char map_name[sizeof(MOD_NAME) + 2 + ADDRESS_NAME_LEN];
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/* The 2 bits controlling the window size are often set to allow reading
4962306a36Sopenharmony_ci * the BIOS, but too small to allow writing, since the lock registers are
5062306a36Sopenharmony_ci * 4MiB lower in the address space than the data.
5162306a36Sopenharmony_ci *
5262306a36Sopenharmony_ci * This is intended to prevent flashing the bios, perhaps accidentally.
5362306a36Sopenharmony_ci *
5462306a36Sopenharmony_ci * This parameter allows the normal driver to over-ride the BIOS settings.
5562306a36Sopenharmony_ci *
5662306a36Sopenharmony_ci * The bits are 6 and 7.  If both bits are set, it is a 5MiB window.
5762306a36Sopenharmony_ci * If only the 7 Bit is set, it is a 4MiB window.  Otherwise, a
5862306a36Sopenharmony_ci * 64KiB window.
5962306a36Sopenharmony_ci *
6062306a36Sopenharmony_ci */
6162306a36Sopenharmony_cistatic uint win_size_bits;
6262306a36Sopenharmony_cimodule_param(win_size_bits, uint, 0);
6362306a36Sopenharmony_ciMODULE_PARM_DESC(win_size_bits, "ROM window size bits override for 0x43 byte, normally set by BIOS.");
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_cistatic struct amd76xrom_window amd76xrom_window = {
6662306a36Sopenharmony_ci	.maps = LIST_HEAD_INIT(amd76xrom_window.maps),
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic void amd76xrom_cleanup(struct amd76xrom_window *window)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	struct amd76xrom_map_info *map, *scratch;
7262306a36Sopenharmony_ci	u8 byte;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (window->pdev) {
7562306a36Sopenharmony_ci		/* Disable writes through the rom window */
7662306a36Sopenharmony_ci		pci_read_config_byte(window->pdev, 0x40, &byte);
7762306a36Sopenharmony_ci		pci_write_config_byte(window->pdev, 0x40, byte & ~1);
7862306a36Sopenharmony_ci		pci_dev_put(window->pdev);
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	/* Free all of the mtd devices */
8262306a36Sopenharmony_ci	list_for_each_entry_safe(map, scratch, &window->maps, list) {
8362306a36Sopenharmony_ci		if (map->rsrc.parent) {
8462306a36Sopenharmony_ci			release_resource(&map->rsrc);
8562306a36Sopenharmony_ci		}
8662306a36Sopenharmony_ci		mtd_device_unregister(map->mtd);
8762306a36Sopenharmony_ci		map_destroy(map->mtd);
8862306a36Sopenharmony_ci		list_del(&map->list);
8962306a36Sopenharmony_ci		kfree(map);
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci	if (window->rsrc.parent)
9262306a36Sopenharmony_ci		release_resource(&window->rsrc);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (window->virt) {
9562306a36Sopenharmony_ci		iounmap(window->virt);
9662306a36Sopenharmony_ci		window->virt = NULL;
9762306a36Sopenharmony_ci		window->phys = 0;
9862306a36Sopenharmony_ci		window->size = 0;
9962306a36Sopenharmony_ci		window->pdev = NULL;
10062306a36Sopenharmony_ci	}
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_cistatic int amd76xrom_init_one(struct pci_dev *pdev,
10562306a36Sopenharmony_ci			      const struct pci_device_id *ent)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	static char *rom_probe_types[] = { "cfi_probe", "jedec_probe", NULL };
10862306a36Sopenharmony_ci	u8 byte;
10962306a36Sopenharmony_ci	struct amd76xrom_window *window = &amd76xrom_window;
11062306a36Sopenharmony_ci	struct amd76xrom_map_info *map = NULL;
11162306a36Sopenharmony_ci	unsigned long map_top;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/* Remember the pci dev I find the window in - already have a ref */
11462306a36Sopenharmony_ci	window->pdev = pdev;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* Enable the selected rom window.  This is often incorrectly
11762306a36Sopenharmony_ci	 * set up by the BIOS, and the 4MiB offset for the lock registers
11862306a36Sopenharmony_ci	 * requires the full 5MiB of window space.
11962306a36Sopenharmony_ci	 *
12062306a36Sopenharmony_ci	 * This 'write, then read' approach leaves the bits for
12162306a36Sopenharmony_ci	 * other uses of the hardware info.
12262306a36Sopenharmony_ci	 */
12362306a36Sopenharmony_ci	pci_read_config_byte(pdev, 0x43, &byte);
12462306a36Sopenharmony_ci	pci_write_config_byte(pdev, 0x43, byte | win_size_bits );
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	/* Assume the rom window is properly setup, and find it's size */
12762306a36Sopenharmony_ci	pci_read_config_byte(pdev, 0x43, &byte);
12862306a36Sopenharmony_ci	if ((byte & ((1<<7)|(1<<6))) == ((1<<7)|(1<<6))) {
12962306a36Sopenharmony_ci		window->phys = 0xffb00000; /* 5MiB */
13062306a36Sopenharmony_ci	}
13162306a36Sopenharmony_ci	else if ((byte & (1<<7)) == (1<<7)) {
13262306a36Sopenharmony_ci		window->phys = 0xffc00000; /* 4MiB */
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	else {
13562306a36Sopenharmony_ci		window->phys = 0xffff0000; /* 64KiB */
13662306a36Sopenharmony_ci	}
13762306a36Sopenharmony_ci	window->size = 0xffffffffUL - window->phys + 1UL;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/*
14062306a36Sopenharmony_ci	 * Try to reserve the window mem region.  If this fails then
14162306a36Sopenharmony_ci	 * it is likely due to a fragment of the window being
14262306a36Sopenharmony_ci	 * "reserved" by the BIOS.  In the case that the
14362306a36Sopenharmony_ci	 * request_mem_region() fails then once the rom size is
14462306a36Sopenharmony_ci	 * discovered we will try to reserve the unreserved fragment.
14562306a36Sopenharmony_ci	 */
14662306a36Sopenharmony_ci	window->rsrc.name = MOD_NAME;
14762306a36Sopenharmony_ci	window->rsrc.start = window->phys;
14862306a36Sopenharmony_ci	window->rsrc.end   = window->phys + window->size - 1;
14962306a36Sopenharmony_ci	window->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
15062306a36Sopenharmony_ci	if (request_resource(&iomem_resource, &window->rsrc)) {
15162306a36Sopenharmony_ci		window->rsrc.parent = NULL;
15262306a36Sopenharmony_ci		printk(KERN_ERR MOD_NAME
15362306a36Sopenharmony_ci		       " %s(): Unable to register resource %pR - kernel bug?\n",
15462306a36Sopenharmony_ci		       __func__, &window->rsrc);
15562306a36Sopenharmony_ci		return -EBUSY;
15662306a36Sopenharmony_ci	}
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* Enable writes through the rom window */
16062306a36Sopenharmony_ci	pci_read_config_byte(pdev, 0x40, &byte);
16162306a36Sopenharmony_ci	pci_write_config_byte(pdev, 0x40, byte | 1);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	/* FIXME handle registers 0x80 - 0x8C the bios region locks */
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* For write accesses caches are useless */
16662306a36Sopenharmony_ci	window->virt = ioremap(window->phys, window->size);
16762306a36Sopenharmony_ci	if (!window->virt) {
16862306a36Sopenharmony_ci		printk(KERN_ERR MOD_NAME ": ioremap(%08lx, %08lx) failed\n",
16962306a36Sopenharmony_ci			window->phys, window->size);
17062306a36Sopenharmony_ci		goto out;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	/* Get the first address to look for an rom chip at */
17462306a36Sopenharmony_ci	map_top = window->phys;
17562306a36Sopenharmony_ci#if 1
17662306a36Sopenharmony_ci	/* The probe sequence run over the firmware hub lock
17762306a36Sopenharmony_ci	 * registers sets them to 0x7 (no access).
17862306a36Sopenharmony_ci	 * Probe at most the last 4M of the address space.
17962306a36Sopenharmony_ci	 */
18062306a36Sopenharmony_ci	if (map_top < 0xffc00000) {
18162306a36Sopenharmony_ci		map_top = 0xffc00000;
18262306a36Sopenharmony_ci	}
18362306a36Sopenharmony_ci#endif
18462306a36Sopenharmony_ci	/* Loop  through and look for rom chips */
18562306a36Sopenharmony_ci	while((map_top - 1) < 0xffffffffUL) {
18662306a36Sopenharmony_ci		struct cfi_private *cfi;
18762306a36Sopenharmony_ci		unsigned long offset;
18862306a36Sopenharmony_ci		int i;
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci		if (!map) {
19162306a36Sopenharmony_ci			map = kmalloc(sizeof(*map), GFP_KERNEL);
19262306a36Sopenharmony_ci			if (!map)
19362306a36Sopenharmony_ci				goto out;
19462306a36Sopenharmony_ci		}
19562306a36Sopenharmony_ci		memset(map, 0, sizeof(*map));
19662306a36Sopenharmony_ci		INIT_LIST_HEAD(&map->list);
19762306a36Sopenharmony_ci		map->map.name = map->map_name;
19862306a36Sopenharmony_ci		map->map.phys = map_top;
19962306a36Sopenharmony_ci		offset = map_top - window->phys;
20062306a36Sopenharmony_ci		map->map.virt = (void __iomem *)
20162306a36Sopenharmony_ci			(((unsigned long)(window->virt)) + offset);
20262306a36Sopenharmony_ci		map->map.size = 0xffffffffUL - map_top + 1UL;
20362306a36Sopenharmony_ci		/* Set the name of the map to the address I am trying */
20462306a36Sopenharmony_ci		sprintf(map->map_name, "%s @%08Lx",
20562306a36Sopenharmony_ci			MOD_NAME, (unsigned long long)map->map.phys);
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci		/* There is no generic VPP support */
20862306a36Sopenharmony_ci		for(map->map.bankwidth = 32; map->map.bankwidth;
20962306a36Sopenharmony_ci			map->map.bankwidth >>= 1)
21062306a36Sopenharmony_ci		{
21162306a36Sopenharmony_ci			char **probe_type;
21262306a36Sopenharmony_ci			/* Skip bankwidths that are not supported */
21362306a36Sopenharmony_ci			if (!map_bankwidth_supported(map->map.bankwidth))
21462306a36Sopenharmony_ci				continue;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci			/* Setup the map methods */
21762306a36Sopenharmony_ci			simple_map_init(&map->map);
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci			/* Try all of the probe methods */
22062306a36Sopenharmony_ci			probe_type = rom_probe_types;
22162306a36Sopenharmony_ci			for(; *probe_type; probe_type++) {
22262306a36Sopenharmony_ci				map->mtd = do_map_probe(*probe_type, &map->map);
22362306a36Sopenharmony_ci				if (map->mtd)
22462306a36Sopenharmony_ci					goto found;
22562306a36Sopenharmony_ci			}
22662306a36Sopenharmony_ci		}
22762306a36Sopenharmony_ci		map_top += ROM_PROBE_STEP_SIZE;
22862306a36Sopenharmony_ci		continue;
22962306a36Sopenharmony_ci	found:
23062306a36Sopenharmony_ci		/* Trim the size if we are larger than the map */
23162306a36Sopenharmony_ci		if (map->mtd->size > map->map.size) {
23262306a36Sopenharmony_ci			printk(KERN_WARNING MOD_NAME
23362306a36Sopenharmony_ci				" rom(%llu) larger than window(%lu). fixing...\n",
23462306a36Sopenharmony_ci				(unsigned long long)map->mtd->size, map->map.size);
23562306a36Sopenharmony_ci			map->mtd->size = map->map.size;
23662306a36Sopenharmony_ci		}
23762306a36Sopenharmony_ci		if (window->rsrc.parent) {
23862306a36Sopenharmony_ci			/*
23962306a36Sopenharmony_ci			 * Registering the MTD device in iomem may not be possible
24062306a36Sopenharmony_ci			 * if there is a BIOS "reserved" and BUSY range.  If this
24162306a36Sopenharmony_ci			 * fails then continue anyway.
24262306a36Sopenharmony_ci			 */
24362306a36Sopenharmony_ci			map->rsrc.name  = map->map_name;
24462306a36Sopenharmony_ci			map->rsrc.start = map->map.phys;
24562306a36Sopenharmony_ci			map->rsrc.end   = map->map.phys + map->mtd->size - 1;
24662306a36Sopenharmony_ci			map->rsrc.flags = IORESOURCE_MEM | IORESOURCE_BUSY;
24762306a36Sopenharmony_ci			if (request_resource(&window->rsrc, &map->rsrc)) {
24862306a36Sopenharmony_ci				printk(KERN_ERR MOD_NAME
24962306a36Sopenharmony_ci					": cannot reserve MTD resource\n");
25062306a36Sopenharmony_ci				map->rsrc.parent = NULL;
25162306a36Sopenharmony_ci			}
25262306a36Sopenharmony_ci		}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci		/* Make the whole region visible in the map */
25562306a36Sopenharmony_ci		map->map.virt = window->virt;
25662306a36Sopenharmony_ci		map->map.phys = window->phys;
25762306a36Sopenharmony_ci		cfi = map->map.fldrv_priv;
25862306a36Sopenharmony_ci		for(i = 0; i < cfi->numchips; i++) {
25962306a36Sopenharmony_ci			cfi->chips[i].start += offset;
26062306a36Sopenharmony_ci		}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci		/* Now that the mtd devices is complete claim and export it */
26362306a36Sopenharmony_ci		map->mtd->owner = THIS_MODULE;
26462306a36Sopenharmony_ci		if (mtd_device_register(map->mtd, NULL, 0)) {
26562306a36Sopenharmony_ci			map_destroy(map->mtd);
26662306a36Sopenharmony_ci			map->mtd = NULL;
26762306a36Sopenharmony_ci			goto out;
26862306a36Sopenharmony_ci		}
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci		/* Calculate the new value of map_top */
27262306a36Sopenharmony_ci		map_top += map->mtd->size;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci		/* File away the map structure */
27562306a36Sopenharmony_ci		list_add(&map->list, &window->maps);
27662306a36Sopenharmony_ci		map = NULL;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci out:
28062306a36Sopenharmony_ci	/* Free any left over map structures */
28162306a36Sopenharmony_ci	kfree(map);
28262306a36Sopenharmony_ci	/* See if I have any map structures */
28362306a36Sopenharmony_ci	if (list_empty(&window->maps)) {
28462306a36Sopenharmony_ci		amd76xrom_cleanup(window);
28562306a36Sopenharmony_ci		return -ENODEV;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci	return 0;
28862306a36Sopenharmony_ci}
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_cistatic void amd76xrom_remove_one(struct pci_dev *pdev)
29262306a36Sopenharmony_ci{
29362306a36Sopenharmony_ci	struct amd76xrom_window *window = &amd76xrom_window;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	amd76xrom_cleanup(window);
29662306a36Sopenharmony_ci}
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_cistatic const struct pci_device_id amd76xrom_pci_tbl[] = {
29962306a36Sopenharmony_ci	{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7410,
30062306a36Sopenharmony_ci		PCI_ANY_ID, PCI_ANY_ID, },
30162306a36Sopenharmony_ci	{ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_VIPER_7440,
30262306a36Sopenharmony_ci		PCI_ANY_ID, PCI_ANY_ID, },
30362306a36Sopenharmony_ci	{ PCI_VENDOR_ID_AMD, 0x7468 }, /* amd8111 support */
30462306a36Sopenharmony_ci	{ 0, }
30562306a36Sopenharmony_ci};
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(pci, amd76xrom_pci_tbl);
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci#if 0
31062306a36Sopenharmony_cistatic struct pci_driver amd76xrom_driver = {
31162306a36Sopenharmony_ci	.name =		MOD_NAME,
31262306a36Sopenharmony_ci	.id_table =	amd76xrom_pci_tbl,
31362306a36Sopenharmony_ci	.probe =	amd76xrom_init_one,
31462306a36Sopenharmony_ci	.remove =	amd76xrom_remove_one,
31562306a36Sopenharmony_ci};
31662306a36Sopenharmony_ci#endif
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic int __init init_amd76xrom(void)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	struct pci_dev *pdev;
32162306a36Sopenharmony_ci	const struct pci_device_id *id;
32262306a36Sopenharmony_ci	pdev = NULL;
32362306a36Sopenharmony_ci	for(id = amd76xrom_pci_tbl; id->vendor; id++) {
32462306a36Sopenharmony_ci		pdev = pci_get_device(id->vendor, id->device, NULL);
32562306a36Sopenharmony_ci		if (pdev) {
32662306a36Sopenharmony_ci			break;
32762306a36Sopenharmony_ci		}
32862306a36Sopenharmony_ci	}
32962306a36Sopenharmony_ci	if (pdev) {
33062306a36Sopenharmony_ci		return amd76xrom_init_one(pdev, &amd76xrom_pci_tbl[0]);
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci	return -ENXIO;
33362306a36Sopenharmony_ci#if 0
33462306a36Sopenharmony_ci	return pci_register_driver(&amd76xrom_driver);
33562306a36Sopenharmony_ci#endif
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cistatic void __exit cleanup_amd76xrom(void)
33962306a36Sopenharmony_ci{
34062306a36Sopenharmony_ci	amd76xrom_remove_one(amd76xrom_window.pdev);
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cimodule_init(init_amd76xrom);
34462306a36Sopenharmony_cimodule_exit(cleanup_amd76xrom);
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
34762306a36Sopenharmony_ciMODULE_AUTHOR("Eric Biederman <ebiederman@lnxi.com>");
34862306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for BIOS chips on the AMD76X southbridge");
349