162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* sc520cdp.c -- MTD map driver for AMD SC520 Customer Development Platform
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2001 Sysgo Real-Time Solutions GmbH
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * The SC520CDP is an evaluation board for the Elan SC520 processor available
762306a36Sopenharmony_ci * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size,
862306a36Sopenharmony_ci * and up to 512 KiB of 8-bit DIL Flash ROM.
962306a36Sopenharmony_ci * For details see https://www.amd.com/products/epd/desiging/evalboards/18.elansc520/520_cdp_brief/index.html
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/types.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/init.h>
1662306a36Sopenharmony_ci#include <asm/io.h>
1762306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
1862306a36Sopenharmony_ci#include <linux/mtd/map.h>
1962306a36Sopenharmony_ci#include <linux/mtd/concat.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/*
2262306a36Sopenharmony_ci** The Embedded Systems BIOS decodes the first FLASH starting at
2362306a36Sopenharmony_ci** 0x8400000. This is a *terrible* place for it because accessing
2462306a36Sopenharmony_ci** the flash at this location causes the A22 address line to be high
2562306a36Sopenharmony_ci** (that's what 0x8400000 binary's ought to be). But this is the highest
2662306a36Sopenharmony_ci** order address line on the raw flash devices themselves!!
2762306a36Sopenharmony_ci** This causes the top HALF of the flash to be accessed first. Beyond
2862306a36Sopenharmony_ci** the physical limits of the flash, the flash chip aliases over (to
2962306a36Sopenharmony_ci** 0x880000 which causes the bottom half to be accessed. This splits the
3062306a36Sopenharmony_ci** flash into two and inverts it! If you then try to access this from another
3162306a36Sopenharmony_ci** program that does NOT do this insanity, then you *will* access the
3262306a36Sopenharmony_ci** first half of the flash, but not find what you expect there. That
3362306a36Sopenharmony_ci** stuff is in the *second* half! Similarly, the address used by the
3462306a36Sopenharmony_ci** BIOS for the second FLASH bank is also quite a bad choice.
3562306a36Sopenharmony_ci** If REPROGRAM_PAR is defined below (the default), then this driver will
3662306a36Sopenharmony_ci** choose more useful addresses for the FLASH banks by reprogramming the
3762306a36Sopenharmony_ci** responsible PARxx registers in the SC520's MMCR region. This will
3862306a36Sopenharmony_ci** cause the settings to be incompatible with the BIOS's settings, which
3962306a36Sopenharmony_ci** shouldn't be a problem since you are running Linux, (i.e. the BIOS is
4062306a36Sopenharmony_ci** not much use anyway). However, if you need to be compatible with
4162306a36Sopenharmony_ci** the BIOS for some reason, just undefine REPROGRAM_PAR.
4262306a36Sopenharmony_ci*/
4362306a36Sopenharmony_ci#define REPROGRAM_PAR
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#ifdef REPROGRAM_PAR
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci/* These are the addresses we want.. */
5062306a36Sopenharmony_ci#define WINDOW_ADDR_0	0x08800000
5162306a36Sopenharmony_ci#define WINDOW_ADDR_1	0x09000000
5262306a36Sopenharmony_ci#define WINDOW_ADDR_2	0x09800000
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/* .. and these are the addresses the BIOS gives us */
5562306a36Sopenharmony_ci#define WINDOW_ADDR_0_BIOS	0x08400000
5662306a36Sopenharmony_ci#define WINDOW_ADDR_1_BIOS	0x08c00000
5762306a36Sopenharmony_ci#define WINDOW_ADDR_2_BIOS	0x09400000
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ci#else
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define WINDOW_ADDR_0	0x08400000
6262306a36Sopenharmony_ci#define WINDOW_ADDR_1	0x08C00000
6362306a36Sopenharmony_ci#define WINDOW_ADDR_2	0x09400000
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci#endif
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define WINDOW_SIZE_0	0x00800000
6862306a36Sopenharmony_ci#define WINDOW_SIZE_1	0x00800000
6962306a36Sopenharmony_ci#define WINDOW_SIZE_2	0x00080000
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_cistatic struct map_info sc520cdp_map[] = {
7362306a36Sopenharmony_ci	{
7462306a36Sopenharmony_ci		.name = "SC520CDP Flash Bank #0",
7562306a36Sopenharmony_ci		.size = WINDOW_SIZE_0,
7662306a36Sopenharmony_ci		.bankwidth = 4,
7762306a36Sopenharmony_ci		.phys = WINDOW_ADDR_0
7862306a36Sopenharmony_ci	},
7962306a36Sopenharmony_ci	{
8062306a36Sopenharmony_ci		.name = "SC520CDP Flash Bank #1",
8162306a36Sopenharmony_ci		.size = WINDOW_SIZE_1,
8262306a36Sopenharmony_ci		.bankwidth = 4,
8362306a36Sopenharmony_ci		.phys = WINDOW_ADDR_1
8462306a36Sopenharmony_ci	},
8562306a36Sopenharmony_ci	{
8662306a36Sopenharmony_ci		.name = "SC520CDP DIL Flash",
8762306a36Sopenharmony_ci		.size = WINDOW_SIZE_2,
8862306a36Sopenharmony_ci		.bankwidth = 1,
8962306a36Sopenharmony_ci		.phys = WINDOW_ADDR_2
9062306a36Sopenharmony_ci	},
9162306a36Sopenharmony_ci};
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define NUM_FLASH_BANKS	ARRAY_SIZE(sc520cdp_map)
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_cistatic struct mtd_info *mymtd[NUM_FLASH_BANKS];
9662306a36Sopenharmony_cistatic struct mtd_info *merged_mtd;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#ifdef REPROGRAM_PAR
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci/*
10162306a36Sopenharmony_ci** The SC520 MMCR (memory mapped control register) region resides
10262306a36Sopenharmony_ci** at 0xFFFEF000. The 16 Programmable Address Region (PAR) registers
10362306a36Sopenharmony_ci** are at offset 0x88 in the MMCR:
10462306a36Sopenharmony_ci*/
10562306a36Sopenharmony_ci#define SC520_MMCR_BASE		0xFFFEF000
10662306a36Sopenharmony_ci#define SC520_MMCR_EXTENT	0x1000
10762306a36Sopenharmony_ci#define SC520_PAR(x)		((0x88/sizeof(unsigned long)) + (x))
10862306a36Sopenharmony_ci#define NUM_SC520_PAR		16	/* total number of PAR registers */
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci/*
11162306a36Sopenharmony_ci** The highest three bits in a PAR register determine what target
11262306a36Sopenharmony_ci** device is controlled by this PAR. Here, only ROMCS? and BOOTCS
11362306a36Sopenharmony_ci** devices are of interest.
11462306a36Sopenharmony_ci*/
11562306a36Sopenharmony_ci#define SC520_PAR_BOOTCS	(0x4<<29)
11662306a36Sopenharmony_ci#define SC520_PAR_ROMCS0	(0x5<<29)
11762306a36Sopenharmony_ci#define SC520_PAR_ROMCS1	(0x6<<29)
11862306a36Sopenharmony_ci#define SC520_PAR_TRGDEV	(0x7<<29)
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/*
12162306a36Sopenharmony_ci** Bits 28 thru 26 determine some attributes for the
12262306a36Sopenharmony_ci** region controlled by the PAR. (We only use non-cacheable)
12362306a36Sopenharmony_ci*/
12462306a36Sopenharmony_ci#define SC520_PAR_WRPROT	(1<<26)	/* write protected       */
12562306a36Sopenharmony_ci#define SC520_PAR_NOCACHE	(1<<27)	/* non-cacheable         */
12662306a36Sopenharmony_ci#define SC520_PAR_NOEXEC	(1<<28)	/* code execution denied */
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci/*
13062306a36Sopenharmony_ci** Bit 25 determines the granularity: 4K or 64K
13162306a36Sopenharmony_ci*/
13262306a36Sopenharmony_ci#define SC520_PAR_PG_SIZ4	(0<<25)
13362306a36Sopenharmony_ci#define SC520_PAR_PG_SIZ64	(1<<25)
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/*
13662306a36Sopenharmony_ci** Build a value to be written into a PAR register.
13762306a36Sopenharmony_ci** We only need ROM entries, 64K page size:
13862306a36Sopenharmony_ci*/
13962306a36Sopenharmony_ci#define SC520_PAR_ENTRY(trgdev, address, size) \
14062306a36Sopenharmony_ci	((trgdev) | SC520_PAR_NOCACHE | SC520_PAR_PG_SIZ64 | \
14162306a36Sopenharmony_ci	(address) >> 16 | (((size) >> 16) - 1) << 14)
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_cistruct sc520_par_table
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	unsigned long trgdev;
14662306a36Sopenharmony_ci	unsigned long new_par;
14762306a36Sopenharmony_ci	unsigned long default_address;
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_cistatic const struct sc520_par_table par_table[NUM_FLASH_BANKS] =
15162306a36Sopenharmony_ci{
15262306a36Sopenharmony_ci	{	/* Flash Bank #0: selected by ROMCS0 */
15362306a36Sopenharmony_ci		SC520_PAR_ROMCS0,
15462306a36Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_ROMCS0, WINDOW_ADDR_0, WINDOW_SIZE_0),
15562306a36Sopenharmony_ci		WINDOW_ADDR_0_BIOS
15662306a36Sopenharmony_ci	},
15762306a36Sopenharmony_ci	{	/* Flash Bank #1: selected by ROMCS1 */
15862306a36Sopenharmony_ci		SC520_PAR_ROMCS1,
15962306a36Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_ROMCS1, WINDOW_ADDR_1, WINDOW_SIZE_1),
16062306a36Sopenharmony_ci		WINDOW_ADDR_1_BIOS
16162306a36Sopenharmony_ci	},
16262306a36Sopenharmony_ci	{	/* DIL (BIOS) Flash: selected by BOOTCS */
16362306a36Sopenharmony_ci		SC520_PAR_BOOTCS,
16462306a36Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_BOOTCS, WINDOW_ADDR_2, WINDOW_SIZE_2),
16562306a36Sopenharmony_ci		WINDOW_ADDR_2_BIOS
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci};
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic void sc520cdp_setup_par(void)
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	unsigned long __iomem *mmcr;
17362306a36Sopenharmony_ci	unsigned long mmcr_val;
17462306a36Sopenharmony_ci	int i, j;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	/* map in SC520's MMCR area */
17762306a36Sopenharmony_ci	mmcr = ioremap(SC520_MMCR_BASE, SC520_MMCR_EXTENT);
17862306a36Sopenharmony_ci	if(!mmcr) { /* ioremap failed: skip the PAR reprogramming */
17962306a36Sopenharmony_ci		/* force physical address fields to BIOS defaults: */
18062306a36Sopenharmony_ci		for(i = 0; i < NUM_FLASH_BANKS; i++)
18162306a36Sopenharmony_ci			sc520cdp_map[i].phys = par_table[i].default_address;
18262306a36Sopenharmony_ci		return;
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	/*
18662306a36Sopenharmony_ci	** Find the PARxx registers that are responsible for activating
18762306a36Sopenharmony_ci	** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a
18862306a36Sopenharmony_ci	** new value from the table.
18962306a36Sopenharmony_ci	*/
19062306a36Sopenharmony_ci	for(i = 0; i < NUM_FLASH_BANKS; i++) {		/* for each par_table entry  */
19162306a36Sopenharmony_ci		for(j = 0; j < NUM_SC520_PAR; j++) {	/* for each PAR register     */
19262306a36Sopenharmony_ci			mmcr_val = readl(&mmcr[SC520_PAR(j)]);
19362306a36Sopenharmony_ci			/* if target device field matches, reprogram the PAR */
19462306a36Sopenharmony_ci			if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev)
19562306a36Sopenharmony_ci			{
19662306a36Sopenharmony_ci				writel(par_table[i].new_par, &mmcr[SC520_PAR(j)]);
19762306a36Sopenharmony_ci				break;
19862306a36Sopenharmony_ci			}
19962306a36Sopenharmony_ci		}
20062306a36Sopenharmony_ci		if(j == NUM_SC520_PAR)
20162306a36Sopenharmony_ci		{	/* no matching PAR found: try default BIOS address */
20262306a36Sopenharmony_ci			printk(KERN_NOTICE "Could not find PAR responsible for %s\n",
20362306a36Sopenharmony_ci				sc520cdp_map[i].name);
20462306a36Sopenharmony_ci			printk(KERN_NOTICE "Trying default address 0x%lx\n",
20562306a36Sopenharmony_ci				par_table[i].default_address);
20662306a36Sopenharmony_ci			sc520cdp_map[i].phys = par_table[i].default_address;
20762306a36Sopenharmony_ci		}
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci	iounmap(mmcr);
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci#endif
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic int __init init_sc520cdp(void)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	int i, j, devices_found = 0;
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci#ifdef REPROGRAM_PAR
21962306a36Sopenharmony_ci	/* reprogram PAR registers so flash appears at the desired addresses */
22062306a36Sopenharmony_ci	sc520cdp_setup_par();
22162306a36Sopenharmony_ci#endif
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	for (i = 0; i < NUM_FLASH_BANKS; i++) {
22462306a36Sopenharmony_ci		printk(KERN_NOTICE "SC520 CDP flash device: 0x%Lx at 0x%Lx\n",
22562306a36Sopenharmony_ci			(unsigned long long)sc520cdp_map[i].size,
22662306a36Sopenharmony_ci			(unsigned long long)sc520cdp_map[i].phys);
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci		sc520cdp_map[i].virt = ioremap(sc520cdp_map[i].phys, sc520cdp_map[i].size);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci		if (!sc520cdp_map[i].virt) {
23162306a36Sopenharmony_ci			printk("Failed to ioremap\n");
23262306a36Sopenharmony_ci			for (j = 0; j < i; j++) {
23362306a36Sopenharmony_ci				if (mymtd[j]) {
23462306a36Sopenharmony_ci					map_destroy(mymtd[j]);
23562306a36Sopenharmony_ci					iounmap(sc520cdp_map[j].virt);
23662306a36Sopenharmony_ci				}
23762306a36Sopenharmony_ci			}
23862306a36Sopenharmony_ci			return -EIO;
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		simple_map_init(&sc520cdp_map[i]);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci		mymtd[i] = do_map_probe("cfi_probe", &sc520cdp_map[i]);
24462306a36Sopenharmony_ci		if(!mymtd[i])
24562306a36Sopenharmony_ci			mymtd[i] = do_map_probe("jedec_probe", &sc520cdp_map[i]);
24662306a36Sopenharmony_ci		if(!mymtd[i])
24762306a36Sopenharmony_ci			mymtd[i] = do_map_probe("map_rom", &sc520cdp_map[i]);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci		if (mymtd[i]) {
25062306a36Sopenharmony_ci			mymtd[i]->owner = THIS_MODULE;
25162306a36Sopenharmony_ci			++devices_found;
25262306a36Sopenharmony_ci		}
25362306a36Sopenharmony_ci		else {
25462306a36Sopenharmony_ci			iounmap(sc520cdp_map[i].virt);
25562306a36Sopenharmony_ci		}
25662306a36Sopenharmony_ci	}
25762306a36Sopenharmony_ci	if(devices_found >= 2) {
25862306a36Sopenharmony_ci		/* Combine the two flash banks into a single MTD device & register it: */
25962306a36Sopenharmony_ci		merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1");
26062306a36Sopenharmony_ci		if(merged_mtd)
26162306a36Sopenharmony_ci			mtd_device_register(merged_mtd, NULL, 0);
26262306a36Sopenharmony_ci	}
26362306a36Sopenharmony_ci	if(devices_found == 3) /* register the third (DIL-Flash) device */
26462306a36Sopenharmony_ci		mtd_device_register(mymtd[2], NULL, 0);
26562306a36Sopenharmony_ci	return(devices_found ? 0 : -ENXIO);
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic void __exit cleanup_sc520cdp(void)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	int i;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (merged_mtd) {
27362306a36Sopenharmony_ci		mtd_device_unregister(merged_mtd);
27462306a36Sopenharmony_ci		mtd_concat_destroy(merged_mtd);
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci	if (mymtd[2])
27762306a36Sopenharmony_ci		mtd_device_unregister(mymtd[2]);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	for (i = 0; i < NUM_FLASH_BANKS; i++) {
28062306a36Sopenharmony_ci		if (mymtd[i])
28162306a36Sopenharmony_ci			map_destroy(mymtd[i]);
28262306a36Sopenharmony_ci		if (sc520cdp_map[i].virt) {
28362306a36Sopenharmony_ci			iounmap(sc520cdp_map[i].virt);
28462306a36Sopenharmony_ci			sc520cdp_map[i].virt = NULL;
28562306a36Sopenharmony_ci		}
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_cimodule_init(init_sc520cdp);
29062306a36Sopenharmony_cimodule_exit(cleanup_sc520cdp);
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
29362306a36Sopenharmony_ciMODULE_AUTHOR("Sysgo Real-Time Solutions GmbH");
29462306a36Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for AMD SC520 Customer Development Platform");
295