18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/* sc520cdp.c -- MTD map driver for AMD SC520 Customer Development Platform
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2001 Sysgo Real-Time Solutions GmbH
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * The SC520CDP is an evaluation board for the Elan SC520 processor available
78c2ecf20Sopenharmony_ci * from AMD. It has two banks of 32-bit Flash ROM, each 8 Megabytes in size,
88c2ecf20Sopenharmony_ci * and up to 512 KiB of 8-bit DIL Flash ROM.
98c2ecf20Sopenharmony_ci * For details see https://www.amd.com/products/epd/desiging/evalboards/18.elansc520/520_cdp_brief/index.html
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/types.h>
148c2ecf20Sopenharmony_ci#include <linux/kernel.h>
158c2ecf20Sopenharmony_ci#include <linux/init.h>
168c2ecf20Sopenharmony_ci#include <asm/io.h>
178c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h>
188c2ecf20Sopenharmony_ci#include <linux/mtd/map.h>
198c2ecf20Sopenharmony_ci#include <linux/mtd/concat.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci/*
228c2ecf20Sopenharmony_ci** The Embedded Systems BIOS decodes the first FLASH starting at
238c2ecf20Sopenharmony_ci** 0x8400000. This is a *terrible* place for it because accessing
248c2ecf20Sopenharmony_ci** the flash at this location causes the A22 address line to be high
258c2ecf20Sopenharmony_ci** (that's what 0x8400000 binary's ought to be). But this is the highest
268c2ecf20Sopenharmony_ci** order address line on the raw flash devices themselves!!
278c2ecf20Sopenharmony_ci** This causes the top HALF of the flash to be accessed first. Beyond
288c2ecf20Sopenharmony_ci** the physical limits of the flash, the flash chip aliases over (to
298c2ecf20Sopenharmony_ci** 0x880000 which causes the bottom half to be accessed. This splits the
308c2ecf20Sopenharmony_ci** flash into two and inverts it! If you then try to access this from another
318c2ecf20Sopenharmony_ci** program that does NOT do this insanity, then you *will* access the
328c2ecf20Sopenharmony_ci** first half of the flash, but not find what you expect there. That
338c2ecf20Sopenharmony_ci** stuff is in the *second* half! Similarly, the address used by the
348c2ecf20Sopenharmony_ci** BIOS for the second FLASH bank is also quite a bad choice.
358c2ecf20Sopenharmony_ci** If REPROGRAM_PAR is defined below (the default), then this driver will
368c2ecf20Sopenharmony_ci** choose more useful addresses for the FLASH banks by reprogramming the
378c2ecf20Sopenharmony_ci** responsible PARxx registers in the SC520's MMCR region. This will
388c2ecf20Sopenharmony_ci** cause the settings to be incompatible with the BIOS's settings, which
398c2ecf20Sopenharmony_ci** shouldn't be a problem since you are running Linux, (i.e. the BIOS is
408c2ecf20Sopenharmony_ci** not much use anyway). However, if you need to be compatible with
418c2ecf20Sopenharmony_ci** the BIOS for some reason, just undefine REPROGRAM_PAR.
428c2ecf20Sopenharmony_ci*/
438c2ecf20Sopenharmony_ci#define REPROGRAM_PAR
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#ifdef REPROGRAM_PAR
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* These are the addresses we want.. */
508c2ecf20Sopenharmony_ci#define WINDOW_ADDR_0	0x08800000
518c2ecf20Sopenharmony_ci#define WINDOW_ADDR_1	0x09000000
528c2ecf20Sopenharmony_ci#define WINDOW_ADDR_2	0x09800000
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci/* .. and these are the addresses the BIOS gives us */
558c2ecf20Sopenharmony_ci#define WINDOW_ADDR_0_BIOS	0x08400000
568c2ecf20Sopenharmony_ci#define WINDOW_ADDR_1_BIOS	0x08c00000
578c2ecf20Sopenharmony_ci#define WINDOW_ADDR_2_BIOS	0x09400000
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci#else
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#define WINDOW_ADDR_0	0x08400000
628c2ecf20Sopenharmony_ci#define WINDOW_ADDR_1	0x08C00000
638c2ecf20Sopenharmony_ci#define WINDOW_ADDR_2	0x09400000
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci#endif
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci#define WINDOW_SIZE_0	0x00800000
688c2ecf20Sopenharmony_ci#define WINDOW_SIZE_1	0x00800000
698c2ecf20Sopenharmony_ci#define WINDOW_SIZE_2	0x00080000
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_cistatic struct map_info sc520cdp_map[] = {
738c2ecf20Sopenharmony_ci	{
748c2ecf20Sopenharmony_ci		.name = "SC520CDP Flash Bank #0",
758c2ecf20Sopenharmony_ci		.size = WINDOW_SIZE_0,
768c2ecf20Sopenharmony_ci		.bankwidth = 4,
778c2ecf20Sopenharmony_ci		.phys = WINDOW_ADDR_0
788c2ecf20Sopenharmony_ci	},
798c2ecf20Sopenharmony_ci	{
808c2ecf20Sopenharmony_ci		.name = "SC520CDP Flash Bank #1",
818c2ecf20Sopenharmony_ci		.size = WINDOW_SIZE_1,
828c2ecf20Sopenharmony_ci		.bankwidth = 4,
838c2ecf20Sopenharmony_ci		.phys = WINDOW_ADDR_1
848c2ecf20Sopenharmony_ci	},
858c2ecf20Sopenharmony_ci	{
868c2ecf20Sopenharmony_ci		.name = "SC520CDP DIL Flash",
878c2ecf20Sopenharmony_ci		.size = WINDOW_SIZE_2,
888c2ecf20Sopenharmony_ci		.bankwidth = 1,
898c2ecf20Sopenharmony_ci		.phys = WINDOW_ADDR_2
908c2ecf20Sopenharmony_ci	},
918c2ecf20Sopenharmony_ci};
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci#define NUM_FLASH_BANKS	ARRAY_SIZE(sc520cdp_map)
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_cistatic struct mtd_info *mymtd[NUM_FLASH_BANKS];
968c2ecf20Sopenharmony_cistatic struct mtd_info *merged_mtd;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci#ifdef REPROGRAM_PAR
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/*
1018c2ecf20Sopenharmony_ci** The SC520 MMCR (memory mapped control register) region resides
1028c2ecf20Sopenharmony_ci** at 0xFFFEF000. The 16 Programmable Address Region (PAR) registers
1038c2ecf20Sopenharmony_ci** are at offset 0x88 in the MMCR:
1048c2ecf20Sopenharmony_ci*/
1058c2ecf20Sopenharmony_ci#define SC520_MMCR_BASE		0xFFFEF000
1068c2ecf20Sopenharmony_ci#define SC520_MMCR_EXTENT	0x1000
1078c2ecf20Sopenharmony_ci#define SC520_PAR(x)		((0x88/sizeof(unsigned long)) + (x))
1088c2ecf20Sopenharmony_ci#define NUM_SC520_PAR		16	/* total number of PAR registers */
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci/*
1118c2ecf20Sopenharmony_ci** The highest three bits in a PAR register determine what target
1128c2ecf20Sopenharmony_ci** device is controlled by this PAR. Here, only ROMCS? and BOOTCS
1138c2ecf20Sopenharmony_ci** devices are of interest.
1148c2ecf20Sopenharmony_ci*/
1158c2ecf20Sopenharmony_ci#define SC520_PAR_BOOTCS	(0x4<<29)
1168c2ecf20Sopenharmony_ci#define SC520_PAR_ROMCS0	(0x5<<29)
1178c2ecf20Sopenharmony_ci#define SC520_PAR_ROMCS1	(0x6<<29)
1188c2ecf20Sopenharmony_ci#define SC520_PAR_TRGDEV	(0x7<<29)
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci/*
1218c2ecf20Sopenharmony_ci** Bits 28 thru 26 determine some attributes for the
1228c2ecf20Sopenharmony_ci** region controlled by the PAR. (We only use non-cacheable)
1238c2ecf20Sopenharmony_ci*/
1248c2ecf20Sopenharmony_ci#define SC520_PAR_WRPROT	(1<<26)	/* write protected       */
1258c2ecf20Sopenharmony_ci#define SC520_PAR_NOCACHE	(1<<27)	/* non-cacheable         */
1268c2ecf20Sopenharmony_ci#define SC520_PAR_NOEXEC	(1<<28)	/* code execution denied */
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci/*
1308c2ecf20Sopenharmony_ci** Bit 25 determines the granularity: 4K or 64K
1318c2ecf20Sopenharmony_ci*/
1328c2ecf20Sopenharmony_ci#define SC520_PAR_PG_SIZ4	(0<<25)
1338c2ecf20Sopenharmony_ci#define SC520_PAR_PG_SIZ64	(1<<25)
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci/*
1368c2ecf20Sopenharmony_ci** Build a value to be written into a PAR register.
1378c2ecf20Sopenharmony_ci** We only need ROM entries, 64K page size:
1388c2ecf20Sopenharmony_ci*/
1398c2ecf20Sopenharmony_ci#define SC520_PAR_ENTRY(trgdev, address, size) \
1408c2ecf20Sopenharmony_ci	((trgdev) | SC520_PAR_NOCACHE | SC520_PAR_PG_SIZ64 | \
1418c2ecf20Sopenharmony_ci	(address) >> 16 | (((size) >> 16) - 1) << 14)
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistruct sc520_par_table
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	unsigned long trgdev;
1468c2ecf20Sopenharmony_ci	unsigned long new_par;
1478c2ecf20Sopenharmony_ci	unsigned long default_address;
1488c2ecf20Sopenharmony_ci};
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic const struct sc520_par_table par_table[NUM_FLASH_BANKS] =
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	{	/* Flash Bank #0: selected by ROMCS0 */
1538c2ecf20Sopenharmony_ci		SC520_PAR_ROMCS0,
1548c2ecf20Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_ROMCS0, WINDOW_ADDR_0, WINDOW_SIZE_0),
1558c2ecf20Sopenharmony_ci		WINDOW_ADDR_0_BIOS
1568c2ecf20Sopenharmony_ci	},
1578c2ecf20Sopenharmony_ci	{	/* Flash Bank #1: selected by ROMCS1 */
1588c2ecf20Sopenharmony_ci		SC520_PAR_ROMCS1,
1598c2ecf20Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_ROMCS1, WINDOW_ADDR_1, WINDOW_SIZE_1),
1608c2ecf20Sopenharmony_ci		WINDOW_ADDR_1_BIOS
1618c2ecf20Sopenharmony_ci	},
1628c2ecf20Sopenharmony_ci	{	/* DIL (BIOS) Flash: selected by BOOTCS */
1638c2ecf20Sopenharmony_ci		SC520_PAR_BOOTCS,
1648c2ecf20Sopenharmony_ci		SC520_PAR_ENTRY(SC520_PAR_BOOTCS, WINDOW_ADDR_2, WINDOW_SIZE_2),
1658c2ecf20Sopenharmony_ci		WINDOW_ADDR_2_BIOS
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci};
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic void sc520cdp_setup_par(void)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	unsigned long __iomem *mmcr;
1738c2ecf20Sopenharmony_ci	unsigned long mmcr_val;
1748c2ecf20Sopenharmony_ci	int i, j;
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	/* map in SC520's MMCR area */
1778c2ecf20Sopenharmony_ci	mmcr = ioremap(SC520_MMCR_BASE, SC520_MMCR_EXTENT);
1788c2ecf20Sopenharmony_ci	if(!mmcr) { /* ioremap failed: skip the PAR reprogramming */
1798c2ecf20Sopenharmony_ci		/* force physical address fields to BIOS defaults: */
1808c2ecf20Sopenharmony_ci		for(i = 0; i < NUM_FLASH_BANKS; i++)
1818c2ecf20Sopenharmony_ci			sc520cdp_map[i].phys = par_table[i].default_address;
1828c2ecf20Sopenharmony_ci		return;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	/*
1868c2ecf20Sopenharmony_ci	** Find the PARxx registers that are responsible for activating
1878c2ecf20Sopenharmony_ci	** ROMCS0, ROMCS1 and BOOTCS. Reprogram each of these with a
1888c2ecf20Sopenharmony_ci	** new value from the table.
1898c2ecf20Sopenharmony_ci	*/
1908c2ecf20Sopenharmony_ci	for(i = 0; i < NUM_FLASH_BANKS; i++) {		/* for each par_table entry  */
1918c2ecf20Sopenharmony_ci		for(j = 0; j < NUM_SC520_PAR; j++) {	/* for each PAR register     */
1928c2ecf20Sopenharmony_ci			mmcr_val = readl(&mmcr[SC520_PAR(j)]);
1938c2ecf20Sopenharmony_ci			/* if target device field matches, reprogram the PAR */
1948c2ecf20Sopenharmony_ci			if((mmcr_val & SC520_PAR_TRGDEV) == par_table[i].trgdev)
1958c2ecf20Sopenharmony_ci			{
1968c2ecf20Sopenharmony_ci				writel(par_table[i].new_par, &mmcr[SC520_PAR(j)]);
1978c2ecf20Sopenharmony_ci				break;
1988c2ecf20Sopenharmony_ci			}
1998c2ecf20Sopenharmony_ci		}
2008c2ecf20Sopenharmony_ci		if(j == NUM_SC520_PAR)
2018c2ecf20Sopenharmony_ci		{	/* no matching PAR found: try default BIOS address */
2028c2ecf20Sopenharmony_ci			printk(KERN_NOTICE "Could not find PAR responsible for %s\n",
2038c2ecf20Sopenharmony_ci				sc520cdp_map[i].name);
2048c2ecf20Sopenharmony_ci			printk(KERN_NOTICE "Trying default address 0x%lx\n",
2058c2ecf20Sopenharmony_ci				par_table[i].default_address);
2068c2ecf20Sopenharmony_ci			sc520cdp_map[i].phys = par_table[i].default_address;
2078c2ecf20Sopenharmony_ci		}
2088c2ecf20Sopenharmony_ci	}
2098c2ecf20Sopenharmony_ci	iounmap(mmcr);
2108c2ecf20Sopenharmony_ci}
2118c2ecf20Sopenharmony_ci#endif
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cistatic int __init init_sc520cdp(void)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	int i, j, devices_found = 0;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci#ifdef REPROGRAM_PAR
2198c2ecf20Sopenharmony_ci	/* reprogram PAR registers so flash appears at the desired addresses */
2208c2ecf20Sopenharmony_ci	sc520cdp_setup_par();
2218c2ecf20Sopenharmony_ci#endif
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_FLASH_BANKS; i++) {
2248c2ecf20Sopenharmony_ci		printk(KERN_NOTICE "SC520 CDP flash device: 0x%Lx at 0x%Lx\n",
2258c2ecf20Sopenharmony_ci			(unsigned long long)sc520cdp_map[i].size,
2268c2ecf20Sopenharmony_ci			(unsigned long long)sc520cdp_map[i].phys);
2278c2ecf20Sopenharmony_ci
2288c2ecf20Sopenharmony_ci		sc520cdp_map[i].virt = ioremap(sc520cdp_map[i].phys, sc520cdp_map[i].size);
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_ci		if (!sc520cdp_map[i].virt) {
2318c2ecf20Sopenharmony_ci			printk("Failed to ioremap\n");
2328c2ecf20Sopenharmony_ci			for (j = 0; j < i; j++) {
2338c2ecf20Sopenharmony_ci				if (mymtd[j]) {
2348c2ecf20Sopenharmony_ci					map_destroy(mymtd[j]);
2358c2ecf20Sopenharmony_ci					iounmap(sc520cdp_map[j].virt);
2368c2ecf20Sopenharmony_ci				}
2378c2ecf20Sopenharmony_ci			}
2388c2ecf20Sopenharmony_ci			return -EIO;
2398c2ecf20Sopenharmony_ci		}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci		simple_map_init(&sc520cdp_map[i]);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci		mymtd[i] = do_map_probe("cfi_probe", &sc520cdp_map[i]);
2448c2ecf20Sopenharmony_ci		if(!mymtd[i])
2458c2ecf20Sopenharmony_ci			mymtd[i] = do_map_probe("jedec_probe", &sc520cdp_map[i]);
2468c2ecf20Sopenharmony_ci		if(!mymtd[i])
2478c2ecf20Sopenharmony_ci			mymtd[i] = do_map_probe("map_rom", &sc520cdp_map[i]);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci		if (mymtd[i]) {
2508c2ecf20Sopenharmony_ci			mymtd[i]->owner = THIS_MODULE;
2518c2ecf20Sopenharmony_ci			++devices_found;
2528c2ecf20Sopenharmony_ci		}
2538c2ecf20Sopenharmony_ci		else {
2548c2ecf20Sopenharmony_ci			iounmap(sc520cdp_map[i].virt);
2558c2ecf20Sopenharmony_ci		}
2568c2ecf20Sopenharmony_ci	}
2578c2ecf20Sopenharmony_ci	if(devices_found >= 2) {
2588c2ecf20Sopenharmony_ci		/* Combine the two flash banks into a single MTD device & register it: */
2598c2ecf20Sopenharmony_ci		merged_mtd = mtd_concat_create(mymtd, 2, "SC520CDP Flash Banks #0 and #1");
2608c2ecf20Sopenharmony_ci		if(merged_mtd)
2618c2ecf20Sopenharmony_ci			mtd_device_register(merged_mtd, NULL, 0);
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci	if(devices_found == 3) /* register the third (DIL-Flash) device */
2648c2ecf20Sopenharmony_ci		mtd_device_register(mymtd[2], NULL, 0);
2658c2ecf20Sopenharmony_ci	return(devices_found ? 0 : -ENXIO);
2668c2ecf20Sopenharmony_ci}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_cistatic void __exit cleanup_sc520cdp(void)
2698c2ecf20Sopenharmony_ci{
2708c2ecf20Sopenharmony_ci	int i;
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	if (merged_mtd) {
2738c2ecf20Sopenharmony_ci		mtd_device_unregister(merged_mtd);
2748c2ecf20Sopenharmony_ci		mtd_concat_destroy(merged_mtd);
2758c2ecf20Sopenharmony_ci	}
2768c2ecf20Sopenharmony_ci	if (mymtd[2])
2778c2ecf20Sopenharmony_ci		mtd_device_unregister(mymtd[2]);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_FLASH_BANKS; i++) {
2808c2ecf20Sopenharmony_ci		if (mymtd[i])
2818c2ecf20Sopenharmony_ci			map_destroy(mymtd[i]);
2828c2ecf20Sopenharmony_ci		if (sc520cdp_map[i].virt) {
2838c2ecf20Sopenharmony_ci			iounmap(sc520cdp_map[i].virt);
2848c2ecf20Sopenharmony_ci			sc520cdp_map[i].virt = NULL;
2858c2ecf20Sopenharmony_ci		}
2868c2ecf20Sopenharmony_ci	}
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_cimodule_init(init_sc520cdp);
2908c2ecf20Sopenharmony_cimodule_exit(cleanup_sc520cdp);
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2938c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sysgo Real-Time Solutions GmbH");
2948c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for AMD SC520 Customer Development Platform");
295