18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * drivers/mtd/maps/intel_vr_nor.c 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * An MTD map driver for a NOR flash bank on the Expansion Bus of the Intel 58c2ecf20Sopenharmony_ci * Vermilion Range chipset. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * The Vermilion Range Expansion Bus supports four chip selects, each of which 88c2ecf20Sopenharmony_ci * has 64MiB of address space. The 2nd BAR of the Expansion Bus PCI Device 98c2ecf20Sopenharmony_ci * is a 256MiB memory region containing the address spaces for all four of the 108c2ecf20Sopenharmony_ci * chip selects, with start addresses hardcoded on 64MiB boundaries. 118c2ecf20Sopenharmony_ci * 128c2ecf20Sopenharmony_ci * This map driver only supports NOR flash on chip select 0. The buswidth 138c2ecf20Sopenharmony_ci * (either 8 bits or 16 bits) is determined by reading the Expansion Bus Timing 148c2ecf20Sopenharmony_ci * and Control Register for Chip Select 0 (EXP_TIMING_CS0). This driver does 158c2ecf20Sopenharmony_ci * not modify the value in the EXP_TIMING_CS0 register except to enable writing 168c2ecf20Sopenharmony_ci * and disable boot acceleration. The timing parameters in the register are 178c2ecf20Sopenharmony_ci * assumed to have been properly initialized by the BIOS. The reset default 188c2ecf20Sopenharmony_ci * timing parameters are maximally conservative (slow), so access to the flash 198c2ecf20Sopenharmony_ci * will be slower than it should be if the BIOS has not initialized the timing 208c2ecf20Sopenharmony_ci * parameters. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * Author: Andy Lowe <alowe@mvista.com> 238c2ecf20Sopenharmony_ci * 248c2ecf20Sopenharmony_ci * 2006 (c) MontaVista Software, Inc. This file is licensed under 258c2ecf20Sopenharmony_ci * the terms of the GNU General Public License version 2. This program 268c2ecf20Sopenharmony_ci * is licensed "as is" without any warranty of any kind, whether express 278c2ecf20Sopenharmony_ci * or implied. 288c2ecf20Sopenharmony_ci */ 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci#include <linux/module.h> 318c2ecf20Sopenharmony_ci#include <linux/kernel.h> 328c2ecf20Sopenharmony_ci#include <linux/slab.h> 338c2ecf20Sopenharmony_ci#include <linux/pci.h> 348c2ecf20Sopenharmony_ci#include <linux/mtd/mtd.h> 358c2ecf20Sopenharmony_ci#include <linux/mtd/map.h> 368c2ecf20Sopenharmony_ci#include <linux/mtd/partitions.h> 378c2ecf20Sopenharmony_ci#include <linux/mtd/cfi.h> 388c2ecf20Sopenharmony_ci#include <linux/mtd/flashchip.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#define DRV_NAME "vr_nor" 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistruct vr_nor_mtd { 438c2ecf20Sopenharmony_ci void __iomem *csr_base; 448c2ecf20Sopenharmony_ci struct map_info map; 458c2ecf20Sopenharmony_ci struct mtd_info *info; 468c2ecf20Sopenharmony_ci struct pci_dev *dev; 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci/* Expansion Bus Configuration and Status Registers are in BAR 0 */ 508c2ecf20Sopenharmony_ci#define EXP_CSR_MBAR 0 518c2ecf20Sopenharmony_ci/* Expansion Bus Memory Window is BAR 1 */ 528c2ecf20Sopenharmony_ci#define EXP_WIN_MBAR 1 538c2ecf20Sopenharmony_ci/* Maximum address space for Chip Select 0 is 64MiB */ 548c2ecf20Sopenharmony_ci#define CS0_SIZE 0x04000000 558c2ecf20Sopenharmony_ci/* Chip Select 0 is at offset 0 in the Memory Window */ 568c2ecf20Sopenharmony_ci#define CS0_START 0x0 578c2ecf20Sopenharmony_ci/* Chip Select 0 Timing Register is at offset 0 in CSR */ 588c2ecf20Sopenharmony_ci#define EXP_TIMING_CS0 0x00 598c2ecf20Sopenharmony_ci#define TIMING_CS_EN (1 << 31) /* Chip Select Enable */ 608c2ecf20Sopenharmony_ci#define TIMING_BOOT_ACCEL_DIS (1 << 8) /* Boot Acceleration Disable */ 618c2ecf20Sopenharmony_ci#define TIMING_WR_EN (1 << 1) /* Write Enable */ 628c2ecf20Sopenharmony_ci#define TIMING_BYTE_EN (1 << 0) /* 8-bit vs 16-bit bus */ 638c2ecf20Sopenharmony_ci#define TIMING_MASK 0x3FFF0000 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic void vr_nor_destroy_partitions(struct vr_nor_mtd *p) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci mtd_device_unregister(p->info); 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic int vr_nor_init_partitions(struct vr_nor_mtd *p) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci /* register the flash bank */ 738c2ecf20Sopenharmony_ci /* partition the flash bank */ 748c2ecf20Sopenharmony_ci return mtd_device_register(p->info, NULL, 0); 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic void vr_nor_destroy_mtd_setup(struct vr_nor_mtd *p) 788c2ecf20Sopenharmony_ci{ 798c2ecf20Sopenharmony_ci map_destroy(p->info); 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic int vr_nor_mtd_setup(struct vr_nor_mtd *p) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci static const char * const probe_types[] = 858c2ecf20Sopenharmony_ci { "cfi_probe", "jedec_probe", NULL }; 868c2ecf20Sopenharmony_ci const char * const *type; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci for (type = probe_types; !p->info && *type; type++) 898c2ecf20Sopenharmony_ci p->info = do_map_probe(*type, &p->map); 908c2ecf20Sopenharmony_ci if (!p->info) 918c2ecf20Sopenharmony_ci return -ENODEV; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci p->info->dev.parent = &p->dev->dev; 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci return 0; 968c2ecf20Sopenharmony_ci} 978c2ecf20Sopenharmony_ci 988c2ecf20Sopenharmony_cistatic void vr_nor_destroy_maps(struct vr_nor_mtd *p) 998c2ecf20Sopenharmony_ci{ 1008c2ecf20Sopenharmony_ci unsigned int exp_timing_cs0; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* write-protect the flash bank */ 1038c2ecf20Sopenharmony_ci exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); 1048c2ecf20Sopenharmony_ci exp_timing_cs0 &= ~TIMING_WR_EN; 1058c2ecf20Sopenharmony_ci writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci /* unmap the flash window */ 1088c2ecf20Sopenharmony_ci iounmap(p->map.virt); 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci /* unmap the csr window */ 1118c2ecf20Sopenharmony_ci iounmap(p->csr_base); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci/* 1158c2ecf20Sopenharmony_ci * Initialize the map_info structure and map the flash. 1168c2ecf20Sopenharmony_ci * Returns 0 on success, nonzero otherwise. 1178c2ecf20Sopenharmony_ci */ 1188c2ecf20Sopenharmony_cistatic int vr_nor_init_maps(struct vr_nor_mtd *p) 1198c2ecf20Sopenharmony_ci{ 1208c2ecf20Sopenharmony_ci unsigned long csr_phys, csr_len; 1218c2ecf20Sopenharmony_ci unsigned long win_phys, win_len; 1228c2ecf20Sopenharmony_ci unsigned int exp_timing_cs0; 1238c2ecf20Sopenharmony_ci int err; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci csr_phys = pci_resource_start(p->dev, EXP_CSR_MBAR); 1268c2ecf20Sopenharmony_ci csr_len = pci_resource_len(p->dev, EXP_CSR_MBAR); 1278c2ecf20Sopenharmony_ci win_phys = pci_resource_start(p->dev, EXP_WIN_MBAR); 1288c2ecf20Sopenharmony_ci win_len = pci_resource_len(p->dev, EXP_WIN_MBAR); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (!csr_phys || !csr_len || !win_phys || !win_len) 1318c2ecf20Sopenharmony_ci return -ENODEV; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci if (win_len < (CS0_START + CS0_SIZE)) 1348c2ecf20Sopenharmony_ci return -ENXIO; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci p->csr_base = ioremap(csr_phys, csr_len); 1378c2ecf20Sopenharmony_ci if (!p->csr_base) 1388c2ecf20Sopenharmony_ci return -ENOMEM; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); 1418c2ecf20Sopenharmony_ci if (!(exp_timing_cs0 & TIMING_CS_EN)) { 1428c2ecf20Sopenharmony_ci dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 " 1438c2ecf20Sopenharmony_ci "is disabled.\n"); 1448c2ecf20Sopenharmony_ci err = -ENODEV; 1458c2ecf20Sopenharmony_ci goto release; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci if ((exp_timing_cs0 & TIMING_MASK) == TIMING_MASK) { 1488c2ecf20Sopenharmony_ci dev_warn(&p->dev->dev, "Expansion Bus Chip Select 0 " 1498c2ecf20Sopenharmony_ci "is configured for maximally slow access times.\n"); 1508c2ecf20Sopenharmony_ci } 1518c2ecf20Sopenharmony_ci p->map.name = DRV_NAME; 1528c2ecf20Sopenharmony_ci p->map.bankwidth = (exp_timing_cs0 & TIMING_BYTE_EN) ? 1 : 2; 1538c2ecf20Sopenharmony_ci p->map.phys = win_phys + CS0_START; 1548c2ecf20Sopenharmony_ci p->map.size = CS0_SIZE; 1558c2ecf20Sopenharmony_ci p->map.virt = ioremap(p->map.phys, p->map.size); 1568c2ecf20Sopenharmony_ci if (!p->map.virt) { 1578c2ecf20Sopenharmony_ci err = -ENOMEM; 1588c2ecf20Sopenharmony_ci goto release; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci simple_map_init(&p->map); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci /* Enable writes to flash bank */ 1638c2ecf20Sopenharmony_ci exp_timing_cs0 |= TIMING_BOOT_ACCEL_DIS | TIMING_WR_EN; 1648c2ecf20Sopenharmony_ci writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci return 0; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci release: 1698c2ecf20Sopenharmony_ci iounmap(p->csr_base); 1708c2ecf20Sopenharmony_ci return err; 1718c2ecf20Sopenharmony_ci} 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_cistatic const struct pci_device_id vr_nor_pci_ids[] = { 1748c2ecf20Sopenharmony_ci {PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x500D)}, 1758c2ecf20Sopenharmony_ci {0,} 1768c2ecf20Sopenharmony_ci}; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_cistatic void vr_nor_pci_remove(struct pci_dev *dev) 1798c2ecf20Sopenharmony_ci{ 1808c2ecf20Sopenharmony_ci struct vr_nor_mtd *p = pci_get_drvdata(dev); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci vr_nor_destroy_partitions(p); 1838c2ecf20Sopenharmony_ci vr_nor_destroy_mtd_setup(p); 1848c2ecf20Sopenharmony_ci vr_nor_destroy_maps(p); 1858c2ecf20Sopenharmony_ci kfree(p); 1868c2ecf20Sopenharmony_ci pci_release_regions(dev); 1878c2ecf20Sopenharmony_ci pci_disable_device(dev); 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic int vr_nor_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci struct vr_nor_mtd *p = NULL; 1938c2ecf20Sopenharmony_ci unsigned int exp_timing_cs0; 1948c2ecf20Sopenharmony_ci int err; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci err = pci_enable_device(dev); 1978c2ecf20Sopenharmony_ci if (err) 1988c2ecf20Sopenharmony_ci goto out; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci err = pci_request_regions(dev, DRV_NAME); 2018c2ecf20Sopenharmony_ci if (err) 2028c2ecf20Sopenharmony_ci goto disable_dev; 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci p = kzalloc(sizeof(*p), GFP_KERNEL); 2058c2ecf20Sopenharmony_ci err = -ENOMEM; 2068c2ecf20Sopenharmony_ci if (!p) 2078c2ecf20Sopenharmony_ci goto release; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci p->dev = dev; 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci err = vr_nor_init_maps(p); 2128c2ecf20Sopenharmony_ci if (err) 2138c2ecf20Sopenharmony_ci goto release; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci err = vr_nor_mtd_setup(p); 2168c2ecf20Sopenharmony_ci if (err) 2178c2ecf20Sopenharmony_ci goto destroy_maps; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_ci err = vr_nor_init_partitions(p); 2208c2ecf20Sopenharmony_ci if (err) 2218c2ecf20Sopenharmony_ci goto destroy_mtd_setup; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci pci_set_drvdata(dev, p); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci return 0; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci destroy_mtd_setup: 2288c2ecf20Sopenharmony_ci map_destroy(p->info); 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci destroy_maps: 2318c2ecf20Sopenharmony_ci /* write-protect the flash bank */ 2328c2ecf20Sopenharmony_ci exp_timing_cs0 = readl(p->csr_base + EXP_TIMING_CS0); 2338c2ecf20Sopenharmony_ci exp_timing_cs0 &= ~TIMING_WR_EN; 2348c2ecf20Sopenharmony_ci writel(exp_timing_cs0, p->csr_base + EXP_TIMING_CS0); 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_ci /* unmap the flash window */ 2378c2ecf20Sopenharmony_ci iounmap(p->map.virt); 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci /* unmap the csr window */ 2408c2ecf20Sopenharmony_ci iounmap(p->csr_base); 2418c2ecf20Sopenharmony_ci 2428c2ecf20Sopenharmony_ci release: 2438c2ecf20Sopenharmony_ci kfree(p); 2448c2ecf20Sopenharmony_ci pci_release_regions(dev); 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci disable_dev: 2478c2ecf20Sopenharmony_ci pci_disable_device(dev); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci out: 2508c2ecf20Sopenharmony_ci return err; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic struct pci_driver vr_nor_pci_driver = { 2548c2ecf20Sopenharmony_ci .name = DRV_NAME, 2558c2ecf20Sopenharmony_ci .probe = vr_nor_pci_probe, 2568c2ecf20Sopenharmony_ci .remove = vr_nor_pci_remove, 2578c2ecf20Sopenharmony_ci .id_table = vr_nor_pci_ids, 2588c2ecf20Sopenharmony_ci}; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_cimodule_pci_driver(vr_nor_pci_driver); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ciMODULE_AUTHOR("Andy Lowe"); 2638c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MTD map driver for NOR flash on Intel Vermilion Range"); 2648c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2658c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pci, vr_nor_pci_ids); 266