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