162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * PMC551 PCI Mezzanine Ram Device
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Author:
662306a36Sopenharmony_ci *	Mark Ferrell <mferrell@mvista.com>
762306a36Sopenharmony_ci *	Copyright 1999,2000 Nortel Networks
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Description:
1062306a36Sopenharmony_ci *	This driver is intended to support the PMC551 PCI Ram device
1162306a36Sopenharmony_ci *	from Ramix Inc.  The PMC551 is a PMC Mezzanine module for
1262306a36Sopenharmony_ci *	cPCI embedded systems.  The device contains a single SROM
1362306a36Sopenharmony_ci *	that initially programs the V370PDC chipset onboard the
1462306a36Sopenharmony_ci *	device, and various banks of DRAM/SDRAM onboard.  This driver
1562306a36Sopenharmony_ci *	implements this PCI Ram device as an MTD (Memory Technology
1662306a36Sopenharmony_ci *	Device) so that it can be used to hold a file system, or for
1762306a36Sopenharmony_ci *	added swap space in embedded systems.  Since the memory on
1862306a36Sopenharmony_ci *	this board isn't as fast as main memory we do not try to hook
1962306a36Sopenharmony_ci *	it into main memory as that would simply reduce performance
2062306a36Sopenharmony_ci *	on the system.  Using it as a block device allows us to use
2162306a36Sopenharmony_ci *	it as high speed swap or for a high speed disk device of some
2262306a36Sopenharmony_ci *	sort.  Which becomes very useful on diskless systems in the
2362306a36Sopenharmony_ci *	embedded market I might add.
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * Notes:
2662306a36Sopenharmony_ci *	Due to what I assume is more buggy SROM, the 64M PMC551 I
2762306a36Sopenharmony_ci *	have available claims that all 4 of its DRAM banks have 64MiB
2862306a36Sopenharmony_ci *	of ram configured (making a grand total of 256MiB onboard).
2962306a36Sopenharmony_ci *	This is slightly annoying since the BAR0 size reflects the
3062306a36Sopenharmony_ci *	aperture size, not the dram size, and the V370PDC supplies no
3162306a36Sopenharmony_ci *	other method for memory size discovery.  This problem is
3262306a36Sopenharmony_ci *	mostly only relevant when compiled as a module, as the
3362306a36Sopenharmony_ci *	unloading of the module with an aperture size smaller than
3462306a36Sopenharmony_ci *	the ram will cause the driver to detect the onboard memory
3562306a36Sopenharmony_ci *	size to be equal to the aperture size when the module is
3662306a36Sopenharmony_ci *	reloaded.  Soooo, to help, the module supports an msize
3762306a36Sopenharmony_ci *	option to allow the specification of the onboard memory, and
3862306a36Sopenharmony_ci *	an asize option, to allow the specification of the aperture
3962306a36Sopenharmony_ci *	size.  The aperture must be equal to or less then the memory
4062306a36Sopenharmony_ci *	size, the driver will correct this if you screw it up.  This
4162306a36Sopenharmony_ci *	problem is not relevant for compiled in drivers as compiled
4262306a36Sopenharmony_ci *	in drivers only init once.
4362306a36Sopenharmony_ci *
4462306a36Sopenharmony_ci * Credits:
4562306a36Sopenharmony_ci *	Saeed Karamooz <saeed@ramix.com> of Ramix INC. for the
4662306a36Sopenharmony_ci *	initial example code of how to initialize this device and for
4762306a36Sopenharmony_ci *	help with questions I had concerning operation of the device.
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci *	Most of the MTD code for this driver was originally written
5062306a36Sopenharmony_ci *	for the slram.o module in the MTD drivers package which
5162306a36Sopenharmony_ci *	allows the mapping of system memory into an MTD device.
5262306a36Sopenharmony_ci *	Since the PMC551 memory module is accessed in the same
5362306a36Sopenharmony_ci *	fashion as system memory, the slram.c code became a very nice
5462306a36Sopenharmony_ci *	fit to the needs of this driver.  All we added was PCI
5562306a36Sopenharmony_ci *	detection/initialization to the driver and automatically figure
5662306a36Sopenharmony_ci *	out the size via the PCI detection.o, later changes by Corey
5762306a36Sopenharmony_ci *	Minyard set up the card to utilize a 1M sliding apature.
5862306a36Sopenharmony_ci *
5962306a36Sopenharmony_ci *	Corey Minyard <minyard@nortelnetworks.com>
6062306a36Sopenharmony_ci *	* Modified driver to utilize a sliding aperture instead of
6162306a36Sopenharmony_ci *	 mapping all memory into kernel space which turned out to
6262306a36Sopenharmony_ci *	 be very wasteful.
6362306a36Sopenharmony_ci *	* Located a bug in the SROM's initialization sequence that
6462306a36Sopenharmony_ci *	 made the memory unusable, added a fix to code to touch up
6562306a36Sopenharmony_ci *	 the DRAM some.
6662306a36Sopenharmony_ci *
6762306a36Sopenharmony_ci * Bugs/FIXMEs:
6862306a36Sopenharmony_ci *	* MUST fix the init function to not spin on a register
6962306a36Sopenharmony_ci *	waiting for it to set .. this does not safely handle busted
7062306a36Sopenharmony_ci *	devices that never reset the register correctly which will
7162306a36Sopenharmony_ci *	cause the system to hang w/ a reboot being the only chance at
7262306a36Sopenharmony_ci *	recover. [sort of fixed, could be better]
7362306a36Sopenharmony_ci *	* Add I2C handling of the SROM so we can read the SROM's information
7462306a36Sopenharmony_ci *	about the aperture size.  This should always accurately reflect the
7562306a36Sopenharmony_ci *	onboard memory size.
7662306a36Sopenharmony_ci *	* Comb the init routine.  It's still a bit cludgy on a few things.
7762306a36Sopenharmony_ci */
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci#include <linux/kernel.h>
8062306a36Sopenharmony_ci#include <linux/module.h>
8162306a36Sopenharmony_ci#include <linux/uaccess.h>
8262306a36Sopenharmony_ci#include <linux/types.h>
8362306a36Sopenharmony_ci#include <linux/init.h>
8462306a36Sopenharmony_ci#include <linux/ptrace.h>
8562306a36Sopenharmony_ci#include <linux/slab.h>
8662306a36Sopenharmony_ci#include <linux/string.h>
8762306a36Sopenharmony_ci#include <linux/timer.h>
8862306a36Sopenharmony_ci#include <linux/major.h>
8962306a36Sopenharmony_ci#include <linux/fs.h>
9062306a36Sopenharmony_ci#include <linux/ioctl.h>
9162306a36Sopenharmony_ci#include <asm/io.h>
9262306a36Sopenharmony_ci#include <linux/pci.h>
9362306a36Sopenharmony_ci#include <linux/mtd/mtd.h>
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#define PMC551_VERSION \
9662306a36Sopenharmony_ci	"Ramix PMC551 PCI Mezzanine Ram Driver. (C) 1999,2000 Nortel Networks.\n"
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#define PCI_VENDOR_ID_V3_SEMI 0x11b0
9962306a36Sopenharmony_ci#define PCI_DEVICE_ID_V3_SEMI_V370PDC 0x0200
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP0 0x50
10262306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP1 0x54
10362306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP_MAP_ADDR_MASK 0x3ff00000
10462306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP_APERTURE_MASK 0x000000f0
10562306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP_REG_EN 0x00000002
10662306a36Sopenharmony_ci#define PMC551_PCI_MEM_MAP_ENABLE 0x00000001
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci#define PMC551_SDRAM_MA  0x60
10962306a36Sopenharmony_ci#define PMC551_SDRAM_CMD 0x62
11062306a36Sopenharmony_ci#define PMC551_DRAM_CFG  0x64
11162306a36Sopenharmony_ci#define PMC551_SYS_CTRL_REG 0x78
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci#define PMC551_DRAM_BLK0 0x68
11462306a36Sopenharmony_ci#define PMC551_DRAM_BLK1 0x6c
11562306a36Sopenharmony_ci#define PMC551_DRAM_BLK2 0x70
11662306a36Sopenharmony_ci#define PMC551_DRAM_BLK3 0x74
11762306a36Sopenharmony_ci#define PMC551_DRAM_BLK_GET_SIZE(x) (524288 << ((x >> 4) & 0x0f))
11862306a36Sopenharmony_ci#define PMC551_DRAM_BLK_SET_COL_MUX(x, v) (((x) & ~0x00007000) | (((v) & 0x7) << 12))
11962306a36Sopenharmony_ci#define PMC551_DRAM_BLK_SET_ROW_MUX(x, v) (((x) & ~0x00000f00) | (((v) & 0xf) << 8))
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistruct mypriv {
12262306a36Sopenharmony_ci	struct pci_dev *dev;
12362306a36Sopenharmony_ci	u_char *start;
12462306a36Sopenharmony_ci	u32 base_map0;
12562306a36Sopenharmony_ci	u32 curr_map0;
12662306a36Sopenharmony_ci	u32 asize;
12762306a36Sopenharmony_ci	struct mtd_info *nextpmc551;
12862306a36Sopenharmony_ci};
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic struct mtd_info *pmc551list;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
13362306a36Sopenharmony_ci			size_t *retlen, void **virt, resource_size_t *phys);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic int pmc551_erase(struct mtd_info *mtd, struct erase_info *instr)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	struct mypriv *priv = mtd->priv;
13862306a36Sopenharmony_ci	u32 soff_hi;		/* start address offset hi */
13962306a36Sopenharmony_ci	u32 eoff_hi, eoff_lo;	/* end address offset hi/lo */
14062306a36Sopenharmony_ci	unsigned long end;
14162306a36Sopenharmony_ci	u_char *ptr;
14262306a36Sopenharmony_ci	size_t retlen;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
14562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_erase(pos:%ld, len:%ld)\n", (long)instr->addr,
14662306a36Sopenharmony_ci		(long)instr->len);
14762306a36Sopenharmony_ci#endif
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	end = instr->addr + instr->len - 1;
15062306a36Sopenharmony_ci	eoff_hi = end & ~(priv->asize - 1);
15162306a36Sopenharmony_ci	soff_hi = instr->addr & ~(priv->asize - 1);
15262306a36Sopenharmony_ci	eoff_lo = end & (priv->asize - 1);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	pmc551_point(mtd, instr->addr, instr->len, &retlen,
15562306a36Sopenharmony_ci		     (void **)&ptr, NULL);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	if (soff_hi == eoff_hi || mtd->size == priv->asize) {
15862306a36Sopenharmony_ci		/* The whole thing fits within one access, so just one shot
15962306a36Sopenharmony_ci		   will do it. */
16062306a36Sopenharmony_ci		memset(ptr, 0xff, instr->len);
16162306a36Sopenharmony_ci	} else {
16262306a36Sopenharmony_ci		/* We have to do multiple writes to get all the data
16362306a36Sopenharmony_ci		   written. */
16462306a36Sopenharmony_ci		while (soff_hi != eoff_hi) {
16562306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
16662306a36Sopenharmony_ci			printk(KERN_DEBUG "pmc551_erase() soff_hi: %ld, "
16762306a36Sopenharmony_ci				"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
16862306a36Sopenharmony_ci#endif
16962306a36Sopenharmony_ci			memset(ptr, 0xff, priv->asize);
17062306a36Sopenharmony_ci			if (soff_hi + priv->asize >= mtd->size) {
17162306a36Sopenharmony_ci				goto out;
17262306a36Sopenharmony_ci			}
17362306a36Sopenharmony_ci			soff_hi += priv->asize;
17462306a36Sopenharmony_ci			pmc551_point(mtd, (priv->base_map0 | soff_hi),
17562306a36Sopenharmony_ci				     priv->asize, &retlen,
17662306a36Sopenharmony_ci				     (void **)&ptr, NULL);
17762306a36Sopenharmony_ci		}
17862306a36Sopenharmony_ci		memset(ptr, 0xff, eoff_lo);
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci      out:
18262306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
18362306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_erase() done\n");
18462306a36Sopenharmony_ci#endif
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	return 0;
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic int pmc551_point(struct mtd_info *mtd, loff_t from, size_t len,
19062306a36Sopenharmony_ci			size_t *retlen, void **virt, resource_size_t *phys)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	struct mypriv *priv = mtd->priv;
19362306a36Sopenharmony_ci	u32 soff_hi;
19462306a36Sopenharmony_ci	u32 soff_lo;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
19762306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_point(%ld, %ld)\n", (long)from, (long)len);
19862306a36Sopenharmony_ci#endif
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	soff_hi = from & ~(priv->asize - 1);
20162306a36Sopenharmony_ci	soff_lo = from & (priv->asize - 1);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	/* Cheap hack optimization */
20462306a36Sopenharmony_ci	if (priv->curr_map0 != from) {
20562306a36Sopenharmony_ci		pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
20662306a36Sopenharmony_ci					(priv->base_map0 | soff_hi));
20762306a36Sopenharmony_ci		priv->curr_map0 = soff_hi;
20862306a36Sopenharmony_ci	}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	*virt = priv->start + soff_lo;
21162306a36Sopenharmony_ci	*retlen = len;
21262306a36Sopenharmony_ci	return 0;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic int pmc551_unpoint(struct mtd_info *mtd, loff_t from, size_t len)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
21862306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_unpoint()\n");
21962306a36Sopenharmony_ci#endif
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_cistatic int pmc551_read(struct mtd_info *mtd, loff_t from, size_t len,
22462306a36Sopenharmony_ci			size_t * retlen, u_char * buf)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	struct mypriv *priv = mtd->priv;
22762306a36Sopenharmony_ci	u32 soff_hi;		/* start address offset hi */
22862306a36Sopenharmony_ci	u32 eoff_hi, eoff_lo;	/* end address offset hi/lo */
22962306a36Sopenharmony_ci	unsigned long end;
23062306a36Sopenharmony_ci	u_char *ptr;
23162306a36Sopenharmony_ci	u_char *copyto = buf;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
23462306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_read(pos:%ld, len:%ld) asize: %ld\n",
23562306a36Sopenharmony_ci		(long)from, (long)len, (long)priv->asize);
23662306a36Sopenharmony_ci#endif
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	end = from + len - 1;
23962306a36Sopenharmony_ci	soff_hi = from & ~(priv->asize - 1);
24062306a36Sopenharmony_ci	eoff_hi = end & ~(priv->asize - 1);
24162306a36Sopenharmony_ci	eoff_lo = end & (priv->asize - 1);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	pmc551_point(mtd, from, len, retlen, (void **)&ptr, NULL);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	if (soff_hi == eoff_hi) {
24662306a36Sopenharmony_ci		/* The whole thing fits within one access, so just one shot
24762306a36Sopenharmony_ci		   will do it. */
24862306a36Sopenharmony_ci		memcpy(copyto, ptr, len);
24962306a36Sopenharmony_ci		copyto += len;
25062306a36Sopenharmony_ci	} else {
25162306a36Sopenharmony_ci		/* We have to do multiple writes to get all the data
25262306a36Sopenharmony_ci		   written. */
25362306a36Sopenharmony_ci		while (soff_hi != eoff_hi) {
25462306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
25562306a36Sopenharmony_ci			printk(KERN_DEBUG "pmc551_read() soff_hi: %ld, "
25662306a36Sopenharmony_ci				"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
25762306a36Sopenharmony_ci#endif
25862306a36Sopenharmony_ci			memcpy(copyto, ptr, priv->asize);
25962306a36Sopenharmony_ci			copyto += priv->asize;
26062306a36Sopenharmony_ci			if (soff_hi + priv->asize >= mtd->size) {
26162306a36Sopenharmony_ci				goto out;
26262306a36Sopenharmony_ci			}
26362306a36Sopenharmony_ci			soff_hi += priv->asize;
26462306a36Sopenharmony_ci			pmc551_point(mtd, soff_hi, priv->asize, retlen,
26562306a36Sopenharmony_ci				     (void **)&ptr, NULL);
26662306a36Sopenharmony_ci		}
26762306a36Sopenharmony_ci		memcpy(copyto, ptr, eoff_lo);
26862306a36Sopenharmony_ci		copyto += eoff_lo;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci      out:
27262306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
27362306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_read() done\n");
27462306a36Sopenharmony_ci#endif
27562306a36Sopenharmony_ci	*retlen = copyto - buf;
27662306a36Sopenharmony_ci	return 0;
27762306a36Sopenharmony_ci}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_cistatic int pmc551_write(struct mtd_info *mtd, loff_t to, size_t len,
28062306a36Sopenharmony_ci			size_t * retlen, const u_char * buf)
28162306a36Sopenharmony_ci{
28262306a36Sopenharmony_ci	struct mypriv *priv = mtd->priv;
28362306a36Sopenharmony_ci	u32 soff_hi;		/* start address offset hi */
28462306a36Sopenharmony_ci	u32 eoff_hi, eoff_lo;	/* end address offset hi/lo */
28562306a36Sopenharmony_ci	unsigned long end;
28662306a36Sopenharmony_ci	u_char *ptr;
28762306a36Sopenharmony_ci	const u_char *copyfrom = buf;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
29062306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_write(pos:%ld, len:%ld) asize:%ld\n",
29162306a36Sopenharmony_ci		(long)to, (long)len, (long)priv->asize);
29262306a36Sopenharmony_ci#endif
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	end = to + len - 1;
29562306a36Sopenharmony_ci	soff_hi = to & ~(priv->asize - 1);
29662306a36Sopenharmony_ci	eoff_hi = end & ~(priv->asize - 1);
29762306a36Sopenharmony_ci	eoff_lo = end & (priv->asize - 1);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	pmc551_point(mtd, to, len, retlen, (void **)&ptr, NULL);
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	if (soff_hi == eoff_hi) {
30262306a36Sopenharmony_ci		/* The whole thing fits within one access, so just one shot
30362306a36Sopenharmony_ci		   will do it. */
30462306a36Sopenharmony_ci		memcpy(ptr, copyfrom, len);
30562306a36Sopenharmony_ci		copyfrom += len;
30662306a36Sopenharmony_ci	} else {
30762306a36Sopenharmony_ci		/* We have to do multiple writes to get all the data
30862306a36Sopenharmony_ci		   written. */
30962306a36Sopenharmony_ci		while (soff_hi != eoff_hi) {
31062306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
31162306a36Sopenharmony_ci			printk(KERN_DEBUG "pmc551_write() soff_hi: %ld, "
31262306a36Sopenharmony_ci				"eoff_hi: %ld\n", (long)soff_hi, (long)eoff_hi);
31362306a36Sopenharmony_ci#endif
31462306a36Sopenharmony_ci			memcpy(ptr, copyfrom, priv->asize);
31562306a36Sopenharmony_ci			copyfrom += priv->asize;
31662306a36Sopenharmony_ci			if (soff_hi >= mtd->size) {
31762306a36Sopenharmony_ci				goto out;
31862306a36Sopenharmony_ci			}
31962306a36Sopenharmony_ci			soff_hi += priv->asize;
32062306a36Sopenharmony_ci			pmc551_point(mtd, soff_hi, priv->asize, retlen,
32162306a36Sopenharmony_ci				     (void **)&ptr, NULL);
32262306a36Sopenharmony_ci		}
32362306a36Sopenharmony_ci		memcpy(ptr, copyfrom, eoff_lo);
32462306a36Sopenharmony_ci		copyfrom += eoff_lo;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci      out:
32862306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
32962306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551_write() done\n");
33062306a36Sopenharmony_ci#endif
33162306a36Sopenharmony_ci	*retlen = copyfrom - buf;
33262306a36Sopenharmony_ci	return 0;
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci/*
33662306a36Sopenharmony_ci * Fixup routines for the V370PDC
33762306a36Sopenharmony_ci * PCI device ID 0x020011b0
33862306a36Sopenharmony_ci *
33962306a36Sopenharmony_ci * This function basically kick starts the DRAM oboard the card and gets it
34062306a36Sopenharmony_ci * ready to be used.  Before this is done the device reads VERY erratic, so
34162306a36Sopenharmony_ci * much that it can crash the Linux 2.2.x series kernels when a user cat's
34262306a36Sopenharmony_ci * /proc/pci .. though that is mainly a kernel bug in handling the PCI DEVSEL
34362306a36Sopenharmony_ci * register.  FIXME: stop spinning on registers .. must implement a timeout
34462306a36Sopenharmony_ci * mechanism
34562306a36Sopenharmony_ci * returns the size of the memory region found.
34662306a36Sopenharmony_ci */
34762306a36Sopenharmony_cistatic int __init fixup_pmc551(struct pci_dev *dev)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_BUGFIX
35062306a36Sopenharmony_ci	u32 dram_data;
35162306a36Sopenharmony_ci#endif
35262306a36Sopenharmony_ci	u32 size, dcmd, cfg, dtmp;
35362306a36Sopenharmony_ci	u16 cmd, tmp, i;
35462306a36Sopenharmony_ci	u8 bcmd, counter;
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci	/* Sanity Check */
35762306a36Sopenharmony_ci	if (!dev) {
35862306a36Sopenharmony_ci		return -ENODEV;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/*
36262306a36Sopenharmony_ci	 * Attempt to reset the card
36362306a36Sopenharmony_ci	 * FIXME: Stop Spinning registers
36462306a36Sopenharmony_ci	 */
36562306a36Sopenharmony_ci	counter = 0;
36662306a36Sopenharmony_ci	/* unlock registers */
36762306a36Sopenharmony_ci	pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, 0xA5);
36862306a36Sopenharmony_ci	/* read in old data */
36962306a36Sopenharmony_ci	pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
37062306a36Sopenharmony_ci	/* bang the reset line up and down for a few */
37162306a36Sopenharmony_ci	for (i = 0; i < 10; i++) {
37262306a36Sopenharmony_ci		counter = 0;
37362306a36Sopenharmony_ci		bcmd &= ~0x80;
37462306a36Sopenharmony_ci		while (counter++ < 100) {
37562306a36Sopenharmony_ci			pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
37662306a36Sopenharmony_ci		}
37762306a36Sopenharmony_ci		counter = 0;
37862306a36Sopenharmony_ci		bcmd |= 0x80;
37962306a36Sopenharmony_ci		while (counter++ < 100) {
38062306a36Sopenharmony_ci			pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
38162306a36Sopenharmony_ci		}
38262306a36Sopenharmony_ci	}
38362306a36Sopenharmony_ci	bcmd |= (0x40 | 0x20);
38462306a36Sopenharmony_ci	pci_write_config_byte(dev, PMC551_SYS_CTRL_REG, bcmd);
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_ci	/*
38762306a36Sopenharmony_ci	 * Take care and turn off the memory on the device while we
38862306a36Sopenharmony_ci	 * tweak the configurations
38962306a36Sopenharmony_ci	 */
39062306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_COMMAND, &cmd);
39162306a36Sopenharmony_ci	tmp = cmd & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY);
39262306a36Sopenharmony_ci	pci_write_config_word(dev, PCI_COMMAND, tmp);
39362306a36Sopenharmony_ci
39462306a36Sopenharmony_ci	/*
39562306a36Sopenharmony_ci	 * Disable existing aperture before probing memory size
39662306a36Sopenharmony_ci	 */
39762306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_PCI_MEM_MAP0, &dcmd);
39862306a36Sopenharmony_ci	dtmp = (dcmd | PMC551_PCI_MEM_MAP_ENABLE | PMC551_PCI_MEM_MAP_REG_EN);
39962306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_PCI_MEM_MAP0, dtmp);
40062306a36Sopenharmony_ci	/*
40162306a36Sopenharmony_ci	 * Grab old BAR0 config so that we can figure out memory size
40262306a36Sopenharmony_ci	 * This is another bit of kludge going on.  The reason for the
40362306a36Sopenharmony_ci	 * redundancy is I am hoping to retain the original configuration
40462306a36Sopenharmony_ci	 * previously assigned to the card by the BIOS or some previous
40562306a36Sopenharmony_ci	 * fixup routine in the kernel.  So we read the old config into cfg,
40662306a36Sopenharmony_ci	 * then write all 1's to the memory space, read back the result into
40762306a36Sopenharmony_ci	 * "size", and then write back all the old config.
40862306a36Sopenharmony_ci	 */
40962306a36Sopenharmony_ci	pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &cfg);
41062306a36Sopenharmony_ci#ifndef CONFIG_MTD_PMC551_BUGFIX
41162306a36Sopenharmony_ci	pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, ~0);
41262306a36Sopenharmony_ci	pci_read_config_dword(dev, PCI_BASE_ADDRESS_0, &size);
41362306a36Sopenharmony_ci	size = (size & PCI_BASE_ADDRESS_MEM_MASK);
41462306a36Sopenharmony_ci	size &= ~(size - 1);
41562306a36Sopenharmony_ci	pci_write_config_dword(dev, PCI_BASE_ADDRESS_0, cfg);
41662306a36Sopenharmony_ci#else
41762306a36Sopenharmony_ci	/*
41862306a36Sopenharmony_ci	 * Get the size of the memory by reading all the DRAM size values
41962306a36Sopenharmony_ci	 * and adding them up.
42062306a36Sopenharmony_ci	 *
42162306a36Sopenharmony_ci	 * KLUDGE ALERT: the boards we are using have invalid column and
42262306a36Sopenharmony_ci	 * row mux values.  We fix them here, but this will break other
42362306a36Sopenharmony_ci	 * memory configurations.
42462306a36Sopenharmony_ci	 */
42562306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dram_data);
42662306a36Sopenharmony_ci	size = PMC551_DRAM_BLK_GET_SIZE(dram_data);
42762306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
42862306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
42962306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_DRAM_BLK0, dram_data);
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dram_data);
43262306a36Sopenharmony_ci	size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
43362306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
43462306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
43562306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_DRAM_BLK1, dram_data);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dram_data);
43862306a36Sopenharmony_ci	size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
43962306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
44062306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
44162306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_DRAM_BLK2, dram_data);
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dram_data);
44462306a36Sopenharmony_ci	size += PMC551_DRAM_BLK_GET_SIZE(dram_data);
44562306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_COL_MUX(dram_data, 0x5);
44662306a36Sopenharmony_ci	dram_data = PMC551_DRAM_BLK_SET_ROW_MUX(dram_data, 0x9);
44762306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_DRAM_BLK3, dram_data);
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	/*
45062306a36Sopenharmony_ci	 * Oops .. something went wrong
45162306a36Sopenharmony_ci	 */
45262306a36Sopenharmony_ci	if ((size &= PCI_BASE_ADDRESS_MEM_MASK) == 0) {
45362306a36Sopenharmony_ci		return -ENODEV;
45462306a36Sopenharmony_ci	}
45562306a36Sopenharmony_ci#endif				/* CONFIG_MTD_PMC551_BUGFIX */
45662306a36Sopenharmony_ci
45762306a36Sopenharmony_ci	if ((cfg & PCI_BASE_ADDRESS_SPACE) != PCI_BASE_ADDRESS_SPACE_MEMORY) {
45862306a36Sopenharmony_ci		return -ENODEV;
45962306a36Sopenharmony_ci	}
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	/*
46262306a36Sopenharmony_ci	 * Precharge Dram
46362306a36Sopenharmony_ci	 */
46462306a36Sopenharmony_ci	pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0400);
46562306a36Sopenharmony_ci	pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x00bf);
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	/*
46862306a36Sopenharmony_ci	 * Wait until command has gone through
46962306a36Sopenharmony_ci	 * FIXME: register spinning issue
47062306a36Sopenharmony_ci	 */
47162306a36Sopenharmony_ci	do {
47262306a36Sopenharmony_ci		pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
47362306a36Sopenharmony_ci		if (counter++ > 100)
47462306a36Sopenharmony_ci			break;
47562306a36Sopenharmony_ci	} while ((PCI_COMMAND_IO) & cmd);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	/*
47862306a36Sopenharmony_ci	 * Turn on auto refresh
47962306a36Sopenharmony_ci	 * The loop is taken directly from Ramix's example code.  I assume that
48062306a36Sopenharmony_ci	 * this must be held high for some duration of time, but I can find no
48162306a36Sopenharmony_ci	 * documentation refrencing the reasons why.
48262306a36Sopenharmony_ci	 */
48362306a36Sopenharmony_ci	for (i = 1; i <= 8; i++) {
48462306a36Sopenharmony_ci		pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0df);
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci		/*
48762306a36Sopenharmony_ci		 * Make certain command has gone through
48862306a36Sopenharmony_ci		 * FIXME: register spinning issue
48962306a36Sopenharmony_ci		 */
49062306a36Sopenharmony_ci		counter = 0;
49162306a36Sopenharmony_ci		do {
49262306a36Sopenharmony_ci			pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
49362306a36Sopenharmony_ci			if (counter++ > 100)
49462306a36Sopenharmony_ci				break;
49562306a36Sopenharmony_ci		} while ((PCI_COMMAND_IO) & cmd);
49662306a36Sopenharmony_ci	}
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	pci_write_config_word(dev, PMC551_SDRAM_MA, 0x0020);
49962306a36Sopenharmony_ci	pci_write_config_word(dev, PMC551_SDRAM_CMD, 0x0ff);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci	/*
50262306a36Sopenharmony_ci	 * Wait until command completes
50362306a36Sopenharmony_ci	 * FIXME: register spinning issue
50462306a36Sopenharmony_ci	 */
50562306a36Sopenharmony_ci	counter = 0;
50662306a36Sopenharmony_ci	do {
50762306a36Sopenharmony_ci		pci_read_config_word(dev, PMC551_SDRAM_CMD, &cmd);
50862306a36Sopenharmony_ci		if (counter++ > 100)
50962306a36Sopenharmony_ci			break;
51062306a36Sopenharmony_ci	} while ((PCI_COMMAND_IO) & cmd);
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_CFG, &dcmd);
51362306a36Sopenharmony_ci	dcmd |= 0x02000000;
51462306a36Sopenharmony_ci	pci_write_config_dword(dev, PMC551_DRAM_CFG, dcmd);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	/*
51762306a36Sopenharmony_ci	 * Check to make certain fast back-to-back, if not
51862306a36Sopenharmony_ci	 * then set it so
51962306a36Sopenharmony_ci	 */
52062306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_STATUS, &cmd);
52162306a36Sopenharmony_ci	if ((cmd & PCI_COMMAND_FAST_BACK) == 0) {
52262306a36Sopenharmony_ci		cmd |= PCI_COMMAND_FAST_BACK;
52362306a36Sopenharmony_ci		pci_write_config_word(dev, PCI_STATUS, cmd);
52462306a36Sopenharmony_ci	}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	/*
52762306a36Sopenharmony_ci	 * Check to make certain the DEVSEL is set correctly, this device
52862306a36Sopenharmony_ci	 * has a tendency to assert DEVSEL and TRDY when a write is performed
52962306a36Sopenharmony_ci	 * to the memory when memory is read-only
53062306a36Sopenharmony_ci	 */
53162306a36Sopenharmony_ci	if ((cmd & PCI_STATUS_DEVSEL_MASK) != 0x0) {
53262306a36Sopenharmony_ci		cmd &= ~PCI_STATUS_DEVSEL_MASK;
53362306a36Sopenharmony_ci		pci_write_config_word(dev, PCI_STATUS, cmd);
53462306a36Sopenharmony_ci	}
53562306a36Sopenharmony_ci	/*
53662306a36Sopenharmony_ci	 * Set to be prefetchable and put everything back based on old cfg.
53762306a36Sopenharmony_ci	 * it's possible that the reset of the V370PDC nuked the original
53862306a36Sopenharmony_ci	 * setup
53962306a36Sopenharmony_ci	 */
54062306a36Sopenharmony_ci	/*
54162306a36Sopenharmony_ci	   cfg |= PCI_BASE_ADDRESS_MEM_PREFETCH;
54262306a36Sopenharmony_ci	   pci_write_config_dword( dev, PCI_BASE_ADDRESS_0, cfg );
54362306a36Sopenharmony_ci	 */
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci	/*
54662306a36Sopenharmony_ci	 * Turn PCI memory and I/O bus access back on
54762306a36Sopenharmony_ci	 */
54862306a36Sopenharmony_ci	pci_write_config_word(dev, PCI_COMMAND,
54962306a36Sopenharmony_ci			      PCI_COMMAND_MEMORY | PCI_COMMAND_IO);
55062306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
55162306a36Sopenharmony_ci	/*
55262306a36Sopenharmony_ci	 * Some screen fun
55362306a36Sopenharmony_ci	 */
55462306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: %d%sB (0x%x) of %sprefetchable memory at "
55562306a36Sopenharmony_ci		"0x%llx\n", (size < 1024) ? size : (size < 1048576) ?
55662306a36Sopenharmony_ci		size >> 10 : size >> 20,
55762306a36Sopenharmony_ci		(size < 1024) ? "" : (size < 1048576) ? "Ki" : "Mi", size,
55862306a36Sopenharmony_ci		((dcmd & (0x1 << 3)) == 0) ? "non-" : "",
55962306a36Sopenharmony_ci		(unsigned long long)pci_resource_start(dev, 0));
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	/*
56262306a36Sopenharmony_ci	 * Check to see the state of the memory
56362306a36Sopenharmony_ci	 */
56462306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK0, &dcmd);
56562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: DRAM_BLK0 Flags: %s,%s\n"
56662306a36Sopenharmony_ci		"pmc551: DRAM_BLK0 Size: %d at %d\n"
56762306a36Sopenharmony_ci		"pmc551: DRAM_BLK0 Row MUX: %d, Col MUX: %d\n",
56862306a36Sopenharmony_ci		(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
56962306a36Sopenharmony_ci		(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
57062306a36Sopenharmony_ci		PMC551_DRAM_BLK_GET_SIZE(dcmd),
57162306a36Sopenharmony_ci		((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
57262306a36Sopenharmony_ci		((dcmd >> 9) & 0xF));
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK1, &dcmd);
57562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: DRAM_BLK1 Flags: %s,%s\n"
57662306a36Sopenharmony_ci		"pmc551: DRAM_BLK1 Size: %d at %d\n"
57762306a36Sopenharmony_ci		"pmc551: DRAM_BLK1 Row MUX: %d, Col MUX: %d\n",
57862306a36Sopenharmony_ci		(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
57962306a36Sopenharmony_ci		(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
58062306a36Sopenharmony_ci		PMC551_DRAM_BLK_GET_SIZE(dcmd),
58162306a36Sopenharmony_ci		((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
58262306a36Sopenharmony_ci		((dcmd >> 9) & 0xF));
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK2, &dcmd);
58562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: DRAM_BLK2 Flags: %s,%s\n"
58662306a36Sopenharmony_ci		"pmc551: DRAM_BLK2 Size: %d at %d\n"
58762306a36Sopenharmony_ci		"pmc551: DRAM_BLK2 Row MUX: %d, Col MUX: %d\n",
58862306a36Sopenharmony_ci		(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
58962306a36Sopenharmony_ci		(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
59062306a36Sopenharmony_ci		PMC551_DRAM_BLK_GET_SIZE(dcmd),
59162306a36Sopenharmony_ci		((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
59262306a36Sopenharmony_ci		((dcmd >> 9) & 0xF));
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_ci	pci_read_config_dword(dev, PMC551_DRAM_BLK3, &dcmd);
59562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: DRAM_BLK3 Flags: %s,%s\n"
59662306a36Sopenharmony_ci		"pmc551: DRAM_BLK3 Size: %d at %d\n"
59762306a36Sopenharmony_ci		"pmc551: DRAM_BLK3 Row MUX: %d, Col MUX: %d\n",
59862306a36Sopenharmony_ci		(((0x1 << 1) & dcmd) == 0) ? "RW" : "RO",
59962306a36Sopenharmony_ci		(((0x1 << 0) & dcmd) == 0) ? "Off" : "On",
60062306a36Sopenharmony_ci		PMC551_DRAM_BLK_GET_SIZE(dcmd),
60162306a36Sopenharmony_ci		((dcmd >> 20) & 0x7FF), ((dcmd >> 13) & 0x7),
60262306a36Sopenharmony_ci		((dcmd >> 9) & 0xF));
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_COMMAND, &cmd);
60562306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: Memory Access %s\n",
60662306a36Sopenharmony_ci		(((0x1 << 1) & cmd) == 0) ? "off" : "on");
60762306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: I/O Access %s\n",
60862306a36Sopenharmony_ci		(((0x1 << 0) & cmd) == 0) ? "off" : "on");
60962306a36Sopenharmony_ci
61062306a36Sopenharmony_ci	pci_read_config_word(dev, PCI_STATUS, &cmd);
61162306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: Devsel %s\n",
61262306a36Sopenharmony_ci		((PCI_STATUS_DEVSEL_MASK & cmd) == 0x000) ? "Fast" :
61362306a36Sopenharmony_ci		((PCI_STATUS_DEVSEL_MASK & cmd) == 0x200) ? "Medium" :
61462306a36Sopenharmony_ci		((PCI_STATUS_DEVSEL_MASK & cmd) == 0x400) ? "Slow" : "Invalid");
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: %sFast Back-to-Back\n",
61762306a36Sopenharmony_ci		((PCI_COMMAND_FAST_BACK & cmd) == 0) ? "Not " : "");
61862306a36Sopenharmony_ci
61962306a36Sopenharmony_ci	pci_read_config_byte(dev, PMC551_SYS_CTRL_REG, &bcmd);
62062306a36Sopenharmony_ci	printk(KERN_DEBUG "pmc551: EEPROM is under %s control\n"
62162306a36Sopenharmony_ci		"pmc551: System Control Register is %slocked to PCI access\n"
62262306a36Sopenharmony_ci		"pmc551: System Control Register is %slocked to EEPROM access\n",
62362306a36Sopenharmony_ci		(bcmd & 0x1) ? "software" : "hardware",
62462306a36Sopenharmony_ci		(bcmd & 0x20) ? "" : "un", (bcmd & 0x40) ? "" : "un");
62562306a36Sopenharmony_ci#endif
62662306a36Sopenharmony_ci	return size;
62762306a36Sopenharmony_ci}
62862306a36Sopenharmony_ci
62962306a36Sopenharmony_ci/*
63062306a36Sopenharmony_ci * Kernel version specific module stuffages
63162306a36Sopenharmony_ci */
63262306a36Sopenharmony_ci
63362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
63462306a36Sopenharmony_ciMODULE_AUTHOR("Mark Ferrell <mferrell@mvista.com>");
63562306a36Sopenharmony_ciMODULE_DESCRIPTION(PMC551_VERSION);
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci/*
63862306a36Sopenharmony_ci * Stuff these outside the ifdef so as to not bust compiled in driver support
63962306a36Sopenharmony_ci */
64062306a36Sopenharmony_cistatic int msize = 0;
64162306a36Sopenharmony_cistatic int asize = 0;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_cimodule_param(msize, int, 0);
64462306a36Sopenharmony_ciMODULE_PARM_DESC(msize, "memory size in MiB [1 - 1024]");
64562306a36Sopenharmony_cimodule_param(asize, int, 0);
64662306a36Sopenharmony_ciMODULE_PARM_DESC(asize, "aperture size, must be <= memsize [1-1024]");
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci/*
64962306a36Sopenharmony_ci * PMC551 Card Initialization
65062306a36Sopenharmony_ci */
65162306a36Sopenharmony_cistatic int __init init_pmc551(void)
65262306a36Sopenharmony_ci{
65362306a36Sopenharmony_ci	struct pci_dev *PCI_Device = NULL;
65462306a36Sopenharmony_ci	struct mypriv *priv;
65562306a36Sopenharmony_ci	int found = 0;
65662306a36Sopenharmony_ci	struct mtd_info *mtd;
65762306a36Sopenharmony_ci	int length = 0;
65862306a36Sopenharmony_ci
65962306a36Sopenharmony_ci	if (msize) {
66062306a36Sopenharmony_ci		msize = (1 << (ffs(msize) - 1)) << 20;
66162306a36Sopenharmony_ci		if (msize > (1 << 30)) {
66262306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Invalid memory size [%d]\n",
66362306a36Sopenharmony_ci				msize);
66462306a36Sopenharmony_ci			return -EINVAL;
66562306a36Sopenharmony_ci		}
66662306a36Sopenharmony_ci	}
66762306a36Sopenharmony_ci
66862306a36Sopenharmony_ci	if (asize) {
66962306a36Sopenharmony_ci		asize = (1 << (ffs(asize) - 1)) << 20;
67062306a36Sopenharmony_ci		if (asize > (1 << 30)) {
67162306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Invalid aperture size "
67262306a36Sopenharmony_ci				"[%d]\n", asize);
67362306a36Sopenharmony_ci			return -EINVAL;
67462306a36Sopenharmony_ci		}
67562306a36Sopenharmony_ci	}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci	printk(KERN_INFO PMC551_VERSION);
67862306a36Sopenharmony_ci
67962306a36Sopenharmony_ci	/*
68062306a36Sopenharmony_ci	 * PCU-bus chipset probe.
68162306a36Sopenharmony_ci	 */
68262306a36Sopenharmony_ci	for (;;) {
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci		if ((PCI_Device = pci_get_device(PCI_VENDOR_ID_V3_SEMI,
68562306a36Sopenharmony_ci						  PCI_DEVICE_ID_V3_SEMI_V370PDC,
68662306a36Sopenharmony_ci						  PCI_Device)) == NULL) {
68762306a36Sopenharmony_ci			break;
68862306a36Sopenharmony_ci		}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci		printk(KERN_NOTICE "pmc551: Found PCI V370PDC at 0x%llx\n",
69162306a36Sopenharmony_ci			(unsigned long long)pci_resource_start(PCI_Device, 0));
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci		/*
69462306a36Sopenharmony_ci		 * The PMC551 device acts VERY weird if you don't init it
69562306a36Sopenharmony_ci		 * first.  i.e. it will not correctly report devsel.  If for
69662306a36Sopenharmony_ci		 * some reason the sdram is in a wrote-protected state the
69762306a36Sopenharmony_ci		 * device will DEVSEL when it is written to causing problems
69862306a36Sopenharmony_ci		 * with the oldproc.c driver in
69962306a36Sopenharmony_ci		 * some kernels (2.2.*)
70062306a36Sopenharmony_ci		 */
70162306a36Sopenharmony_ci		if ((length = fixup_pmc551(PCI_Device)) <= 0) {
70262306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Cannot init SDRAM\n");
70362306a36Sopenharmony_ci			break;
70462306a36Sopenharmony_ci		}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci		/*
70762306a36Sopenharmony_ci		 * This is needed until the driver is capable of reading the
70862306a36Sopenharmony_ci		 * onboard I2C SROM to discover the "real" memory size.
70962306a36Sopenharmony_ci		 */
71062306a36Sopenharmony_ci		if (msize) {
71162306a36Sopenharmony_ci			length = msize;
71262306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Using specified memory "
71362306a36Sopenharmony_ci				"size 0x%x\n", length);
71462306a36Sopenharmony_ci		} else {
71562306a36Sopenharmony_ci			msize = length;
71662306a36Sopenharmony_ci		}
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci		mtd = kzalloc(sizeof(struct mtd_info), GFP_KERNEL);
71962306a36Sopenharmony_ci		if (!mtd)
72062306a36Sopenharmony_ci			break;
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci		priv = kzalloc(sizeof(struct mypriv), GFP_KERNEL);
72362306a36Sopenharmony_ci		if (!priv) {
72462306a36Sopenharmony_ci			kfree(mtd);
72562306a36Sopenharmony_ci			break;
72662306a36Sopenharmony_ci		}
72762306a36Sopenharmony_ci		mtd->priv = priv;
72862306a36Sopenharmony_ci		priv->dev = PCI_Device;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci		if (asize > length) {
73162306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: reducing aperture size to "
73262306a36Sopenharmony_ci				"fit %dM\n", length >> 20);
73362306a36Sopenharmony_ci			priv->asize = asize = length;
73462306a36Sopenharmony_ci		} else if (asize == 0 || asize == length) {
73562306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Using existing aperture "
73662306a36Sopenharmony_ci				"size %dM\n", length >> 20);
73762306a36Sopenharmony_ci			priv->asize = asize = length;
73862306a36Sopenharmony_ci		} else {
73962306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Using specified aperture "
74062306a36Sopenharmony_ci				"size %dM\n", asize >> 20);
74162306a36Sopenharmony_ci			priv->asize = asize;
74262306a36Sopenharmony_ci		}
74362306a36Sopenharmony_ci		priv->start = pci_iomap(PCI_Device, 0, priv->asize);
74462306a36Sopenharmony_ci
74562306a36Sopenharmony_ci		if (!priv->start) {
74662306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Unable to map IO space\n");
74762306a36Sopenharmony_ci			kfree(mtd->priv);
74862306a36Sopenharmony_ci			kfree(mtd);
74962306a36Sopenharmony_ci			break;
75062306a36Sopenharmony_ci		}
75162306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
75262306a36Sopenharmony_ci		printk(KERN_DEBUG "pmc551: setting aperture to %d\n",
75362306a36Sopenharmony_ci			ffs(priv->asize >> 20) - 1);
75462306a36Sopenharmony_ci#endif
75562306a36Sopenharmony_ci
75662306a36Sopenharmony_ci		priv->base_map0 = (PMC551_PCI_MEM_MAP_REG_EN
75762306a36Sopenharmony_ci				   | PMC551_PCI_MEM_MAP_ENABLE
75862306a36Sopenharmony_ci				   | (ffs(priv->asize >> 20) - 1) << 4);
75962306a36Sopenharmony_ci		priv->curr_map0 = priv->base_map0;
76062306a36Sopenharmony_ci		pci_write_config_dword(priv->dev, PMC551_PCI_MEM_MAP0,
76162306a36Sopenharmony_ci					priv->curr_map0);
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci#ifdef CONFIG_MTD_PMC551_DEBUG
76462306a36Sopenharmony_ci		printk(KERN_DEBUG "pmc551: aperture set to %d\n",
76562306a36Sopenharmony_ci			(priv->base_map0 & 0xF0) >> 4);
76662306a36Sopenharmony_ci#endif
76762306a36Sopenharmony_ci
76862306a36Sopenharmony_ci		mtd->size = msize;
76962306a36Sopenharmony_ci		mtd->flags = MTD_CAP_RAM;
77062306a36Sopenharmony_ci		mtd->_erase = pmc551_erase;
77162306a36Sopenharmony_ci		mtd->_read = pmc551_read;
77262306a36Sopenharmony_ci		mtd->_write = pmc551_write;
77362306a36Sopenharmony_ci		mtd->_point = pmc551_point;
77462306a36Sopenharmony_ci		mtd->_unpoint = pmc551_unpoint;
77562306a36Sopenharmony_ci		mtd->type = MTD_RAM;
77662306a36Sopenharmony_ci		mtd->name = "PMC551 RAM board";
77762306a36Sopenharmony_ci		mtd->erasesize = 0x10000;
77862306a36Sopenharmony_ci		mtd->writesize = 1;
77962306a36Sopenharmony_ci		mtd->owner = THIS_MODULE;
78062306a36Sopenharmony_ci
78162306a36Sopenharmony_ci		if (mtd_device_register(mtd, NULL, 0)) {
78262306a36Sopenharmony_ci			printk(KERN_NOTICE "pmc551: Failed to register new device\n");
78362306a36Sopenharmony_ci			pci_iounmap(PCI_Device, priv->start);
78462306a36Sopenharmony_ci			kfree(mtd->priv);
78562306a36Sopenharmony_ci			kfree(mtd);
78662306a36Sopenharmony_ci			break;
78762306a36Sopenharmony_ci		}
78862306a36Sopenharmony_ci
78962306a36Sopenharmony_ci		/* Keep a reference as the mtd_device_register worked */
79062306a36Sopenharmony_ci		pci_dev_get(PCI_Device);
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci		printk(KERN_NOTICE "Registered pmc551 memory device.\n");
79362306a36Sopenharmony_ci		printk(KERN_NOTICE "Mapped %dMiB of memory from 0x%p to 0x%p\n",
79462306a36Sopenharmony_ci			priv->asize >> 20,
79562306a36Sopenharmony_ci			priv->start, priv->start + priv->asize);
79662306a36Sopenharmony_ci		printk(KERN_NOTICE "Total memory is %d%sB\n",
79762306a36Sopenharmony_ci			(length < 1024) ? length :
79862306a36Sopenharmony_ci			(length < 1048576) ? length >> 10 : length >> 20,
79962306a36Sopenharmony_ci			(length < 1024) ? "" : (length < 1048576) ? "Ki" : "Mi");
80062306a36Sopenharmony_ci		priv->nextpmc551 = pmc551list;
80162306a36Sopenharmony_ci		pmc551list = mtd;
80262306a36Sopenharmony_ci		found++;
80362306a36Sopenharmony_ci	}
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	/* Exited early, reference left over */
80662306a36Sopenharmony_ci	pci_dev_put(PCI_Device);
80762306a36Sopenharmony_ci
80862306a36Sopenharmony_ci	if (!pmc551list) {
80962306a36Sopenharmony_ci		printk(KERN_NOTICE "pmc551: not detected\n");
81062306a36Sopenharmony_ci		return -ENODEV;
81162306a36Sopenharmony_ci	} else {
81262306a36Sopenharmony_ci		printk(KERN_NOTICE "pmc551: %d pmc551 devices loaded\n", found);
81362306a36Sopenharmony_ci		return 0;
81462306a36Sopenharmony_ci	}
81562306a36Sopenharmony_ci}
81662306a36Sopenharmony_ci
81762306a36Sopenharmony_ci/*
81862306a36Sopenharmony_ci * PMC551 Card Cleanup
81962306a36Sopenharmony_ci */
82062306a36Sopenharmony_cistatic void __exit cleanup_pmc551(void)
82162306a36Sopenharmony_ci{
82262306a36Sopenharmony_ci	int found = 0;
82362306a36Sopenharmony_ci	struct mtd_info *mtd;
82462306a36Sopenharmony_ci	struct mypriv *priv;
82562306a36Sopenharmony_ci
82662306a36Sopenharmony_ci	while ((mtd = pmc551list)) {
82762306a36Sopenharmony_ci		priv = mtd->priv;
82862306a36Sopenharmony_ci		pmc551list = priv->nextpmc551;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_ci		if (priv->start) {
83162306a36Sopenharmony_ci			printk(KERN_DEBUG "pmc551: unmapping %dMiB starting at "
83262306a36Sopenharmony_ci				"0x%p\n", priv->asize >> 20, priv->start);
83362306a36Sopenharmony_ci			pci_iounmap(priv->dev, priv->start);
83462306a36Sopenharmony_ci		}
83562306a36Sopenharmony_ci		pci_dev_put(priv->dev);
83662306a36Sopenharmony_ci
83762306a36Sopenharmony_ci		kfree(mtd->priv);
83862306a36Sopenharmony_ci		mtd_device_unregister(mtd);
83962306a36Sopenharmony_ci		kfree(mtd);
84062306a36Sopenharmony_ci		found++;
84162306a36Sopenharmony_ci	}
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	printk(KERN_NOTICE "pmc551: %d pmc551 devices unloaded\n", found);
84462306a36Sopenharmony_ci}
84562306a36Sopenharmony_ci
84662306a36Sopenharmony_cimodule_init(init_pmc551);
84762306a36Sopenharmony_cimodule_exit(cleanup_pmc551);
848